2007-04-02 00:06:00 +02:00
// Copyright (c) 2006-2007, Rodrigo Braz Monteiro
2006-02-24 03:54:30 +01: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
//
///////////
// Headers
2009-01-04 07:31:48 +01:00
# include "config.h"
2008-01-16 19:29:29 +01:00
2007-12-31 07:46:22 +01:00
# ifdef WITH_FFMPEG
2007-04-22 02:23:21 +02:00
# ifdef WIN32
2007-04-02 00:06:00 +02:00
# define EMULATE_INTTYPES
2008-07-15 02:08:05 +02:00
# define __STDC_CONSTANT_MACROS 1
# include <stdint.h>
# endif /* WIN32 */
2006-02-24 03:54:30 +01:00
# include <wx/wxprec.h>
2006-05-06 03:44:51 +02:00
# include <wx/image.h>
2006-03-01 04:17:31 +01:00
# include <algorithm>
2008-07-15 02:08:05 +02:00
# include <vector>
2008-03-05 04:20:55 +01:00
# include "video_provider_lavc.h"
2007-04-02 00:06:00 +02:00
# include "mkv_wrap.h"
# include "lavc_file.h"
2006-02-24 06:58:45 +01:00
# include "utils.h"
# include "vfr.h"
2006-05-06 03:56:27 +02:00
# include "ass_file.h"
2008-07-15 02:08:05 +02:00
# include "lavc_keyframes.h"
# include "video_context.h"
2008-07-15 16:24:00 +02:00
# include "options.h"
2006-02-24 03:54:30 +01:00
///////////////
// Constructor
2008-03-07 22:27:36 +01:00
LAVCVideoProvider : : LAVCVideoProvider ( Aegisub : : String filename , double fps ) {
2006-02-24 04:54:40 +01:00
// Init variables
codecContext = NULL ;
2006-04-15 00:35:02 +02:00
lavcfile = NULL ;
2006-02-24 04:54:40 +01:00
codec = NULL ;
stream = NULL ;
2006-02-24 06:24:08 +01:00
frame = NULL ;
2008-01-20 22:15:22 +01:00
frameRGB = NULL ;
bufferRGB = NULL ;
sws_context = NULL ;
2006-02-24 08:27:42 +01:00
buffer1 = NULL ;
buffer2 = NULL ;
buffer1Size = 0 ;
buffer2Size = 0 ;
2006-02-24 04:54:40 +01:00
vidStream = - 1 ;
2006-02-24 08:27:42 +01:00
validFrame = false ;
2008-07-15 02:08:05 +02:00
framesData . clear ( ) ;
2006-02-24 04:54:40 +01:00
// Load
2007-03-24 01:11:49 +01:00
LoadVideo ( filename , fps ) ;
2006-02-24 03:54:30 +01:00
}
//////////////
// Destructor
LAVCVideoProvider : : ~ LAVCVideoProvider ( ) {
2006-02-24 04:54:40 +01:00
Close ( ) ;
2006-02-24 03:54:30 +01:00
}
2006-02-24 04:54:40 +01:00
//////////////
// Load video
2008-03-07 22:27:36 +01:00
void LAVCVideoProvider : : LoadVideo ( Aegisub : : String filename , double fps ) {
2006-02-24 04:54:40 +01:00
// Close first
Close ( ) ;
2006-04-15 00:35:02 +02:00
lavcfile = LAVCFile : : Create ( filename ) ;
2006-02-24 04:54:40 +01:00
// Load
try {
int result = 0 ;
// Find video stream
vidStream = - 1 ;
codecContext = NULL ;
2008-03-24 12:30:35 +01:00
for ( int i = 0 ; i < ( int ) lavcfile - > fctx - > nb_streams ; i + + ) {
2006-04-15 00:35:02 +02:00
codecContext = lavcfile - > fctx - > streams [ i ] - > codec ;
2006-02-24 04:54:40 +01:00
if ( codecContext - > codec_type = = CODEC_TYPE_VIDEO ) {
2006-04-15 00:35:02 +02:00
stream = lavcfile - > fctx - > streams [ i ] ;
2006-02-24 04:54:40 +01:00
vidStream = i ;
break ;
}
}
2008-08-14 18:06:04 +02:00
if ( vidStream = = - 1 ) throw _T ( " ffmpeg video provider: Could not find a video stream " ) ;
2006-02-24 04:54:40 +01:00
// Find codec
codec = avcodec_find_decoder ( codecContext - > codec_id ) ;
2008-08-14 18:06:04 +02:00
if ( ! codec ) throw _T ( " ffmpeg video provider: Could not find suitable video decoder " ) ;
2006-02-24 04:54:40 +01:00
// Enable truncation
2006-02-24 06:58:45 +01:00
//if (codec->capabilities & CODEC_CAP_TRUNCATED) codecContext->flags |= CODEC_FLAG_TRUNCATED;
2006-02-24 04:54:40 +01:00
// Open codec
result = avcodec_open ( codecContext , codec ) ;
2008-08-14 18:06:04 +02:00
if ( result < 0 ) throw _T ( " ffmpeg video provider: Failed to open video decoder " ) ;
2006-02-24 06:24:08 +01:00
2008-07-15 02:08:05 +02:00
// 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 ( ) ;
2006-02-25 05:54:21 +01:00
2009-06-24 19:43:36 +02:00
std : : vector < int > timecodesVector ;
for ( int i = 0 ; i < length ; i + + ) {
int timestamp = ( int ) ( ( framesData [ i ] . DTS * ( int64_t ) lavcfile - > fctx - > streams [ vidStream ] - > time_base . num * 1000 )
/ ( int64_t ) lavcfile - > fctx - > streams [ vidStream ] - > time_base . den ) ;
timecodesVector . push_back ( timestamp ) ;
}
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 ) ;
}
} else { // no timecodes loaded, go ahead and apply
VFR_Input . SetVFR ( timecodesVector ) ;
VFR_Output . SetVFR ( timecodesVector ) ;
}
2006-02-24 06:24:08 +01:00
// Allocate frame
frame = avcodec_alloc_frame ( ) ;
2006-02-24 06:58:45 +01:00
// Set frame
frameNumber = - 1 ;
2008-07-15 02:08:05 +02:00
lastFrameNumber = - 1 ;
2008-07-15 16:24:00 +02:00
allowUnsafeSeeking = Options . AsBool ( _T ( " FFmpeg allow unsafe seeking " ) ) ;
2006-02-24 04:54:40 +01:00
}
// Catch errors
catch ( . . . ) {
Close ( ) ;
throw ;
}
}
///////////////
// Close video
void LAVCVideoProvider : : Close ( ) {
2006-02-24 08:27:42 +01:00
// Clean buffers
if ( buffer1 ) delete buffer1 ;
if ( buffer2 ) delete buffer2 ;
buffer1 = NULL ;
buffer2 = NULL ;
buffer1Size = 0 ;
buffer2Size = 0 ;
2006-02-24 06:24:08 +01:00
// Clean frame
2007-08-08 00:10:44 +02:00
if ( frame ) av_free ( ( void * ) frame ) ;
2006-02-24 06:24:08 +01:00
frame = NULL ;
2008-01-20 22:15:22 +01:00
// Free SWS context and other stuff from RGB conversion
if ( sws_context )
sws_freeContext ( sws_context ) ;
sws_context = NULL ;
if ( frameRGB )
av_free ( frameRGB ) ;
frameRGB = NULL ;
if ( bufferRGB )
delete ( bufferRGB ) ;
bufferRGB = NULL ;
2006-02-24 04:54:40 +01:00
// Close codec context
2006-02-24 06:24:08 +01:00
if ( codec & & codecContext ) avcodec_close ( codecContext ) ;
2006-02-24 04:54:40 +01:00
codecContext = NULL ;
codec = NULL ;
// Close format context
2006-04-15 00:35:02 +02:00
if ( lavcfile )
lavcfile - > Release ( ) ;
lavcfile = NULL ;
2006-02-24 04:54:40 +01:00
}
2006-02-24 06:24:08 +01:00
//////////////////
// Get next frame
2008-07-15 02:08:05 +02:00
bool LAVCVideoProvider : : GetNextFrame ( int64_t * startDTS ) {
2006-02-24 06:58:45 +01:00
AVPacket packet ;
2008-07-15 02:08:05 +02:00
* startDTS = - 1 ; // magic
// Read packet
2006-04-15 00:35:02 +02:00
while ( av_read_frame ( lavcfile - > fctx , & packet ) > = 0 ) {
2006-02-24 06:58:45 +01:00
// Check if packet is part of video stream
if ( packet . stream_index = = vidStream ) {
// Decode frame
int frameFinished ;
2008-07-15 02:08:05 +02:00
if ( * startDTS < 0 )
* startDTS = packet . dts ;
2006-02-24 06:58:45 +01:00
avcodec_decode_video ( codecContext , frame , & frameFinished , packet . data , packet . size ) ;
// Success?
if ( frameFinished ) {
2006-02-24 07:34:35 +01:00
// Set time
lastDecodeTime = packet . dts ;
2008-07-15 02:08:05 +02:00
// Free packet and return
2006-02-24 06:58:45 +01:00
av_free_packet ( & packet ) ;
2006-02-25 03:27:40 +01:00
return true ;
2006-02-24 06:24:08 +01:00
}
}
2008-07-15 02:08:05 +02:00
// free packet
av_free_packet ( & packet ) ;
2006-02-24 06:58:45 +01:00
}
2006-02-25 03:27:40 +01:00
// No more packets
return false ;
2006-02-24 06:24:08 +01:00
}
2006-02-24 04:54:40 +01:00
/////////////
// Get frame
2008-03-06 20:20:25 +01:00
const AegiVideoFrame LAVCVideoProvider : : GetFrame ( int n , int formatType ) {
2006-02-24 06:58:45 +01:00
// Return stored frame
2008-07-15 02:08:05 +02:00
// n = MID(0,n,GetFrameCount()-1);
if ( n = = lastFrameNumber ) {
2007-03-24 03:07:06 +01:00
if ( ! validFrame ) validFrame = true ;
return curFrame ;
2006-02-24 08:27:42 +01:00
}
2006-02-24 06:58:45 +01:00
2008-07-15 02:08:05 +02:00
if ( frameNumber < 0 )
frameNumber = 0 ;
2008-07-15 16:35:22 +02:00
// Find closest keyframe to the frame we want
int closestKeyFrame = FindClosestKeyframe ( n ) ;
2006-02-25 21:48:32 +01:00
2008-07-15 16:35:22 +02:00
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 ) {
// turns out we did need it, just do it
av_seek_frame ( lavcfile - > fctx , vidStream , framesData [ closestKeyFrame ] . DTS , AVSEEK_FLAG_BACKWARD ) ;
avcodec_flush_buffers ( codecContext ) ;
hasSeeked = true ;
}
2008-07-15 02:08:05 +02:00
2008-07-15 16:35:22 +02:00
// regardless of whether we sekeed or not, 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 ) {
// guessing destination, may be unsafe
if ( allowUnsafeSeeking )
frameNumber = ClosestFrameFromDTS ( startTime ) ;
else
throw _T ( " ffmpeg video provider: frame accurate seeking failed " ) ;
}
2006-02-24 07:34:35 +01:00
2008-07-15 16:35:22 +02:00
}
2006-02-25 21:48:32 +01:00
2008-07-15 16:35:22 +02:00
frameNumber + + ;
} while ( frameNumber < = n ) ;
2008-01-20 22:15:22 +01:00
2007-03-31 05:23:46 +02:00
// Get aegisub frame
2007-03-28 00:56:08 +02:00
AegiVideoFrame & final = curFrame ;
2008-01-20 22:15:22 +01:00
2007-03-24 03:07:06 +01:00
if ( frame ) {
2008-01-20 22:15:22 +01:00
int w = codecContext - > width ;
int h = codecContext - > height ;
PixelFormat srcFormat = codecContext - > pix_fmt ;
2008-11-26 03:03:53 +01:00
PixelFormat dstFormat = PIX_FMT_BGRA ;
2008-01-20 22:15:22 +01:00
// Allocate RGB32 buffer
if ( ! sws_context ) //first frame
{
frameRGB = avcodec_alloc_frame ( ) ;
unsigned int dstSize = avpicture_get_size ( dstFormat , w , h ) ;
bufferRGB = new uint8_t [ dstSize ] ;
2008-08-03 18:52:54 +02:00
sws_context = sws_getContext ( w , h , srcFormat , w , h , dstFormat , SWS_PRINT_INFO | SWS_BICUBIC , NULL , NULL , NULL ) ;
2008-09-03 19:03:20 +02:00
// sws_getContext() always returns NULL if context creation failed
2008-08-03 18:52:54 +02:00
if ( sws_context = = NULL )
throw _T ( " ffmpeg video provider: failed to initialize SwScaler colorspace conversion " ) ;
2008-01-20 22:15:22 +01:00
}
avpicture_fill ( ( AVPicture * ) frameRGB , bufferRGB , dstFormat , w , h ) ;
2007-03-24 03:07:06 +01:00
// Set AegiVideoFrame
final . w = codecContext - > width ;
final . h = codecContext - > height ;
final . flipped = false ;
2008-01-20 22:15:22 +01:00
final . invertChannels = true ;
final . format = FORMAT_RGB32 ;
2007-03-24 03:07:06 +01:00
// Allocate
2008-01-20 22:15:22 +01:00
for ( int i = 0 ; i < 4 ; i + + ) final . pitch [ i ] = frameRGB - > linesize [ i ] ;
2007-03-24 03:07:06 +01:00
final . Allocate ( ) ;
2008-01-20 22:15:22 +01:00
// Convert to RGB32, and write directly to the output frame
sws_scale ( sws_context , frame - > data , frame - > linesize , 0 , h , final . data , frameRGB - > linesize ) ;
}
else // No frame available
{
final = AegiVideoFrame ( GetWidth ( ) , GetHeight ( ) ) ;
2007-03-24 03:07:06 +01:00
}
2006-02-24 03:54:30 +01:00
2007-03-24 03:07:06 +01:00
// Set current frame
validFrame = true ;
2008-07-15 02:08:05 +02:00
lastFrameNumber = n ;
2006-02-24 03:54:30 +01:00
2007-03-24 03:07:06 +01:00
// Return
2008-01-20 22:15:22 +01:00
return final ;
2006-02-24 03:54:30 +01:00
}
2006-02-24 04:54:40 +01:00
////////////////
// Get position
2006-02-24 03:54:30 +01:00
int LAVCVideoProvider : : GetPosition ( ) {
2006-02-24 06:58:45 +01:00
return frameNumber ;
2006-02-24 03:54:30 +01:00
}
2006-02-24 04:54:40 +01:00
////////////////////////
// Get number of frames
2006-02-24 03:54:30 +01:00
int LAVCVideoProvider : : GetFrameCount ( ) {
2006-02-25 21:48:32 +01:00
return length ;
2006-02-24 03:54:30 +01:00
}
2006-02-24 04:54:40 +01:00
//////////////////
// Get frame rate
2006-02-24 03:54:30 +01:00
double LAVCVideoProvider : : GetFPS ( ) {
2006-02-24 04:54:40 +01:00
return double ( stream - > r_frame_rate . num ) / double ( stream - > r_frame_rate . den ) ;
2006-02-24 03:54:30 +01:00
}
2006-02-24 04:54:40 +01:00
//////////////////////
// Get original width
2007-03-24 01:11:49 +01:00
int LAVCVideoProvider : : GetWidth ( ) {
2006-03-26 00:38:46 +01:00
return codecContext - > width ;
2006-02-24 03:54:30 +01:00
}
2006-02-24 04:54:40 +01:00
///////////////////////
// Get original height
2007-03-24 01:11:49 +01:00
int LAVCVideoProvider : : GetHeight ( ) {
2006-03-26 00:38:46 +01:00
return codecContext - > height ;
2006-02-24 03:54:30 +01:00
}
2007-12-31 07:46:22 +01:00
2008-07-15 02:08:05 +02:00
//////////////////////
// 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 ;
}
2007-12-31 07:46:22 +01:00
# endif // WITH_FFMPEG