// Copyright (c) 2007-2009 Fredrik Mellbin // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #include "utils.h" #include "indexing.h" #include #include #include #include #ifdef _MSC_VER # include #endif // Export the array but not its data type... fun... typedef struct CodecTags{ char str[20]; enum CodecID id; } CodecTags; extern "C" { extern const AVCodecTag codec_bmp_tags[]; extern const CodecTags ff_mkv_codec_tags[]; extern const AVCodecTag codec_movvideo_tags[]; extern const AVCodecTag codec_wav_tags[]; } #ifdef __UNIX__ #define _fseeki64 fseeko #define _ftelli64 ftello #define _snprintf snprintf #endif TFrameInfo::TFrameInfo(int64_t DTS, bool KeyFrame) { this->DTS = DTS; this->KeyFrame = KeyFrame; this->SampleStart = 0; this->FilePos = 0; this->FrameSize = 0; } TFrameInfo::TFrameInfo(int64_t DTS, int64_t FilePos, unsigned int FrameSize, bool KeyFrame) { this->DTS = DTS; this->KeyFrame = KeyFrame; this->SampleStart = 0; this->FilePos = FilePos; this->FrameSize = FrameSize; } TFrameInfo::TFrameInfo(int64_t DTS, int64_t SampleStart, bool KeyFrame) { this->DTS = DTS; this->KeyFrame = KeyFrame; this->SampleStart = SampleStart; this->FilePos = 0; this->FrameSize = 0; } TFrameInfo::TFrameInfo(int64_t DTS, int64_t SampleStart, int64_t FilePos, unsigned int FrameSize, bool KeyFrame) { this->DTS = DTS; this->KeyFrame = KeyFrame; this->SampleStart = SampleStart; this->FilePos = FilePos; this->FrameSize = FrameSize; } int FFTrack::WriteTimecodes(const char *TimecodeFile, char *ErrorMsg, unsigned MsgSize) { std::ofstream Timecodes(TimecodeFile, std::ios::out | std::ios::trunc); if (!Timecodes.is_open()) { _snprintf(ErrorMsg, MsgSize, "Failed to open '%s' for writing", TimecodeFile); return 1; } Timecodes << "# timecode format v2\n"; for (iterator Cur=begin(); Cur!=end(); Cur++) Timecodes << std::fixed << ((Cur->DTS * TB.Num) / (double)TB.Den) << "\n"; return 0; } int FFTrack::FrameFromDTS(int64_t DTS) { for (int i = 0; i < static_cast(size()); i++) if (at(i).DTS == DTS) return i; return -1; } int FFTrack::ClosestFrameFromDTS(int64_t DTS) { int Frame = 0; int64_t BestDiff = 0xFFFFFFFFFFFFFFLL; // big number for (int i = 0; i < static_cast(size()); i++) { int64_t CurrentDiff = FFABS(at(i).DTS - DTS); if (CurrentDiff < BestDiff) { BestDiff = CurrentDiff; Frame = i; } } return Frame; } int FFTrack::FindClosestVideoKeyFrame(int Frame) { Frame = FFMIN(FFMAX(Frame, 0), static_cast(size()) - 1); for (int i = Frame; i > 0; i--) if (at(i).KeyFrame) return i; return 0; } int FFTrack::FindClosestAudioKeyFrame(int64_t Sample) { for (size_t i = 0; i < size(); i++) { if (at(i).SampleStart == Sample && at(i).KeyFrame) return i; else if (at(i).SampleStart > Sample && at(i).KeyFrame) return i - 1; } return size() - 1; } FFTrack::FFTrack() { this->TT = FFMS_TYPE_UNKNOWN; this->TB.Num = 0; this->TB.Den = 0; } FFTrack::FFTrack(int64_t Num, int64_t Den, FFMS_TrackType TT) { this->TT = TT; this->TB.Num = Num; this->TB.Den = Den; } int FFIndex::WriteIndex(const char *IndexFile, char *ErrorMsg, unsigned MsgSize) { std::ofstream IndexStream(IndexFile, std::ios::out | std::ios::binary | std::ios::trunc); if (!IndexStream.is_open()) { _snprintf(ErrorMsg, MsgSize, "Failed to open '%s' for writing", IndexFile); return 1; } // Write the index file header IndexHeader IH; IH.Id = INDEXID; IH.Version = INDEXVERSION; IH.Tracks = size(); IH.Decoder = Decoder; IH.LAVUVersion = LIBAVUTIL_VERSION_INT; IH.LAVFVersion = LIBAVFORMAT_VERSION_INT; IH.LAVCVersion = LIBAVCODEC_VERSION_INT; IH.LSWSVersion = LIBSWSCALE_VERSION_INT; IH.LPPVersion = LIBPOSTPROC_VERSION_INT; IndexStream.write(reinterpret_cast(&IH), sizeof(IH)); for (unsigned int i = 0; i < IH.Tracks; i++) { FFMS_TrackType TT = at(i).TT; IndexStream.write(reinterpret_cast(&TT), sizeof(TT)); int64_t Num = at(i).TB.Num; IndexStream.write(reinterpret_cast(&Num), sizeof(Num)); int64_t Den = at(i).TB.Den; IndexStream.write(reinterpret_cast(&Den), sizeof(Den)); int64_t Frames = at(i).size(); IndexStream.write(reinterpret_cast(&Frames), sizeof(Frames)); for (FFTrack::iterator Cur=at(i).begin(); Cur!=at(i).end(); Cur++) IndexStream.write(reinterpret_cast(&*Cur), sizeof(TFrameInfo)); } return 0; } int FFIndex::ReadIndex(const char *IndexFile, char *ErrorMsg, unsigned MsgSize) { std::ifstream Index(IndexFile, std::ios::in | std::ios::binary); if (!Index.is_open()) { _snprintf(ErrorMsg, MsgSize, "Failed to open '%s' for reading", IndexFile); return 1; } // Read the index file header IndexHeader IH; Index.read(reinterpret_cast(&IH), sizeof(IH)); if (IH.Id != INDEXID) { _snprintf(ErrorMsg, MsgSize, "'%s' is not a valid index file", IndexFile); return 2; } if (IH.Version != INDEXVERSION) { _snprintf(ErrorMsg, MsgSize, "'%s' is not the expected index version", IndexFile); return 3; } if (IH.LAVUVersion != LIBAVUTIL_VERSION_INT || IH.LAVFVersion != LIBAVFORMAT_VERSION_INT || IH.LAVCVersion != LIBAVCODEC_VERSION_INT || IH.LSWSVersion != LIBSWSCALE_VERSION_INT || IH.LPPVersion != LIBPOSTPROC_VERSION_INT) { _snprintf(ErrorMsg, MsgSize, "A different FFmpeg build was used to create '%s'", IndexFile); return 4; } try { Decoder = IH.Decoder; for (unsigned int i = 0; i < IH.Tracks; i++) { // Read how many records belong to the current stream FFMS_TrackType TT; Index.read(reinterpret_cast(&TT), sizeof(TT)); int64_t Num; Index.read(reinterpret_cast(&Num), sizeof(Num)); int64_t Den; Index.read(reinterpret_cast(&Den), sizeof(Den)); int64_t Frames; Index.read(reinterpret_cast(&Frames), sizeof(Frames)); push_back(FFTrack(Num, Den, TT)); TFrameInfo FI(0, false); for (size_t j = 0; j < Frames; j++) { Index.read(reinterpret_cast(&FI), sizeof(TFrameInfo)); at(i).push_back(FI); } } } catch (...) { _snprintf(ErrorMsg, MsgSize, "Unknown error while reading index information in '%s'", IndexFile); return 5; } return 0; } int GetCPUFlags() { // FIXME Add proper feature detection when msvc isn't used int Flags = PP_CPU_CAPS_MMX | PP_CPU_CAPS_MMX2; #ifdef _MSC_VER Flags = 0; int CPUInfo[4]; __cpuid(CPUInfo, 0); // PP and SWS defines have the same values for their defines so this actually works if (CPUInfo[3] & (1 << 23)) Flags |= PP_CPU_CAPS_MMX; if (CPUInfo[3] & (1 << 25)) Flags |= PP_CPU_CAPS_MMX2; #endif return Flags; } FFMS_TrackType HaaliTrackTypeToFFTrackType(int TT) { switch (TT) { case TT_VIDEO: return FFMS_TYPE_VIDEO; break; case TT_AUDIO: return FFMS_TYPE_AUDIO; break; case TT_SUB: return FFMS_TYPE_SUBTITLE; break; default: return FFMS_TYPE_UNKNOWN; } } int ReadFrame(uint64_t FilePos, unsigned int &FrameSize, CompressedStream *CS, MatroskaReaderContext &Context, char *ErrorMsg, unsigned MsgSize) { if (CS) { char CSBuffer[4096]; unsigned int DecompressedFrameSize = 0; cs_NextFrame(CS, FilePos, FrameSize); for (;;) { int ReadBytes = cs_ReadData(CS, CSBuffer, sizeof(CSBuffer)); if (ReadBytes < 0) { _snprintf(ErrorMsg, MsgSize, "Error decompressing data: %s", cs_GetLastError(CS)); return 1; } if (ReadBytes == 0) { FrameSize = DecompressedFrameSize; return 0; } if (Context.BufferSize < DecompressedFrameSize + ReadBytes) { Context.BufferSize = FrameSize; Context.Buffer = (uint8_t *)realloc(Context.Buffer, Context.BufferSize + 16); if (Context.Buffer == NULL) { _snprintf(ErrorMsg, MsgSize, "Out of memory"); return 2; } } memcpy(Context.Buffer + DecompressedFrameSize, CSBuffer, ReadBytes); DecompressedFrameSize += ReadBytes; } } else { if (_fseeki64(Context.ST.fp, FilePos, SEEK_SET)) { _snprintf(ErrorMsg, MsgSize, "fseek(): %s", strerror(errno)); return 3; } if (Context.BufferSize < FrameSize) { Context.BufferSize = FrameSize; Context.Buffer = (uint8_t *)realloc(Context.Buffer, Context.BufferSize + 16); if (Context.Buffer == NULL) { _snprintf(ErrorMsg, MsgSize, "Out of memory"); return 4; } } size_t ReadBytes = fread(Context.Buffer, 1, FrameSize, Context.ST.fp); if (ReadBytes != FrameSize) { if (ReadBytes == 0) { if (feof(Context.ST.fp)) { _snprintf(ErrorMsg, MsgSize, "Unexpected EOF while reading frame"); return 5; } else { _snprintf(ErrorMsg, MsgSize, "Error reading frame: %s", strerror(errno)); return 6; } } else { _snprintf(ErrorMsg, MsgSize, "Short read while reading frame"); return 7; } _snprintf(ErrorMsg, MsgSize, "Unknown read error"); return 8; } return 0; } } bool AudioFMTIsFloat(SampleFormat FMT){ switch (FMT) { case SAMPLE_FMT_FLT: case SAMPLE_FMT_DBL: return true; default: return false; } } void InitNullPacket(AVPacket *pkt) { av_init_packet(pkt); pkt->data = NULL; pkt->size = 0; } void FillAP(TAudioProperties &AP, AVCodecContext *CTX, FFTrack &Frames) { AP.BitsPerSample = av_get_bits_per_sample_format(CTX->sample_fmt); AP.Channels = CTX->channels;; AP.Float = AudioFMTIsFloat(CTX->sample_fmt); AP.SampleRate = CTX->sample_rate; AP.NumSamples = (Frames.back()).SampleStart; AP.FirstTime = ((Frames.front().DTS * Frames.TB.Num) / (double)Frames.TB.Den) / 1000; AP.LastTime = ((Frames.back().DTS * Frames.TB.Num) / (double)Frames.TB.Den) / 1000; } #ifdef HAALISOURCE unsigned vtSize(VARIANT &vt) { if (V_VT(&vt) != (VT_ARRAY | VT_UI1)) return 0; long lb,ub; if (FAILED(SafeArrayGetLBound(V_ARRAY(&vt),1,&lb)) || FAILED(SafeArrayGetUBound(V_ARRAY(&vt),1,&ub))) return 0; return ub - lb + 1; } void vtCopy(VARIANT& vt,void *dest) { unsigned sz = vtSize(vt); if (sz > 0) { void *vp; if (SUCCEEDED(SafeArrayAccessData(V_ARRAY(&vt),&vp))) { memcpy(dest,vp,sz); SafeArrayUnaccessData(V_ARRAY(&vt)); } } } #else // used for matroska<->ffmpeg codec ID mapping to avoid Win32 dependency typedef struct BITMAPINFOHEADER { uint32_t biSize; int32_t biWidth; int32_t biHeight; uint16_t biPlanes; uint16_t biBitCount; uint32_t biCompression; uint32_t biSizeImage; int32_t biXPelsPerMeter; int32_t biYPelsPerMeter; uint32_t biClrUsed; uint32_t biClrImportant; } BITMAPINFOHEADER; #define MAKEFOURCC(ch0, ch1, ch2, ch3)\ ((uint32_t)(uint8_t)(ch0) | ((uint32_t)(uint8_t)(ch1) << 8) |\ ((uint32_t)(uint8_t)(ch2) << 16) | ((uint32_t)(uint8_t)(ch3) << 24 )) #endif CodecID MatroskaToFFCodecID(char *Codec, void *CodecPrivate) { /* Look up native codecs */ for(int i = 0; ff_mkv_codec_tags[i].id != CODEC_ID_NONE; i++){ if(!strncmp(ff_mkv_codec_tags[i].str, Codec, strlen(ff_mkv_codec_tags[i].str))){ return ff_mkv_codec_tags[i].id; } } /* Video codecs for "avi in mkv" mode */ const AVCodecTag *const tags[] = { codec_bmp_tags, 0 }; if (!strcmp(Codec, "V_MS/VFW/FOURCC")) return av_codec_get_id(tags, ((BITMAPINFOHEADER *)CodecPrivate)->biCompression); // FIXME /* Audio codecs for "avi in mkv" mode */ //#include "Mmreg.h" //((WAVEFORMATEX *)TI->CodecPrivate)->wFormatTag /* Fixup for uncompressed video formats */ /* Fixup for uncompressed audio formats */ return CODEC_ID_NONE; }