1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-01-09 21:42:13 +00:00
OpenMW/apps/openmw/mwsound/ffmpeg_decoder.cpp
AnyOldName3 28131fd62b Fixes for a whole bunch of warnings
These warnings were always enabled, but we didn't see them due to https://gitlab.com/OpenMW/openmw/-/issues/7882.
I do not fully understand the cause of 7822 as I can't repro it in a minimal CMake project.

Some of these fixes are thought through.
Some are sensible best guesses.
Some are kind of a stab in the dark as I don't know whether there was a
possible bug the warning was telling me about that I've done nothing to
help by introducing a static_cast.

Nearly all of these warnings were about some kind of narrowing
conversion, so I'm not sure why they weren't firing with GCC and Clang,
which have -Wall -Wextra -pedantic set, which should imply -Wnarrowing,
and they can't have been affected by 7882.

There were also some warnings being triggered from Boost code.
The vast majority of library headers that do questionable things weren't
firing warnings off, but for some reason, /external:I wasn't putting
these Boost headers into external mode.

We need these warnings dealt with one way or another so we can switch
the default Windows CI from MSBuild (which doesn't do ccache) to Ninja
(which does).
I have the necessary magic for that on a branch, but the branch won't
build because of these warnings.
2024-03-14 23:39:33 +00:00

475 lines
15 KiB
C++

#include "ffmpeg_decoder.hpp"
#include <memory>
#include <algorithm>
#include <stdexcept>
#include <components/debug/debuglog.hpp>
#include <components/vfs/manager.hpp>
namespace MWSound
{
int FFmpeg_Decoder::readPacket(void* user_data, uint8_t* buf, int buf_size)
{
try
{
std::istream& stream = *static_cast<FFmpeg_Decoder*>(user_data)->mDataStream;
stream.clear();
stream.read((char*)buf, buf_size);
std::streamsize count = stream.gcount();
if (count == 0)
return AVERROR_EOF;
if (count > std::numeric_limits<int>::max())
return AVERROR_BUG;
return static_cast<int>(count);
}
catch (std::exception&)
{
return AVERROR_UNKNOWN;
}
}
int FFmpeg_Decoder::writePacket(void*, uint8_t*, int)
{
Log(Debug::Error) << "can't write to read-only stream";
return -1;
}
int64_t FFmpeg_Decoder::seek(void* user_data, int64_t offset, int whence)
{
std::istream& stream = *static_cast<FFmpeg_Decoder*>(user_data)->mDataStream;
whence &= ~AVSEEK_FORCE;
stream.clear();
if (whence == AVSEEK_SIZE)
{
size_t prev = stream.tellg();
stream.seekg(0, std::ios_base::end);
size_t size = stream.tellg();
stream.seekg(prev, std::ios_base::beg);
return size;
}
if (whence == SEEK_SET)
stream.seekg(offset, std::ios_base::beg);
else if (whence == SEEK_CUR)
stream.seekg(offset, std::ios_base::cur);
else if (whence == SEEK_END)
stream.seekg(offset, std::ios_base::end);
else
return -1;
return stream.tellg();
}
/* Used by getAV*Data to search for more compressed data, and buffer it in the
* correct stream. It won't buffer data for streams that the app doesn't have a
* handle for. */
bool FFmpeg_Decoder::getNextPacket()
{
if (!mStream)
return false;
std::ptrdiff_t stream_idx = mStream - mFormatCtx->streams;
while (av_read_frame(mFormatCtx, &mPacket) >= 0)
{
/* Check if the packet belongs to this stream */
if (stream_idx == mPacket.stream_index)
{
if (mPacket.pts != (int64_t)AV_NOPTS_VALUE)
mNextPts = av_q2d((*mStream)->time_base) * mPacket.pts;
return true;
}
/* Free the packet and look for another */
av_packet_unref(&mPacket);
}
return false;
}
bool FFmpeg_Decoder::getAVAudioData()
{
bool got_frame = false;
if (mCodecCtx->codec_type != AVMEDIA_TYPE_AUDIO)
return false;
do
{
/* Decode some data, and check for errors */
int ret = avcodec_receive_frame(mCodecCtx, mFrame);
if (ret == AVERROR(EAGAIN))
{
if (mPacket.size == 0 && !getNextPacket())
return false;
ret = avcodec_send_packet(mCodecCtx, &mPacket);
av_packet_unref(&mPacket);
if (ret == 0)
continue;
}
if (ret != 0)
return false;
av_packet_unref(&mPacket);
if (mFrame->nb_samples == 0)
continue;
got_frame = true;
if (mSwr)
{
if (!mDataBuf || mDataBufLen < mFrame->nb_samples)
{
av_freep(&mDataBuf);
if (av_samples_alloc(&mDataBuf, nullptr, av_get_channel_layout_nb_channels(mOutputChannelLayout),
mFrame->nb_samples, mOutputSampleFormat, 0)
< 0)
return false;
else
mDataBufLen = mFrame->nb_samples;
}
if (swr_convert(mSwr, (uint8_t**)&mDataBuf, mFrame->nb_samples, (const uint8_t**)mFrame->extended_data,
mFrame->nb_samples)
< 0)
{
return false;
}
mFrameData = &mDataBuf;
}
else
mFrameData = &mFrame->data[0];
} while (!got_frame);
mNextPts += (double)mFrame->nb_samples / mCodecCtx->sample_rate;
return true;
}
size_t FFmpeg_Decoder::readAVAudioData(void* data, size_t length)
{
size_t dec = 0;
while (dec < length)
{
/* If there's no decoded data, find some */
if (mFramePos >= mFrameSize)
{
if (!getAVAudioData())
break;
mFramePos = 0;
mFrameSize = mFrame->nb_samples * av_get_channel_layout_nb_channels(mOutputChannelLayout)
* av_get_bytes_per_sample(mOutputSampleFormat);
}
/* Get the amount of bytes remaining to be written, and clamp to
* the amount of decoded data we have */
size_t rem = std::min<size_t>(length - dec, mFrameSize - mFramePos);
/* Copy the data to the app's buffer and increment */
memcpy(data, mFrameData[0] + mFramePos, rem);
data = (char*)data + rem;
dec += rem;
mFramePos += rem;
}
/* Return the number of bytes we were able to get */
return dec;
}
void FFmpeg_Decoder::open(const std::string& fname)
{
close();
mDataStream = mResourceMgr->get(fname);
if ((mFormatCtx = avformat_alloc_context()) == nullptr)
throw std::runtime_error("Failed to allocate context");
try
{
mFormatCtx->pb = avio_alloc_context(nullptr, 0, 0, this, readPacket, writePacket, seek);
if (!mFormatCtx->pb || avformat_open_input(&mFormatCtx, fname.c_str(), nullptr, nullptr) != 0)
{
// "Note that a user-supplied AVFormatContext will be freed on failure".
if (mFormatCtx)
{
if (mFormatCtx->pb != nullptr)
{
if (mFormatCtx->pb->buffer != nullptr)
{
av_free(mFormatCtx->pb->buffer);
mFormatCtx->pb->buffer = nullptr;
}
av_free(mFormatCtx->pb);
mFormatCtx->pb = nullptr;
}
avformat_free_context(mFormatCtx);
}
mFormatCtx = nullptr;
throw std::runtime_error("Failed to allocate input stream");
}
if (avformat_find_stream_info(mFormatCtx, nullptr) < 0)
throw std::runtime_error("Failed to find stream info in " + fname);
for (size_t j = 0; j < mFormatCtx->nb_streams; j++)
{
if (mFormatCtx->streams[j]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
{
mStream = &mFormatCtx->streams[j];
break;
}
}
if (!mStream)
throw std::runtime_error("No audio streams in " + fname);
const AVCodec* codec = avcodec_find_decoder((*mStream)->codecpar->codec_id);
if (!codec)
{
std::string ss = "No codec found for id " + std::to_string((*mStream)->codecpar->codec_id);
throw std::runtime_error(ss);
}
AVCodecContext* avctx = avcodec_alloc_context3(codec);
avcodec_parameters_to_context(avctx, (*mStream)->codecpar);
// This is not needed anymore above FFMpeg version 4.0
#if LIBAVCODEC_VERSION_INT < 3805796
av_codec_set_pkt_timebase(avctx, (*mStream)->time_base);
#endif
mCodecCtx = avctx;
if (avcodec_open2(mCodecCtx, codec, nullptr) < 0)
throw std::runtime_error(std::string("Failed to open audio codec ") + codec->long_name);
mFrame = av_frame_alloc();
if (mCodecCtx->sample_fmt == AV_SAMPLE_FMT_U8P)
mOutputSampleFormat = AV_SAMPLE_FMT_U8;
// FIXME: Check for AL_EXT_FLOAT32 support
// else if (mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLT || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLTP)
// mOutputSampleFormat = AV_SAMPLE_FMT_S16;
else
mOutputSampleFormat = AV_SAMPLE_FMT_S16;
mOutputChannelLayout = (*mStream)->codecpar->channel_layout;
if (mOutputChannelLayout == 0)
mOutputChannelLayout = av_get_default_channel_layout(mCodecCtx->channels);
mCodecCtx->channel_layout = mOutputChannelLayout;
}
catch (...)
{
if (mStream)
avcodec_free_context(&mCodecCtx);
mStream = nullptr;
if (mFormatCtx != nullptr)
{
if (mFormatCtx->pb->buffer != nullptr)
{
av_free(mFormatCtx->pb->buffer);
mFormatCtx->pb->buffer = nullptr;
}
av_free(mFormatCtx->pb);
mFormatCtx->pb = nullptr;
avformat_close_input(&mFormatCtx);
}
}
}
void FFmpeg_Decoder::close()
{
if (mStream)
avcodec_free_context(&mCodecCtx);
mStream = nullptr;
av_packet_unref(&mPacket);
av_freep(&mDataBuf);
av_frame_free(&mFrame);
swr_free(&mSwr);
if (mFormatCtx)
{
if (mFormatCtx->pb != nullptr)
{
// mFormatCtx->pb->buffer must be freed by hand,
// if not, valgrind will show memleak, see:
//
// https://trac.ffmpeg.org/ticket/1357
//
if (mFormatCtx->pb->buffer != nullptr)
{
av_freep(&mFormatCtx->pb->buffer);
}
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 80, 100)
avio_context_free(&mFormatCtx->pb);
#else
av_freep(&mFormatCtx->pb);
#endif
}
avformat_close_input(&mFormatCtx);
}
mDataStream.reset();
}
std::string FFmpeg_Decoder::getName()
{
// In the FFMpeg 4.0 a "filename" field was replaced by "url"
#if LIBAVCODEC_VERSION_INT < 3805796
return mFormatCtx->filename;
#else
return mFormatCtx->url;
#endif
}
void FFmpeg_Decoder::getInfo(int* samplerate, ChannelConfig* chans, SampleType* type)
{
if (!mStream)
throw std::runtime_error("No audio stream info");
if (mOutputSampleFormat == AV_SAMPLE_FMT_U8)
*type = SampleType_UInt8;
else if (mOutputSampleFormat == AV_SAMPLE_FMT_S16)
*type = SampleType_Int16;
else if (mOutputSampleFormat == AV_SAMPLE_FMT_FLT)
*type = SampleType_Float32;
else
{
mOutputSampleFormat = AV_SAMPLE_FMT_S16;
*type = SampleType_Int16;
}
if (mOutputChannelLayout == AV_CH_LAYOUT_MONO)
*chans = ChannelConfig_Mono;
else if (mOutputChannelLayout == AV_CH_LAYOUT_STEREO)
*chans = ChannelConfig_Stereo;
else if (mOutputChannelLayout == AV_CH_LAYOUT_QUAD)
*chans = ChannelConfig_Quad;
else if (mOutputChannelLayout == AV_CH_LAYOUT_5POINT1)
*chans = ChannelConfig_5point1;
else if (mOutputChannelLayout == AV_CH_LAYOUT_7POINT1)
*chans = ChannelConfig_7point1;
else
{
char str[1024];
av_get_channel_layout_string(str, sizeof(str), mCodecCtx->channels, mCodecCtx->channel_layout);
Log(Debug::Error) << "Unsupported channel layout: " << str;
if (mCodecCtx->channels == 1)
{
mOutputChannelLayout = AV_CH_LAYOUT_MONO;
*chans = ChannelConfig_Mono;
}
else
{
mOutputChannelLayout = AV_CH_LAYOUT_STEREO;
*chans = ChannelConfig_Stereo;
}
}
*samplerate = mCodecCtx->sample_rate;
int64_t ch_layout = mCodecCtx->channel_layout;
if (ch_layout == 0)
ch_layout = av_get_default_channel_layout(mCodecCtx->channels);
if (mOutputSampleFormat != mCodecCtx->sample_fmt || mOutputChannelLayout != ch_layout)
{
mSwr = swr_alloc_set_opts(mSwr, // SwrContext
mOutputChannelLayout, // output ch layout
mOutputSampleFormat, // output sample format
mCodecCtx->sample_rate, // output sample rate
ch_layout, // input ch layout
mCodecCtx->sample_fmt, // input sample format
mCodecCtx->sample_rate, // input sample rate
0, // logging level offset
nullptr); // log context
if (!mSwr)
throw std::runtime_error("Couldn't allocate SwrContext");
int init = swr_init(mSwr);
if (init < 0)
throw std::runtime_error("Couldn't initialize SwrContext: " + std::to_string(init));
}
}
size_t FFmpeg_Decoder::read(char* buffer, size_t bytes)
{
if (!mStream)
{
Log(Debug::Error) << "No audio stream";
return 0;
}
return readAVAudioData(buffer, bytes);
}
void FFmpeg_Decoder::readAll(std::vector<char>& output)
{
if (!mStream)
{
Log(Debug::Error) << "No audio stream";
return;
}
while (getAVAudioData())
{
size_t got = mFrame->nb_samples * av_get_channel_layout_nb_channels(mOutputChannelLayout)
* av_get_bytes_per_sample(mOutputSampleFormat);
const char* inbuf = reinterpret_cast<char*>(mFrameData[0]);
output.insert(output.end(), inbuf, inbuf + got);
}
}
size_t FFmpeg_Decoder::getSampleOffset()
{
std::size_t delay = (mFrameSize - mFramePos) / av_get_channel_layout_nb_channels(mOutputChannelLayout)
/ av_get_bytes_per_sample(mOutputSampleFormat);
return static_cast<std::size_t>(mNextPts * mCodecCtx->sample_rate) - delay;
}
FFmpeg_Decoder::FFmpeg_Decoder(const VFS::Manager* vfs)
: Sound_Decoder(vfs)
, mFormatCtx(nullptr)
, mCodecCtx(nullptr)
, mStream(nullptr)
, mFrame(nullptr)
, mFrameSize(0)
, mFramePos(0)
, mNextPts(0.0)
, mSwr(nullptr)
, mOutputSampleFormat(AV_SAMPLE_FMT_NONE)
, mOutputChannelLayout(0)
, mDataBuf(nullptr)
, mFrameData(nullptr)
, mDataBufLen(0)
{
memset(&mPacket, 0, sizeof(mPacket));
/* We need to make sure ffmpeg is initialized. Optionally silence warning
* output from the lib */
static bool done_init = false;
if (!done_init)
{
// This is not needed anymore above FFMpeg version 4.0
#if LIBAVCODEC_VERSION_INT < 3805796
av_register_all();
#endif
av_log_set_level(AV_LOG_ERROR);
done_init = true;
}
}
FFmpeg_Decoder::~FFmpeg_Decoder()
{
close();
}
}