#include #include #include #include #include #include #include #include #include #include #include extern "C" { #include #include #include } #include "MatroskaParser.h" #include "avisynth.h" #include "stdiostream.c" class FFBase : public IClip { private: SwsContext *SWS; int ConvertToFormat; int ConvertFromFormat; protected: VideoInfo VI; 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, &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_LANCZOS, 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: virtual bool __stdcall GetParity(int n) { return 0; } virtual void __stdcall GetAudio(void* buf, __int64 start, __int64 count, IScriptEnvironment* env) { } virtual void __stdcall SetCacheHints(int cachehints, int frame_range) { } virtual const VideoInfo& __stdcall GetVideoInfo() { return VI; } FFBase() { SWS = NULL; ConvertToFormat = PIX_FMT_NONE; ConvertFromFormat = PIX_FMT_NONE; memset(&VI, 0, sizeof(VI)); } }; class FFMKVSource : public FFBase { private: AVCodecContext CodecContext; AVCodec *Codec; AVFrame *Frame; int CurrentFrame; struct FFMKVFrameInfo { ulonglong DTS; bool KeyFrame; }; std::vector FrameToDTS; std::map DTSToFrame; StdIoStream ST; MatroskaFile *MF; char ErrorMessage[256]; unsigned int BufferSize; void *Buffer; CompressedStream *CS; int ReadNextFrame(AVFrame *Frame, ulonglong *StartTime, IScriptEnvironment* Env); CodecID MatroskaToFFCodecID(TrackInfo *TI) { char *Codec = TI->CodecID; // fourcc list from ffdshow if (!strcmp(Codec, "V_MS/VFW/FOURCC")) { switch (((BITMAPINFOHEADER *)TI->CodecPrivate)->biCompression) { case MAKEFOURCC('F', 'F', 'D', 'S'): case MAKEFOURCC('F', 'V', 'F', 'W'): case MAKEFOURCC('X', 'V', 'I', 'D'): case MAKEFOURCC('D', 'I', 'V', 'X'): case MAKEFOURCC('D', 'X', '5', '0'): case MAKEFOURCC('M', 'P', '4', 'V'): case MAKEFOURCC('3', 'I', 'V', 'X'): case MAKEFOURCC('W', 'V', '1', 'F'): case MAKEFOURCC('F', 'M', 'P', '4'): case MAKEFOURCC('S', 'M', 'P', '4'): return CODEC_ID_MPEG4; case MAKEFOURCC('D', 'I', 'V', '3'): case MAKEFOURCC('D', 'V', 'X', '3'): case MAKEFOURCC('M', 'P', '4', '3'): return CODEC_ID_MSMPEG4V3; case MAKEFOURCC('M', 'P', '4', '2'): return CODEC_ID_MSMPEG4V2; case MAKEFOURCC('M', 'P', '4', '1'): return CODEC_ID_MSMPEG4V1; case MAKEFOURCC('W', 'M', 'V', '1'): return CODEC_ID_WMV1; case MAKEFOURCC('W', 'M', 'V', '2'): return CODEC_ID_WMV2; case MAKEFOURCC('W', 'M', 'V', '3'): return CODEC_ID_WMV3; /* case MAKEFOURCC('M', 'S', 'S', '1'): case MAKEFOURCC('M', 'S', 'S', '2'): case MAKEFOURCC('W', 'V', 'P', '2'): case MAKEFOURCC('W', 'M', 'V', 'P'): return CODEC_ID_WMV9_LIB; */ case MAKEFOURCC('W', 'V', 'C', '1'): return CODEC_ID_VC1; case MAKEFOURCC('V', 'P', '5', '0'): return CODEC_ID_VP5; case MAKEFOURCC('V', 'P', '6', '0'): case MAKEFOURCC('V', 'P', '6', '1'): case MAKEFOURCC('V', 'P', '6', '2'): return CODEC_ID_VP6; case MAKEFOURCC('V', 'P', '6', 'F'): case MAKEFOURCC('F', 'L', 'V', '4'): return CODEC_ID_VP6F; case MAKEFOURCC('C', 'A', 'V', 'S'): return CODEC_ID_CAVS; case MAKEFOURCC('M', 'P', 'G', '1'): case MAKEFOURCC('M', 'P', 'E', 'G'): return CODEC_ID_MPEG2VIDEO; // not a typo case MAKEFOURCC('M', 'P', 'G', '2'): case MAKEFOURCC('E', 'M', '2', 'V'): case MAKEFOURCC('M', 'M', 'E', 'S'): return CODEC_ID_MPEG2VIDEO; case MAKEFOURCC('H', '2', '6', '3'): case MAKEFOURCC('S', '2', '6', '3'): case MAKEFOURCC('L', '2', '6', '3'): case MAKEFOURCC('M', '2', '6', '3'): case MAKEFOURCC('U', '2', '6', '3'): case MAKEFOURCC('X', '2', '6', '3'): return CODEC_ID_H263; case MAKEFOURCC('H', '2', '6', '4'): case MAKEFOURCC('X', '2', '6', '4'): case MAKEFOURCC('V', 'S', 'S', 'H'): case MAKEFOURCC('D', 'A', 'V', 'C'): case MAKEFOURCC('P', 'A', 'V', 'C'): case MAKEFOURCC('A', 'V', 'C', '1'): return CODEC_ID_H264; case MAKEFOURCC('M', 'J', 'P', 'G'): case MAKEFOURCC('L', 'J', 'P', 'G'): case MAKEFOURCC('M', 'J', 'L', 'S'): case MAKEFOURCC('J', 'P', 'E', 'G'): // questionable fourcc? case MAKEFOURCC('A', 'V', 'R', 'N'): case MAKEFOURCC('M', 'J', 'P', 'A'): return CODEC_ID_MJPEG; case MAKEFOURCC('D', 'V', 'S', 'D'): case MAKEFOURCC('D', 'V', '2', '5'): case MAKEFOURCC('D', 'V', '5', '0'): case MAKEFOURCC('C', 'D', 'V', 'C'): case MAKEFOURCC('C', 'D', 'V', '5'): case MAKEFOURCC('D', 'V', 'I', 'S'): case MAKEFOURCC('P', 'D', 'V', 'C'): return CODEC_ID_DVVIDEO; case MAKEFOURCC('H', 'F', 'Y', 'U'): case MAKEFOURCC('F', 'F', 'V', 'H'): return CODEC_ID_HUFFYUV; case MAKEFOURCC('C', 'Y', 'U', 'V'): return CODEC_ID_CYUV; case MAKEFOURCC('A', 'S', 'V', '1'): return CODEC_ID_ASV1; case MAKEFOURCC('A', 'S', 'V', '2'): return CODEC_ID_ASV2; case MAKEFOURCC('V', 'C', 'R', '1'): return CODEC_ID_VCR1; case MAKEFOURCC('T', 'H', 'E', 'O'): return CODEC_ID_THEORA; case MAKEFOURCC('S', 'V', 'Q', '1'): return CODEC_ID_SVQ1; case MAKEFOURCC('S', 'V', 'Q', '3'): return CODEC_ID_SVQ3; case MAKEFOURCC('R', 'P', 'Z', 'A'): return CODEC_ID_RPZA; case MAKEFOURCC('F', 'F', 'V', '1'): return CODEC_ID_FFV1; case MAKEFOURCC('V', 'P', '3', '1'): return CODEC_ID_VP3; case MAKEFOURCC('R', 'L', 'E', '8'): return CODEC_ID_MSRLE; case MAKEFOURCC('M', 'S', 'Z', 'H'): return CODEC_ID_MSZH; case MAKEFOURCC('Z', 'L', 'I', 'B'): return CODEC_ID_FLV1; case MAKEFOURCC('F', 'L', 'V', '1'): return CODEC_ID_ZLIB; /* case MAKEFOURCC('P', 'N', 'G', '1'): return CODEC_ID_COREPNG; */ case MAKEFOURCC('M', 'P', 'N', 'G'): return CODEC_ID_PNG; /* case MAKEFOURCC('A', 'V', 'I', 'S'): return CODEC_ID_AVISYNTH; */ case MAKEFOURCC('C', 'R', 'A', 'M'): return CODEC_ID_MSVIDEO1; case MAKEFOURCC('R', 'T', '2', '1'): return CODEC_ID_INDEO2; case MAKEFOURCC('I', 'V', '3', '2'): case MAKEFOURCC('I', 'V', '3', '1'): return CODEC_ID_INDEO3; case MAKEFOURCC('C', 'V', 'I', 'D'): return CODEC_ID_CINEPAK; case MAKEFOURCC('R', 'V', '1', '0'): return CODEC_ID_RV10; case MAKEFOURCC('R', 'V', '2', '0'): return CODEC_ID_RV20; case MAKEFOURCC('8', 'B', 'P', 'S'): return CODEC_ID_8BPS; case MAKEFOURCC('Q', 'R', 'L', 'E'): return CODEC_ID_QTRLE; case MAKEFOURCC('D', 'U', 'C', 'K'): return CODEC_ID_TRUEMOTION1; case MAKEFOURCC('T', 'M', '2', '0'): return CODEC_ID_TRUEMOTION2; case MAKEFOURCC('T', 'S', 'C', 'C'): return CODEC_ID_TSCC; case MAKEFOURCC('S', 'N', 'O', 'W'): return CODEC_ID_SNOW; case MAKEFOURCC('Q', 'P', 'E', 'G'): case MAKEFOURCC('Q', '1', '_', '0'): case MAKEFOURCC('Q', '1', '_', '1'): return CODEC_ID_QPEG; case MAKEFOURCC('H', '2', '6', '1'): case MAKEFOURCC('M', '2', '6', '1'): return CODEC_ID_H261; case MAKEFOURCC('L', 'O', 'C', 'O'): return CODEC_ID_LOCO; case MAKEFOURCC('W', 'N', 'V', '1'): return CODEC_ID_WNV1; case MAKEFOURCC('C', 'S', 'C', 'D'): return CODEC_ID_CSCD; case MAKEFOURCC('Z', 'M', 'B', 'V'): return CODEC_ID_ZMBV; case MAKEFOURCC('U', 'L', 'T', 'I'): return CODEC_ID_ULTI; case MAKEFOURCC('V', 'I', 'X', 'L'): return CODEC_ID_VIXL; case MAKEFOURCC('A', 'A', 'S', 'C'): return CODEC_ID_AASC; case MAKEFOURCC('F', 'P', 'S', '1'): return CODEC_ID_FRAPS; default: return CODEC_ID_NONE; } } else if (!strcmp(Codec, "V_MPEG4/ISO/AVC")) return CODEC_ID_H264; else if (!strcmp(Codec, "V_MPEG4/ISO/ASP")) return CODEC_ID_MPEG4; else if (!strcmp(Codec, "V_MPEG2")) return CODEC_ID_MPEG2VIDEO; else if (!strcmp(Codec, "V_MPEG1")) return CODEC_ID_MPEG2VIDEO; // still not a typo else if (!strcmp(Codec, "V_SNOW")) return CODEC_ID_SNOW; else if (!strcmp(Codec, "V_THEORA")) return CODEC_ID_THEORA; else if (!strncmp(Codec, "V_REAL/RV", 9)) { switch (Codec[9]) { case '1': return CODEC_ID_RV10; case '2': return CODEC_ID_RV20; case '3': return CODEC_ID_RV30; case '4': return CODEC_ID_RV40; default: return CODEC_ID_NONE; } } else return CODEC_ID_NONE; } public: FFMKVSource(const char *Source, int Track, FILE *Timecodes, IScriptEnvironment* Env) { BufferSize = 0; Buffer = NULL; Frame = NULL; CS = NULL; CurrentFrame = 0; memset(&ST,0,sizeof(ST)); ST.base.read = (int (__cdecl *)(InputStream *,ulonglong,void *,int))StdIoRead; ST.base.scan = (longlong (__cdecl *)(InputStream *,ulonglong,unsigned int))StdIoScan; ST.base.getcachesize = (unsigned int (__cdecl *)(InputStream *))StdIoGetCacheSize; ST.base.geterror = (const char *(__cdecl *)(InputStream *))StdIoGetLastError; ST.base.memalloc = (void *(__cdecl *)(InputStream *,size_t))StdIoMalloc; ST.base.memrealloc = (void *(__cdecl *)(InputStream *,void *,size_t))StdIoRealloc; ST.base.memfree = (void (__cdecl *)(InputStream *,void *)) StdIoFree; ST.base.progress = (int (__cdecl *)(InputStream *,ulonglong,ulonglong))StdIoProgress; ST.fp = fopen(Source ,"rb"); if (ST.fp == NULL) Env->ThrowError("FFmpegSource: Can't open '%s': %s\n", Source, strerror(errno)); setvbuf(ST.fp, NULL, _IOFBF, CACHESIZE); MF = mkv_OpenEx(&ST.base, 0, 0, ErrorMessage, sizeof(ErrorMessage)); if (MF == NULL) { fclose(ST.fp); Env->ThrowError("FFmpegSource: Can't parse Matroska file: %s\n", ErrorMessage); } if (Track < 0) for (unsigned int i = 0; i < mkv_GetNumTracks(MF); i++) if (mkv_GetTrackInfo(MF, i)->Type == TT_VIDEO) { Track = i; break; } if (Track < 0) Env->ThrowError("FFmpegSource: No video track found"); if ((unsigned)Track >= mkv_GetNumTracks(MF)) Env->ThrowError("FFmpegSource: Invalid track number: %d\n", Track); TrackInfo *TI = mkv_GetTrackInfo(MF, Track); if (TI->Type != TT_VIDEO) Env->ThrowError("FFmpegSource: Selected track is not video"); mkv_SetTrackMask(MF, ~(1 << Track)); if (TI->CompEnabled) { CS = cs_Create(MF, Track, ErrorMessage, sizeof(ErrorMessage)); if (CS == NULL) Env->ThrowError("FFmpegSource: Can't create decompressor: %s\n", ErrorMessage); } avcodec_get_context_defaults(&CodecContext); CodecContext.extradata = (uint8_t *)TI->CodecPrivate; CodecContext.extradata_size = TI->CodecPrivateSize; Codec = avcodec_find_decoder(MatroskaToFFCodecID(TI)); if(Codec == NULL) Env->ThrowError("FFmpegSource: Codec not found"); if(avcodec_open(&CodecContext, Codec) < 0) Env->ThrowError("FFmpegSource: Could not open codec"); 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); unsigned TrackNumber, FrameSize, FrameFlags; ulonglong StartTime, EndTime, FilePos; while (mkv_ReadFrame(MF, 0, &TrackNumber, &StartTime, &EndTime, &FilePos, &FrameSize, &FrameFlags) == 0) { FFMKVFrameInfo FI; FI.DTS = StartTime; FI.KeyFrame = (FrameFlags & FRAME_KF) != 0; FrameToDTS.push_back(FI); DTSToFrame[StartTime] = VI.num_frames; if (Timecodes) fprintf(Timecodes, "%f\n", (StartTime * mkv_TruncFloat(TI->TimecodeScale)) / (double)(1000000)); VI.num_frames++; } Frame = avcodec_alloc_frame(); mkv_Seek(MF, FrameToDTS[0].DTS, MKVF_SEEK_TO_PREV_KEYFRAME); } ~FFMKVSource() { free(Buffer); mkv_Close(MF); fclose(ST.fp); av_free(Frame); avcodec_close(&CodecContext); } PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* Env); }; int FFMKVSource::ReadNextFrame(AVFrame *Frame, ulonglong *StartTime, IScriptEnvironment* Env) { unsigned TrackNumber, FrameFlags, FrameSize; ulonglong EndTime, FilePos, StartTime2; *StartTime = -1; int FrameFinished = 0; int Ret = -1; while (mkv_ReadFrame(MF, 0, &TrackNumber, &StartTime2, &EndTime, &FilePos, &FrameSize, &FrameFlags) == 0) { if ((longlong)*StartTime < 0) *StartTime = StartTime2; if (CS) { char CSBuffer[1024]; cs_NextFrame(CS, FilePos, FrameSize); for (;;) { int ReadBytes = cs_ReadData(CS, CSBuffer, sizeof(CSBuffer)); if (ReadBytes < 0) Env->ThrowError("FFmpegSource: Error decompressing data: %s\n", cs_GetLastError(CS)); if (ReadBytes == 0) break; Ret = avcodec_decode_video(&CodecContext, Frame, &FrameFinished, (uint8_t *)CSBuffer, ReadBytes); if (FrameFinished) goto Done; } } else { size_t ReadBytes; if (fseek(ST.fp, FilePos, SEEK_SET)) Env->ThrowError("FFmpegSource: fseek(): %s\n", strerror(errno)); if (BufferSize < FrameSize) { BufferSize = FrameSize; Buffer = realloc(Buffer, BufferSize); if (Buffer == NULL) Env->ThrowError("FFmpegSource: Out of memory\n"); } ReadBytes = fread(Buffer, 1, FrameSize, ST.fp); if (ReadBytes != FrameSize) { if (ReadBytes == 0) { if (feof(ST.fp)) fprintf(stderr, "FFmpegSource: Unexpected EOF while reading frame\n"); else fprintf(stderr, "FFmpegSource: Error reading frame: %s\n", strerror(errno)); } else fprintf(stderr,"FFmpegSource: Short read while reading frame\n"); goto Done; } Ret = avcodec_decode_video(&CodecContext, Frame, &FrameFinished, (uint8_t *)Buffer, FrameSize); if (FrameFinished) goto Done; } } Done: return Ret; } PVideoFrame __stdcall FFMKVSource::GetFrame(int n, IScriptEnvironment* Env) { bool HasSeeked = false; bool HasBiggerKF = false; for (int i = CurrentFrame + 1; i <= n; i++) if (FrameToDTS[i].KeyFrame) { HasBiggerKF = true; break; } if (n < CurrentFrame || HasBiggerKF) { mkv_Seek(MF, FrameToDTS[n].DTS, MKVF_SEEK_TO_PREV_KEYFRAME); avcodec_flush_buffers(&CodecContext); HasSeeked = true; } do { ulonglong StartTime; int Ret = ReadNextFrame(Frame, &StartTime, Env); if (HasSeeked) { CurrentFrame = DTSToFrame[StartTime]; HasSeeked = false; } CurrentFrame++; } while (CurrentFrame <= n); return OutputFrame(Frame, Env); } class FFmpegSource : public FFBase { private: AVFormatContext *FormatContext; AVCodecContext *CodecContext; AVCodec *Codec; AVFrame *Frame; int Track; int CurrentFrame; std::vector FrameToDTS; std::map DTSToFrame; bool ForceSeek; int ReadNextFrame(AVFrame *Frame, int64_t *DTS); public: FFmpegSource(const char *Source, int _Track, bool _ForceSeek, FILE *Timecodes, IScriptEnvironment* Env) : Track(_Track), ForceSeek(_ForceSeek) { CurrentFrame = 0; if(av_open_input_file(&FormatContext, Source, NULL, 0, NULL) != 0) Env->ThrowError("FFmpegSource: Couldn't open \"%s\"", Source); if(av_find_stream_info(FormatContext) < 0) Env->ThrowError("FFmpegSource: Couldn't find stream information"); if (Track >= (int)FormatContext->nb_streams) Env->ThrowError("FFmpegSource: Invalid track number"); if (Track < 0) for(unsigned int i = 0; i < FormatContext->nb_streams; i++) if(FormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO) { Track = i; break; } if(Track < -1) Env->ThrowError("FFmpegSource: Couldn't find a video stream"); if (FormatContext->streams[Track]->codec->codec_type != CODEC_TYPE_VIDEO) Env->ThrowError("FFmpegSource: Selected stream doesn't contain video"); 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"); 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 = FormatContext->streams[Track]->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(CodecContext->pix_fmt, Env); // skip indexing for avi if (!strcmp(FormatContext->iformat->name, "avi") && !Timecodes) { for (int i = 0; i < VI.num_frames; i++) { FrameToDTS.push_back(i); DTSToFrame[i] = i; } } else { AVPacket Packet; VI.num_frames = 0; while (av_read_frame(FormatContext, &Packet) >= 0) { if (Packet.stream_index == Track) { FrameToDTS.push_back(Packet.dts); DTSToFrame[Packet.dts] = VI.num_frames; if (Timecodes) fprintf(Timecodes, "%f\n", (Packet.dts * FormatContext->streams[Track]->time_base.num * 1000) / (double)(FormatContext->streams[Track]->time_base.den)); VI.num_frames++; } av_free_packet(&Packet); } } Frame = avcodec_alloc_frame(); av_seek_frame(FormatContext, Track, 0, AVSEEK_FLAG_BACKWARD); } ~FFmpegSource() { av_free(Frame); avcodec_close(CodecContext); av_close_input_file(FormatContext); } PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* Env); }; int FFmpegSource::ReadNextFrame(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) break; } return Ret; } PVideoFrame __stdcall FFmpegSource::GetFrame(int n, IScriptEnvironment* Env) { bool HasSeeked = false; int IndexPosition = av_index_search_timestamp(FormatContext->streams[Track], FrameToDTS[n], AVSEEK_FLAG_BACKWARD); int64_t NearestIndexDTS = -1; if (IndexPosition >= 0) NearestIndexDTS = FormatContext->streams[Track]->index_entries[IndexPosition].timestamp; if (n < CurrentFrame || NearestIndexDTS > FrameToDTS[CurrentFrame] || (ForceSeek && IndexPosition == -1 && n > CurrentFrame + 10)) { av_seek_frame(FormatContext, Track, FrameToDTS[n], AVSEEK_FLAG_BACKWARD); avcodec_flush_buffers(CodecContext); HasSeeked = true; } do { int64_t DTS; int Ret = ReadNextFrame(Frame, &DTS); if (HasSeeked) { CurrentFrame = DTSToFrame[DTS]; HasSeeked = DTS < 0; } CurrentFrame++; } while (CurrentFrame <= n || HasSeeked); return OutputFrame(Frame, Env); } AVSValue __cdecl CreateFFmpegSource(AVSValue Args, void* UserData, IScriptEnvironment* Env) { if (!Args[0].Defined()) Env->ThrowError("FFmpegSource: No source specified"); av_register_all(); AVFormatContext *FormatContext; if (av_open_input_file(&FormatContext, Args[0].AsString(), 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); FILE *TCFile = NULL; const char *Timecodes = Args[3].AsString(""); 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"); } FFBase *Ret; if (IsMatroska) Ret = new FFMKVSource(Args[0].AsString(), Args[1].AsInt(-1), TCFile, Env); else Ret = new FFmpegSource(Args[0].AsString(), Args[1].AsInt(-1), Args[2].AsBool(false), TCFile, Env); if (TCFile) fclose(TCFile); return Ret; } extern "C" __declspec(dllexport) const char* __stdcall AvisynthPluginInit2(IScriptEnvironment* Env) { Env->AddFunction("FFmpegSource", "[source]s[track]i[forceseek]b[timecodes]s", CreateFFmpegSource, 0); return "FFmpegSource"; };