2008-09-03 19:03:20 +02:00
// 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
///////////
// Headers
# 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"
2008-09-03 19:03:20 +02:00
///////////////
// Constructor
FFmpegSourceVideoProvider : : FFmpegSourceVideoProvider ( Aegisub : : String filename , double fps ) {
// initialize ffmpegsource
FFMS_Init ( ) ;
// clean up variables
VideoSource = NULL ;
2008-09-23 22:01:11 +02:00
Index = NULL ;
2008-09-10 23:05:54 +02:00
DstFormat = FFMS_PIX_FMT_NONE ;
LastDstFormat = FFMS_PIX_FMT_NONE ;
2008-09-03 19:03:20 +02:00
KeyFramesLoaded = false ;
FrameNumber = - 1 ;
MessageSize = sizeof ( FFMSErrorMessage ) ;
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 ( ) ;
}
///////////////
// Open video
void FFmpegSourceVideoProvider : : LoadVideo ( Aegisub : : String filename , double fps ) {
// make sure we don't have anything messy lying around
Close ( ) ;
2008-10-28 05:39:10 +01:00
wxString FileNameWX = wxFileName ( wxString ( filename . c_str ( ) , wxConvFile ) ) . GetShortPath ( ) ;
2008-09-03 19:03:20 +02:00
// generate a name for the cache file
2008-10-28 05:39:10 +01:00
wxString CacheName = GetCacheFilename ( filename ) ;
2008-09-03 19:03:20 +02:00
2008-09-05 00:17:34 +02:00
// try to read index
Index = FFMS_ReadIndex ( CacheName . char_str ( ) , FFMSErrorMessage , MessageSize ) ;
if ( Index = = NULL ) {
2008-09-24 01:30:27 +02:00
// index didn't exist or was invalid, we'll have to (re)create it
try {
2008-10-28 05:39:10 +01:00
try {
// ignore audio decoding errors here, we don't care right now
Index = DoIndexing ( Index , FileNameWX , CacheName , FFMSTrackMaskAll , true ) ;
} catch ( . . . ) {
// Try without audio
Index = DoIndexing ( Index , FileNameWX , CacheName , FFMSTrackMaskNone , true ) ;
}
2008-09-24 01:30:27 +02:00
} catch ( wxString temp ) {
ErrorMsg < < 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
}
}
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 " ) ) )
SeekMode = 2 ;
else
SeekMode = 1 ;
2008-09-03 19:03:20 +02:00
2008-09-28 02:40:37 +02:00
// FIXME: provide a way to choose which audio track to load?
int TrackNumber = FFMS_GetFirstTrackOfType ( Index , FFMS_TYPE_VIDEO , FFMSErrorMessage , MessageSize ) ;
if ( TrackNumber < 0 ) {
wxString temp ( FFMSErrorMessage , wxConvUTF8 ) ;
2008-10-02 00:08:28 +02:00
ErrorMsg < < _T ( " Couldn't find any video tracks: " ) < < temp ;
2008-09-28 02:40:37 +02:00
throw ErrorMsg ;
}
VideoSource = FFMS_CreateVideoSource ( FileNameWX . char_str ( ) , TrackNumber , Index , " " , Threads , SeekMode , FFMSErrorMessage , MessageSize ) ;
2008-09-03 19:03:20 +02:00
if ( VideoSource = = NULL ) {
2008-09-23 23:06:11 +02:00
wxString temp ( FFMSErrorMessage , wxConvUTF8 ) ;
2008-10-02 00:08:28 +02:00
ErrorMsg < < _T ( " Failed to open video track: " ) < < temp ;
2008-09-03 19:03:20 +02:00
throw ErrorMsg ;
}
// load video properties
VideoInfo = FFMS_GetVideoProperties ( VideoSource ) ;
// get frame info data
2008-10-02 00:08:28 +02:00
FrameInfoVector * FrameData = FFMS_GetVSTrackIndex ( VideoSource ) ;
if ( FrameData = = NULL )
throw _T ( " FFmpegSource video provider: failed to get frame data " ) ;
const TrackTimeBase * TimeBase = FFMS_GetTimeBase ( FrameData ) ;
if ( TimeBase = = NULL )
throw _T ( " FFmpegSource video provider: failed to get track time base " ) ;
2008-09-03 22:27:50 +02:00
const FrameInfo * 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 + + ) {
CurFrameData = FFMS_GetFrameInfo ( FrameData , CurFrameNum , FFMSErrorMessage , MessageSize ) ;
if ( CurFrameData = = NULL ) {
2008-09-23 23:06:11 +02:00
wxString temp ( FFMSErrorMessage , wxConvUTF8 ) ;
2008-10-02 00:08:28 +02:00
ErrorMsg < < _T ( " Couldn't get framedata for frame " ) < < CurFrameNum < < _T ( " : " ) < < temp ;
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
int64_t Timestamp = ( int64_t ) ( ( CurFrameData - > DTS * TimeBase - > Num ) / ( double ) 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 ( ) {
if ( VideoSource )
FFMS_DestroyVideoSource ( VideoSource ) ;
VideoSource = NULL ;
2008-09-23 22:01:11 +02:00
if ( Index )
FFMS_DestroyFrameIndex ( Index ) ;
2008-09-03 19:03:20 +02:00
2008-09-10 23:05:54 +02:00
DstFormat = FFMS_PIX_FMT_NONE ;
LastDstFormat = FFMS_PIX_FMT_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 ;
}
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-11-26 03:24:37 +01:00
bool big_endian = Endian : : BigToMachine ( ( unsigned int ) 1 ) = = ( unsigned int ) 1 ;
2008-09-10 23:05:54 +02:00
// choose output format
2008-11-26 03:03:53 +01:00
if ( FormatType & FORMAT_RGB32 & & big_endian ) {
DstFormat = FFMS_PIX_FMT_BGR32_1 ;
DstFrame . format = FORMAT_RGB32 ;
} else if ( FormatType & FORMAT_RGB32 & & ! big_endian ) {
2008-09-11 00:01:35 +02:00
DstFormat = FFMS_PIX_FMT_RGB32 ;
2008-09-10 23:05:54 +02:00
DstFrame . format = FORMAT_RGB32 ;
} else if ( FormatType & FORMAT_RGB24 ) {
2008-11-26 03:03:53 +01:00
DstFormat = FFMS_PIX_FMT_BGR24 ;
2008-09-10 23:05:54 +02:00
DstFrame . format = FORMAT_RGB24 ;
} else if ( FormatType & FORMAT_YV12 ) {
DstFormat = FFMS_PIX_FMT_YUV420P ; // may or may not work
DstFrame . format = FORMAT_YV12 ;
} else if ( FormatType & FORMAT_YUY2 ) {
DstFormat = FFMS_PIX_FMT_YUYV422 ;
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 ) {
2008-10-02 00:08:28 +02:00
if ( FFMS_SetOutputFormat ( VideoSource , DstFormat , w , h , FFMSErrorMessage , MessageSize ) ) {
wxString temp ( FFMSErrorMessage , wxConvUTF8 ) ;
ErrorMsg < < _T ( " Failed to set output format: " ) < < temp ;
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
2008-09-03 19:03:20 +02:00
const AVFrameLite * SrcFrame = FFMS_GetFrame ( VideoSource , n , FFMSErrorMessage , MessageSize ) ;
if ( SrcFrame = = NULL ) {
2008-09-23 23:06:11 +02:00
wxString temp ( FFMSErrorMessage , wxConvUTF8 ) ;
2008-10-02 00:08:28 +02:00
ErrorMsg < < _T ( " Failed to retrieve frame: " ) < < temp ;
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 ( ) ;
2008-09-10 23:05:54 +02:00
// copy data to destination, skipping planes with no data in them
for ( int j = 0 ; j < 4 ; j + + ) {
if ( SrcFrame - > Linesize [ j ] > 0 )
memcpy ( DstFrame . data [ j ] , SrcFrame - > Data [ j ] , DstFrame . pitch [ j ] * DstFrame . h ) ;
}
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 */