136 lines
3.7 KiB
C++
136 lines
3.7 KiB
C++
#include "decoder.hpp"
|
|
#include <utility>
|
|
extern "C" {
|
|
#include <libavcodec/avcodec.h>
|
|
#include <libswscale/swscale.h>
|
|
#include <libavutil/imgutils.h>
|
|
} //extern C
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <stdexcept>
|
|
|
|
namespace mgs::ffmpeg {
|
|
namespace {
|
|
constexpr int Align = 32;
|
|
|
|
::AVPixelFormat get_favourite_format_ifp (::AVCodecContext* s, const ::AVPixelFormat* fmt) {
|
|
assert(s->opaque);
|
|
|
|
const ::AVPixelFormat* curr_fmt = fmt;
|
|
const auto fav_format = static_cast<const Decoder*>(s->opaque)->destination_format();
|
|
while (*curr_fmt != -1) {
|
|
if (*curr_fmt == fav_format)
|
|
return fav_format;
|
|
++curr_fmt;
|
|
}
|
|
return ::avcodec_default_get_format(s, fmt);
|
|
}
|
|
|
|
} //unnamed namespace
|
|
|
|
Decoder::Decoder (UniqueAVCodecContext&& context, ::AVPixelFormat dst_format) :
|
|
m_context(std::move(context)),
|
|
m_packet(::av_packet_alloc()),
|
|
m_frame1(::av_frame_alloc()),
|
|
m_frame2(::av_frame_alloc()),
|
|
m_dst_format(dst_format)
|
|
{
|
|
assert(not m_context->opaque);
|
|
assert(m_context->codec);
|
|
m_context->opaque = this;
|
|
m_context->get_format = &get_favourite_format_ifp;
|
|
|
|
if (::avcodec_open2(m_context.get(), m_context->codec, nullptr) < 0)
|
|
throw std::runtime_error("Could not open codec");
|
|
}
|
|
|
|
Decoder::~Decoder() noexcept = default;
|
|
|
|
std::size_t Decoder::decode (const std::uint8_t* input, std::size_t input_size, std::uint8_t* out, std::size_t out_size) {
|
|
//from https://stackoverflow.com/questions/55131040/how-to-decode-mjpeg-with-ffmpeg
|
|
if (not input_size)
|
|
return 0;
|
|
|
|
UniqueAVMem buf_data {static_cast<std::uint8_t*>(::av_malloc(input_size))};
|
|
std::copy(input, input + input_size, buf_data.get());
|
|
if (::av_packet_from_data (m_packet.get(), buf_data.get(), input_size))
|
|
throw std::runtime_error("Setting packet data failed");
|
|
buf_data.release();
|
|
|
|
const int send_packet_ret = ::avcodec_send_packet(m_context.get(), m_packet.get());
|
|
::av_packet_unref(m_packet.get());
|
|
if (send_packet_ret < 0)
|
|
return 0;
|
|
|
|
if (not m_sws_context)
|
|
init_sws_context();
|
|
|
|
const int recv_frame_ret = ::avcodec_receive_frame(m_context.get(), m_frame1.get());
|
|
if (recv_frame_ret < 0)
|
|
return 0;
|
|
|
|
const std::size_t new_out_size = ::av_image_get_buffer_size(
|
|
m_dst_format,
|
|
m_context->width,
|
|
m_context->height,
|
|
Align
|
|
);
|
|
|
|
assert(m_frame2->data[0]);
|
|
::sws_scale(
|
|
m_sws_context.get(),
|
|
m_frame1->data,
|
|
m_frame1->linesize,
|
|
0,
|
|
m_frame1->height,
|
|
m_frame2->data,
|
|
m_frame2->linesize
|
|
);
|
|
const std::size_t out_size_safe = std::min(out_size, new_out_size);
|
|
std::copy(m_frame2->data[0], m_frame2->data[0] + out_size_safe, out);
|
|
|
|
return new_out_size;
|
|
}
|
|
|
|
::AVPixelFormat Decoder::destination_format() const noexcept {
|
|
return m_dst_format;
|
|
}
|
|
|
|
void Decoder::init_sws_context() {
|
|
const std::size_t new_out_size = ::av_image_get_buffer_size(
|
|
m_dst_format,
|
|
m_context->width,
|
|
m_context->height,
|
|
Align
|
|
);
|
|
|
|
//are you calling this method before ffmpeg had a chance to autodetect
|
|
//the correct src pix format?
|
|
//it should be a side effect of avcodec_send_packet()
|
|
assert(::AVPixelFormat::AV_PIX_FMT_NONE != m_context->pix_fmt);
|
|
|
|
m_sws_context = UniqueSwsContext{::sws_getContext(
|
|
m_context->width, //int src_width
|
|
m_context->height, //int src_height
|
|
m_context->pix_fmt, //AV_PIX_FMT_YUVJ422P, //AVPixelFormat src_format
|
|
m_context->width, //int dst_width
|
|
m_context->height, //int dst_height
|
|
m_dst_format, //AVPixelFormat dst_format
|
|
SWS_FAST_BILINEAR, //int flags
|
|
nullptr, //SwsFilter* src_filter
|
|
nullptr, //SwsFilter* dst_filter
|
|
nullptr //const double* param
|
|
)};
|
|
m_frame2_mem.reset(static_cast<std::uint8_t*>(::av_malloc(new_out_size)));
|
|
::av_image_fill_arrays(
|
|
m_frame2->data,
|
|
m_frame2->linesize,
|
|
m_frame2_mem.get(),
|
|
m_dst_format,
|
|
m_context->width,
|
|
m_context->height,
|
|
Align
|
|
);
|
|
}
|
|
|
|
} //namespace mgs::ffmpeg
|