mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-31 17:45:35 +00:00
Use RAII for AVIOContext, AVFormatContext, AVCodecContext and AVFrame pointers
This commit is contained in:
parent
0fa4b0137a
commit
f184d8f390
2 changed files with 122 additions and 113 deletions
|
@ -1,15 +1,41 @@
|
|||
#include "ffmpeg_decoder.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/vfs/manager.hpp>
|
||||
|
||||
namespace MWSound
|
||||
{
|
||||
void AVIOContextDeleter::operator()(AVIOContext* ptr) const
|
||||
{
|
||||
if (ptr->buffer != nullptr)
|
||||
av_freep(&ptr->buffer);
|
||||
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 80, 100)
|
||||
avio_context_free(&ptr);
|
||||
#else
|
||||
av_free(ptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
void AVFormatContextDeleter::operator()(AVFormatContext* ptr) const
|
||||
{
|
||||
avformat_close_input(&ptr);
|
||||
}
|
||||
|
||||
void AVCodecContextDeleter::operator()(AVCodecContext* ptr) const
|
||||
{
|
||||
avcodec_free_context(&ptr);
|
||||
}
|
||||
|
||||
void AVFrameDeleter::operator()(AVFrame* ptr) const
|
||||
{
|
||||
av_frame_free(&ptr);
|
||||
}
|
||||
|
||||
int FFmpeg_Decoder::readPacket(void* user_data, uint8_t* buf, int buf_size)
|
||||
{
|
||||
|
@ -75,7 +101,7 @@ namespace MWSound
|
|||
return false;
|
||||
|
||||
std::ptrdiff_t stream_idx = mStream - mFormatCtx->streams;
|
||||
while (av_read_frame(mFormatCtx, &mPacket) >= 0)
|
||||
while (av_read_frame(mFormatCtx.get(), &mPacket) >= 0)
|
||||
{
|
||||
/* Check if the packet belongs to this stream */
|
||||
if (stream_idx == mPacket.stream_index)
|
||||
|
@ -102,12 +128,12 @@ namespace MWSound
|
|||
do
|
||||
{
|
||||
/* Decode some data, and check for errors */
|
||||
int ret = avcodec_receive_frame(mCodecCtx, mFrame);
|
||||
int ret = avcodec_receive_frame(mCodecCtx.get(), mFrame.get());
|
||||
if (ret == AVERROR(EAGAIN))
|
||||
{
|
||||
if (mPacket.size == 0 && !getNextPacket())
|
||||
return false;
|
||||
ret = avcodec_send_packet(mCodecCtx, &mPacket);
|
||||
ret = avcodec_send_packet(mCodecCtx.get(), &mPacket);
|
||||
av_packet_unref(&mPacket);
|
||||
if (ret == 0)
|
||||
continue;
|
||||
|
@ -187,137 +213,95 @@ namespace MWSound
|
|||
close();
|
||||
mDataStream = mResourceMgr->get(fname);
|
||||
|
||||
if ((mFormatCtx = avformat_alloc_context()) == nullptr)
|
||||
AVIOContextPtr ioCtx(avio_alloc_context(nullptr, 0, 0, this, readPacket, writePacket, seek));
|
||||
if (ioCtx == nullptr)
|
||||
throw std::runtime_error("Failed to allocate AVIO context");
|
||||
|
||||
AVFormatContext* formatCtx = avformat_alloc_context();
|
||||
if (formatCtx == nullptr)
|
||||
throw std::runtime_error("Failed to allocate context");
|
||||
|
||||
try
|
||||
formatCtx->pb = ioCtx.get();
|
||||
|
||||
// avformat_open_input frees user supplied AVFormatContext on failure
|
||||
if (avformat_open_input(&formatCtx, fname.c_str(), nullptr, nullptr) != 0)
|
||||
throw std::runtime_error("Failed to open input");
|
||||
|
||||
AVFormatContextPtr formatCtxPtr(std::exchange(formatCtx, nullptr));
|
||||
|
||||
if (avformat_find_stream_info(formatCtxPtr.get(), nullptr) < 0)
|
||||
throw std::runtime_error("Failed to find stream info");
|
||||
|
||||
AVStream** stream = nullptr;
|
||||
for (size_t j = 0; j < formatCtxPtr->nb_streams; j++)
|
||||
{
|
||||
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)
|
||||
if (formatCtxPtr->streams[j]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
|
||||
{
|
||||
// "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");
|
||||
stream = &formatCtxPtr->streams[j];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (avformat_find_stream_info(mFormatCtx, nullptr) < 0)
|
||||
throw std::runtime_error("Failed to find stream info in " + fname);
|
||||
if (stream == nullptr)
|
||||
throw std::runtime_error("No audio streams");
|
||||
|
||||
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((*stream)->codecpar->codec_id);
|
||||
if (codec == nullptr)
|
||||
throw std::runtime_error("No codec found for id " + std::to_string((*stream)->codecpar->codec_id));
|
||||
|
||||
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* codecCtx = avcodec_alloc_context3(codec);
|
||||
if (codecCtx == nullptr)
|
||||
throw std::runtime_error("Failed to allocate codec context");
|
||||
|
||||
AVCodecContext* avctx = avcodec_alloc_context3(codec);
|
||||
avcodec_parameters_to_context(avctx, (*mStream)->codecpar);
|
||||
avcodec_parameters_to_context(codecCtx, (*stream)->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);
|
||||
av_codec_set_pkt_timebase(avctx, (*stream)->time_base);
|
||||
#endif
|
||||
|
||||
mCodecCtx = avctx;
|
||||
AVCodecContextPtr codecCtxPtr(std::exchange(codecCtx, nullptr));
|
||||
|
||||
if (avcodec_open2(mCodecCtx, codec, nullptr) < 0)
|
||||
throw std::runtime_error(std::string("Failed to open audio codec ") + codec->long_name);
|
||||
if (avcodec_open2(codecCtxPtr.get(), codec, nullptr) < 0)
|
||||
throw std::runtime_error(std::string("Failed to open audio codec ") + codec->long_name);
|
||||
|
||||
mFrame = av_frame_alloc();
|
||||
AVFramePtr frame(av_frame_alloc());
|
||||
if (frame == nullptr)
|
||||
throw std::runtime_error("Failed to allocate frame");
|
||||
|
||||
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;
|
||||
if (codecCtxPtr->sample_fmt == AV_SAMPLE_FMT_U8P)
|
||||
mOutputSampleFormat = AV_SAMPLE_FMT_U8;
|
||||
// FIXME: Check for AL_EXT_FLOAT32 support
|
||||
// else if (codecCtxPtr->sample_fmt == AV_SAMPLE_FMT_FLT || codecCtxPtr->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);
|
||||
mOutputChannelLayout = (*stream)->codecpar->channel_layout;
|
||||
if (mOutputChannelLayout == 0)
|
||||
mOutputChannelLayout = av_get_default_channel_layout(codecCtxPtr->channels);
|
||||
|
||||
mCodecCtx->channel_layout = mOutputChannelLayout;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
if (mStream)
|
||||
avcodec_free_context(&mCodecCtx);
|
||||
mStream = nullptr;
|
||||
codecCtxPtr->channel_layout = mOutputChannelLayout;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
mIoCtx = std::move(ioCtx);
|
||||
mFrame = std::move(frame);
|
||||
mFormatCtx = std::move(formatCtxPtr);
|
||||
mCodecCtx = std::move(codecCtxPtr);
|
||||
mStream = stream;
|
||||
}
|
||||
|
||||
void FFmpeg_Decoder::close()
|
||||
{
|
||||
if (mStream)
|
||||
avcodec_free_context(&mCodecCtx);
|
||||
mStream = nullptr;
|
||||
mCodecCtx.reset();
|
||||
|
||||
av_packet_unref(&mPacket);
|
||||
av_freep(&mDataBuf);
|
||||
av_frame_free(&mFrame);
|
||||
mFrame.reset();
|
||||
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);
|
||||
}
|
||||
|
||||
mFormatCtx.reset();
|
||||
mIoCtx.reset();
|
||||
mDataStream.reset();
|
||||
}
|
||||
|
||||
|
@ -436,10 +420,7 @@ namespace MWSound
|
|||
|
||||
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)
|
||||
|
@ -470,5 +451,4 @@ namespace MWSound
|
|||
{
|
||||
close();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -32,14 +32,43 @@ extern "C"
|
|||
|
||||
namespace MWSound
|
||||
{
|
||||
struct AVIOContextDeleter
|
||||
{
|
||||
void operator()(AVIOContext* ptr) const;
|
||||
};
|
||||
|
||||
using AVIOContextPtr = std::unique_ptr<AVIOContext, AVIOContextDeleter>;
|
||||
|
||||
struct AVFormatContextDeleter
|
||||
{
|
||||
void operator()(AVFormatContext* ptr) const;
|
||||
};
|
||||
|
||||
using AVFormatContextPtr = std::unique_ptr<AVFormatContext, AVFormatContextDeleter>;
|
||||
|
||||
struct AVCodecContextDeleter
|
||||
{
|
||||
void operator()(AVCodecContext* ptr) const;
|
||||
};
|
||||
|
||||
using AVCodecContextPtr = std::unique_ptr<AVCodecContext, AVCodecContextDeleter>;
|
||||
|
||||
struct AVFrameDeleter
|
||||
{
|
||||
void operator()(AVFrame* ptr) const;
|
||||
};
|
||||
|
||||
using AVFramePtr = std::unique_ptr<AVFrame, AVFrameDeleter>;
|
||||
|
||||
class FFmpeg_Decoder final : public Sound_Decoder
|
||||
{
|
||||
AVFormatContext* mFormatCtx;
|
||||
AVCodecContext* mCodecCtx;
|
||||
AVIOContextPtr mIoCtx;
|
||||
AVFormatContextPtr mFormatCtx;
|
||||
AVCodecContextPtr mCodecCtx;
|
||||
AVStream** mStream;
|
||||
|
||||
AVPacket mPacket;
|
||||
AVFrame* mFrame;
|
||||
AVFramePtr mFrame;
|
||||
|
||||
std::size_t mFrameSize;
|
||||
std::size_t mFramePos;
|
||||
|
|
Loading…
Reference in a new issue