From f90e1b1c4e8cc58239815158e5a1c28ee11b793e Mon Sep 17 00:00:00 2001 From: Alexander Drozdov Date: Thu, 9 Apr 2026 22:31:40 +1000 Subject: [PATCH 1/2] Fix decoding for the multiple frames per packet Fix #174 --- src/avcpp/codeccontext.cpp | 31 ++++++++++++------ src/avcpp/packet.cpp | 16 ++++++--- src/avcpp/packet.h | 4 +++ tests/CMakeLists.txt | 3 +- tests/CodecContext.cpp | 67 ++++++++++++++++++++++++++++++++++++++ tests/Packet.cpp | 6 ++++ 6 files changed, 111 insertions(+), 16 deletions(-) create mode 100644 tests/CodecContext.cpp diff --git a/src/avcpp/codeccontext.cpp b/src/avcpp/codeccontext.cpp index f67807e3..5bec20af 100644 --- a/src/avcpp/codeccontext.cpp +++ b/src/avcpp/codeccontext.cpp @@ -893,17 +893,28 @@ std::pair CodecContext2::decodeCommon(AVFrame *outF if (!decodeProc) return make_error_pair(Errors::CodecInvalidDecodeProc); - if (offset && inPacket.size() && offset >= inPacket.size()) - return make_error_pair(Errors::CodecDecodingOffsetToLarge); - + // sizeof(AVPacket) is not a part of the public API + std::unique_ptr pkt; + auto processPkt = inPacket.raw(); frameFinished = 0; - AVPacket pkt = *inPacket.raw(); - pkt.data += offset; - pkt.size -= offset; + if (inPacket.raw() && offset) { + if (offset && inPacket.size() && offset >= inPacket.size()) + return make_error_pair(Errors::CodecDecodingOffsetToLarge); + + std::error_code ec; + pkt.reset(inPacket.makeRef(ec)); + if (ec) { + return {ec.value(), &ec.category()}; + } + + pkt->data += offset; + pkt->size -= offset; + processPkt = pkt.get(); + } - int decoded = decodeProc(m_raw, outFrame, &frameFinished, &pkt); - return make_error_pair(decoded); + // not a "flush" internally: just read-out Frame from output queue + return make_error_pair(decodeProc(m_raw, outFrame, &frameFinished, processPkt)); } std::pair CodecContext2::encodeCommon(Packet &outPacket, const AVFrame *inFrame, int &gotPacket, int (*encodeProc)(AVCodecContext *, AVPacket *, const AVFrame *, int *)) noexcept @@ -1028,7 +1039,7 @@ CodecContext2::decodeCommon(T &outFrame, // Dial with PTS/DTS in packet/stream timebase - if (inPacket.timeBase() != Rational()) + if (inPacket.raw() && inPacket.timeBase() != Rational()) outFrame.setTimeBase(inPacket.timeBase()); #if AVCPP_HAS_AVFORMAT else @@ -1051,7 +1062,7 @@ CodecContext2::decodeCommon(T &outFrame, // Convert to decoder/frame time base. Seems not nessesary. outFrame.setTimeBase(timeBase()); - if (inPacket) + if (inPacket.raw() && inPacket) outFrame.setStreamIndex(inPacket.streamIndex()); #if AVCPP_HAS_AVFORMAT else diff --git a/src/avcpp/packet.cpp b/src/avcpp/packet.cpp index f12ccca8..d1e5cd5f 100644 --- a/src/avcpp/packet.cpp +++ b/src/avcpp/packet.cpp @@ -5,19 +5,25 @@ using namespace std; namespace av { +#if AVCPP_API_AVCODEC_NEW_INIT_PACKET +Packet::Packet(std::nullptr_t) + : FFWrapperPtr(nullptr), + m_completeFlag(false), + m_timeBase(0,0) +{ +} +#endif + Packet::Packet() + : m_completeFlag(false), + m_timeBase(0, 0) { #if AVCPP_API_AVCODEC_NEW_INIT_PACKET m_raw = av_packet_alloc(); #else av_init_packet(raw()); #endif - raw()->stream_index = -1; // no stream - - m_completeFlag = false; - m_timeBase = Rational(0, 0); - } Packet::Packet(const Packet &packet) diff --git a/src/avcpp/packet.h b/src/avcpp/packet.h index df787870..71c27cba 100644 --- a/src/avcpp/packet.h +++ b/src/avcpp/packet.h @@ -65,6 +65,10 @@ class Packet : */ struct wrap_data_static {}; +#if AVCPP_API_AVCODEC_NEW_INIT_PACKET + explicit Packet(std::nullptr_t); // explicit nullptr packet. There is no extra internal checks, so be careful +#endif + Packet(); Packet(const Packet &packet, OptionalErrorCode ec); Packet(const Packet &packet); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9fcdd356..38f35a0e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -15,7 +15,8 @@ add_executable(test_executor PixelSampleFormat.cpp Common.cpp Buffer.cpp - FormatCustomIO_test.cpp) + FormatCustomIO_test.cpp + CodecContext.cpp) target_link_libraries(test_executor PUBLIC Catch2::Catch2WithMain avcpp::avcpp) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../catch2/contrib") diff --git a/tests/CodecContext.cpp b/tests/CodecContext.cpp new file mode 100644 index 00000000..e3891bc3 --- /dev/null +++ b/tests/CodecContext.cpp @@ -0,0 +1,67 @@ +// +// +// TODO: test incomplete. Sounds like RAWVIDEO decoder does not support multiple frames per-packet: only first one +// consumed +// +// + +#include + +#include "avcpp/avconfig.h" + +#include + +#include "avcpp/packet.h" +#include "avcpp/codeccontext.h" + +namespace { +constexpr std::size_t W = 320; +constexpr std::size_t H = 240; +const av::PixelFormat FMT{AV_PIX_FMT_GRAY8}; +constexpr std::size_t FRAMES = 4; +constexpr std::size_t FRAME_SIZE = W * H; +constexpr std::size_t FRAMES_SIZE = FRAME_SIZE * FRAMES; +} + +TEST_CASE("CodecContext") +{ + SECTION("Decode packet with multiple frames") { + + std::vector block; + block.reserve(FRAMES_SIZE + AV_INPUT_BUFFER_PADDING_SIZE); + block.resize(FRAMES_SIZE); // Gray + + av::Codec vcodec = av::findDecodingCodec(AVCodecID::AV_CODEC_ID_RAWVIDEO); + av::VideoDecoderContext vdec{vcodec}; + vdec.setHeight(H); + vdec.setWidth(W); + vdec.setPixelFormat(FMT); + vdec.setTimeBase({1, 1}); + vdec.open(); + + for (auto i = 0; i < FRAMES; ++i) { + std::fill_n(block.data() + i * FRAME_SIZE, FRAME_SIZE, (i + 1) * 50); + } + + // + av::Packet pkt{block, av::Packet::wrap_data_static{}}; + pkt.setTimeBase(vdec.timeBase()); + + static std::size_t ITERS = 4; + std::size_t framesCount = 0; + for (auto i = 0; i < ITERS; ++i) { + pkt.setPts({i, {1,1}}); + auto frame = vdec.decode(pkt); + if (!frame) // TBD + break; + // in case, when multiple frames per packet + while (frame) { + std::cout << "Frame: " << int(frame.data()[0]) << ", cnt=" << (framesCount++) << std::endl; + frame = vdec.decode(av::Packet{nullptr}); + } + } + + // TBD: rawvideo does not support multiple frames per packet. + // CHECK(framesCount == ITERS * FRAMES); + } +} \ No newline at end of file diff --git a/tests/Packet.cpp b/tests/Packet.cpp index fddd9959..75e2f4a0 100644 --- a/tests/Packet.cpp +++ b/tests/Packet.cpp @@ -106,6 +106,12 @@ TEST_CASE("Packet define", "[Packet][Construct]") CHECK(pkt.refCount() == 1); } + SECTION("Nullptr packet") { + av::Packet pkt{nullptr}; + CHECK(pkt.raw() == nullptr); + // any other operation not permitted + } + SECTION("setPts/Dts") { { av::Packet pkt; From 6bf8c76b120cf2cc448d706ea22f112017803605 Mon Sep 17 00:00:00 2001 From: Alexander Drozdov Date: Mon, 13 Apr 2026 10:09:53 +1000 Subject: [PATCH 2/2] Frame: fix isValid() to work with any HW-encoded frames Sounds like FFmpeg uses data[3] as a reference to the internal HW frame ID at the same time, when data[0] still is NULL. At same time hw_frames_ctx is not a NULL. Also, add isHwFrame(). Fix #175 --- src/avcpp/frame.cpp | 8 ++++++-- src/avcpp/frame.h | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/avcpp/frame.cpp b/src/avcpp/frame.cpp index 7ccbe34d..7254f217 100644 --- a/src/avcpp/frame.cpp +++ b/src/avcpp/frame.cpp @@ -646,12 +646,16 @@ bool FrameCommon::isComplete() const { return m_isComplete; } bool FrameCommon::isValid() const { return (!isNull() && ((m_raw->data[0] && m_raw->linesize[0]) || - ((m_raw->format == AV_PIX_FMT_VAAPI) && ((intptr_t)m_raw->data[3] > 0))) - ); + ((m_raw->hw_frames_ctx) && ((intptr_t)m_raw->data[3] > 0)))); } FrameCommon::operator bool() const { return isValid() && isComplete(); } +bool FrameCommon::isHwFrame() const +{ + return !isNull() && !!m_raw->hw_frames_ctx; +} + uint8_t *FrameCommon::data(size_t plane) { if (!m_raw || plane >= size_t(AV_NUM_DATA_POINTERS + m_raw->nb_extended_buf)) return nullptr; diff --git a/src/avcpp/frame.h b/src/avcpp/frame.h index 5b42b85c..4678b027 100644 --- a/src/avcpp/frame.h +++ b/src/avcpp/frame.h @@ -110,6 +110,8 @@ class FrameCommon : public FFWrapperPtr operator bool() const; + bool isHwFrame() const; + uint8_t *data(size_t plane = 0); const uint8_t *data(size_t plane = 0) const;