mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 08:15:59 -10:00
231 lines
6.4 KiB
C++
231 lines
6.4 KiB
C++
#include "cutscene/ffmpeg/AudioDecoder.h"
|
|
|
|
#include "tracing/tracing.h"
|
|
|
|
namespace {
|
|
const int OUT_CH_LAYOUT = AV_CH_LAYOUT_STEREO;
|
|
const int OUT_SAMPLE_RATE = 48000;
|
|
const AVSampleFormat OUT_SAMPLE_FORMAT = AV_SAMPLE_FMT_S16;
|
|
const int OUT_NUM_CHANNELS = av_popcount64(OUT_CH_LAYOUT);
|
|
|
|
const int DEFAULT_SRC_NUM_SAMPLES = 1024;
|
|
|
|
SwrContext* getSWRContext(uint64_t layout, int rate, AVSampleFormat inFmt) {
|
|
auto swr = swr_alloc();
|
|
|
|
av_opt_set_int(swr, "in_channel_layout", layout, 0);
|
|
av_opt_set_int(swr, "in_sample_rate", rate, 0);
|
|
av_opt_set_int(swr, "in_sample_fmt", inFmt, 0);
|
|
|
|
av_opt_set_int(swr, "out_channel_layout", OUT_CH_LAYOUT, 0);
|
|
av_opt_set_int(swr, "out_sample_rate", OUT_SAMPLE_RATE, 0);
|
|
av_opt_set_int(swr, "out_sample_fmt", OUT_SAMPLE_FORMAT, 0);
|
|
|
|
swr_init(swr);
|
|
|
|
return swr;
|
|
}
|
|
|
|
int alloc_array_and_samples(uint8_t*** outData, int* linesize, int channels, int samples, AVSampleFormat format) {
|
|
auto planes = av_sample_fmt_is_planar(format) ? channels : 1;
|
|
|
|
*outData = reinterpret_cast<uint8_t**>(av_mallocz(planes * sizeof(**outData)));
|
|
|
|
|
|
auto ret = av_samples_alloc(*outData, linesize, channels, samples, format, 0);
|
|
|
|
if (ret < 0) {
|
|
av_freep(outData);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int64_t getDelay(SwrContext* ctx, int64_t sampleRate) {
|
|
#ifdef WITH_LIBAV
|
|
return avresample_get_delay(ctx);
|
|
#else
|
|
return swr_get_delay(ctx, sampleRate);
|
|
#endif
|
|
}
|
|
|
|
int resample_convert(SwrContext* ctx, uint8_t** output,
|
|
int out_plane_size, int out_samples, uint8_t** input,
|
|
int in_plane_size, int in_samples) {
|
|
#ifdef WITH_LIBAV
|
|
return avresample_convert(ctx, output, out_plane_size, out_samples, input, in_plane_size, in_samples);
|
|
#else
|
|
(void)out_plane_size;
|
|
(void)in_plane_size;
|
|
|
|
return swr_convert(ctx, output, out_samples, (const uint8_t**) input, in_samples);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
namespace cutscene {
|
|
namespace ffmpeg {
|
|
AudioDecoder::AudioDecoder(DecoderStatus* status)
|
|
: FFMPEGStreamDecoder(status) {
|
|
m_audioBuffer.reserve(static_cast<size_t>(OUT_SAMPLE_RATE * OUT_NUM_CHANNELS / 2));
|
|
|
|
m_resampleCtx = getSWRContext(m_status->audioCodecPars.channel_layout, m_status->audioCodecPars.sample_rate,
|
|
m_status->audioCodecPars.audio_format);
|
|
|
|
/*
|
|
* compute the number of converted samples: buffering is avoided
|
|
* ensuring that the output buffer will contain at least all the
|
|
* converted input samples
|
|
*/
|
|
m_maxOutNumSamples = m_outNumSamples = static_cast<int>(av_rescale_rnd(DEFAULT_SRC_NUM_SAMPLES,
|
|
OUT_SAMPLE_RATE,
|
|
m_status->audioCodecPars.sample_rate,
|
|
AV_ROUND_UP));
|
|
|
|
auto ret = alloc_array_and_samples(&m_outData, &m_outLinesize, OUT_NUM_CHANNELS,
|
|
m_outNumSamples, OUT_SAMPLE_FORMAT);
|
|
|
|
if (ret < 0) {
|
|
mprintf(("FFMPEG: Failed to allocate samples array!\n"));
|
|
}
|
|
}
|
|
|
|
AudioDecoder::~AudioDecoder() {
|
|
if (m_outData) {
|
|
av_freep(&m_outData[0]);
|
|
}
|
|
av_freep(&m_outData);
|
|
|
|
swr_free(&m_resampleCtx);
|
|
}
|
|
|
|
void AudioDecoder::flushAudioBuffer() {
|
|
if (m_audioBuffer.empty()) {
|
|
// Nothing to do here
|
|
return;
|
|
}
|
|
|
|
AudioFramePtr audioFrame(new AudioFrame());
|
|
audioFrame->channels = OUT_NUM_CHANNELS;
|
|
audioFrame->rate = OUT_SAMPLE_RATE;
|
|
|
|
audioFrame->audioData.assign(m_audioBuffer.begin(), m_audioBuffer.end());
|
|
|
|
pushFrame(std::move(audioFrame));
|
|
m_audioBuffer.clear();
|
|
}
|
|
|
|
void AudioDecoder::handleDecodedFrame(AVFrame* frame) {
|
|
/* compute destination number of samples */
|
|
m_outNumSamples = static_cast<int>(av_rescale_rnd(getDelay(m_resampleCtx, frame->sample_rate)
|
|
+ frame->nb_samples, OUT_SAMPLE_RATE, frame->sample_rate,
|
|
AV_ROUND_UP));
|
|
|
|
if (m_outNumSamples > m_maxOutNumSamples) {
|
|
av_freep(&m_outData[0]);
|
|
auto ret = av_samples_alloc(m_outData, &m_outLinesize, OUT_NUM_CHANNELS, m_outNumSamples,
|
|
OUT_SAMPLE_FORMAT, 1);
|
|
if (ret < 0) {
|
|
mprintf(("FFMPEG: Failed to allocate samples!!!\n"));
|
|
return;
|
|
}
|
|
|
|
m_maxOutNumSamples = m_outNumSamples;
|
|
}
|
|
|
|
/* convert to destination format */
|
|
auto ret = resample_convert(m_resampleCtx, m_outData, 0, m_outNumSamples,
|
|
(uint8_t**) frame->data, 0, frame->nb_samples);
|
|
if (ret < 0) {
|
|
mprintf(("FFMPEG: Error while converting audio!\n"));
|
|
return;
|
|
}
|
|
|
|
auto outBufsize = av_samples_get_buffer_size(&m_outLinesize, OUT_NUM_CHANNELS, ret, OUT_SAMPLE_FORMAT, 1);
|
|
if (outBufsize < 0) {
|
|
mprintf(("FFMPEG: Could not get sample buffer size!\n"));
|
|
return;
|
|
}
|
|
|
|
auto begin = reinterpret_cast<short*>(m_outData[0]);
|
|
auto end = reinterpret_cast<short*>(m_outData[0] + outBufsize);
|
|
|
|
auto size = std::distance(begin, end);
|
|
auto newSize = m_audioBuffer.size() + size;
|
|
|
|
if (newSize <= m_audioBuffer.capacity()) {
|
|
// We haven't filled the buffer yet
|
|
m_audioBuffer.insert(m_audioBuffer.end(), begin, end);
|
|
} else {
|
|
flushAudioBuffer();
|
|
m_audioBuffer.assign(begin, end);
|
|
}
|
|
}
|
|
|
|
void AudioDecoder::decodePacket(AVPacket* packet) {
|
|
TRACE_SCOPE(tracing::CutsceneFFmpegAudioDecoder);
|
|
#if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(57, 24, 255)
|
|
int send_result;
|
|
do {
|
|
send_result = avcodec_send_packet(m_status->audioCodecCtx, packet);
|
|
|
|
while (avcodec_receive_frame(m_status->audioCodecCtx, m_decodeFrame) == 0) {
|
|
handleDecodedFrame(m_decodeFrame);
|
|
}
|
|
} while (send_result == AVERROR(EAGAIN));
|
|
#else
|
|
int finishedFrame = 0;
|
|
auto err = avcodec_decode_audio4(m_status->audioCodecCtx, m_decodeFrame, &finishedFrame, packet);
|
|
|
|
if (err >= 0 && finishedFrame) {
|
|
handleDecodedFrame(m_decodeFrame);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void AudioDecoder::finishDecoding() {
|
|
TRACE_SCOPE(tracing::CutsceneFFmpegAudioDecoder);
|
|
#if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(57, 24, 255)
|
|
// Send flush packet
|
|
avcodec_send_packet(m_status->audioCodecCtx, nullptr);
|
|
|
|
// Handle those decoders that have a delay
|
|
while (true) {
|
|
auto ret = avcodec_receive_frame(m_status->audioCodecCtx, m_decodeFrame);
|
|
|
|
if (ret == 0) {
|
|
handleDecodedFrame(m_decodeFrame);
|
|
}
|
|
else {
|
|
// Everything consumed or error
|
|
break;
|
|
}
|
|
}
|
|
#else
|
|
// Handle those decoders that have a delay
|
|
AVPacket nullPacket;
|
|
memset(&nullPacket, 0, sizeof(nullPacket));
|
|
nullPacket.data = nullptr;
|
|
nullPacket.size = 0;
|
|
|
|
while (true) {
|
|
int finishedFrame = 1;
|
|
auto err = avcodec_decode_audio4(m_status->audioCodecCtx, m_decodeFrame, &finishedFrame, &nullPacket);
|
|
|
|
if (err < 0 || !finishedFrame) {
|
|
break;
|
|
}
|
|
|
|
handleDecodedFrame(m_decodeFrame);
|
|
}
|
|
#endif
|
|
|
|
// Push the last bits of audio data into the queue
|
|
flushAudioBuffer();
|
|
}
|
|
void AudioDecoder::flushBuffers() {
|
|
avcodec_flush_buffers(m_status->audioCodecCtx);
|
|
}
|
|
}
|
|
}
|