diff --git a/FFmpegSource/ffbase.cpp b/FFmpegSource/ffbase.cpp new file mode 100644 index 000000000..b2e85d769 --- /dev/null +++ b/FFmpegSource/ffbase.cpp @@ -0,0 +1,239 @@ +#include "ffmpegsource.h" + +int GetSWSCPUFlags(IScriptEnvironment *Env) { + int Flags = 0; + long CPUFlags = Env->GetCPUFlags(); + + if (CPUFlags & CPUF_MMX) + CPUFlags |= SWS_CPU_CAPS_MMX; + if (CPUFlags & CPUF_INTEGER_SSE) + CPUFlags |= SWS_CPU_CAPS_MMX2; + if (CPUFlags & CPUF_3DNOW) + CPUFlags |= SWS_CPU_CAPS_3DNOW; + + return Flags; +} + +int FFBase::FrameFromDTS(int64_t ADTS) { + for (int i = 0; i < (int)FrameToDTS.size(); i++) + if (FrameToDTS[i].DTS == ADTS) + return i; + return -1; +} + +int FFBase::ClosestFrameFromDTS(int64_t ADTS) { + int Frame = 0; + int64_t BestDiff = 0xFFFFFFFFFFFFFF; + for (int i = 0; i < (int)FrameToDTS.size(); i++) { + int64_t CurrentDiff = FFABS(FrameToDTS[i].DTS - ADTS); + if (CurrentDiff < BestDiff) { + BestDiff = CurrentDiff; + Frame = i; + } + } + return Frame; +} + +int FFBase::FindClosestKeyFrame(int AFrame) { + for (int i = AFrame; i > 0; i--) + if (FrameToDTS[i].KeyFrame) + return i; + return 0; +} + +bool FFBase::LoadFrameInfoFromFile(const char *AVideoCacheFile, const char *ASource, int AVideoTrack) { + char DefaultCacheFilename[1024]; + sprintf(DefaultCacheFilename, "%s.ffv%dcache", ASource, AVideoTrack); + if (!strcmp(AVideoCacheFile, "")) + AVideoCacheFile = DefaultCacheFilename; + + FILE *CacheFile = fopen(AVideoCacheFile, "r"); + if (!CacheFile) + return false; + + if (fscanf(CacheFile, "%d\r\n", &VI.num_frames) <= 0) { + fclose(CacheFile); + return false; + } + + for (int i = 0; i < VI.num_frames; i++) { + int64_t DTSTemp; + int KFTemp; + fscanf(CacheFile, "%lld %d\r\n", &DTSTemp, &KFTemp); + FrameToDTS.push_back(FrameInfo(DTSTemp, KFTemp != 0)); + } + + fclose(CacheFile); + return true; +} + +bool FFBase::SaveFrameInfoToFile(const char *AVideoCacheFile, const char *ASource, int AVideoTrack) { + char DefaultCacheFilename[1024]; + sprintf(DefaultCacheFilename, "%s.ffv%dcache", ASource, AVideoTrack); + if (!strcmp(AVideoCacheFile, "")) + AVideoCacheFile = DefaultCacheFilename; + + FILE *CacheFile = fopen(AVideoCacheFile, "wb"); + if (!CacheFile) + return false; + + fprintf(CacheFile, "%d\r\n", VI.num_frames); + for (int i = 0; i < VI.num_frames; i++) + fprintf(CacheFile, "%lld %d\r\n", FrameToDTS[i].DTS, (int)(FrameToDTS[i].KeyFrame ? 1 : 0)); + + fclose(CacheFile); + return true; +} + +bool FFBase::SaveTimecodesToFile(const char *ATimecodeFile, int64_t ScaleD, int64_t ScaleN) { + if (!strcmp(ATimecodeFile, "")) + return true; + + FILE *TimecodeFile = fopen(ATimecodeFile, "wb"); + if (!TimecodeFile) + return false; + + for (int i = 0; i < VI.num_frames; i++) + fprintf(TimecodeFile, "%f\r\n", (FrameToDTS[i].DTS * ScaleD) / (double)ScaleN); + + fclose(TimecodeFile); + return true; +} + +bool FFBase::PrepareAudioCache(const char *AAudioCacheFile, const char *ASource, int AAudioTrack, IScriptEnvironment *Env) { + char DefaultCacheFilename[1024]; + sprintf(DefaultCacheFilename, "%s.ffa%dcache", ASource, AAudioTrack); + if (!strcmp(AAudioCacheFile, "")) + AAudioCacheFile = DefaultCacheFilename; + + AudioCache = fopen(AAudioCacheFile, "rb"); + if (!AudioCache) { + AudioCache = fopen(AAudioCacheFile, "wb+"); + if (!AudioCache) + Env->ThrowError("FFmpegSource: Failed to open the audio cache file for writing"); + return false; + } + + _fseeki64(AudioCache, 0, SEEK_END); + int64_t CacheSize = _ftelli64(AudioCache); + if (CacheSize > 0) { + VI.num_audio_samples = VI.AudioSamplesFromBytes(CacheSize); + return true; + } + + AudioCache = freopen(AAudioCacheFile, "wb", AudioCache); + if (!AudioCache) + Env->ThrowError("FFmpegSource: Failed to open the audio cache file for writing"); + + return false; +} + +void FFBase::InitPP(int AWidth, int AHeight, const char *APPString, int AQuality, int APixelFormat, IScriptEnvironment *Env) { + if (!strcmp(APPString, "")) + return; + + if (AQuality < 0 || AQuality > PP_QUALITY_MAX) + Env->ThrowError("FFmpegSource: Quality is out of range"); + + PPMode = pp_get_mode_by_name_and_quality((char *)APPString, AQuality); + if (!PPMode) + Env->ThrowError("FFmpegSource: Invalid postprocesing settings"); + + int Flags = GetPPCPUFlags(Env); + + switch (APixelFormat) { + 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; + default: + Env->ThrowError("FFmpegSource: Input format is not supported for postprocessing"); + } + PPContext = pp_get_context(VI.width, VI.height, Flags); + if (avpicture_alloc(&PPPicture, APixelFormat, AWidth, AHeight) < 0) + Env->ThrowError("FFmpegSource: Failed to allocate picture"); +} + +void FFBase::SetOutputFormat(int ACurrentFormat, IScriptEnvironment *Env) { + int Loss; + int BestFormat = avcodec_find_best_pix_fmt((1 << PIX_FMT_YUV420P) | (1 << PIX_FMT_YUYV422) | (1 << PIX_FMT_RGB32) | (1 << PIX_FMT_BGR24), ACurrentFormat, 1 /* Required to prevent pointless RGB32 => RGB24 conversion */, &Loss); + + switch (BestFormat) { + case PIX_FMT_YUV420P: VI.pixel_type = VideoInfo::CS_I420; break; + case PIX_FMT_YUYV422: VI.pixel_type = VideoInfo::CS_YUY2; break; + case PIX_FMT_RGB32: VI.pixel_type = VideoInfo::CS_BGR32; break; + case PIX_FMT_BGR24: VI.pixel_type = VideoInfo::CS_BGR24; break; + default: + Env->ThrowError("FFmpegSource: No suitable output format found"); + } + + if (BestFormat != ACurrentFormat) { + ConvertToFormat = BestFormat; + SWS = sws_getContext(VI.width, VI.height, ACurrentFormat, VI.width, VI.height, ConvertToFormat, GetSWSCPUFlags(Env) | SWS_BICUBIC, NULL, NULL, NULL); + } +} + +PVideoFrame FFBase::OutputFrame(AVFrame *AFrame, IScriptEnvironment *Env) { + AVPicture *SrcPicture = (AVPicture *)AFrame; + + if (PPContext) { + pp_postprocess(AFrame->data, AFrame->linesize, PPPicture.data, PPPicture.linesize, VI.width, VI.height, AFrame->qscale_table, AFrame->qstride, PPMode, PPContext, AFrame->pict_type | (AFrame->qscale_type ? PP_PICT_TYPE_QP2 : 0)); + SrcPicture = &PPPicture; + } + + PVideoFrame Dst = Env->NewVideoFrame(VI); + + if (ConvertToFormat != PIX_FMT_NONE && VI.pixel_type == VideoInfo::CS_I420) { + uint8_t *DstData[3] = {Dst->GetWritePtr(PLANAR_Y), Dst->GetWritePtr(PLANAR_U), Dst->GetWritePtr(PLANAR_V)}; + int DstStride[3] = {Dst->GetPitch(PLANAR_Y), Dst->GetPitch(PLANAR_U), Dst->GetPitch(PLANAR_V)}; + sws_scale(SWS, SrcPicture->data, SrcPicture->linesize, 0, VI.height, DstData, DstStride); + } else if (ConvertToFormat != PIX_FMT_NONE) { + if (VI.IsRGB()) { + uint8_t *DstData[1] = {Dst->GetWritePtr() + Dst->GetPitch() * (Dst->GetHeight() - 1)}; + int DstStride[1] = {-Dst->GetPitch()}; + sws_scale(SWS, SrcPicture->data, SrcPicture->linesize, 0, VI.height, DstData, DstStride); + } else { + uint8_t *DstData[1] = {Dst->GetWritePtr()}; + int DstStride[1] = {Dst->GetPitch()}; + sws_scale(SWS, SrcPicture->data, SrcPicture->linesize, 0, VI.height, DstData, DstStride); + } + } else if (VI.pixel_type == VideoInfo::CS_I420) { + Env->BitBlt(Dst->GetWritePtr(PLANAR_Y), Dst->GetPitch(PLANAR_Y), SrcPicture->data[0], SrcPicture->linesize[0], Dst->GetRowSize(PLANAR_Y), Dst->GetHeight(PLANAR_Y)); + Env->BitBlt(Dst->GetWritePtr(PLANAR_U), Dst->GetPitch(PLANAR_U), SrcPicture->data[1], SrcPicture->linesize[1], Dst->GetRowSize(PLANAR_U), Dst->GetHeight(PLANAR_U)); + Env->BitBlt(Dst->GetWritePtr(PLANAR_V), Dst->GetPitch(PLANAR_V), SrcPicture->data[2], SrcPicture->linesize[2], Dst->GetRowSize(PLANAR_V), Dst->GetHeight(PLANAR_V)); + } else { + if (VI.IsRGB()) + Env->BitBlt(Dst->GetWritePtr() + Dst->GetPitch() * (Dst->GetHeight() - 1), -Dst->GetPitch(), SrcPicture->data[0], SrcPicture->linesize[0], Dst->GetRowSize(), Dst->GetHeight()); + else + Env->BitBlt(Dst->GetWritePtr(), Dst->GetPitch(), SrcPicture->data[0], SrcPicture->linesize[0], Dst->GetRowSize(), Dst->GetHeight()); + } + + return Dst; +} + +void __stdcall FFBase::GetAudio(void* Buf, __int64 Start, __int64 Count, IScriptEnvironment* Env) { + _fseeki64(AudioCache, VI.BytesFromAudioSamples(Start), SEEK_SET); + fread(Buf, 1, VI.BytesFromAudioSamples(Count), AudioCache); +} + +FFBase::FFBase() { + memset(&VI, 0, sizeof(VI)); + AudioCache = NULL; + PPContext = NULL; + PPMode = NULL; + SWS = NULL; + ConvertToFormat = PIX_FMT_NONE; + memset(&PPPicture, 0, sizeof(PPPicture)); + DecodeFrame = avcodec_alloc_frame(); +} + +FFBase::~FFBase() { + if (SWS) + sws_freeContext(SWS); + if (PPMode) + pp_free_mode(PPMode); + if (PPContext) + pp_free_context(PPContext); + avpicture_free(&PPPicture); + av_free(DecodeFrame); +} \ No newline at end of file diff --git a/FFmpegSource/ffmpegsource.cpp b/FFmpegSource/ffmpegsource.cpp index de208fada..1634b2972 100644 --- a/FFmpegSource/ffmpegsource.cpp +++ b/FFmpegSource/ffmpegsource.cpp @@ -1,221 +1,64 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -extern "C" { -#include -#include -#include -} - -#include "MatroskaParser.h" -#include "avisynth.h" +#include "ffmpegsource.h" #include "stdiostream.c" #include "matroskacodecs.c" -class FFBase : public IClip { -protected: - VideoInfo VI; -public: - bool __stdcall GetParity(int n) { return false; } - void __stdcall GetAudio(void* buf, __int64 start, __int64 count, IScriptEnvironment* env) { } - void __stdcall SetCacheHints(int cachehints, int frame_range) { } - PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* env) { return NULL; } - const VideoInfo& __stdcall GetVideoInfo() { return VI; } - - FFBase() { - memset(&VI, 0, sizeof(VI)); - } -}; - -class FFVideoBase : public FFBase { -private: - SwsContext *SWS; - int ConvertToFormat; - int ConvertFromFormat; -protected: - struct FrameInfo { - int64_t DTS; - bool KeyFrame; - - FrameInfo(int64_t _DTS, bool _KeyFrame) : DTS(_DTS), KeyFrame(_KeyFrame) {}; - }; - - std::vector FrameToDTS; - - int FindClosestKeyFrame(int Frame) { - for (unsigned int i = Frame; i > 0; i--) - if (FrameToDTS[i].KeyFrame) - return i; - return 0; - } - - int FrameFromDTS(int64_t DTS) { - for (unsigned int i = 0; i < FrameToDTS.size(); i++) - if (FrameToDTS[i].DTS == DTS) - return i; - return -1; - } - - int ClosestFrameFromDTS(int64_t DTS) { - int Frame = 0; - int64_t BestDiff = 0xFFFFFFFFFFFFFF; - for (unsigned int i = 0; i < FrameToDTS.size(); i++) { - int64_t CurrentDiff = FFABS(FrameToDTS[i].DTS - DTS); - if (CurrentDiff < BestDiff) { - BestDiff = CurrentDiff; - Frame = i; - } - } - return Frame; - } - - bool LoadFrameInfoFromFile(FILE *CacheFile) { - if (ftell(CacheFile)) { - rewind(CacheFile); - - fscanf(CacheFile, "%d\n", &VI.num_frames); - - for (int i = 0; i < VI.num_frames; i++) { - int64_t DTSTemp; - int KFTemp; - fscanf(CacheFile, "%lld %d\n", &DTSTemp, &KFTemp); - FrameToDTS.push_back(FrameInfo(DTSTemp, KFTemp != 0)); - } - - return true; - } - - return false; - } - - void SetOutputFormat(int CurrentFormat, IScriptEnvironment *Env) { - int Loss; - int BestFormat = avcodec_find_best_pix_fmt((1 << PIX_FMT_YUV420P) | (1 << PIX_FMT_YUYV422) | (1 << PIX_FMT_RGB32) | (1 << PIX_FMT_BGR24), CurrentFormat, 1 /* Required to prevent pointless RGB32 => RGB24 conversion */, &Loss); - - switch (BestFormat) { - case PIX_FMT_YUV420P: VI.pixel_type = VideoInfo::CS_I420; break; - case PIX_FMT_YUYV422: VI.pixel_type = VideoInfo::CS_YUY2; break; - case PIX_FMT_RGB32: VI.pixel_type = VideoInfo::CS_BGR32; break; - case PIX_FMT_BGR24: VI.pixel_type = VideoInfo::CS_BGR24; break; - default: - Env->ThrowError("FFmpegSource: No suitable output format found"); - } - - if (BestFormat != CurrentFormat) { - ConvertFromFormat = CurrentFormat; - ConvertToFormat = BestFormat; - SWS = sws_getContext(VI.width, VI.height, ConvertFromFormat, VI.width, VI.height, ConvertToFormat, SWS_BICUBIC, NULL, NULL, NULL); - } - } - - PVideoFrame OutputFrame(AVFrame *Frame, IScriptEnvironment *Env) { - PVideoFrame Dst = Env->NewVideoFrame(VI); - - if (ConvertToFormat != PIX_FMT_NONE && VI.pixel_type == VideoInfo::CS_I420) { - uint8_t *DstData[3] = {Dst->GetWritePtr(PLANAR_Y), Dst->GetWritePtr(PLANAR_U), Dst->GetWritePtr(PLANAR_V)}; - int DstStride[3] = {Dst->GetPitch(PLANAR_Y), Dst->GetPitch(PLANAR_U), Dst->GetPitch(PLANAR_V)}; - sws_scale(SWS, Frame->data, Frame->linesize, 0, VI.height, DstData, DstStride); - } else if (ConvertToFormat != PIX_FMT_NONE) { - if (VI.IsRGB()) { - uint8_t *DstData[1] = {Dst->GetWritePtr() + Dst->GetPitch() * (Dst->GetHeight() - 1)}; - int DstStride[1] = {-Dst->GetPitch()}; - sws_scale(SWS, Frame->data, Frame->linesize, 0, VI.height, DstData, DstStride); - } else { - uint8_t *DstData[1] = {Dst->GetWritePtr()}; - int DstStride[1] = {Dst->GetPitch()}; - sws_scale(SWS, Frame->data, Frame->linesize, 0, VI.height, DstData, DstStride); - } - } else if (VI.pixel_type == VideoInfo::CS_I420) { - Env->BitBlt(Dst->GetWritePtr(PLANAR_Y), Dst->GetPitch(PLANAR_Y), Frame->data[0], Frame->linesize[0], Dst->GetRowSize(PLANAR_Y), Dst->GetHeight(PLANAR_Y)); - Env->BitBlt(Dst->GetWritePtr(PLANAR_U), Dst->GetPitch(PLANAR_U), Frame->data[1], Frame->linesize[1], Dst->GetRowSize(PLANAR_U), Dst->GetHeight(PLANAR_U)); - Env->BitBlt(Dst->GetWritePtr(PLANAR_V), Dst->GetPitch(PLANAR_V), Frame->data[2], Frame->linesize[2], Dst->GetRowSize(PLANAR_V), Dst->GetHeight(PLANAR_V)); - } else { - if (VI.IsRGB()) - Env->BitBlt(Dst->GetWritePtr() + Dst->GetPitch() * (Dst->GetHeight() - 1), -Dst->GetPitch(), Frame->data[0], Frame->linesize[0], Dst->GetRowSize(), Dst->GetHeight()); - else - Env->BitBlt(Dst->GetWritePtr(), Dst->GetPitch(), Frame->data[0], Frame->linesize[0], Dst->GetRowSize(), Dst->GetHeight()); - } - - return Dst; - } - -public: - FFVideoBase() { - SWS = NULL; - ConvertToFormat = PIX_FMT_NONE; - ConvertFromFormat = PIX_FMT_NONE; - } -}; - -class FFAudioBase : public FFBase { -protected: - struct SampleInfo { - int64_t SampleStart; - int64_t DTS; - int64_t FilePos; - bool KeyFrame; - SampleInfo(int64_t _SampleStart, int64_t _DTS, int64_t _FilePos, bool _KeyFrame) { - DTS = _DTS; - SampleStart = _SampleStart; - FilePos = _FilePos; - KeyFrame = _KeyFrame; - } - }; - - std::vector SI; - - int FindClosestAudioKeyFrame(int64_t Sample) { - for (unsigned int i = SI.size() - 1; i > 0; i--) - if (SI[i].SampleStart <= Sample && SI[i].KeyFrame) - return i; - return 0; - } - - int SampleFromDTS(int64_t DTS) { - for (unsigned int i = 0; i < SI.size(); i++) - if (SI[i].DTS == DTS) - return i; - return -1; - } - - int ClosestSampleFromDTS(int64_t DTS) { - int Index = 0; - int64_t BestDiff = 0xFFFFFFFFFFFFFF; - for (unsigned int i = 0; i < SI.size(); i++) { - int64_t CurrentDiff = FFABS(SI[i].DTS - DTS); - if (CurrentDiff < BestDiff) { - BestDiff = CurrentDiff; - Index = i; - } - } - return Index; - } -}; - -class FFMatroskaBase { +class FFMatroskaSource : public FFBase { private: StdIoStream ST; unsigned int BufferSize; - CompressedStream *CS; -protected: - AVCodecContext CodecContext; - AVCodec *Codec; + CompressedStream *VideoCS; + CompressedStream *AudioCS; + + AVCodecContext *VideoCodecContext; + MatroskaFile *MF; - int Track; char ErrorMessage[256]; uint8_t *Buffer; - FFMatroskaBase(const char *Source, int _Track, unsigned char TrackType, IScriptEnvironment* Env) { - Track = _Track; + int CurrentFrame; + + int ReadFrame(uint64_t AFilePos, unsigned int AFrameSize, CompressedStream *ACS, IScriptEnvironment *Env); + int DecodeNextFrame(AVFrame *AFrame, int64_t *AFirstStartTime, IScriptEnvironment* Env); + + int GetTrackIndex(int Index, unsigned char ATrackType, IScriptEnvironment *Env) { + if (Index == -1) + for (unsigned int i = 0; i < mkv_GetNumTracks(MF); i++) + if (mkv_GetTrackInfo(MF, i)->Type == ATrackType) { + Index = i; + break; + } + + if (Index == -1) + Env->ThrowError("FFmpegSource: No %s track found", (ATrackType & TT_VIDEO) ? "video" : "audio"); + if (Index <= -2) + return -2; + + if (Index >= (int)mkv_GetNumTracks(MF)) + Env->ThrowError("FFmpegSource: Invalid %s track number", (ATrackType & TT_VIDEO) ? "video" : "audio"); + + TrackInfo *TI = mkv_GetTrackInfo(MF, Index); + + if (TI->Type != ATrackType) + Env->ThrowError("FFmpegSource: Selected track is not %s", (ATrackType & TT_VIDEO) ? "video" : "audio"); + + return Index; + } + +public: + FFMatroskaSource(const char *ASource, int AVideoTrack, int AAudioTrack, const char *ATimecodes, bool AVCache, const char *AVideoCache, const char *AAudioCache, const char *APPString, int AQuality, IScriptEnvironment* Env) { + CurrentFrame = 0; + int VideoTrack; + int AudioTrack; + unsigned int TrackMask = ~0; + AVCodecContext *AudioCodecContext = NULL; + AVCodec *AudioCodec = NULL; + VideoCodecContext = NULL; + AVCodec *VideoCodec = NULL; + TrackInfo *VideoTI = NULL; BufferSize = 0; Buffer = NULL; - CS = NULL; + VideoCS = NULL; + AudioCS = NULL; memset(&ST,0,sizeof(ST)); ST.base.read = (int (__cdecl *)(InputStream *,ulonglong,void *,int))StdIoRead; @@ -227,9 +70,9 @@ protected: ST.base.memfree = (void (__cdecl *)(InputStream *,void *)) StdIoFree; ST.base.progress = (int (__cdecl *)(InputStream *,ulonglong,ulonglong))StdIoProgress; - ST.fp = fopen(Source, "rb"); + ST.fp = fopen(ASource, "rb"); if (ST.fp == NULL) - Env->ThrowError("FFmpegSource: Can't open '%s': %s", Source, strerror(errno)); + Env->ThrowError("FFmpegSource: Can't open '%s': %s", ASource, strerror(errno)); setvbuf(ST.fp, NULL, _IOFBF, CACHESIZE); @@ -239,327 +82,249 @@ protected: Env->ThrowError("FFmpegSource: Can't parse Matroska file: %s", ErrorMessage); } - if (Track < 0) - for (unsigned int i = 0; i < mkv_GetNumTracks(MF); i++) - if (mkv_GetTrackInfo(MF, i)->Type == TrackType) { - Track = i; - break; - } + VideoTrack = GetTrackIndex(AVideoTrack, TT_VIDEO, Env); + AudioTrack = GetTrackIndex(AAudioTrack, TT_AUDIO, Env); - if (Track < 0) - Env->ThrowError("FFmpegSource: No suitable track found"); + bool VCacheIsValid = true; + bool ACacheIsValid = true; - if ((unsigned int)Track >= mkv_GetNumTracks(MF)) - Env->ThrowError("FFmpegSource: Invalid track number"); + if (VideoTrack >= 0) { + VCacheIsValid = LoadFrameInfoFromFile(AVideoCache, ASource, VideoTrack); - TrackInfo *TI = mkv_GetTrackInfo(MF, Track); + VideoTI = mkv_GetTrackInfo(MF, VideoTrack); - if (TI->Type != TrackType) - Env->ThrowError("FFmpegSource: Selected track is not of the right type"); + if (VideoTI->CompEnabled) { + VideoCS = cs_Create(MF, VideoTrack, ErrorMessage, sizeof(ErrorMessage)); + if (VideoCS == NULL) + Env->ThrowError("FFmpegSource: Can't create decompressor: %s", ErrorMessage); + } - mkv_SetTrackMask(MF, ~(1 << Track)); + VideoCodecContext = avcodec_alloc_context(); + VideoCodecContext->extradata = (uint8_t *)VideoTI->CodecPrivate; + VideoCodecContext->extradata_size = VideoTI->CodecPrivateSize; - if (TI->CompEnabled) { - CS = cs_Create(MF, Track, ErrorMessage, sizeof(ErrorMessage)); - if (CS == NULL) - Env->ThrowError("FFmpegSource: Can't create decompressor: %s", ErrorMessage); + VideoCodec = avcodec_find_decoder(MatroskaToFFCodecID(VideoTI)); + if (VideoCodec == NULL) + Env->ThrowError("FFmpegSource: Video codec not found"); + + if (avcodec_open(VideoCodecContext, VideoCodec) < 0) + Env->ThrowError("FFmpegSource: Could not open video codec"); + + VI.image_type = VideoInfo::IT_TFF; + VI.width = VideoTI->AV.Video.PixelWidth; + VI.height = VideoTI->AV.Video.PixelHeight; + VI.fps_denominator = 1; + VI.fps_numerator = 30; + + SetOutputFormat(VideoCodecContext->pix_fmt, Env); + InitPP(VI.width, VI.height, APPString, AQuality, VideoCodecContext->pix_fmt, Env); + + if (!VCacheIsValid) + TrackMask &= ~(1 << VideoTrack); } - avcodec_get_context_defaults(&CodecContext); - CodecContext.extradata = (uint8_t *)TI->CodecPrivate; - CodecContext.extradata_size = TI->CodecPrivateSize; + if (AudioTrack >= 0) { + TrackInfo *AudioTI = mkv_GetTrackInfo(MF, AudioTrack); - Codec = avcodec_find_decoder(MatroskaToFFCodecID(TI)); - if (Codec == NULL) - Env->ThrowError("FFmpegSource: Codec not found"); + if (AudioTI->CompEnabled) { + AudioCS = cs_Create(MF, AudioTrack, ErrorMessage, sizeof(ErrorMessage)); + if (AudioCS == NULL) + Env->ThrowError("FFmpegSource: Can't create decompressor: %s", ErrorMessage); + } - if (avcodec_open(&CodecContext, Codec) < 0) - Env->ThrowError("FFmpegSource: Could not open codec"); + AudioCodecContext = avcodec_alloc_context(); + AudioCodecContext->extradata = (uint8_t *)AudioTI->CodecPrivate; + AudioCodecContext->extradata_size = AudioTI->CodecPrivateSize; + + AudioCodec = avcodec_find_decoder(MatroskaToFFCodecID(AudioTI)); + if (AudioCodec == NULL) + Env->ThrowError("FFmpegSource: Audio codec not found"); + + if (avcodec_open(AudioCodecContext, AudioCodec) < 0) + Env->ThrowError("FFmpegSource: Could not open audio codec"); + + switch (AudioCodecContext->sample_fmt) { + case SAMPLE_FMT_U8: VI.sample_type = SAMPLE_INT8; break; + case SAMPLE_FMT_S16: VI.sample_type = SAMPLE_INT16; break; + case SAMPLE_FMT_S24: VI.sample_type = SAMPLE_INT24; break; + case SAMPLE_FMT_S32: VI.sample_type = SAMPLE_INT32; break; + case SAMPLE_FMT_FLT: VI.sample_type = SAMPLE_FLOAT; break; + default: + Env->ThrowError("FFmpegSource: Unsupported/unknown sample format"); + } + + VI.nchannels = AudioCodecContext->channels; + VI.audio_samples_per_second = AudioCodecContext->sample_rate; + + ACacheIsValid = PrepareAudioCache(AAudioCache, ASource, AudioTrack, Env); + if (!ACacheIsValid) + TrackMask &= ~(1 << AudioTrack); + } + + mkv_SetTrackMask(MF, TrackMask); + + uint8_t DecodingBuffer[AVCODEC_MAX_AUDIO_FRAME_SIZE]; + + // Needs to be indexed? + if (!ACacheIsValid || !VCacheIsValid) { + uint64_t StartTime, EndTime, FilePos; + unsigned int Track, FrameFlags, FrameSize; + + while (mkv_ReadFrame(MF, 0, &Track, &StartTime, &EndTime, &FilePos, &FrameSize, &FrameFlags) == 0) + if (Track == VideoTrack && !VCacheIsValid) { + FrameToDTS.push_back(FrameInfo(StartTime, (FrameFlags & FRAME_KF) != 0)); + VI.num_frames++; + } else if (Track == AudioTrack && !ACacheIsValid) { + int Size = ReadFrame(FilePos, FrameSize, AudioCS, Env); + uint8_t *Data = Buffer; + + while (Size > 0) { + int TempOutputBufSize = AVCODEC_MAX_AUDIO_FRAME_SIZE; + int Ret = avcodec_decode_audio2(AudioCodecContext, (int16_t *)DecodingBuffer, &TempOutputBufSize, Data, Size); + if (Ret < 0) + Env->ThrowError("FFmpegSource: Audio decoding error"); + + Size -= Ret; + Data += Ret; + VI.num_audio_samples += VI.AudioSamplesFromBytes(TempOutputBufSize); + + fwrite(DecodingBuffer, 1, TempOutputBufSize, AudioCache); + } + } + + if (AVCache && !VCacheIsValid) + if (!SaveFrameInfoToFile(AVideoCache, ASource, VideoTrack)) + Env->ThrowError("FFmpegSource: Failed to write video cache info"); + } + + if (AudioTrack >= 0) { + avcodec_close(AudioCodecContext); + av_free(AudioCodecContext); + } + + if (VideoTrack >= 0) { + mkv_SetTrackMask(MF, ~(1 << VideoTrack)); + mkv_Seek(MF, FrameToDTS[0].DTS, MKVF_SEEK_TO_PREV_KEYFRAME); + + if (FrameToDTS.size() >= 2) { + double DTSDiff = (double)(FrameToDTS.back().DTS - FrameToDTS.front().DTS); + VI.fps_denominator = (unsigned int)(DTSDiff * mkv_TruncFloat(VideoTI->TimecodeScale) / (double)1000 / (double)(VI.num_frames - 1) + 0.5); + VI.fps_numerator = 1000000; + } + + + if (!SaveTimecodesToFile(ATimecodes, mkv_TruncFloat(VideoTI->TimecodeScale), 1000000)) + Env->ThrowError("FFmpegSource: Failed to write timecodes"); + } } - ~FFMatroskaBase() { + ~FFMatroskaSource() { free(Buffer); mkv_Close(MF); fclose(ST.fp); - avcodec_close(&CodecContext); - } - - unsigned int ReadNextFrame(int64_t *FirstStartTime, IScriptEnvironment *Env) { - unsigned int TrackNumber, FrameFlags, FrameSize; - uint64_t EndTime, FilePos, StartTime; - *FirstStartTime = -1; - - while (mkv_ReadFrame(MF, 0, &TrackNumber, &StartTime, &EndTime, &FilePos, &FrameSize, &FrameFlags) == 0) { - if (*FirstStartTime < 0) - *FirstStartTime = StartTime; - if (CS) { - char CSBuffer[4096]; - - int DecompressedFrameSize = 0; - - cs_NextFrame(CS, FilePos, FrameSize); - - for (;;) { - int ReadBytes = cs_ReadData(CS, CSBuffer, sizeof(CSBuffer)); - if (ReadBytes < 0) - Env->ThrowError("FFmpegSource: Error decompressing data: %s", cs_GetLastError(CS)); - if (ReadBytes == 0) - return DecompressedFrameSize; - - if (BufferSize < DecompressedFrameSize + ReadBytes) { - BufferSize = FrameSize; - Buffer = (uint8_t *)realloc(Buffer, BufferSize); - if (Buffer == NULL) - Env->ThrowError("FFmpegSource: Out of memory"); - } - - memcpy(Buffer + DecompressedFrameSize, CSBuffer, ReadBytes); - DecompressedFrameSize += ReadBytes; - } - } else { - if (fseek(ST.fp, FilePos, SEEK_SET)) - Env->ThrowError("FFmpegSource: fseek(): %s", strerror(errno)); - - if (BufferSize < FrameSize) { - BufferSize = FrameSize; - Buffer = (uint8_t *)realloc(Buffer, BufferSize); - if (Buffer == NULL) - Env->ThrowError("FFmpegSource: Out of memory"); - } - - size_t ReadBytes = fread(Buffer, 1, FrameSize, ST.fp); - if (ReadBytes != FrameSize) { - if (ReadBytes == 0) { - if (feof(ST.fp)) - Env->ThrowError("FFmpegSource: Unexpected EOF while reading frame"); - else - Env->ThrowError("FFmpegSource: Error reading frame: %s", strerror(errno)); - } else - Env->ThrowError("FFmpegSource: Short read while reading frame"); - return 0; - } - - return FrameSize; - } - } - - return 0; - } -}; - - -class FFMKASource : public FFAudioBase, private FFMatroskaBase { -private: - int CurrentSample; - int DecodeNextAudioBlock(uint8_t *Buf, int64_t *FirstStartTime, int64_t *Count, IScriptEnvironment* Env); -public: - FFMKASource(const char *Source, int _Track, IScriptEnvironment* Env) : FFMatroskaBase(Source, _Track, TT_AUDIO, Env) { - CurrentSample = 0; - TrackInfo *TI = mkv_GetTrackInfo(MF, Track); - - switch (CodecContext.sample_fmt) { - case SAMPLE_FMT_U8: VI.sample_type = SAMPLE_INT8; break; - case SAMPLE_FMT_S16: VI.sample_type = SAMPLE_INT16; break; - case SAMPLE_FMT_S24: VI.sample_type = SAMPLE_INT24; break; - case SAMPLE_FMT_S32: VI.sample_type = SAMPLE_INT32; break; - case SAMPLE_FMT_FLT: VI.sample_type = SAMPLE_FLOAT; break; - default: - Env->ThrowError("FFmpegSource: Unsupported/unknown sample format"); - } - - VI.nchannels = TI->AV.Audio.Channels; - VI.audio_samples_per_second = mkv_TruncFloat(TI->AV.Audio.SamplingFreq); - - unsigned TrackNumber, FrameSize, FrameFlags; - ulonglong StartTime, EndTime, FilePos; - - while (mkv_ReadFrame(MF, 0, &TrackNumber, &StartTime, &EndTime, &FilePos, &FrameSize, &FrameFlags) == 0) { - if (!(FrameFlags & FRAME_UNKNOWN_START)) - SI.push_back(SampleInfo(VI.num_audio_samples, StartTime, FilePos, (FrameFlags & FRAME_KF) != 0)); - if (true) { - switch (CodecContext.codec_id) { - case CODEC_ID_MP3: - case CODEC_ID_MP2: - VI.num_audio_samples += 1152; break; - default: - Env->ThrowError("Only mp2 and mp3 is currently supported in matroska"); - } - } else { - } - } - - mkv_Seek(MF, SI[0].DTS, MKVF_SEEK_TO_PREV_KEYFRAME); - avcodec_flush_buffers(&CodecContext); - } - - void __stdcall GetAudio(void* Buf, __int64 Start, __int64 Count, IScriptEnvironment* Env); -}; - -int FFMKASource::DecodeNextAudioBlock(uint8_t *Buf, int64_t *FirstStartTime, int64_t *Count, IScriptEnvironment* Env) { - int Ret = -1; - int FrameSize; - int64_t TempStartTime = -1; - *FirstStartTime = -1; - *Count = 0; - - while (FrameSize = ReadNextFrame(&TempStartTime, Env)) { - if (*FirstStartTime < 0) - *FirstStartTime = TempStartTime; - uint8_t *Data = (uint8_t *)Buffer; - int Size = FrameSize; - - while (Size > 0) { - int TempOutputBufSize = AVCODEC_MAX_AUDIO_FRAME_SIZE; - Ret = avcodec_decode_audio2(&CodecContext, (int16_t *)Buf, &TempOutputBufSize, Data, Size); - if (Ret < 0) - goto Done; - Size -= Ret; - Data += Ret; - Buf += TempOutputBufSize; - *Count += VI.AudioSamplesFromBytes(TempOutputBufSize); - - if (Size <= 0) - goto Done; - } - } - -Done: - return Ret; -} - -void __stdcall FFMKASource::GetAudio(void* Buf, __int64 Start, __int64 Count, IScriptEnvironment* Env) { - bool HasSeeked = false; - int ClosestKF = FindClosestAudioKeyFrame(Start); - - memset(Buf, 0, VI.BytesFromAudioSamples(Count)); - - if (Start < CurrentSample || SI[ClosestKF].SampleStart > CurrentSample) { - mkv_Seek(MF, SI[max(ClosestKF - 10, 0)].DTS, MKVF_SEEK_TO_PREV_KEYFRAME); - avcodec_flush_buffers(&CodecContext); - HasSeeked = true; - } - - uint8_t *DstBuf = (uint8_t *)Buf; - int64_t RemainingSamples = Count; - int64_t DecodeCount; - - do { - int64_t StartTime; - uint8_t DecodingBuffer[AVCODEC_MAX_AUDIO_FRAME_SIZE]; - int64_t DecodeStart = CurrentSample; - int Ret = DecodeNextAudioBlock(DecodingBuffer, &StartTime, &DecodeCount, Env); - - if (HasSeeked) { - HasSeeked = false; - - int CurrentSampleIndex = SampleFromDTS(StartTime); - if (StartTime < 0 || CurrentSampleIndex < 0 || (CurrentSample = DecodeStart = SI[max(CurrentSampleIndex, 0)].SampleStart) < 0) - Env->ThrowError("FFmpegSource: Sample accurate seeking is not possible in this file"); - } - - int OffsetBytes = VI.BytesFromAudioSamples(max(0, Start - DecodeStart)); - int CopyBytes = max(0, VI.BytesFromAudioSamples(min(RemainingSamples, DecodeCount - max(0, Start - DecodeStart)))); - - memcpy(DstBuf, DecodingBuffer + OffsetBytes, CopyBytes); - DstBuf += CopyBytes; - - CurrentSample += DecodeCount; - RemainingSamples -= VI.AudioSamplesFromBytes(CopyBytes); - - } while (RemainingSamples > 0 && DecodeCount > 0); -} - -class FFMKVSource : public FFVideoBase, private FFMatroskaBase { -private: - AVFrame *Frame; - int CurrentFrame; - - int DecodeNextFrame(AVFrame *Frame, int64_t *FirstStartTime, IScriptEnvironment* Env); -public: - FFMKVSource(const char *Source, int _Track, FILE *Timecodes, bool Cache, FILE *CacheFile, IScriptEnvironment* Env) : FFMatroskaBase(Source, _Track, TT_VIDEO, Env) { - Frame = NULL; - CurrentFrame = 0; - TrackInfo *TI = mkv_GetTrackInfo(MF, Track); - - VI.image_type = VideoInfo::IT_TFF; - VI.width = TI->AV.Video.PixelWidth; - VI.height = TI->AV.Video.PixelHeight; - VI.fps_denominator = 1; - VI.fps_numerator = 30; - - SetOutputFormat(CodecContext.pix_fmt, Env); - - // Needs to be indexed? - if (!(Cache && LoadFrameInfoFromFile(CacheFile))) { - unsigned int TrackNumber, FrameSize, FrameFlags; - uint64_t StartTime, EndTime, FilePos; - - while (mkv_ReadFrame(MF, 0, &TrackNumber, &StartTime, &EndTime, &FilePos, &FrameSize, &FrameFlags) == 0) { - FrameToDTS.push_back(FrameInfo(StartTime, (FrameFlags & FRAME_KF) != 0)); - VI.num_frames++; - } - - if (VI.num_frames == 0) - Env->ThrowError("FFmpegSource: Video track contains no frames"); - - mkv_Seek(MF, FrameToDTS[0].DTS, MKVF_SEEK_TO_PREV_KEYFRAME); - - if (Cache) { - fprintf(CacheFile, "%d\n", VI.num_frames); - for (int i = 0; i < VI.num_frames; i++) - fprintf(CacheFile, "%lld %d\n", FrameToDTS[i].DTS, (int)(FrameToDTS[i].KeyFrame ? 1 : 0)); - } - } - - if (Timecodes) - for (int i = 0; i < VI.num_frames; i++) - fprintf(Timecodes, "%f\n", (FrameToDTS[i].DTS * mkv_TruncFloat(TI->TimecodeScale)) / (double)(1000000)); - - Frame = avcodec_alloc_frame(); - } - - ~FFMKVSource() { - av_free(Frame); + if (AudioCache) + fclose(AudioCache); + if (VideoCodecContext) + avcodec_close(VideoCodecContext); + av_free(VideoCodecContext); } PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* Env); }; -int FFMKVSource::DecodeNextFrame(AVFrame *Frame, int64_t *FirstStartTime, IScriptEnvironment* Env) { +int FFMatroskaSource::ReadFrame(uint64_t AFilePos, unsigned int AFrameSize, CompressedStream *ACS, IScriptEnvironment *Env) { + if (ACS) { + char CSBuffer[4096]; + + unsigned int DecompressedFrameSize = 0; + + cs_NextFrame(ACS, AFilePos, AFrameSize); + + for (;;) { + int ReadBytes = cs_ReadData(ACS, CSBuffer, sizeof(CSBuffer)); + if (ReadBytes < 0) + Env->ThrowError("FFmpegSource: Error decompressing data: %s", cs_GetLastError(ACS)); + if (ReadBytes == 0) { + return DecompressedFrameSize; + } + + if (BufferSize < DecompressedFrameSize + ReadBytes) { + BufferSize = AFrameSize; + Buffer = (uint8_t *)realloc(Buffer, BufferSize); + if (Buffer == NULL) + Env->ThrowError("FFmpegSource: Out of memory"); + } + + memcpy(Buffer + DecompressedFrameSize, CSBuffer, ReadBytes); + DecompressedFrameSize += ReadBytes; + } + } else { + if (fseek(ST.fp, AFilePos, SEEK_SET)) + Env->ThrowError("FFmpegSource: fseek(): %s", strerror(errno)); + + if (BufferSize < AFrameSize) { + BufferSize = AFrameSize; + Buffer = (uint8_t *)realloc(Buffer, BufferSize); + if (Buffer == NULL) + Env->ThrowError("FFmpegSource: Out of memory"); + } + + size_t ReadBytes = fread(Buffer, 1, AFrameSize, ST.fp); + if (ReadBytes != AFrameSize) { + if (ReadBytes == 0) { + if (feof(ST.fp)) + Env->ThrowError("FFmpegSource: Unexpected EOF while reading frame"); + else + Env->ThrowError("FFmpegSource: Error reading frame: %s", strerror(errno)); + } else + Env->ThrowError("FFmpegSource: Short read while reading frame"); + Env->ThrowError("FFmpegSource: Unknown read error"); + } + + return AFrameSize; + } + + return 0; +} + +int FFMatroskaSource::DecodeNextFrame(AVFrame *AFrame, int64_t *AFirstStartTime, IScriptEnvironment* Env) { int FrameFinished = 0; int Ret = -1; - int FrameSize; - int64_t TempStartTime = -1; + *AFirstStartTime = -1; - while (FrameSize = ReadNextFrame(&TempStartTime, Env)) { - if (*FirstStartTime < 0) - *FirstStartTime = TempStartTime; - Ret = avcodec_decode_video(&CodecContext, Frame, &FrameFinished, Buffer, FrameSize); + uint64_t StartTime, EndTime, FilePos; + unsigned int Track, FrameFlags, FrameSize; + + while (mkv_ReadFrame(MF, 0, &Track, &StartTime, &EndTime, &FilePos, &FrameSize, &FrameFlags) == 0) { + FrameSize = ReadFrame(FilePos, FrameSize, VideoCS, Env); + if (*AFirstStartTime < 0) + *AFirstStartTime = StartTime; + Ret = avcodec_decode_video(VideoCodecContext, AFrame, &FrameFinished, Buffer, FrameSize); if (FrameFinished) goto Done; } // Flush the last frame - if (CurrentFrame == VI.num_frames - 1) - Ret = avcodec_decode_video(&CodecContext, Frame, &FrameFinished, NULL, 0); + if (CurrentFrame == VI.num_frames - 1 && VideoCodecContext->has_b_frames) + Ret = avcodec_decode_video(VideoCodecContext, AFrame, &FrameFinished, NULL, 0); Done: return Ret; } -PVideoFrame __stdcall FFMKVSource::GetFrame(int n, IScriptEnvironment* Env) { +PVideoFrame __stdcall FFMatroskaSource::GetFrame(int n, IScriptEnvironment* Env) { bool HasSeeked = false; if (n < CurrentFrame || FindClosestKeyFrame(n) > CurrentFrame) { mkv_Seek(MF, FrameToDTS[n].DTS, MKVF_SEEK_TO_PREV_KEYFRAME); - avcodec_flush_buffers(&CodecContext); + avcodec_flush_buffers(VideoCodecContext); HasSeeked = true; } do { int64_t StartTime; - int Ret = DecodeNextFrame(Frame, &StartTime, Env); + int Ret = DecodeNextFrame(DecodeFrame, &StartTime, Env); if (HasSeeked) { HasSeeked = false; @@ -571,369 +336,141 @@ PVideoFrame __stdcall FFMKVSource::GetFrame(int n, IScriptEnvironment* Env) { CurrentFrame++; } while (CurrentFrame <= n); - return OutputFrame(Frame, Env); + return OutputFrame(DecodeFrame, Env); } -class FFAVFormatBase { -protected: +class FFmpegSource : public FFBase { +private: AVFormatContext *FormatContext; - AVCodecContext *CodecContext; - AVCodec *Codec; - int Track; -public: - FFAVFormatBase(const char *Source, int _Track, CodecType TrackType, IScriptEnvironment* Env) { - FormatContext = NULL; - Codec = NULL; - Track = _Track; + AVCodecContext *VideoCodecContext; - if (av_open_input_file(&FormatContext, Source, NULL, 0, NULL) != 0) - Env->ThrowError("FFmpegSource: Couldn't open \"%s\"", Source); + int VideoTrack; - if (av_find_stream_info(FormatContext) < 0) - Env->ThrowError("FFmpegSource: Couldn't find stream information"); - - if (Track < 0) - for(unsigned int i = 0; i < FormatContext->nb_streams; i++) - if(FormatContext->streams[i]->codec->codec_type == TrackType) { - Track = i; - break; - } - - if(Track < -1) - Env->ThrowError("FFmpegSource: No suitable track found"); - - if (Track >= (int)FormatContext->nb_streams) - Env->ThrowError("FFmpegSource: Invalid track number"); - - if (FormatContext->streams[Track]->codec->codec_type != TrackType) - Env->ThrowError("FFmpegSource: Selected track is not of the right type"); - - CodecContext = FormatContext->streams[Track]->codec; - - Codec = avcodec_find_decoder(CodecContext->codec_id); - if(Codec == NULL) - Env->ThrowError("FFmpegSource: Codec not found"); - - if(avcodec_open(CodecContext, Codec) < 0) - Env->ThrowError("FFmpegSource: Could not open codec"); - } - - ~FFAVFormatBase() { - avcodec_close(CodecContext); - av_close_input_file(FormatContext); - } -}; - -class FFAudioSource : public FFAudioBase, private FFAVFormatBase { -private: - int CurrentSample; - - int DecodeNextAudioBlock(uint8_t *Buf, int64_t *DTS, int64_t *Count, IScriptEnvironment* Env); -public: - FFAudioSource(const char *Source, int _Track, IScriptEnvironment* Env) : FFAVFormatBase(Source, _Track, CODEC_TYPE_AUDIO, Env) { - CurrentSample = 0; - - switch (CodecContext->sample_fmt) { - case SAMPLE_FMT_U8: VI.sample_type = SAMPLE_INT8; break; - case SAMPLE_FMT_S16: VI.sample_type = SAMPLE_INT16; break; - case SAMPLE_FMT_S24: VI.sample_type = SAMPLE_INT24; break; - case SAMPLE_FMT_S32: VI.sample_type = SAMPLE_INT32; break; - case SAMPLE_FMT_FLT: VI.sample_type = SAMPLE_FLOAT; break; - default: - Env->ThrowError("FFmpegSource: Unsupported/unknown sample format"); - } - - VI.nchannels = CodecContext->channels; - VI.audio_samples_per_second = CodecContext->sample_rate; - - if (CodecContext->frame_size == 0) - Env->ThrowError("Variable frame sizes currently unsupported"); - AVPacket Packet; - while (av_read_frame(FormatContext, &Packet) >= 0) { - if (Packet.stream_index == Track) { - SI.push_back(SampleInfo(VI.num_audio_samples, Packet.dts, Packet.pos, (Packet.flags & PKT_FLAG_KEY) != 0)); - VI.num_audio_samples += CodecContext->frame_size; - } - av_free_packet(&Packet); - } - - if (VI.num_audio_samples == 0) - Env->ThrowError("FFmpegSource: Audio track contains no samples"); - - av_seek_frame(FormatContext, Track, SI[0].DTS, AVSEEK_FLAG_BACKWARD); - avcodec_flush_buffers(CodecContext); - } - - void __stdcall GetAudio(void* Buf, __int64 Start, __int64 Count, IScriptEnvironment* Env); -}; - -int FFAudioSource::DecodeNextAudioBlock(uint8_t *Buf, int64_t *DTS, int64_t *Count, IScriptEnvironment* Env) { - AVPacket Packet; - int Ret = -1; - *Count = 0; - *DTS = -1; - - while (av_read_frame(FormatContext, &Packet) >= 0) { - if (Packet.stream_index == Track) { - if (*DTS < 0) - *DTS = Packet.dts; - - uint8_t *Data = (uint8_t *)Packet.data; - int Size = Packet.size; - - while (Size > 0) { - int TempOutputBufSize = AVCODEC_MAX_AUDIO_FRAME_SIZE; - Ret = avcodec_decode_audio2(CodecContext, (int16_t *)Buf, &TempOutputBufSize, Data, Size); - if (Ret < 0) { - av_free_packet(&Packet); - goto Done; - } - - Size -= Ret; - Data += Ret; - Buf += TempOutputBufSize; - *Count += VI.AudioSamplesFromBytes(TempOutputBufSize); - - if (Size <= 0) { - av_free_packet(&Packet); - goto Done; - } - } - } - - av_free_packet(&Packet); - } - -Done: - return Ret; -} - -void __stdcall FFAudioSource::GetAudio(void* Buf, __int64 Start, __int64 Count, IScriptEnvironment* Env) { - bool HasSeeked = false; - int ClosestKF = FindClosestAudioKeyFrame(Start); - - memset(Buf, 0, VI.BytesFromAudioSamples(Count)); - - if (Start < CurrentSample || SI[ClosestKF].SampleStart > CurrentSample) { - av_seek_frame(FormatContext, Track, SI[max(ClosestKF - 10, 0)].DTS, AVSEEK_FLAG_BACKWARD); - avcodec_flush_buffers(CodecContext); - HasSeeked = true; - } - - uint8_t *DstBuf = (uint8_t *)Buf; - int64_t RemainingSamples = Count; - int64_t DecodeCount; - - do { - int64_t StartTime; - uint8_t DecodingBuffer[AVCODEC_MAX_AUDIO_FRAME_SIZE]; - int64_t DecodeStart = CurrentSample; - int Ret = DecodeNextAudioBlock(DecodingBuffer, &StartTime, &DecodeCount, Env); - - if (HasSeeked) { - HasSeeked = false; - - if (StartTime < 0 || (CurrentSample = DecodeStart = SI[max(ClosestSampleFromDTS(StartTime) - 0, 0)].SampleStart) < 0) - Env->ThrowError("FFmpegSource: Sample accurate seeking is not possible in this file"); - } - - int OffsetBytes = VI.BytesFromAudioSamples(max(0, Start - DecodeStart)); - int CopyBytes = max(0, VI.BytesFromAudioSamples(min(RemainingSamples, DecodeCount - max(0, Start - DecodeStart)))); - - memcpy(DstBuf, DecodingBuffer + OffsetBytes, CopyBytes); - DstBuf += CopyBytes; - - CurrentSample += DecodeCount; - RemainingSamples -= VI.AudioSamplesFromBytes(CopyBytes); - - } while (RemainingSamples > 0 && DecodeCount > 0); -} - -class FFVideoSource : public FFVideoBase, private FFAVFormatBase { -private: - AVFrame *Frame; int CurrentFrame; int SeekMode; int DecodeNextFrame(AVFrame *Frame, int64_t *DTS); + + int GetTrackIndex(int Index, CodecType ATrackType, IScriptEnvironment *Env) { + if (Index == -1) + for (unsigned int i = 0; i < FormatContext->nb_streams; i++) + if (FormatContext->streams[i]->codec->codec_type == ATrackType) { + Index = i; + break; + } + + if (Index == -1) + Env->ThrowError("FFmpegSource: No %s track found", (ATrackType == CODEC_TYPE_VIDEO) ? "video" : "audio"); + if (Index <= -2) + return -2; + + if (Index >= (int)FormatContext->nb_streams) + Env->ThrowError("FFmpegSource: Invalid %s track number", (ATrackType == CODEC_TYPE_VIDEO) ? "video" : "audio"); + + if (FormatContext->streams[Index]->codec->codec_type != ATrackType) + Env->ThrowError("FFmpegSource: Selected track is not %s", (ATrackType == CODEC_TYPE_VIDEO) ? "video" : "audio"); + + return Index; + } public: - FFVideoSource(const char *Source, int _Track, int _SeekMode, FILE *Timecodes, bool Cache, FILE *CacheFile, IScriptEnvironment* Env) : FFAVFormatBase(Source, _Track, CODEC_TYPE_VIDEO, Env) { + FFmpegSource(const char *ASource, int AVideoTrack, int AAudioTrack, const char *ATimecodes, bool AVCache, const char *AVideoCache, const char *AAudioCache, const char *APPString, int AQuality, int ASeekMode, IScriptEnvironment* Env) { CurrentFrame = 0; - Frame = NULL; - SeekMode = _SeekMode; + SeekMode = ASeekMode; - VI.image_type = VideoInfo::IT_TFF; - VI.width = CodecContext->width; - VI.height = CodecContext->height; - VI.fps_denominator = FormatContext->streams[Track]->time_base.num; - VI.fps_numerator = FormatContext->streams[Track]->time_base.den; - VI.num_frames = (int)FormatContext->streams[Track]->duration; + AVCodecContext *AudioCodecContext = NULL; + AVCodec *AudioCodec; + AVCodec *VideoCodec; - // sanity check framerate - if (VI.fps_denominator > VI.fps_numerator || VI.fps_denominator <= 0 || VI.fps_numerator <= 0) { - VI.fps_denominator = 1; - VI.fps_numerator = 30; + FormatContext = NULL; + VideoCodecContext = NULL; + VideoCodec = NULL; + + if (av_open_input_file(&FormatContext, ASource, NULL, 0, NULL) != 0) + Env->ThrowError("FFmpegSource: Couldn't open '%s'", ASource); + + if (av_find_stream_info(FormatContext) < 0) + Env->ThrowError("FFmpegSource: Couldn't find stream information"); + + VideoTrack = GetTrackIndex(AVideoTrack, CODEC_TYPE_VIDEO, Env); + int AudioTrack = GetTrackIndex(AAudioTrack, CODEC_TYPE_AUDIO, Env); + + bool VCacheIsValid = true; + bool ACacheIsValid = true; + + if (VideoTrack >= 0) { + VCacheIsValid = LoadFrameInfoFromFile(AVideoCache, ASource, VideoTrack); + + VideoCodecContext = FormatContext->streams[VideoTrack]->codec; + + VideoCodec = avcodec_find_decoder(VideoCodecContext->codec_id); + if (VideoCodec == NULL) + Env->ThrowError("FFmpegSource: Video codec not found"); + + if (avcodec_open(VideoCodecContext, VideoCodec) < 0) + Env->ThrowError("FFmpegSource: Could not open video codec"); + + VI.image_type = VideoInfo::IT_TFF; + VI.width = VideoCodecContext->width; + VI.height = VideoCodecContext->height; + VI.fps_denominator = FormatContext->streams[VideoTrack]->time_base.num; + VI.fps_numerator = FormatContext->streams[VideoTrack]->time_base.den; + VI.num_frames = (int)FormatContext->streams[VideoTrack]->duration; + + // sanity check framerate + if (VI.fps_denominator > VI.fps_numerator || VI.fps_denominator <= 0 || VI.fps_numerator <= 0) { + VI.fps_denominator = 1; + VI.fps_numerator = 30; + } + + SetOutputFormat(VideoCodecContext->pix_fmt, Env); + InitPP(VI.width, VI.height, APPString, AQuality, VideoCodecContext->pix_fmt, Env); } - - SetOutputFormat(CodecContext->pix_fmt, Env); + + if (AudioTrack >= 0) { + AudioCodecContext = FormatContext->streams[AudioTrack]->codec; + + AudioCodec = avcodec_find_decoder(AudioCodecContext->codec_id); + if (AudioCodec == NULL) + Env->ThrowError("FFmpegSource: Audio codec not found"); + + if (avcodec_open(AudioCodecContext, AudioCodec) < 0) + Env->ThrowError("FFmpegSource: Could not open audio codec"); + + switch (AudioCodecContext->sample_fmt) { + case SAMPLE_FMT_U8: VI.sample_type = SAMPLE_INT8; break; + case SAMPLE_FMT_S16: VI.sample_type = SAMPLE_INT16; break; + case SAMPLE_FMT_S24: VI.sample_type = SAMPLE_INT24; break; + case SAMPLE_FMT_S32: VI.sample_type = SAMPLE_INT32; break; + case SAMPLE_FMT_FLT: VI.sample_type = SAMPLE_FLOAT; break; + default: + Env->ThrowError("FFmpegSource: Unsupported/unknown sample format"); + } + + VI.nchannels = AudioCodecContext->channels; + VI.audio_samples_per_second = AudioCodecContext->sample_rate; + + ACacheIsValid = PrepareAudioCache(AAudioCache, ASource, AudioTrack, Env); + } + + uint8_t DecodingBuffer[AVCODEC_MAX_AUDIO_FRAME_SIZE]; // Needs to be indexed? - if (!(Cache && LoadFrameInfoFromFile(CacheFile))) { + if (!ACacheIsValid || !VCacheIsValid) { AVPacket Packet; VI.num_frames = 0; while (av_read_frame(FormatContext, &Packet) >= 0) { - if (Packet.stream_index == Track) { + if (Packet.stream_index == VideoTrack && !VCacheIsValid) { FrameToDTS.push_back(FrameInfo(Packet.dts, (Packet.flags & PKT_FLAG_KEY) != 0)); VI.num_frames++; - } - av_free_packet(&Packet); - } - - if (VI.num_frames == 0) - Env->ThrowError("FFmpegSource: Video track contains no frames"); - - av_seek_frame(FormatContext, Track, FrameToDTS[0].DTS, AVSEEK_FLAG_BACKWARD); - - if (Cache) { - fprintf(CacheFile, "%d\n", VI.num_frames); - for (int i = 0; i < VI.num_frames; i++) - fprintf(CacheFile, "%lld %d\n", FrameToDTS[i].DTS, (int)(FrameToDTS[i].KeyFrame ? 1 : 0)); - } - } - - if (Timecodes) - for (int i = 0; i < VI.num_frames; i++) - fprintf(Timecodes, "%f\n", (FrameToDTS[i].DTS * FormatContext->streams[Track]->time_base.num * 1000) / (double)(FormatContext->streams[Track]->time_base.den)); - - Frame = avcodec_alloc_frame(); - } - - ~FFVideoSource() { - av_free(Frame); - } - - PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* Env); -}; - -int FFVideoSource::DecodeNextFrame(AVFrame *Frame, int64_t *DTS) { - AVPacket Packet; - int FrameFinished = 0; - int Ret = -1; - *DTS = -1; - - while (av_read_frame(FormatContext, &Packet) >= 0) { - if (Packet.stream_index == Track) { - Ret = avcodec_decode_video(CodecContext, Frame, &FrameFinished, Packet.data, Packet.size); - - if (*DTS < 0) - *DTS = Packet.dts; - } - - av_free_packet(&Packet); - - if (FrameFinished) - goto Done; - } - - // Flush the last frame - if (CurrentFrame == VI.num_frames - 1) - Ret = avcodec_decode_video(CodecContext, Frame, &FrameFinished, NULL, 0); - -Done: - return Ret; -} - -PVideoFrame __stdcall FFVideoSource::GetFrame(int n, IScriptEnvironment* Env) { - bool HasSeeked = false; - int ClosestKF = FindClosestKeyFrame(n); - - if (SeekMode == 0) { - if (n < CurrentFrame) { - av_seek_frame(FormatContext, Track, FrameToDTS[0].DTS, AVSEEK_FLAG_BACKWARD); - avcodec_flush_buffers(CodecContext); - CurrentFrame = 0; - } - } else { - // 10 frames is used as a margin to prevent excessive seeking since the predicted best keyframe isn't always selected by avformat - if (n < CurrentFrame || ClosestKF > CurrentFrame + 10 || (SeekMode == 3 && n > CurrentFrame + 10)) { - av_seek_frame(FormatContext, Track, (SeekMode == 3) ? FrameToDTS[n].DTS : FrameToDTS[ClosestKF].DTS, AVSEEK_FLAG_BACKWARD); - avcodec_flush_buffers(CodecContext); - HasSeeked = true; - } - } - - do { - int64_t DTS; - int Ret = DecodeNextFrame(Frame, &DTS); - - if (SeekMode == 0 && Frame->key_frame) - FrameToDTS[CurrentFrame].KeyFrame = true; - - if (HasSeeked) { - HasSeeked = false; - - // Is the seek destination time known? Does it belong to a frame? - if (DTS < 0 || (CurrentFrame = FrameFromDTS(DTS)) < 0) { - switch (SeekMode) { - case 1: - Env->ThrowError("FFmpegSource: Frame accurate seeking is not possible in this file"); - case 2: - case 3: - CurrentFrame = ClosestFrameFromDTS(DTS); - break; - default: - Env->ThrowError("FFmpegSource: Failed assertion"); - } - } - } - - CurrentFrame++; - } while (CurrentFrame <= n); - - return OutputFrame(Frame, Env); -} - -class FFAudioRefSource : public FFBase, private FFAVFormatBase { -private: - FILE *AudioCache; -public: - FFAudioRefSource(const char *Source, int _Track, const char *CacheFile, IScriptEnvironment* Env) : FFAVFormatBase(Source, _Track, CODEC_TYPE_AUDIO, Env) { - switch (CodecContext->sample_fmt) { - case SAMPLE_FMT_U8: VI.sample_type = SAMPLE_INT8; break; - case SAMPLE_FMT_S16: VI.sample_type = SAMPLE_INT16; break; - case SAMPLE_FMT_S24: VI.sample_type = SAMPLE_INT24; break; - case SAMPLE_FMT_S32: VI.sample_type = SAMPLE_INT32; break; - case SAMPLE_FMT_FLT: VI.sample_type = SAMPLE_FLOAT; break; - default: - Env->ThrowError("FFmpegSource: Unsupported/unknown sample format"); - } - - VI.nchannels = CodecContext->channels; - VI.audio_samples_per_second = CodecContext->sample_rate; - - AudioCache = fopen(CacheFile, "ab+"); - if (AudioCache == NULL) - Env->ThrowError("FFmpegSource: Failed to open the cache file"); - _fseeki64(AudioCache, 0, SEEK_END); - int64_t CacheSize = _ftelli64(AudioCache); - if (CacheSize > 0) { - VI.num_audio_samples = VI.AudioSamplesFromBytes(CacheSize); - } else { - AVPacket Packet; - uint8_t DecodingBuffer[AVCODEC_MAX_AUDIO_FRAME_SIZE]; - - while (av_read_frame(FormatContext, &Packet) >= 0) { - if (Packet.stream_index == Track) { - uint8_t *Data = (uint8_t *)Packet.data; + } else if (Packet.stream_index == AudioTrack && !ACacheIsValid) { int Size = Packet.size; + uint8_t *Data = Packet.data; while (Size > 0) { int TempOutputBufSize = AVCODEC_MAX_AUDIO_FRAME_SIZE; - int Ret = avcodec_decode_audio2(CodecContext, (int16_t *)DecodingBuffer, &TempOutputBufSize, Data, Size); - if (Ret < 0) { - av_free_packet(&Packet); + int Ret = avcodec_decode_audio2(AudioCodecContext, (int16_t *)DecodingBuffer, &TempOutputBufSize, Data, Size); + if (Ret < 0) Env->ThrowError("FFmpegSource: Audio decoding error"); - } Size -= Ret; Data += Ret; @@ -945,121 +482,150 @@ public: av_free_packet(&Packet); } + + if (AVCache) + if (!SaveFrameInfoToFile(AVideoCache, ASource, VideoTrack)) + Env->ThrowError("FFmpegSource: Failed to write video cache info"); + } + + if (AudioTrack >= 0) + avcodec_close(AudioCodecContext); + + if (VideoTrack >= 0) { + av_seek_frame(FormatContext, VideoTrack, FrameToDTS[0].DTS, AVSEEK_FLAG_BACKWARD); + + if (!SaveTimecodesToFile(ATimecodes, FormatContext->streams[VideoTrack]->time_base.num * 1000, FormatContext->streams[VideoTrack]->time_base.den)) + Env->ThrowError("FFmpegSource: Failed to write timecodes"); } } - ~FFAudioRefSource() { - fclose(AudioCache); + ~FFmpegSource() { + if (VideoTrack >= 0) + avcodec_close(VideoCodecContext); + av_close_input_file(FormatContext); } - void __stdcall GetAudio(void* Buf, __int64 Start, __int64 Count, IScriptEnvironment* Env) { - _fseeki64(AudioCache, VI.BytesFromAudioSamples(Start), SEEK_SET); - fread(Buf, 1, VI.BytesFromAudioSamples(Count), AudioCache); - } + PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* Env); }; -AVSValue __cdecl CreateFFVideoSource(AVSValue Args, void* UserData, IScriptEnvironment* Env) { - if (!Args[0].Defined()) - Env->ThrowError("FFmpegSource: No source specified"); +int FFmpegSource::DecodeNextFrame(AVFrame *AFrame, int64_t *AStartTime) { + AVPacket Packet; + int FrameFinished = 0; + int Ret = -1; + *AStartTime = -1; - const char *Source = Args[0].AsString(); - int Track = Args[1].AsInt(-1); - int SeekMode = Args[2].AsInt(1); - const char *Timecodes = Args[3].AsString(""); - bool Cache = Args[4].AsBool(true); + while (av_read_frame(FormatContext, &Packet) >= 0) { + if (Packet.stream_index == VideoTrack) { + Ret = avcodec_decode_video(VideoCodecContext, AFrame, &FrameFinished, Packet.data, Packet.size); - // Generate default cache filename - char DefaultCacheFilename[1024]; - strcpy(DefaultCacheFilename, Source); - strcat(DefaultCacheFilename, ".ffcache"); - const char *CacheFilename = Args[5].AsString(DefaultCacheFilename); + if (*AStartTime < 0) + *AStartTime = Packet.dts; + } - if (SeekMode < 0 || SeekMode > 3) - Env->ThrowError("FFmpegSource: Invalid seek mode selected"); + av_free_packet(&Packet); - FILE *TCFile = NULL; - if (strcmp(Timecodes, "")) { - TCFile = fopen(Timecodes, "w"); - if (!TCFile) - Env->ThrowError("FFmpegSource: Failed to open timecode output file for writing"); - fprintf(TCFile, "# timecode format v2\n"); + if (FrameFinished) + goto Done; } - FILE *CacheFile = NULL; - if (Cache) { - CacheFile = fopen(CacheFilename, "a+"); - if (!CacheFile) - Env->ThrowError("FFmpegSource: Failed to open cache file"); - fseek(CacheFile, 0, SEEK_END); - } - - av_register_all(); - AVFormatContext *FormatContext; - - if (av_open_input_file(&FormatContext, Source, NULL, 0, NULL) != 0) - Env->ThrowError("FFmpegSource: Couldn't open \"%s\"", Args[0].AsString()); - bool IsMatroska = !strcmp(FormatContext->iformat->name, "matroska"); - av_close_input_file(FormatContext); - - FFBase *Ret; - - if (IsMatroska) - Ret = new FFMKVSource(Source, Track, TCFile, Cache, CacheFile, Env); - else - Ret = new FFVideoSource(Source, Track, SeekMode, TCFile, Cache, CacheFile, Env); - - if (TCFile) - fclose(TCFile); - - if (CacheFile) - fclose(CacheFile); + // Flush the last frame + if (CurrentFrame == VI.num_frames - 1 && VideoCodecContext->has_b_frames) + Ret = avcodec_decode_video(VideoCodecContext, AFrame, &FrameFinished, NULL, 0); +Done: return Ret; } -AVSValue __cdecl CreateFFAudioSource(AVSValue Args, void* UserData, IScriptEnvironment* Env) { +PVideoFrame __stdcall FFmpegSource::GetFrame(int n, IScriptEnvironment* Env) { + bool HasSeeked = false; + int ClosestKF = FindClosestKeyFrame(n); + + if (SeekMode == 0) { + if (n < CurrentFrame) { + av_seek_frame(FormatContext, VideoTrack, FrameToDTS[0].DTS, AVSEEK_FLAG_BACKWARD); + avcodec_flush_buffers(VideoCodecContext); + CurrentFrame = 0; + } + } else { + // 10 frames is used as a margin to prevent excessive seeking since the predicted best keyframe isn't always selected by avformat + if (n < CurrentFrame || ClosestKF > CurrentFrame + 10 || (SeekMode == 3 && n > CurrentFrame + 10)) { + av_seek_frame(FormatContext, VideoTrack, (SeekMode == 3) ? FrameToDTS[n].DTS : FrameToDTS[ClosestKF].DTS, AVSEEK_FLAG_BACKWARD); + avcodec_flush_buffers(VideoCodecContext); + HasSeeked = true; + } + } + + do { + int64_t StartTime; + DecodeNextFrame(DecodeFrame, &StartTime); + + if (HasSeeked) { + HasSeeked = false; + + // Is the seek destination time known? Does it belong to a frame? + if (StartTime < 0 || (CurrentFrame = FrameFromDTS(StartTime)) < 0) { + switch (SeekMode) { + case 1: + Env->ThrowError("FFmpegSource: Frame accurate seeking is not possible in this file"); + case 2: + case 3: + CurrentFrame = ClosestFrameFromDTS(StartTime); + break; + default: + Env->ThrowError("FFmpegSource: Failed assertion"); + } + } + } + + CurrentFrame++; + } while (CurrentFrame <= n); + + return OutputFrame(DecodeFrame, Env); +} + +AVSValue __cdecl CreateFFmpegSource(AVSValue Args, void* UserData, IScriptEnvironment* Env) { + if (!UserData) { + av_register_all(); + UserData = (void *)-1; + } + if (!Args[0].Defined()) Env->ThrowError("FFmpegSource: No source specified"); const char *Source = Args[0].AsString(); - int Track = Args[1].AsInt(-1); + int VTrack = Args[1].AsInt(-1); + int ATrack = Args[2].AsInt(-2); + const char *Timecodes = Args[3].AsString(""); + bool VCache = Args[4].AsBool(true); + const char *VCacheFile = Args[5].AsString(""); + const char *ACacheFile = Args[6].AsString(""); + const char *PPString = Args[7].AsString(""); + int PPQuality = Args[8].AsInt(PP_QUALITY_MAX); + int SeekMode = Args[9].AsInt(1); + + if (VTrack <= -2 && ATrack <= -2) + Env->ThrowError("FFmpegSource: No tracks selected"); - av_register_all(); AVFormatContext *FormatContext; if (av_open_input_file(&FormatContext, Source, NULL, 0, NULL) != 0) - Env->ThrowError("FFmpegSource: Couldn't open \"%s\"", Source); + Env->ThrowError("FFmpegSource: Couldn't open %s", Args[0].AsString()); bool IsMatroska = !strcmp(FormatContext->iformat->name, "matroska"); av_close_input_file(FormatContext); if (IsMatroska) - return new FFMKASource(Source, Track, Env); + return new FFMatroskaSource(Source, VTrack, ATrack, Timecodes, VCache, VCacheFile, ACacheFile, PPString, PPQuality, Env); else - return new FFAudioSource(Source, Track, Env); + return new FFmpegSource(Source, VTrack, ATrack, Timecodes, VCache, VCacheFile, ACacheFile, PPString, PPQuality, SeekMode, Env); } -AVSValue __cdecl CreateFFAudioRefSource(AVSValue Args, void* UserData, IScriptEnvironment* Env) { - if (!Args[0].Defined()) - Env->ThrowError("FFmpegSource: No source specified"); - - const char *Source = Args[0].AsString(); - int Track = Args[1].AsInt(-1); - - // Generate default cache filename - char DefaultCacheFilename[1024]; - strcpy(DefaultCacheFilename, Source); - strcat(DefaultCacheFilename, ".ffracache"); - const char *CacheFilename = Args[2].AsString(DefaultCacheFilename); - - av_register_all(); - - return new FFAudioRefSource(Source, Track, CacheFilename, Env); +AVSValue __cdecl CreateFFPP(AVSValue Args, void* UserData, IScriptEnvironment* Env) { + return new FFPP(Args[0].AsClip(), Args[1].AsString(""), Args[2].AsInt(PP_QUALITY_MAX), Env); } extern "C" __declspec(dllexport) const char* __stdcall AvisynthPluginInit2(IScriptEnvironment* Env) { - Env->AddFunction("FFVideoSource", "[source]s[track]i[seekmode]i[timecodes]s[cache]b[cachefile]s", CreateFFVideoSource, 0); - Env->AddFunction("FFAudioSource", "[source]s[track]i", CreateFFAudioSource, 0); - Env->AddFunction("FFAudioRefSource", "[source]s[track]i[cachefile]s", CreateFFAudioRefSource, 0); + Env->AddFunction("FFmpegSource", "[source]s[vtrack]i[atrack]i[timecodes]s[vcache]b[vcachefile]s[acachefile]s[pp]s[ppquality]i[seekmode]i", CreateFFmpegSource, 0); + Env->AddFunction("FFPP", "c[pp]s[ppquality]i", CreateFFPP, 0); return "FFmpegSource"; }; diff --git a/FFmpegSource/ffmpegsource.h b/FFmpegSource/ffmpegsource.h new file mode 100644 index 000000000..b16a07d3b --- /dev/null +++ b/FFmpegSource/ffmpegsource.h @@ -0,0 +1,77 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "MatroskaParser.h" +#include "avisynth.h" + +extern "C" { +#include +#include +#include +#include +} + +int GetPPCPUFlags(IScriptEnvironment *Env); +int GetSWSCPUFlags(IScriptEnvironment *Env); +CodecID MatroskaToFFCodecID(TrackInfo *TI); + +class FFPP : public GenericVideoFilter { +private: + pp_context_t *PPContext; + pp_mode_t *PPMode; + SwsContext *SWSTo422P; + SwsContext *SWSFrom422P; + AVPicture InputPicture; + AVPicture OutputPicture; +public: + FFPP(PClip AChild, const char *APPString, int AQuality, IScriptEnvironment *Env); + ~FFPP(); + PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* Env); +}; + +class FFBase : public IClip{ +private: + pp_context_t *PPContext; + pp_mode_t *PPMode; + SwsContext *SWS; + int ConvertToFormat; + AVPicture PPPicture; +protected: + VideoInfo VI; + AVFrame *DecodeFrame; + FILE *AudioCache; + + struct FrameInfo { + int64_t DTS; + bool KeyFrame; + FrameInfo(int64_t ADTS, bool AKeyFrame) : DTS(ADTS), KeyFrame(AKeyFrame) {}; + }; + + std::vector FrameToDTS; + + int FindClosestKeyFrame(int AFrame); + int FrameFromDTS(int64_t ADTS); + int ClosestFrameFromDTS(int64_t ADTS); + bool LoadFrameInfoFromFile(const char *AVideoCacheFile, const char *ASource, int AVideoTrack); + bool SaveFrameInfoToFile(const char *AVideoCacheFile, const char *ASource, int AVideoTrack); + bool SaveTimecodesToFile(const char *ATimecodeFile, int64_t ScaleD, int64_t ScaleN); + bool PrepareAudioCache(const char *AAudioCacheFile, const char *ASource, int AAudioTrack, IScriptEnvironment *Env); + void InitPP(int AWidth, int AHeight, const char *APPString, int AQuality, int APixelFormat, IScriptEnvironment *Env); + void SetOutputFormat(int ACurrentFormat, IScriptEnvironment *Env); + PVideoFrame OutputFrame(AVFrame *AFrame, IScriptEnvironment *Env); + +public: + FFBase(); + ~FFBase(); + + bool __stdcall GetParity(int n) { return false; } + void __stdcall SetCacheHints(int cachehints, int frame_range) { } + const VideoInfo& __stdcall GetVideoInfo() { return VI; } + void __stdcall GetAudio(void* Buf, __int64 Start, __int64 Count, IScriptEnvironment* Env); +}; \ No newline at end of file diff --git a/FFmpegSource/ffmpegsource.html b/FFmpegSource/ffmpegsource.html index e1d151bd8..15fb2c6fa 100644 --- a/FFmpegSource/ffmpegsource.html +++ b/FFmpegSource/ffmpegsource.html @@ -10,6 +10,15 @@ FFmpegSource Documentation

Changes

    +
  • 1.4
      +
    • Naming scheme of cache files changed to prevent confusion with the default names in files with multiple tracks of the same type
    • +
    • Use mmx optimizations in swscaler when possible
    • +
    • Now uses normal windows linebreaks in all files
    • +
    • Removed FFAudioSource
    • +
    • Merged FFVideoSource and FFAudioRefSource into FFmpegSource
    • +
    • Added postprocessing with libpostproc in FFmpegSource and separately in FFPP
    • +
  • +
  • 1.3
    • Compiled against ffmpeg rev9620
    • Added FFAudioRefSource
    • @@ -36,55 +45,6 @@ FFmpegSource Documentation
    -

    Usage

    -

    -FFVideoSource(string source, int track = -1, int seekmode = 1, string timecodes, bool cache = true, string cachefile)
    -A video source that is frame accurate for most popular containers -

    - -

    -FFAudioSource(string source, int track = -1)
    -Very unreliable attempt at sample accurate seeking in audio files -

    - -

    -FFAudioRefSource(string source, int track = -1, string cachefile)
    -Decodes all audio to a temporary file which ensures that it will always be sample accurate -

    - -

    -source: - source file -

    - -

    -track: - track number as seen by the relevant demuxer, starts from 0, -1 means it will pick the first suitable track -

    - -

    -seekmode: - force how seeking is handled, has no effect on matroska files which always use the equivalent of seekmode=1
    - 0: linear access, the definition of slow but should make some formats "usable"
    - 1: safe normal, bases seeking decisions on the reported keyframe positions
    - 2: unsafe normal, same as 1 but no error will be thrown if the exact destination has to be guessed
    - 3: aggressive, seek in the forward direction even if no closer keyframe is known to exist, only useful for containers where avformat doesn't report keyframes properly and testing -

    - -

    -timecodes: - file to output timecodes to, if the file exists it will always be overwritten -

    - -

    -cache: - write indexing information to a file for later use -

    - -

    -cachefile: - if specified the file to store the index information or raw audio in, if nothing is specified (source).ffcache is used for video and (source).ffracache for audio -

    Compatibility - Video

      @@ -98,9 +58,108 @@ Decodes all audio to a temporary file which ensures that it will always be sampl

      Compatibility - Audio

        -
      • Not sample accurate ever?
      • +
      • Sample accurate in all containers
      +

      Usage

      +

      +FFmpegSource(string source, int vtrack = -1, int atrack = -2, string timecodes, bool vcache = true, string vcachefile, string acachefile, string pp, int ppquality = 6, int seekmode = 1)
      +

      + +

      +FFPP(clip, string pp, int ppquality = 6)
      +Separate postprocessing which also seems to include a few simple deinterlacers +

      + + +

      +source: + Source file. +

      + +

      +atrack & vtrack: + Track number as seen by the relevant demuxer, starts from 0, -1 means it will pick the first suitable track and -2 means it's disabled. +

      + + +

      +timecodes: + File to output timecodes to, if the file exists it will be overwritten. +

      + +

      +vcache: + Write video indexing information to a file for later use. +

      + +

      +vcachefile & acachefile: + Specifies the file to store the index information or raw audio in, if nothing is specified (source).ffv(tracknumber)cache is used for video and (source).ffa(tracknumber)cache for audio. +

      + +

      +pp: + See the table below for a full description, an empty string means no processing. It is recommended to avoid the autoq option since it's currently unknown what effect it will have on the processing. +

      + +

      +ppquality: + The quality to use for the specified postprocessing. Valid values are 0-6 where 0 usually means that no actual processing is done. +

      + +

      +seekmode: + Force how seeking is handled, has no effect on matroska files which always use the equivalent of seekmode=1
      + 0: linear access, the definition of slow but should make some formats "usable"
      + 1: safe normal, bases seeking decisions on the reported keyframe positions
      + 2: unsafe normal, same as 1 but no error will be thrown if the exact destination has to be guessed
      + 3: aggressive, seek in the forward direction even if no closer keyframe is known to exist, only useful for testing and containers where avformat doesn't report keyframes properly +

      + +

      PP string format

      +
      +Available postprocessing filters:
      +Filters                        Options
      +short  long name       short   long option     Description
      +*      *               a       autoq           CPU power dependent enabler
      +                       c       chrom           chrominance filtering enabled
      +                       y       nochrom         chrominance filtering disabled
      +                       n       noluma          luma filtering disabled
      +hb     hdeblock        (2 threshold)           horizontal deblocking filter
      +       1. difference factor: default=32, higher -> more deblocking
      +       2. flatness threshold: default=39, lower -> more deblocking
      +                       the h & v deblocking filters share these
      +                       so you can't set different thresholds for h / v
      +vb     vdeblock        (2 threshold)           vertical deblocking filter
      +ha     hadeblock       (2 threshold)           horizontal deblocking filter
      +va     vadeblock       (2 threshold)           vertical deblocking filter
      +h1     x1hdeblock                              experimental h deblock filter 1
      +v1     x1vdeblock                              experimental v deblock filter 1
      +dr     dering                                  deringing filter
      +al     autolevels                              automatic brightness / contrast
      +                       f        fullyrange     stretch luminance to (0..255)
      +lb     linblenddeint                           linear blend deinterlacer
      +li     linipoldeint                            linear interpolating deinterlace
      +ci     cubicipoldeint                          cubic interpolating deinterlacer
      +md     mediandeint                             median deinterlacer
      +fd     ffmpegdeint                             ffmpeg deinterlacer
      +l5     lowpass5                                FIR lowpass deinterlacer
      +de     default                                 hb:a,vb:a,dr:a
      +fa     fast                                    h1:a,v1:a,dr:a
      +ac                                             ha:a:128:7,va:a,dr:a
      +tn     tmpnoise        (3 threshold)           temporal noise reducer
      +                     1. <= 2. <= 3.            larger -> stronger filtering
      +fq     forceQuant                   force quantizer
      +Usage:
      +[:

      Compiling

      diff --git a/FFmpegSource/ffpp.cpp b/FFmpegSource/ffpp.cpp new file mode 100644 index 000000000..813f7969d --- /dev/null +++ b/FFmpegSource/ffpp.cpp @@ -0,0 +1,91 @@ +#include "ffmpegsource.h" + +int GetPPCPUFlags(IScriptEnvironment *Env) { + int Flags = 0; + long CPUFlags = Env->GetCPUFlags(); + + if (CPUFlags & CPUF_MMX) + CPUFlags |= PP_CPU_CAPS_MMX; + if (CPUFlags & CPUF_INTEGER_SSE) + CPUFlags |= PP_CPU_CAPS_MMX2; + if (CPUFlags & CPUF_3DNOW) + CPUFlags |= PP_CPU_CAPS_3DNOW; + + return Flags; +} + +FFPP::FFPP(PClip AChild, const char *APPString, int AQuality, IScriptEnvironment *Env) : GenericVideoFilter(AChild) { + if (!strcmp(APPString, "")) + Env->ThrowError("FFPP: PP argument is empty"); + if (AQuality < 0 || AQuality > PP_QUALITY_MAX) + Env->ThrowError("FFPP: Quality is out of range"); + + PPContext = NULL; + PPMode = NULL; + SWSTo422P = NULL; + SWSFrom422P = NULL; + + memset(&InputPicture, 0, sizeof(InputPicture)); + memset(&OutputPicture, 0, sizeof(OutputPicture)); + + PPMode = pp_get_mode_by_name_and_quality((char *)APPString, AQuality); + if (!PPMode) + Env->ThrowError("FFPP: Invalid postprocesing settings"); + + int Flags = GetPPCPUFlags(Env); + + if (vi.IsYV12()) { + Flags |= PP_FORMAT_420; + } else if (vi.IsYUY2()) { + Flags |= PP_FORMAT_422; + SWSTo422P = sws_getContext(vi.width, vi.height, PIX_FMT_YUV422, vi.width, vi.height, PIX_FMT_YUV422P, GetSWSCPUFlags(Env) | SWS_BICUBIC, NULL, NULL, NULL); + SWSFrom422P = sws_getContext(vi.width, vi.height, PIX_FMT_YUV422P, vi.width, vi.height, PIX_FMT_YUV422, GetSWSCPUFlags(Env) | SWS_BICUBIC, NULL, NULL, NULL); + avpicture_alloc(&InputPicture, PIX_FMT_YUV422P, vi.width, vi.height); + avpicture_alloc(&OutputPicture, PIX_FMT_YUV422P, vi.width, vi.height); + } else { + Env->ThrowError("FFPP: Only YV12 and YUY2 video supported"); + } + + PPContext = pp_get_context(vi.width, vi.height, Flags); + if (!PPContext) + Env->ThrowError("FFPP: Failed to create context"); +} + +FFPP::~FFPP() { + if (PPMode) + pp_free_mode(PPMode); + if (PPContext) + pp_free_context(PPContext); + if (SWSTo422P) + sws_freeContext(SWSTo422P); + if (SWSFrom422P) + sws_freeContext(SWSFrom422P); + avpicture_free(&InputPicture); + avpicture_free(&OutputPicture); +} + +PVideoFrame __stdcall FFPP::GetFrame(int n, IScriptEnvironment* Env) { + PVideoFrame Src = child->GetFrame(n, Env); + PVideoFrame Dst = Env->NewVideoFrame(vi); + + if (vi.IsYV12()) { + uint8_t *SrcData[3] = {(uint8_t *)Src->GetReadPtr(PLANAR_Y), (uint8_t *)Src->GetReadPtr(PLANAR_U), (uint8_t *)Src->GetReadPtr(PLANAR_V)}; + int SrcStride[3] = {Src->GetPitch(PLANAR_Y), Src->GetPitch(PLANAR_U), Src->GetPitch(PLANAR_V)}; + uint8_t *DstData[3] = {Dst->GetWritePtr(PLANAR_Y), Dst->GetWritePtr(PLANAR_U), Dst->GetWritePtr(PLANAR_V)}; + int DstStride[3] = {Dst->GetPitch(PLANAR_Y), Dst->GetPitch(PLANAR_U), Dst->GetPitch(PLANAR_V)}; + + pp_postprocess(SrcData, SrcStride, DstData, DstStride, vi.width, vi.height, NULL, 0, PPMode, PPContext, 0); + } else if (vi.IsYUY2()) { + uint8_t *SrcData[1] = {(uint8_t *)Src->GetReadPtr()}; + int SrcStride[1] = {Src->GetPitch()}; + sws_scale(SWSTo422P, SrcData, SrcStride, 0, vi.height, InputPicture.data, InputPicture.linesize); + + pp_postprocess(InputPicture.data, InputPicture.linesize, OutputPicture.data, OutputPicture.linesize, vi.width, vi.height, NULL, 0, PPMode, PPContext, 0); + + uint8_t *DstData[1] = {Dst->GetWritePtr()}; + int DstStride[1] = {Dst->GetPitch()}; + sws_scale(SWSFrom422P, OutputPicture.data, OutputPicture.linesize, 0, vi.height, DstData, DstStride); + } + + return Dst; +} \ No newline at end of file