mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-03-30 07:21:12 +00:00
Use RAII for AVIOContext, AVFormatContext, AVCodecContext and AVFrame pointers
This commit is contained in:
parent
0fa4b0137a
commit
f184d8f390
@ -1,15 +1,41 @@
|
|||||||
#include "ffmpeg_decoder.hpp"
|
#include "ffmpeg_decoder.hpp"
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <memory>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include <components/debug/debuglog.hpp>
|
#include <components/debug/debuglog.hpp>
|
||||||
#include <components/vfs/manager.hpp>
|
#include <components/vfs/manager.hpp>
|
||||||
|
|
||||||
namespace MWSound
|
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)
|
int FFmpeg_Decoder::readPacket(void* user_data, uint8_t* buf, int buf_size)
|
||||||
{
|
{
|
||||||
@ -75,7 +101,7 @@ namespace MWSound
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
std::ptrdiff_t stream_idx = mStream - mFormatCtx->streams;
|
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 */
|
/* Check if the packet belongs to this stream */
|
||||||
if (stream_idx == mPacket.stream_index)
|
if (stream_idx == mPacket.stream_index)
|
||||||
@ -102,12 +128,12 @@ namespace MWSound
|
|||||||
do
|
do
|
||||||
{
|
{
|
||||||
/* Decode some data, and check for errors */
|
/* 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 (ret == AVERROR(EAGAIN))
|
||||||
{
|
{
|
||||||
if (mPacket.size == 0 && !getNextPacket())
|
if (mPacket.size == 0 && !getNextPacket())
|
||||||
return false;
|
return false;
|
||||||
ret = avcodec_send_packet(mCodecCtx, &mPacket);
|
ret = avcodec_send_packet(mCodecCtx.get(), &mPacket);
|
||||||
av_packet_unref(&mPacket);
|
av_packet_unref(&mPacket);
|
||||||
if (ret == 0)
|
if (ret == 0)
|
||||||
continue;
|
continue;
|
||||||
@ -187,137 +213,95 @@ namespace MWSound
|
|||||||
close();
|
close();
|
||||||
mDataStream = mResourceMgr->get(fname);
|
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");
|
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 (formatCtxPtr->streams[j]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
|
||||||
if (!mFormatCtx->pb || avformat_open_input(&mFormatCtx, fname.c_str(), nullptr, nullptr) != 0)
|
|
||||||
{
|
{
|
||||||
// "Note that a user-supplied AVFormatContext will be freed on failure".
|
stream = &formatCtxPtr->streams[j];
|
||||||
if (mFormatCtx)
|
break;
|
||||||
{
|
|
||||||
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)
|
if (stream == nullptr)
|
||||||
throw std::runtime_error("Failed to find stream info in " + fname);
|
throw std::runtime_error("No audio streams");
|
||||||
|
|
||||||
for (size_t j = 0; j < mFormatCtx->nb_streams; j++)
|
const AVCodec* codec = avcodec_find_decoder((*stream)->codecpar->codec_id);
|
||||||
{
|
if (codec == nullptr)
|
||||||
if (mFormatCtx->streams[j]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
|
throw std::runtime_error("No codec found for id " + std::to_string((*stream)->codecpar->codec_id));
|
||||||
{
|
|
||||||
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);
|
AVCodecContext* codecCtx = avcodec_alloc_context3(codec);
|
||||||
if (!codec)
|
if (codecCtx == nullptr)
|
||||||
{
|
throw std::runtime_error("Failed to allocate codec context");
|
||||||
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(codecCtx, (*stream)->codecpar);
|
||||||
avcodec_parameters_to_context(avctx, (*mStream)->codecpar);
|
|
||||||
|
|
||||||
// This is not needed anymore above FFMpeg version 4.0
|
// This is not needed anymore above FFMpeg version 4.0
|
||||||
#if LIBAVCODEC_VERSION_INT < 3805796
|
#if LIBAVCODEC_VERSION_INT < 3805796
|
||||||
av_codec_set_pkt_timebase(avctx, (*mStream)->time_base);
|
av_codec_set_pkt_timebase(avctx, (*stream)->time_base);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
mCodecCtx = avctx;
|
AVCodecContextPtr codecCtxPtr(std::exchange(codecCtx, nullptr));
|
||||||
|
|
||||||
if (avcodec_open2(mCodecCtx, codec, nullptr) < 0)
|
if (avcodec_open2(codecCtxPtr.get(), codec, nullptr) < 0)
|
||||||
throw std::runtime_error(std::string("Failed to open audio codec ") + codec->long_name);
|
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)
|
if (codecCtxPtr->sample_fmt == AV_SAMPLE_FMT_U8P)
|
||||||
mOutputSampleFormat = AV_SAMPLE_FMT_U8;
|
mOutputSampleFormat = AV_SAMPLE_FMT_U8;
|
||||||
// FIXME: Check for AL_EXT_FLOAT32 support
|
// FIXME: Check for AL_EXT_FLOAT32 support
|
||||||
// else if (mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLT || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLTP)
|
// else if (codecCtxPtr->sample_fmt == AV_SAMPLE_FMT_FLT || codecCtxPtr->sample_fmt == AV_SAMPLE_FMT_FLTP)
|
||||||
// mOutputSampleFormat = AV_SAMPLE_FMT_S16;
|
// mOutputSampleFormat = AV_SAMPLE_FMT_S16;
|
||||||
else
|
else
|
||||||
mOutputSampleFormat = AV_SAMPLE_FMT_S16;
|
mOutputSampleFormat = AV_SAMPLE_FMT_S16;
|
||||||
|
|
||||||
mOutputChannelLayout = (*mStream)->codecpar->channel_layout;
|
mOutputChannelLayout = (*stream)->codecpar->channel_layout;
|
||||||
if (mOutputChannelLayout == 0)
|
if (mOutputChannelLayout == 0)
|
||||||
mOutputChannelLayout = av_get_default_channel_layout(mCodecCtx->channels);
|
mOutputChannelLayout = av_get_default_channel_layout(codecCtxPtr->channels);
|
||||||
|
|
||||||
mCodecCtx->channel_layout = mOutputChannelLayout;
|
codecCtxPtr->channel_layout = mOutputChannelLayout;
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
if (mStream)
|
|
||||||
avcodec_free_context(&mCodecCtx);
|
|
||||||
mStream = nullptr;
|
|
||||||
|
|
||||||
if (mFormatCtx != nullptr)
|
mIoCtx = std::move(ioCtx);
|
||||||
{
|
mFrame = std::move(frame);
|
||||||
if (mFormatCtx->pb->buffer != nullptr)
|
mFormatCtx = std::move(formatCtxPtr);
|
||||||
{
|
mCodecCtx = std::move(codecCtxPtr);
|
||||||
av_free(mFormatCtx->pb->buffer);
|
mStream = stream;
|
||||||
mFormatCtx->pb->buffer = nullptr;
|
|
||||||
}
|
|
||||||
av_free(mFormatCtx->pb);
|
|
||||||
mFormatCtx->pb = nullptr;
|
|
||||||
|
|
||||||
avformat_close_input(&mFormatCtx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FFmpeg_Decoder::close()
|
void FFmpeg_Decoder::close()
|
||||||
{
|
{
|
||||||
if (mStream)
|
|
||||||
avcodec_free_context(&mCodecCtx);
|
|
||||||
mStream = nullptr;
|
mStream = nullptr;
|
||||||
|
mCodecCtx.reset();
|
||||||
|
|
||||||
av_packet_unref(&mPacket);
|
av_packet_unref(&mPacket);
|
||||||
av_freep(&mDataBuf);
|
av_freep(&mDataBuf);
|
||||||
av_frame_free(&mFrame);
|
mFrame.reset();
|
||||||
swr_free(&mSwr);
|
swr_free(&mSwr);
|
||||||
|
|
||||||
if (mFormatCtx)
|
mFormatCtx.reset();
|
||||||
{
|
mIoCtx.reset();
|
||||||
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();
|
mDataStream.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -436,10 +420,7 @@ namespace MWSound
|
|||||||
|
|
||||||
FFmpeg_Decoder::FFmpeg_Decoder(const VFS::Manager* vfs)
|
FFmpeg_Decoder::FFmpeg_Decoder(const VFS::Manager* vfs)
|
||||||
: Sound_Decoder(vfs)
|
: Sound_Decoder(vfs)
|
||||||
, mFormatCtx(nullptr)
|
|
||||||
, mCodecCtx(nullptr)
|
|
||||||
, mStream(nullptr)
|
, mStream(nullptr)
|
||||||
, mFrame(nullptr)
|
|
||||||
, mFrameSize(0)
|
, mFrameSize(0)
|
||||||
, mFramePos(0)
|
, mFramePos(0)
|
||||||
, mNextPts(0.0)
|
, mNextPts(0.0)
|
||||||
@ -470,5 +451,4 @@ namespace MWSound
|
|||||||
{
|
{
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -32,14 +32,43 @@ extern "C"
|
|||||||
|
|
||||||
namespace MWSound
|
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
|
class FFmpeg_Decoder final : public Sound_Decoder
|
||||||
{
|
{
|
||||||
AVFormatContext* mFormatCtx;
|
AVIOContextPtr mIoCtx;
|
||||||
AVCodecContext* mCodecCtx;
|
AVFormatContextPtr mFormatCtx;
|
||||||
|
AVCodecContextPtr mCodecCtx;
|
||||||
AVStream** mStream;
|
AVStream** mStream;
|
||||||
|
|
||||||
AVPacket mPacket;
|
AVPacket mPacket;
|
||||||
AVFrame* mFrame;
|
AVFramePtr mFrame;
|
||||||
|
|
||||||
std::size_t mFrameSize;
|
std::size_t mFrameSize;
|
||||||
std::size_t mFramePos;
|
std::size_t mFramePos;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user