2009-07-16 16:48:47 +02:00
// Copyright (c) 2008-2009, Karl Blomster
2008-09-03 19:03:20 +02:00
// 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
//
2009-01-04 07:31:48 +01:00
# include "config.h"
2008-09-03 19:03:20 +02:00
# ifdef WITH_FFMPEGSOURCE
///////////
// Headers
2009-05-03 20:05:30 +02:00
# include <wx/utils.h>
2009-07-16 16:48:47 +02:00
# include <wx/choicdlg.h>
# include <map>
2008-12-28 04:07:40 +01:00
# include "include/aegisub/aegisub.h"
2008-09-03 19:03:20 +02:00
# include "video_provider_ffmpegsource.h"
# include "video_context.h"
2008-09-05 00:17:34 +02:00
# include "options.h"
2008-11-26 03:03:53 +01:00
# include "aegisub_endian.h"
2009-05-25 17:52:42 +02:00
# ifdef WIN32
# include <objbase.h>
# endif
2008-09-03 19:03:20 +02:00
///////////////
// Constructor
FFmpegSourceVideoProvider : : FFmpegSourceVideoProvider ( Aegisub : : String filename , double fps ) {
2009-05-25 18:42:33 +02:00
COMInited = false ;
2009-05-25 17:52:42 +02:00
# ifdef WIN32
2009-05-25 18:42:33 +02:00
HRESULT res ;
res = CoInitializeEx ( NULL , COINIT_APARTMENTTHREADED ) ;
if ( SUCCEEDED ( res ) )
COMInited = true ;
else if ( res ! = RPC_E_CHANGED_MODE )
2009-05-25 17:52:42 +02:00
throw _T ( " FFmpegSource video provider: COM initialization failure " ) ;
# endif
// initialize ffmpegsource
2009-07-14 00:30:48 +02:00
// FIXME: CPU detection?
FFMS_Init ( 0 ) ;
2008-09-03 19:03:20 +02:00
// clean up variables
VideoSource = NULL ;
2009-04-25 12:31:39 +02:00
DstFormat = FFMS_GetPixFmt ( " none " ) ;
LastDstFormat = FFMS_GetPixFmt ( " none " ) ;
2008-09-03 19:03:20 +02:00
KeyFramesLoaded = false ;
FrameNumber = - 1 ;
2009-07-16 16:48:47 +02:00
MsgSize = sizeof ( FFMSErrMsg ) ;
2008-09-23 23:06:11 +02:00
ErrorMsg = _T ( " FFmpegSource video provider: " ) ;
2008-09-03 19:03:20 +02:00
// and here we go
2008-09-23 03:19:31 +02:00
try {
LoadVideo ( filename , fps ) ;
} catch ( . . . ) {
Close ( ) ;
2008-10-28 05:24:45 +01:00
throw ;
2008-09-23 03:19:31 +02:00
}
2008-09-03 19:03:20 +02:00
}
///////////////
// Destructor
FFmpegSourceVideoProvider : : ~ FFmpegSourceVideoProvider ( ) {
Close ( ) ;
2009-05-25 17:52:42 +02:00
# ifdef WIN32
2009-05-25 18:42:33 +02:00
if ( COMInited )
CoUninitialize ( ) ;
2009-05-25 17:52:42 +02:00
# endif
2008-09-03 19:03:20 +02:00
}
///////////////
// Open video
void FFmpegSourceVideoProvider : : LoadVideo ( Aegisub : : String filename , double fps ) {
// make sure we don't have anything messy lying around
Close ( ) ;
2009-07-14 00:30:48 +02:00
wxString FileNameWX = wxFileName ( wxString ( filename . c_str ( ) , wxConvFile ) ) . GetShortPath ( ) ;
2008-09-03 19:03:20 +02:00
2009-07-16 16:48:47 +02:00
FFIndexer * Indexer = FFMS_CreateIndexer ( FileNameWX . mb_str ( wxConvUTF8 ) , FFMSErrMsg , MsgSize ) ;
if ( Indexer = = NULL ) {
// error messages that can possibly contain a filename use this method instead of
// wxString::Format because they may contain utf8 characters
ErrorMsg . Append ( _T ( " Failed to create indexer: " ) ) . Append ( wxString ( FFMSErrMsg , wxConvUTF8 ) ) ;
throw ErrorMsg ;
}
std : : map < int , wxString > TrackList = GetTracksOfType ( Indexer , FFMS_TYPE_VIDEO ) ;
if ( TrackList . size ( ) < = 0 )
throw _T ( " FFmpegSource video provider: no video tracks found " ) ;
// initialize the track number to an invalid value so we can detect later on
// whether the user actually had to choose a track or not
int TrackNumber = - 1 ;
if ( TrackList . size ( ) > 1 ) {
TrackNumber = AskForTrackSelection ( TrackList , FFMS_TYPE_VIDEO ) ;
// if it's still -1 here, user pressed cancel
if ( TrackNumber = = - 1 )
throw _T ( " FFmpegSource video provider: video loading cancelled by user " ) ;
}
2008-09-03 19:03:20 +02:00
// generate a name for the cache file
2008-12-28 04:07:40 +01:00
wxString CacheName = GetCacheFilename ( filename . c_str ( ) ) ;
2008-09-03 19:03:20 +02:00
2008-09-05 00:17:34 +02:00
// try to read index
2009-07-14 00:30:48 +02:00
FFIndex * Index = NULL ;
2009-07-16 16:48:47 +02:00
Index = FFMS_ReadIndex ( CacheName . mb_str ( wxConvUTF8 ) , FFMSErrMsg , MsgSize ) ;
bool IndexIsValid = false ;
if ( Index ! = NULL ) {
if ( FFMS_IndexBelongsToFile ( Index , FileNameWX . mb_str ( wxConvUTF8 ) , FFMSErrMsg , MsgSize ) ) {
FFMS_DestroyIndex ( Index ) ;
Index = NULL ;
}
else
IndexIsValid = true ;
2009-07-14 00:30:48 +02:00
}
2009-07-16 16:48:47 +02:00
// time to examine the index and check if the track we want is indexed
// technically this isn't really needed since all video tracks should always be indexed,
// but a bit of sanity checking never hurt anyone
if ( IndexIsValid & & TrackNumber > = 0 ) {
FFTrack * TempTrackData = FFMS_GetTrackFromIndex ( Index , TrackNumber ) ;
if ( FFMS_GetNumFrames ( TempTrackData ) < = 0 ) {
IndexIsValid = false ;
FFMS_DestroyIndex ( Index ) ;
Index = NULL ;
}
}
// moment of truth
if ( ! IndexIsValid ) {
int TrackMask = Options . AsBool ( _T ( " FFmpegSource always index all tracks " ) ) ? FFMSTrackMaskAll : FFMSTrackMaskNone ;
2008-09-24 01:30:27 +02:00
try {
2008-10-28 05:39:10 +01:00
try {
// ignore audio decoding errors here, we don't care right now
2009-07-16 16:48:47 +02:00
Index = DoIndexing ( Indexer , CacheName , TrackMask , true ) ;
2008-10-28 05:39:10 +01:00
} catch ( . . . ) {
2009-07-16 16:48:47 +02:00
// something borked, try if it works without audio
Index = DoIndexing ( Indexer , CacheName , FFMSTrackMaskNone , true ) ;
2008-10-28 05:39:10 +01:00
}
2008-09-24 01:30:27 +02:00
} catch ( wxString temp ) {
2009-07-16 16:48:47 +02:00
ErrorMsg . Append ( temp ) ;
2008-09-03 19:03:20 +02:00
throw ErrorMsg ;
2008-09-24 01:30:27 +02:00
} catch ( . . . ) {
throw ;
2008-09-03 19:03:20 +02:00
}
}
2009-07-16 16:48:47 +02:00
2009-05-03 20:05:30 +02:00
// update access time of index file so it won't get cleaned away
wxFileName ( CacheName ) . Touch ( ) ;
// we have now read the index and may proceed with cleaning the index cache
if ( ! CleanCache ( ) ) {
//do something?
}
2009-07-16 16:48:47 +02:00
// track number still not set?
if ( TrackNumber < 0 ) {
// just grab the first track
TrackNumber = FFMS_GetFirstIndexedTrackOfType ( Index , FFMS_TYPE_VIDEO , FFMSErrMsg , MsgSize ) ;
if ( TrackNumber < 0 ) {
FFMS_DestroyIndex ( Index ) ;
Index = NULL ;
ErrorMsg . Append ( wxString : : Format ( _T ( " Couldn't find any video tracks: %s " ) , FFMSErrMsg ) ) ;
throw ErrorMsg ;
}
}
2008-09-03 23:22:33 +02:00
// set thread count
int Threads = Options . AsInt ( _T ( " FFmpegSource decoding threads " ) ) ;
2008-09-03 19:03:20 +02:00
if ( Threads < 1 )
throw _T ( " FFmpegSource video provider: invalid decoding thread count " ) ;
2008-09-03 23:22:33 +02:00
// set seekmode
// TODO: give this its own option?
int SeekMode ;
if ( Options . AsBool ( _T ( " FFmpeg allow unsafe seeking " ) ) )
2009-04-26 01:08:45 +02:00
SeekMode = FFMS_SEEK_UNSAFE ;
2008-09-03 23:22:33 +02:00
else
2009-04-26 01:08:45 +02:00
SeekMode = FFMS_SEEK_NORMAL ;
2008-09-03 19:03:20 +02:00
2009-07-16 16:48:47 +02:00
VideoSource = FFMS_CreateVideoSource ( FileNameWX . mb_str ( wxConvUTF8 ) , TrackNumber , Index , " " , Threads , SeekMode , FFMSErrMsg , MsgSize ) ;
2009-05-28 21:34:52 +02:00
FFMS_DestroyIndex ( Index ) ;
2009-04-29 19:40:02 +02:00
Index = NULL ;
2008-09-03 19:03:20 +02:00
if ( VideoSource = = NULL ) {
2009-07-16 16:48:47 +02:00
ErrorMsg . Append ( wxString : : Format ( _T ( " Failed to open video track: %s " ) , FFMSErrMsg ) ) ;
2008-09-03 19:03:20 +02:00
throw ErrorMsg ;
}
// load video properties
2009-05-18 00:12:46 +02:00
VideoInfo = FFMS_GetVideoProperties ( VideoSource ) ;
2008-09-03 19:03:20 +02:00
// get frame info data
2009-05-18 00:12:46 +02:00
FFTrack * FrameData = FFMS_GetTrackFromVideo ( VideoSource ) ;
2008-10-02 00:08:28 +02:00
if ( FrameData = = NULL )
throw _T ( " FFmpegSource video provider: failed to get frame data " ) ;
2009-07-14 00:30:48 +02:00
const FFTrackTimeBase * TimeBase = FFMS_GetTimeBase ( FrameData ) ;
2008-10-02 00:08:28 +02:00
if ( TimeBase = = NULL )
throw _T ( " FFmpegSource video provider: failed to get track time base " ) ;
2008-09-03 22:27:50 +02:00
2009-05-20 20:57:03 +02:00
const FFFrameInfo * CurFrameData ;
2008-09-03 19:03:20 +02:00
2008-09-03 22:27:50 +02:00
// build list of keyframes and timecodes
2008-09-03 19:03:20 +02:00
for ( int CurFrameNum = 0 ; CurFrameNum < VideoInfo - > NumFrames ; CurFrameNum + + ) {
2009-05-23 16:18:51 +02:00
CurFrameData = FFMS_GetFrameInfo ( FrameData , CurFrameNum ) ;
2008-09-03 19:03:20 +02:00
if ( CurFrameData = = NULL ) {
2009-07-16 16:48:47 +02:00
ErrorMsg . Append ( wxString : : Format ( _T ( " Couldn't get info about frame %d " ) , CurFrameNum ) ) ;
2008-09-03 19:03:20 +02:00
throw ErrorMsg ;
}
2008-09-03 22:27:50 +02:00
// keyframe?
if ( CurFrameData - > KeyFrame )
2008-09-03 19:03:20 +02:00
KeyFramesList . Add ( CurFrameNum ) ;
2008-09-03 22:27:50 +02:00
// calculate timestamp and add to timecodes vector
2009-07-14 00:30:48 +02:00
int Timestamp = ( int ) ( ( CurFrameData - > DTS * TimeBase - > Num ) / TimeBase - > Den ) ;
2008-09-06 04:54:22 +02:00
TimecodesVector . push_back ( Timestamp ) ;
2008-09-03 19:03:20 +02:00
}
KeyFramesLoaded = true ;
2008-09-03 22:27:50 +02:00
// override already loaded timecodes?
Timecodes . SetVFR ( TimecodesVector ) ;
int OverrideTC = wxYES ;
if ( VFR_Output . IsLoaded ( ) ) {
OverrideTC = wxMessageBox ( _ ( " You already have timecodes loaded. Would you like to replace them with timecodes from the video file? " ) , _ ( " Replace timecodes? " ) , wxYES_NO | wxICON_QUESTION ) ;
if ( OverrideTC = = wxYES ) {
VFR_Input . SetVFR ( TimecodesVector ) ;
VFR_Output . SetVFR ( TimecodesVector ) ;
}
2008-09-03 23:03:18 +02:00
} else { // no timecodes loaded, go ahead and apply
VFR_Input . SetVFR ( TimecodesVector ) ;
VFR_Output . SetVFR ( TimecodesVector ) ;
2008-09-03 22:27:50 +02:00
}
2008-09-03 19:03:20 +02:00
FrameNumber = 0 ;
}
///////////////
// Close video
void FFmpegSourceVideoProvider : : Close ( ) {
2009-04-29 19:40:02 +02:00
FFMS_DestroyVideoSource ( VideoSource ) ;
2008-09-03 19:03:20 +02:00
VideoSource = NULL ;
2009-04-25 12:31:39 +02:00
DstFormat = FFMS_GetPixFmt ( " none " ) ;
LastDstFormat = FFMS_GetPixFmt ( " none " ) ;
2008-09-03 19:03:20 +02:00
KeyFramesLoaded = false ;
KeyFramesList . clear ( ) ;
2008-09-03 22:27:50 +02:00
TimecodesVector . clear ( ) ;
2008-09-03 19:03:20 +02:00
FrameNumber = - 1 ;
2009-05-10 02:12:04 +02:00
CurFrame . Clear ( ) ;
2008-09-03 19:03:20 +02:00
}
2008-09-05 00:17:34 +02:00
2008-09-03 19:03:20 +02:00
///////////////
// Get frame
2008-09-03 23:03:18 +02:00
const AegiVideoFrame FFmpegSourceVideoProvider : : GetFrame ( int _n , int FormatType ) {
// don't try to seek to insane places
int n = _n ;
if ( n < 0 )
n = 0 ;
if ( n > = GetFrameCount ( ) )
n = GetFrameCount ( ) - 1 ;
// set position
FrameNumber = n ;
2008-09-10 23:05:54 +02:00
// these are for convenience
int w = VideoInfo - > Width ;
int h = VideoInfo - > Height ;
// this is what we'll return eventually
AegiVideoFrame & DstFrame = CurFrame ;
2008-11-26 03:03:53 +01:00
2008-09-10 23:05:54 +02:00
// choose output format
2009-07-16 21:25:43 +02:00
if ( FormatType & FORMAT_RGB32 ) {
2009-04-25 12:31:39 +02:00
DstFormat = FFMS_GetPixFmt ( " bgra " ) ;
2008-09-10 23:05:54 +02:00
DstFrame . format = FORMAT_RGB32 ;
} else if ( FormatType & FORMAT_RGB24 ) {
2009-04-25 12:31:39 +02:00
DstFormat = FFMS_GetPixFmt ( " bgr24 " ) ;
2008-09-10 23:05:54 +02:00
DstFrame . format = FORMAT_RGB24 ;
} else if ( FormatType & FORMAT_YV12 ) {
2009-07-16 21:20:14 +02:00
DstFormat = FFMS_GetPixFmt ( " yuv420p " ) ;
2008-09-10 23:05:54 +02:00
DstFrame . format = FORMAT_YV12 ;
} else if ( FormatType & FORMAT_YUY2 ) {
2009-07-16 21:20:14 +02:00
DstFormat = FFMS_GetPixFmt ( " yuyv422 " ) ; // may or may not work
2008-09-10 23:05:54 +02:00
DstFrame . format = FORMAT_YUY2 ;
} else
throw _T ( " FFmpegSource video provider: upstream provider requested unknown or unsupported pixel format " ) ;
// requested format was changed since last time we were called, (re)set output format
if ( LastDstFormat ! = DstFormat ) {
2009-07-16 16:48:47 +02:00
if ( FFMS_SetOutputFormatV ( VideoSource , 1 < < DstFormat , w , h , FFMS_RESIZER_BICUBIC , FFMSErrMsg , MsgSize ) ) {
ErrorMsg . Append ( wxString : : Format ( _T ( " Failed to set output format: %s " ) , FFMSErrMsg ) ) ;
2008-10-02 00:08:28 +02:00
throw ErrorMsg ;
}
2008-09-10 23:05:54 +02:00
LastDstFormat = DstFormat ;
}
2008-09-03 23:03:18 +02:00
2008-09-10 23:05:54 +02:00
// decode frame
2009-07-16 16:48:47 +02:00
const FFAVFrame * SrcFrame = FFMS_GetFrame ( VideoSource , n , FFMSErrMsg , MsgSize ) ;
2008-09-03 19:03:20 +02:00
if ( SrcFrame = = NULL ) {
2009-07-16 16:48:47 +02:00
ErrorMsg . Append ( wxString : : Format ( _T ( " Failed to retrieve frame: %s " ) , FFMSErrMsg ) ) ;
2008-09-03 19:03:20 +02:00
throw ErrorMsg ;
}
// set some properties
DstFrame . w = w ;
DstFrame . h = h ;
DstFrame . flipped = false ;
2008-09-10 23:05:54 +02:00
if ( DstFrame . format = = FORMAT_RGB32 | | DstFrame . format = = FORMAT_RGB24 )
2008-09-03 22:27:50 +02:00
DstFrame . invertChannels = true ;
2008-09-10 23:05:54 +02:00
else
2008-09-03 22:27:50 +02:00
DstFrame . invertChannels = false ;
2008-09-03 19:03:20 +02:00
2008-09-10 23:05:54 +02:00
// allocate destination
for ( int i = 0 ; i < 4 ; i + + )
DstFrame . pitch [ i ] = SrcFrame - > Linesize [ i ] ;
2008-09-03 19:03:20 +02:00
DstFrame . Allocate ( ) ;
2009-07-16 21:20:14 +02:00
// copy data to destination
memcpy ( DstFrame . data [ 0 ] , SrcFrame - > Data [ 0 ] , DstFrame . pitch [ 0 ] * DstFrame . h ) ;
// if we're dealing with YUV formats we need to copy the U and V planes as well
if ( DstFrame . format = = FORMAT_YUY2 | | DstFrame . format = = FORMAT_YV12 ) {
// YV12 has half the vertical U/V resolution too because of the subsampling
int UVHeight = DstFrame . format = = FORMAT_YUY2 ? DstFrame . h : DstFrame . h / 2 ;
memcpy ( DstFrame . data [ 1 ] , SrcFrame - > Data [ 1 ] , DstFrame . pitch [ 1 ] * UVHeight ) ;
memcpy ( DstFrame . data [ 2 ] , SrcFrame - > Data [ 2 ] , DstFrame . pitch [ 2 ] * UVHeight ) ;
2008-09-10 23:05:54 +02:00
}
2008-09-03 19:03:20 +02:00
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 ) ;
}
2008-09-05 00:17:34 +02:00
# endif /* WITH_FFMPEGSOURCE */