ffmpeg 5 compatibility plus cinematic audio playback on OpenAL & XAudio2

(cherry picked from commit bcb683e8e6ba6cb23acac2f1121c6e3eece1ed01)
This commit is contained in:
Stephen Saunders 2022-02-05 00:24:07 -05:00
parent 813767feea
commit 3be85d9c4b
9 changed files with 770 additions and 35 deletions

View file

@ -680,7 +680,8 @@ set(SOUND_INCLUDES
sound/snd_local.h
sound/sound.h
sound/SoundVoice.h
sound/WaveFile.h)
sound/WaveFile.h
sound/CinematicAudio.h)
set(SOUND_SOURCES
sound/snd_emitter.cpp
@ -688,28 +689,31 @@ set(SOUND_SOURCES
sound/snd_system.cpp
sound/snd_world.cpp
sound/SoundVoice.cpp
sound/WaveFile.cpp
)
sound/WaveFile.cpp)
set(XAUDIO2_INCLUDES
sound/XAudio2/XA2_SoundHardware.h
sound/XAudio2/XA2_SoundSample.h
sound/XAudio2/XA2_SoundVoice.h)
sound/XAudio2/XA2_SoundVoice.h
sound/Xaudio2/XA2_CinematicAudio.h)
set(XAUDIO2_SOURCES
sound/XAudio2/XA2_SoundHardware.cpp
sound/XAudio2/XA2_SoundSample.cpp
sound/XAudio2/XA2_SoundVoice.cpp)
sound/XAudio2/XA2_SoundVoice.cpp
sound/Xaudio2/XA2_CinematicAudio.cpp)
set(OPENAL_INCLUDES
sound/OpenAL/AL_SoundHardware.h
sound/OpenAL/AL_SoundSample.h
sound/OpenAL/AL_SoundVoice.h)
sound/OpenAL/AL_SoundVoice.h
sound/OpenAL/AL_CinematicAudio.h)
set(OPENAL_SOURCES
sound/OpenAL/AL_SoundHardware.cpp
sound/OpenAL/AL_SoundSample.cpp
sound/OpenAL/AL_SoundVoice.cpp)
sound/OpenAL/AL_SoundVoice.cpp
sound/OpenAL/AL_CinematicAudio.cpp)
set(STUBAUDIO_INCLUDES
sound/stub/SoundStub.h)
@ -1533,16 +1537,18 @@ if(MSVC)
include_directories(libs/ffmpeg-win64/include)
include_directories(libs/ffmpeg-win64/include/libswscale)
include_directories(libs/ffmpeg-win64/include/libavformat)
include_directories(libs/ffmpeg-win64/include/libavdevice)
include_directories(libs/ffmpeg-win64/include/libavutil)
include_directories(libs/ffmpeg-win64/include/libavcodec)
include_directories(libs/ffmpeg-win64/include/libswresample)
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/libs/ffmpeg-win64/lib)
else()
include_directories(libs/ffmpeg-win32/include)
include_directories(libs/ffmpeg-win32/include/libswscale)
include_directories(libs/ffmpeg-win32/include/libavformat)
include_directories(libs/ffmpeg-win32/include/libavdevice)
include_directories(libs/ffmpeg-win32/include/libavutil)
include_directories(libs/ffmpeg-win32/include/libavcodec)
include_directories(libs/ffmpeg-win32/include/libswresample)
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/libs/ffmpeg-win32/lib)
endif()
@ -1551,7 +1557,8 @@ if(MSVC)
avcodec
avformat
avutil
swscale)
swscale
swresample)
endif()
@ -1717,8 +1724,8 @@ else()
# SRS - Added support for OpenAL Soft headers on OSX (vs default macOS SDK headers)
if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin" AND NOT OPENAL_INCLUDE_DIR MATCHES "SDKs/MacOSX.*\.sdk")
include_directories(${OPENAL_INCLUDE_DIR})
add_definitions(-DUSE_OPENAL_SOFT_INCLUDES)
include_directories(${OPENAL_INCLUDE_DIR})
add_definitions(-DUSE_OPENAL_SOFT_INCLUDES)
endif()
list(APPEND RBDOOM3_INCLUDES ${OPENAL_INCLUDES})

View file

@ -7,6 +7,8 @@
# FFMPEG_LIBAVCODEC
# FFMPEG_LIBAVFORMAT
# FFMPEG_LIBAVUTIL
# FFMPEG_LIBSWSCALE
# FFMPEG_LIBSWRESAMPLE
#
# Copyright (c) 2008 Andreas Schneider <mail@cynapses.org>
# Modified for other libraries by Lasse Kärkkäinen <tronic>
@ -29,6 +31,7 @@ else (FFMPEG_LIBRARIES AND FFMPEG_INCLUDE_DIR)
pkg_check_modules(_FFMPEG_AVFORMAT libavformat)
pkg_check_modules(_FFMPEG_AVUTIL libavutil)
pkg_check_modules(_FFMPEG_SWSCALE libswscale)
pkg_check_modules(_FFMPEG_SWRESAMPLE libswresample)
endif (PKG_CONFIG_FOUND)
find_path(FFMPEG_AVCODEC_INCLUDE_DIR
@ -57,7 +60,12 @@ else (FFMPEG_LIBRARIES AND FFMPEG_INCLUDE_DIR)
PATHS ${_FFMPEG_SWSCALE_LIBRARY_DIRS} /usr/lib /usr/local/lib /opt/local/lib /sw/lib
)
if (FFMPEG_LIBAVCODEC AND FFMPEG_LIBAVFORMAT AND FFMPEG_LIBAVUTIL AND FFMPEG_LIBSWSCALE)
find_library(FFMPEG_LIBSWRESAMPLE
NAMES swresample
PATHS ${_FFMPEG_SWRESAMPLE_LIBRARY_DIRS} /usr/lib /usr/local/lib /opt/local/lib /sw/lib
)
if (FFMPEG_LIBAVCODEC AND FFMPEG_LIBAVFORMAT AND FFMPEG_LIBAVUTIL AND FFMPEG_LIBSWSCALE AND FFMPEG_LIBSWRESAMPLE)
set(FFMPEG_FOUND TRUE)
endif()
@ -69,6 +77,7 @@ else (FFMPEG_LIBRARIES AND FFMPEG_INCLUDE_DIR)
${FFMPEG_LIBAVFORMAT}
${FFMPEG_LIBAVUTIL}
${FFMPEG_LIBSWSCALE}
${FFMPEG_LIBSWRESAMPLE}
)
endif (FFMPEG_FOUND)

View file

@ -5028,6 +5028,8 @@ bool idGameLocal::SkipCinematic( void )
{
skipCinematic = true;
cinematicMaxSkipTime = gameLocal.time + SEC2MS( g_cinematicMaxSkipTime.GetFloat() );
// SRS - Skip the remainder of the currently playing cinematic sound
soundSystem->GetPlayingSoundWorld()->Skip( cinematicMaxSkipTime );
}
return true;

View file

@ -30,6 +30,11 @@ If you have questions concerning this license or the applicable additional terms
#include "precompiled.h"
#pragma hdrstop
#if defined(_MSC_VER) && !defined(USE_OPENAL)
#include <sound/XAudio2/XA2_CinematicAudio.h>
#else
#include <sound/OpenAL/AL_CinematicAudio.h>
#endif
extern idCVar s_noSound;
@ -63,7 +68,10 @@ extern "C"
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
#include <libavutil/imgutils.h>
}
bool hasplanar = true;
#endif
#ifdef USE_BINKDEC
@ -99,18 +107,27 @@ private:
#if defined(USE_FFMPEG)
int video_stream_index;
int audio_stream_index; //GK: Make extra indexer for audio
AVFormatContext* fmt_ctx;
AVFrame* frame;
AVFrame* frame2;
AVFrame* frame3; //GK: make extra frame for audio
#if LIBAVCODEC_VERSION_MAJOR > 58
const AVCodec* dec;
#else
AVCodec* dec;
#endif
AVCodecContext* dec_ctx;
AVCodecContext* dec_ctx2;
SwsContext* img_convert_ctx;
bool hasFrame;
long framePos;
AVSampleFormat dst_smp;
SwrContext* swr_ctx;
cinData_t ImageForTimeFFMPEG( int milliseconds );
bool InitFromFFMPEGFile( const char* qpath, bool looping );
void FFMPEGReset();
std::queue<AVPacket> packets[NUM_BUFFERS];
#endif
#ifdef USE_BINKDEC
BinkHandle binkHandle;
@ -193,6 +210,9 @@ private:
void RoQPrepMcomp( int xoff, int yoff );
void RoQReset();
// RB end
//GK:Also init variables for XAudio2 or OpenAL (SRS - this must be an instance variable)
CinematicAudio* cinematicAudio;
};
// Carl: ROQ files from original Doom 3
@ -237,13 +257,6 @@ idCinematicLocal::InitCinematic
// RB: 64 bit fixes, changed long to int
void idCinematic::InitCinematic()
{
#if defined(USE_FFMPEG)
// Carl: ffmpeg for Bink and regular video files
//common->Warning( "Loading FFMPEG...\n" );
avcodec_register_all();
av_register_all();
#endif
// Carl: Doom 3 ROQ:
float t_ub, t_vr, t_ug, t_vg;
int i;
@ -417,13 +430,17 @@ idCinematicLocal::idCinematicLocal()
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55,28,1)
frame = av_frame_alloc();
frame2 = av_frame_alloc();
frame3 = av_frame_alloc();
#else
frame = avcodec_alloc_frame();
frame2 = avcodec_alloc_frame();
frame3 = avcodec_alloc_frame();
#endif // LIBAVCODEC_VERSION_INT
dec_ctx = NULL;
dec_ctx2 = NULL;
fmt_ctx = NULL;
video_stream_index = -1;
audio_stream_index = -1;
img_convert_ctx = NULL;
hasFrame = false;
framePos = -1;
@ -479,6 +496,12 @@ idCinematicLocal::idCinematicLocal()
img->AllocImage( opts, TF_LINEAR, TR_REPEAT );
}
//GK: Make sure the cinematic voices are the first to be initialized
#if defined(_MSC_VER) && !defined(USE_OPENAL)
cinematicAudio = new( TAG_AUDIO ) CinematicAudio_XAudio2;
#else
cinematicAudio = new( TAG_AUDIO ) CinematicAudio_OpenAL;
#endif
}
/*
@ -500,12 +523,16 @@ idCinematicLocal::~idCinematicLocal()
// Carl: ffmpeg for bink and other video files:
// RB: TODO double check this. It seems we have different versions of ffmpeg on Kubuntu 13.10 and the win32 development files
#if defined(_WIN32) || defined(_WIN64)
//#if defined(_WIN32) || defined(_WIN64)
// SRS - Should use the same version criteria as when the frames are allocated in idCinematicLocal() above
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55,28,1)
av_frame_free( &frame );
av_frame_free( &frame2 );
av_frame_free( &frame3 );
#else
av_freep( &frame );
av_freep( &frame2 );
av_freep( &frame3 );
#endif
if( fmt_ctx )
@ -535,6 +562,51 @@ idCinematicLocal::~idCinematicLocal()
delete img;
img = NULL;
//GK: Properly close local XAudio2 or OpenAL voice
cinematicAudio->ShutdownAudio();
}
#if defined(USE_FFMPEG)
/*
==============
idCinematicLocal::GetSampleFormat
==============
*/
const char* GetSampleFormat( AVSampleFormat sample_fmt )
{
switch( sample_fmt )
{
case AV_SAMPLE_FMT_U8:
case AV_SAMPLE_FMT_U8P:
{
return "8-bit";
}
case AV_SAMPLE_FMT_S16:
case AV_SAMPLE_FMT_S16P:
{
return "16-bit";
}
case AV_SAMPLE_FMT_S32:
case AV_SAMPLE_FMT_S32P:
{
return "32-bit";
}
case AV_SAMPLE_FMT_FLT:
case AV_SAMPLE_FMT_FLTP:
{
return "Float";
}
case AV_SAMPLE_FMT_DBL:
case AV_SAMPLE_FMT_DBLP:
{
return "Double";
}
default:
{
return "Unknown";
}
}
}
/*
@ -542,10 +614,11 @@ idCinematicLocal::~idCinematicLocal()
idCinematicLocal::InitFromFFMPEGFile
==============
*/
#if defined(USE_FFMPEG)
bool idCinematicLocal::InitFromFFMPEGFile( const char* qpath, bool amilooping )
{
int ret;
int ret2;
int file_size;
looping = amilooping;
startTime = 0;
isRoQ = false;
@ -557,6 +630,7 @@ bool idCinematicLocal::InitFromFFMPEGFile( const char* qpath, bool amilooping )
if( testFile )
{
fullpath = testFile->GetFullPath();
file_size = testFile->Length();
fileSystem->CloseFile( testFile );
}
// RB: case sensitivity HACK for Linux
@ -569,6 +643,7 @@ bool idCinematicLocal::InitFromFFMPEGFile( const char* qpath, bool amilooping )
if( testFile )
{
fullpath = testFile->GetFullPath();
file_size = testFile->Length();
fileSystem->CloseFile( testFile );
}
else
@ -598,14 +673,65 @@ bool idCinematicLocal::InitFromFFMPEGFile( const char* qpath, bool amilooping )
return false;
}
video_stream_index = ret;
dec_ctx = fmt_ctx->streams[video_stream_index]->codec;
dec_ctx = avcodec_alloc_context3( dec );
if( ( ret = avcodec_parameters_to_context( dec_ctx, fmt_ctx->streams[video_stream_index]->codecpar ) ) < 0 )
{
char* error = new char[256];
av_strerror( ret, error, 256 );
common->Warning( "idCinematic: Failed to create codec context from codec parameters with error: %s\n", error );
}
dec_ctx->time_base = fmt_ctx->streams[video_stream_index]->time_base;
dec_ctx->framerate = fmt_ctx->streams[video_stream_index]->avg_frame_rate;
dec_ctx->pkt_timebase = fmt_ctx->streams[video_stream_index]->time_base;
/* init the video decoder */
if( ( ret = avcodec_open2( dec_ctx, dec, NULL ) ) < 0 )
{
common->Warning( "idCinematic: Cannot open video decoder for: '%s', %d\n", qpath, looping );
char* error = new char[256];
av_strerror( ret, error, 256 );
common->Warning( "idCinematic: Cannot open video decoder for: '%s', %d, with message: %s\n", qpath, looping, error );
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 );
if( ret2 >= 0 ) //Make audio optional (only intro video has audio no other)
{
audio_stream_index = ret2;
dec_ctx2 = avcodec_alloc_context3( dec );
if( ( ret2 = avcodec_parameters_to_context( dec_ctx2, fmt_ctx->streams[audio_stream_index]->codecpar ) ) < 0 )
{
char* error = new char[256];
av_strerror( ret2, error, 256 );
common->Warning( "idCinematic: Failed to create codec context from codec parameters with error: %s\n", error );
}
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 )
{
common->Warning( "idCinematic: Cannot open audio decoder for: '%s', %d\n", qpath, looping );
//return false;
}
if( dec_ctx2->sample_fmt >= 5 )
{
dst_smp = static_cast<AVSampleFormat>( dec_ctx2->sample_fmt - 5 );
swr_ctx = swr_alloc_set_opts( NULL, dec_ctx2->channel_layout, dst_smp, dec_ctx2->sample_rate, dec_ctx2->channel_layout, dec_ctx2->sample_fmt, dec_ctx2->sample_rate, 0, NULL );
int res = swr_init( swr_ctx );
hasplanar = true;
}
else
{
hasplanar = false;
}
common->Printf( "Cinematic audio stream found: Sample Rate=%d Hz, Channels=%d, Format=%s, Planar=%d\n", dec_ctx2->sample_rate, dec_ctx2->channels, GetSampleFormat( dec_ctx2->sample_fmt ), hasplanar );
cinematicAudio->InitAudio( dec_ctx2 );
}
else
{
// SRS - Most cinematics have no audio, so disable the warning to reduce distracting log messages
//common->Warning("idCinematic: Cannot find an audio stream in: '%s', %d\n", qpath, looping);
}
//GK:End
CIN_WIDTH = dec_ctx->width;
CIN_HEIGHT = dec_ctx->height;
/** Calculate Duration in seconds
@ -626,12 +752,24 @@ bool idCinematicLocal::InitFromFFMPEGFile( const char* qpath, bool amilooping )
*/
int ticksPerFrame = dec_ctx->ticks_per_frame;
float durationSec = static_cast<double>( fmt_ctx->streams[video_stream_index]->duration ) * static_cast<double>( ticksPerFrame ) / static_cast<double>( avr.den );
//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 )
{
durationSec = file_size / dec_ctx->bit_rate;
}
else
{
durationSec = 100;
}
}
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, %f FPS, %f sec\n", qpath, looping, CIN_WIDTH, CIN_HEIGHT, frameRate, durationSec );
image = ( byte* )Mem_Alloc( CIN_WIDTH * CIN_HEIGHT * 4 * 2, TAG_CINEMATIC );
avpicture_fill( ( AVPicture* )frame2, image, AV_PIX_FMT_BGR32, CIN_WIDTH, CIN_HEIGHT );
image = ( byte* )Mem_Alloc( CIN_WIDTH * CIN_HEIGHT * 4 * 2, 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 );
@ -666,7 +804,7 @@ void idCinematicLocal::FFMPEGReset()
{
status = FMV_LOOPED;
}
else
else if( av_seek_frame( fmt_ctx, audio_stream_index, 0, 0 ) < 0 && av_seek_frame( fmt_ctx, video_stream_index, 0, 0 ) < 0 )
{
status = FMV_EOF;
}
@ -1080,7 +1218,9 @@ idCinematicLocal::ImageForTimeFFMPEG
cinData_t idCinematicLocal::ImageForTimeFFMPEG( int thisTime )
{
cinData_t cinData;
uint8_t** tBuffer2 = NULL;
int num_bytes = 0;
if( thisTime <= 0 )
{
thisTime = Sys_Milliseconds();
@ -1130,10 +1270,14 @@ cinData_t idCinematicLocal::ImageForTimeFFMPEG( int thisTime )
AVPacket packet;
while( framePos < desiredFrame )
{
int frameFinished = 0;
int frameFinished = -1;
//GK: Separate frame finisher for audio in order to not have the video lagging
int frameFinished1 = -1;
int res = 0;
// Do a single frame by getting packets until we have a full frame
while( !frameFinished )
while( frameFinished != 0 )
{
// if we got to the end or failed
if( av_read_frame( fmt_ctx, &packet ) < 0 )
@ -1163,10 +1307,74 @@ cinData_t idCinematicLocal::ImageForTimeFFMPEG( int thisTime )
if( packet.stream_index == video_stream_index )
{
// Decode video frame
avcodec_decode_video2( dec_ctx, frame, &frameFinished, &packet );
if( ( res = avcodec_send_packet( dec_ctx, &packet ) ) != 0 )
{
char* error = new char[256];
av_strerror( res, error, 256 );
common->Warning( "idCinematic: Failed to send packet for decoding with message: %s\n", error );
}
else
{
if( ( frameFinished = avcodec_receive_frame( dec_ctx, frame ) ) != 0 )
{
char* error = new char[256];
av_strerror( frameFinished, error, 256 );
common->Warning( "idCinematic: Failed to receive frame from decoding with message: %s\n", error );
}
}
}
//GK:Begin
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() );
if( res != 0 && res != AVERROR( EAGAIN ) )
{
char* error = new char[256];
av_strerror( res, error, 256 );
common->Warning( "idCinematic: Failed to send packet for decoding with message: %s\n", error );
}
else
{
packet = packets->front();
packets->pop();
if( ( frameFinished1 = avcodec_receive_frame( dec_ctx2, frame3 ) ) != 0 )
{
char* error = new char[256];
av_strerror( frameFinished1, error, 256 );
common->Warning( "idCinematic: Failed to receive frame from decoding with message: %s\n", error );
}
else
{
int bufflinesize;
if( hasplanar )
{
av_samples_alloc_array_and_samples( &tBuffer2,
&bufflinesize,
frame3->channels,
av_rescale_rnd( frame3->nb_samples, frame3->sample_rate, frame3->sample_rate, AV_ROUND_UP ),
dst_smp,
0 );
int res = swr_convert( swr_ctx, tBuffer2, bufflinesize, ( const uint8_t** )frame3->extended_data, frame3->nb_samples );
num_bytes = av_samples_get_buffer_size( &bufflinesize, frame3->channels,
res, dst_smp, 1 );
}
else
{
num_bytes = frame3->linesize[0];
tBuffer2 = ( uint8_t** )malloc( sizeof( frame3->extended_data ) / sizeof( uint8_t* ) );
tBuffer2[0] = ( uint8_t* )malloc( num_bytes );
if( num_bytes > 0 )
{
memcpy( tBuffer2[0], frame3->extended_data[0], num_bytes );
}
}
}
}
}
//GK:End
// Free the packet that was allocated by av_read_frame
av_free_packet( &packet );
av_packet_unref( &packet );
}
framePos++;
@ -1181,6 +1389,12 @@ cinData_t idCinematicLocal::ImageForTimeFFMPEG( int thisTime )
img->UploadScratch( image, CIN_WIDTH, CIN_HEIGHT );
hasFrame = true;
cinData.image = img;
// SRS - If we have cinematic audio data, start playing it now
if( tBuffer2 )
{
cinematicAudio->PlayAudio( tBuffer2[0], num_bytes );
}
return cinData;
}

View file

@ -0,0 +1,36 @@
/**
* Copyright (C) 2021 George Kalmpokis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to
* do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software. As clarification, there
* is no requirement that the copyright notice and permission be included in
* binary distributions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <precompiled.h>
#ifndef __CINEMATIC_AUDIO_H__
#define __CINEMATIC_AUDIO_H__
class CinematicAudio
{
public:
virtual void InitAudio( void* audioContext ) = 0;
virtual void PlayAudio( uint8_t* data, int size ) = 0;
virtual void ShutdownAudio() = 0;
};
#endif

View file

@ -0,0 +1,216 @@
/**
* Copyright (C) 2021 George Kalmpokis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to
* do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software. As clarification, there
* is no requirement that the copyright notice and permission be included in
* binary distributions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "precompiled.h"
#include "AL_CinematicAudio.h"
#include <sound/snd_local.h>
#if defined(USE_FFMPEG)
extern "C"
{
#define __STDC_CONSTANT_MACROS
#include <libavcodec/avcodec.h>
}
#endif
extern idCVar s_noSound;
extern idCVar s_volume_dB;
CinematicAudio_OpenAL::CinematicAudio_OpenAL():
av_rate_cin( 0 ),
av_sample_cin( 0 ),
offset( 0 ),
trigger( false )
{
alGenSources( 1, &alMusicSourceVoicecin );
alSource3i( alMusicSourceVoicecin, AL_POSITION, 0, 0, 0 );
alSourcei( alMusicSourceVoicecin, AL_SOURCE_RELATIVE, AL_TRUE );
alSourcei( alMusicSourceVoicecin, AL_ROLLOFF_FACTOR, 0 );
alListenerf( AL_GAIN, s_noSound.GetBool() ? 0.0f : DBtoLinear( s_volume_dB.GetFloat() ) ); //GK: Set the sound volume the same that is used in DOOM 3
alGenBuffers( NUM_BUFFERS, &alMusicBuffercin[0] );
}
void CinematicAudio_OpenAL::InitAudio( void* audioContext )
{
//SRS - This InitAudio() implementation is FFMPEG-only until we have a BinkDec solution as well
#if defined(USE_FFMPEG)
AVCodecContext* dec_ctx2 = ( AVCodecContext* )audioContext;
av_rate_cin = dec_ctx2->sample_rate;
switch( dec_ctx2->sample_fmt )
{
case AV_SAMPLE_FMT_U8:
case AV_SAMPLE_FMT_U8P:
{
av_sample_cin = dec_ctx2->channels == 2 ? AL_FORMAT_STEREO8 : AL_FORMAT_MONO8;
break;
}
case AV_SAMPLE_FMT_S16:
case AV_SAMPLE_FMT_S16P:
{
av_sample_cin = dec_ctx2->channels == 2 ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16;
break;
}
case AV_SAMPLE_FMT_FLT:
case AV_SAMPLE_FMT_FLTP:
{
av_sample_cin = dec_ctx2->channels == 2 ? AL_FORMAT_STEREO_FLOAT32 : AL_FORMAT_MONO_FLOAT32;
break;
}
default:
{
common->Warning( "Unknown or incompatible cinematic audio format for OpenAL, sample_fmt = %d\n", dec_ctx2->sample_fmt );
return;
}
}
alSourceRewind( alMusicSourceVoicecin );
alSourcei( alMusicSourceVoicecin, AL_BUFFER, 0 );
offset = 0;
trigger = false;
#endif
}
void CinematicAudio_OpenAL::PlayAudio( uint8_t* data, int size )
{
ALint processed, state;
alGetSourcei( alMusicSourceVoicecin, AL_SOURCE_STATE, &state );
alGetSourcei( alMusicSourceVoicecin, AL_BUFFERS_PROCESSED, &processed );
if( trigger )
{
tBuffer->push( data );
sizes->push( size );
while( processed > 0 )
{
ALuint bufid;
alSourceUnqueueBuffers( alMusicSourceVoicecin, 1, &bufid );
processed--;
if( !tBuffer->empty() )
{
int tempSize = sizes->front();
sizes->pop();
uint8_t* tempdata = tBuffer->front();
tBuffer->pop();
if( tempSize > 0 )
{
alBufferData( bufid, av_sample_cin, tempdata, tempSize, av_rate_cin );
alSourceQueueBuffers( alMusicSourceVoicecin, 1, &bufid );
ALenum error = alGetError();
if( error != AL_NO_ERROR )
{
common->Warning( "OpenAL Cinematic: %s\n", alGetString( error ) );
return;
}
}
offset++;
if( offset == NUM_BUFFERS )
{
offset = 0;
}
}
}
}
else
{
alBufferData( alMusicBuffercin[offset], av_sample_cin, data, size, av_rate_cin );
offset++;
if( offset == NUM_BUFFERS )
{
alSourceQueueBuffers( alMusicSourceVoicecin, offset, alMusicBuffercin );
ALenum error = alGetError();
if( error != AL_NO_ERROR )
{
common->Warning( "OpenAL Cinematic: %s\n", alGetString( error ) );
return;
}
trigger = true;
offset = 0;
}
}
if( trigger )
{
if( state != AL_PLAYING )
{
ALint queued;
alGetSourcei( alMusicSourceVoicecin, AL_BUFFERS_QUEUED, &queued );
if( queued == 0 )
{
return;
}
alSourcePlay( alMusicSourceVoicecin );
ALenum error = alGetError();
if( error != AL_NO_ERROR )
{
common->Warning( "OpenAL Cinematic: %s\n", alGetString( error ) );
return;
}
}
}
}
void CinematicAudio_OpenAL::ShutdownAudio()
{
if( alIsSource( alMusicSourceVoicecin ) )
{
alSourceStop( alMusicSourceVoicecin );
// SRS - Make sure we don't try to unqueue buffers that were never queued in the first place
if( !tBuffer->empty() )
{
alSourceUnqueueBuffers( alMusicSourceVoicecin, NUM_BUFFERS, alMusicBuffercin );
}
alSourcei( alMusicSourceVoicecin, AL_BUFFER, 0 );
alDeleteSources( 1, &alMusicSourceVoicecin );
if( CheckALErrors() == AL_NO_ERROR )
{
alMusicSourceVoicecin = 0;
}
}
if( alMusicBuffercin )
{
alDeleteBuffers( NUM_BUFFERS, alMusicBuffercin );
}
if( !tBuffer->empty() )
{
int buffersize = tBuffer->size();
while( buffersize > 0 )
{
tBuffer->pop();
buffersize--;
}
}
if( !sizes->empty() )
{
int buffersize = sizes->size();
while( buffersize > 0 )
{
sizes->pop();
buffersize--;
}
}
}

View file

@ -0,0 +1,61 @@
/**
* Copyright (C) 2021 George Kalmpokis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to
* do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software. As clarification, there
* is no requirement that the copyright notice and permission be included in
* binary distributions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <precompiled.h>
#include "../CinematicAudio.h"
// SRS - Added check on OSX for OpenAL Soft headers vs macOS SDK headers
#if defined(__APPLE__) && !defined(USE_OPENAL_SOFT_INCLUDES)
#include <OpenAL/al.h>
#else
#include <AL/al.h>
#endif
#include <queue>
#ifndef __CINEMATIC_AUDIO_AL_H__
#define __CINEMATIC_AUDIO_AL_H__
#define NUM_BUFFERS 4
class CinematicAudio_OpenAL: public CinematicAudio
{
public:
CinematicAudio_OpenAL();
void InitAudio( void* audioContext );
void PlayAudio( uint8_t* data, int size );
void ShutdownAudio();
private:
ALuint alMusicSourceVoicecin;
ALuint alMusicBuffercin[NUM_BUFFERS];
ALenum av_sample_cin;
int av_rate_cin;
int offset;
bool trigger;
//GK: Unlike XAudio2 which can accept buffer until the end of this world.
// OpenAL can accept buffers as long as there are freely available buffers.
// 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 poping those frames instead of the current, so we don't lose any audio frames and the sound doesn't crack anymore.
std::queue<uint8_t*> tBuffer[NUM_BUFFERS];
std::queue<int> sizes[NUM_BUFFERS];
};
#endif

View file

@ -0,0 +1,144 @@
/**
* Copyright (C) 2021 George Kalmpokis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to
* do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software. As clarification, there
* is no requirement that the copyright notice and permission be included in
* binary distributions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "precompiled.h"
#include "XA2_CinematicAudio.h"
#include <sound/snd_local.h>
#if defined(USE_FFMPEG)
extern "C"
{
#define __STDC_CONSTANT_MACROS
#include <libavcodec/avcodec.h>
}
#endif
void CinematicAudio_XAudio2::InitAudio( void* audioContext )
{
//SRS - This InitAudio() implementation is FFMPEG-only until we have a BinkDec solution as well
#if defined(USE_FFMPEG)
AVCodecContext* dec_ctx2 = ( AVCodecContext* )audioContext;
int format_byte = 0;
bool use_ext = false;
switch( dec_ctx2->sample_fmt )
{
case AV_SAMPLE_FMT_U8:
case AV_SAMPLE_FMT_U8P:
{
format_byte = 1;
break;
}
case AV_SAMPLE_FMT_S16:
case AV_SAMPLE_FMT_S16P:
{
format_byte = 2;
break;
}
case AV_SAMPLE_FMT_FLT:
case AV_SAMPLE_FMT_FLTP:
{
format_byte = 4;
use_ext = true;
break;
}
default:
{
common->Warning( "Unknown or incompatible cinematic audio format for XAudio2, sample_fmt = %d\n", dec_ctx2->sample_fmt );
return;
}
}
WAVEFORMATEXTENSIBLE exvoice = { 0 };
voiceFormatcine.wFormatTag = WAVE_FORMAT_EXTENSIBLE; //Use extensible wave format in order to handle properly the audio
voiceFormatcine.nChannels = dec_ctx2->channels; //fixed
voiceFormatcine.nSamplesPerSec = dec_ctx2->sample_rate; //fixed
voiceFormatcine.wBitsPerSample = format_byte * 8; //fixed
voiceFormatcine.nBlockAlign = format_byte * voiceFormatcine.nChannels; //fixed
voiceFormatcine.nAvgBytesPerSec = voiceFormatcine.nSamplesPerSec * voiceFormatcine.nBlockAlign; //fixed
voiceFormatcine.cbSize = 22; //fixed
exvoice.Format = voiceFormatcine;
switch( voiceFormatcine.nChannels )
{
case 1:
exvoice.dwChannelMask = SPEAKER_MONO;
break;
case 2:
exvoice.dwChannelMask = SPEAKER_STEREO;
break;
case 4:
exvoice.dwChannelMask = SPEAKER_QUAD;
break;
case 5:
exvoice.dwChannelMask = SPEAKER_5POINT1_SURROUND;
break;
case 7:
exvoice.dwChannelMask = SPEAKER_7POINT1_SURROUND;
break;
default:
exvoice.dwChannelMask = SPEAKER_MONO;
break;
}
exvoice.Samples.wReserved = 0;
exvoice.Samples.wValidBitsPerSample = voiceFormatcine.wBitsPerSample;
exvoice.Samples.wSamplesPerBlock = voiceFormatcine.wBitsPerSample;
exvoice.SubFormat = use_ext ? KSDATAFORMAT_SUBTYPE_IEEE_FLOAT : KSDATAFORMAT_SUBTYPE_PCM;
( ( IXAudio2* )soundSystemLocal.GetInternal() )->CreateSourceVoice( &pMusicSourceVoice1, ( WAVEFORMATEX* )&exvoice, XAUDIO2_VOICE_USEFILTER ); //Use the XAudio2 that the game has initialized instead of making your own
#endif
}
void CinematicAudio_XAudio2::PlayAudio( uint8_t* data, int size )
{
//Store the data to XAudio2 buffer
Packet.Flags = XAUDIO2_END_OF_STREAM;
Packet.AudioBytes = size;
Packet.pAudioData = ( BYTE* )data;
Packet.PlayBegin = 0;
Packet.PlayLength = 0;
Packet.LoopBegin = 0;
Packet.LoopLength = 0;
Packet.LoopCount = 0;
Packet.pContext = NULL;
HRESULT hr;
if( FAILED( hr = pMusicSourceVoice1->SubmitSourceBuffer( &Packet ) ) )
{
int fail = 1;
}
// Play the source voice
if( FAILED( hr = pMusicSourceVoice1->Start( 0 ) ) )
{
int fail = 1;
}
}
void CinematicAudio_XAudio2::ShutdownAudio()
{
if( pMusicSourceVoice1 )
{
pMusicSourceVoice1->Stop();
pMusicSourceVoice1->FlushSourceBuffers();
pMusicSourceVoice1->DestroyVoice();
pMusicSourceVoice1 = NULL;
}
}

View file

@ -0,0 +1,46 @@
/**
* Copyright (C) 2021 George Kalmpokis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to
* do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software. As clarification, there
* is no requirement that the copyright notice and permission be included in
* binary distributions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <precompiled.h>
#include "../CinematicAudio.h"
#include <xaudio2.h>
#ifndef __CINEMATIC_AUDIO_XA2_H__
#define __CINEMATIC_AUDIO_XA2_H__
class CinematicAudio_XAudio2: public CinematicAudio
{
public:
CinematicAudio_XAudio2():
pMusicSourceVoice1( NULL )
{
}
void InitAudio( void* audioContext );
void PlayAudio( uint8_t* data, int size );
void ShutdownAudio();
private:
WAVEFORMATEX voiceFormatcine = { 0 };
IXAudio2SourceVoice* pMusicSourceVoice1;
XAUDIO2_BUFFER Packet = { 0 };
};
#endif