From 2d154902333ae94c5e21ba79a71c67b72e518638 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Mon, 21 Feb 2011 21:16:05 +0000 Subject: [PATCH] Update libffms to 2.15 Originally committed to SVN as r5348. --- aegisub/libffms/include/ffms.h | 99 ++-- aegisub/libffms/include/ffmscompat.h | 28 +- aegisub/libffms/src/core/audiosource.cpp | 354 ++++++++--- aegisub/libffms/src/core/audiosource.h | 136 +++-- aegisub/libffms/src/core/ffms.cpp | 98 ++- aegisub/libffms/src/core/indexing.cpp | 591 ++++++++++++------- aegisub/libffms/src/core/indexing.h | 37 +- aegisub/libffms/src/core/lavfaudio.cpp | 212 ++----- aegisub/libffms/src/core/lavfindexer.cpp | 124 ++-- aegisub/libffms/src/core/lavfvideo.cpp | 69 ++- aegisub/libffms/src/core/matroskaaudio.cpp | 194 ++---- aegisub/libffms/src/core/matroskaindexer.cpp | 157 ++--- aegisub/libffms/src/core/matroskaparser.c | 174 ++++-- aegisub/libffms/src/core/matroskaparser.h | 50 +- aegisub/libffms/src/core/matroskavideo.cpp | 71 ++- aegisub/libffms/src/core/stdiostream.h | 2 + aegisub/libffms/src/core/utils.cpp | 491 +++++++++++---- aegisub/libffms/src/core/utils.h | 117 +++- aegisub/libffms/src/core/videosource.cpp | 66 ++- aegisub/libffms/src/core/videosource.h | 16 +- aegisub/src/audio_provider_ffmpegsource.cpp | 8 + aegisub/src/video_provider_ffmpegsource.cpp | 4 + 22 files changed, 1901 insertions(+), 1197 deletions(-) diff --git a/aegisub/libffms/include/ffms.h b/aegisub/libffms/include/ffms.h index 0168497e4..931f7fcad 100644 --- a/aegisub/libffms/include/ffms.h +++ b/aegisub/libffms/include/ffms.h @@ -22,28 +22,32 @@ #define FFMS_H // Version format: major - minor - micro - bump -#define FFMS_VERSION ((2 << 24) | (13 << 16)| (0 << 8) | 1) +#define FFMS_VERSION ((2 << 24) | (14 << 16) | (2 << 8) | 0) #include #ifdef __cplusplus -# define EXTERN_C extern "C" +# define FFMS_EXTERN_C extern "C" # define FFMS_CLASS_TYPE class #else -# define EXTERN_C +# define FFMS_EXTERN_C # define FFMS_CLASS_TYPE struct #endif #ifdef _WIN32 # define FFMS_CC __stdcall -# ifdef FFMS_EXPORTS -# define FFMS_API(ret) EXTERN_C __declspec(dllexport) ret FFMS_CC +# ifdef _MSC_VER +# ifdef FFMS_EXPORTS +# define FFMS_API(ret) FFMS_EXTERN_C __declspec(dllexport) ret FFMS_CC +# else +# define FFMS_API(ret) FFMS_EXTERN_C __declspec(dllimport) ret FFMS_CC +# endif # else -# define FFMS_API(ret) EXTERN_C __declspec(dllimport) ret FFMS_CC +# define FFMS_API(ret) FFMS_EXTERN_C ret FFMS_CC # endif #else # define FFMS_CC -# define FFMS_API(ret) EXTERN_C ret FFMS_CC +# define FFMS_API(ret) FFMS_EXTERN_C ret FFMS_CC #endif typedef struct { @@ -64,33 +68,34 @@ enum FFMS_Errors { FFMS_ERROR_SUCCESS = 0, // Main types - where the error occurred - FFMS_ERROR_INDEX = 1, - FFMS_ERROR_INDEXING, - FFMS_ERROR_POSTPROCESSING, - FFMS_ERROR_SCALING, - FFMS_ERROR_DECODING, - FFMS_ERROR_SEEKING, - FFMS_ERROR_PARSER, - FFMS_ERROR_TRACK, - FFMS_ERROR_WAVE_WRITER, - FFMS_ERROR_CANCELLED, + FFMS_ERROR_INDEX = 1, // index file handling + FFMS_ERROR_INDEXING, // indexing + FFMS_ERROR_POSTPROCESSING, // video postprocessing (libpostproc) + FFMS_ERROR_SCALING, // image scaling (libswscale) + FFMS_ERROR_DECODING, // audio/video decoding + FFMS_ERROR_SEEKING, // seeking + FFMS_ERROR_PARSER, // file parsing + FFMS_ERROR_TRACK, // track handling + FFMS_ERROR_WAVE_WRITER, // WAVE64 file writer + FFMS_ERROR_CANCELLED, // operation aborted // Subtypes - what caused the error - FFMS_ERROR_UNKNOWN = 20, - FFMS_ERROR_UNSUPPORTED, - FFMS_ERROR_FILE_READ, - FFMS_ERROR_FILE_WRITE, - FFMS_ERROR_NO_FILE, - FFMS_ERROR_VERSION, - FFMS_ERROR_ALLOCATION_FAILED, - FFMS_ERROR_INVALID_ARGUMENT, - FFMS_ERROR_CODEC, - FFMS_ERROR_NOT_AVAILABLE, - FFMS_ERROR_FILE_MISMATCH, - FFMS_ERROR_USER + FFMS_ERROR_UNKNOWN = 20, // unknown error + FFMS_ERROR_UNSUPPORTED, // format or operation is not supported with this binary + FFMS_ERROR_FILE_READ, // cannot read from file + FFMS_ERROR_FILE_WRITE, // cannot write to file + FFMS_ERROR_NO_FILE, // no such file or directory + FFMS_ERROR_VERSION, // wrong version + FFMS_ERROR_ALLOCATION_FAILED, // out of memory + FFMS_ERROR_INVALID_ARGUMENT, // invalid or nonsensical argument + FFMS_ERROR_CODEC, // decoder error + FFMS_ERROR_NOT_AVAILABLE, // requested mode or operation unavailable in this binary + FFMS_ERROR_FILE_MISMATCH, // provided index does not match the file + FFMS_ERROR_USER // problem exists between keyboard and chair }; enum FFMS_Sources { + FFMS_SOURCE_DEFAULT = 0x00, FFMS_SOURCE_LAVF = 0x01, FFMS_SOURCE_MATROSKA = 0x02, FFMS_SOURCE_HAALIMPEG = 0x04, @@ -102,7 +107,8 @@ enum FFMS_CPUFeatures { FFMS_CPU_CAPS_MMX2 = 0x02, FFMS_CPU_CAPS_3DNOW = 0x04, FFMS_CPU_CAPS_ALTIVEC = 0x08, - FFMS_CPU_CAPS_BFIN = 0x10 + FFMS_CPU_CAPS_BFIN = 0x10, + FFMS_CPU_CAPS_SSE2 = 0x20 }; enum FFMS_SeekMode { @@ -123,18 +129,18 @@ enum FFMS_IndexErrorHandling { enum FFMS_TrackType { FFMS_TYPE_UNKNOWN = -1, FFMS_TYPE_VIDEO, - FFMS_TYPE_AUDIO, - FFMS_TYPE_DATA, - FFMS_TYPE_SUBTITLE, - FFMS_TYPE_ATTACHMENT + FFMS_TYPE_AUDIO, + FFMS_TYPE_DATA, + FFMS_TYPE_SUBTITLE, + FFMS_TYPE_ATTACHMENT }; enum FFMS_SampleFormat { - FFMS_FMT_U8 = 0, - FFMS_FMT_S16, - FFMS_FMT_S32, - FFMS_FMT_FLT, - FFMS_FMT_DBL + FFMS_FMT_U8 = 0, + FFMS_FMT_S16, + FFMS_FMT_S32, + FFMS_FMT_FLT, + FFMS_FMT_DBL }; enum FFMS_AudioChannel { @@ -174,6 +180,12 @@ enum FFMS_Resizers { FFMS_RESIZER_SPLINE = 0x0400 }; +enum FFMS_AudioDelayModes { + FFMS_DELAY_NO_SHIFT = -3, + FFMS_DELAY_TIME_ZERO = -2, + FFMS_DELAY_FIRST_VIDEO_TRACK = -1 +}; + typedef struct { uint8_t *Data[4]; int Linesize[4]; @@ -236,11 +248,11 @@ typedef int (FFMS_CC *TAudioNameCallback)(const char *SourceFile, int Track, con // Most functions return 0 on success // Functions without error message output can be assumed to never fail in a graceful way -FFMS_API(void) FFMS_Init(int CPUFeatures); +FFMS_API(void) FFMS_Init(int CPUFeatures, int UseUTF8Paths); FFMS_API(int) FFMS_GetLogLevel(); FFMS_API(void) FFMS_SetLogLevel(int Level); FFMS_API(FFMS_VideoSource *) FFMS_CreateVideoSource(const char *SourceFile, int Track, FFMS_Index *Index, int Threads, int SeekMode, FFMS_ErrorInfo *ErrorInfo); -FFMS_API(FFMS_AudioSource *) FFMS_CreateAudioSource(const char *SourceFile, int Track, FFMS_Index *Index, FFMS_ErrorInfo *ErrorInfo); +FFMS_API(FFMS_AudioSource *) FFMS_CreateAudioSource(const char *SourceFile, int Track, FFMS_Index *Index, int DelayMode, FFMS_ErrorInfo *ErrorInfo); FFMS_API(void) FFMS_DestroyVideoSource(FFMS_VideoSource *V); FFMS_API(void) FFMS_DestroyAudioSource(FFMS_AudioSource *A); FFMS_API(const FFMS_VideoProperties *) FFMS_GetVideoProperties(FFMS_VideoSource *V); @@ -253,6 +265,8 @@ FFMS_API(void) FFMS_ResetOutputFormatV(FFMS_VideoSource *V); FFMS_API(int) FFMS_SetPP(FFMS_VideoSource *V, const char *PP, FFMS_ErrorInfo *ErrorInfo); FFMS_API(void) FFMS_ResetPP(FFMS_VideoSource *V); FFMS_API(void) FFMS_DestroyIndex(FFMS_Index *Index); +FFMS_API(int) FFMS_GetSourceType(FFMS_Index *Index); +FFMS_API(int) FFMS_GetSourceTypeI(FFMS_Indexer *Indexer); FFMS_API(int) FFMS_GetFirstTrackOfType(FFMS_Index *Index, int TrackType, FFMS_ErrorInfo *ErrorInfo); FFMS_API(int) FFMS_GetFirstIndexedTrackOfType(FFMS_Index *Index, int TrackType, FFMS_ErrorInfo *ErrorInfo); FFMS_API(int) FFMS_GetNumTracks(FFMS_Index *Index); @@ -260,6 +274,7 @@ FFMS_API(int) FFMS_GetNumTracksI(FFMS_Indexer *Indexer); FFMS_API(int) FFMS_GetTrackType(FFMS_Track *T); FFMS_API(int) FFMS_GetTrackTypeI(FFMS_Indexer *Indexer, int Track); FFMS_API(const char *) FFMS_GetCodecNameI(FFMS_Indexer *Indexer, int Track); +FFMS_API(const char *) FFMS_GetFormatNameI(FFMS_Indexer *Indexer); FFMS_API(int) FFMS_GetNumFrames(FFMS_Track *T); FFMS_API(const FFMS_FrameInfo *) FFMS_GetFrameInfo(FFMS_Track *T, int Frame); FFMS_API(FFMS_Track *) FFMS_GetTrackFromIndex(FFMS_Index *Index, int Track); @@ -270,6 +285,7 @@ FFMS_API(int) FFMS_WriteTimecodes(FFMS_Track *T, const char *TimecodeFile, FFMS_ FFMS_API(FFMS_Index *) FFMS_MakeIndex(const char *SourceFile, int IndexMask, int DumpMask, TAudioNameCallback ANC, void *ANCPrivate, int ErrorHandling, TIndexCallback IC, void *ICPrivate, FFMS_ErrorInfo *ErrorInfo); FFMS_API(int) FFMS_DefaultAudioFilename(const char *SourceFile, int Track, const FFMS_AudioProperties *AP, char *FileName, int FNSize, void *Private); FFMS_API(FFMS_Indexer *) FFMS_CreateIndexer(const char *SourceFile, FFMS_ErrorInfo *ErrorInfo); +FFMS_API(FFMS_Indexer *) FFMS_CreateIndexerWithDemuxer(const char *SourceFile, int Demuxer, FFMS_ErrorInfo *ErrorInfo); FFMS_API(FFMS_Index *) FFMS_DoIndexing(FFMS_Indexer *Indexer, int IndexMask, int DumpMask, TAudioNameCallback ANC, void *ANCPrivate, int ErrorHandling, TIndexCallback IC, void *ICPrivate, FFMS_ErrorInfo *ErrorInfo); FFMS_API(void) FFMS_CancelIndexing(FFMS_Indexer *Indexer); FFMS_API(FFMS_Index *) FFMS_ReadIndex(const char *IndexFile, FFMS_ErrorInfo *ErrorInfo); @@ -279,4 +295,5 @@ FFMS_API(int) FFMS_GetPixFmt(const char *Name); FFMS_API(int) FFMS_GetPresentSources(); FFMS_API(int) FFMS_GetEnabledSources(); + #endif diff --git a/aegisub/libffms/include/ffmscompat.h b/aegisub/libffms/include/ffmscompat.h index ba3c2625f..3e21fb396 100644 --- a/aegisub/libffms/include/ffmscompat.h +++ b/aegisub/libffms/include/ffmscompat.h @@ -45,17 +45,41 @@ # if (LIBAVCODEC_VERSION_INT) >= (AV_VERSION_INT(52,29,0)) # define FFMS_HAVE_FFMPEG_COLORSPACE_INFO # else +# define AVCOL_RANGE_JPEG 2 # ifdef _MSC_VER # pragma message("WARNING: Your FFmpeg is too old to support reporting colorspace and luma range information. The corresponding fields of FFMS_VideoProperties will be set to 0. Please update FFmpeg to get rid of this warning.") # else # warning "Your FFmpeg is too old to support reporting colorspace and luma range information. The corresponding fields of FFMS_VideoProperties will be set to 0. Please update FFmpeg to get rid of this warning." # endif # endif +# if (LIBAVCODEC_VERSION_INT) < (AV_VERSION_INT(52,30,2)) +# define AV_PKT_FLAG_KEY PKT_FLAG_KEY +# endif +# if (LIBAVCODEC_VERSION_INT) >= (AV_VERSION_INT(52,94,3)) // there are ~3 revisions where this will break but fixing that is :effort: +# undef SampleFormat +# else +# define AVSampleFormat SampleFormat +# define av_get_bits_per_sample_fmt av_get_bits_per_sample_format +# define AV_SAMPLE_FMT_U8 SAMPLE_FMT_U8 +# define AV_SAMPLE_FMT_S16 SAMPLE_FMT_S16 +# define AV_SAMPLE_FMT_S32 SAMPLE_FMT_S32 +# define AV_SAMPLE_FMT_FLT SAMPLE_FMT_FLT +# define AV_SAMPLE_FMT_DBL SAMPLE_FMT_DBL +# endif #endif -#ifndef AV_PKT_FLAG_KEY -# define AV_PKT_FLAG_KEY PKT_FLAG_KEY +#ifdef LIBAVUTIL_VERSION_INT +# if (LIBAVUTIL_VERSION_INT) < (AV_VERSION_INT(50, 8, 0)) +# define av_get_pix_fmt avcodec_get_pix_fmt +# endif #endif +#ifdef LIBSWSCALE_VERSION_INT +# if (LIBSWSCALE_VERSION_INT) < (AV_VERSION_INT(0,8,0)) +# define FFMS_SWS_CONST_PARAM +# else +# define FFMS_SWS_CONST_PARAM const +# endif +#endif #endif // FFMSCOMPAT_H diff --git a/aegisub/libffms/src/core/audiosource.cpp b/aegisub/libffms/src/core/audiosource.cpp index 4a24096eb..060775535 100644 --- a/aegisub/libffms/src/core/audiosource.cpp +++ b/aegisub/libffms/src/core/audiosource.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2007-2009 Fredrik Mellbin +// Copyright (c) 2010 Thomas Goyne // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -20,113 +20,285 @@ #include "audiosource.h" -extern "C" { -#include -} +#include +#include -/* Audio Cache */ - -TAudioBlock::TAudioBlock(int64_t Start, int64_t Samples, uint8_t *SrcData, size_t SrcBytes) { - this->Start = Start; - this->Samples = Samples; - Data = new uint8_t[SrcBytes]; - memcpy(Data, SrcData, SrcBytes); -} - -TAudioBlock::~TAudioBlock() { - delete[] Data; -} - -TAudioCache::TAudioCache() { - MaxCacheBlocks = 0; - BytesPerSample = 0; -} - -TAudioCache::~TAudioCache() { - for (TAudioCache::iterator it=begin(); it != end(); it++) - delete *it; -} - -void TAudioCache::Initialize(int BytesPerSample, int MaxCacheBlocks) { - this->BytesPerSample = BytesPerSample; - this->MaxCacheBlocks = MaxCacheBlocks; -} - -void TAudioCache::CacheBlock(int64_t Start, int64_t Samples, uint8_t *SrcData) { - if (BytesPerSample > 0) { - for (TAudioCache::iterator it=begin(); it != end(); it++) { - if ((*it)->Start == Start) { - delete *it; - erase(it); - break; - } - } - - push_front(new TAudioBlock(Start, Samples, SrcData, static_cast(Samples * BytesPerSample))); - if (static_cast(size()) >= MaxCacheBlocks) { - delete back(); - pop_back(); - } - } -} - -bool TAudioCache::AudioBlockComp(TAudioBlock *A, TAudioBlock *B) { - return A->Start < B->Start; -} - -int64_t TAudioCache::FillRequest(int64_t Start, int64_t Samples, uint8_t *Dst) { - // May be better to move used blocks to the front - std::list UsedBlocks; - for (TAudioCache::iterator it=begin(); it != end(); it++) { - int64_t SrcOffset = FFMAX(0, Start - (*it)->Start); - int64_t DstOffset = FFMAX(0, (*it)->Start - Start); - int64_t CopySamples = FFMIN((*it)->Samples - SrcOffset, Samples - DstOffset); - if (CopySamples > 0) { - memcpy(Dst + DstOffset * BytesPerSample, (*it)->Data + SrcOffset * BytesPerSample, static_cast(CopySamples * BytesPerSample)); - UsedBlocks.push_back(*it); - } - } - UsedBlocks.sort(AudioBlockComp); - int64_t Ret = Start; - for (std::list::iterator it = UsedBlocks.begin(); it != UsedBlocks.end(); it++) { - if (it == UsedBlocks.begin() || Ret == (*it)->Start) - Ret = (*it)->Start + (*it)->Samples; - else - break; - } - return FFMIN(Ret, Start + Samples); -} - -/* FFMS_AudioSource base class */ - -FFMS_AudioSource::FFMS_AudioSource(const char *SourceFile, FFMS_Index *Index, int Track) : CurrentSample(0) { - if (Track < 0 || Track >= static_cast(Index->size())) +FFMS_AudioSource::FFMS_AudioSource(const char *SourceFile, FFMS_Index &Index, int Track) +: Delay(0) +, MaxCacheBlocks(50) +, BytesPerSample(0) +, Decoded(0) +, CurrentSample(-1) +, PacketNumber(0) +, CurrentFrame(NULL) +, TrackNumber(Track) +, SeekOffset(0) +, DecodingBuffer(AVCODEC_MAX_AUDIO_FRAME_SIZE * 10) +{ + if (Track < 0 || Track >= static_cast(Index.size())) throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_INVALID_ARGUMENT, "Out of bounds track index selected"); - if (Index->at(Track).TT != FFMS_TYPE_AUDIO) + if (Index[Track].TT != FFMS_TYPE_AUDIO) throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_INVALID_ARGUMENT, "Not an audio track"); - if (Index[Track].size() == 0) + if (Index[Track].empty()) throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_INVALID_ARGUMENT, "Audio track contains no audio frames"); - if (!Index->CompareFileSignature(SourceFile)) + Frames = Index[Track]; + + if (!Index.CompareFileSignature(SourceFile)) throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_FILE_MISMATCH, "The index does not match the source file"); +} +void FFMS_AudioSource::Init(FFMS_Index &Index, int DelayMode) { + // The first packet after a seek is often decoded incorrectly, which + // makes it impossible to ever correctly seek back to the beginning, so + // store the first block now - DecodingBuffer = (uint8_t *) av_malloc(AVCODEC_MAX_AUDIO_FRAME_SIZE * 10 * sizeof(*DecodingBuffer)); - if (!DecodingBuffer) - throw std::bad_alloc(); + // In addition, anything with the same PTS as the first packet can't be + // distinguished from the first packet and so can't be seeked to, so + // store those as well + + // Some of LAVF's splitters don't like to seek to the beginning of the + // file (ts and?), so cache a few blocks even if PTSes are unique + // Packet 7 is the last packet I've had be unseekable to, so cache up to + // 10 for a bit of an extra buffer + CacheIterator end = Cache.end(); + while (PacketNumber < Frames.size() && + ((Frames[0].PTS != ffms_av_nopts_value && Frames[PacketNumber].PTS == Frames[0].PTS) || + Cache.size() < 10)) { + + DecodeNextBlock(); + if (Decoded) + CacheBlock(end, CurrentSample, Decoded, &DecodingBuffer[0]); + } + // Store the iterator to the last element of the cache which is used for + // correctness rather than speed, so that when looking for one to delete + // we know how much to skip + CacheNoDelete = Cache.end(); + --CacheNoDelete; + + // Read properties of the audio which may not be available until the first + // frame has been decoded + FillAP(AP, CodecContext, Frames); + + if (AP.SampleRate <= 0 || AP.BitsPerSample <= 0) + throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_CODEC, + "Codec returned zero size audio"); + + if (DelayMode < FFMS_DELAY_NO_SHIFT) + throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_INVALID_ARGUMENT, + "Bad audio delay compensation mode"); + + if (DelayMode == FFMS_DELAY_NO_SHIFT) return; + + if (DelayMode > (signed)Index.size()) + throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_INVALID_ARGUMENT, + "Out of bounds track index selected for audio delay compensation"); + + if (DelayMode >= 0 && Index[DelayMode].TT != FFMS_TYPE_VIDEO) + throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_INVALID_ARGUMENT, + "Audio delay compensation must be relative to a video track"); + + double AdjustRelative = 0; + if (DelayMode != FFMS_DELAY_TIME_ZERO) { + if (DelayMode == FFMS_DELAY_FIRST_VIDEO_TRACK) { + for (size_t i = 0; i < Index.size(); ++i) { + if (Index[i].TT == FFMS_TYPE_VIDEO) { + DelayMode = i; + break; + } + } + } + + if (DelayMode >= 0) { + const FFMS_Track &VTrack = Index[DelayMode]; + AdjustRelative = VTrack[0].PTS * VTrack.TB.Num / (double)VTrack.TB.Den; + } + } + + Delay = static_cast((Frames[0].PTS * Frames.TB.Num / (double)Frames.TB.Den - AdjustRelative) * AP.SampleRate / 1000. + .5); + AP.NumSamples += Delay; } -FFMS_AudioSource::~FFMS_AudioSource() { - av_free(DecodingBuffer); +void FFMS_AudioSource::CacheBlock(CacheIterator &pos, int64_t Start, size_t Samples, uint8_t *SrcData) { + Cache.insert(pos, AudioBlock(Start, Samples, SrcData, Samples * BytesPerSample)); + + if (Cache.size() >= MaxCacheBlocks) { + // Kill the oldest one + CacheIterator min = CacheNoDelete; + // Never drop the first one as the first packet decoded after a seek + // is often decoded incorrectly and we can't seek to before the first one + ++min; + for (CacheIterator it = min; it != Cache.end(); ++it) + if (it->Age < min->Age) min = it; + if (min == pos) ++pos; + Cache.erase(min); + } } -void FFMS_AudioSource::GetAudioCheck(int64_t Start, int64_t Count) { - if (Start < 0 || Start + Count > AP.NumSamples) +void FFMS_AudioSource::DecodeNextBlock() { + if (BytesPerSample == 0) BytesPerSample = (av_get_bits_per_sample_fmt(CodecContext->sample_fmt) * CodecContext->channels) / 8; + + CurrentFrame = &Frames[PacketNumber]; + + AVPacket Packet; + if (!ReadPacket(&Packet)) + throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_UNKNOWN, "ReadPacket unexpectedly failed to read a packet"); + + // ReadPacket may have changed the packet number + CurrentFrame = &Frames[PacketNumber]; + CurrentSample = CurrentFrame->SampleStart; + ++PacketNumber; + + uint8_t *Buf = &DecodingBuffer[0]; + uint8_t *Data = Packet.data; + while (Packet.size > 0) { + int TempOutputBufSize = AVCODEC_MAX_AUDIO_FRAME_SIZE * 10 - (Buf - &DecodingBuffer[0]); + int Ret = avcodec_decode_audio3(CodecContext, (int16_t *)Buf, &TempOutputBufSize, &Packet); + + // Should only ever happen if the user chose to ignore decoding errors + // during indexing, so continue to just ignore decoding errors + if (Ret < 0) break; + + if (Ret > 0) { + Packet.size -= Ret; + Packet.data += Ret; + Buf += TempOutputBufSize; + } + } + Packet.data = Data; + FreePacket(&Packet); + + Decoded = (Buf - &DecodingBuffer[0]) / BytesPerSample; + if (Decoded == 0) { + // zero sample packets aren't included in the index so we didn't + // actually move to the next packet + --PacketNumber; + } +} + +static bool SampleStartComp(const TFrameInfo &a, const TFrameInfo &b) { + return a.SampleStart < b.SampleStart; +} + +void FFMS_AudioSource::GetAudio(void *Buf, int64_t Start, int64_t Count) { + if (Start < 0 || Start + Count > AP.NumSamples || Count < 0) throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_INVALID_ARGUMENT, "Out of bounds audio samples requested"); + + uint8_t *Dst = static_cast(Buf); + + // Apply audio delay (if any) and fill any samples before the start time with zero + Start -= Delay; + if (Start < 0) { + size_t Bytes = static_cast(BytesPerSample * FFMIN(-Start, Count)); + memset(Dst, 0, Bytes); + + Count += Start; + // Entire request was before the start of the audio + if (Count <= 0) return; + + Start = 0; + Dst += Bytes; + } + + CacheIterator it = Cache.begin(); + + while (Count > 0) { + // Find first useful cache block + while (it != Cache.end() && it->Start + it->Samples <= Start) ++it; + + // Cache has the next block we want + if (it != Cache.end() && it->Start <= Start) { + int64_t SrcOffset = FFMAX(0, Start - it->Start); + int64_t DstOffset = FFMAX(0, it->Start - Start); + int64_t CopySamples = FFMIN(it->Samples - SrcOffset, Count - DstOffset); + size_t Bytes = static_cast(CopySamples * BytesPerSample); + + memcpy(Dst + DstOffset * BytesPerSample, &it->Data[SrcOffset * BytesPerSample], Bytes); + Start += CopySamples; + Count -= CopySamples; + Dst += Bytes; + ++it; + } + // Decode another block + else { + if (Start < CurrentSample && SeekOffset == -1) + throw FFMS_Exception(FFMS_ERROR_SEEKING, FFMS_ERROR_CODEC, "Audio stream is not seekable"); + + if (SeekOffset >= 0 && (Start < CurrentSample || Start > CurrentSample + Decoded * 5)) { + TFrameInfo f; + f.SampleStart = Start; + int NewPacketNumber = std::distance(Frames.begin(), std::lower_bound(Frames.begin(), Frames.end(), f, SampleStartComp)); + NewPacketNumber = FFMAX(0, NewPacketNumber - SeekOffset - 15); + while (NewPacketNumber > 0 && !Frames[NewPacketNumber].KeyFrame) --NewPacketNumber; + + // Only seek forward if it'll actually result in moving forward + if (Start < CurrentSample || static_cast(NewPacketNumber) > PacketNumber) { + PacketNumber = NewPacketNumber; + Decoded = 0; + CurrentSample = -1; + avcodec_flush_buffers(CodecContext); + Seek(); + } + } + + // Decode everything between the last keyframe and the block we want + while (CurrentSample + Decoded <= Start) DecodeNextBlock(); + if (CurrentSample > Start) + throw FFMS_Exception(FFMS_ERROR_SEEKING, FFMS_ERROR_CODEC, "Seeking is severely broken"); + + CacheBlock(it, CurrentSample, Decoded, &DecodingBuffer[0]); + + size_t FirstSample = static_cast(Start - CurrentSample); + size_t Samples = static_cast(Decoded - FirstSample); + size_t Bytes = FFMIN(Samples, static_cast(Count)) * BytesPerSample; + + memcpy(Dst, &DecodingBuffer[FirstSample * BytesPerSample], Bytes); + + Start += Samples; + Count -= Samples; + Dst += Bytes; + } + } +} + +size_t GetSeekablePacketNumber(FFMS_Track const& Frames, size_t PacketNumber) { + // Packets don't always have unique PTSes, so we may not be able to + // uniquely identify the packet we want. This function attempts to find + // a PTS we can seek to which will let us figure out which packet we're + // on before we get to the packet we actually wanted + + // MatroskaAudioSource doesn't need this, as it seeks by byte offset + // rather than PTS. LAVF theoretically can seek by byte offset, but we + // don't use it as not all demuxers support it and it's broken in some of + // those that claim to support it + + // However much we might wish to, we can't seek to before packet zero + if (PacketNumber == 0) return PacketNumber; + + // Desired packet's PTS is unique, so don't do anything + if (Frames[PacketNumber].PTS != Frames[PacketNumber - 1].PTS && + (PacketNumber + 1 == Frames.size() || Frames[PacketNumber].PTS != Frames[PacketNumber + 1].PTS)) + return PacketNumber; + + // When decoding, we only reliably know what packet we're at when the + // newly parsed packet has a different PTS from the previous one. As such, + // we walk backwards until we hit a different PTS and then seek to there, + // so that we can then decode until we hit the PTS group we actually wanted + // (and thereby know that we're at the first packet in the group rather + // than whatever the splitter happened to choose) + + // This doesn't work if our desired packet has the same PTS as the first + // packet, but this scenario should never come up anyway; we permanently + // cache the decoded results from those packets, so there's no need to ever + // seek to them + int64_t PTS = Frames[PacketNumber].PTS; + while (PacketNumber > 0 && PTS == Frames[PacketNumber].PTS) + --PacketNumber; + return PacketNumber; } diff --git a/aegisub/libffms/src/core/audiosource.h b/aegisub/libffms/src/core/audiosource.h index f73900422..3d2d8806c 100644 --- a/aegisub/libffms/src/core/audiosource.h +++ b/aegisub/libffms/src/core/audiosource.h @@ -28,7 +28,6 @@ extern "C" { #include #include -#include #include #include "indexing.h" #include "utils.h" @@ -46,93 +45,118 @@ extern "C" { # include "guids.h" #endif -class TAudioBlock { -public: - int64_t Start; - int64_t Samples; - uint8_t *Data; - - TAudioBlock(int64_t Start, int64_t Samples, uint8_t *SrcData, size_t SrcBytes); - ~TAudioBlock(); -}; - -class TAudioCache : private std::list { -private: - int MaxCacheBlocks; - int BytesPerSample; - static bool AudioBlockComp(TAudioBlock *A, TAudioBlock *B); -public: - TAudioCache(); - ~TAudioCache(); - void Initialize(int BytesPerSample, int MaxCacheBlocks); - void CacheBlock(int64_t Start, int64_t Samples, uint8_t *SrcData); - int64_t FillRequest(int64_t Start, int64_t Samples, uint8_t *Dst); -}; - class FFMS_AudioSource { -friend class FFSourceResources; + struct AudioBlock { + int64_t Age; + int64_t Start; + int64_t Samples; + std::vector Data; + + AudioBlock(int64_t Start, int64_t Samples, uint8_t *SrcData, size_t SrcBytes) + : Start(Start) + , Samples(Samples) + , Data(SrcData, SrcData + SrcBytes) + { + static int64_t Now = 0; + Age = Now++; + } + }; + typedef std::list::iterator CacheIterator; + + // delay in samples to apply to the audio + int64_t Delay; + // cache of decoded audio blocks + std::list Cache; + // max size of the cache in blocks + size_t MaxCacheBlocks; + // pointer to last element of the cache which should never be deleted + CacheIterator CacheNoDelete; + // bytes per sample * number of channels + size_t BytesPerSample; + // Number of samples stored in the decoding buffer + size_t Decoded; + + // Insert a block into the cache + void CacheBlock(CacheIterator &pos, int64_t Start, size_t Samples, uint8_t *SrcData); + + // Called after seeking + virtual void Seek() { }; + // Read the next packet from the file + virtual bool ReadPacket(AVPacket *) = 0; + virtual void FreePacket(AVPacket *) { } protected: - TAudioCache AudioCache; + // First sample which is stored in the decoding buffer int64_t CurrentSample; - uint8_t *DecodingBuffer; + // Next packet to be read + size_t PacketNumber; + // Current audio frame + TFrameInfo *CurrentFrame; + // Track which this corresponds to + int TrackNumber; + // Number of packets which the demuxer requires to know where it is + // If -1, seeking is assumed to be impossible + int SeekOffset; + + // Buffer which audio is decoded into + AlignedBuffer DecodingBuffer; FFMS_Track Frames; - AVCodecContext *CodecContext; - int AudioTrack; + FFCodecContext CodecContext; FFMS_AudioProperties AP; - virtual void Free(bool CloseCodec) = 0; + void DecodeNextBlock(); + // Initialization which has to be done after the codec is opened + void Init(FFMS_Index &Index, int DelayMode); + + FFMS_AudioSource(const char *SourceFile, FFMS_Index &Index, int Track); + public: - FFMS_AudioSource(const char *SourceFile, FFMS_Index *Index, int Track); - virtual ~FFMS_AudioSource(); + virtual ~FFMS_AudioSource() { }; FFMS_Track *GetTrack() { return &Frames; } - const FFMS_AudioProperties& GetAudioProperties() { return AP; } - virtual void GetAudio(void *Buf, int64_t Start, int64_t Count) = 0; - void GetAudioCheck(int64_t Start, int64_t Count); + const FFMS_AudioProperties& GetAudioProperties() const { return AP; } + void GetAudio(void *Buf, int64_t Start, int64_t Count); }; class FFLAVFAudio : public FFMS_AudioSource { -private: AVFormatContext *FormatContext; - FFSourceResources Res; - void DecodeNextAudioBlock(int64_t *Count); - void Free(bool CloseCodec); + bool ReadPacket(AVPacket *); + void FreePacket(AVPacket *); + void Seek(); + public: - FFLAVFAudio(const char *SourceFile, int Track, FFMS_Index *Index); - void GetAudio(void *Buf, int64_t Start, int64_t Count); + FFLAVFAudio(const char *SourceFile, int Track, FFMS_Index &Index, int DelayMode); + ~FFLAVFAudio(); }; class FFMatroskaAudio : public FFMS_AudioSource { -private: MatroskaFile *MF; MatroskaReaderContext MC; - CompressedStream *CS; + TrackInfo *TI; + std::auto_ptr TCC; char ErrorMessage[256]; - FFSourceResources Res; - size_t PacketNumber; - void DecodeNextAudioBlock(int64_t *Count); - void Free(bool CloseCodec); + bool ReadPacket(AVPacket *); + public: - FFMatroskaAudio(const char *SourceFile, int Track, FFMS_Index *Index); - void GetAudio(void *Buf, int64_t Start, int64_t Count); + FFMatroskaAudio(const char *SourceFile, int Track, FFMS_Index &Index, int DelayMode); + ~FFMatroskaAudio(); }; #ifdef HAALISOURCE class FFHaaliAudio : public FFMS_AudioSource { -private: CComPtr pMMC; - std::vector CodecPrivate; - FFSourceResources Res; + CComPtr pMMF; + + bool ReadPacket(AVPacket *); + void Seek(); - void DecodeNextAudioBlock(int64_t *AFirstStartTime, int64_t *Count); - void Free(bool CloseCodec); public: - FFHaaliAudio(const char *SourceFile, int Track, FFMS_Index *Index, enum FFMS_Sources SourceMode); - void GetAudio(void *Buf, int64_t Start, int64_t Count); + FFHaaliAudio(const char *SourceFile, int Track, FFMS_Index &Index, enum FFMS_Sources SourceMode, int DelayMode); }; #endif // HAALISOURCE +size_t GetSeekablePacketNumber(FFMS_Track const& Frames, size_t PacketNumber); + #endif diff --git a/aegisub/libffms/src/core/ffms.cpp b/aegisub/libffms/src/core/ffms.cpp index ab2e27d53..92f10ef38 100644 --- a/aegisub/libffms/src/core/ffms.cpp +++ b/aegisub/libffms/src/core/ffms.cpp @@ -25,52 +25,73 @@ #include "audiosource.h" #include "indexing.h" +extern "C" { +#include +} +#ifdef FFMS_WIN_DEBUG +# include +#endif + static bool FFmpegInited = false; bool HasHaaliMPEG = false; bool HasHaaliOGG = false; int CPUFeatures = 0; +bool GlobalUseUTF8Paths = false; + #ifdef FFMS_WIN_DEBUG extern "C" int av_log_level; void av_log_windebug_callback(void* ptr, int level, const char* fmt, va_list vl) { - static int print_prefix=1; - static int count; - static char line[1024], prev[1024]; - AVClass* avc= ptr ? *(AVClass**)ptr : NULL; - if(level>av_log_level) - return; -#undef fprintf - if(print_prefix && avc) { - snprintf(line, sizeof(line), "[%s @ %p]", avc->item_name(ptr), ptr); - }else - line[0]=0; + static int print_prefix=1; + static int count; + static char line[1024] = {0}, prev[1024] = {0}; + AVClass* avc = ptr ? *(AVClass**)ptr : NULL; + if(level > av_log_level) + return; - vsnprintf(line + strlen(line), sizeof(line) - strlen(line), fmt, vl); + int written = 0; + if(print_prefix && avc) { + written = snprintf(line, sizeof(line), "[%s @ %p]", avc->item_name(ptr), ptr); + } - print_prefix= line[strlen(line)-1] == '\n'; - if(print_prefix && !strcmp(line, prev)){ - count++; - return; - } - if(count>0){ + written += vsnprintf(line + written, sizeof(line) - written, fmt, vl); + + print_prefix = line[written-1] == '\n'; + line[sizeof(line) - 1] = 0; + if(print_prefix && !strcmp(line, prev)){ + count++; + return; + } + if(count > 0){ std::stringstream ss; ss << " Last message repeated " << count << " times\n"; OutputDebugStringA(ss.str().c_str()); - count=0; - } + count = 0; + } OutputDebugStringA(line); - strcpy(prev, line); + strcpy(prev, line); } #endif -FFMS_API(void) FFMS_Init(int CPUFeatures) { +FFMS_API(void) FFMS_Init(int CPUFeatures, int UseUTF8Paths) { if (!FFmpegInited) { av_register_all(); +#ifdef _WIN32 + if (UseUTF8Paths) { + ffms_patch_lavf_file_open(); + GlobalUseUTF8Paths = true; + } + else { + GlobalUseUTF8Paths = false; + } +#else + GlobalUseUTF8Paths = false; +#endif #ifdef FFMS_WIN_DEBUG av_log_set_callback(av_log_windebug_callback); av_log_set_level(AV_LOG_INFO); @@ -98,6 +119,8 @@ FFMS_API(void) FFMS_SetLogLevel(int Level) { } FFMS_API(FFMS_VideoSource *) FFMS_CreateVideoSource(const char *SourceFile, int Track, FFMS_Index *Index, int Threads, int SeekMode, FFMS_ErrorInfo *ErrorInfo) { + if (Threads < 1) + Threads = 1; try { switch (Index->Decoder) { case FFMS_SOURCE_LAVF: @@ -123,21 +146,21 @@ FFMS_API(FFMS_VideoSource *) FFMS_CreateVideoSource(const char *SourceFile, int } } -FFMS_API(FFMS_AudioSource *) FFMS_CreateAudioSource(const char *SourceFile, int Track, FFMS_Index *Index, FFMS_ErrorInfo *ErrorInfo) { +FFMS_API(FFMS_AudioSource *) FFMS_CreateAudioSource(const char *SourceFile, int Track, FFMS_Index *Index, int DelayMode, FFMS_ErrorInfo *ErrorInfo) { try { switch (Index->Decoder) { case FFMS_SOURCE_LAVF: - return new FFLAVFAudio(SourceFile, Track, Index); + return new FFLAVFAudio(SourceFile, Track, *Index, DelayMode); case FFMS_SOURCE_MATROSKA: - return new FFMatroskaAudio(SourceFile, Track, Index); + return new FFMatroskaAudio(SourceFile, Track, *Index, DelayMode); #ifdef HAALISOURCE case FFMS_SOURCE_HAALIMPEG: if (HasHaaliMPEG) - return new FFHaaliAudio(SourceFile, Track, Index, FFMS_SOURCE_HAALIMPEG); + return new FFHaaliAudio(SourceFile, Track, *Index, FFMS_SOURCE_HAALIMPEG, DelayMode); throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_NOT_AVAILABLE, "Haali MPEG/TS source unavailable"); case FFMS_SOURCE_HAALIOGG: if (HasHaaliOGG) - return new FFHaaliAudio(SourceFile, Track, Index, FFMS_SOURCE_HAALIOGG); + return new FFHaaliAudio(SourceFile, Track, *Index, FFMS_SOURCE_HAALIOGG, DelayMode); throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_NOT_AVAILABLE, "Haali OGG/OGM source unavailable"); #endif default: @@ -227,6 +250,10 @@ FFMS_API(void) FFMS_DestroyIndex(FFMS_Index *Index) { delete Index; } +FFMS_API(int) FFMS_GetSourceType(FFMS_Index *Index) { + return Index->Decoder; +} + FFMS_API(int) FFMS_GetFirstTrackOfType(FFMS_Index *Index, int TrackType, FFMS_ErrorInfo *ErrorInfo) { ClearErrorInfo(ErrorInfo); for (int i = 0; i < static_cast(Index->size()); i++) @@ -351,9 +378,13 @@ FFMS_API(int) FFMS_DefaultAudioFilename(const char *SourceFile, int Track, const } FFMS_API(FFMS_Indexer *) FFMS_CreateIndexer(const char *SourceFile, FFMS_ErrorInfo *ErrorInfo) { + return FFMS_CreateIndexerWithDemuxer(SourceFile, FFMS_SOURCE_DEFAULT, ErrorInfo); +} + +FFMS_API(FFMS_Indexer *) FFMS_CreateIndexerWithDemuxer(const char *SourceFile, int Demuxer, FFMS_ErrorInfo *ErrorInfo) { ClearErrorInfo(ErrorInfo); try { - return FFMS_Indexer::CreateIndexer(SourceFile); + return FFMS_Indexer::CreateIndexer(SourceFile, static_cast(Demuxer)); } catch (FFMS_Exception &e) { e.CopyOut(ErrorInfo); return NULL; @@ -419,9 +450,10 @@ FFMS_API(int) FFMS_WriteIndex(const char *IndexFile, FFMS_Index *Index, FFMS_Err } FFMS_API(int) FFMS_GetPixFmt(const char *Name) { - return avcodec_get_pix_fmt(Name); + return av_get_pix_fmt(Name); } + FFMS_API(int) FFMS_GetPresentSources() { int Sources = FFMS_SOURCE_LAVF | FFMS_SOURCE_MATROSKA; #ifdef HAALISOURCE @@ -440,3 +472,11 @@ FFMS_API(int) FFMS_GetEnabledSources() { Sources |= FFMS_SOURCE_HAALIOGG; return Sources; } + +FFMS_API(const char *) FFMS_GetFormatNameI(FFMS_Indexer *Indexer) { + return Indexer->GetFormatName(); +} + +FFMS_API(int) FFMS_GetSourceTypeI(FFMS_Indexer *Indexer) { + return Indexer->GetSourceType(); +} diff --git a/aegisub/libffms/src/core/indexing.cpp b/aegisub/libffms/src/core/indexing.cpp index 0f071e212..76f9c568d 100644 --- a/aegisub/libffms/src/core/indexing.cpp +++ b/aegisub/libffms/src/core/indexing.cpp @@ -19,20 +19,29 @@ // THE SOFTWARE. #include "indexing.h" -#include -#include + #include +#include +#include +#include + extern "C" { -#include #include +#include } +#undef max + #define INDEXID 0x53920873 extern bool HasHaaliMPEG; extern bool HasHaaliOGG; +#ifndef FFMS_USE_POSTPROC +unsigned postproc_version() { return 0; } // ugly workaround to avoid lots of ifdeffing +#endif // FFMS_USE_POSTPROC + struct IndexHeader { uint32_t Id; uint32_t Version; @@ -48,17 +57,20 @@ struct IndexHeader { }; struct TrackHeader { - uint32_t TT; - uint32_t Frames; - int64_t Num; - int64_t Den; + uint32_t TT; + uint32_t Frames; + int64_t Num; + int64_t Den; + uint32_t UseDTS; }; + SharedVideoContext::SharedVideoContext(bool FreeCodecContext) { CodecContext = NULL; Parser = NULL; - CS = NULL; + BitStreamFilter = NULL; this->FreeCodecContext = FreeCodecContext; + TCC = NULL; } SharedVideoContext::~SharedVideoContext() { @@ -67,19 +79,17 @@ SharedVideoContext::~SharedVideoContext() { if (FreeCodecContext) av_freep(&CodecContext); } - - if (Parser) - av_parser_close(Parser); - - if (CS) - cs_Destroy(CS); + av_parser_close(Parser); + if (BitStreamFilter) + av_bitstream_filter_close(BitStreamFilter); + delete TCC; } SharedAudioContext::SharedAudioContext(bool FreeCodecContext) { W64Writer = NULL; CodecContext = NULL; CurrentSample = 0; - CS = NULL; + TCC = NULL; this->FreeCodecContext = FreeCodecContext; } @@ -90,9 +100,7 @@ SharedAudioContext::~SharedAudioContext() { if (FreeCodecContext) av_freep(&CodecContext); } - - if (CS) - cs_Destroy(CS); + delete TCC; } TFrameInfo::TFrameInfo() { @@ -113,22 +121,20 @@ TFrameInfo TFrameInfo::VideoFrameInfo(int64_t PTS, int RepeatPict, bool KeyFrame return TFrameInfo(PTS, 0, 0, RepeatPict, KeyFrame, FilePos, FrameSize); } -TFrameInfo TFrameInfo::AudioFrameInfo(int64_t PTS, int64_t SampleStart, unsigned int SampleCount, bool KeyFrame, int64_t FilePos, unsigned int FrameSize) { - return TFrameInfo(PTS, SampleStart, SampleCount, 0, KeyFrame, FilePos, FrameSize); +TFrameInfo TFrameInfo::AudioFrameInfo(int64_t PTS, int64_t SampleStart, int64_t SampleCount, bool KeyFrame, int64_t FilePos, unsigned int FrameSize) { + return TFrameInfo(PTS, SampleStart, static_cast(SampleCount), 0, KeyFrame, FilePos, FrameSize); } void FFMS_Track::WriteTimecodes(const char *TimecodeFile) { ffms_fstream Timecodes(TimecodeFile, std::ios::out | std::ios::trunc); - if (!Timecodes.is_open()) { - std::ostringstream buf; - buf << "Failed to open '" << TimecodeFile << "' for writing"; - throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str()); - } + if (!Timecodes.is_open()) + throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, + std::string("Failed to open '") + TimecodeFile + "' for writing"); Timecodes << "# timecode format v2\n"; - for (iterator Cur=begin(); Cur!=end(); Cur++) + for (iterator Cur = begin(); Cur != end(); ++Cur) Timecodes << std::fixed << ((Cur->PTS * TB.Num) / (double)TB.Den) << "\n"; } @@ -139,108 +145,91 @@ int FFMS_Track::FrameFromPTS(int64_t PTS) { return -1; } -int FFMS_Track::ClosestFrameFromPTS(int64_t PTS) { - int Frame = 0; - int64_t BestDiff = 0xFFFFFFFFFFFFFFLL; // big number - for (int i = 0; i < static_cast(size()); i++) { - int64_t CurrentDiff = FFABS(at(i).PTS - PTS); - if (CurrentDiff < BestDiff) { - BestDiff = CurrentDiff; - Frame = i; - } - } - - return Frame; -} - -int FFMS_Track::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 FFMS_Track::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; -} - -FFMS_Track::FFMS_Track() { - this->TT = FFMS_TYPE_UNKNOWN; - this->TB.Num = 0; - this->TB.Den = 0; -} - -FFMS_Track::FFMS_Track(int64_t Num, int64_t Den, FFMS_TrackType TT) { - this->TT = TT; - this->TB.Num = Num; - this->TB.Den = Den; -} - -void FFMS_Index::CalculateFileSignature(const char *Filename, int64_t *Filesize, uint8_t Digest[20]) { - FILE *SFile = ffms_fopen(Filename,"rb"); - - if (SFile == NULL) { - std::ostringstream buf; - buf << "Failed to open '" << Filename << "' for hashing"; - throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str()); - } - - const int BlockSize = 1024*1024; - std::vector FileBuffer(BlockSize); - std::vector ctxmem(av_sha1_size); - AVSHA1 *ctx = (AVSHA1 *)&ctxmem[0]; - av_sha1_init(ctx); - - memset(&FileBuffer[0], 0, BlockSize); - fread(&FileBuffer[0], 1, BlockSize, SFile); - if (ferror(SFile) && !feof(SFile)) { - av_sha1_final(ctx, Digest); - fclose(SFile); - std::ostringstream buf; - buf << "Failed to read '" << Filename << "' for hashing"; - throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str()); - } - av_sha1_update(ctx, &FileBuffer[0], BlockSize); - - fseeko(SFile, -BlockSize, SEEK_END); - memset(&FileBuffer[0], 0, BlockSize); - fread(&FileBuffer[0], 1, BlockSize, SFile); - if (ferror(SFile) && !feof(SFile)) { - av_sha1_final(ctx, Digest); - fclose(SFile); - std::ostringstream buf; - buf << "Failed to seek with offset " << BlockSize << " from file end in '" << Filename << "' for hashing"; - throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str()); - } - av_sha1_update(ctx, &FileBuffer[0], BlockSize); - - fseeko(SFile, 0, SEEK_END); - if (ferror(SFile)) { - av_sha1_final(ctx, Digest); - fclose(SFile); - std::ostringstream buf; - buf << "Failed to seek to end of '" << Filename << "' for hashing"; - throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str()); - } - *Filesize = ftello(SFile); - fclose(SFile); - - av_sha1_final(ctx, Digest); -} - static bool PTSComparison(TFrameInfo FI1, TFrameInfo FI2) { return FI1.PTS < FI2.PTS; } +int FFMS_Track::ClosestFrameFromPTS(int64_t PTS) { + TFrameInfo F; + F.PTS = PTS; + + iterator Pos = std::lower_bound(begin(), end(), F, PTSComparison); + int Frame = std::distance(begin(), Pos); + if (Pos == begin() || FFABS(Pos->PTS - PTS) <= FFABS((Pos - 1)->PTS - PTS)) + return Frame; + return Frame - 1; +} + +int FFMS_Track::FindClosestVideoKeyFrame(int Frame) { + Frame = FFMIN(FFMAX(Frame, 0), static_cast(size()) - 1); + for (; Frame > 0 && !at(Frame).KeyFrame; Frame--) ; + for (; Frame > 0 && !at(at(Frame).OriginalPos).KeyFrame; Frame--) ; + return Frame; +} + +FFMS_Track::FFMS_Track() { + this->TT = FFMS_TYPE_UNKNOWN; + this->TB.Num = 0; + this->TB.Den = 0; + this->UseDTS = false; +} + +FFMS_Track::FFMS_Track(int64_t Num, int64_t Den, FFMS_TrackType TT, bool UseDTS) { + this->TT = TT; + this->TB.Num = Num; + this->TB.Den = Den; + this->UseDTS = UseDTS; +} + +void FFMS_Index::CalculateFileSignature(const char *Filename, int64_t *Filesize, uint8_t Digest[20]) { + FILE *SFile = ffms_fopen(Filename,"rb"); + if (!SFile) + throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, + std::string("Failed to open '") + Filename + "' for hashing"); + + std::vector FileBuffer(1024*1024, 0); + std::vector ctxmem(av_sha1_size); + AVSHA1 *ctx = (AVSHA1 *)(&ctxmem[0]); + av_sha1_init(ctx); + + try { + fread(&FileBuffer[0], 1, FileBuffer.size(), SFile); + if (ferror(SFile) && !feof(SFile)) + throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, + std::string("Failed to read '") + Filename + "' for hashing"); + + av_sha1_update(ctx, &FileBuffer[0], FileBuffer.size()); + + fseeko(SFile, -(int)FileBuffer.size(), SEEK_END); + std::fill(FileBuffer.begin(), FileBuffer.end(), 0); + fread(&FileBuffer[0], 1, FileBuffer.size(), SFile); + if (ferror(SFile) && !feof(SFile)) { + std::ostringstream buf; + buf << "Failed to seek with offset " << FileBuffer.size() << " from file end in '" << Filename << "' for hashing"; + throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str()); + } + + av_sha1_update(ctx, &FileBuffer[0], FileBuffer.size()); + + fseeko(SFile, 0, SEEK_END); + if (ferror(SFile)) + throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, + std::string("Failed to seek to end of '") + Filename + "' for hashing"); + + *Filesize = ftello(SFile); + } + catch (...) { + fclose(SFile); + av_sha1_final(ctx, Digest); + throw; + } + fclose(SFile); + av_sha1_final(ctx, Digest); +} + void FFMS_Index::Sort() { - for (FFMS_Index::iterator Cur=begin(); Cur!=end(); Cur++) { + for (FFMS_Index::iterator Cur = begin(); Cur != end(); ++Cur) { + if (Cur->size() > 2 && Cur->front().PTS >= Cur->back().PTS) Cur->pop_back(); for (size_t i = 0; i < Cur->size(); i++) Cur->at(i).OriginalPos = i; @@ -265,13 +254,42 @@ bool FFMS_Index::CompareFileSignature(const char *Filename) { return (CFilesize == Filesize && !memcmp(CDigest, Digest, sizeof(Digest))); } +#define CHUNK 65536 + +static unsigned int z_def(ffms_fstream *IndexStream, z_stream *stream, void *in, size_t in_sz, int finish) { + unsigned int total = 0, have; + int ret; + char out[CHUNK]; + + if (!finish && (in_sz == 0 || in == NULL)) return 0; + + stream->next_in = (Bytef*) in; + stream->avail_in = in_sz; + do { + do { + stream->avail_out = CHUNK; + stream->next_out = (Bytef*) out; + ret = deflate(stream, finish ? Z_FINISH : Z_NO_FLUSH); + have = CHUNK - stream->avail_out; + if (have) IndexStream->write(out, have); + total += have; + } while (stream->avail_out == 0); + } while (finish && ret != Z_STREAM_END); + if (finish) deflateEnd(stream); + return total; +} + void FFMS_Index::WriteIndex(const char *IndexFile) { ffms_fstream IndexStream(IndexFile, std::ios::out | std::ios::binary | std::ios::trunc); - if (!IndexStream.is_open()) { - std::ostringstream buf; - buf << "Failed to open '" << IndexFile << "' for writing"; - throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str()); + if (!IndexStream.is_open()) + throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, + std::string("Failed to open '") + IndexFile + "' for writing"); + + z_stream stream; + memset(&stream, 0, sizeof(z_stream)); + if (deflateInit(&stream, 9) != Z_OK) { + throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, "Failed to initialize zlib"); } // Write the index file header @@ -288,51 +306,99 @@ void FFMS_Index::WriteIndex(const char *IndexFile) { IH.FileSize = Filesize; memcpy(IH.FileSignature, Digest, sizeof(Digest)); - IndexStream.write(reinterpret_cast(&IH), sizeof(IH)); - - for (unsigned int i = 0; i < IH.Tracks; i++) { - TrackHeader TH; - TH.TT = at(i).TT; - TH.Frames = at(i).size(); - TH.Num = at(i).TB.Num;; - TH.Den = at(i).TB.Den; + z_def(&IndexStream, &stream, &IH, sizeof(IndexHeader), 0); - IndexStream.write(reinterpret_cast(&TH), sizeof(TH)); - IndexStream.write(reinterpret_cast(FFMS_GET_VECTOR_PTR(at(i))), TH.Frames * sizeof(TFrameInfo)); + for (unsigned int i = 0; i < IH.Tracks; i++) { + FFMS_Track &ctrack = at(i); + TrackHeader TH; + TH.TT = ctrack.TT; + TH.Frames = ctrack.size(); + TH.Num = ctrack.TB.Num;; + TH.Den = ctrack.TB.Den; + TH.UseDTS = ctrack.UseDTS; + + FFMS_Track temptrack; + temptrack.resize(TH.Frames); + + if (TH.Frames) + temptrack[0] = ctrack[0]; + + for (size_t j = 1; j < ctrack.size(); j++) { + temptrack[j] = ctrack[j]; + temptrack[j].FilePos = ctrack[j].FilePos - ctrack[j - 1].FilePos; + temptrack[j].OriginalPos = ctrack[j].OriginalPos - ctrack[j - 1].OriginalPos; + temptrack[j].PTS = ctrack[j].PTS - ctrack[j - 1].PTS; + temptrack[j].SampleStart = ctrack[j].SampleStart - ctrack[j - 1].SampleStart; + } + + z_def(&IndexStream, &stream, &TH, sizeof(TrackHeader), 0); + if (TH.Frames) + z_def(&IndexStream, &stream, FFMS_GET_VECTOR_PTR(temptrack), TH.Frames * sizeof(TFrameInfo), 0); } + z_def(&IndexStream, &stream, NULL, 0, 1); +} + +static unsigned int z_inf(ffms_fstream *Index, z_stream *stream, void *in, size_t in_sz, void *out, size_t out_sz) { + if (out_sz == 0 || out == 0) return 0; + stream->next_out = (Bytef*) out; + stream->avail_out = out_sz; + + do { + if (stream->avail_in) memmove(in, stream->next_in, stream->avail_in); + Index->read(((char*)in) + stream->avail_in, in_sz - stream->avail_in); + stream->next_in = (Bytef*) in; + stream->avail_in += Index->gcount(); + + switch (inflate(stream, Z_SYNC_FLUSH)) { + case Z_NEED_DICT: + inflateEnd(stream); + throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, "Failed to read data: Dictionary error."); + case Z_DATA_ERROR: + inflateEnd(stream); + throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, "Failed to read data: Data error."); + case Z_MEM_ERROR: + inflateEnd(stream); + throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, "Failed to read data: Memory error."); + case Z_STREAM_END: + inflateEnd(stream); + return out_sz - stream->avail_out; + } + + } while (stream->avail_out); + return out_sz; } void FFMS_Index::ReadIndex(const char *IndexFile) { ffms_fstream Index(IndexFile, std::ios::in | std::ios::binary); - if (!Index.is_open()) { - std::ostringstream buf; - buf << "Failed to open '" << IndexFile << "' for reading"; - throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str()); - } - + if (!Index.is_open()) + throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, + std::string("Failed to open '") + IndexFile + "' for reading"); + + z_stream stream; + memset(&stream, 0, sizeof(z_stream)); + unsigned char in[CHUNK]; + if (inflateInit(&stream) != Z_OK) + throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, + "Failed to initialize zlib"); + // Read the index file header IndexHeader IH; - Index.read(reinterpret_cast(&IH), sizeof(IH)); - if (IH.Id != INDEXID) { - std::ostringstream buf; - buf << "'" << IndexFile << "' is not a valid index file"; - throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str()); - } + z_inf(&Index, &stream, &in, CHUNK, &IH, sizeof(IndexHeader)); - if (IH.Version != FFMS_VERSION) { - std::ostringstream buf; - buf << "'" << IndexFile << "' is not the expected index version"; - throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str()); - } + if (IH.Id != INDEXID) + throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, + std::string("'") + IndexFile + "' is not a valid index file"); + + if (IH.Version != FFMS_VERSION) + throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, + std::string("'") + IndexFile + "' is the expected index version"); if (IH.LAVUVersion != avutil_version() || IH.LAVFVersion != avformat_version() || IH.LAVCVersion != avcodec_version() || IH.LSWSVersion != swscale_version() || - IH.LPPVersion != postproc_version()) { - std::ostringstream buf; - buf << "A different FFmpeg build was used to create '" << IndexFile << "'"; - throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str()); - } + IH.LPPVersion != postproc_version()) + throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, + std::string("A different FFmpeg build was used to create '") + IndexFile + "'"); if (!(IH.Decoder & FFMS_GetEnabledSources())) throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_NOT_AVAILABLE, @@ -345,24 +411,33 @@ void FFMS_Index::ReadIndex(const char *IndexFile) { try { for (unsigned int i = 0; i < IH.Tracks; i++) { TrackHeader TH; - Index.read(reinterpret_cast(&TH), sizeof(TH)); - push_back(FFMS_Track(TH.Num, TH.Den, static_cast(TH.TT))); + z_inf(&Index, &stream, &in, CHUNK, &TH, sizeof(TrackHeader)); + push_back(FFMS_Track(TH.Num, TH.Den, static_cast(TH.TT), TH.UseDTS != 0)); + FFMS_Track &ctrack = at(i); if (TH.Frames) { - at(i).resize(TH.Frames); - Index.read(reinterpret_cast(FFMS_GET_VECTOR_PTR(at(i))), TH.Frames * sizeof(TFrameInfo)); + ctrack.resize(TH.Frames); + z_inf(&Index, &stream, &in, CHUNK, FFMS_GET_VECTOR_PTR(ctrack), TH.Frames * sizeof(TFrameInfo)); + } + + for (size_t j = 1; j < ctrack.size(); j++) { + ctrack[j].FilePos = ctrack[j].FilePos + ctrack[j - 1].FilePos; + ctrack[j].OriginalPos = ctrack[j].OriginalPos + ctrack[j - 1].OriginalPos; + ctrack[j].PTS = ctrack[j].PTS + ctrack[j - 1].PTS; + ctrack[j].SampleStart = ctrack[j].SampleStart + ctrack[j - 1].SampleStart; } } - - } catch (...) { - std::ostringstream buf; - buf << "Unknown error while reading index information in '" << IndexFile << "'"; - throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str()); + } + catch (FFMS_Exception const&) { + throw; + } + catch (...) { + throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, + std::string("Unknown error while reading index information in '") + IndexFile + "'"); } } FFMS_Index::FFMS_Index() { - // this comment documents nothing } FFMS_Index::FFMS_Index(int64_t Filesize, uint8_t Digest[20]) { @@ -387,7 +462,7 @@ void FFMS_Indexer::SetErrorHandling(int ErrorHandling) { } void FFMS_Indexer::SetProgressCallback(TIndexCallback IC, void *ICPrivate) { - this->IC = IC; + this->IC = IC; this->ICPrivate = ICPrivate; } @@ -396,38 +471,67 @@ void FFMS_Indexer::SetAudioNameCallback(TAudioNameCallback ANC, void *ANCPrivate this->ANCPrivate = ANCPrivate; } -FFMS_Indexer *FFMS_Indexer::CreateIndexer(const char *Filename) { +FFMS_Indexer *FFMS_Indexer::CreateIndexer(const char *Filename, enum FFMS_Sources Demuxer) { AVFormatContext *FormatContext = NULL; - if (av_open_input_file(&FormatContext, Filename, NULL, 0, NULL) != 0) { - std::ostringstream buf; - buf << "Can't open '" << Filename << "'"; - throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str()); - } + if (av_open_input_file(&FormatContext, Filename, NULL, 0, NULL) != 0) + throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, + std::string("Can't open '") + Filename + "'"); - // Do matroska indexing instead? - if (!strcmp(FormatContext->iformat->name, "matroska")) { - av_close_input_file(FormatContext); - return new FFMatroskaIndexer(Filename); - } + // Demuxer was not forced, probe for the best one to use + if (Demuxer == FFMS_SOURCE_DEFAULT) { + // Do matroska indexing instead? + if (!strncmp(FormatContext->iformat->name, "matroska", 8)) { + av_close_input_file(FormatContext); + return new FFMatroskaIndexer(Filename); + } #ifdef HAALISOURCE - // Do haali ts indexing instead? - if (HasHaaliMPEG && (!strcmp(FormatContext->iformat->name, "mpeg") || !strcmp(FormatContext->iformat->name, "mpegts"))) { - av_close_input_file(FormatContext); - return new FFHaaliIndexer(Filename, FFMS_SOURCE_HAALIMPEG); - } + // Do haali ts indexing instead? + if (HasHaaliMPEG && (!strcmp(FormatContext->iformat->name, "mpeg") || !strcmp(FormatContext->iformat->name, "mpegts"))) { + av_close_input_file(FormatContext); + return new FFHaaliIndexer(Filename, FFMS_SOURCE_HAALIMPEG); + } - if (HasHaaliOGG && !strcmp(FormatContext->iformat->name, "ogg")) { - av_close_input_file(FormatContext); - return new FFHaaliIndexer(Filename, FFMS_SOURCE_HAALIOGG); - } + if (HasHaaliOGG && !strcmp(FormatContext->iformat->name, "ogg")) { + av_close_input_file(FormatContext); + return new FFHaaliIndexer(Filename, FFMS_SOURCE_HAALIOGG); + } #endif - return new FFLAVFIndexer(Filename, FormatContext); + return new FFLAVFIndexer(Filename, FormatContext); + } + + // someone forced a demuxer, use it + if (Demuxer != FFMS_SOURCE_LAVF) + av_close_input_file(FormatContext); +#if !defined(HAALISOURCE) + if (Demuxer == FFMS_SOURCE_HAALIOGG || Demuxer == FFMS_SOURCE_HAALIMPEG) { + throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_NOT_AVAILABLE, "Your binary was not compiled with support for Haali's DirectShow parsers"); + } +#endif // !defined(HAALISOURCE) + + switch (Demuxer) { + case FFMS_SOURCE_LAVF: + return new FFLAVFIndexer(Filename, FormatContext); +#ifdef HAALISOURCE + case FFMS_SOURCE_HAALIOGG: + if (!HasHaaliOGG) + throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_NOT_AVAILABLE, "Haali's Ogg parser is not available"); + return new FFHaaliIndexer(Filename, FFMS_SOURCE_HAALIOGG); + case FFMS_SOURCE_HAALIMPEG: + if (!HasHaaliMPEG) + throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_NOT_AVAILABLE, "Haali's MPEG PS/TS parser is not available"); + return new FFHaaliIndexer(Filename, FFMS_SOURCE_HAALIMPEG); +#endif + case FFMS_SOURCE_MATROSKA: + return new FFMatroskaIndexer(Filename); + default: + throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_INVALID_ARGUMENT, "Invalid demuxer requested"); + } } -FFMS_Indexer::FFMS_Indexer(const char *Filename) { +FFMS_Indexer::FFMS_Indexer(const char *Filename) : DecodingBuffer(AVCODEC_MAX_AUDIO_FRAME_SIZE * 10) { IndexMask = 0; DumpMask = 0; ErrorHandling = FFMS_IEH_CLEAR_TRACK; @@ -435,36 +539,99 @@ FFMS_Indexer::FFMS_Indexer(const char *Filename) { ICPrivate = NULL; ANC = NULL; ANCPrivate = NULL; - DecodingBuffer = (int16_t *) av_malloc(AVCODEC_MAX_AUDIO_FRAME_SIZE * 5 * sizeof(*DecodingBuffer)); - if (!DecodingBuffer) - throw std::bad_alloc(); FFMS_Index::CalculateFileSignature(Filename, &Filesize, Digest); } FFMS_Indexer::~FFMS_Indexer() { - av_free(DecodingBuffer); + } void FFMS_Indexer::WriteAudio(SharedAudioContext &AudioContext, FFMS_Index *Index, int Track, int DBSize) { // Delay writer creation until after an audio frame has been decoded. This ensures that all parameters are known when writing the headers. - if (DBSize > 0) { - if (!AudioContext.W64Writer) { - FFMS_AudioProperties AP; - FillAP(AP, AudioContext.CodecContext, (*Index)[Track]); - int FNSize = (*ANC)(SourceFile, Track, &AP, NULL, 0, ANCPrivate); - std::vector WName(FNSize); - (*ANC)(SourceFile, Track, &AP, &WName[0], FNSize, ANCPrivate); - std::string WN(&WName[0]); - try { - AudioContext.W64Writer = new Wave64Writer(WN.c_str(), av_get_bits_per_sample_format(AudioContext.CodecContext->sample_fmt), - AudioContext.CodecContext->channels, AudioContext.CodecContext->sample_rate, (AudioContext.CodecContext->sample_fmt == SAMPLE_FMT_FLT) || (AudioContext.CodecContext->sample_fmt == SAMPLE_FMT_DBL)); - } catch (...) { - throw FFMS_Exception(FFMS_ERROR_WAVE_WRITER, FFMS_ERROR_FILE_WRITE, - "Failed to write wave data"); - } + if (DBSize <= 0) return; + + if (!AudioContext.W64Writer) { + FFMS_AudioProperties AP; + FillAP(AP, AudioContext.CodecContext, (*Index)[Track]); + int FNSize = (*ANC)(SourceFile, Track, &AP, NULL, 0, ANCPrivate); + if (FNSize <= 0) { + DumpMask = DumpMask & ~(1 << Track); + return; } - AudioContext.W64Writer->WriteData(DecodingBuffer, DBSize); + std::vector WName(FNSize); + (*ANC)(SourceFile, Track, &AP, &WName[0], FNSize, ANCPrivate); + std::string WN(&WName[0]); + try { + AudioContext.W64Writer = + new Wave64Writer(WN.c_str(), + av_get_bits_per_sample_fmt(AudioContext.CodecContext->sample_fmt), + AudioContext.CodecContext->channels, + AudioContext.CodecContext->sample_rate, + (AudioContext.CodecContext->sample_fmt == AV_SAMPLE_FMT_FLT) || (AudioContext.CodecContext->sample_fmt == AV_SAMPLE_FMT_DBL)); + } catch (...) { + throw FFMS_Exception(FFMS_ERROR_WAVE_WRITER, FFMS_ERROR_FILE_WRITE, + "Failed to write wave data"); + } + } + + AudioContext.W64Writer->WriteData(&DecodingBuffer[0], DBSize); +} + +int64_t FFMS_Indexer::IndexAudioPacket(int Track, AVPacket *Packet, SharedAudioContext &Context, FFMS_Index &TrackIndices) { + AVCodecContext *CodecContext = Context.CodecContext; + int64_t StartSample = Context.CurrentSample; + int Read = 0; + while (Packet->size > 0) { + int dbsize = AVCODEC_MAX_AUDIO_FRAME_SIZE*10; + int Ret = avcodec_decode_audio3(CodecContext, (int16_t *)&DecodingBuffer[0], &dbsize, Packet); + if (Ret < 0) { + if (ErrorHandling == FFMS_IEH_ABORT) { + throw FFMS_Exception(FFMS_ERROR_CODEC, FFMS_ERROR_DECODING, "Audio decoding error"); + } else if (ErrorHandling == FFMS_IEH_CLEAR_TRACK) { + TrackIndices[Track].clear(); + IndexMask &= ~(1 << Track); + } else if (ErrorHandling == FFMS_IEH_STOP_TRACK) { + IndexMask &= ~(1 << Track); + } + break; + } + Packet->size -= Ret; + Packet->data += Ret; + Read += Ret; + + CheckAudioProperties(Track, CodecContext); + + if (dbsize > 0) + Context.CurrentSample += (dbsize * 8) / (av_get_bits_per_sample_fmt(CodecContext->sample_fmt) * CodecContext->channels); + + if (DumpMask & (1 << Track)) + WriteAudio(Context, &TrackIndices, Track, dbsize); + } + Packet->size += Read; + Packet->data -= Read; + return Context.CurrentSample - StartSample; +} + +void FFMS_Indexer::CheckAudioProperties(int Track, AVCodecContext *Context) { + std::map::iterator it = LastAudioProperties.find(Track); + if (it == LastAudioProperties.end()) { + FFMS_AudioProperties &AP = LastAudioProperties[Track]; + AP.SampleRate = Context->sample_rate; + AP.SampleFormat = Context->sample_fmt; + AP.Channels = Context->channels; + } + else if (it->second.SampleRate != Context->sample_rate || + it->second.SampleFormat != Context->sample_fmt || + it->second.Channels != Context->channels) { + std::ostringstream buf; + buf << + "Audio format change detected. This is currently unsupported." + << " Channels: " << it->second.Channels << " -> " << Context->channels << ";" + << " Sample rate: " << it->second.SampleRate << " -> " << Context->sample_rate << ";" + << " Sample format: " << GetLAVCSampleFormatName((AVSampleFormat)it->second.SampleFormat) << " -> " + << GetLAVCSampleFormatName(Context->sample_fmt); + throw FFMS_Exception(FFMS_ERROR_UNSUPPORTED, FFMS_ERROR_DECODING, buf.str()); } } diff --git a/aegisub/libffms/src/core/indexing.h b/aegisub/libffms/src/core/indexing.h index 1e8ca545f..b48ce17c1 100644 --- a/aegisub/libffms/src/core/indexing.h +++ b/aegisub/libffms/src/core/indexing.h @@ -19,8 +19,9 @@ // THE SOFTWARE. #ifndef INDEXING_H -#define INDEXING_H +#define INDEXING_H +#include #include #include "utils.h" #include "wave64writer.h" @@ -38,14 +39,14 @@ #endif - class SharedVideoContext { private: bool FreeCodecContext; public: AVCodecContext *CodecContext; AVCodecParserContext *Parser; - CompressedStream *CS; + AVBitStreamFilterContext *BitStreamFilter; + TrackCompressionContext *TCC; SharedVideoContext(bool FreeCodecContext); ~SharedVideoContext(); @@ -58,7 +59,7 @@ public: AVCodecContext *CodecContext; Wave64Writer *W64Writer; int64_t CurrentSample; - CompressedStream *CS; + TrackCompressionContext *TCC; SharedAudioContext(bool FreeCodecContext); ~SharedAudioContext(); @@ -75,7 +76,7 @@ public: TFrameInfo(); static TFrameInfo VideoFrameInfo(int64_t PTS, int RepeatPict, bool KeyFrame, int64_t FilePos = 0, unsigned int FrameSize = 0); - static TFrameInfo AudioFrameInfo(int64_t PTS, int64_t SampleStart, unsigned int SampleCount, bool KeyFrame, int64_t FilePos = 0, unsigned int FrameSize = 0); + static TFrameInfo AudioFrameInfo(int64_t PTS, int64_t SampleStart, int64_t SampleCount, bool KeyFrame, int64_t FilePos = 0, unsigned int FrameSize = 0); private: TFrameInfo(int64_t PTS, int64_t SampleStart, unsigned int SampleCount, int RepeatPict, bool KeyFrame, int64_t FilePos, unsigned int FrameSize); }; @@ -84,15 +85,15 @@ class FFMS_Track : public std::vector { public: FFMS_TrackType TT; FFMS_TrackTimeBase TB; + bool UseDTS; int FindClosestVideoKeyFrame(int Frame); - int FindClosestAudioKeyFrame(int64_t Sample); int FrameFromPTS(int64_t PTS); int ClosestFrameFromPTS(int64_t PTS); void WriteTimecodes(const char *TimecodeFile); FFMS_Track(); - FFMS_Track(int64_t Num, int64_t Den, FFMS_TrackType TT); + FFMS_Track(int64_t Num, int64_t Den, FFMS_TrackType TT, bool UseDTS = false); }; class FFMS_Index : public std::vector { @@ -113,6 +114,7 @@ public: }; class FFMS_Indexer { + std::map LastAudioProperties; protected: int IndexMask; int DumpMask; @@ -122,14 +124,18 @@ protected: TAudioNameCallback ANC; void *ANCPrivate; const char *SourceFile; - int16_t *DecodingBuffer; + AlignedBuffer DecodingBuffer; + FFMS_Sources Demuxer; + const char *FormatName; int64_t Filesize; uint8_t Digest[20]; void WriteAudio(SharedAudioContext &AudioContext, FFMS_Index *Index, int Track, int DBSize); + void CheckAudioProperties(int Track, AVCodecContext *Context); + int64_t IndexAudioPacket(int Track, AVPacket *Packet, SharedAudioContext &Context, FFMS_Index &TrackIndices); public: - static FFMS_Indexer *CreateIndexer(const char *Filename); + static FFMS_Indexer *CreateIndexer(const char *Filename, enum FFMS_Sources Demuxer = FFMS_SOURCE_DEFAULT); FFMS_Indexer(const char *Filename); virtual ~FFMS_Indexer(); void SetIndexMask(int IndexMask); @@ -141,11 +147,13 @@ public: virtual int GetNumberOfTracks() = 0; virtual FFMS_TrackType GetTrackType(int Track) = 0; virtual const char *GetTrackCodec(int Track) = 0; + virtual FFMS_Sources GetSourceType() = 0; + virtual const char *GetFormatName() = 0; }; class FFLAVFIndexer : public FFMS_Indexer { -private: AVFormatContext *FormatContext; + void ReadTS(const AVPacket &Packet, int64_t &TS, bool &UseDTS); public: FFLAVFIndexer(const char *Filename, AVFormatContext *FormatContext); ~FFLAVFIndexer(); @@ -153,6 +161,8 @@ public: int GetNumberOfTracks(); FFMS_TrackType GetTrackType(int Track); const char *GetTrackCodec(int Track); + const char *GetFormatName(); + FFMS_Sources GetSourceType(); }; class FFMatroskaIndexer : public FFMS_Indexer { @@ -167,6 +177,8 @@ public: int GetNumberOfTracks(); FFMS_TrackType GetTrackType(int Track); const char *GetTrackCodec(int Track); + const char *GetFormatName(); + FFMS_Sources GetSourceType(); }; #ifdef HAALISOURCE @@ -177,9 +189,6 @@ private: CComPtr pMMC; int NumTracks; FFMS_TrackType TrackType[32]; - AVCodec *Codec[32]; - std::vector CodecPrivate[32]; - int CodecPrivateSize[32]; CComQIPtr PropertyBags[32]; int64_t Duration; public: @@ -188,6 +197,8 @@ public: int GetNumberOfTracks(); FFMS_TrackType GetTrackType(int Track); const char *GetTrackCodec(int Track); + const char *GetFormatName(); + FFMS_Sources GetSourceType(); }; #endif // HAALISOURCE diff --git a/aegisub/libffms/src/core/lavfaudio.cpp b/aegisub/libffms/src/core/lavfaudio.cpp index b8879eefe..77abf539a 100644 --- a/aegisub/libffms/src/core/lavfaudio.cpp +++ b/aegisub/libffms/src/core/lavfaudio.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2007-2009 Fredrik Mellbin +// Copyright (c) 2011 Thomas Goyne // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -19,175 +19,69 @@ // THE SOFTWARE. #include "audiosource.h" +#include - - -void FFLAVFAudio::Free(bool CloseCodec) { - if (CloseCodec) - avcodec_close(CodecContext); - if (FormatContext) - av_close_input_file(FormatContext); -} - -FFLAVFAudio::FFLAVFAudio(const char *SourceFile, int Track, FFMS_Index *Index) - : Res(FFSourceResources(this)), FFMS_AudioSource(SourceFile, Index, Track){ - FormatContext = NULL; - AVCodec *Codec = NULL; - AudioTrack = Track; - Frames = (*Index)[AudioTrack]; - +FFLAVFAudio::FFLAVFAudio(const char *SourceFile, int Track, FFMS_Index &Index, int DelayMode) +: FFMS_AudioSource(SourceFile, Index, Track) +, FormatContext(NULL) +{ LAVFOpenFile(SourceFile, FormatContext); - CodecContext = FormatContext->streams[AudioTrack]->codec; + CodecContext.reset(FormatContext->streams[TrackNumber]->codec); + assert(CodecContext); - Codec = avcodec_find_decoder(CodecContext->codec_id); - if (Codec == NULL) - throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_CODEC, - "Audio codec not found"); + AVCodec *Codec = avcodec_find_decoder(CodecContext->codec_id); + try { + if (!Codec) + throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_CODEC, + "Audio codec not found"); - if (avcodec_open(CodecContext, Codec) < 0) - throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_CODEC, - "Could not open audio codec"); - - Res.CloseCodec(true); - - // Always try to decode a frame to make sure all required parameters are known - int64_t Dummy; - DecodeNextAudioBlock(&Dummy); - - if (av_seek_frame(FormatContext, AudioTrack, Frames[0].PTS, AVSEEK_FLAG_BACKWARD) < 0) - av_seek_frame(FormatContext, AudioTrack, Frames[0].PTS, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_ANY); - avcodec_flush_buffers(CodecContext); - - FillAP(AP, CodecContext, Frames); - - if (AP.SampleRate <= 0 || AP.BitsPerSample <= 0) - throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_CODEC, - "Codec returned zero size audio"); - - AudioCache.Initialize((AP.Channels * AP.BitsPerSample) / 8, 50); -} - -void FFLAVFAudio::DecodeNextAudioBlock(int64_t *Count) { - const size_t SizeConst = (av_get_bits_per_sample_format(CodecContext->sample_fmt) * CodecContext->channels) / 8; - int Ret = -1; - *Count = 0; - uint8_t *Buf = DecodingBuffer; - AVPacket Packet, TempPacket; - InitNullPacket(Packet); - InitNullPacket(TempPacket); - - while (av_read_frame(FormatContext, &Packet) >= 0) { - if (Packet.stream_index == AudioTrack) { - TempPacket.data = Packet.data; - TempPacket.size = Packet.size; - - while (TempPacket.size > 0) { - int TempOutputBufSize = AVCODEC_MAX_AUDIO_FRAME_SIZE * 10; - Ret = avcodec_decode_audio3(CodecContext, (int16_t *)Buf, &TempOutputBufSize, &TempPacket); - - if (Ret < 0) {// throw error or something? - av_free_packet(&Packet); - goto Done; - } - - if (Ret > 0) { - TempPacket.size -= Ret; - TempPacket.data += Ret; - Buf += TempOutputBufSize; - if (SizeConst) - *Count += TempOutputBufSize / SizeConst; - } - } - - av_free_packet(&Packet); - goto Done; - } - - av_free_packet(&Packet); + if (avcodec_open(CodecContext, Codec) < 0) + throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_CODEC, + "Could not open audio codec"); + } + catch (...) { + av_close_input_file(FormatContext); + throw; } -Done:; + if (Frames.back().PTS == Frames.front().PTS) + SeekOffset = -1; + else + SeekOffset = 10; + Init(Index, DelayMode); } -void FFLAVFAudio::GetAudio(void *Buf, int64_t Start, int64_t Count) { - GetAudioCheck(Start, Count); +FFLAVFAudio::~FFLAVFAudio() { + av_close_input_file(FormatContext); +} - const int64_t SizeConst = (av_get_bits_per_sample_format(CodecContext->sample_fmt) * CodecContext->channels) / 8; - memset(Buf, 0, static_cast(SizeConst * Count)); +void FFLAVFAudio::Seek() { + size_t TargetPacket = GetSeekablePacketNumber(Frames, PacketNumber); - unsigned int PreDecBlocks = 0; - uint8_t *DstBuf = static_cast(Buf); + if (av_seek_frame(FormatContext, TrackNumber, Frames[TargetPacket].PTS, AVSEEK_FLAG_BACKWARD) < 0) + av_seek_frame(FormatContext, TrackNumber, Frames[TargetPacket].PTS, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_ANY); - // Fill with everything in the cache - int64_t CacheEnd = AudioCache.FillRequest(Start, Count, DstBuf); - // Was everything in the cache? - if (CacheEnd == Start + Count) - return; - - size_t CurrentAudioBlock; - if (CurrentSample != CacheEnd) { - PreDecBlocks = 15; - CurrentAudioBlock = FFMAX((int64_t)Frames.FindClosestAudioKeyFrame(CacheEnd) - PreDecBlocks - 20, (int64_t)0); - - if (CurrentAudioBlock <= PreDecBlocks) { - CurrentAudioBlock = 0; - PreDecBlocks = 0; - } - - // Did the seeking fail? - if (av_seek_frame(FormatContext, AudioTrack, Frames[CurrentAudioBlock].PTS, AVSEEK_FLAG_BACKWARD) < 0) - av_seek_frame(FormatContext, AudioTrack, Frames[CurrentAudioBlock].PTS, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_ANY); - - avcodec_flush_buffers(CodecContext); - - // Pretend we got to the first audio frame when PreDecBlocks = 0 - if (PreDecBlocks > 0) { - AVPacket Packet; - InitNullPacket(Packet); - - // Establish where we actually are - // Trigger on packet PTS difference since groups can otherwise be indistinguishable - int64_t LastPTS = - 1; - while (av_read_frame(FormatContext, &Packet) >= 0) { - if (Packet.stream_index == AudioTrack) { - if (LastPTS < 0) { - LastPTS = Packet.dts; - } else if (LastPTS != Packet.dts) { - for (size_t i = 0; i < Frames.size(); i++) - if (Frames[i].PTS == Packet.dts) { - // The current match was consumed - CurrentAudioBlock = i + 1; - break; - } - - av_free_packet(&Packet); - break; - } - } - - av_free_packet(&Packet); - } - } - } else { - CurrentAudioBlock = Frames.FindClosestAudioKeyFrame(CurrentSample); + if (TargetPacket != PacketNumber) { + // Decode until the PTS changes so we know where we are + int64_t LastPTS = Frames[PacketNumber].PTS; + while (LastPTS == Frames[PacketNumber].PTS) DecodeNextBlock(); } - - int64_t DecodeCount; - - do { - DecodeNextAudioBlock(&DecodeCount); - - // Cache the block if enough blocks before it have been decoded to avoid garbage - if (PreDecBlocks == 0) { - AudioCache.CacheBlock(Frames[CurrentAudioBlock].SampleStart, DecodeCount, DecodingBuffer); - CacheEnd = AudioCache.FillRequest(CacheEnd, Start + Count - CacheEnd, DstBuf + (CacheEnd - Start) * SizeConst); - } else { - PreDecBlocks--; - } - - CurrentAudioBlock++; - if (CurrentAudioBlock < Frames.size()) - CurrentSample = Frames[CurrentAudioBlock].SampleStart; - } while (Start + Count - CacheEnd > 0 && CurrentAudioBlock < Frames.size()); +} + +bool FFLAVFAudio::ReadPacket(AVPacket *Packet) { + InitNullPacket(*Packet); + + while (av_read_frame(FormatContext, Packet) >= 0) { + if (Packet->stream_index == TrackNumber) { + while (PacketNumber > 0 && Frames[PacketNumber].PTS > Packet->pts) --PacketNumber; + while (Frames[PacketNumber].PTS < Packet->pts) ++PacketNumber; + return true; + } + av_free_packet(Packet); + } + return false; +} +void FFLAVFAudio::FreePacket(AVPacket *Packet) { + av_free_packet(Packet); } diff --git a/aegisub/libffms/src/core/lavfindexer.cpp b/aegisub/libffms/src/core/lavfindexer.cpp index fc65376de..e5bedeed9 100644 --- a/aegisub/libffms/src/core/lavfindexer.cpp +++ b/aegisub/libffms/src/core/lavfindexer.cpp @@ -45,27 +45,27 @@ FFMS_Index *FFLAVFIndexer::DoIndexing() { TrackIndices->Decoder = FFMS_SOURCE_LAVF; for (unsigned int i = 0; i < FormatContext->nb_streams; i++) { - TrackIndices->push_back(FFMS_Track((int64_t)FormatContext->streams[i]->time_base.num * 1000, + TrackIndices->push_back(FFMS_Track((int64_t)FormatContext->streams[i]->time_base.num * 1000, FormatContext->streams[i]->time_base.den, static_cast(FormatContext->streams[i]->codec->codec_type))); - if (static_cast(FormatContext->streams[i]->codec->codec_type) == FFMS_TYPE_VIDEO && - (VideoContexts[i].Parser = av_parser_init(FormatContext->streams[i]->codec->codec_id))) { - + if (FormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO) { AVCodec *VideoCodec = avcodec_find_decoder(FormatContext->streams[i]->codec->codec_id); - if (VideoCodec == NULL) + if (!VideoCodec) throw FFMS_Exception(FFMS_ERROR_CODEC, FFMS_ERROR_UNSUPPORTED, "Video codec not found"); - if (avcodec_open(FormatContext->streams[i]->codec, VideoCodec) < 0) + if (avcodec_open(FormatContext->streams[i]->codec, VideoCodec) < 0) throw FFMS_Exception(FFMS_ERROR_CODEC, FFMS_ERROR_DECODING, "Could not open video codec"); VideoContexts[i].CodecContext = FormatContext->streams[i]->codec; - VideoContexts[i].Parser->flags = PARSER_FLAG_COMPLETE_FRAMES; + VideoContexts[i].Parser = av_parser_init(FormatContext->streams[i]->codec->codec_id); + if (VideoContexts[i].Parser) + VideoContexts[i].Parser->flags = PARSER_FLAG_COMPLETE_FRAMES; + IndexMask |= 1 << i; } - - if (IndexMask & (1 << i) && FormatContext->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO) { + else if (IndexMask & (1 << i) && FormatContext->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO) { AVCodecContext *AudioCodecContext = FormatContext->streams[i]->codec; AVCodec *AudioCodec = avcodec_find_decoder(AudioCodecContext->codec_id); @@ -83,71 +83,51 @@ FFMS_Index *FFLAVFIndexer::DoIndexing() { } } - // - - AVPacket Packet, TempPacket; + AVPacket Packet; InitNullPacket(Packet); - InitNullPacket(TempPacket); + std::vector LastValidTS; + LastValidTS.resize(FormatContext->nb_streams, ffms_av_nopts_value); + while (av_read_frame(FormatContext, &Packet) >= 0) { // Update progress - if (IC) { + // FormatContext->pb can apparently be NULL when opening images. + if (IC && FormatContext->pb) { if ((*IC)(FormatContext->pb->pos, FormatContext->file_size, ICPrivate)) throw FFMS_Exception(FFMS_ERROR_CANCELLED, FFMS_ERROR_USER, "Cancelled by user"); } + if (!(IndexMask & (1 << Packet.stream_index))) { + av_free_packet(&Packet); + continue; + } + + int Track = Packet.stream_index; + bool KeyFrame = !!(Packet.flags & AV_PKT_FLAG_KEY); + ReadTS(Packet, LastValidTS[Track], (*TrackIndices)[Track].UseDTS); + + if (FormatContext->streams[Track]->codec->codec_type == CODEC_TYPE_VIDEO) { + if (LastValidTS[Track] == ffms_av_nopts_value) + throw FFMS_Exception(FFMS_ERROR_INDEXING, FFMS_ERROR_PARSER, + "Invalid initial pts and dts"); - // Only create index entries for video for now to save space - if (FormatContext->streams[Packet.stream_index]->codec->codec_type == CODEC_TYPE_VIDEO) { - uint8_t *OB; - int OBSize; int RepeatPict = -1; - if (VideoContexts[Packet.stream_index].Parser) { - av_parser_parse2(VideoContexts[Packet.stream_index].Parser, VideoContexts[Packet.stream_index].CodecContext, &OB, &OBSize, Packet.data, Packet.size, Packet.pts, Packet.dts, Packet.pos); - RepeatPict = VideoContexts[Packet.stream_index].Parser->repeat_pict; + if (VideoContexts[Track].Parser) { + uint8_t *OB; + int OBSize; + av_parser_parse2(VideoContexts[Track].Parser, VideoContexts[Track].CodecContext, &OB, &OBSize, Packet.data, Packet.size, Packet.pts, Packet.dts, Packet.pos); + RepeatPict = VideoContexts[Track].Parser->repeat_pict; } - (*TrackIndices)[Packet.stream_index].push_back(TFrameInfo::VideoFrameInfo(Packet.dts, RepeatPict, (Packet.flags & AV_PKT_FLAG_KEY) ? 1 : 0)); - } else if (FormatContext->streams[Packet.stream_index]->codec->codec_type == CODEC_TYPE_AUDIO && (IndexMask & (1 << Packet.stream_index))) { - int64_t StartSample = AudioContexts[Packet.stream_index].CurrentSample; - AVCodecContext *AudioCodecContext = FormatContext->streams[Packet.stream_index]->codec; - TempPacket.data = Packet.data; - TempPacket.size = Packet.size; - TempPacket.flags = Packet.flags; + (*TrackIndices)[Track].push_back(TFrameInfo::VideoFrameInfo(LastValidTS[Track], RepeatPict, KeyFrame, Packet.pos)); + } + else if (FormatContext->streams[Track]->codec->codec_type == CODEC_TYPE_AUDIO) { + int64_t StartSample = AudioContexts[Track].CurrentSample; + int64_t SampleCount = IndexAudioPacket(Track, &Packet, AudioContexts[Track], *TrackIndices); - while (TempPacket.size > 0) { - int dbsize = AVCODEC_MAX_AUDIO_FRAME_SIZE*10; - int Ret = avcodec_decode_audio3(AudioCodecContext, &DecodingBuffer[0], &dbsize, &TempPacket); - if (Ret < 0) { - if (ErrorHandling == FFMS_IEH_ABORT) { - throw FFMS_Exception(FFMS_ERROR_CODEC, FFMS_ERROR_DECODING, - "Audio decoding error"); - } else if (ErrorHandling == FFMS_IEH_CLEAR_TRACK) { - (*TrackIndices)[Packet.stream_index].clear(); - IndexMask &= ~(1 << Packet.stream_index); - break; - } else if (ErrorHandling == FFMS_IEH_STOP_TRACK) { - IndexMask &= ~(1 << Packet.stream_index); - break; - } else if (ErrorHandling == FFMS_IEH_IGNORE) { - break; - } - } - - if (Ret > 0) { - TempPacket.size -= Ret; - TempPacket.data += Ret; - } - - if (dbsize > 0) - AudioContexts[Packet.stream_index].CurrentSample += (dbsize * 8) / (av_get_bits_per_sample_format(AudioCodecContext->sample_fmt) * AudioCodecContext->channels); - - if (DumpMask & (1 << Packet.stream_index)) - WriteAudio(AudioContexts[Packet.stream_index], TrackIndices.get(), Packet.stream_index, dbsize); - } - - (*TrackIndices)[Packet.stream_index].push_back(TFrameInfo::AudioFrameInfo(Packet.dts, StartSample, - static_cast(AudioContexts[Packet.stream_index].CurrentSample - StartSample), (Packet.flags & AV_PKT_FLAG_KEY) ? 1 : 0)); + if (SampleCount != 0) + (*TrackIndices)[Track].push_back(TFrameInfo::AudioFrameInfo(LastValidTS[Track], + StartSample, SampleCount, KeyFrame, Packet.pos)); } av_free_packet(&Packet); @@ -157,6 +137,15 @@ FFMS_Index *FFLAVFIndexer::DoIndexing() { return TrackIndices.release(); } +void FFLAVFIndexer::ReadTS(const AVPacket &Packet, int64_t &TS, bool &UseDTS) { + if (!UseDTS && Packet.pts != ffms_av_nopts_value) + TS = Packet.pts; + if (TS == ffms_av_nopts_value) + UseDTS = true; + if (UseDTS && Packet.dts != ffms_av_nopts_value) + TS = Packet.dts; +} + int FFLAVFIndexer::GetNumberOfTracks() { return FormatContext->nb_streams; } @@ -165,6 +154,15 @@ FFMS_TrackType FFLAVFIndexer::GetTrackType(int Track) { return static_cast(FormatContext->streams[Track]->codec->codec_type); } -const char *FFLAVFIndexer::GetTrackCodec(int Track) { - return (avcodec_find_decoder(FormatContext->streams[Track]->codec->codec_id))->name; +const char *FFLAVFIndexer::GetTrackCodec(int Track) { + AVCodec *codec = avcodec_find_decoder(FormatContext->streams[Track]->codec->codec_id); + return codec ? codec->name : NULL; +} + +const char *FFLAVFIndexer::GetFormatName() { + return this->FormatContext->iformat->name; +} + +FFMS_Sources FFLAVFIndexer::GetSourceType() { + return FFMS_SOURCE_LAVF; } diff --git a/aegisub/libffms/src/core/lavfvideo.cpp b/aegisub/libffms/src/core/lavfvideo.cpp index 07bc40746..cb2b9bdb2 100644 --- a/aegisub/libffms/src/core/lavfvideo.cpp +++ b/aegisub/libffms/src/core/lavfvideo.cpp @@ -45,7 +45,8 @@ FFLAVFVideo::FFLAVFVideo(const char *SourceFile, int Track, FFMS_Index *Index, "Video track is unseekable"); CodecContext = FormatContext->streams[VideoTrack]->codec; - CodecContext->thread_count = Threads; + if (avcodec_thread_init(CodecContext, Threads)) + CodecContext->thread_count = 1; Codec = avcodec_find_decoder(CodecContext->codec_id); if (Codec == NULL) @@ -82,6 +83,15 @@ FFLAVFVideo::FFLAVFVideo(const char *SourceFile, int Track, FFMS_Index *Index, VP.ColorSpace = 0; VP.ColorRange = 0; #endif + // these pixfmt's are deprecated but still used + if ( + CodecContext->pix_fmt == PIX_FMT_YUVJ420P + || CodecContext->pix_fmt == PIX_FMT_YUVJ422P + || CodecContext->pix_fmt == PIX_FMT_YUVJ444P + ) + VP.ColorRange = AVCOL_RANGE_JPEG; + + VP.FirstTime = ((Frames.front().PTS * Frames.TB.Num) / (double)Frames.TB.Den) / 1000; VP.LastTime = ((Frames.back().PTS * Frames.TB.Num) / (double)Frames.TB.Den) / 1000; @@ -95,12 +105,20 @@ FFLAVFVideo::FFLAVFVideo(const char *SourceFile, int Track, FFMS_Index *Index, VP.FPSNumerator = 30; } - // Adjust framerate to match the duration of the first frame + // Calculate the average framerate if (Frames.size() >= 2) { - unsigned int PTSDiff = (unsigned int)FFMAX(Frames[1].PTS - Frames[0].PTS, 1); - VP.FPSDenominator *= PTSDiff; + double PTSDiff = (double)(Frames.back().PTS - Frames.front().PTS); + double TD = (double)(Frames.TB.Den); + double TN = (double)(Frames.TB.Num); + VP.FPSDenominator = (unsigned int)(((double)1000000) / (double)((VP.NumFrames - 1) / ((PTSDiff * TN/TD) / (double)1000))); + VP.FPSNumerator = 1000000; } + // attempt to correct framerate to the proper NTSC fraction, if applicable + CorrectNTSCRationalFramerate(&VP.FPSNumerator, &VP.FPSDenominator); + // correct the timebase, if necessary + CorrectTimebase(&VP, &Frames.TB); + // Cannot "output" to PPFrame without doing all other initialization // This is the additional mess required for seekmode=-1 to work in a reasonable way OutputFrame(DecodeFrame); @@ -116,22 +134,31 @@ void FFLAVFVideo::DecodeNextFrame(int64_t *AStartTime) { int FrameFinished = 0; *AStartTime = -1; + if (InitialDecode == -1) { + if (DelayCounter > CodecContext->has_b_frames) { + DelayCounter--; + goto Done; + } else { + InitialDecode = 0; + } + } + while (av_read_frame(FormatContext, &Packet) >= 0) { if (Packet.stream_index == VideoTrack) { - if (*AStartTime < 0) - *AStartTime = Packet.dts; + if (*AStartTime < 0) { + if (Frames.UseDTS) + *AStartTime = Packet.dts; + else + *AStartTime = Packet.pts; + } avcodec_decode_video2(CodecContext, DecodeFrame, &FrameFinished, &Packet); - if (CodecContext->codec_id == CODEC_ID_MPEG4) { - if (IsPackedFrame(Packet)) { - MPEG4Counter++; - } else if (IsNVOP(Packet) && MPEG4Counter && FrameFinished) { - MPEG4Counter--; - } else if (IsNVOP(Packet) && !MPEG4Counter && !FrameFinished) { - av_free_packet(&Packet); - goto Done; - } + if (!FrameFinished) + DelayCounter++; + if (DelayCounter > CodecContext->has_b_frames && !InitialDecode) { + av_free_packet(&Packet); + goto Done; } } @@ -152,7 +179,8 @@ void FFLAVFVideo::DecodeNextFrame(int64_t *AStartTime) { goto Error; Error: -Done:; +Done: + if (InitialDecode == 1) InitialDecode = -1; } FFMS_Frame *FFLAVFVideo::GetFrame(int n) { @@ -173,6 +201,8 @@ FFMS_Frame *FFLAVFVideo::GetFrame(int n) { av_seek_frame(FormatContext, VideoTrack, Frames[0].PTS, AVSEEK_FLAG_BACKWARD); avcodec_flush_buffers(CodecContext); CurrentFrame = 0; + DelayCounter = 0; + InitialDecode = 1; } } else { // 10 frames is used as a margin to prevent excessive seeking since the predicted best keyframe isn't always selected by avformat @@ -183,6 +213,8 @@ ReSeek: AVSEEK_FLAG_BACKWARD); avcodec_flush_buffers(CodecContext); HasSeeked = true; + DelayCounter = 0; + InitialDecode = 1; } } } else if (n < CurrentFrame) { @@ -192,11 +224,14 @@ ReSeek: do { int64_t StartTime; + if (CurrentFrame+CodecContext->has_b_frames >= n) + CodecContext->skip_frame = AVDISCARD_DEFAULT; + else + CodecContext->skip_frame = AVDISCARD_NONREF; DecodeNextFrame(&StartTime); if (HasSeeked) { HasSeeked = false; - MPEG4Counter = 0; // Is the seek destination time known? Does it belong to a frame? if (StartTime < 0 || (CurrentFrame = Frames.FrameFromPTS(StartTime)) < 0) { diff --git a/aegisub/libffms/src/core/matroskaaudio.cpp b/aegisub/libffms/src/core/matroskaaudio.cpp index 6974c422e..ec8bd0bac 100644 --- a/aegisub/libffms/src/core/matroskaaudio.cpp +++ b/aegisub/libffms/src/core/matroskaaudio.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2007-2009 Fredrik Mellbin +// Copyright (c) 2011 Thomas Goyne // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -19,168 +19,58 @@ // THE SOFTWARE. #include "audiosource.h" +#include -void FFMatroskaAudio::Free(bool CloseCodec) { - if (CS) - cs_Destroy(CS); - if (MC.ST.fp) { - mkv_Close(MF); - fclose(MC.ST.fp); - } - if (CloseCodec) - avcodec_close(CodecContext); - av_freep(&CodecContext); -} - -FFMatroskaAudio::FFMatroskaAudio(const char *SourceFile, int Track, FFMS_Index *Index) - : Res(FFSourceResources(this)), FFMS_AudioSource(SourceFile, Index, Track) { - CodecContext = NULL; - AVCodec *Codec = NULL; - TrackInfo *TI = NULL; - CS = NULL; - PacketNumber = 0; - Frames = (*Index)[Track]; - - MC.ST.fp = ffms_fopen(SourceFile, "rb"); - if (MC.ST.fp == NULL) { - std::ostringstream buf; - buf << "Can't open '" << SourceFile << "': " << strerror(errno); - throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str()); - } +FFMatroskaAudio::FFMatroskaAudio(const char *SourceFile, int Track, FFMS_Index &Index, int DelayMode) +: FFMS_AudioSource(SourceFile, Index, Track) +, TI(NULL) +{ + if (!(MC.ST.fp = ffms_fopen(SourceFile, "rb"))) + throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, + std::string("Can't open '") + SourceFile + "': " + strerror(errno)); setvbuf(MC.ST.fp, NULL, _IOFBF, CACHESIZE); - MF = mkv_OpenEx(&MC.ST.base, 0, 0, ErrorMessage, sizeof(ErrorMessage)); - if (MF == NULL) { - fclose(MC.ST.fp); - std::ostringstream buf; - buf << "Can't parse Matroska file: " << ErrorMessage; - throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str()); - } - + if (!(MF = mkv_OpenEx(&MC.ST.base, 0, 0, ErrorMessage, sizeof(ErrorMessage)))) + throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, + std::string("Can't parse Matroska file: ") + ErrorMessage); TI = mkv_GetTrackInfo(MF, Track); + assert(TI); - if (TI->CompEnabled) { - CS = cs_Create(MF, Track, ErrorMessage, sizeof(ErrorMessage)); - if (CS == NULL) { - Free(false); - std::ostringstream buf; - buf << "Can't create decompressor: " << ErrorMessage; - throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str()); - } + if (TI->CompEnabled) + TCC.reset(new TrackCompressionContext(MF, TI, Track)); + + CodecContext.reset(avcodec_alloc_context(), DeleteMatroskaCodecContext); + assert(CodecContext); + + AVCodec *Codec = avcodec_find_decoder(MatroskaToFFCodecID(TI->CodecID, TI->CodecPrivate, 0, TI->AV.Audio.BitDepth)); + if (!Codec) { + mkv_Close(MF); + throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_CODEC, "Audio codec not found"); } - CodecContext = avcodec_alloc_context(); - - Codec = avcodec_find_decoder(MatroskaToFFCodecID(TI->CodecID, TI->CodecPrivate, 0, TI->AV.Audio.BitDepth)); - if (Codec == NULL) - throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_CODEC, - "Audio codec not found"); - InitializeCodecContextFromMatroskaTrackInfo(TI, CodecContext); - if (avcodec_open(CodecContext, Codec) < 0) - throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_CODEC, - "Could not open audio codec"); - - Res.CloseCodec(true); - - // Always try to decode a frame to make sure all required parameters are known - int64_t Dummy; - DecodeNextAudioBlock(&Dummy); - - avcodec_flush_buffers(CodecContext); - - FillAP(AP, CodecContext, Frames); - - if (AP.SampleRate <= 0 || AP.BitsPerSample <= 0) - throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_CODEC, - "Codec returned zero size audio"); - - AudioCache.Initialize((AP.Channels * AP.BitsPerSample) / 8, 50); -} - -void FFMatroskaAudio::GetAudio(void *Buf, int64_t Start, int64_t Count) { - GetAudioCheck(Start, Count); - - const int64_t SizeConst = (av_get_bits_per_sample_format(CodecContext->sample_fmt) * CodecContext->channels) / 8; - memset(Buf, 0, static_cast(SizeConst * Count)); - - unsigned int PreDecBlocks = 0; - uint8_t *DstBuf = static_cast(Buf); - - // Fill with everything in the cache - int64_t CacheEnd = AudioCache.FillRequest(Start, Count, DstBuf); - // Was everything in the cache? - if (CacheEnd == Start + Count) - return; - - if (CurrentSample != CacheEnd) { - PreDecBlocks = 15; - PacketNumber = FFMAX((int64_t)Frames.FindClosestAudioKeyFrame(CacheEnd) - PreDecBlocks, (int64_t)0); - - if (PacketNumber <= PreDecBlocks) { - PacketNumber = 0; - PreDecBlocks = 0; - } - - avcodec_flush_buffers(CodecContext); - } - - int64_t DecodeCount; - - do { - const TFrameInfo &FI = Frames[Frames[PacketNumber].OriginalPos]; - DecodeNextAudioBlock(&DecodeCount); - - // Cache the block if enough blocks before it have been decoded to avoid garbage - if (PreDecBlocks == 0) { - AudioCache.CacheBlock(FI.SampleStart, DecodeCount, DecodingBuffer); - CacheEnd = AudioCache.FillRequest(CacheEnd, Start + Count - CacheEnd, DstBuf + (CacheEnd - Start) * SizeConst); - } else { - PreDecBlocks--; - } - - } while (Start + Count - CacheEnd > 0 && PacketNumber < Frames.size()); -} - -void FFMatroskaAudio::DecodeNextAudioBlock(int64_t *Count) { - const size_t SizeConst = (av_get_bits_per_sample_format(CodecContext->sample_fmt) * CodecContext->channels) / 8; - int Ret = -1; - *Count = 0; - uint8_t *Buf = DecodingBuffer; - AVPacket TempPacket; - InitNullPacket(TempPacket); - - const TFrameInfo &FI = Frames[Frames[PacketNumber].OriginalPos]; - unsigned int FrameSize = FI.FrameSize; - CurrentSample = FI.SampleStart + FI.SampleCount; - PacketNumber++; - - ReadFrame(FI.FilePos, FrameSize, CS, MC); - TempPacket.data = MC.Buffer; - TempPacket.size = FrameSize; - if (FI.KeyFrame) - TempPacket.flags = AV_PKT_FLAG_KEY; - else - TempPacket.flags = 0; - - while (TempPacket.size > 0) { - int TempOutputBufSize = AVCODEC_MAX_AUDIO_FRAME_SIZE; - Ret = avcodec_decode_audio3(CodecContext, (int16_t *)Buf, &TempOutputBufSize, &TempPacket); - - if (Ret < 0) // throw error or something? - goto Done; - - if (Ret > 0) { - TempPacket.size -= Ret; - TempPacket.data += Ret; - Buf += TempOutputBufSize; - if (SizeConst) - *Count += TempOutputBufSize / SizeConst; - } + if (avcodec_open(CodecContext, Codec) < 0) { + mkv_Close(MF); + throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_CODEC, "Could not open audio codec"); } -Done:; + Init(Index, DelayMode); +} + +FFMatroskaAudio::~FFMatroskaAudio() { + TCC.reset(); // cs_Destroy() must be called before mkv_Close() + mkv_Close(MF); +} + +bool FFMatroskaAudio::ReadPacket(AVPacket *Packet) { + ReadFrame(CurrentFrame->FilePos, CurrentFrame->FrameSize, TCC.get(), MC); + InitNullPacket(*Packet); + Packet->data = MC.Buffer; + Packet->size = CurrentFrame->FrameSize + ((TCC.get() && TCC->CompressionMethod == COMP_PREPEND) ? TCC->CompressedPrivateDataSize : 0); + Packet->flags = CurrentFrame->KeyFrame ? AV_PKT_FLAG_KEY : 0; + + return true; } diff --git a/aegisub/libffms/src/core/matroskaindexer.cpp b/aegisub/libffms/src/core/matroskaindexer.cpp index a83dd6e79..72bd4c9f3 100644 --- a/aegisub/libffms/src/core/matroskaindexer.cpp +++ b/aegisub/libffms/src/core/matroskaindexer.cpp @@ -42,7 +42,6 @@ FFMatroskaIndexer::FFMatroskaIndexer(const char *Filename) : FFMS_Indexer(Filena MF = mkv_OpenEx(&MC.ST.base, 0, 0, ErrorMessage, sizeof(ErrorMessage)); if (MF == NULL) { - fclose(MC.ST.fp); std::ostringstream buf; buf << "Can't parse Matroska file: " << ErrorMessage; throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str()); @@ -56,11 +55,9 @@ FFMatroskaIndexer::FFMatroskaIndexer(const char *Filename) : FFMS_Indexer(Filena FFMatroskaIndexer::~FFMatroskaIndexer() { mkv_Close(MF); - fclose(MC.ST.fp); } FFMS_Index *FFMatroskaIndexer::DoIndexing() { - char ErrorMessage[256]; std::vector AudioContexts(mkv_GetNumTracks(MF), SharedAudioContext(true)); std::vector VideoContexts(mkv_GetNumTracks(MF), SharedVideoContext(true)); @@ -71,68 +68,43 @@ FFMS_Index *FFMatroskaIndexer::DoIndexing() { TrackInfo *TI = mkv_GetTrackInfo(MF, i); TrackIndices->push_back(FFMS_Track(mkv_TruncFloat(mkv_GetTrackInfo(MF, i)->TimecodeScale), 1000000, HaaliTrackTypeToFFTrackType(mkv_GetTrackInfo(MF, i)->Type))); - if (HaaliTrackTypeToFFTrackType(TI->Type) == FFMS_TYPE_VIDEO && Codec[i] && (VideoContexts[i].Parser = av_parser_init(Codec[i]->id))) { + if (!Codec[i]) continue; - AVCodecContext *CodecContext = avcodec_alloc_context(); + AVCodecContext *CodecContext = avcodec_alloc_context(); + InitializeCodecContextFromMatroskaTrackInfo(TI, CodecContext); - InitializeCodecContextFromMatroskaTrackInfo(TI, CodecContext); + try { + if (TI->Type == TT_VIDEO && (VideoContexts[i].Parser = av_parser_init(Codec[i]->id))) { + if (avcodec_open(CodecContext, Codec[i]) < 0) + throw FFMS_Exception(FFMS_ERROR_CODEC, FFMS_ERROR_DECODING, + "Could not open video codec"); - if (avcodec_open(CodecContext, Codec[i]) < 0) { + if (TI->CompEnabled) + VideoContexts[i].TCC = new TrackCompressionContext(MF, TI, i); + + VideoContexts[i].CodecContext = CodecContext; + VideoContexts[i].Parser->flags = PARSER_FLAG_COMPLETE_FRAMES; + } + else if (IndexMask & (1 << i) && TI->Type == TT_AUDIO) { + if (avcodec_open(CodecContext, Codec[i]) < 0) + throw FFMS_Exception(FFMS_ERROR_CODEC, FFMS_ERROR_DECODING, + "Could not open audio codec"); + + if (TI->CompEnabled) + AudioContexts[i].TCC = new TrackCompressionContext(MF, TI, i); + + AudioContexts[i].CodecContext = CodecContext; + } else { + IndexMask &= ~(1 << i); av_freep(&CodecContext); - throw FFMS_Exception(FFMS_ERROR_CODEC, FFMS_ERROR_DECODING, - "Could not open video codec"); } - - if (TI->CompEnabled) { - VideoContexts[i].CS = cs_Create(MF, i, ErrorMessage, sizeof(ErrorMessage)); - if (VideoContexts[i].CS == NULL) { - std::ostringstream buf; - buf << "Can't create decompressor: " << ErrorMessage; - throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str()); - } - } - - VideoContexts[i].CodecContext = CodecContext; - VideoContexts[i].Parser->flags = PARSER_FLAG_COMPLETE_FRAMES; } - - if (IndexMask & (1 << i) && TI->Type == TT_AUDIO) { - AVCodecContext *AudioCodecContext = avcodec_alloc_context(); - InitializeCodecContextFromMatroskaTrackInfo(TI, AudioCodecContext); - AudioContexts[i].CodecContext = AudioCodecContext; - - if (TI->CompEnabled) { - AudioContexts[i].CS = cs_Create(MF, i, ErrorMessage, sizeof(ErrorMessage)); - if (AudioContexts[i].CS == NULL) { - av_freep(&AudioCodecContext); - AudioContexts[i].CodecContext = NULL; - std::ostringstream buf; - buf << "Can't create decompressor: " << ErrorMessage; - throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str()); - } - } - - AVCodec *AudioCodec = Codec[i]; - if (AudioCodec == NULL) { - av_freep(&AudioCodecContext); - AudioContexts[i].CodecContext = NULL; - throw FFMS_Exception(FFMS_ERROR_CODEC, FFMS_ERROR_UNSUPPORTED, - "Audio codec not found"); - } - - if (avcodec_open(AudioCodecContext, AudioCodec) < 0) { - av_freep(&AudioCodecContext); - AudioContexts[i].CodecContext = NULL; - throw FFMS_Exception(FFMS_ERROR_CODEC, FFMS_ERROR_DECODING, - "Could not open audio codec"); - } - } else { - IndexMask &= ~(1 << i); + catch (...) { + av_freep(&CodecContext); + throw; } } - // - ulonglong StartTime, EndTime, FilePos; unsigned int Track, FrameFlags, FrameSize; AVPacket TempPacket; @@ -140,13 +112,9 @@ FFMS_Index *FFMatroskaIndexer::DoIndexing() { while (mkv_ReadFrame(MF, 0, &Track, &StartTime, &EndTime, &FilePos, &FrameSize, &FrameFlags) == 0) { // Update progress - if (IC) { - if ((*IC)(ftello(MC.ST.fp), Filesize, ICPrivate)) - throw FFMS_Exception(FFMS_ERROR_CANCELLED, FFMS_ERROR_USER, - "Cancelled by user"); - } - - // Only create index entries for video for now to save space + if (IC && (*IC)(ftello(MC.ST.fp), Filesize, ICPrivate)) + throw FFMS_Exception(FFMS_ERROR_CANCELLED, FFMS_ERROR_USER, "Cancelled by user"); + if (mkv_GetTrackInfo(MF, Track)->Type == TT_VIDEO) { uint8_t *OB; int OBSize; @@ -159,50 +127,19 @@ FFMS_Index *FFMatroskaIndexer::DoIndexing() { (*TrackIndices)[Track].push_back(TFrameInfo::VideoFrameInfo(StartTime, RepeatPict, (FrameFlags & FRAME_KF) != 0, FilePos, FrameSize)); } else if (mkv_GetTrackInfo(MF, Track)->Type == TT_AUDIO && (IndexMask & (1 << Track))) { - int64_t StartSample = AudioContexts[Track].CurrentSample; + TrackCompressionContext *TCC = AudioContexts[Track].TCC; unsigned int CompressedFrameSize = FrameSize; - AVCodecContext *AudioCodecContext = AudioContexts[Track].CodecContext; - ReadFrame(FilePos, FrameSize, AudioContexts[Track].CS, MC); + ReadFrame(FilePos, FrameSize, TCC, MC); TempPacket.data = MC.Buffer; - TempPacket.size = FrameSize; - if ((FrameFlags & FRAME_KF) != 0) - TempPacket.flags = AV_PKT_FLAG_KEY; - else - TempPacket.flags = 0; + TempPacket.size = (TCC && TCC->CompressionMethod == COMP_PREPEND) ? FrameSize + TCC->CompressedPrivateDataSize : FrameSize; + TempPacket.flags = FrameFlags & FRAME_KF ? AV_PKT_FLAG_KEY : 0; - while (TempPacket.size > 0) { - int dbsize = AVCODEC_MAX_AUDIO_FRAME_SIZE*10; - int Ret = avcodec_decode_audio3(AudioCodecContext, DecodingBuffer, &dbsize, &TempPacket); - if (Ret < 0) { - if (ErrorHandling == FFMS_IEH_ABORT) { - throw FFMS_Exception(FFMS_ERROR_CODEC, FFMS_ERROR_DECODING, - "Audio decoding error"); - } else if (ErrorHandling == FFMS_IEH_CLEAR_TRACK) { - (*TrackIndices)[Track].clear(); - IndexMask &= ~(1 << Track); - break; - } else if (ErrorHandling == FFMS_IEH_STOP_TRACK) { - IndexMask &= ~(1 << Track); - break; - } else if (ErrorHandling == FFMS_IEH_IGNORE) { - break; - } - } + int64_t StartSample = AudioContexts[Track].CurrentSample; + int64_t SampleCount = IndexAudioPacket(Track, &TempPacket, AudioContexts[Track], *TrackIndices); - if (Ret > 0) { - TempPacket.size -= Ret; - TempPacket.data += Ret; - } - - if (dbsize > 0) - AudioContexts[Track].CurrentSample += (dbsize * 8) / (av_get_bits_per_sample_format(AudioCodecContext->sample_fmt) * AudioCodecContext->channels); - - if (DumpMask & (1 << Track)) - WriteAudio(AudioContexts[Track], TrackIndices.get(), Track, dbsize); - } - - (*TrackIndices)[Track].push_back(TFrameInfo::AudioFrameInfo(StartTime, StartSample, - static_cast(AudioContexts[Track].CurrentSample - StartSample), (FrameFlags & FRAME_KF) != 0, FilePos, CompressedFrameSize)); + if (SampleCount != 0) + (*TrackIndices)[Track].push_back(TFrameInfo::AudioFrameInfo(StartTime, StartSample, + SampleCount, (FrameFlags & FRAME_KF) != 0, FilePos, CompressedFrameSize)); } } @@ -219,8 +156,14 @@ FFMS_TrackType FFMatroskaIndexer::GetTrackType(int Track) { } const char *FFMatroskaIndexer::GetTrackCodec(int Track) { - if (Codec[Track]) - return Codec[Track]->name; - else - return "Unsupported codec/Unknown codec name"; + return Codec[Track] ? Codec[Track]->name : NULL; } + +const char *FFMatroskaIndexer::GetFormatName() { + return "matroska"; +} + +FFMS_Sources FFMatroskaIndexer::GetSourceType() { + return FFMS_SOURCE_MATROSKA; +} + diff --git a/aegisub/libffms/src/core/matroskaparser.c b/aegisub/libffms/src/core/matroskaparser.c index dcf9fb76c..8a7b22b9d 100644 --- a/aegisub/libffms/src/core/matroskaparser.c +++ b/aegisub/libffms/src/core/matroskaparser.c @@ -1,32 +1,38 @@ /* - * Copyright (c) 2004-2009 Mike Matsnev. All Rights Reserved. + * Copyright (c) 2004-2008 Mike Matsnev. All Rights Reserved. * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice immediately at the beginning of the file, without modification, - * this list of conditions, and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Absolutely no warranty of function or purpose is made by the author - * Mike Matsnev. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: * + * 1. Redistributions of source code must retain the above copyright + * notice immediately at the beginning of the file, without modification, + * this list of conditions, and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Absolutely no warranty of function or purpose is made by the author + * Mike Matsnev. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $Id: MatroskaParser.c,v 1.75 2010/08/14 08:38:41 mike Exp $ + * */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + #include #include #include @@ -34,11 +40,19 @@ #include #ifdef _WIN32 +#ifdef _MSC_VER // MS names some functions differently #define alloca _alloca #define inline __inline - +#else /* _MSC_VER */ +// support for building with MinGW on Windows +#include +#endif /* _MSC_VER */ #include +#endif /* _WIN32 */ + +#ifdef HAVE_ALLOCA_H +#include #endif #ifndef EVCBUG @@ -56,6 +70,7 @@ #define EBML_MAX_SIZE_LENGTH 8 #define MATROSKA_VERSION 2 #define MATROSKA_DOCTYPE "matroska" +#define WEBM_DOCTYPE "webm" #define MAX_STRING_LEN 1023 #define QSEGSIZE 512 @@ -707,12 +722,22 @@ static inline ulonglong readVLUInt(MatroskaFile *mf) { return readVLUIntImp(mf,NULL); } -static ulonglong readSize(MatroskaFile *mf) { +static ulonglong readSizeUnspec(MatroskaFile *mf) { int m; ulonglong v = readVLUIntImp(mf,&m); // see if it's unspecified if (v == (MAXU64 >> (57-m*7))) + return MAXU64; + + return v; +} + +static ulonglong readSize(MatroskaFile *mf) { + ulonglong v = readSizeUnspec(mf); + + // see if it's unspecified + if (v == MAXU64) errorjmp(mf,"Unspecified element size is not supported here."); return v; @@ -860,7 +885,7 @@ static void readLangCC(MatroskaFile *mf, ulonglong len, char lcc[4]) { /////////////////////////////////////////////////////////////////////////// // file parser -#define FOREACH(f,tl) \ +#define FOREACH2(f,tl,clid) \ { \ ulonglong tmplen = (tl); \ { \ @@ -869,14 +894,18 @@ static void readLangCC(MatroskaFile *mf, ulonglong len, char lcc[4]) { int id; \ for (;;) { \ cur = filepos(mf); \ - if (cur == start + tmplen) \ + if (tmplen != MAXU64 && cur == start + tmplen) \ break; \ id = readID(f); \ if (id==EOF) \ errorjmp(mf,"Unexpected EOF while reading EBML container"); \ - len = readSize(mf); \ + len = id == clid ? readSizeUnspec(mf) : readSize(mf); \ switch (id) { +#define FOREACH(f,tl) FOREACH2(f,tl,EOF) + +#define RESTART() (tmplen=len),(start=cur) + #define ENDFOR1(f) \ default: \ skipbytes(f,len); \ @@ -939,7 +968,7 @@ static void parseEBML(MatroskaFile *mf,ulonglong toplen) { break; case 0x4282: // DocType readString(mf,len,buf,sizeof(buf)); - if (strcmp(buf,MATROSKA_DOCTYPE)) + if (strcmp(buf,MATROSKA_DOCTYPE) != 0 && strcmp(buf,WEBM_DOCTYPE) != 0) errorjmp(mf,"Unsupported DocType: %s",buf); break; case 0x4287: // DocTypeVersion @@ -1074,31 +1103,57 @@ static void parseSegmentInfo(MatroskaFile *mf,ulonglong toplen) { } static void parseFirstCluster(MatroskaFile *mf,ulonglong toplen) { - ulonglong end = filepos(mf) + toplen; + int seenTimecode = 0, seenBlock = 0; + longlong tc; + ulonglong clstart = filepos(mf); mf->seen.Cluster = 1; mf->firstTimecode = 0; - FOREACH(mf,toplen) + FOREACH2(mf,toplen,0x1f43b675) case 0xe7: // Timecode - mf->firstTimecode += readUInt(mf,(unsigned)len); + tc = readUInt(mf,(unsigned)len); + if (!seenTimecode) { + seenTimecode = 1; + mf->firstTimecode += tc; + } + + if (seenBlock) { +out: + if (toplen != MAXU64) + skipbytes(mf,clstart + toplen - filepos(mf)); + else if (len != MAXU64) + skipbytes(mf,cur + len - filepos(mf)); + return; + } break; case 0xa3: // BlockEx readVLUInt(mf); // track number - mf->firstTimecode += readSInt(mf, 2); + tc = readSInt(mf, 2); + if (!seenBlock) { + seenBlock = 1; + mf->firstTimecode += tc; + } - skipbytes(mf,end - filepos(mf)); - return; + if (seenTimecode) + goto out; + break; case 0xa0: // BlockGroup FOREACH(mf,len) case 0xa1: // Block readVLUInt(mf); // track number - mf->firstTimecode += readSInt(mf,2); + tc = readSInt(mf,2); + if (!seenBlock) { + seenBlock = 1; + mf->firstTimecode += tc; + } - skipbytes(mf,end - filepos(mf)); - return; + if (seenTimecode) + goto out; ENDFOR(mf); break; + case 0x1f43b675: + return; ENDFOR(mf); } @@ -1148,14 +1203,10 @@ static void parseVideoInfo(MatroskaFile *mf,ulonglong toplen,struct TrackInfo *t break; case 0x54b2: // DisplayUnit v = readUInt(mf,(unsigned)len); - if (v>2) - errorjmp(mf,"Invalid DisplayUnit: %d",(int)v); ti->AV.Video.DisplayUnit = (unsigned char)v; break; case 0x54b3: // AspectRatioType v = readUInt(mf,(unsigned)len); - if (v>2) - errorjmp(mf,"Invalid AspectRatioType: %d",(int)v); ti->AV.Video.AspectRatioType = (unsigned char)v; break; case 0x54aa: // PixelCropBottom @@ -1431,7 +1482,7 @@ static void parseTrackEntry(MatroskaFile *mf,ulonglong toplen) { errorjmp(mf, "inflateInit failed"); zs.next_in = (Bytef *)cp; - zs.avail_in = cplen; + zs.avail_in = (uInt)cplen; do { zs.next_out = tmp; @@ -1449,7 +1500,7 @@ static void parseTrackEntry(MatroskaFile *mf,ulonglong toplen) { inflateReset(&zs); zs.next_in = (Bytef *)cp; - zs.avail_in = cplen; + zs.avail_in = (uInt)cplen; zs.next_out = ncp; zs.avail_out = ncplen; @@ -1988,7 +2039,7 @@ static void parseSegment(MatroskaFile *mf,ulonglong toplen) { mf->flags &= ~MPF_ERROR; else { // we want to read data until we find a seekhead or a trackinfo - FOREACH(mf,toplen) + FOREACH2(mf,toplen,0x1f43b675) case 0x114d9b74: // SeekHead if (mf->flags & MKVF_AVOID_SEEKS) { skipbytes(mf,len); @@ -2019,9 +2070,10 @@ static void parseSegment(MatroskaFile *mf,ulonglong toplen) { case 0x1f43b675: // Cluster if (!mf->pCluster) mf->pCluster = cur; - if (mf->seen.Cluster) - skipbytes(mf,len); - else + if (mf->seen.Cluster) { + if (len != MAXU64) + skipbytes(mf,len); + } else parseFirstCluster(mf,len); break; case 0x1654ae6b: // Tracks @@ -2347,8 +2399,8 @@ static int readMoreBlocks(MatroskaFile *mf) { if (cid == EOF) goto ex; if (cid == 0x1f43b675) { - toplen = readSize(mf); - if (toplen < MAXCLUSTER) { + toplen = readSizeUnspec(mf); + if (toplen < MAXCLUSTER || toplen == MAXU64) { // reset error flags mf->flags &= ~MPF_ERROR; ret = RBRESYNC; @@ -2373,12 +2425,15 @@ static int readMoreBlocks(MatroskaFile *mf) { ret = EOF; break; } - toplen = readSize(mf); + toplen = cid == 0x1f43b675 ? readSizeUnspec(mf) : readSize(mf); if (cid == 0x1f43b675) { // Cluster unsigned char have_timecode = 0; - FOREACH(mf,toplen) + FOREACH2(mf,toplen,0x1f43b675) + case 0x1f43b675: + RESTART(); + break; case 0xe7: // Timecode mf->tcCluster = readUInt(mf,(unsigned)len); have_timecode = 1; @@ -2518,7 +2573,10 @@ static void reindex(MatroskaFile *mf) { if (id != 0x1f43b675) // shouldn't happen continue; - size = readVLUInt(mf); + size = readSizeUnspec(mf); + if (size == MAXU64) + break; + if (size >= MAXCLUSTER || size < 1024) continue; @@ -2651,7 +2709,6 @@ static void parseFile(MatroskaFile *mf) { ulonglong len = filepos(mf), adjust; unsigned i; int id = readID(mf); - int m; if (id==EOF) errorjmp(mf,"Unexpected EOF at start of file"); @@ -2672,12 +2729,11 @@ static void parseFile(MatroskaFile *mf) { if (id==EOF) errorjmp(mf,"No segments found in the file"); segment: - len = readVLUIntImp(mf,&m); - // see if it's unspecified - if (len == (MAXU64 >> (57-m*7))) - len = MAXU64; + len = readSizeUnspec(mf); if (id == 0x18538067) // Segment break; + if (len == MAXU64) + errorjmp(mf,"No segments found in the file"); skipbytes(mf,len); } diff --git a/aegisub/libffms/src/core/matroskaparser.h b/aegisub/libffms/src/core/matroskaparser.h index 3b7032799..b3e1f75ad 100644 --- a/aegisub/libffms/src/core/matroskaparser.h +++ b/aegisub/libffms/src/core/matroskaparser.h @@ -1,30 +1,32 @@ /* - * Copyright (c) 2004-2009 Mike Matsnev. All Rights Reserved. + * Copyright (c) 2004-2008 Mike Matsnev. All Rights Reserved. * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice immediately at the beginning of the file, without modification, - * this list of conditions, and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Absolutely no warranty of function or purpose is made by the author - * Mike Matsnev. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: * + * 1. Redistributions of source code must retain the above copyright + * notice immediately at the beginning of the file, without modification, + * this list of conditions, and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Absolutely no warranty of function or purpose is made by the author + * Mike Matsnev. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $Id: MatroskaParser.h,v 1.22 2008/04/29 21:03:09 mike Exp $ + * */ #ifndef MATROSKA_PARSER_H diff --git a/aegisub/libffms/src/core/matroskavideo.cpp b/aegisub/libffms/src/core/matroskavideo.cpp index a815c6fac..d2a7ddc07 100644 --- a/aegisub/libffms/src/core/matroskavideo.cpp +++ b/aegisub/libffms/src/core/matroskavideo.cpp @@ -23,11 +23,10 @@ void FFMatroskaVideo::Free(bool CloseCodec) { - if (CS) - cs_Destroy(CS); + if (TCC) + delete TCC; if (MC.ST.fp) { mkv_Close(MF); - fclose(MC.ST.fp); } if (CloseCodec) avcodec_close(CodecContext); @@ -41,7 +40,7 @@ FFMatroskaVideo::FFMatroskaVideo(const char *SourceFile, int Track, AVCodec *Codec = NULL; CodecContext = NULL; TrackInfo *TI = NULL; - CS = NULL; + TCC = NULL; PacketNumber = 0; VideoTrack = Track; Frames = (*Index)[VideoTrack]; @@ -57,7 +56,6 @@ FFMatroskaVideo::FFMatroskaVideo(const char *SourceFile, int Track, MF = mkv_OpenEx(&MC.ST.base, 0, 0, ErrorMessage, sizeof(ErrorMessage)); if (MF == NULL) { - fclose(MC.ST.fp); std::ostringstream buf; buf << "Can't parse Matroska file: " << ErrorMessage; throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str()); @@ -65,18 +63,12 @@ FFMatroskaVideo::FFMatroskaVideo(const char *SourceFile, int Track, TI = mkv_GetTrackInfo(MF, VideoTrack); - if (TI->CompEnabled) { - CS = cs_Create(MF, VideoTrack, ErrorMessage, sizeof(ErrorMessage)); - if (CS == NULL) { - Free(false); - std::ostringstream buf; - buf << "Can't create decompressor: " << ErrorMessage; - throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str()); - } - } + if (TI->CompEnabled) + TCC = new TrackCompressionContext(MF, TI, VideoTrack); CodecContext = avcodec_alloc_context(); - CodecContext->thread_count = Threads; + if (avcodec_thread_init(CodecContext, Threads)) + CodecContext->thread_count = 1; Codec = avcodec_find_decoder(MatroskaToFFCodecID(TI->CodecID, TI->CodecPrivate)); if (Codec == NULL) @@ -113,6 +105,14 @@ FFMatroskaVideo::FFMatroskaVideo(const char *SourceFile, int Track, VP.ColorSpace = 0; VP.ColorRange = 0; #endif + // these pixfmt's are deprecated but still used + if ( + CodecContext->pix_fmt == PIX_FMT_YUVJ420P + || CodecContext->pix_fmt == PIX_FMT_YUVJ422P + || CodecContext->pix_fmt == PIX_FMT_YUVJ444P + ) + VP.ColorRange = AVCOL_RANGE_JPEG; + VP.FirstTime = ((Frames.front().PTS * Frames.TB.Num) / (double)Frames.TB.Den) / 1000; VP.LastTime = ((Frames.back().PTS * Frames.TB.Num) / (double)Frames.TB.Den) / 1000; @@ -127,6 +127,11 @@ FFMatroskaVideo::FFMatroskaVideo(const char *SourceFile, int Track, VP.FPSNumerator = 1000000; } + // attempt to correct framerate to the proper NTSC fraction, if applicable + CorrectNTSCRationalFramerate(&VP.FPSNumerator, &VP.FPSDenominator); + // correct the timebase, if necessary + CorrectTimebase(&VP, &Frames.TB); + // Output the already decoded frame so it isn't wasted OutputFrame(DecodeFrame); @@ -147,6 +152,15 @@ void FFMatroskaVideo::DecodeNextFrame() { InitNullPacket(Packet); unsigned int FrameSize; + + if (InitialDecode == -1) { + if (DelayCounter > CodecContext->has_b_frames) { + DelayCounter--; + goto Done; + } else { + InitialDecode = 0; + } + } while (PacketNumber < Frames.size()) { // The additional indirection is because the packets are stored in @@ -154,10 +168,10 @@ void FFMatroskaVideo::DecodeNextFrame() { // in the other sources where less is done manually const TFrameInfo &FI = Frames[Frames[PacketNumber].OriginalPos]; FrameSize = FI.FrameSize; - ReadFrame(FI.FilePos, FrameSize, CS, MC); + ReadFrame(FI.FilePos, FrameSize, TCC, MC); Packet.data = MC.Buffer; - Packet.size = FrameSize; + Packet.size = (TCC && TCC->CompressionMethod == COMP_PREPEND) ? FrameSize + TCC->CompressedPrivateDataSize : FrameSize; if (FI.KeyFrame) Packet.flags = AV_PKT_FLAG_KEY; else @@ -167,15 +181,10 @@ void FFMatroskaVideo::DecodeNextFrame() { avcodec_decode_video2(CodecContext, DecodeFrame, &FrameFinished, &Packet); - if (CodecContext->codec_id == CODEC_ID_MPEG4) { - if (IsPackedFrame(Packet)) { - MPEG4Counter++; - } else if (IsNVOP(Packet) && MPEG4Counter && FrameFinished) { - MPEG4Counter--; - } else if (IsNVOP(Packet) && !MPEG4Counter && !FrameFinished) { - goto Done; - } - } + if (!FrameFinished) + DelayCounter++; + if (DelayCounter > CodecContext->has_b_frames && !InitialDecode) + goto Done; if (FrameFinished) goto Done; @@ -192,7 +201,8 @@ void FFMatroskaVideo::DecodeNextFrame() { goto Error; Error: -Done:; +Done: + if (InitialDecode == 1) InitialDecode = -1; } FFMS_Frame *FFMatroskaVideo::GetFrame(int n) { @@ -203,13 +213,18 @@ FFMS_Frame *FFMatroskaVideo::GetFrame(int n) { int ClosestKF = Frames.FindClosestVideoKeyFrame(n); if (CurrentFrame > n || ClosestKF > CurrentFrame + 10) { - MPEG4Counter = 0; + DelayCounter = 0; + InitialDecode = 1; PacketNumber = ClosestKF; CurrentFrame = ClosestKF; avcodec_flush_buffers(CodecContext); } do { + if (CurrentFrame+CodecContext->has_b_frames >= n) + CodecContext->skip_frame = AVDISCARD_DEFAULT; + else + CodecContext->skip_frame = AVDISCARD_NONREF; DecodeNextFrame(); CurrentFrame++; } while (CurrentFrame <= n); diff --git a/aegisub/libffms/src/core/stdiostream.h b/aegisub/libffms/src/core/stdiostream.h index 7d1cf0607..116b0e089 100644 --- a/aegisub/libffms/src/core/stdiostream.h +++ b/aegisub/libffms/src/core/stdiostream.h @@ -21,6 +21,8 @@ #ifndef STDIOSTREAM_H #define STDIOSTREAM_H +#undef __STRICT_ANSI__ + #include #include #include diff --git a/aegisub/libffms/src/core/utils.cpp b/aegisub/libffms/src/core/utils.cpp index 9c0c31a87..7eb470eec 100644 --- a/aegisub/libffms/src/core/utils.cpp +++ b/aegisub/libffms/src/core/utils.cpp @@ -20,15 +20,18 @@ #include #include - #include "utils.h" #include "indexing.h" -#ifdef FFMS_USE_UTF8_PATHS +#ifdef _WIN32 # define WIN32_LEAN_AND_MEAN # include -#endif - +# include +# include +extern "C" { +# include "libavutil/avstring.h" +} +#endif // _WIN32 // Export the array but not its data type... fun... @@ -42,9 +45,15 @@ extern const AVCodecTag ff_codec_bmp_tags[]; extern const CodecTags ff_mkv_codec_tags[]; extern const AVCodecTag ff_codec_movvideo_tags[]; extern const AVCodecTag ff_codec_wav_tags[]; + +/* if you have this, we'll assume you have a new enough libavutil too */ +#if LIBSWSCALE_VERSION_INT >= AV_VERSION_INT(0, 12, 0) +# include +#endif } extern int CPUFeatures; +extern bool GlobalUseUTF8Paths; @@ -65,7 +74,7 @@ int FFMS_Exception::CopyOut(FFMS_ErrorInfo *ErrorInfo) const { if (ErrorInfo) { ErrorInfo->ErrorType = _ErrorType; ErrorInfo->SubType = _SubType; - + if (ErrorInfo->BufferSize > 0) { memset(ErrorInfo->Buffer, 0, ErrorInfo->BufferSize); _Message.copy(ErrorInfo->Buffer, ErrorInfo->BufferSize - 1); @@ -75,8 +84,36 @@ int FFMS_Exception::CopyOut(FFMS_ErrorInfo *ErrorInfo) const { return (_ErrorType << 16) | _SubType; } -int GetSWSCPUFlags() { - int Flags = 0; +TrackCompressionContext::TrackCompressionContext(MatroskaFile *MF, TrackInfo *TI, unsigned int Track) { + CS = NULL; + CompressedPrivateData = NULL; + CompressedPrivateDataSize = 0; + CompressionMethod = TI->CompMethod; + + if (CompressionMethod == COMP_ZLIB) { + char ErrorMessage[512]; + CS = cs_Create(MF, Track, ErrorMessage, sizeof(ErrorMessage)); + if (CS == NULL) { + std::ostringstream buf; + buf << "Can't create MKV track decompressor: " << ErrorMessage; + throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str()); + } + } else if (CompressionMethod == COMP_PREPEND) { + CompressedPrivateData = TI->CompMethodPrivate; + CompressedPrivateDataSize = TI->CompMethodPrivateSize; + } else { + throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, + "Can't create MKV track decompressor: unknown or unsupported compression method"); + } +} + +TrackCompressionContext::~TrackCompressionContext() { + if (CS) + cs_Destroy(CS); +} + +int64_t GetSWSCPUFlags() { + int64_t Flags = 0; if (CPUFeatures & FFMS_CPU_CAPS_MMX) Flags |= SWS_CPU_CAPS_MMX; @@ -88,13 +125,65 @@ int GetSWSCPUFlags() { Flags |= SWS_CPU_CAPS_ALTIVEC; if (CPUFeatures & FFMS_CPU_CAPS_BFIN) Flags |= SWS_CPU_CAPS_BFIN; +#ifdef SWS_CPU_CAPS_SSE2 + if (CPUFeatures & FFMS_CPU_CAPS_SSE2) + Flags |= SWS_CPU_CAPS_SSE2; +#endif return Flags; } +static int handle_jpeg(PixelFormat *format) +{ + switch (*format) { + case PIX_FMT_YUVJ420P: *format = PIX_FMT_YUV420P; return 1; + case PIX_FMT_YUVJ422P: *format = PIX_FMT_YUV422P; return 1; + case PIX_FMT_YUVJ444P: *format = PIX_FMT_YUV444P; return 1; + case PIX_FMT_YUVJ440P: *format = PIX_FMT_YUV440P; return 1; + default: return 0; + } +} +SwsContext *GetSwsContext(int SrcW, int SrcH, PixelFormat SrcFormat, int DstW, int DstH, PixelFormat DstFormat, int64_t Flags, int ColorSpace) { +#if LIBSWSCALE_VERSION_INT < AV_VERSION_INT(0, 12, 0) + return sws_getContext(SrcW, SrcH, SrcFormat, DstW, DstH, DstFormat, Flags, 0, 0, 0); +#else + SwsContext *Context = sws_alloc_context(); + if (!Context) return 0; + + if (ColorSpace == -1) + ColorSpace = (SrcW > 1024 || SrcH >= 600) ? SWS_CS_ITU709 : SWS_CS_DEFAULT; + + int SrcRange = handle_jpeg(&SrcFormat); + int DstRange = handle_jpeg(&DstFormat); + + av_set_int(Context, "sws_flags", Flags); + av_set_int(Context, "srcw", SrcW); + av_set_int(Context, "srch", SrcH); + av_set_int(Context, "dstw", DstW); + av_set_int(Context, "dsth", DstH); + av_set_int(Context, "src_range", SrcRange); + av_set_int(Context, "dst_range", DstRange); + av_set_int(Context, "src_format", SrcFormat); + av_set_int(Context, "dst_format", DstFormat); + + sws_setColorspaceDetails(Context, sws_getCoefficients(ColorSpace), SrcRange, sws_getCoefficients(ColorSpace), DstRange, 0, 1<<16, 1<<16); + + if(sws_init_context(Context, 0, 0) < 0){ + sws_freeContext(Context); + return 0; + } + + return Context; +#endif + +} + + int GetPPCPUFlags() { int Flags = 0; +#ifdef FFMS_USE_POSTPROC +// not exactly a pretty solution but it'll never get called anyway if (CPUFeatures & FFMS_CPU_CAPS_MMX) Flags |= PP_CPU_CAPS_MMX; if (CPUFeatures & FFMS_CPU_CAPS_MMX2) @@ -103,6 +192,7 @@ int GetPPCPUFlags() { Flags |= PP_CPU_CAPS_3DNOW; if (CPUFeatures & FFMS_CPU_CAPS_ALTIVEC) Flags |= PP_CPU_CAPS_ALTIVEC; +#endif // FFMS_USE_POSTPROC return Flags; } @@ -126,8 +216,30 @@ FFMS_TrackType HaaliTrackTypeToFFTrackType(int TT) { } } -void ReadFrame(uint64_t FilePos, unsigned int &FrameSize, CompressedStream *CS, MatroskaReaderContext &Context) { - if (CS) { +const char *GetLAVCSampleFormatName(AVSampleFormat s) { + switch (s) { + case AV_SAMPLE_FMT_U8: return "8-bit unsigned integer"; + case AV_SAMPLE_FMT_S16: return "16-bit signed integer"; + case AV_SAMPLE_FMT_S32: return "32-bit signed integer"; + case AV_SAMPLE_FMT_FLT: return "Single-precision floating point"; + case AV_SAMPLE_FMT_DBL: return "Double-precision floating point"; + default: return "Unknown"; + } +} + +template static void safe_aligned_reallocz(T *&ptr, size_t old_size, size_t new_size) { + void *newalloc = av_mallocz(new_size); + if (newalloc) { + memcpy(newalloc, ptr, FFMIN(old_size, new_size)); + } + av_free(ptr); + ptr = static_cast(newalloc); +} + +void ReadFrame(uint64_t FilePos, unsigned int &FrameSize, TrackCompressionContext *TCC, MatroskaReaderContext &Context) { + memset(Context.Buffer, 0, Context.BufferSize); // necessary to avoid lavc hurfing a durf with some mpeg4 video streams + if (TCC && TCC->CS) { + CompressedStream *CS = TCC->CS; unsigned int DecompressedFrameSize = 0; cs_NextFrame(CS, FilePos, FrameSize); @@ -142,17 +254,16 @@ void ReadFrame(uint64_t FilePos, unsigned int &FrameSize, CompressedStream *CS, if (ReadBytes == 0) { FrameSize = DecompressedFrameSize; - memset(Context.Buffer + DecompressedFrameSize, 0, - Context.BufferSize + FF_INPUT_BUFFER_PADDING_SIZE - DecompressedFrameSize); return; } - if (Context.BufferSize < DecompressedFrameSize + ReadBytes) { - Context.BufferSize = DecompressedFrameSize + ReadBytes; - Context.Buffer = (uint8_t *)realloc(Context.Buffer, Context.BufferSize + FF_INPUT_BUFFER_PADDING_SIZE); + if (Context.BufferSize < DecompressedFrameSize + ReadBytes + FF_INPUT_BUFFER_PADDING_SIZE) { + size_t NewSize = (DecompressedFrameSize + ReadBytes) * 2; + safe_aligned_reallocz(Context.Buffer, Context.BufferSize, NewSize); if (Context.Buffer == NULL) throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_ALLOCATION_FAILED, "Out of memory"); + Context.BufferSize = NewSize; } memcpy(Context.Buffer + DecompressedFrameSize, Context.CSBuffer, ReadBytes); @@ -165,15 +276,37 @@ void ReadFrame(uint64_t FilePos, unsigned int &FrameSize, CompressedStream *CS, throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_SEEKING, buf.str()); } - if (Context.BufferSize < FrameSize) { - Context.BufferSize = FrameSize; - Context.Buffer = (uint8_t *)realloc(Context.Buffer, Context.BufferSize + 16); + if (TCC && TCC->CompressionMethod == COMP_PREPEND) { + unsigned ReqBufsize = FrameSize + TCC->CompressedPrivateDataSize + FF_INPUT_BUFFER_PADDING_SIZE; + if (Context.BufferSize < ReqBufsize) { + size_t NewSize = ReqBufsize * 2; + safe_aligned_reallocz(Context.Buffer, Context.BufferSize, NewSize); + if (Context.Buffer == NULL) + throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_ALLOCATION_FAILED, "Out of memory"); + Context.BufferSize = NewSize; + } + + /* // maybe faster? maybe not? + for (int i=0; i < TCC->CompressedPrivateDataSize; i++) + *(Context.Buffer)++ = ((uint8_t *)TCC->CompressedPrivateData)[i]; + */ + // screw it, memcpy and fuck the losers who use header compression + memcpy(Context.Buffer, TCC->CompressedPrivateData, TCC->CompressedPrivateDataSize); + } + else if (Context.BufferSize < FrameSize + FF_INPUT_BUFFER_PADDING_SIZE) { + size_t NewSize = FrameSize * 2; + safe_aligned_reallocz(Context.Buffer, Context.BufferSize, NewSize); if (Context.Buffer == NULL) throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_ALLOCATION_FAILED, "Out of memory"); + Context.BufferSize = NewSize; } - size_t ReadBytes = fread(Context.Buffer, 1, FrameSize, Context.ST.fp); + uint8_t *TargetPtr = Context.Buffer; + if (TCC && TCC->CompressionMethod == COMP_PREPEND) + TargetPtr += TCC->CompressedPrivateDataSize; + + size_t ReadBytes = fread(TargetPtr, 1, FrameSize, Context.ST.fp); if (ReadBytes != FrameSize) { if (ReadBytes == 0) { if (feof(Context.ST.fp)) { @@ -202,32 +335,20 @@ void InitNullPacket(AVPacket &pkt) { pkt.size = 0; } -bool IsPackedFrame(AVPacket &pkt) { - for (int i = 0; i < pkt.size - 5; i++) - if (pkt.data[i] == 0x00 && pkt.data[i + 1] == 0x00 && pkt.data[i + 2] == 0x01 && pkt.data[i + 3] == 0xB6 && (pkt.data[i + 4] & 0x40)) - for (i = i + 5; i < pkt.size - 5; i++) - if (pkt.data[i] == 0x00 && pkt.data[i + 1] == 0x00 && pkt.data[i + 2] == 0x01 && pkt.data[i + 3] == 0xB6 && (pkt.data[i + 4] & 0xC0) == 0x80) - return true; - return false; -} - -bool IsNVOP(AVPacket &pkt) { - static const uint8_t MPEG4NVOP[] = { 0x00, 0x00, 0x01, 0xB6 }; - return (pkt.size >= 4 && pkt.size <= 8) && !memcmp(pkt.data, MPEG4NVOP, 4); -} - void FillAP(FFMS_AudioProperties &AP, AVCodecContext *CTX, FFMS_Track &Frames) { AP.SampleFormat = static_cast(CTX->sample_fmt); - AP.BitsPerSample = av_get_bits_per_sample_format(CTX->sample_fmt); - if (CTX->sample_fmt == SAMPLE_FMT_S32 && CTX->bits_per_raw_sample) + AP.BitsPerSample = av_get_bits_per_sample_fmt(CTX->sample_fmt); + if (CTX->sample_fmt == AV_SAMPLE_FMT_S32 && CTX->bits_per_raw_sample) AP.BitsPerSample = CTX->bits_per_raw_sample; AP.Channels = CTX->channels;; AP.ChannelLayout = CTX->channel_layout; AP.SampleRate = CTX->sample_rate; - AP.NumSamples = (Frames.back()).SampleStart + (Frames.back()).SampleCount; - AP.FirstTime = ((Frames.front().PTS * Frames.TB.Num) / (double)Frames.TB.Den) / 1000; - AP.LastTime = ((Frames.back().PTS * Frames.TB.Num) / (double)Frames.TB.Den) / 1000; + if (Frames.size() > 0) { + AP.NumSamples = (Frames.back()).SampleStart + (Frames.back()).SampleCount; + AP.FirstTime = ((Frames.front().PTS * Frames.TB.Num) / (double)Frames.TB.Den) / 1000; + AP.LastTime = ((Frames.back().PTS * Frames.TB.Num) / (double)Frames.TB.Den) / 1000; + } } #ifdef HAALISOURCE @@ -271,7 +392,7 @@ CodecID MatroskaToFFCodecID(char *Codec, void *CodecPrivate, unsigned int FourCC case 16: CID = CODEC_ID_PCM_S16LE; break; case 24: CID = CODEC_ID_PCM_S24LE; break; case 32: CID = CODEC_ID_PCM_S32LE; break; - } + } break; case CODEC_ID_PCM_S16BE: switch (BitsPerSample) { @@ -279,10 +400,12 @@ CodecID MatroskaToFFCodecID(char *Codec, void *CodecPrivate, unsigned int FourCC case 16: CID = CODEC_ID_PCM_S16BE; break; case 24: CID = CODEC_ID_PCM_S24BE; break; case 32: CID = CODEC_ID_PCM_S32BE; break; - } + } + break; + default: break; } - + return CID; } } @@ -327,75 +450,139 @@ void InitializeCodecContextFromMatroskaTrackInfo(TrackInfo *TI, AVCodecContext * #ifdef HAALISOURCE -void InitializeCodecContextFromHaaliInfo(CComQIPtr pBag, AVCodecContext *CodecContext) { - if (pBag) { - CComVariant pV; +FFCodecContext InitializeCodecContextFromHaaliInfo(CComQIPtr pBag) { + CComVariant pV; + if (FAILED(pBag->Read(L"Type", &pV, NULL)) && SUCCEEDED(pV.ChangeType(VT_UI4))) + return FFCodecContext(); + + unsigned int TT = pV.uintVal; + + FFCodecContext CodecContext(avcodec_alloc_context(), DeleteHaaliCodecContext); + + unsigned int FourCC = 0; + if (TT == TT_VIDEO) { + pV.Clear(); + if (SUCCEEDED(pBag->Read(L"Video.PixelWidth", &pV, NULL)) && SUCCEEDED(pV.ChangeType(VT_UI4))) + CodecContext->coded_width = pV.uintVal; pV.Clear(); - if (SUCCEEDED(pBag->Read(L"Type", &pV, NULL)) && SUCCEEDED(pV.ChangeType(VT_UI4))) { + if (SUCCEEDED(pBag->Read(L"Video.PixelHeight", &pV, NULL)) && SUCCEEDED(pV.ChangeType(VT_UI4))) + CodecContext->coded_height = pV.uintVal; - unsigned int TT = pV.uintVal; + pV.Clear(); + if (SUCCEEDED(pBag->Read(L"FOURCC", &pV, NULL)) && SUCCEEDED(pV.ChangeType(VT_UI4))) + FourCC = pV.uintVal; - if (TT == TT_VIDEO) { - - pV.Clear(); - if (SUCCEEDED(pBag->Read(L"Video.PixelWidth", &pV, NULL)) && SUCCEEDED(pV.ChangeType(VT_UI4))) - CodecContext->coded_width = pV.uintVal; - - pV.Clear(); - if (SUCCEEDED(pBag->Read(L"Video.PixelHeight", &pV, NULL)) && SUCCEEDED(pV.ChangeType(VT_UI4))) - CodecContext->coded_height = pV.uintVal; - - } else if (TT == TT_AUDIO) { - - pV.Clear(); - if (SUCCEEDED(pBag->Read(L"Audio.SamplingFreq", &pV, NULL)) && SUCCEEDED(pV.ChangeType(VT_UI4))) - CodecContext->sample_rate = pV.uintVal; - - pV.Clear(); - if (SUCCEEDED(pBag->Read(L"Audio.BitDepth", &pV, NULL)) && SUCCEEDED(pV.ChangeType(VT_UI4))) - CodecContext->bits_per_coded_sample = pV.uintVal; - - pV.Clear(); - if (SUCCEEDED(pBag->Read(L"Audio.Channels", &pV, NULL)) && SUCCEEDED(pV.ChangeType(VT_UI4))) - CodecContext->channels = pV.uintVal; - - } + FFMS_BITMAPINFOHEADER bih; + memset(&bih, 0, sizeof bih); + if (FourCC) { + // Reconstruct the missing codec private part for VC1 + bih.biSize = sizeof bih; + bih.biCompression = FourCC; + bih.biBitCount = 24; + bih.biPlanes = 1; + bih.biHeight = CodecContext->coded_height; } - } + + pV.Clear(); + if (SUCCEEDED(pBag->Read(L"CodecPrivate", &pV, NULL))) { + bih.biSize += vtSize(pV); + CodecContext->extradata = static_cast(av_malloc(bih.biSize)); + // prepend BITMAPINFOHEADER if there's anything interesting in it (i.e. we're decoding VC1) + if (FourCC) + memcpy(CodecContext->extradata, &bih, sizeof bih); + vtCopy(pV, CodecContext->extradata + (FourCC ? sizeof bih : 0)); + } + // use the BITMAPINFOHEADER only. not sure if this is correct or if it's ever going to be used... + else { + CodecContext->extradata = static_cast(av_malloc(bih.biSize)); + memcpy(CodecContext->extradata, &bih, sizeof bih); + } + CodecContext->extradata_size = bih.biSize; + } + else if (TT == TT_AUDIO) { + pV.Clear(); + if (SUCCEEDED(pBag->Read(L"CodecPrivate", &pV, NULL))) { + CodecContext->extradata_size = vtSize(pV); + CodecContext->extradata = static_cast(av_malloc(CodecContext->extradata_size)); + vtCopy(pV, CodecContext->extradata); + } + + pV.Clear(); + if (SUCCEEDED(pBag->Read(L"Audio.SamplingFreq", &pV, NULL)) && SUCCEEDED(pV.ChangeType(VT_UI4))) + CodecContext->sample_rate = pV.uintVal; + + pV.Clear(); + if (SUCCEEDED(pBag->Read(L"Audio.BitDepth", &pV, NULL)) && SUCCEEDED(pV.ChangeType(VT_UI4))) + CodecContext->bits_per_coded_sample = pV.uintVal; + + pV.Clear(); + if (SUCCEEDED(pBag->Read(L"Audio.Channels", &pV, NULL)) && SUCCEEDED(pV.ChangeType(VT_UI4))) + CodecContext->channels = pV.uintVal; + } + + pV.Clear(); + if (SUCCEEDED(pBag->Read(L"CodecID", &pV, NULL)) && SUCCEEDED(pV.ChangeType(VT_BSTR))) { + char CodecStr[2048]; + wcstombs(CodecStr, pV.bstrVal, 2000); + + CodecContext->codec = avcodec_find_decoder(MatroskaToFFCodecID(CodecStr, CodecContext->extradata, FourCC, CodecContext->bits_per_coded_sample)); + } + return CodecContext; } #endif + +// All this filename chikanery that follows is supposed to make sure both local +// codepage (used by avisynth etc) and UTF8 (potentially used by API users) strings +// work correctly on Win32. +// It's a really ugly hack, and I blame Microsoft for it. +#ifdef _WIN32 +static wchar_t *dup_char_to_wchar(const char *s, unsigned int cp) { + wchar_t *w; + int l; + if (!(l = MultiByteToWideChar(cp, MB_ERR_INVALID_CHARS, s, -1, NULL, 0))) + return NULL; + if (!(w = (wchar_t *)malloc(l * sizeof(wchar_t)))) + return NULL; + if (MultiByteToWideChar(cp, MB_ERR_INVALID_CHARS, s, -1 , w, l) <= 0) { + free(w); + w = NULL; + } + return w; +} +#endif + FILE *ffms_fopen(const char *filename, const char *mode) { -#ifdef FFMS_USE_UTF8_PATHS - // Hack: support utf8-in-char* filenames on windows - wchar_t filename_wide[MAX_PATH*2]; - // 64 characters of mode string ought to be more than enough for everyone - wchar_t mode_wide[64]; - if ((MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, filename, -1, filename_wide, MAX_PATH) > 0) - && (MultiByteToWideChar(CP_ACP, NULL, mode, -1, mode_wide, 60) > 0)) - return _wfopen(filename_wide, mode_wide); +#ifdef _WIN32 + unsigned int codepage; + if (GlobalUseUTF8Paths) + codepage = CP_UTF8; else - return fopen(filename, mode); + codepage = CP_ACP; + + FILE *ret; + wchar_t *filename_wide = dup_char_to_wchar(filename, codepage); + wchar_t *mode_wide = dup_char_to_wchar(mode, codepage); + if (filename_wide && mode_wide) + ret = _wfopen(filename_wide, mode_wide); + else + ret = fopen(filename, mode); + + free(filename_wide); + free(mode_wide); + + return ret; #else return fopen(filename, mode); -#endif +#endif /* _WIN32 */ } size_t ffms_mbstowcs (wchar_t *wcstr, const char *mbstr, size_t max) { -#ifdef FFMS_USE_UTF8_PATHS - // try utf8 first - int len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, mbstr, -1, NULL, 0); - if (len > 0) { - MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, mbstr, -1, wcstr, max); - return static_cast(len); - } - // failed, use local ANSI codepage - else { - len = MultiByteToWideChar(CP_ACP, NULL, mbstr, -1, wcstr, max); - return static_cast(len); - } +#ifdef _WIN32 + // this is only called by HaaliOpenFile anyway, so I think this is safe + return static_cast(MultiByteToWideChar((GlobalUseUTF8Paths ? CP_UTF8 : CP_ACP), MB_ERR_INVALID_CHARS, mbstr, -1, wcstr, max)); #else return mbstowcs(wcstr, mbstr, max); #endif @@ -404,22 +591,82 @@ size_t ffms_mbstowcs (wchar_t *wcstr, const char *mbstr, size_t max) { // ffms_fstream stuff void ffms_fstream::open(const char *filename, std::ios_base::openmode mode) { -#ifdef FFMS_USE_UTF8_PATHS - // Hack: support utf8-in-char* filenames on windows - wchar_t filename_wide[MAX_PATH*2]; - if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, filename, -1, filename_wide, MAX_PATH) > 0) + // Unlike MSVC, mingw's iostream library doesn't have an fstream overload + // that takes a wchar_t* filename, which means you can't open unicode + // filenames with it on Windows. gg. +#if defined(_WIN32) && !defined(__MINGW32__) + unsigned int codepage = GlobalUseUTF8Paths ? CP_UTF8 : CP_ACP; + + wchar_t *filename_wide = dup_char_to_wchar(filename, codepage); + if (filename_wide) std::fstream::open(filename_wide, mode); else std::fstream::open(filename, mode); -#else + + free(filename_wide); +#else // defined(_WIN32) && !defined(__MINGW32__) std::fstream::open(filename, mode); -#endif +#endif // defined(_WIN32) && !defined(__MINGW32__) } ffms_fstream::ffms_fstream(const char *filename, std::ios_base::openmode mode) { open(filename, mode); } + +#ifdef _WIN32 +int ffms_wchar_open(const char *fname, int oflags, int pmode) { + wchar_t *wfname = dup_char_to_wchar(fname, CP_UTF8); + if (wfname) { + int ret = _wopen(wfname, oflags, pmode); + free(wfname); + return ret; + } + return -1; +} + +static int ffms_lavf_file_open(URLContext *h, const char *filename, int flags) { + int access; + int fd; + + av_strstart(filename, "file:", &filename); + + if (flags & URL_RDWR) { + access = _O_CREAT | _O_TRUNC | _O_RDWR; + } else if (flags & URL_WRONLY) { + access = _O_CREAT | _O_TRUNC | _O_WRONLY; + } else { + access = _O_RDONLY; + } +#ifdef _O_BINARY + access |= _O_BINARY; +#endif + fd = ffms_wchar_open(filename, access, 0666); + if (fd == -1) + return AVERROR(ENOENT); + h->priv_data = (void *) (intptr_t) fd; + return 0; +} + +// Hijack lavf's file protocol handler's open function and use our own instead. +// Hack by nielsm. +void ffms_patch_lavf_file_open() { + URLProtocol *proto = av_protocol_next(NULL); + while (proto != NULL) { + if (strcmp("file", proto->name) == 0) { + break; + } + proto = proto->next; + } + if (proto != NULL) { + proto->url_open = &ffms_lavf_file_open; + } +} +#endif // _WIN32 + +// End of filename hackery. + + #ifdef HAALISOURCE CComPtr HaaliOpenFile(const char *SourceFile, enum FFMS_Sources SourceMode) { @@ -465,12 +712,10 @@ CComPtr HaaliOpenFile(const char *SourceFile, enum FFMS_Sources So #endif void LAVFOpenFile(const char *SourceFile, AVFormatContext *&FormatContext) { - if (av_open_input_file(&FormatContext, SourceFile, NULL, 0, NULL) != 0) { - std::ostringstream buf; - buf << "Couldn't open '" << SourceFile << "'"; - throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str()); - } - + if (av_open_input_file(&FormatContext, SourceFile, NULL, 0, NULL) != 0) + throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, + std::string("Couldn't open '") + SourceFile + "'"); + if (av_find_stream_info(FormatContext) < 0) { av_close_input_file(FormatContext); FormatContext = NULL; @@ -478,3 +723,39 @@ void LAVFOpenFile(const char *SourceFile, AVFormatContext *&FormatContext) { "Couldn't find stream information"); } } + + +// attempt to correct framerate to the proper NTSC fraction, if applicable +// code stolen from Perian +void CorrectNTSCRationalFramerate(int *Num, int *Den) { + AVRational TempFPS; + TempFPS.den = *Num; // not a typo + TempFPS.num = *Den; // still not a typo + + av_reduce(&TempFPS.num, &TempFPS.den, TempFPS.num, TempFPS.den, INT_MAX); + + if (TempFPS.num == 1) { + *Num = TempFPS.den; + *Den = TempFPS.num; + } + else { + double FTimebase = av_q2d(TempFPS); + double NearestNTSC = floor(FTimebase * 1001.0 + 0.5) / 1001.0; + const double SmallInterval = 1.0/120.0; + + if (fabs(FTimebase - NearestNTSC) < SmallInterval) { + *Num = int((1001.0 / FTimebase) + 0.5); + *Den = 1001; + } + } +} + +// correct the timebase if it is invalid +void CorrectTimebase(FFMS_VideoProperties *VP, FFMS_TrackTimeBase *TTimebase) { + double Timebase = (double)TTimebase->Num / TTimebase->Den; + double FPS = (double)VP->FPSNumerator / VP->FPSDenominator; + if ((1000/Timebase) / FPS < 1) { + TTimebase->Den = VP->FPSNumerator; + TTimebase->Num = (int64_t)VP->FPSDenominator * 1000; + } +} diff --git a/aegisub/libffms/src/core/utils.h b/aegisub/libffms/src/core/utils.h index 0e377fe06..30f101dee 100644 --- a/aegisub/libffms/src/core/utils.h +++ b/aegisub/libffms/src/core/utils.h @@ -30,10 +30,13 @@ extern "C" { #include "stdiostream.h" +#include #include #include #include +#ifdef FFMS_USE_POSTPROC #include +#endif // FFMS_USE_POSTPROC } // must be included after ffmpeg headers @@ -57,17 +60,17 @@ const int64_t ffms_av_nopts_value = static_cast(1) << 63; // used for matroska<->ffmpeg codec ID mapping to avoid Win32 dependency typedef struct FFMS_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; + 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; } FFMS_BITMAPINFOHEADER; class FFMS_Exception : public std::exception { @@ -110,8 +113,35 @@ public: _Arg = Arg; } }; +// auto_ptr-ish holder for AVCodecContexts with overridable deleter +class FFCodecContext { + AVCodecContext *CodecContext; + void (*Deleter)(AVCodecContext *); +public: + FFCodecContext() : CodecContext(0), Deleter(0) { } + FFCodecContext(FFCodecContext &r) : CodecContext(r.CodecContext), Deleter(r.Deleter) { r.CodecContext = 0; } + FFCodecContext(AVCodecContext *c, void (*d)(AVCodecContext *)) : CodecContext(c), Deleter(d) { } + FFCodecContext& operator=(FFCodecContext r) { reset(r.CodecContext, r.Deleter); r.CodecContext = 0; return *this; } + ~FFCodecContext() { reset(); } + AVCodecContext* operator->() { return CodecContext; } + operator AVCodecContext*() { return CodecContext; } + void reset(AVCodecContext *c = 0, void (*d)(AVCodecContext *) = 0) { + if (CodecContext && Deleter) Deleter(CodecContext); + CodecContext = c; + Deleter = d; + } +}; -struct MatroskaReaderContext { +inline void DeleteHaaliCodecContext(AVCodecContext *CodecContext) { + av_freep(&CodecContext->extradata); + av_freep(&CodecContext); +} +inline void DeleteMatroskaCodecContext(AVCodecContext *CodecContext) { + avcodec_close(CodecContext); + av_freep(&CodecContext); +} + +class MatroskaReaderContext { public: StdIoStream ST; uint8_t *Buffer; @@ -120,12 +150,16 @@ public: MatroskaReaderContext() { InitStdIoStream(&ST); - Buffer = NULL; - BufferSize = 0; + Buffer = static_cast(av_mallocz(16384)); // arbitrarily decided number + if (!Buffer) + throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_ALLOCATION_FAILED, "Out of memory"); + BufferSize = 16384; } ~MatroskaReaderContext() { - free(Buffer); + if (Buffer) + av_free(Buffer); + if (ST.fp) fclose(ST.fp); } }; @@ -135,28 +169,67 @@ public: ffms_fstream(const char *filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out); }; -int GetSWSCPUFlags(); +template +class AlignedBuffer { + T *buf; + +public: + AlignedBuffer(size_t n = 1, bool zero = false) { + buf = (T*) av_malloc(sizeof(*buf) * n); + if (!buf) throw std::bad_alloc(); + } + + ~AlignedBuffer() { + av_free(buf); + buf = 0; + } + + const T &operator[] (size_t i) const { return buf[i]; } + T &operator[] (size_t i) { return buf[i]; } +}; + + +class TrackCompressionContext { +public: + CompressedStream *CS; + unsigned CompressionMethod; + void *CompressedPrivateData; + unsigned CompressedPrivateDataSize; + + TrackCompressionContext(MatroskaFile *MF, TrackInfo *TI, unsigned int Track); + ~TrackCompressionContext(); +}; + + +int64_t GetSWSCPUFlags(); +SwsContext *GetSwsContext(int SrcW, int SrcH, PixelFormat SrcFormat, int DstW, int DstH, PixelFormat DstFormat, int64_t Flags, int ColorSpace = -1); int GetPPCPUFlags(); void ClearErrorInfo(FFMS_ErrorInfo *ErrorInfo); FFMS_TrackType HaaliTrackTypeToFFTrackType(int TT); -void ReadFrame(uint64_t FilePos, unsigned int &FrameSize, CompressedStream *CS, MatroskaReaderContext &Context); -bool AudioFMTIsFloat(SampleFormat FMT); +void ReadFrame(uint64_t FilePos, unsigned int &FrameSize, TrackCompressionContext *TCC, MatroskaReaderContext &Context); +bool AudioFMTIsFloat(AVSampleFormat FMT); void InitNullPacket(AVPacket &pkt); -bool IsPackedFrame(AVPacket &pkt); -bool IsNVOP(AVPacket &pkt); void FillAP(FFMS_AudioProperties &AP, AVCodecContext *CTX, FFMS_Track &Frames); + #ifdef HAALISOURCE unsigned vtSize(VARIANT &vt); void vtCopy(VARIANT& vt,void *dest); -void InitializeCodecContextFromHaaliInfo(CComQIPtr pBag, AVCodecContext *CodecContext); +FFCodecContext InitializeCodecContextFromHaaliInfo(CComQIPtr pBag); #endif + void InitializeCodecContextFromMatroskaTrackInfo(TrackInfo *TI, AVCodecContext *CodecContext); CodecID MatroskaToFFCodecID(char *Codec, void *CodecPrivate, unsigned int FourCC = 0, unsigned int BitsPerSample = 0); FILE *ffms_fopen(const char *filename, const char *mode); size_t ffms_mbstowcs (wchar_t *wcstr, const char *mbstr, size_t max); +#ifdef _WIN32 +void ffms_patch_lavf_file_open(); +#endif // _WIN32 #ifdef HAALISOURCE CComPtr HaaliOpenFile(const char *SourceFile, enum FFMS_Sources SourceMode); -#endif +#endif // HAALISOURCE void LAVFOpenFile(const char *SourceFile, AVFormatContext *&FormatContext); +void CorrectNTSCRationalFramerate(int *Num, int *Den); +void CorrectTimebase(FFMS_VideoProperties *VP, FFMS_TrackTimeBase *TTimebase); +const char *GetLAVCSampleFormatName(AVSampleFormat s); #endif diff --git a/aegisub/libffms/src/core/videosource.cpp b/aegisub/libffms/src/core/videosource.cpp index 45e1230b1..1c3d5dcdb 100644 --- a/aegisub/libffms/src/core/videosource.cpp +++ b/aegisub/libffms/src/core/videosource.cpp @@ -27,6 +27,8 @@ void FFMS_VideoSource::GetFrameCheck(int n) { } void FFMS_VideoSource::SetPP(const char *PP) { + +#ifdef FFMS_USE_POSTPROC if (PPMode) pp_free_mode(PPMode); PPMode = NULL; @@ -43,9 +45,14 @@ void FFMS_VideoSource::SetPP(const char *PP) { ReAdjustPP(CodecContext->pix_fmt, CodecContext->width, CodecContext->height); OutputFrame(DecodeFrame); +#else + throw FFMS_Exception(FFMS_ERROR_POSTPROCESSING, FFMS_ERROR_UNSUPPORTED, + "FFMS2 was not compiled with postprocessing support"); +#endif /* FFMS_USE_POSTPROC */ } void FFMS_VideoSource::ResetPP() { +#ifdef FFMS_USE_POSTPROC if (PPContext) pp_free_context(PPContext); PPContext = NULL; @@ -54,10 +61,12 @@ void FFMS_VideoSource::ResetPP() { pp_free_mode(PPMode); PPMode = NULL; +#endif /* FFMS_USE_POSTPROC */ OutputFrame(DecodeFrame); } void FFMS_VideoSource::ReAdjustPP(PixelFormat VPixelFormat, int Width, int Height) { +#ifdef FFMS_USE_POSTPROC if (PPContext) pp_free_context(PPContext); PPContext = NULL; @@ -68,10 +77,17 @@ void FFMS_VideoSource::ReAdjustPP(PixelFormat VPixelFormat, int Width, int Heigh int Flags = GetPPCPUFlags(); switch (VPixelFormat) { - case PIX_FMT_YUV420P: Flags |= PP_FORMAT_420; break; - case PIX_FMT_YUV422P: Flags |= PP_FORMAT_422; break; - case PIX_FMT_YUV411P: Flags |= PP_FORMAT_411; break; - case PIX_FMT_YUV444P: Flags |= PP_FORMAT_444; break; + case PIX_FMT_YUV420P: + case PIX_FMT_YUVJ420P: + Flags |= PP_FORMAT_420; break; + case PIX_FMT_YUV422P: + case PIX_FMT_YUVJ422P: + Flags |= PP_FORMAT_422; break; + case PIX_FMT_YUV411P: + Flags |= PP_FORMAT_411; break; + case PIX_FMT_YUV444P: + case PIX_FMT_YUVJ444P: + Flags |= PP_FORMAT_444; break; default: ResetPP(); throw FFMS_Exception(FFMS_ERROR_POSTPROCESSING, FFMS_ERROR_UNSUPPORTED, @@ -82,8 +98,13 @@ void FFMS_VideoSource::ReAdjustPP(PixelFormat VPixelFormat, int Width, int Heigh avpicture_free(&PPFrame); avpicture_alloc(&PPFrame, VPixelFormat, Width, Height); +#else + return; +#endif /* FFMS_USE_POSTPROC */ + } + static void CopyAVPictureFields(AVPicture &Picture, FFMS_Frame &Dst) { for (int i = 0; i < 4; i++) { Dst.Data[i] = Picture.data[i]; @@ -99,17 +120,18 @@ FFMS_Frame *FFMS_VideoSource::OutputFrame(AVFrame *Frame) { ReAdjustOutputFormat(TargetPixelFormats, TargetWidth, TargetHeight, TargetResizer); } +#ifdef FFMS_USE_POSTPROC if (PPMode) { pp_postprocess(const_cast(Frame->data), Frame->linesize, PPFrame.data, PPFrame.linesize, CodecContext->width, CodecContext->height, Frame->qscale_table, Frame->qstride, PPMode, PPContext, Frame->pict_type | (Frame->qscale_type ? PP_PICT_TYPE_QP2 : 0)); if (SWS) { - sws_scale(SWS, PPFrame.data, PPFrame.linesize, 0, CodecContext->height, SWSFrame.data, SWSFrame.linesize); + sws_scale(SWS, const_cast(PPFrame.data), PPFrame.linesize, 0, CodecContext->height, SWSFrame.data, SWSFrame.linesize); CopyAVPictureFields(SWSFrame, LocalFrame); } else { CopyAVPictureFields(PPFrame, LocalFrame); } } else { if (SWS) { - sws_scale(SWS, Frame->data, Frame->linesize, 0, CodecContext->height, SWSFrame.data, SWSFrame.linesize); + sws_scale(SWS, const_cast(Frame->data), Frame->linesize, 0, CodecContext->height, SWSFrame.data, SWSFrame.linesize); CopyAVPictureFields(SWSFrame, LocalFrame); } else { // Special case to avoid ugly casts @@ -119,6 +141,18 @@ FFMS_Frame *FFMS_VideoSource::OutputFrame(AVFrame *Frame) { } } } +#else // FFMS_USE_POSTPROC + if (SWS) { + sws_scale(SWS, const_cast(Frame->data), Frame->linesize, 0, CodecContext->height, SWSFrame.data, SWSFrame.linesize); + CopyAVPictureFields(SWSFrame, LocalFrame); + } else { + // Special case to avoid ugly casts + for (int i = 0; i < 4; i++) { + LocalFrame.Data[i] = Frame->data[i]; + LocalFrame.Linesize[i] = Frame->linesize[i]; + } + } +#endif // FFMS_USE_POSTPROC LocalFrame.EncodedWidth = CodecContext->width; LocalFrame.EncodedHeight = CodecContext->height; @@ -148,7 +182,7 @@ FFMS_VideoSource::FFMS_VideoSource(const char *SourceFile, FFMS_Index *Index, in throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_INVALID_ARGUMENT, "Not a video track"); - if (Index[Track].size() == 0) + if (Index->at(Track).size() == 0) throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_INVALID_ARGUMENT, "Video track contains no frames"); @@ -157,12 +191,15 @@ FFMS_VideoSource::FFMS_VideoSource(const char *SourceFile, FFMS_Index *Index, in "The index does not match the source file"); memset(&VP, 0, sizeof(VP)); +#ifdef FFMS_USE_POSTPROC PPContext = NULL; PPMode = NULL; +#endif // FFMS_USE_POSTPROC SWS = NULL; LastFrameNum = 0; CurrentFrame = 1; - MPEG4Counter = 0; + DelayCounter = 0; + InitialDecode = 1; CodecContext = NULL; LastFrameHeight = -1; LastFrameWidth = -1; @@ -175,21 +212,26 @@ FFMS_VideoSource::FFMS_VideoSource(const char *SourceFile, FFMS_Index *Index, in DecodeFrame = avcodec_alloc_frame(); // Dummy allocations so the unallocated case doesn't have to be handled later +#ifdef FFMS_USE_POSTPROC avpicture_alloc(&PPFrame, PIX_FMT_GRAY8, 16, 16); +#endif // FFMS_USE_POSTPROC avpicture_alloc(&SWSFrame, PIX_FMT_GRAY8, 16, 16); } FFMS_VideoSource::~FFMS_VideoSource() { +#ifdef FFMS_USE_POSTPROC if (PPMode) pp_free_mode(PPMode); if (PPContext) pp_free_context(PPContext); + avpicture_free(&PPFrame); +#endif // FFMS_USE_POSTPROC + if (SWS) sws_freeContext(SWS); - avpicture_free(&PPFrame); avpicture_free(&SWSFrame); av_freep(&DecodeFrame); } @@ -224,8 +266,10 @@ void FFMS_VideoSource::ReAdjustOutputFormat(int64_t TargetFormats, int Width, in } if (CodecContext->pix_fmt != OutputFormat || Width != CodecContext->width || Height != CodecContext->height) { - SWS = sws_getContext(CodecContext->width, CodecContext->height, CodecContext->pix_fmt, Width, Height, - OutputFormat, GetSWSCPUFlags() | Resizer, NULL, NULL, NULL); + int ColorSpace = CodecContext->colorspace; + if (ColorSpace == AVCOL_SPC_UNSPECIFIED) ColorSpace = -1; + SWS = GetSwsContext(CodecContext->width, CodecContext->height, CodecContext->pix_fmt, Width, Height, + OutputFormat, GetSWSCPUFlags() | Resizer, ColorSpace); if (SWS == NULL) { ResetOutputFormat(); throw FFMS_Exception(FFMS_ERROR_SCALING, FFMS_ERROR_INVALID_ARGUMENT, diff --git a/aegisub/libffms/src/core/videosource.h b/aegisub/libffms/src/core/videosource.h index bf09a919a..ee4f92dcc 100644 --- a/aegisub/libffms/src/core/videosource.h +++ b/aegisub/libffms/src/core/videosource.h @@ -25,7 +25,9 @@ extern "C" { #include #include #include +#ifdef FFMS_USE_POSTPROC #include +#endif // FFMS_USE_POSTPROC } // must be included after ffmpeg headers @@ -52,8 +54,10 @@ extern "C" { class FFMS_VideoSource { friend class FFSourceResources; private: +#ifdef FFMS_USE_POSTPROC pp_context_t *PPContext; pp_mode_t *PPMode; +#endif // FFMS_USE_POSTPROC SwsContext *SWS; int LastFrameHeight; int LastFrameWidth; @@ -73,7 +77,8 @@ protected: FFMS_Track Frames; int VideoTrack; int CurrentFrame; - int MPEG4Counter; + int DelayCounter; + int InitialDecode; AVCodecContext *CodecContext; FFMS_VideoSource(const char *SourceFile, FFMS_Index *Index, int Track); @@ -112,7 +117,7 @@ class FFMatroskaVideo : public FFMS_VideoSource { private: MatroskaFile *MF; MatroskaReaderContext MC; - CompressedStream *CS; + TrackCompressionContext *TCC; char ErrorMessage[256]; FFSourceResources Res; size_t PacketNumber; @@ -122,15 +127,14 @@ protected: void Free(bool CloseCodec); public: FFMatroskaVideo(const char *SourceFile, int Track, FFMS_Index *Index, int Threads); - FFMS_Frame *GetFrame(int n); + FFMS_Frame *GetFrame(int n); }; #ifdef HAALISOURCE class FFHaaliVideo : public FFMS_VideoSource { -private: + FFCodecContext HCodecContext; CComPtr pMMC; - std::vector CodecPrivate; AVBitStreamFilterContext *BitStreamFilter; FFSourceResources Res; @@ -139,7 +143,7 @@ protected: void Free(bool CloseCodec); public: FFHaaliVideo(const char *SourceFile, int Track, FFMS_Index *Index, int Threads, enum FFMS_Sources SourceMode); - FFMS_Frame *GetFrame(int n); + FFMS_Frame *GetFrame(int n); }; #endif // HAALISOURCE diff --git a/aegisub/src/audio_provider_ffmpegsource.cpp b/aegisub/src/audio_provider_ffmpegsource.cpp index 1db614614..1ca5db947 100644 --- a/aegisub/src/audio_provider_ffmpegsource.cpp +++ b/aegisub/src/audio_provider_ffmpegsource.cpp @@ -60,7 +60,11 @@ FFmpegSourceAudioProvider::FFmpegSourceAudioProvider(wxString filename) { else if (res != RPC_E_CHANGED_MODE) throw _T("FFmpegSource video provider: COM initialization failure"); #endif +#if FFMS_VERSION >= ((2 << 24) | (14 << 16) | (0 << 8) | 0) + FFMS_Init(0, 1); +#else FFMS_Init(0); +#endif ErrInfo.Buffer = FFMSErrMsg; ErrInfo.BufferSize = sizeof(FFMSErrMsg); @@ -180,7 +184,11 @@ void FFmpegSourceAudioProvider::LoadAudio(wxString filename) { // warn user? } +#if FFMS_VERSION >= ((2 << 24) | (14 << 16) | (1 << 8) | 0) + AudioSource = FFMS_CreateAudioSource(FileNameShort.utf8_str(), TrackNumber, Index, FFMS_DELAY_FIRST_VIDEO_TRACK, &ErrInfo); +#else AudioSource = FFMS_CreateAudioSource(FileNameShort.utf8_str(), TrackNumber, Index, &ErrInfo); +#endif FFMS_DestroyIndex(Index); Index = NULL; if (!AudioSource) { diff --git a/aegisub/src/video_provider_ffmpegsource.cpp b/aegisub/src/video_provider_ffmpegsource.cpp index 23a23654a..921c5ff02 100644 --- a/aegisub/src/video_provider_ffmpegsource.cpp +++ b/aegisub/src/video_provider_ffmpegsource.cpp @@ -66,7 +66,11 @@ FFmpegSourceVideoProvider::FFmpegSourceVideoProvider(wxString filename) { #endif // initialize ffmpegsource // FIXME: CPU detection? +#if FFMS_VERSION >= ((2 << 24) | (14 << 16) | (0 << 8) | 0) + FFMS_Init(0, 1); +#else FFMS_Init(0); +#endif // clean up variables VideoSource = NULL;