From 893ff2f78a02d1d84f46f6e266de4b510ce9cc76 Mon Sep 17 00:00:00 2001 From: Karl Blomster Date: Tue, 15 Jul 2008 00:08:05 +0000 Subject: [PATCH] Major rewrite of the video providing system. Hilights: - It is now the responsibility of each video provider to provide a list of keyframe positions and (if it can) timecodes. - The ffmpeg video provider now indexes files before opening them and does no longer rely on stream->duration to determine the number of frames. Fixes opening of MKV files, but it does not (currently) open timecodes automatically and reported keyframe positions seem way off. Status of frame-accuracy with MKV files unknown but it may very well work. - Modified the way the ffmpeg video provider seeks (inspired by code from Myrsloik's ffmpegsource). Should no longer lose the first frame and should also no longer be frame-inaccurate, at least not with AVI. - DirectShow video provider may or may not be completely broken, not tested. Originally committed to SVN as r2252. --- aegisub/audio_provider_lavc.cpp | 4 +- aegisub/include/aegisub/video_provider.h | 6 ++ aegisub/lavc_keyframes.cpp | 33 +++++- aegisub/lavc_keyframes.h | 15 ++- aegisub/video_context.cpp | 100 ++++-------------- aegisub/video_provider_avs.cpp | 63 ++++++++++++ aegisub/video_provider_avs.h | 9 ++ aegisub/video_provider_cache.cpp | 12 +++ aegisub/video_provider_cache.h | 5 + aegisub/video_provider_dshow.cpp | 56 ++++++++++ aegisub/video_provider_dshow.h | 10 ++ aegisub/video_provider_dummy.h | 7 ++ aegisub/video_provider_lavc.cpp | 124 +++++++++++++++++++---- aegisub/video_provider_lavc.h | 17 +++- 14 files changed, 351 insertions(+), 110 deletions(-) diff --git a/aegisub/audio_provider_lavc.cpp b/aegisub/audio_provider_lavc.cpp index c0a902da4..86ba6f980 100644 --- a/aegisub/audio_provider_lavc.cpp +++ b/aegisub/audio_provider_lavc.cpp @@ -213,8 +213,10 @@ void LAVCAudioProvider::GetAudio(void *buf, int64_t start, int64_t count) if (retval <= 0) throw _T("ffmpeg audio provider: failed to decode audio"); /* decoding succeeded but the output buffer is empty, go to next packet */ - if (temp_output_buffer_size == 0) + if (temp_output_buffer_size == 0) { + av_free_packet(&packet); continue; + } decoded_bytes = temp_output_buffer_size; decoded_samples = decoded_bytes / 2; /* 2 bytes per sample */ diff --git a/aegisub/include/aegisub/video_provider.h b/aegisub/include/aegisub/video_provider.h index cd78ba604..b57a670a2 100644 --- a/aegisub/include/aegisub/video_provider.h +++ b/aegisub/include/aegisub/video_provider.h @@ -39,8 +39,10 @@ ////////// // Headers +#include #include "video_frame.h" #include "aegisub.h" +#include "vfr.h" ////////////// @@ -64,6 +66,10 @@ public: virtual int GetWidth()=0; // Returns the video width in pixels virtual int GetHeight()=0; // Returns the video height in pixels virtual double GetFPS()=0; // Get framerate in frames per second + virtual bool AreKeyFramesLoaded()=0; // Returns true if keyframe info is loaded, false otherwise + virtual bool IsVFR()=0; // Returns true if video is VFR + virtual wxArrayInt GetKeyFrames()=0; // Returns list of keyframes + virtual FrameRate GetTrueFrameRate()=0; // Returns magic VFR stuff // Use this to set any post-loading warnings, such as "being loaded with unreliable seeking" virtual Aegisub::String GetWarning() { return L""; } diff --git a/aegisub/lavc_keyframes.cpp b/aegisub/lavc_keyframes.cpp index b099ac56d..48e8d4eda 100644 --- a/aegisub/lavc_keyframes.cpp +++ b/aegisub/lavc_keyframes.cpp @@ -47,10 +47,11 @@ #include "dialog_progress.h" #include "lavc_keyframes.h" + /////////////// // Constructor LAVCKeyFrames::LAVCKeyFrames(const Aegisub::String filename) - : file(0), stream(0), streamN(-1) { + : file(0), stream(0), streamN(-1), numFrames(0) { // Open LAVCFile file = LAVCFile::Create(filename); @@ -63,6 +64,7 @@ LAVCKeyFrames::LAVCKeyFrames(const Aegisub::String filename) } } if (streamN == -1) throw _T("ffmpeg keyframes reader: Could not find a video stream"); + } ////////////// @@ -77,11 +79,18 @@ wxArrayInt LAVCKeyFrames::GetKeyFrames() { wxArrayInt keyframes; AVPacket packet; - // sanity check stream duration - if (stream->duration == AV_NOPTS_VALUE) - throw _T("ffmpeg keyframes reader: demuxer returned invalid stream length"); - int total_frames = stream->duration; // FIXME: this will most likely NOT WORK for VFR files! + int total_frames; + // check if the stream duration is bogus (will happen for MKV files) + if (stream->duration == AV_NOPTS_VALUE) { + // throw _T("ffmpeg keyframes reader: demuxer returned invalid stream length"); + // FIXME: find some less dumb way to do this + total_frames = 123456; // random suitably big number + } + else { + total_frames = stream->duration; + } register unsigned int frameN = 0; // Number of parsed frames + numFrames = 0; volatile bool canceled = false; DialogProgress *progress = new DialogProgress(NULL,_("Load keyframes"),&canceled,_("Reading keyframes from video"),0,total_frames); @@ -91,6 +100,8 @@ wxArrayInt LAVCKeyFrames::GetKeyFrames() { while (av_read_frame(file->fctx, &packet) == 0 && !canceled) { // Check if packet is part of video stream if (packet.stream_index == streamN) { + framesData.push_back(FrameInfo(packet.dts, (packet.flags & PKT_FLAG_KEY) ? 1 : 0)); + // Check if the packet contains a keyframe if (packet.flags & PKT_FLAG_KEY) // note: frame numbers start from 0, watch out for the fencepost error @@ -108,6 +119,8 @@ wxArrayInt LAVCKeyFrames::GetKeyFrames() { av_free_packet(&packet); } + numFrames = frameN; + // Clean up progress if (!canceled) progress->Destroy(); else throw wxString(_T("Keyframe loading cancelled by user")); @@ -115,4 +128,14 @@ wxArrayInt LAVCKeyFrames::GetKeyFrames() { return keyframes; } +// returns the video duration in frames +int LAVCKeyFrames::GetNumFrames() { + return numFrames; +} + +// returns the frame metadata vector +FrameInfoVector LAVCKeyFrames::GetFrameData() { + return framesData; +} + #endif // WITH_FFMPEG diff --git a/aegisub/lavc_keyframes.h b/aegisub/lavc_keyframes.h index 6aa36e10b..ddde41740 100644 --- a/aegisub/lavc_keyframes.h +++ b/aegisub/lavc_keyframes.h @@ -37,15 +37,28 @@ #include #include "lavc_file.h" +#include + +struct FrameInfo { + int64_t DTS; + bool isKeyFrame; + FrameInfo(int64_t ADTS, bool isAKeyFrame) : DTS(ADTS), isKeyFrame(isAKeyFrame) {}; +}; + +typedef std::vector FrameInfoVector; class LAVCKeyFrames { private: LAVCFile* file; // Video file AVStream* stream; // Used stream - int streamN; // Stream index + int numFrames; // number of frames in the video + protected: + FrameInfoVector framesData; public: LAVCKeyFrames(const Aegisub::String filename); ~LAVCKeyFrames(); wxArrayInt GetKeyFrames(); + int GetNumFrames(); + FrameInfoVector GetFrameData(); }; diff --git a/aegisub/video_context.cpp b/aegisub/video_context.cpp index 523bc439a..0101b3a3f 100644 --- a/aegisub/video_context.cpp +++ b/aegisub/video_context.cpp @@ -61,17 +61,6 @@ #include "ass_dialogue.h" #include "ass_style.h" #include "subs_grid.h" -#include "vfw_wrap.h" - -#ifdef WITH_FFMPEG -#ifdef WIN32 -#define __STDC_CONSTANT_MACROS 1 -#include -#endif /* WIN32 */ -#include "lavc_keyframes.h" -#endif - -#include "mkv_wrap.h" #include "options.h" #include "subs_edit_box.h" #include "audio_display.h" @@ -254,8 +243,7 @@ void VideoContext::SetVideo(const wxString &filename) { if (!filename.IsEmpty()) { try { grid->CommitChanges(true); - bool isVfr = false; - double overFps = 0; + // double overFps = 0; FrameRate temp; // Unload timecodes @@ -263,73 +251,6 @@ void VideoContext::SetVideo(const wxString &filename) { //if (VFR_Output.IsLoaded()) unload = wxMessageBox(_("Do you want to unload timecodes, too?"),_("Unload timecodes?"),wxYES_NO | wxICON_QUESTION); //if (unload == wxYES) VFR_Output.Unload(); - // Read extra data from file - bool mkvOpen = MatroskaWrapper::wrapper.IsOpen(); - wxString ext = filename.Right(4).Lower(); - KeyFrames.Clear(); - if (ext == _T(".mkv") || mkvOpen) { - // Parse mkv - if (!mkvOpen) MatroskaWrapper::wrapper.Open(filename); - - // Get keyframes - KeyFrames = MatroskaWrapper::wrapper.GetKeyFrames(); - keyFramesLoaded = true; - - // Ask to override timecodes - int override = wxYES; - if (VFR_Output.IsLoaded()) override = wxMessageBox(_("You already have timecodes loaded. Replace them with the timecodes from the Matroska file?"),_("Replace timecodes?"),wxYES_NO | wxICON_QUESTION); - if (override == wxYES) { - MatroskaWrapper::wrapper.SetToTimecodes(temp); - isVfr = temp.GetFrameRateType() == VFR; - if (isVfr) { - overFps = temp.GetCommonFPS(); - MatroskaWrapper::wrapper.SetToTimecodes(VFR_Input); - MatroskaWrapper::wrapper.SetToTimecodes(VFR_Output); - } - } - - // Close mkv - MatroskaWrapper::wrapper.Close(); - } -// do we have ffmpeg? if so try to load keyframes with it -#ifdef WITH_FFMPEG - else { - keyFramesLoaded = false; - KeyFrames.Clear(); - LAVCKeyFrames k(filename.c_str()); - KeyFrames = k.GetKeyFrames(); - keyFramesLoaded = true; - } -#else -// no ffmpeg, check if we have windows, if so we can load keyframes -// from AVI files using VFW -#ifdef __WINDOWS__ - else if (ext == _T(".avi")) { - keyFramesLoaded = false; - KeyFrames.Clear(); - KeyFrames = VFWWrapper::GetKeyFrames(filename); - keyFramesLoaded = true; - } -#endif -#endif - - // Check if the file is all keyframes - bool isAllKeyFrames = true; - for (unsigned int i=1; iSetCurrent(); @@ -338,7 +259,7 @@ void VideoContext::SetVideo(const wxString &filename) { #endif // Choose a provider - provider = VideoProviderFactoryManager::GetProvider(filename,overFps); + provider = VideoProviderFactoryManager::GetProvider(filename, 0); loaded = provider != NULL; // Get subtitles provider @@ -349,13 +270,28 @@ void VideoContext::SetVideo(const wxString &filename) { catch (wxString err) { wxMessageBox(_T("Error while loading subtitles provider: ") + err,_T("Subtitles provider")); } catch (const wchar_t *err) { wxMessageBox(_T("Error while loading subtitles provider: ") + wxString(err),_T("Subtitles provider")); } + KeyFrames.Clear(); + // load keyframes if available + if (provider->AreKeyFramesLoaded()) { + KeyFrames = provider->GetKeyFrames(); + keyFramesLoaded = true; + } + else { + keyFramesLoaded = false; + } + + bool isVfr = provider->IsVFR(); + // Set frame rate fps = provider->GetFPS(); if (!isVfr || provider->IsNativelyByFrames()) { VFR_Input.SetCFR(fps); if (VFR_Output.GetFrameRateType() != VFR) VFR_Output.SetCFR(fps); } - else provider->OverrideFrameTimeList(temp.GetFrameTimeList()); + else { + FrameRate temp = provider->GetTrueFrameRate(); + provider->OverrideFrameTimeList(temp.GetFrameTimeList()); + } // Gather video parameters length = provider->GetFrameCount(); diff --git a/aegisub/video_provider_avs.cpp b/aegisub/video_provider_avs.cpp index aa1831b5f..08caec63f 100644 --- a/aegisub/video_provider_avs.cpp +++ b/aegisub/video_provider_avs.cpp @@ -48,6 +48,8 @@ #include "vfr.h" #include "ass_file.h" #include "gl_wrap.h" +#include "mkv_wrap.h" +#include "vfw_wrap.h" /////////////// @@ -61,6 +63,9 @@ AvisynthVideoProvider::AvisynthVideoProvider(Aegisub::String _filename, double _ num_frames = 0; last_fnum = -1; byFrame = false; + KeyFrames.Clear(); + keyFramesLoaded = false; + isVfr = false; AVSTRACE(_T("AvisynthVideoProvider: Loading Subtitles Renderer")); LoadRenderer(); @@ -285,6 +290,64 @@ PClip AvisynthVideoProvider::OpenVideo(Aegisub::String _filename, bool mpeg2dec3 throw _T("Avisynth: No usable video found in ") + _filename; } + // Read keyframes and timecodes from MKV file + isVfr = false; + FrameRate temp; + double overFps = 0; + bool mkvOpen = MatroskaWrapper::wrapper.IsOpen(); + KeyFrames.Clear(); + if (extension == _T(".mkv") || mkvOpen) { + // Parse mkv + if (!mkvOpen) MatroskaWrapper::wrapper.Open(_filename); + + // Get keyframes + KeyFrames = MatroskaWrapper::wrapper.GetKeyFrames(); + keyFramesLoaded = true; + + // Ask to override timecodes + int override = wxYES; + if (VFR_Output.IsLoaded()) override = wxMessageBox(_("You already have timecodes loaded. Replace them with the timecodes from the Matroska file?"),_("Replace timecodes?"),wxYES_NO | wxICON_QUESTION); + if (override == wxYES) { + MatroskaWrapper::wrapper.SetToTimecodes(temp); + isVfr = temp.GetFrameRateType() == VFR; + if (isVfr) { + overFps = temp.GetCommonFPS(); + MatroskaWrapper::wrapper.SetToTimecodes(VFR_Input); + MatroskaWrapper::wrapper.SetToTimecodes(VFR_Output); + trueFrameRate = temp; + } + } + + // Close mkv + MatroskaWrapper::wrapper.Close(); + } +// check if we have windows, if so we can load keyframes from AVI files using VFW +#ifdef __WINDOWS__ + else if (extension == _T(".avi")) { + keyFramesLoaded = false; + KeyFrames.Clear(); + KeyFrames = VFWWrapper::GetKeyFrames(_filename); + keyFramesLoaded = true; + } +#endif /* __WINDOWS__ */ + + // Check if the file is all keyframes + bool isAllKeyFrames = true; + for (unsigned int i=1; iGetFPS(); } +bool VideoProviderCache::IsVFR() { + return master->IsVFR(); +} +bool VideoProviderCache::AreKeyFramesLoaded() { + return master->AreKeyFramesLoaded(); +} +wxArrayInt VideoProviderCache::GetKeyFrames() { + return master->GetKeyFrames(); +} +FrameRate VideoProviderCache::GetTrueFrameRate() { + return master->GetTrueFrameRate(); +} void VideoProviderCache::OverrideFrameTimeList(Aegisub::IntArray list) { master->OverrideFrameTimeList(list); } diff --git a/aegisub/video_provider_cache.h b/aegisub/video_provider_cache.h index 408da0f1c..952ea7b89 100644 --- a/aegisub/video_provider_cache.h +++ b/aegisub/video_provider_cache.h @@ -41,6 +41,7 @@ // Headers #include #include "include/aegisub/video_provider.h" +#include "vfr.h" //////////////// @@ -86,6 +87,10 @@ public: virtual int GetWidth(); // Returns the video width in pixels virtual int GetHeight(); // Returns the video height in pixels virtual double GetFPS(); // Get framerate in frames per second + virtual bool AreKeyFramesLoaded(); + virtual bool IsVFR(); + virtual wxArrayInt GetKeyFrames(); + virtual FrameRate GetTrueFrameRate(); virtual void OverrideFrameTimeList(Aegisub::IntArray list); // Override the list with the provided one, for VFR handling virtual bool IsNativelyByFrames(); virtual Aegisub::String GetWarning(); diff --git a/aegisub/video_provider_dshow.cpp b/aegisub/video_provider_dshow.cpp index 9a7ccfccc..f5bc9fb1e 100644 --- a/aegisub/video_provider_dshow.cpp +++ b/aegisub/video_provider_dshow.cpp @@ -298,6 +298,62 @@ HRESULT DirectShowVideoProvider::OpenVideo(wxString _filename) { // Register graph with Running Objects Table for remote graphedit connection RegROT(); + // Read keyframes and timecodes from MKV file + isVfr = false; + FrameRate temp; + double overFps = 0; + bool mkvOpen = MatroskaWrapper::wrapper.IsOpen(); + KeyFrames.Clear(); + wxString extension = _filename.Right(4).Lower(); + if (extension == _T(".mkv") || mkvOpen) { + // Parse mkv + if (!mkvOpen) MatroskaWrapper::wrapper.Open(_filename); + + // Get keyframes + KeyFrames = MatroskaWrapper::wrapper.GetKeyFrames(); + keyFramesLoaded = true; + + // Ask to override timecodes + int override = wxYES; + if (VFR_Output.IsLoaded()) override = wxMessageBox(_("You already have timecodes loaded. Replace them with the timecodes from the Matroska file?"),_("Replace timecodes?"),wxYES_NO | wxICON_QUESTION); + if (override == wxYES) { + MatroskaWrapper::wrapper.SetToTimecodes(temp); + isVfr = temp.GetFrameRateType() == VFR; + if (isVfr) { + overFps = temp.GetCommonFPS(); + MatroskaWrapper::wrapper.SetToTimecodes(VFR_Input); + MatroskaWrapper::wrapper.SetToTimecodes(VFR_Output); + trueFrameRate = temp; + } + } + + // Close mkv + MatroskaWrapper::wrapper.Close(); + } + else if (extension == _T(".avi")) { + keyFramesLoaded = false; + KeyFrames.Clear(); + KeyFrames = VFWWrapper::GetKeyFrames(_filename); + keyFramesLoaded = true; + } + + // Check if the file is all keyframes + bool isAllKeyFrames = true; + for (unsigned int i=1; i #include "include/aegisub/video_provider.h" #include "videosink.h" +#include "vfr.h" /////////////////////////////////// @@ -78,6 +79,10 @@ private: double fps; __int64 defd; + wxArrayInt KeyFrames; + bool keyFramesLoaded; + bool isVfr; + HRESULT OpenVideo(wxString _filename); void CloseVideo(); @@ -110,6 +115,11 @@ public: double GetFPS() { return fps; }; int GetWidth() { return width; }; int GetHeight() { return height; }; + bool AreKeyFramesLoaded() { return keyFramesLoaded; }; + wxArrayInt GetKeyFrames() { return KeyFrames; }; + bool IsVFR() { return isVfr; }; + FrameRate GetTrueFrameRate() { return isVfr? trueFrameRate: FrameRate() }; + Aegisub::String GetDecoderName() { return L"DirectShow"; } bool IsNativelyByFrames() { return false; } diff --git a/aegisub/video_provider_dummy.h b/aegisub/video_provider_dummy.h index 2377a775b..b1513261d 100644 --- a/aegisub/video_provider_dummy.h +++ b/aegisub/video_provider_dummy.h @@ -43,6 +43,7 @@ // Headers #include "include/aegisub/video_provider.h" #include +#include "vfr.h" //////////////////////// @@ -72,6 +73,12 @@ public: int GetWidth(); int GetHeight(); double GetFPS(); + + bool AreKeyFramesLoaded() { return false; }; + wxArrayInt GetKeyFrames() { return wxArrayInt(); }; + bool IsVFR() { return false; }; + FrameRate GetTrueFrameRate() { return FrameRate(); }; + Aegisub::String GetDecoderName(); }; diff --git a/aegisub/video_provider_lavc.cpp b/aegisub/video_provider_lavc.cpp index 2cfb0df7c..39ed3afd9 100644 --- a/aegisub/video_provider_lavc.cpp +++ b/aegisub/video_provider_lavc.cpp @@ -41,16 +41,22 @@ #ifdef WIN32 #define EMULATE_INTTYPES -#endif +#define __STDC_CONSTANT_MACROS 1 +#include +#endif /* WIN32 */ + #include #include #include +#include #include "video_provider_lavc.h" #include "mkv_wrap.h" #include "lavc_file.h" #include "utils.h" #include "vfr.h" #include "ass_file.h" +#include "lavc_keyframes.h" +#include "video_context.h" /////////////// @@ -71,6 +77,7 @@ LAVCVideoProvider::LAVCVideoProvider(Aegisub::String filename,double fps) { buffer2Size = 0; vidStream = -1; validFrame = false; + framesData.clear(); // Load LoadVideo(filename,fps); @@ -119,8 +126,13 @@ void LAVCVideoProvider::LoadVideo(Aegisub::String filename, double fps) { result = avcodec_open(codecContext,codec); if (result < 0) throw _T("Failed to open video decoder"); - // Check length - length = stream->duration; + // Parse file for keyframes and other useful stuff + LAVCKeyFrames LAVCFrameData(filename); + KeyFramesList = LAVCFrameData.GetKeyFrames(); + keyFramesLoaded = true; + // set length etc. + length = LAVCFrameData.GetNumFrames(); + framesData = LAVCFrameData.GetFrameData(); #if 0 isMkv = false; length = stream->duration; @@ -141,6 +153,7 @@ void LAVCVideoProvider::LoadVideo(Aegisub::String filename, double fps) { // Set frame frameNumber = -1; + lastFrameNumber = -1; } // Catch errors @@ -196,14 +209,19 @@ void LAVCVideoProvider::Close() { ////////////////// // Get next frame -bool LAVCVideoProvider::GetNextFrame() { - // Read packet +bool LAVCVideoProvider::GetNextFrame(int64_t *startDTS) { AVPacket packet; + *startDTS = -1; // magic + + // Read packet while (av_read_frame(lavcfile->fctx, &packet)>=0) { // Check if packet is part of video stream if(packet.stream_index == vidStream) { // Decode frame int frameFinished; + if (*startDTS < 0) + *startDTS = packet.dts; + avcodec_decode_video(codecContext, frame, &frameFinished, packet.data, packet.size); // Success? @@ -211,11 +229,13 @@ bool LAVCVideoProvider::GetNextFrame() { // Set time lastDecodeTime = packet.dts; - // Free packet + // Free packet and return av_free_packet(&packet); return true; } } + // free packet + av_free_packet(&packet); } // No more packets @@ -299,22 +319,27 @@ wxBitmap LAVCVideoProvider::AVFrameToWX(AVFrame *source, int n) { // Get frame const AegiVideoFrame LAVCVideoProvider::GetFrame(int n,int formatType) { // Return stored frame - n = MID(0,n,GetFrameCount()-1); - if (n == frameNumber) { + // n = MID(0,n,GetFrameCount()-1); + if (n == lastFrameNumber) { if (!validFrame) validFrame = true; return curFrame; } + if (frameNumber < 0) + frameNumber = 0; + // Following frame, just get it - if (n == frameNumber+1) { - GetNextFrame(); - } + /* if (n == frameNumber+1) { + int64_t temp = -1; + GetNextFrame(&temp); + } */ // Needs to seek - else { + // else { // Prepare seek - int64_t seekTo; - int result = 0; + // int64_t seekTo; + // int result = 0; + int closestKeyFrame = FindClosestKeyframe(n); #if 0 // Get time to seek to @@ -352,11 +377,37 @@ const AegiVideoFrame LAVCVideoProvider::GetFrame(int n,int formatType) { // Constant frame rate else { #endif - seekTo = n; - result = av_seek_frame(lavcfile->fctx,vidStream,seekTo,AVSEEK_FLAG_BACKWARD); + // seekTo = closestKeyFrame; + bool hasSeeked = false; + + // do we really need to seek? + // 10 frames is used as a margin to prevent excessive seeking since the predicted best keyframe isn't always selected by avformat + if (n < frameNumber || closestKeyFrame > frameNumber+10) { + // do it + av_seek_frame(lavcfile->fctx, vidStream, framesData[closestKeyFrame].DTS, AVSEEK_FLAG_BACKWARD); + avcodec_flush_buffers(codecContext); + hasSeeked = true; + } + + // decode frames until we have the one we want + do { + int64_t startTime; + GetNextFrame(&startTime); + + if (hasSeeked) { + hasSeeked = false; + + // is the seek destination known? does it belong to a frame? + if (startTime < 0 || (frameNumber = FrameFromDTS(startTime)) < 0) + throw _T("ffmpeg video provider: frame accurate seeking failed"); + //frameNumber = ClosestFrameFromDTS(startTime); + } + + frameNumber++; + } while (frameNumber <= n); // Seek to keyframe - if (result == 0) { + /* if (result == 0) { avcodec_flush_buffers(codecContext); // Seek until final frame @@ -369,11 +420,11 @@ const AegiVideoFrame LAVCVideoProvider::GetFrame(int n,int formatType) { // Failed seeking else { GetNextFrame(); - } + }*/ #if 0 } #endif - } + //} // Get aegisub frame @@ -418,7 +469,7 @@ const AegiVideoFrame LAVCVideoProvider::GetFrame(int n,int formatType) { // Set current frame validFrame = true; - frameNumber = n; + lastFrameNumber = n; // Return return final; @@ -459,4 +510,37 @@ int LAVCVideoProvider::GetHeight() { return codecContext->height; } +////////////////////// +// Find the keyframe we should seek to if we want to seek to a given frame N +int LAVCVideoProvider::FindClosestKeyframe(int frameN) { + for (int i = frameN; i > 0; i--) + if (framesData[i].isKeyFrame) + return i; + return 0; +} + +////////////////////// +// Convert a DTS into a frame number +int LAVCVideoProvider::FrameFromDTS(int64_t ADTS) { + for (int i = 0; i < (int)framesData.size(); i++) + if (framesData[i].DTS == ADTS) + return i; + return -1; +} + +////////////////////// +// Find closest frame to the given DTS +int LAVCVideoProvider::ClosestFrameFromDTS(int64_t ADTS) { + int n = 0; + int64_t bestDiff = 0xFFFFFFFFFFFFFFLL; // big number + for (int i = 0; i < (int)framesData.size(); i++) { + int64_t currentDiff = FFABS(framesData[i].DTS - ADTS); + if (currentDiff < bestDiff) { + bestDiff = currentDiff; + n = i; + } + } + return n; +} + #endif // WITH_FFMPEG diff --git a/aegisub/video_provider_lavc.h b/aegisub/video_provider_lavc.h index 8bb8e7ce1..91bc9ffc2 100644 --- a/aegisub/video_provider_lavc.h +++ b/aegisub/video_provider_lavc.h @@ -42,6 +42,7 @@ #ifdef WIN32 #define EMULATE_INTTYPES #endif +#include extern "C" { #include #include @@ -51,6 +52,7 @@ extern "C" { #include "include/aegisub/aegisub.h" #include "mkv_wrap.h" #include "lavc_file.h" +#include "lavc_keyframes.h" /////////////////////// @@ -70,6 +72,10 @@ private: AVFrame *frameRGB; uint8_t *bufferRGB; SwsContext *sws_context; + + wxArrayInt KeyFramesList; + bool keyFramesLoaded; + // bool isVfr; // currently unused int display_w; int display_h; @@ -79,16 +85,21 @@ private: bool isMkv; int64_t lastDecodeTime; int frameNumber; + int lastFrameNumber; int length; AegiVideoFrame curFrame; bool validFrame; + FrameInfoVector framesData; uint8_t *buffer1; uint8_t *buffer2; int buffer1Size; int buffer2Size; - bool GetNextFrame(); + int FindClosestKeyframe(int frameN); + int FrameFromDTS(int64_t ADTS); + int ClosestFrameFromDTS(int64_t ADTS); + bool GetNextFrame(int64_t *DTS); void LoadVideo(Aegisub::String filename, double fps); void Close(); @@ -105,6 +116,10 @@ public: int GetWidth(); int GetHeight(); double GetFPS(); + bool AreKeyFramesLoaded() { return keyFramesLoaded; }; + wxArrayInt GetKeyFrames() { return KeyFramesList; }; + bool IsVFR() { return false; }; // FIXME: bork? + FrameRate GetTrueFrameRate() { return FrameRate(); }; // nothing useful here Aegisub::String GetDecoderName() { return L"FFMpeg/libavcodec"; } bool IsNativelyByFrames() { return true; } int GetDesiredCacheSize() { return 8; }