Fix for cinematic audio when playing Bink video files with ffmpeg decoder, improve ffmpeg a/v resync

This commit is contained in:
Stephen Saunders 2023-11-01 19:13:47 -04:00
parent 8f84e54bb0
commit f7751f6428

View file

@ -130,9 +130,8 @@ private:
cinData_t ImageForTimeFFMPEG( int milliseconds, nvrhi::ICommandList* commandList );
bool InitFromFFMPEGFile( const char* qpath, bool looping, nvrhi::ICommandList* commandList );
void FFMPEGReset();
uint8_t* lagBuffer[NUM_LAG_FRAMES] = {};
int lagBufSize[NUM_LAG_FRAMES] = {};
int lagIndex;
std::queue<uint8_t*> lagBuffer;
std::queue<int> lagBufSize;
bool skipLag;
#endif
#ifdef USE_BINKDEC
@ -469,7 +468,6 @@ idCinematicLocal::idCinematicLocal()
img_convert_ctx = NULL;
hasFrame = false;
framePos = -1;
lagIndex = 0;
skipLag = false;
#endif
@ -842,14 +840,11 @@ void idCinematicLocal::FFMPEGReset()
{
cinematicAudio->ResetAudio();
lagIndex = 0;
for( int i = 0; i < NUM_LAG_FRAMES; i++ )
while( !lagBuffer.empty() )
{
lagBufSize[ i ] = 0;
if( lagBuffer[ i ] )
{
av_freep( &lagBuffer[ i ] );
}
av_freep( &lagBuffer.front() );
lagBuffer.pop();
lagBufSize.pop();
}
}
@ -910,7 +905,7 @@ bool idCinematicLocal::InitFromBinkDecFile( const char* qpath, bool amilooping,
}
else
{
common->Warning( "idCinematic: Cannot open BinkDec video file: '%s', %d\n", qpath, looping );
common->Warning( "idCinematic: Cannot open Bink video file: '%s', %d\n", qpath, looping );
return false;
}
}
@ -918,7 +913,7 @@ bool idCinematicLocal::InitFromBinkDecFile( const char* qpath, bool amilooping,
binkHandle = Bink_Open( fullpath );
if( !binkHandle.isValid )
{
common->Warning( "idCinematic: Cannot open BinkDec video file: '%s', %d\n", qpath, looping );
common->Warning( "idCinematic: Cannot open Bink video file: '%s', %d\n", qpath, looping );
return false;
}
@ -948,7 +943,7 @@ bool idCinematicLocal::InitFromBinkDecFile( const char* qpath, bool amilooping,
numFrames = Bink_GetNumFrames( binkHandle );
float durationSec = numFrames / frameRate; // SRS - fixed Bink durationSec calculation
animationLength = durationSec * 1000; // SRS - animationLength is in milliseconds
common->Printf( "Loaded BinkDec file: '%s', looping=%d, %dx%d, %3.2f FPS, %4.1f sec\n", qpath, looping, CIN_WIDTH, CIN_HEIGHT, frameRate, durationSec );
common->Printf( "Loaded Bink file: '%s', looping=%d, %dx%d, %3.2f FPS, %4.1f sec\n", qpath, looping, CIN_WIDTH, CIN_HEIGHT, frameRate, durationSec );
memset( yuvBuffer, 0, sizeof( yuvBuffer ) );
@ -1174,14 +1169,11 @@ void idCinematicLocal::Close()
swr_free( &swr_ctx );
}
lagIndex = 0;
for( int i = 0; i < NUM_LAG_FRAMES; i++ )
while( !lagBuffer.empty() )
{
lagBufSize[ i ] = 0;
if( lagBuffer[ i ] )
{
av_freep( &lagBuffer[ i ] );
}
av_freep( &lagBuffer.front() );
lagBuffer.pop();
lagBufSize.pop();
}
}
@ -1378,6 +1370,7 @@ cinData_t idCinematicLocal::ImageForTimeFFMPEG( int thisTime, nvrhi::ICommandLis
char error[64];
uint8_t* audioBuffer = NULL;
int num_bytes = 0;
bool syncLost = false;
memset( &cinData, 0, sizeof( cinData ) );
if( !fmt_ctx )
@ -1491,8 +1484,8 @@ cinData_t idCinematicLocal::ImageForTimeFFMPEG( int thisTime, nvrhi::ICommandLis
common->Warning( "idCinematic: Failed to receive audio frame from decoding with error: %s\n", error );
}
}
// SRS - For the final (desired) frame only: allocate audio buffers, convert to packed format, and play the synced audio frames
else if( framePos + 1 == desiredFrame )
// SRS - Allocate audio buffer, convert to packed format, save in queue, and play synced audio for desired frame
else
{
// SRS - Since destination sample format is packed (non-planar), returned bufflinesize equals num_bytes
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59,37,100)
@ -1521,28 +1514,48 @@ cinData_t idCinematicLocal::ImageForTimeFFMPEG( int thisTime, nvrhi::ICommandLis
memcpy( audioBuffer, frame3->extended_data[0], num_bytes );
}
}
// SRS - If we have cinematic audio data, play a lagged frame (for bik video sync) and save the current frame
// SRS - If we have cinematic audio data, save the current frame onto the back of the queue
if( num_bytes > 0 )
{
// SRS - If we have a lagged cinematic audio frame, then play it now
if( lagBufSize[ lagIndex ] > 0 )
// SRS - If queue is at max size we have lost a/v sync: drop frame and set syncLost flag
if( lagBuffer.size() == ( skipLag ? 1 : NUM_LAG_FRAMES ) )
{
// SRS - Note that PlayAudio() is responsible for releasing any audio buffers sent to it
cinematicAudio->PlayAudio( lagBuffer[ lagIndex ], lagBufSize[ lagIndex ] );
av_freep( &lagBuffer.front() );
lagBuffer.pop();
lagBufSize.pop();
syncLost = true;
}
// SRS - Save the current (new) audio buffer and its size to play in the future
lagBuffer[ lagIndex ] = audioBuffer;
lagBufSize[ lagIndex ] = num_bytes;
// SRS - If skipLag is true (e.g. RoQ, mp4, webm), reduce lag to 1 frame since AV playback is already synced
lagIndex = ( lagIndex + 1 ) % ( skipLag ? 1 : NUM_LAG_FRAMES );
// SRS - Save the current (new) audio buffer and its size to play during the desired frame
lagBuffer.push( audioBuffer );
lagBufSize.push( num_bytes );
}
// SRS - Not sure if an audioBuffer can ever be allocated on failure, but check and free just in case
else if( audioBuffer )
{
av_freep( &audioBuffer );
}
// SRS - If we have any synced audio frames available for the desired frame, play now and drain queue
if( framePos + 1 == desiredFrame )
{
if( syncLost )
{
// SRS - If we have lost sync, reset / resync audio stream before starting to play again
cinematicAudio->ResetAudio();
syncLost = false;
}
while( !lagBuffer.empty() )
{
// SRS - Note that PlayAudio() is responsible for releasing any audio buffers sent to it
cinematicAudio->PlayAudio( lagBuffer.front(), lagBufSize.front() );
lagBuffer.pop();
lagBufSize.pop();
}
}
//common->Printf( "idCinematic: video pts = %7.3f, audio pts = %7.3f, samples = %4d, num_bytes = %5d\n", static_cast<double>( frame->pts ) * av_q2d( dec_ctx->pkt_timebase ), static_cast<double>( frame3->pts ) * av_q2d( dec_ctx2->pkt_timebase ), frame3->nb_samples, num_bytes );
}
}