forked from mirror/openmw-tes3mp
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
322 lines
9.1 KiB
C++
322 lines
9.1 KiB
C++
#include "audiodecoder.hpp"
|
|
|
|
#include <stdexcept>
|
|
|
|
extern "C"
|
|
{
|
|
|
|
#include <libavcodec/avcodec.h>
|
|
|
|
#include <libswresample/swresample.h>
|
|
|
|
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
|
|
#define av_frame_alloc avcodec_alloc_frame
|
|
#endif
|
|
|
|
}
|
|
|
|
#include "videostate.hpp"
|
|
|
|
namespace
|
|
{
|
|
void fail(const std::string &str)
|
|
{
|
|
throw std::runtime_error(str);
|
|
}
|
|
|
|
const double AUDIO_DIFF_AVG_NB = 20;
|
|
}
|
|
|
|
namespace Video
|
|
{
|
|
|
|
// Moved to implementation file, so that HAVE_SWRESAMPLE is used at library compile time only
|
|
struct AudioResampler
|
|
{
|
|
AudioResampler()
|
|
: mSwr(NULL)
|
|
{
|
|
}
|
|
|
|
~AudioResampler()
|
|
{
|
|
swr_free(&mSwr);
|
|
}
|
|
|
|
SwrContext* mSwr;
|
|
};
|
|
|
|
MovieAudioDecoder::MovieAudioDecoder(VideoState* videoState)
|
|
: mVideoState(videoState)
|
|
, mAVStream(*videoState->audio_st)
|
|
, mOutputSampleFormat(AV_SAMPLE_FMT_NONE)
|
|
, mOutputChannelLayout(0)
|
|
, mOutputSampleRate(0)
|
|
, mFramePos(0)
|
|
, mFrameSize(0)
|
|
, mAudioClock(0.0)
|
|
, mDataBuf(NULL)
|
|
, mFrameData(NULL)
|
|
, mDataBufLen(0)
|
|
, mFrame(av_frame_alloc())
|
|
, mAudioDiffAccum(0.0)
|
|
, mAudioDiffAvgCoef(exp(log(0.01 / AUDIO_DIFF_AVG_NB)))
|
|
/* Correct audio only if larger error than this */
|
|
, mAudioDiffThreshold(2.0 * 0.050/* 50 ms */)
|
|
, mAudioDiffAvgCount(0)
|
|
{
|
|
mAudioResampler.reset(new AudioResampler());
|
|
}
|
|
|
|
MovieAudioDecoder::~MovieAudioDecoder()
|
|
{
|
|
av_freep(&mFrame);
|
|
av_freep(&mDataBuf);
|
|
}
|
|
|
|
void MovieAudioDecoder::setupFormat()
|
|
{
|
|
if (mAudioResampler->mSwr)
|
|
return; // already set up
|
|
|
|
AVSampleFormat inputSampleFormat = mAVStream->codec->sample_fmt;
|
|
|
|
uint64_t inputChannelLayout = mAVStream->codec->channel_layout;
|
|
if (inputChannelLayout == 0)
|
|
inputChannelLayout = av_get_default_channel_layout(mAVStream->codec->channels);
|
|
|
|
int inputSampleRate = mAVStream->codec->sample_rate;
|
|
|
|
mOutputSampleRate = inputSampleRate;
|
|
mOutputSampleFormat = inputSampleFormat;
|
|
mOutputChannelLayout = inputChannelLayout;
|
|
adjustAudioSettings(mOutputSampleFormat, mOutputChannelLayout, mOutputSampleRate);
|
|
|
|
if (inputSampleFormat != mOutputSampleFormat
|
|
|| inputChannelLayout != mOutputChannelLayout
|
|
|| inputSampleRate != mOutputSampleRate)
|
|
{
|
|
mAudioResampler->mSwr = swr_alloc_set_opts(mAudioResampler->mSwr,
|
|
mOutputChannelLayout,
|
|
mOutputSampleFormat,
|
|
mOutputSampleRate,
|
|
inputChannelLayout,
|
|
inputSampleFormat,
|
|
inputSampleRate,
|
|
0, // logging level offset
|
|
NULL); // log context
|
|
if(!mAudioResampler->mSwr)
|
|
fail(std::string("Couldn't allocate SwrContext"));
|
|
if(swr_init(mAudioResampler->mSwr) < 0)
|
|
fail(std::string("Couldn't initialize SwrContext"));
|
|
}
|
|
}
|
|
|
|
int MovieAudioDecoder::synchronize_audio()
|
|
{
|
|
if(mVideoState->av_sync_type == AV_SYNC_AUDIO_MASTER)
|
|
return 0;
|
|
|
|
int sample_skip = 0;
|
|
|
|
// accumulate the clock difference
|
|
double diff = mVideoState->get_master_clock() - mVideoState->get_audio_clock();
|
|
mAudioDiffAccum = diff + mAudioDiffAvgCoef * mAudioDiffAccum;
|
|
if(mAudioDiffAvgCount < AUDIO_DIFF_AVG_NB)
|
|
mAudioDiffAvgCount++;
|
|
else
|
|
{
|
|
double avg_diff = mAudioDiffAccum * (1.0 - mAudioDiffAvgCoef);
|
|
if(fabs(avg_diff) >= mAudioDiffThreshold)
|
|
{
|
|
int n = av_get_bytes_per_sample(mOutputSampleFormat) *
|
|
av_get_channel_layout_nb_channels(mOutputChannelLayout);
|
|
sample_skip = ((int)(diff * mAVStream->codec->sample_rate) * n);
|
|
}
|
|
}
|
|
|
|
return sample_skip;
|
|
}
|
|
|
|
int MovieAudioDecoder::audio_decode_frame(AVFrame *frame, int &sample_skip)
|
|
{
|
|
AVPacket *pkt = &mPacket;
|
|
|
|
for(;;)
|
|
{
|
|
while(pkt->size > 0)
|
|
{
|
|
int len1, got_frame;
|
|
|
|
len1 = avcodec_decode_audio4(mAVStream->codec, frame, &got_frame, pkt);
|
|
if(len1 < 0) break;
|
|
|
|
if(len1 <= pkt->size)
|
|
{
|
|
/* Move the unread data to the front and clear the end bits */
|
|
int remaining = pkt->size - len1;
|
|
memmove(pkt->data, &pkt->data[len1], remaining);
|
|
av_shrink_packet(pkt, remaining);
|
|
}
|
|
|
|
/* No data yet? Look for more frames */
|
|
if(!got_frame || frame->nb_samples <= 0)
|
|
continue;
|
|
|
|
if(mAudioResampler->mSwr)
|
|
{
|
|
if(!mDataBuf || mDataBufLen < frame->nb_samples)
|
|
{
|
|
av_freep(&mDataBuf);
|
|
if(av_samples_alloc(&mDataBuf, NULL, av_get_channel_layout_nb_channels(mOutputChannelLayout),
|
|
frame->nb_samples, mOutputSampleFormat, 0) < 0)
|
|
break;
|
|
else
|
|
mDataBufLen = frame->nb_samples;
|
|
}
|
|
|
|
if(swr_convert(mAudioResampler->mSwr, (uint8_t**)&mDataBuf, frame->nb_samples,
|
|
(const uint8_t**)frame->extended_data, frame->nb_samples) < 0)
|
|
{
|
|
break;
|
|
}
|
|
mFrameData = &mDataBuf;
|
|
}
|
|
else
|
|
mFrameData = &frame->data[0];
|
|
|
|
mAudioClock += (double)frame->nb_samples /
|
|
(double)mAVStream->codec->sample_rate;
|
|
|
|
/* We have data, return it and come back for more later */
|
|
return frame->nb_samples * av_get_channel_layout_nb_channels(mOutputChannelLayout) *
|
|
av_get_bytes_per_sample(mOutputSampleFormat);
|
|
}
|
|
av_free_packet(pkt);
|
|
|
|
/* next packet */
|
|
if(mVideoState->audioq.get(pkt, mVideoState) < 0)
|
|
return -1;
|
|
|
|
if(pkt->data == mVideoState->mFlushPktData)
|
|
{
|
|
avcodec_flush_buffers(mAVStream->codec);
|
|
mAudioDiffAccum = 0.0;
|
|
mAudioDiffAvgCount = 0;
|
|
mAudioClock = av_q2d(mAVStream->time_base)*pkt->pts;
|
|
sample_skip = 0;
|
|
|
|
if(mVideoState->audioq.get(pkt, mVideoState) < 0)
|
|
return -1;
|
|
}
|
|
|
|
/* if update, update the audio clock w/pts */
|
|
if(pkt->pts != AV_NOPTS_VALUE)
|
|
mAudioClock = av_q2d(mAVStream->time_base)*pkt->pts;
|
|
}
|
|
}
|
|
|
|
size_t MovieAudioDecoder::read(char *stream, size_t len)
|
|
{
|
|
if (mVideoState->mPaused)
|
|
{
|
|
// fill the buffer with silence
|
|
size_t sampleSize = av_get_bytes_per_sample(mOutputSampleFormat);
|
|
char* data[1];
|
|
data[0] = stream;
|
|
av_samples_set_silence((uint8_t**)data, 0, len/sampleSize, 1, mOutputSampleFormat);
|
|
return len;
|
|
}
|
|
|
|
int sample_skip = synchronize_audio();
|
|
size_t total = 0;
|
|
|
|
while(total < len)
|
|
{
|
|
if(mFramePos >= mFrameSize)
|
|
{
|
|
/* We have already sent all our data; get more */
|
|
mFrameSize = audio_decode_frame(mFrame, sample_skip);
|
|
if(mFrameSize < 0)
|
|
{
|
|
/* If error, we're done */
|
|
break;
|
|
}
|
|
|
|
mFramePos = std::min<ssize_t>(mFrameSize, sample_skip);
|
|
if(sample_skip > 0 || mFrameSize > -sample_skip)
|
|
sample_skip -= mFramePos;
|
|
continue;
|
|
}
|
|
|
|
size_t len1 = len - total;
|
|
if(mFramePos >= 0)
|
|
{
|
|
len1 = std::min<size_t>(len1, mFrameSize-mFramePos);
|
|
memcpy(stream, mFrameData[0]+mFramePos, len1);
|
|
}
|
|
else
|
|
{
|
|
len1 = std::min<size_t>(len1, -mFramePos);
|
|
|
|
int n = av_get_bytes_per_sample(mOutputSampleFormat)
|
|
* av_get_channel_layout_nb_channels(mOutputChannelLayout);
|
|
|
|
/* add samples by copying the first sample*/
|
|
if(n == 1)
|
|
memset(stream, *mFrameData[0], len1);
|
|
else if(n == 2)
|
|
{
|
|
const int16_t val = *((int16_t*)mFrameData[0]);
|
|
for(size_t nb = 0;nb < len1;nb += n)
|
|
*((int16_t*)(stream+nb)) = val;
|
|
}
|
|
else if(n == 4)
|
|
{
|
|
const int32_t val = *((int32_t*)mFrameData[0]);
|
|
for(size_t nb = 0;nb < len1;nb += n)
|
|
*((int32_t*)(stream+nb)) = val;
|
|
}
|
|
else if(n == 8)
|
|
{
|
|
const int64_t val = *((int64_t*)mFrameData[0]);
|
|
for(size_t nb = 0;nb < len1;nb += n)
|
|
*((int64_t*)(stream+nb)) = val;
|
|
}
|
|
else
|
|
{
|
|
for(size_t nb = 0;nb < len1;nb += n)
|
|
memcpy(stream+nb, mFrameData[0], n);
|
|
}
|
|
}
|
|
|
|
total += len1;
|
|
stream += len1;
|
|
mFramePos += len1;
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
double MovieAudioDecoder::getAudioClock()
|
|
{
|
|
return mAudioClock;
|
|
}
|
|
|
|
int MovieAudioDecoder::getOutputSampleRate() const
|
|
{
|
|
return mOutputSampleRate;
|
|
}
|
|
|
|
uint64_t MovieAudioDecoder::getOutputChannelLayout() const
|
|
{
|
|
return mOutputChannelLayout;
|
|
}
|
|
|
|
AVSampleFormat MovieAudioDecoder::getOutputSampleFormat() const
|
|
{
|
|
return mOutputSampleFormat;
|
|
}
|
|
|
|
}
|