diff --git a/aegisub/lavc_file.h b/aegisub/lavc_file.h index 21e8ab8ca..155421943 100644 --- a/aegisub/lavc_file.h +++ b/aegisub/lavc_file.h @@ -41,8 +41,8 @@ #endif #include extern "C" { -#include -#include +#include +#include } #include "include/aegisub/aegisub.h" diff --git a/aegisub/lavc_keyframes.cpp b/aegisub/lavc_keyframes.cpp index 48e8d4eda..e46142d6e 100644 --- a/aegisub/lavc_keyframes.cpp +++ b/aegisub/lavc_keyframes.cpp @@ -100,7 +100,7 @@ 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)); + framesData.push_back(LAVCFrameInfo(packet.dts, (packet.flags & PKT_FLAG_KEY) ? 1 : 0)); // Check if the packet contains a keyframe if (packet.flags & PKT_FLAG_KEY) @@ -134,7 +134,7 @@ int LAVCKeyFrames::GetNumFrames() { } // returns the frame metadata vector -FrameInfoVector LAVCKeyFrames::GetFrameData() { +LAVCFrameInfoVector LAVCKeyFrames::GetFrameData() { return framesData; } diff --git a/aegisub/lavc_keyframes.h b/aegisub/lavc_keyframes.h index ddde41740..460dc68a0 100644 --- a/aegisub/lavc_keyframes.h +++ b/aegisub/lavc_keyframes.h @@ -39,13 +39,13 @@ #include "lavc_file.h" #include -struct FrameInfo { +struct LAVCFrameInfo { int64_t DTS; bool isKeyFrame; - FrameInfo(int64_t ADTS, bool isAKeyFrame) : DTS(ADTS), isKeyFrame(isAKeyFrame) {}; + LAVCFrameInfo(int64_t ADTS, bool isAKeyFrame) : DTS(ADTS), isKeyFrame(isAKeyFrame) {}; }; -typedef std::vector FrameInfoVector; +typedef std::vector LAVCFrameInfoVector; class LAVCKeyFrames { private: @@ -54,11 +54,11 @@ class LAVCKeyFrames { int streamN; // Stream index int numFrames; // number of frames in the video protected: - FrameInfoVector framesData; + LAVCFrameInfoVector framesData; public: LAVCKeyFrames(const Aegisub::String filename); ~LAVCKeyFrames(); wxArrayInt GetKeyFrames(); int GetNumFrames(); - FrameInfoVector GetFrameData(); + LAVCFrameInfoVector GetFrameData(); }; diff --git a/aegisub/video_provider_ffmpegsource.cpp b/aegisub/video_provider_ffmpegsource.cpp new file mode 100644 index 000000000..813b8c3e1 --- /dev/null +++ b/aegisub/video_provider_ffmpegsource.cpp @@ -0,0 +1,259 @@ +// Copyright (c) 2008, Karl Blomster +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------------- +// +// AEGISUB +// +// Website: http://aegisub.cellosoft.com +// Contact: mailto:zeratul@cellosoft.com +// + +#ifdef WITH_FFMPEGSOURCE + +#ifdef WIN32 +#define EMULATE_INTTYPES +#endif /* WIN32 */ + +/////////// +// Headers +#include +#include "video_provider_ffmpegsource.h" +#include +#include "vfr.h" +#include "video_context.h" +// #include "options.h" // for later use + + +/////////////// +// Constructor +FFmpegSourceVideoProvider::FFmpegSourceVideoProvider(Aegisub::String filename, double fps) { + // initialize ffmpegsource + FFMS_Init(); + + // clean up variables + VideoSource = NULL; + SWSContext = NULL; + BufferRGB = NULL; + KeyFramesLoaded = false; + FrameAllocated = false; + FrameNumber = -1; + MessageSize = sizeof(FFMSErrorMessage); + + // and here we go + LoadVideo(filename, fps); +} + +/////////////// +// Destructor +FFmpegSourceVideoProvider::~FFmpegSourceVideoProvider() { + Close(); +} + +/////////////// +// Open video +void FFmpegSourceVideoProvider::LoadVideo(Aegisub::String filename, double fps) { + // make sure we don't have anything messy lying around + Close(); + + wxString FileNameWX(filename.c_str(), wxConvUTF8); + + // generate a name for the cache file + wxString CacheName(filename.c_str()); + CacheName.append(_T(".ffindex")); + + // initialize index object + FrameIndex *Index = FFMS_CreateFrameIndex(); + // try to read index (these index functions all return 0 on success) + if (FFMS_ReadIndex(CacheName.char_str(), Index, FFMSErrorMessage, MessageSize)) { + // reading failed, create index + if (FFMS_MakeIndex(FileNameWX.char_str(), Index, 0, NULL, NULL, FFMSErrorMessage, MessageSize)) { + ErrorMsg.Printf(_T("FFmpegSource video provider: %s"), FFMSErrorMessage); + throw ErrorMsg; + } + // write it to disk + if (FFMS_WriteIndex(CacheName.char_str(), Index, FFMSErrorMessage, MessageSize)) { + ErrorMsg.Printf(_T("FFmpegSource video provider: %s"), FFMSErrorMessage); + throw ErrorMsg; + } + } + + // TODO: make this user-configurable + int Threads = 1; + if (Threads < 1) + throw _T("FFmpegSource video provider: invalid decoding thread count"); + + // TODO: tie this to the option "ffmpeg allow unsafe seeking" + int SeekMode = 1; + + // finally create the actual video source + VideoSource = FFMS_CreateVideoSource(FileNameWX.char_str(), -1, Index, "", Threads, SeekMode, FFMSErrorMessage, MessageSize); + if (VideoSource == NULL) { + ErrorMsg.Printf(_T("FFmpegSource video provider: %s"), FFMSErrorMessage); + throw ErrorMsg; + } + + // load video properties + VideoInfo = FFMS_GetVideoProperties(VideoSource); + + // get frame info data + // disabled until Myrsloik answers some questions +#if 0 + FrameInfoVector FrameData = FFMS_GetTITrackIndex(Index, -1, FFMSErrorMessage, MessageSize); + if (FrameData == NULL) { + ErrorMsg.Printf(_T("FFmpegSource video provider: %s"), FFMSErrorMessage); + throw ErrorMsg; + } + FrameInfo CurFrameData; + + // build list of keyframes + for (int CurFrameNum = 0; CurFrameNum < VideoInfo->NumFrames; CurFrameNum++) { + CurFrameData = FFMS_GetFrameInfo(FrameData, CurFrameNum, FFMSErrorMessage, MessageSize); + if (CurFrameData == NULL) { + ErrorMsg.Printf(_T("FFmpegSource video provider: %s"), FFMSErrorMessage); + throw ErrorMsg; + } + if (CurFrameData.KeyFrame) + KeyFramesList.Add(CurFrameNum); + } + KeyFramesLoaded = true; +#endif + + // TODO: VFR handling + + // we don't need this anymore + FFMS_DestroyFrameIndex(Index); + + FrameNumber = 0; +} + +/////////////// +// Close video +void FFmpegSourceVideoProvider::Close() { + if (SWSContext) + sws_freeContext(SWSContext); + SWSContext = NULL; + if (VideoSource) + FFMS_DestroyVideoSource(VideoSource); + VideoSource = NULL; + if (FrameAllocated) + avpicture_free(&FrameRGB); + FrameAllocated = false; + if (BufferRGB) + delete BufferRGB; + + KeyFramesLoaded = false; + KeyFramesList.clear(); + FrameNumber = -1; +} + +/////////////// +// Get frame +const AegiVideoFrame FFmpegSourceVideoProvider::GetFrame(int n, int FormatType) { + const AVFrameLite *SrcFrame = FFMS_GetFrame(VideoSource, n, FFMSErrorMessage, MessageSize); + if (SrcFrame == NULL) { + ErrorMsg.Printf(_T("FFmpegSource video provider: %s"), FFMSErrorMessage); + throw ErrorMsg; + } + + // set position + FrameNumber = n; + + AVPicture *SrcPicture = reinterpret_cast(const_cast(SrcFrame)); + + // prepare stuff for conversion to RGB32 + int w = VideoInfo->Width; + int h = VideoInfo->Height; + PixelFormat DstFormat; + + switch (FormatType) { + case FORMAT_RGB32: DstFormat = PIX_FMT_RGB32; break; + case FORMAT_RGB24: DstFormat = PIX_FMT_RGB24; break; + // TODO: add YV12? + default: throw _T("FFmpegSource video provider: upstream provider requested unknown or unsupported pixel format"); + } + + if (!SWSContext) { + if (avpicture_alloc(&FrameRGB, DstFormat, w, h) != 0) + throw _T("FFmpegSource video provider: could not allocate output picture buffer"); + FrameAllocated = true; + unsigned int DstSize = avpicture_get_size(DstFormat,w,h); + BufferRGB = new uint8_t[DstSize]; + + // initialize swscaler context + SWSContext = sws_getContext(w, h, VideoInfo->PixelFormat, w, h, DstFormat, SWS_BICUBIC, NULL, NULL, NULL); + if (SWSContext == NULL) + throw _T("FFmpegSource video provider: failed to initialize SWScale colorspace conversion"); + } + avpicture_fill(&FrameRGB, BufferRGB, DstFormat, w, h); + + // this is what we'll return eventually + AegiVideoFrame &DstFrame = CurFrame; + + // set some properties + DstFrame.w = w; + DstFrame.h = h; + // only rgb supported for now + DstFrame.flipped = false; + DstFrame.invertChannels = true; + DstFrame.format = (VideoFrameFormat)FormatType; + + // allocate destination frame + for (int i=0;i<4;i++) DstFrame.pitch[i] = FrameRGB.linesize[i]; + DstFrame.Allocate(); + + // let swscale do the conversion to RGB and write directly to the output frame + sws_scale(SWSContext, SrcPicture->data, SrcPicture->linesize, 0, h, DstFrame.data, FrameRGB.linesize); + + return DstFrame; +} + + +/////////////// +// Utility functions +int FFmpegSourceVideoProvider::GetWidth() { + return VideoInfo->Width; +} + +int FFmpegSourceVideoProvider::GetHeight() { + return VideoInfo->Height; +} + +int FFmpegSourceVideoProvider::GetFrameCount() { + return VideoInfo->NumFrames; +} + +int FFmpegSourceVideoProvider::GetPosition() { + return FrameNumber; +} + +double FFmpegSourceVideoProvider::GetFPS() { + return double(VideoInfo->FPSNumerator) / double(VideoInfo->FPSDenominator); +} + + +#endif /* WITH_FFMPEGSOURCE */ \ No newline at end of file diff --git a/aegisub/video_provider_ffmpegsource.h b/aegisub/video_provider_ffmpegsource.h new file mode 100644 index 000000000..6c55045cb --- /dev/null +++ b/aegisub/video_provider_ffmpegsource.h @@ -0,0 +1,109 @@ +// Copyright (c) 2008, Karl Blomster +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------------- +// +// AEGISUB +// +// Website: http://aegisub.cellosoft.com +// Contact: mailto:zeratul@cellosoft.com +// + +/////////// +// Headers +#include +#ifdef WITH_FFMPEGSOURCE +#ifdef WIN32 +#define EMULATE_INTTYPES +#endif /* WIN32 */ +#include "include/aegisub/video_provider.h" +#include "include/aegisub/aegisub.h" +extern "C" { +#include +#include +#include +} +#include +#include + + + +/////////////////////// +// FFmpegSource provider +class FFmpegSourceVideoProvider : public VideoProvider { +private: + VideoBase *VideoSource; + const VideoProperties *VideoInfo; + SwsContext *SWSContext; + + int FrameNumber; + wxArrayInt KeyFramesList; + bool KeyFramesLoaded; + + AVPicture FrameRGB; + bool FrameAllocated; + uint8_t *BufferRGB; + AegiVideoFrame CurFrame; + + char FFMSErrorMessage[1024]; + unsigned MessageSize; + wxString ErrorMsg; + + void LoadVideo(Aegisub::String filename, double fps); + void Close(); + +protected: + +public: + FFmpegSourceVideoProvider(Aegisub::String filename, double fps); + ~FFmpegSourceVideoProvider(); + + const AegiVideoFrame GetFrame(int n, int formatType); + int GetPosition(); + int GetFrameCount(); + + int GetWidth(); + int GetHeight(); + double GetFPS(); + bool AreKeyFramesLoaded() { return KeyFramesLoaded; }; + wxArrayInt GetKeyFrames() { return KeyFramesList; }; + bool IsVFR() { return false; }; // FIXME: might want to talk to Myrsloik about this + FrameRate GetTrueFrameRate() { return FrameRate(); }; // FIXME: try to remember what this actually means + Aegisub::String GetDecoderName() { return L"FFmpegSource"; } + bool IsNativelyByFrames() { return true; } + int GetDesiredCacheSize() { return 8; } +}; + + +/////////// +// Factory +class FFmpegSourceVideoProviderFactory : public VideoProviderFactory { +public: + VideoProvider *CreateProvider(Aegisub::String video,double fps=0.0) { return new FFmpegSourceVideoProvider(video,fps); } +}; + +#endif /* WITH_FFMPEGSOURCE */ \ No newline at end of file diff --git a/aegisub/video_provider_lavc.cpp b/aegisub/video_provider_lavc.cpp index 567cbed56..cdfce26d7 100644 --- a/aegisub/video_provider_lavc.cpp +++ b/aegisub/video_provider_lavc.cpp @@ -425,6 +425,7 @@ const AegiVideoFrame LAVCVideoProvider::GetFrame(int n,int formatType) { bufferRGB = new uint8_t[dstSize]; sws_context = sws_getContext(w, h, srcFormat, w, h, dstFormat, SWS_PRINT_INFO | SWS_BICUBIC, NULL, NULL, NULL); + // sws_getContext() always returns NULL if context creation failed if (sws_context == NULL) throw _T("ffmpeg video provider: failed to initialize SwScaler colorspace conversion"); } diff --git a/aegisub/video_provider_lavc.h b/aegisub/video_provider_lavc.h index e9b393b29..fd6f7369a 100644 --- a/aegisub/video_provider_lavc.h +++ b/aegisub/video_provider_lavc.h @@ -44,9 +44,9 @@ #endif #include extern "C" { -#include -#include -#include +#include +#include +#include } #include "include/aegisub/video_provider.h" #include "include/aegisub/aegisub.h" @@ -90,7 +90,7 @@ private: int length; AegiVideoFrame curFrame; bool validFrame; - FrameInfoVector framesData; + LAVCFrameInfoVector framesData; uint8_t *buffer1; uint8_t *buffer2; diff --git a/aegisub/video_provider_manager.cpp b/aegisub/video_provider_manager.cpp index 2125ddb81..34fbf2008 100644 --- a/aegisub/video_provider_manager.cpp +++ b/aegisub/video_provider_manager.cpp @@ -49,6 +49,9 @@ #ifdef WITH_FFMPEG #include "video_provider_lavc.h" #endif +#ifdef WITH_FFMPEGSOURCE +#include "video_provider_ffmpegsource.h" +#endif #include "video_provider_dummy.h" #include "video_provider_cache.h" @@ -103,6 +106,9 @@ void VideoProviderFactoryManager::RegisterProviders() { #ifdef WITH_FFMPEG RegisterFactory(new LAVCVideoProviderFactory(),_T("FFMPEG")); #endif +#ifdef WITH_FFMPEGSOURCE + RegisterFactory(new FFmpegSourceVideoProviderFactory(),_T("FFmpegSource")); +#endif } diff --git a/aegisub/win32/config0.h b/aegisub/win32/config0.h index 3f32af01c..0ec2a20fb 100644 --- a/aegisub/win32/config0.h +++ b/aegisub/win32/config0.h @@ -142,6 +142,11 @@ // #define WITH_STATIC_FFMPEG +// Enable ffmpegsource video and audio providers +// Requires: ffmpegsource version 2 +#define WITH_FFMPEGSOURCE + + // Enable Ruby support for Automation // Requires: Ruby 1.9 //#define WITH_RUBY diff --git a/aegisub/win64/config0.h b/aegisub/win64/config0.h index 55922db43..84e8845d0 100644 --- a/aegisub/win64/config0.h +++ b/aegisub/win64/config0.h @@ -138,6 +138,11 @@ // #define WITH_STATIC_FFMPEG +// Enable ffmpegsource video and audio providers +// Requires: ffmpegsource version 2 +// #define WITH_FFMPEGSOURCE + + // Enable Ruby support for Automation // Requires: Ruby 1.9 //#define WITH_RUBY