diff --git a/neo/framework/Common.cpp b/neo/framework/Common.cpp index df3303a0..fd5f7c55 100644 --- a/neo/framework/Common.cpp +++ b/neo/framework/Common.cpp @@ -1571,10 +1571,6 @@ void idCommonLocal::Shutdown() printf( "uiManager->Shutdown();\n" ); uiManager->Shutdown(); - // shut down the sound system - printf( "soundSystem->Shutdown();\n" ); - soundSystem->Shutdown(); - // shut down the user command input code printf( "usercmdGen->Shutdown();\n" ); usercmdGen->Shutdown(); @@ -1584,9 +1580,16 @@ void idCommonLocal::Shutdown() eventLoop->Shutdown(); // shutdown the decl manager + // SRS - Note this also shuts down all cinematic resources, including cinematic audio voices printf( "declManager->Shutdown();\n" ); declManager->Shutdown(); + // shut down the sound system + // SRS - Shut down sound system after decl manager so cinematic audio voices are destroyed first + // Important for XAudio2 where the mastering voice cannot be destroyed if any other voices exist + printf( "soundSystem->Shutdown();\n" ); + soundSystem->Shutdown(); + // shut down the renderSystem printf( "renderSystem->Shutdown();\n" ); renderSystem->Shutdown(); diff --git a/neo/framework/common_frame.cpp b/neo/framework/common_frame.cpp index 2c2b1351..d7df5d6e 100644 --- a/neo/framework/common_frame.cpp +++ b/neo/framework/common_frame.cpp @@ -862,7 +862,7 @@ void idCommonLocal::Frame() // RB begin #if defined(USE_DOOMCLASSIC) // If we're in Doom or Doom 2, run tics and upload the new texture. - // SRS - Add check for com_pause cvar to make sure window is in focus - if not classic game should be paused (FIXME: but classic music still plays in background) + // SRS - Add check for com_pause cvar to make sure window is in focus - if not classic game should be paused if( ( GetCurrentGame() == DOOM_CLASSIC || GetCurrentGame() == DOOM2_CLASSIC ) && !( Dialog().IsDialogPausing() || session->IsSystemUIShowing() || com_pause.GetInteger() ) ) { RunDoomClassicFrame(); @@ -922,16 +922,18 @@ void idCommonLocal::Frame() { soundWorld->Pause(); soundSystem->SetPlayingSoundWorld( menuSoundWorld ); + soundSystem->SetMute( false ); } else { soundWorld->UnPause(); soundSystem->SetPlayingSoundWorld( soundWorld ); + soundSystem->SetMute( false ); } - // SRS - Play silence when dialog waiting or window not in focus + // SRS - Mute all sound output when dialog waiting or window not in focus (mutes Doom3, Classic, Cinematic Audio) if( Dialog().IsDialogPausing() || session->IsSystemUIShowing() || com_pause.GetInteger() ) { - soundSystem->SetPlayingSoundWorld( NULL ); + soundSystem->SetMute( true ); } soundSystem->Render(); diff --git a/neo/renderer/Cinematic.cpp b/neo/renderer/Cinematic.cpp index e4242b14..16c45225 100644 --- a/neo/renderer/Cinematic.cpp +++ b/neo/renderer/Cinematic.cpp @@ -71,11 +71,7 @@ extern "C" #include #include } -// SRS - For handling cinematic audio packets -#include -#define NUM_PACKETS 4 -#define NUM_LAG_FRAMES 15 // SRS - Lag cinematic audio by 15 frames (~1/2 sec at 30 fps) to sync with FFMPEG video -bool hasplanar = true; +#define NUM_LAG_FRAMES 15 // SRS - Lag audio by 15 frames (~1/2 sec at 30 fps) for ffmpeg bik decoder AV sync #endif #ifdef USE_BINKDEC @@ -118,8 +114,10 @@ private: AVFrame* frame3; //GK: make extra frame for audio #if LIBAVCODEC_VERSION_MAJOR > 58 const AVCodec* dec; + const AVCodec* dec2; // SRS - Separate decoder for audio #else AVCodec* dec; + AVCodec* dec2; // SRS - Separate decoder for audio #endif AVCodecContext* dec_ctx; AVCodecContext* dec_ctx2; @@ -127,14 +125,15 @@ private: bool hasFrame; long framePos; AVSampleFormat dst_smp; + bool hasplanar; SwrContext* swr_ctx; cinData_t ImageForTimeFFMPEG( int milliseconds ); bool InitFromFFMPEGFile( const char* qpath, bool looping ); void FFMPEGReset(); - std::queue packets[NUM_PACKETS]; uint8_t* lagBuffer[NUM_LAG_FRAMES] = {}; int lagBufSize[NUM_LAG_FRAMES] = {}; int lagIndex; + bool skipLag; #endif #ifdef USE_BINKDEC BinkHandle binkHandle; @@ -436,7 +435,6 @@ idCinematicLocal::idCinematicLocal() isRoQ = false; // SRS - Initialize isRoQ for all cases, not just FFMPEG #if defined(USE_FFMPEG) // Carl: ffmpeg stuff, for bink and normal video files: -// fmt_ctx = avformat_alloc_context(); #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55,28,1) frame = av_frame_alloc(); frame2 = av_frame_alloc(); @@ -451,10 +449,13 @@ idCinematicLocal::idCinematicLocal() fmt_ctx = NULL; video_stream_index = -1; audio_stream_index = -1; + hasplanar = false; + swr_ctx = NULL; img_convert_ctx = NULL; hasFrame = false; framePos = -1; lagIndex = 0; + skipLag = false; #endif #ifdef USE_BINKDEC @@ -548,30 +549,9 @@ idCinematicLocal::~idCinematicLocal() av_freep( &frame2 ); av_freep( &frame3 ); #endif - - // SRS - Free any lagged cinematic audio buffers - for( int i = 0; i < NUM_LAG_FRAMES; i++ ) - { - av_freep( &lagBuffer[ i ] ); - } - - if( fmt_ctx ) - { - avformat_free_context( fmt_ctx ); - } - - if( img_convert_ctx ) - { - sws_freeContext( img_convert_ctx ); - } #endif #ifdef USE_BINKDEC - if( binkHandle.isValid ) - { - Bink_Close( binkHandle ); - } - delete imgY; imgY = NULL; delete imgCr; @@ -677,8 +657,20 @@ bool idCinematicLocal::InitFromFFMPEGFile( const char* qpath, bool amilooping ) if( ( ret = avformat_open_input( &fmt_ctx, fullpath, NULL, NULL ) ) < 0 ) { - common->Warning( "idCinematic: Cannot open FFMPEG video file: '%s', %d\n", qpath, looping ); - return false; + // SRS - another case sensitivity hack for Linux, this time for ffmpeg and RoQ files + idStr ext; + fullpath.ExtractFileExtension( ext ); + if( idStr::Cmp( ext.c_str(), "roq" ) == 0 ) + { + // SRS - If ffmpeg can't open .roq file, then try again with .RoQ extension instead + fullpath.Replace( ".roq", ".RoQ" ); + ret = avformat_open_input( &fmt_ctx, fullpath, NULL, NULL ); + } + if( ret < 0 ) + { + common->Warning( "idCinematic: Cannot open FFMPEG video file: '%s', %d\n", qpath, looping ); + return false; + } } if( ( ret = avformat_find_stream_info( fmt_ctx, NULL ) ) < 0 ) { @@ -712,12 +704,12 @@ bool idCinematicLocal::InitFromFFMPEGFile( const char* qpath, bool amilooping ) return false; } //GK:Begin - //After the video decoder is open then try to open audio decoder since it will re-bind the main decoder from video to audio - ret2 = av_find_best_stream( fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, &dec, 0 ); + //After the video decoder is open then try to open audio decoder + ret2 = av_find_best_stream( fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, &dec2, 0 ); if( ret2 >= 0 ) //Make audio optional (only intro video has audio no other) { audio_stream_index = ret2; - dec_ctx2 = avcodec_alloc_context3( dec ); + dec_ctx2 = avcodec_alloc_context3( dec2 ); if( ( ret2 = avcodec_parameters_to_context( dec_ctx2, fmt_ctx->streams[audio_stream_index]->codecpar ) ) < 0 ) { char* error = new char[256]; @@ -727,7 +719,7 @@ bool idCinematicLocal::InitFromFFMPEGFile( const char* qpath, bool amilooping ) dec_ctx2->time_base = fmt_ctx->streams[audio_stream_index]->time_base; dec_ctx2->framerate = fmt_ctx->streams[audio_stream_index]->avg_frame_rate; dec_ctx2->pkt_timebase = fmt_ctx->streams[audio_stream_index]->time_base; - if( ( ret2 = avcodec_open2( dec_ctx2, dec, NULL ) ) < 0 ) + if( ( ret2 = avcodec_open2( dec_ctx2, dec2, NULL ) ) < 0 ) { common->Warning( "idCinematic: Cannot open audio decoder for: '%s', %d\n", qpath, looping ); //return false; @@ -777,28 +769,32 @@ bool idCinematicLocal::InitFromFFMPEGFile( const char* qpath, bool amilooping ) //GK: No duration is given. Check if we get at least bitrate to calculate the length, otherwise set it to a fixed 100 seconds (should it be lower ?) if( durationSec < 0 ) { - if( dec_ctx->bit_rate > 0 ) + // SRS - First check the file context bit rate and estimate duration using file size and overall bit rate + if( fmt_ctx->bit_rate > 0 ) { - durationSec = file_size / dec_ctx->bit_rate; + durationSec = file_size * 8.0 / fmt_ctx->bit_rate; } + // SRS - Likely an RoQ file, so use the video bit rate tolerance plus audio bit rate to estimate duration, then add 10% to correct for variable bit rate + else if( dec_ctx->bit_rate_tolerance > 0 ) + { + durationSec = file_size * 8.0 / ( dec_ctx->bit_rate_tolerance + ( dec_ctx2 ? dec_ctx2->bit_rate : 0 ) ) * 1.1; + } + // SRS - Otherwise just set a large max duration else { - durationSec = 100; + durationSec = 100.0; } } animationLength = durationSec * 1000; frameRate = av_q2d( fmt_ctx->streams[video_stream_index]->avg_frame_rate ); - common->Printf( "Loaded FFMPEG file: '%s', looping=%d, %dx%d, %f FPS, %f sec\n", qpath, looping, CIN_WIDTH, CIN_HEIGHT, frameRate, durationSec ); + common->Printf( "Loaded FFMPEG file: '%s', looping=%d, %dx%d, %3.2f FPS, %4.1f sec\n", qpath, looping, CIN_WIDTH, CIN_HEIGHT, frameRate, durationSec ); - image = ( byte* )Mem_Alloc( CIN_WIDTH * CIN_HEIGHT * 4 * 2, TAG_CINEMATIC ); + // SRS - Get number of image bytes needed by querying with NULL first, then allocate image and fill with correct parameters + int img_bytes = av_image_fill_arrays( frame2->data, frame2->linesize, NULL, AV_PIX_FMT_BGR32, CIN_WIDTH, CIN_HEIGHT, 1 ); + image = ( byte* )Mem_Alloc( img_bytes, TAG_CINEMATIC ); av_image_fill_arrays( frame2->data, frame2->linesize, image, AV_PIX_FMT_BGR32, CIN_WIDTH, CIN_HEIGHT, 1 ); //GK: Straight out of the FFMPEG source code - if( img_convert_ctx ) - { - sws_freeContext( img_convert_ctx ); - } img_convert_ctx = sws_getContext( dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt, CIN_WIDTH, CIN_HEIGHT, AV_PIX_FMT_BGR32, SWS_BICUBIC, NULL, NULL, NULL ); - buf = NULL; status = FMV_PLAY; hasFrame = false; framePos = -1; @@ -821,12 +817,38 @@ void idCinematicLocal::FFMPEGReset() //startTime = 0; framePos = -1; + + // SRS - If we have an ffmpeg audio context and are not looping, or skipLag is true, reset audio to release any stale buffers + if( dec_ctx2 && ( !( looping && status == FMV_EOF ) || skipLag ) ) + { + cinematicAudio->ResetAudio(); - if( av_seek_frame( fmt_ctx, video_stream_index, 0, 0 ) >= 0 ) + for( int i = 0; i < NUM_LAG_FRAMES; i++ ) + { + lagBufSize[ i ] = 0; + if( lagBuffer[ i ] ) + { + av_freep( &lagBuffer[ i ] ); + } + } + } + + // SRS - For non-RoQ (i.e. bik) files, use standard frame seek to rewind the stream + if( dec_ctx->codec_id != AV_CODEC_ID_ROQ && av_seek_frame( fmt_ctx, video_stream_index, 0, 0 ) >= 0 ) { status = FMV_LOOPED; } - else if( av_seek_frame( fmt_ctx, audio_stream_index, 0, 0 ) < 0 && av_seek_frame( fmt_ctx, video_stream_index, 0, 0 ) < 0 ) + // SRS - Special handling for RoQ files: only byte seek works and ffmpeg RoQ decoder needs reset + else if( dec_ctx->codec_id == AV_CODEC_ID_ROQ && av_seek_frame( fmt_ctx, video_stream_index, 0, AVSEEK_FLAG_BYTE ) >= 0 ) + { + // Close and reopen the ffmpeg RoQ codec without clearing the context - this seems to reset the decoder properly + avcodec_close( dec_ctx ); + avcodec_open2( dec_ctx, dec, NULL ); + + status = FMV_LOOPED; + } + // SRS - Can't rewind the stream so we really are at EOF + else { status = FMV_EOF; } @@ -888,7 +910,7 @@ bool idCinematicLocal::InitFromBinkDecFile( const char* qpath, bool amilooping ) { trackIndex = 0; // SRS - Use the first audio track - is this reasonable? binkInfo = Bink_GetAudioTrackDetails( binkHandle, trackIndex ); - common->Printf( "Cinematic audio stream found: Sample Rate=%d Hz, Channels=%d\n", binkInfo.sampleRate, binkInfo.nChannels ); + common->Printf( "Cinematic audio stream found: Sample Rate=%d Hz, Channels=%d, Format=16-bit\n", binkInfo.sampleRate, binkInfo.nChannels ); cinematicAudio->InitAudio( &binkInfo ); } @@ -896,11 +918,10 @@ 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, %f FPS, %f sec\n", qpath, looping, CIN_WIDTH, CIN_HEIGHT, frameRate, durationSec ); + 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 ); memset( yuvBuffer, 0, sizeof( yuvBuffer ) ); - buf = NULL; status = FMV_PLAY; hasFrame = false; // SRS - Implemented hasFrame for BinkDec behaviour consistency with FFMPEG framePos = -1; @@ -913,6 +934,13 @@ bool idCinematicLocal::InitFromBinkDecFile( const char* qpath, bool amilooping ) void idCinematicLocal::BinkDecReset() { framePos = -1; + + // SRS - If we have bink audio tracks, reset audio to release any stale buffers (even if looping) + if( audioTracks > 0 ) + { + cinematicAudio->ResetAudio(); + } + Bink_GotoFrame( binkHandle, 0 ); status = FMV_LOOPED; } @@ -927,7 +955,8 @@ bool idCinematicLocal::InitFromFile( const char* qpath, bool amilooping ) { unsigned short RoQID; - Close(); + // SRS - Don't need to call Close() here, all initialization is handled by constructor + //Close(); inMemory = 0; animationLength = 100000; @@ -942,33 +971,42 @@ bool idCinematicLocal::InitFromFile( const char* qpath, bool amilooping ) sprintf( fileName, "%s", qpath ); } // Carl: Look for original Doom 3 RoQ files first: - idStr ext; - fileName.ExtractFileExtension( ext ); - fileName = fileName.StripFileExtension(); - fileName = fileName + ".roq"; - //if (fileName == "video\\loadvideo.roq") { - // fileName = "video\\idlogo.roq"; + idStr temp = fileName.StripFileExtension() + ".roq"; + + // SRS - Cool legacy support, but leaving this disabled since it might break existing mods + //if( temp == "video\\loadvideo.roq" ) + //{ + // temp = "video\\idlogo.roq"; //} - iFile = fileSystem->OpenFileRead( fileName ); + iFile = fileSystem->OpenFileRead( temp ); - // Carl: If the RoQ file doesn't exist, try using ffmpeg instead: + // Carl: If the RoQ file doesn't exist, try using bik file extension instead: if( !iFile ) { + //idLib::Warning( "Original Doom 3 RoQ Cinematic not found: '%s'\n", temp.c_str() ); #if defined(USE_FFMPEG) - //idLib::Warning( "Original Doom 3 RoQ Cinematic not found: '%s'\n", fileName.c_str() ); - idStr temp = fileName.StripFileExtension() + ".bik"; + temp = fileName.StripFileExtension() + ".bik"; + skipLag = false; // SRS - Enable lag buffer for ffmpeg bik decoder AV sync + + // SRS - Support RoQ cinematic playback via ffmpeg decoder - better quality plus audio support + } + else + { + fileSystem->CloseFile( iFile ); // SRS - Close the RoQ file and let ffmpeg reopen it + iFile = NULL; + skipLag = true; // SRS - Disable lag buffer for ffmpeg RoQ decoder AV sync + } + { + // SRS End + animationLength = 0; - hasFrame = false; - RoQShutdown(); fileName = temp; //idLib::Warning( "New filename: '%s'\n", fileName.c_str() ); return InitFromFFMPEGFile( fileName.c_str(), amilooping ); #elif defined(USE_BINKDEC) - idStr temp = fileName.StripFileExtension() + ".bik"; + temp = fileName.StripFileExtension() + ".bik"; animationLength = 0; - hasFrame = false; - RoQShutdown(); fileName = temp; //idLib::Warning( "New filename: '%s'\n", fileName.c_str() ); return InitFromBinkDecFile( fileName.c_str(), amilooping ); @@ -979,6 +1017,7 @@ bool idCinematicLocal::InitFromFile( const char* qpath, bool amilooping ) } // Carl: The rest of this function is for original Doom 3 RoQ files: isRoQ = true; + fileName = temp; ROQSize = iFile->Length(); looping = amilooping; @@ -1004,6 +1043,7 @@ bool idCinematicLocal::InitFromFile( const char* qpath, bool amilooping ) RoQ_init(); status = FMV_PLAY; ImageForTime( 0 ); + common->Printf( "Loaded RoQ file: '%s', looping=%d, %dx%d, %3.2f FPS\n", fileName.c_str(), looping, CIN_WIDTH, CIN_HEIGHT, frameRate ); status = ( looping ) ? FMV_PLAY : FMV_IDLE; return true; } @@ -1027,23 +1067,43 @@ void idCinematicLocal::Close() status = FMV_EOF; } - RoQShutdown(); - + if( isRoQ ) + { + RoQShutdown(); + } #if defined(USE_FFMPEG) - hasFrame = false; - - if( !isRoQ ) + else //if( !isRoQ ) { if( img_convert_ctx ) { sws_freeContext( img_convert_ctx ); + img_convert_ctx = NULL; } - img_convert_ctx = NULL; + // SRS - Free audio codec context, resample context, and any lagged audio buffers + if( dec_ctx2 ) + { + avcodec_free_context( &dec_ctx2 ); + + // SRS - Free resample context if we were decoding planar audio + if( swr_ctx ) + { + swr_free( &swr_ctx ); + } + + for( int i = 0; i < NUM_LAG_FRAMES; i++ ) + { + lagBufSize[ i ] = 0; + if( lagBuffer[ i ] ) + { + av_freep( &lagBuffer[ i ] ); + } + } + } if( dec_ctx ) { - avcodec_close( dec_ctx ); + avcodec_free_context( &dec_ctx ); } if( fmt_ctx ) @@ -1052,11 +1112,8 @@ void idCinematicLocal::Close() } status = FMV_EOF; } -#endif -#ifdef USE_BINKDEC - hasFrame = false; - - if( !isRoQ ) +#elif defined(USE_BINKDEC) + else //if( !isRoQ ) { if( binkHandle.isValid ) { @@ -1121,8 +1178,7 @@ cinData_t idCinematicLocal::ImageForTime( int thisTime ) { return ImageForTimeFFMPEG( thisTime ); } -#endif -#ifdef USE_BINKDEC // DG: libbinkdec support +#elif defined(USE_BINKDEC) // DG: libbinkdec support if( !isRoQ ) { return ImageForTimeBinkDec( thisTime ); @@ -1132,15 +1188,16 @@ cinData_t idCinematicLocal::ImageForTime( int thisTime ) // Carl: Handle original Doom 3 RoQ video files cinData_t cinData; - if( thisTime == 0 ) + // SRS - Changed from == 0 to <= 0 to match behaviour of FFMPEG and BinkDec decoders + if( thisTime <= 0 ) { thisTime = Sys_Milliseconds(); } - if( thisTime < 0 ) - { - thisTime = 0; - } + //if( thisTime < 0 ) + //{ + // thisTime = 0; + //} memset( &cinData, 0, sizeof( cinData ) ); @@ -1177,7 +1234,8 @@ cinData_t idCinematicLocal::ImageForTime( int thisTime ) tfps = 0; } - if( tfps < numQuads ) + // SRS - Need to use numQuads - 1 for frame position (otherwise get into reset loop at start) + if( tfps < numQuads - 1 ) { RoQReset(); buf = NULL; @@ -1186,6 +1244,7 @@ cinData_t idCinematicLocal::ImageForTime( int thisTime ) if( buf == NULL ) { + // SRS - This frame init loop is not really necessary, but leaving in to avoid breakage while( buf == NULL ) { RoQInterrupt(); @@ -1193,32 +1252,31 @@ cinData_t idCinematicLocal::ImageForTime( int thisTime ) } else { - while( ( tfps != numQuads && status == FMV_PLAY ) ) + // SRS - This frame loop is really all we need and could handle the above case as well + while( ( numQuads - 1 < tfps && status == FMV_PLAY ) ) { RoQInterrupt(); } } - if( status == FMV_LOOPED ) - { - status = FMV_PLAY; - while( buf == NULL && status == FMV_PLAY ) - { - RoQInterrupt(); - } - startTime = thisTime; - } + // SRS - This is redundant code, virtually identical logic correctly handles looping below + //if( status == FMV_LOOPED ) + //{ + // status = FMV_PLAY; + // while( buf == NULL && status == FMV_PLAY ) + // { + // RoQInterrupt(); + // } + // startTime = thisTime; + //} - if( status == FMV_EOF ) + if( status == FMV_LOOPED || status == FMV_EOF ) { if( looping ) { - RoQReset(); + //RoQReset(); // SRS - RoQReset() already called by RoQInterrupt() when looping buf = NULL; - if( status == FMV_LOOPED ) - { - status = FMV_PLAY; - } + status = FMV_PLAY; while( buf == NULL && status == FMV_PLAY ) { RoQInterrupt(); @@ -1227,8 +1285,10 @@ cinData_t idCinematicLocal::ImageForTime( int thisTime ) } else { + buf = NULL; status = FMV_IDLE; - RoQShutdown(); + //RoQShutdown(); //SRS - RoQShutdown() not needed on EOF, return null data instead + return cinData; } } @@ -1288,6 +1348,8 @@ cinData_t idCinematicLocal::ImageForTimeFFMPEG( int thisTime ) if( desiredFrame < framePos ) { FFMPEGReset(); + hasFrame = false; + status = FMV_PLAY; } if( hasFrame && desiredFrame == framePos ) @@ -1320,7 +1382,7 @@ cinData_t idCinematicLocal::ImageForTimeFFMPEG( int thisTime ) { desiredFrame = 0; FFMPEGReset(); - framePos = -1; + hasFrame = false; startTime = thisTime; if( av_read_frame( fmt_ctx, &packet ) < 0 ) { @@ -1331,6 +1393,7 @@ cinData_t idCinematicLocal::ImageForTimeFFMPEG( int thisTime ) } else { + hasFrame = false; status = FMV_IDLE; return cinData; } @@ -1358,8 +1421,7 @@ cinData_t idCinematicLocal::ImageForTimeFFMPEG( int thisTime ) //GK:Begin else if( packet.stream_index == audio_stream_index ) //Check if it found any audio data { - packets->push( packet ); - res = avcodec_send_packet( dec_ctx2, &packets->front() ); + res = avcodec_send_packet( dec_ctx2, &packet ); if( res != 0 && res != AVERROR( EAGAIN ) ) { char* error = new char[256]; @@ -1368,8 +1430,6 @@ cinData_t idCinematicLocal::ImageForTimeFFMPEG( int thisTime ) } else { - packet = packets->front(); - packets->pop(); if( ( frameFinished1 = avcodec_receive_frame( dec_ctx2, frame3 ) ) != 0 ) { char* error = new char[256]; @@ -1436,7 +1496,7 @@ cinData_t idCinematicLocal::ImageForTimeFFMPEG( int thisTime ) lagBuffer[ lagIndex ] = audioBuffer; lagBufSize[ lagIndex ] = num_bytes; - lagIndex = ( lagIndex + 1 ) % NUM_LAG_FRAMES; + lagIndex = ( lagIndex + 1 ) % ( skipLag ? 1 : NUM_LAG_FRAMES ); } return cinData; @@ -1484,28 +1544,12 @@ cinData_t idCinematicLocal::ImageForTimeBinkDec( int thisTime ) desiredFrame = 0; } - if( desiredFrame >= numFrames ) - { - status = FMV_EOF; - if( looping ) - { - desiredFrame = 0; - BinkDecReset(); - framePos = -1; - startTime = thisTime; - status = FMV_PLAY; - } - else - { - status = FMV_IDLE; - return cinData; - } - } - // SRS - Enable video replay within PDAs if( desiredFrame < framePos ) { BinkDecReset(); + hasFrame = false; + status = FMV_PLAY; } // SRS end @@ -1521,6 +1565,25 @@ cinData_t idCinematicLocal::ImageForTimeBinkDec( int thisTime ) return cinData; } + if( desiredFrame >= numFrames ) + { + status = FMV_EOF; + if( looping ) + { + desiredFrame = 0; + BinkDecReset(); + hasFrame = false; + startTime = thisTime; + status = FMV_PLAY; + } + else + { + hasFrame = false; + status = FMV_IDLE; + return cinData; + } + } + // Bink_GotoFrame(binkHandle, desiredFrame); // apparently Bink_GotoFrame() doesn't work super well, so skip frames // (if necessary) by calling Bink_GetNextFrame() @@ -1586,7 +1649,7 @@ cinData_t idCinematicLocal::ImageForTimeBinkDec( int thisTime ) if( audioTracks > 0 ) { - audioBuffer = ( int16_t* )malloc( binkInfo.idealBufferSize ); + audioBuffer = ( int16_t* )Mem_Alloc( binkInfo.idealBufferSize, TAG_AUDIO ); num_bytes = Bink_GetAudioData( binkHandle, trackIndex, audioBuffer ); // SRS - If we have cinematic audio data, start playing it now @@ -1598,7 +1661,7 @@ cinData_t idCinematicLocal::ImageForTimeBinkDec( int thisTime ) else { // SRS - Even though we have no audio data to play, still need to free the audio buffer - free( audioBuffer ); + Mem_Free( audioBuffer ); } } @@ -3077,7 +3140,7 @@ redump: // // read in next frame data // - if( RoQPlayed >= ROQSize ) + if( RoQPlayed >= ROQSize || status == FMV_EOF ) // SRS - handle FMV_EOF case { if( looping ) { @@ -3153,11 +3216,14 @@ idCinematicLocal::RoQShutdown */ void idCinematicLocal::RoQShutdown() { + // SRS - Depending on status, this could prevent closing of iFile on shutdown, disable it + /* if( status == FMV_IDLE ) { return; } - status = FMV_IDLE; + */ + status = FMV_EOF; // SRS - Changed from FMV_IDLE to FMV_EOF for shutdown consistency if( iFile ) { diff --git a/neo/renderer/OpenGL/RenderDebug_GL.cpp b/neo/renderer/OpenGL/RenderDebug_GL.cpp index de7b0077..22effb11 100644 --- a/neo/renderer/OpenGL/RenderDebug_GL.cpp +++ b/neo/renderer/OpenGL/RenderDebug_GL.cpp @@ -3256,13 +3256,20 @@ void idRenderBackend::DBG_TestImage() { cinData_t cin; - cin = tr.testVideo->ImageForTime( viewDef->renderView.time[1] - tr.testVideoStartTime ); + // SRS - Don't need calibrated time for testing cinematics, so just call ImageForTime( 0 ) for current system time + // This simplification allows cinematic test playback to work over both 2D and 3D background scenes + cin = tr.testVideo->ImageForTime( 0 /*viewDef->renderView.time[1] - tr.testVideoStartTime*/ ); if( cin.imageY != NULL ) { image = cin.imageY; imageCr = cin.imageCr; imageCb = cin.imageCb; } + // SRS - Also handle ffmpeg and original RoQ decoders for test videos (using cin.image) + else if( cin.image != NULL ) + { + image = cin.image; + } else { tr.testImage = NULL; @@ -3302,9 +3309,9 @@ void idRenderBackend::DBG_TestImage() float scale[16] = { 0 }; scale[0] = w; // scale - scale[5] = -h; // scale + scale[5] = h; // scale (SRS - changed h from -ve to +ve so video plays right side up) scale[12] = halfScreenWidth - ( halfScreenWidth * w ); // translate - scale[13] = halfScreenHeight - ( halfScreenHeight * h ); // translate + scale[13] = halfScreenHeight - ( halfScreenHeight * h ) - h; // translate (SRS - moved up by h) scale[10] = 1.0f; scale[15] = 1.0f; @@ -3340,7 +3347,9 @@ void idRenderBackend::DBG_TestImage() imageCr->Bind(); GL_SelectTexture( 2 ); imageCb->Bind(); - renderProgManager.BindShader_Bink(); + // SRS - Use Bink shader without sRGB to linear conversion, otherwise cinematic colours may be wrong + // BindShader_BinkGUI() does not seem to work here - perhaps due to vertex shader input dependencies? + renderProgManager.BindShader_Bink_sRGB(); } else { diff --git a/neo/renderer/RenderBackend.cpp b/neo/renderer/RenderBackend.cpp index e073109e..f27a2fe4 100644 --- a/neo/renderer/RenderBackend.cpp +++ b/neo/renderer/RenderBackend.cpp @@ -602,8 +602,8 @@ void idRenderBackend::BindVariableStageImage( const textureStage_t* texture, con GL_SelectTexture( 0 ); cin.image->Bind(); - /* - if( backEnd.viewDef->is2Dgui ) + // SRS - Reenable shaders so ffmpeg and RoQ decoder cinematics are rendered with correct colour + if( viewDef->is2Dgui ) { renderProgManager.BindShader_TextureVertexColor_sRGB(); } @@ -611,7 +611,6 @@ void idRenderBackend::BindVariableStageImage( const textureStage_t* texture, con { renderProgManager.BindShader_TextureVertexColor(); } - */ } else { diff --git a/neo/renderer/RenderProgs.cpp b/neo/renderer/RenderProgs.cpp index 58856cee..deabe6d8 100644 --- a/neo/renderer/RenderProgs.cpp +++ b/neo/renderer/RenderProgs.cpp @@ -213,6 +213,9 @@ void idRenderProgManager::Init() { BUILTIN_STEREO_DEGHOST, "builtin/VR/stereoDeGhost", "", 0, false, SHADER_STAGE_DEFAULT, LAYOUT_DRAW_VERT }, { BUILTIN_STEREO_WARP, "builtin/VR/stereoWarp", "", 0, false, SHADER_STAGE_DEFAULT, LAYOUT_DRAW_VERT }, { BUILTIN_BINK, "builtin/video/bink", "", 0, false, SHADER_STAGE_DEFAULT, LAYOUT_DRAW_VERT }, + // SRS - Added Bink shader without sRGB to linear conversion for testVideo cmd + { BUILTIN_BINK_SRGB, "builtin/video/bink", "_sRGB", BIT( USE_SRGB ), false, SHADER_STAGE_DEFAULT, LAYOUT_DRAW_VERT }, + // SRS end { BUILTIN_BINK_GUI, "builtin/video/bink_gui", "", 0, false, SHADER_STAGE_DEFAULT, LAYOUT_DRAW_VERT }, { BUILTIN_STEREO_INTERLACE, "builtin/VR/stereoInterlace", "", 0, false, SHADER_STAGE_DEFAULT, LAYOUT_DRAW_VERT }, { BUILTIN_MOTION_BLUR, "builtin/post/motionBlur", "", 0, false, SHADER_STAGE_DEFAULT, LAYOUT_DRAW_VERT }, diff --git a/neo/renderer/RenderProgs.h b/neo/renderer/RenderProgs.h index 3709606e..2cd68966 100644 --- a/neo/renderer/RenderProgs.h +++ b/neo/renderer/RenderProgs.h @@ -693,6 +693,12 @@ public: BindShader_Builtin( BUILTIN_BINK ); } + // SRS - Added Bink shader without sRGB to linear conversion for testVideo cmd + void BindShader_Bink_sRGB() + { + BindShader_Builtin( BUILTIN_BINK_SRGB ); + } + void BindShader_BinkGUI() { BindShader_Builtin( BUILTIN_BINK_GUI ); @@ -854,6 +860,7 @@ private: BUILTIN_STEREO_DEGHOST, BUILTIN_STEREO_WARP, BUILTIN_BINK, + BUILTIN_BINK_SRGB, // SRS - Added Bink shader without sRGB to linear conversion for testVideo cmd BUILTIN_BINK_GUI, BUILTIN_STEREO_INTERLACE, BUILTIN_MOTION_BLUR, diff --git a/neo/renderer/RenderSystem_init.cpp b/neo/renderer/RenderSystem_init.cpp index 95afe199..1fe1ddc3 100644 --- a/neo/renderer/RenderSystem_init.cpp +++ b/neo/renderer/RenderSystem_init.cpp @@ -617,7 +617,8 @@ void R_TestVideo_f( const idCmdArgs& args ) cinData_t cin; cin = tr.testVideo->ImageForTime( 0 ); - if( cin.imageY == NULL ) + // SRS - Also handle ffmpeg and original RoQ decoders for test videos (using cin.image) + if( cin.imageY == NULL && cin.image == NULL ) { delete tr.testVideo; tr.testVideo = NULL; @@ -630,7 +631,8 @@ void R_TestVideo_f( const idCmdArgs& args ) int len = tr.testVideo->AnimationLength(); common->Printf( "%5.1f seconds of video\n", len * 0.001 ); - tr.testVideoStartTime = tr.primaryRenderView.time[1]; + // SRS - Not needed or used since InitFromFile() sets the correct start time automatically + //tr.testVideoStartTime = tr.primaryRenderView.time[1]; // try to play the matching wav file idStr wavString = args.Argv( ( args.Argc() == 2 ) ? 1 : 2 ); diff --git a/neo/renderer/Vulkan/RenderDebug_VK.cpp b/neo/renderer/Vulkan/RenderDebug_VK.cpp index 93f46a33..305dd4bb 100644 --- a/neo/renderer/Vulkan/RenderDebug_VK.cpp +++ b/neo/renderer/Vulkan/RenderDebug_VK.cpp @@ -563,13 +563,20 @@ void idRenderBackend::DBG_TestImage() { cinData_t cin; - cin = tr.testVideo->ImageForTime( viewDef->renderView.time[1] - tr.testVideoStartTime ); + // SRS - Don't need calibrated time for testing cinematics, so just call ImageForTime( 0 ) for current system time + // This simplification allows cinematic test playback to work over both 2D and 3D background scenes + cin = tr.testVideo->ImageForTime( 0 /*viewDef->renderView.time[1] - tr.testVideoStartTime*/ ); if( cin.imageY != NULL ) { image = cin.imageY; imageCr = cin.imageCr; imageCb = cin.imageCb; } + // SRS - Also handle ffmpeg and original RoQ decoders for test videos (using cin.image) + else if( cin.image != NULL ) + { + image = cin.image; + } else { tr.testImage = NULL; @@ -610,7 +617,7 @@ void idRenderBackend::DBG_TestImage() scale[0] = w; // scale scale[5] = h; // scale scale[12] = halfScreenWidth - ( halfScreenWidth * w ); // translate - scale[13] = halfScreenHeight - ( halfScreenHeight * h ); // translate + scale[13] = halfScreenHeight - ( halfScreenHeight * h ) - h; // translate (SRS - moved up by h) scale[10] = 1.0f; scale[15] = 1.0f; @@ -646,7 +653,9 @@ void idRenderBackend::DBG_TestImage() GL_SelectTexture( 2 ); imageCb->Bind(); - renderProgManager.BindShader_Bink(); + // SRS - Use Bink shader with no sRGB to linear conversion, otherwise cinematic colours may be wrong + // BindShader_BinkGUI() does not seem to work here - perhaps due to vertex shader input dependencies? + renderProgManager.BindShader_Bink_sRGB(); } else { diff --git a/neo/sound/CinematicAudio.h b/neo/sound/CinematicAudio.h index 08042f37..009de0a1 100644 --- a/neo/sound/CinematicAudio.h +++ b/neo/sound/CinematicAudio.h @@ -30,6 +30,7 @@ class CinematicAudio public: virtual void InitAudio( void* audioContext ) = 0; virtual void PlayAudio( uint8_t* data, int size ) = 0; + virtual void ResetAudio() = 0; virtual void ShutdownAudio() = 0; }; diff --git a/neo/sound/OpenAL/AL_CinematicAudio.cpp b/neo/sound/OpenAL/AL_CinematicAudio.cpp index 25f23133..36176f03 100644 --- a/neo/sound/OpenAL/AL_CinematicAudio.cpp +++ b/neo/sound/OpenAL/AL_CinematicAudio.cpp @@ -108,28 +108,35 @@ void CinematicAudio_OpenAL::PlayAudio( uint8_t* data, int size ) if( trigger ) { - tBuffer->push( data ); - sizes->push( size ); + tBuffer.push( data ); + sizes.push( size ); while( processed > 0 ) { ALuint bufid; - alSourceUnqueueBuffers( alMusicSourceVoicecin, 1, &bufid ); - processed--; - if( !tBuffer->empty() ) + // SRS - Only unqueue an alBuffer if we don't already have a free bufid to use + if( bufids.size() == 0 ) { - int tempSize = sizes->front(); - sizes->pop(); - uint8_t* tempdata = tBuffer->front(); - tBuffer->pop(); + alSourceUnqueueBuffers( alMusicSourceVoicecin, 1, &bufid ); + bufids.push( bufid ); + processed--; + } + if( !tBuffer.empty() ) + { + uint8_t* tempdata = tBuffer.front(); + tBuffer.pop(); + int tempSize = sizes.front(); + sizes.pop(); if( tempSize > 0 ) { + bufid = bufids.front(); + bufids.pop(); alBufferData( bufid, av_sample_cin, tempdata, tempSize, av_rate_cin ); // SRS - We must free the audio buffer once it has been copied into an alBuffer #if defined(USE_FFMPEG) av_freep( &tempdata ); #elif defined(USE_BINKDEC) - free( tempdata ); + Mem_Free( tempdata ); #endif alSourceQueueBuffers( alMusicSourceVoicecin, 1, &bufid ); ALenum error = alGetError(); @@ -140,6 +147,10 @@ void CinematicAudio_OpenAL::PlayAudio( uint8_t* data, int size ) } } } + else // SRS - When no new audio frames left to queue, break and continue playing + { + break; + } } } else @@ -149,7 +160,7 @@ void CinematicAudio_OpenAL::PlayAudio( uint8_t* data, int size ) #if defined(USE_FFMPEG) av_freep( &data ); #elif defined(USE_BINKDEC) - free( data ); + Mem_Free( data ); #endif offset++; if( offset == NUM_BUFFERS ) @@ -186,18 +197,44 @@ void CinematicAudio_OpenAL::PlayAudio( uint8_t* data, int size ) } } +void CinematicAudio_OpenAL::ResetAudio() +{ + if( alIsSource( alMusicSourceVoicecin ) ) + { + alSourceRewind( alMusicSourceVoicecin ); + alSourcei( alMusicSourceVoicecin, AL_BUFFER, 0 ); + } + + while( !tBuffer.empty() ) + { + uint8_t* tempdata = tBuffer.front(); + tBuffer.pop(); + sizes.pop(); + if( tempdata ) + { + // SRS - We must free any audio buffers that have not been copied into an alBuffer +#if defined(USE_FFMPEG) + av_freep( &tempdata ); +#elif defined(USE_BINKDEC) + Mem_Free( tempdata ); +#endif + } + } + + while( !bufids.empty() ) + { + bufids.pop(); + } + + offset = 0; + trigger = false; +} + void CinematicAudio_OpenAL::ShutdownAudio() { if( alIsSource( alMusicSourceVoicecin ) ) { alSourceStop( alMusicSourceVoicecin ); - // SRS - Make sure we don't try to unqueue buffers that were never processed - ALint processed; - alGetSourcei( alMusicSourceVoicecin, AL_BUFFERS_PROCESSED, &processed ); - if( processed > 0 ) - { - alSourceUnqueueBuffers( alMusicSourceVoicecin, processed, alMusicBuffercin ); - } alSourcei( alMusicSourceVoicecin, AL_BUFFER, 0 ); alDeleteSources( 1, &alMusicSourceVoicecin ); if( CheckALErrors() == AL_NO_ERROR ) @@ -210,29 +247,25 @@ void CinematicAudio_OpenAL::ShutdownAudio() { alDeleteBuffers( NUM_BUFFERS, alMusicBuffercin ); } - if( !tBuffer->empty() ) + + while( !tBuffer.empty() ) { - int buffersize = tBuffer->size(); - while( buffersize > 0 ) + uint8_t* tempdata = tBuffer.front(); + tBuffer.pop(); + sizes.pop(); + if( tempdata ) { - uint8_t* tempdata = tBuffer->front(); - tBuffer->pop(); // SRS - We must free any audio buffers that have not been copied into an alBuffer #if defined(USE_FFMPEG) av_freep( &tempdata ); #elif defined(USE_BINKDEC) - free( tempdata ); + Mem_Free( tempdata ); #endif - buffersize--; } } - if( !sizes->empty() ) + + while( !bufids.empty() ) { - int buffersize = sizes->size(); - while( buffersize > 0 ) - { - sizes->pop(); - buffersize--; - } + bufids.pop(); } } diff --git a/neo/sound/OpenAL/AL_CinematicAudio.h b/neo/sound/OpenAL/AL_CinematicAudio.h index a9ed7200..cc0ca6b9 100644 --- a/neo/sound/OpenAL/AL_CinematicAudio.h +++ b/neo/sound/OpenAL/AL_CinematicAudio.h @@ -41,6 +41,7 @@ public: CinematicAudio_OpenAL(); void InitAudio( void* audioContext ); void PlayAudio( uint8_t* data, int size ); + void ResetAudio(); void ShutdownAudio(); private: ALuint alMusicSourceVoicecin; @@ -55,8 +56,9 @@ private: // So, what happens if there are no freely available buffers but we still geting audio frames ? Loss of data. // That why now I am using two queues in order to store the frames (and their sizes) and when we have available buffers, // then start popping those frames instead of the current, so we don't lose any audio frames and the sound doesn't crack anymore. - std::queue tBuffer[NUM_BUFFERS]; - std::queue sizes[NUM_BUFFERS]; + std::queue tBuffer; + std::queue sizes; + std::queue bufids; // SRS - Added queue of free alBuffer ids to handle audio frame underflow (starvation) case }; #endif diff --git a/neo/sound/OpenAL/AL_SoundVoice.cpp b/neo/sound/OpenAL/AL_SoundVoice.cpp index d5e09644..39ff1186 100644 --- a/neo/sound/OpenAL/AL_SoundVoice.cpp +++ b/neo/sound/OpenAL/AL_SoundVoice.cpp @@ -206,11 +206,14 @@ void idSoundVoice_OpenAL::DestroyInternal() idLib::Printf( "%dms: %i destroyed\n", Sys_Milliseconds(), openalSource ); } + // SRS - Make sure the source is stopped before detaching buffers + alSourceStop( openalSource ); + alSourcei( openalSource, AL_BUFFER, 0 ); + + // SRS - Delete source only after detaching buffers above alDeleteSources( 1, &openalSource ); openalSource = 0; - alSourcei( openalSource, AL_BUFFER, 0 ); - if( openalStreamingBuffer[0] && openalStreamingBuffer[1] && openalStreamingBuffer[2] ) { CheckALErrors(); diff --git a/neo/sound/XAudio2/XA2_CinematicAudio.cpp b/neo/sound/XAudio2/XA2_CinematicAudio.cpp index ac5f1090..488d8b1b 100644 --- a/neo/sound/XAudio2/XA2_CinematicAudio.cpp +++ b/neo/sound/XAudio2/XA2_CinematicAudio.cpp @@ -52,7 +52,7 @@ public: #if defined(USE_FFMPEG) av_freep( &data ); #elif defined(USE_BINKDEC) - free( data ); + Mem_Free( data ); #endif } //Unused methods are stubs @@ -179,6 +179,15 @@ void CinematicAudio_XAudio2::PlayAudio( uint8_t* data, int size ) } } +void CinematicAudio_XAudio2::ResetAudio() +{ + if( pMusicSourceVoice1 ) + { + pMusicSourceVoice1->Stop(); + pMusicSourceVoice1->FlushSourceBuffers(); + } +} + void CinematicAudio_XAudio2::ShutdownAudio() { if( pMusicSourceVoice1 ) diff --git a/neo/sound/XAudio2/XA2_CinematicAudio.h b/neo/sound/XAudio2/XA2_CinematicAudio.h index 5c64a972..998a6594 100644 --- a/neo/sound/XAudio2/XA2_CinematicAudio.h +++ b/neo/sound/XAudio2/XA2_CinematicAudio.h @@ -33,6 +33,7 @@ public: CinematicAudio_XAudio2(); void InitAudio( void* audioContext ); void PlayAudio( uint8_t* data, int size ); + void ResetAudio(); void ShutdownAudio(); private: WAVEFORMATEX voiceFormatcine = { 0 }; diff --git a/neo/sound/snd_system.cpp b/neo/sound/snd_system.cpp index 76b789a3..9cf15661 100644 --- a/neo/sound/snd_system.cpp +++ b/neo/sound/snd_system.cpp @@ -532,6 +532,7 @@ cinData_t idSoundSystemLocal::ImageForTime( const int milliseconds, const bool w cd.imageY = NULL; cd.imageCr = NULL; cd.imageCb = NULL; + cd.image = NULL; cd.imageWidth = 0; cd.imageHeight = 0; cd.status = FMV_IDLE;