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.
This commit is contained in:
Karl Blomster 2008-07-15 00:08:05 +00:00
parent 7e2b6afdf1
commit 893ff2f78a
14 changed files with 351 additions and 110 deletions

View file

@ -213,8 +213,10 @@ void LAVCAudioProvider::GetAudio(void *buf, int64_t start, int64_t count)
if (retval <= 0) if (retval <= 0)
throw _T("ffmpeg audio provider: failed to decode audio"); throw _T("ffmpeg audio provider: failed to decode audio");
/* decoding succeeded but the output buffer is empty, go to next packet */ /* 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; continue;
}
decoded_bytes = temp_output_buffer_size; decoded_bytes = temp_output_buffer_size;
decoded_samples = decoded_bytes / 2; /* 2 bytes per sample */ decoded_samples = decoded_bytes / 2; /* 2 bytes per sample */

View file

@ -39,8 +39,10 @@
////////// //////////
// Headers // Headers
#include <wx/wxprec.h>
#include "video_frame.h" #include "video_frame.h"
#include "aegisub.h" #include "aegisub.h"
#include "vfr.h"
////////////// //////////////
@ -64,6 +66,10 @@ public:
virtual int GetWidth()=0; // Returns the video width in pixels virtual int GetWidth()=0; // Returns the video width in pixels
virtual int GetHeight()=0; // Returns the video height in pixels virtual int GetHeight()=0; // Returns the video height in pixels
virtual double GetFPS()=0; // Get framerate in frames per second 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" // Use this to set any post-loading warnings, such as "being loaded with unreliable seeking"
virtual Aegisub::String GetWarning() { return L""; } virtual Aegisub::String GetWarning() { return L""; }

View file

@ -47,10 +47,11 @@
#include "dialog_progress.h" #include "dialog_progress.h"
#include "lavc_keyframes.h" #include "lavc_keyframes.h"
/////////////// ///////////////
// Constructor // Constructor
LAVCKeyFrames::LAVCKeyFrames(const Aegisub::String filename) LAVCKeyFrames::LAVCKeyFrames(const Aegisub::String filename)
: file(0), stream(0), streamN(-1) { : file(0), stream(0), streamN(-1), numFrames(0) {
// Open LAVCFile // Open LAVCFile
file = LAVCFile::Create(filename); 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"); if (streamN == -1) throw _T("ffmpeg keyframes reader: Could not find a video stream");
} }
////////////// //////////////
@ -77,11 +79,18 @@ wxArrayInt LAVCKeyFrames::GetKeyFrames() {
wxArrayInt keyframes; wxArrayInt keyframes;
AVPacket packet; AVPacket packet;
// sanity check stream duration int total_frames;
if (stream->duration == AV_NOPTS_VALUE) // check if the stream duration is bogus (will happen for MKV files)
throw _T("ffmpeg keyframes reader: demuxer returned invalid stream length"); if (stream->duration == AV_NOPTS_VALUE) {
int total_frames = stream->duration; // FIXME: this will most likely NOT WORK for VFR files! // 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 register unsigned int frameN = 0; // Number of parsed frames
numFrames = 0;
volatile bool canceled = false; volatile bool canceled = false;
DialogProgress *progress = new DialogProgress(NULL,_("Load keyframes"),&canceled,_("Reading keyframes from video"),0,total_frames); 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) { while (av_read_frame(file->fctx, &packet) == 0 && !canceled) {
// Check if packet is part of video stream // Check if packet is part of video stream
if (packet.stream_index == streamN) { 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 // Check if the packet contains a keyframe
if (packet.flags & PKT_FLAG_KEY) if (packet.flags & PKT_FLAG_KEY)
// note: frame numbers start from 0, watch out for the fencepost error // note: frame numbers start from 0, watch out for the fencepost error
@ -108,6 +119,8 @@ wxArrayInt LAVCKeyFrames::GetKeyFrames() {
av_free_packet(&packet); av_free_packet(&packet);
} }
numFrames = frameN;
// Clean up progress // Clean up progress
if (!canceled) progress->Destroy(); if (!canceled) progress->Destroy();
else throw wxString(_T("Keyframe loading cancelled by user")); else throw wxString(_T("Keyframe loading cancelled by user"));
@ -115,4 +128,14 @@ wxArrayInt LAVCKeyFrames::GetKeyFrames() {
return keyframes; 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 #endif // WITH_FFMPEG

View file

@ -37,15 +37,28 @@
#include <wx/wxprec.h> #include <wx/wxprec.h>
#include "lavc_file.h" #include "lavc_file.h"
#include <vector>
struct FrameInfo {
int64_t DTS;
bool isKeyFrame;
FrameInfo(int64_t ADTS, bool isAKeyFrame) : DTS(ADTS), isKeyFrame(isAKeyFrame) {};
};
typedef std::vector<FrameInfo> FrameInfoVector;
class LAVCKeyFrames { class LAVCKeyFrames {
private: private:
LAVCFile* file; // Video file LAVCFile* file; // Video file
AVStream* stream; // Used stream AVStream* stream; // Used stream
int streamN; // Stream index int streamN; // Stream index
int numFrames; // number of frames in the video
protected:
FrameInfoVector framesData;
public: public:
LAVCKeyFrames(const Aegisub::String filename); LAVCKeyFrames(const Aegisub::String filename);
~LAVCKeyFrames(); ~LAVCKeyFrames();
wxArrayInt GetKeyFrames(); wxArrayInt GetKeyFrames();
int GetNumFrames();
FrameInfoVector GetFrameData();
}; };

View file

@ -61,17 +61,6 @@
#include "ass_dialogue.h" #include "ass_dialogue.h"
#include "ass_style.h" #include "ass_style.h"
#include "subs_grid.h" #include "subs_grid.h"
#include "vfw_wrap.h"
#ifdef WITH_FFMPEG
#ifdef WIN32
#define __STDC_CONSTANT_MACROS 1
#include <stdint.h>
#endif /* WIN32 */
#include "lavc_keyframes.h"
#endif
#include "mkv_wrap.h"
#include "options.h" #include "options.h"
#include "subs_edit_box.h" #include "subs_edit_box.h"
#include "audio_display.h" #include "audio_display.h"
@ -254,8 +243,7 @@ void VideoContext::SetVideo(const wxString &filename) {
if (!filename.IsEmpty()) { if (!filename.IsEmpty()) {
try { try {
grid->CommitChanges(true); grid->CommitChanges(true);
bool isVfr = false; // double overFps = 0;
double overFps = 0;
FrameRate temp; FrameRate temp;
// Unload timecodes // 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 (VFR_Output.IsLoaded()) unload = wxMessageBox(_("Do you want to unload timecodes, too?"),_("Unload timecodes?"),wxYES_NO | wxICON_QUESTION);
//if (unload == wxYES) VFR_Output.Unload(); //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; i<KeyFrames.GetCount(); i++) {
// Is the last keyframe not this keyframe -1?
if (KeyFrames[i-1] != (int)(i-1)) {
// It's not all keyframes, go ahead
isAllKeyFrames = false;
break;
}
}
// If it is all keyframes, discard the keyframe info as it is useless
if (isAllKeyFrames) {
KeyFrames.Clear();
keyFramesLoaded = false;
}
// Set GL context // Set GL context
#ifdef __WXMAC__ #ifdef __WXMAC__
GetGLContext(displayList.front())->SetCurrent(); GetGLContext(displayList.front())->SetCurrent();
@ -338,7 +259,7 @@ void VideoContext::SetVideo(const wxString &filename) {
#endif #endif
// Choose a provider // Choose a provider
provider = VideoProviderFactoryManager::GetProvider(filename,overFps); provider = VideoProviderFactoryManager::GetProvider(filename, 0);
loaded = provider != NULL; loaded = provider != NULL;
// Get subtitles provider // 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 (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")); } 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 // Set frame rate
fps = provider->GetFPS(); fps = provider->GetFPS();
if (!isVfr || provider->IsNativelyByFrames()) { if (!isVfr || provider->IsNativelyByFrames()) {
VFR_Input.SetCFR(fps); VFR_Input.SetCFR(fps);
if (VFR_Output.GetFrameRateType() != VFR) VFR_Output.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 // Gather video parameters
length = provider->GetFrameCount(); length = provider->GetFrameCount();

View file

@ -48,6 +48,8 @@
#include "vfr.h" #include "vfr.h"
#include "ass_file.h" #include "ass_file.h"
#include "gl_wrap.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; num_frames = 0;
last_fnum = -1; last_fnum = -1;
byFrame = false; byFrame = false;
KeyFrames.Clear();
keyFramesLoaded = false;
isVfr = false;
AVSTRACE(_T("AvisynthVideoProvider: Loading Subtitles Renderer")); AVSTRACE(_T("AvisynthVideoProvider: Loading Subtitles Renderer"));
LoadRenderer(); LoadRenderer();
@ -285,6 +290,64 @@ PClip AvisynthVideoProvider::OpenVideo(Aegisub::String _filename, bool mpeg2dec3
throw _T("Avisynth: No usable video found in ") + _filename; 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; i<KeyFrames.GetCount(); i++) {
// Is the last keyframe not this keyframe -1?
if (KeyFrames[i-1] != (int)(i-1)) {
// It's not all keyframes, go ahead
isAllKeyFrames = false;
break;
}
}
// If it is all keyframes, discard the keyframe info as it is useless
if (isAllKeyFrames) {
KeyFrames.Clear();
keyFramesLoaded = false;
}
// Convert to RGB32 // Convert to RGB32
// If "Avisynth renders its own subs" is enabled, this should always be done, // If "Avisynth renders its own subs" is enabled, this should always be done,
// regardless of shaders being enabled or not. (Since VSFilter will convert // regardless of shaders being enabled or not. (Since VSFilter will convert

View file

@ -61,6 +61,11 @@ private:
wxArrayInt frameTime; wxArrayInt frameTime;
bool byFrame; bool byFrame;
wxArrayInt KeyFrames;
bool keyFramesLoaded;
bool isVfr;
FrameRate trueFrameRate;
PClip RGB32Video; PClip RGB32Video;
PClip SubtitledVideo; PClip SubtitledVideo;
@ -88,6 +93,10 @@ public:
double GetFPS() { return (double)vi.fps_numerator/(double)vi.fps_denominator; }; double GetFPS() { return (double)vi.fps_numerator/(double)vi.fps_denominator; };
int GetWidth() { return vi.width; }; int GetWidth() { return vi.width; };
int GetHeight() { return vi.height; }; int GetHeight() { return vi.height; };
bool AreKeyFramesLoaded() { return keyFramesLoaded; };
wxArrayInt GetKeyFrames() { return KeyFrames; };
bool IsVFR() { return isVfr; };
FrameRate GetTrueFrameRate() { return isVfr? trueFrameRate: FrameRate(); };
void OverrideFrameTimeList(wxArrayInt list); void OverrideFrameTimeList(wxArrayInt list);
bool IsNativelyByFrames() { return byFrame; } bool IsNativelyByFrames() { return byFrame; }

View file

@ -162,6 +162,18 @@ int VideoProviderCache::GetHeight() {
double VideoProviderCache::GetFPS() { double VideoProviderCache::GetFPS() {
return master->GetFPS(); return master->GetFPS();
} }
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) { void VideoProviderCache::OverrideFrameTimeList(Aegisub::IntArray list) {
master->OverrideFrameTimeList(list); master->OverrideFrameTimeList(list);
} }

View file

@ -41,6 +41,7 @@
// Headers // Headers
#include <list> #include <list>
#include "include/aegisub/video_provider.h" #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 GetWidth(); // Returns the video width in pixels
virtual int GetHeight(); // Returns the video height in pixels virtual int GetHeight(); // Returns the video height in pixels
virtual double GetFPS(); // Get framerate in frames per second 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 void OverrideFrameTimeList(Aegisub::IntArray list); // Override the list with the provided one, for VFR handling
virtual bool IsNativelyByFrames(); virtual bool IsNativelyByFrames();
virtual Aegisub::String GetWarning(); virtual Aegisub::String GetWarning();

View file

@ -298,6 +298,62 @@ HRESULT DirectShowVideoProvider::OpenVideo(wxString _filename) {
// Register graph with Running Objects Table for remote graphedit connection // Register graph with Running Objects Table for remote graphedit connection
RegROT(); 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<KeyFrames.GetCount(); i++) {
// Is the last keyframe not this keyframe -1?
if (KeyFrames[i-1] != (int)(i-1)) {
// It's not all keyframes, go ahead
isAllKeyFrames = false;
break;
}
}
// If it is all keyframes, discard the keyframe info as it is useless
if (isAllKeyFrames) {
KeyFrames.Clear();
keyFramesLoaded = false;
}
//NextFrame(); //NextFrame();
// Set frame count // Set frame count

View file

@ -52,6 +52,7 @@
#include <initguid.h> #include <initguid.h>
#include "include/aegisub/video_provider.h" #include "include/aegisub/video_provider.h"
#include "videosink.h" #include "videosink.h"
#include "vfr.h"
/////////////////////////////////// ///////////////////////////////////
@ -78,6 +79,10 @@ private:
double fps; double fps;
__int64 defd; __int64 defd;
wxArrayInt KeyFrames;
bool keyFramesLoaded;
bool isVfr;
HRESULT OpenVideo(wxString _filename); HRESULT OpenVideo(wxString _filename);
void CloseVideo(); void CloseVideo();
@ -110,6 +115,11 @@ public:
double GetFPS() { return fps; }; double GetFPS() { return fps; };
int GetWidth() { return width; }; int GetWidth() { return width; };
int GetHeight() { return height; }; 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"; } Aegisub::String GetDecoderName() { return L"DirectShow"; }
bool IsNativelyByFrames() { return false; } bool IsNativelyByFrames() { return false; }

View file

@ -43,6 +43,7 @@
// Headers // Headers
#include "include/aegisub/video_provider.h" #include "include/aegisub/video_provider.h"
#include <wx/colour.h> #include <wx/colour.h>
#include "vfr.h"
//////////////////////// ////////////////////////
@ -72,6 +73,12 @@ public:
int GetWidth(); int GetWidth();
int GetHeight(); int GetHeight();
double GetFPS(); double GetFPS();
bool AreKeyFramesLoaded() { return false; };
wxArrayInt GetKeyFrames() { return wxArrayInt(); };
bool IsVFR() { return false; };
FrameRate GetTrueFrameRate() { return FrameRate(); };
Aegisub::String GetDecoderName(); Aegisub::String GetDecoderName();
}; };

View file

@ -41,16 +41,22 @@
#ifdef WIN32 #ifdef WIN32
#define EMULATE_INTTYPES #define EMULATE_INTTYPES
#endif #define __STDC_CONSTANT_MACROS 1
#include <stdint.h>
#endif /* WIN32 */
#include <wx/wxprec.h> #include <wx/wxprec.h>
#include <wx/image.h> #include <wx/image.h>
#include <algorithm> #include <algorithm>
#include <vector>
#include "video_provider_lavc.h" #include "video_provider_lavc.h"
#include "mkv_wrap.h" #include "mkv_wrap.h"
#include "lavc_file.h" #include "lavc_file.h"
#include "utils.h" #include "utils.h"
#include "vfr.h" #include "vfr.h"
#include "ass_file.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; buffer2Size = 0;
vidStream = -1; vidStream = -1;
validFrame = false; validFrame = false;
framesData.clear();
// Load // Load
LoadVideo(filename,fps); LoadVideo(filename,fps);
@ -119,8 +126,13 @@ void LAVCVideoProvider::LoadVideo(Aegisub::String filename, double fps) {
result = avcodec_open(codecContext,codec); result = avcodec_open(codecContext,codec);
if (result < 0) throw _T("Failed to open video decoder"); if (result < 0) throw _T("Failed to open video decoder");
// Check length // Parse file for keyframes and other useful stuff
length = stream->duration; LAVCKeyFrames LAVCFrameData(filename);
KeyFramesList = LAVCFrameData.GetKeyFrames();
keyFramesLoaded = true;
// set length etc.
length = LAVCFrameData.GetNumFrames();
framesData = LAVCFrameData.GetFrameData();
#if 0 #if 0
isMkv = false; isMkv = false;
length = stream->duration; length = stream->duration;
@ -141,6 +153,7 @@ void LAVCVideoProvider::LoadVideo(Aegisub::String filename, double fps) {
// Set frame // Set frame
frameNumber = -1; frameNumber = -1;
lastFrameNumber = -1;
} }
// Catch errors // Catch errors
@ -196,14 +209,19 @@ void LAVCVideoProvider::Close() {
////////////////// //////////////////
// Get next frame // Get next frame
bool LAVCVideoProvider::GetNextFrame() { bool LAVCVideoProvider::GetNextFrame(int64_t *startDTS) {
// Read packet
AVPacket packet; AVPacket packet;
*startDTS = -1; // magic
// Read packet
while (av_read_frame(lavcfile->fctx, &packet)>=0) { while (av_read_frame(lavcfile->fctx, &packet)>=0) {
// Check if packet is part of video stream // Check if packet is part of video stream
if(packet.stream_index == vidStream) { if(packet.stream_index == vidStream) {
// Decode frame // Decode frame
int frameFinished; int frameFinished;
if (*startDTS < 0)
*startDTS = packet.dts;
avcodec_decode_video(codecContext, frame, &frameFinished, packet.data, packet.size); avcodec_decode_video(codecContext, frame, &frameFinished, packet.data, packet.size);
// Success? // Success?
@ -211,11 +229,13 @@ bool LAVCVideoProvider::GetNextFrame() {
// Set time // Set time
lastDecodeTime = packet.dts; lastDecodeTime = packet.dts;
// Free packet // Free packet and return
av_free_packet(&packet); av_free_packet(&packet);
return true; return true;
} }
} }
// free packet
av_free_packet(&packet);
} }
// No more packets // No more packets
@ -299,22 +319,27 @@ wxBitmap LAVCVideoProvider::AVFrameToWX(AVFrame *source, int n) {
// Get frame // Get frame
const AegiVideoFrame LAVCVideoProvider::GetFrame(int n,int formatType) { const AegiVideoFrame LAVCVideoProvider::GetFrame(int n,int formatType) {
// Return stored frame // Return stored frame
n = MID(0,n,GetFrameCount()-1); // n = MID(0,n,GetFrameCount()-1);
if (n == frameNumber) { if (n == lastFrameNumber) {
if (!validFrame) validFrame = true; if (!validFrame) validFrame = true;
return curFrame; return curFrame;
} }
if (frameNumber < 0)
frameNumber = 0;
// Following frame, just get it // Following frame, just get it
if (n == frameNumber+1) { /* if (n == frameNumber+1) {
GetNextFrame(); int64_t temp = -1;
} GetNextFrame(&temp);
} */
// Needs to seek // Needs to seek
else { // else {
// Prepare seek // Prepare seek
int64_t seekTo; // int64_t seekTo;
int result = 0; // int result = 0;
int closestKeyFrame = FindClosestKeyframe(n);
#if 0 #if 0
// Get time to seek to // Get time to seek to
@ -352,11 +377,37 @@ const AegiVideoFrame LAVCVideoProvider::GetFrame(int n,int formatType) {
// Constant frame rate // Constant frame rate
else { else {
#endif #endif
seekTo = n; // seekTo = closestKeyFrame;
result = av_seek_frame(lavcfile->fctx,vidStream,seekTo,AVSEEK_FLAG_BACKWARD); 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 // Seek to keyframe
if (result == 0) { /* if (result == 0) {
avcodec_flush_buffers(codecContext); avcodec_flush_buffers(codecContext);
// Seek until final frame // Seek until final frame
@ -369,11 +420,11 @@ const AegiVideoFrame LAVCVideoProvider::GetFrame(int n,int formatType) {
// Failed seeking // Failed seeking
else { else {
GetNextFrame(); GetNextFrame();
} }*/
#if 0 #if 0
} }
#endif #endif
} //}
// Get aegisub frame // Get aegisub frame
@ -418,7 +469,7 @@ const AegiVideoFrame LAVCVideoProvider::GetFrame(int n,int formatType) {
// Set current frame // Set current frame
validFrame = true; validFrame = true;
frameNumber = n; lastFrameNumber = n;
// Return // Return
return final; return final;
@ -459,4 +510,37 @@ int LAVCVideoProvider::GetHeight() {
return codecContext->height; 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 #endif // WITH_FFMPEG

View file

@ -42,6 +42,7 @@
#ifdef WIN32 #ifdef WIN32
#define EMULATE_INTTYPES #define EMULATE_INTTYPES
#endif #endif
#include <vector>
extern "C" { extern "C" {
#include <ffmpeg/avcodec.h> #include <ffmpeg/avcodec.h>
#include <ffmpeg/avformat.h> #include <ffmpeg/avformat.h>
@ -51,6 +52,7 @@ extern "C" {
#include "include/aegisub/aegisub.h" #include "include/aegisub/aegisub.h"
#include "mkv_wrap.h" #include "mkv_wrap.h"
#include "lavc_file.h" #include "lavc_file.h"
#include "lavc_keyframes.h"
/////////////////////// ///////////////////////
@ -71,6 +73,10 @@ private:
uint8_t *bufferRGB; uint8_t *bufferRGB;
SwsContext *sws_context; SwsContext *sws_context;
wxArrayInt KeyFramesList;
bool keyFramesLoaded;
// bool isVfr; // currently unused
int display_w; int display_w;
int display_h; int display_h;
@ -79,16 +85,21 @@ private:
bool isMkv; bool isMkv;
int64_t lastDecodeTime; int64_t lastDecodeTime;
int frameNumber; int frameNumber;
int lastFrameNumber;
int length; int length;
AegiVideoFrame curFrame; AegiVideoFrame curFrame;
bool validFrame; bool validFrame;
FrameInfoVector framesData;
uint8_t *buffer1; uint8_t *buffer1;
uint8_t *buffer2; uint8_t *buffer2;
int buffer1Size; int buffer1Size;
int buffer2Size; 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 LoadVideo(Aegisub::String filename, double fps);
void Close(); void Close();
@ -105,6 +116,10 @@ public:
int GetWidth(); int GetWidth();
int GetHeight(); int GetHeight();
double GetFPS(); 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"; } Aegisub::String GetDecoderName() { return L"FFMpeg/libavcodec"; }
bool IsNativelyByFrames() { return true; } bool IsNativelyByFrames() { return true; }
int GetDesiredCacheSize() { return 8; } int GetDesiredCacheSize() { return 8; }