Commit 873ecbe6 for openh264
commit 873ecbe67c8583e9eb8d39b30a1a7a213ebcd833
Author: BenzhengZhang <140143892+BenzhengZhang@users.noreply.github.com>
Date: Tue Sep 1 14:13:46 2026 +0800
decoder: restrict IncreasePicBuff ownership reset to new slots only (#3995)
Co-authored-by: benzzhan <benzzhan@cisco.com>
diff --git a/codec/decoder/core/src/decoder.cpp b/codec/decoder/core/src/decoder.cpp
index e3d36c26..198b3e5a 100644
--- a/codec/decoder/core/src/decoder.cpp
+++ b/codec/decoder/core/src/decoder.cpp
@@ -148,7 +148,8 @@ static int32_t IncreasePicBuff (PWelsDecoderContext pCtx, PPicBuff* ppPicBuf, co
pPicNewBuf->iCurrentIdx = pPicOldBuf->iCurrentIdx;
* ppPicBuf = pPicNewBuf;
- for (int32_t i = 0; i < pPicNewBuf->iCapacity; i++) {
+ // only initialize new slots; old slots must preserve iRefCount pinned by output buffering
+ for (int32_t i = kiOldSize; i < pPicNewBuf->iCapacity; i++) {
pPicNewBuf->ppPic[i]->bUsedAsRef = false;
pPicNewBuf->ppPic[i]->bIsLongRef = false;
pPicNewBuf->ppPic[i]->iRefCount = 0;
diff --git a/test/api/decode_api_test.cpp b/test/api/decode_api_test.cpp
index 30ecef18..ce92da29 100644
--- a/test/api/decode_api_test.cpp
+++ b/test/api/decode_api_test.cpp
@@ -6,6 +6,9 @@
#include "BaseEncoderTest.h"
#include "wels_common_defs.h"
#include "utils/HashFunctions.h"
+#include <climits>
+#include <cstring>
+#include <fstream>
#include <string>
#include <vector>
#include "encode_decode_api_test.h"
@@ -1274,3 +1277,117 @@ TEST_F (DecodeParseCrashAPI, ParseOnlyCrash_General) {
}
+// Verify B-frame decode is deterministic across reinit; guards against stale
+// output caused by IncreasePicBuff clearing iRefCount of old pinned slots.
+namespace {
+static uint64_t Fnv1a64Frame (const uint8_t* y, int w, int h, int stride_y,
+ const uint8_t* u, const uint8_t* v, int stride_uv) {
+ uint64_t hash = 14695981039346656037ull;
+ for (int row = 0; row < h; ++row) {
+ for (int col = 0; col < w; ++col) {
+ hash ^= y[row * stride_y + col];
+ hash *= 1099511628211ull;
+ }
+ }
+ for (int row = 0; row < h / 2; ++row) {
+ for (int col = 0; col < w / 2; ++col) {
+ hash ^= u[row * stride_uv + col];
+ hash *= 1099511628211ull;
+ hash ^= v[row * stride_uv + col];
+ hash *= 1099511628211ull;
+ }
+ }
+ return hash;
+}
+
+static std::vector<uint64_t> DecodeBFrameStreamHashes (ISVCDecoder* dec, const char* path) {
+ std::ifstream f (path, std::ios::binary);
+ if (!f.is_open()) return {};
+ std::vector<uint8_t> bs ((std::istreambuf_iterator<char> (f)), {});
+
+ std::vector<uint64_t> hashes;
+ size_t pos = 0;
+
+ auto decode_nal = [&] (const uint8_t* data, int len) {
+ uint8_t* dst[3] = {nullptr, nullptr, nullptr};
+ SBufferInfo info;
+ memset (&info, 0, sizeof (info));
+ dec->DecodeFrame2 (data, len, dst, &info);
+ if (info.iBufferStatus == 1) {
+ const int w = info.UsrData.sSystemBuffer.iWidth;
+ const int h = info.UsrData.sSystemBuffer.iHeight;
+ hashes.push_back (Fnv1a64Frame (dst[0], w, h,
+ info.UsrData.sSystemBuffer.iStride[0],
+ dst[1], dst[2],
+ info.UsrData.sSystemBuffer.iStride[1]));
+ }
+ };
+
+ while (pos + 4 < bs.size()) {
+ // find next start code
+ size_t next = pos + 4;
+ while (next + 3 < bs.size()) {
+ if ((bs[next] == 0 && bs[next+1] == 0 && bs[next+2] == 0 && bs[next+3] == 1) ||
+ (bs[next] == 0 && bs[next+1] == 0 && bs[next+2] == 1))
+ break;
+ ++next;
+ }
+ if (next + 3 >= bs.size()) next = bs.size();
+ decode_nal (bs.data() + pos, static_cast<int> (next - pos));
+ pos = next;
+ }
+
+ int32_t eos = 1;
+ dec->SetOption (DECODER_OPTION_END_OF_STREAM, &eos);
+ int32_t rem = 0;
+ dec->GetOption (DECODER_OPTION_NUM_OF_FRAMES_REMAINING_IN_BUFFER, &rem);
+ for (int32_t i = 0; i < rem; ++i) {
+ uint8_t* dst[3] = {nullptr, nullptr, nullptr};
+ SBufferInfo info;
+ memset (&info, 0, sizeof (info));
+ dec->FlushFrame (dst, &info);
+ if (info.iBufferStatus == 1) {
+ const int w = info.UsrData.sSystemBuffer.iWidth;
+ const int h = info.UsrData.sSystemBuffer.iHeight;
+ hashes.push_back (Fnv1a64Frame (dst[0], w, h,
+ info.UsrData.sSystemBuffer.iStride[0],
+ dst[1], dst[2],
+ info.UsrData.sSystemBuffer.iStride[1]));
+ }
+ }
+ return hashes;
+}
+} // namespace
+
+TEST (DecoderDpbGrowthStability, BFrameDecodeIsConsistentAcrossReinit) {
+ ISVCDecoder* dec = nullptr;
+ ASSERT_EQ (WelsCreateDecoder (&dec), 0);
+ ASSERT_NE (dec, nullptr);
+
+ SDecodingParam param;
+ memset (¶m, 0, sizeof (param));
+ param.uiTargetDqLayer = UCHAR_MAX;
+ param.eEcActiveIdc = ERROR_CON_SLICE_COPY;
+
+ ASSERT_EQ (dec->Initialize (¶m), 0);
+ const std::vector<uint64_t> hashes1 =
+ DecodeBFrameStreamHashes (dec, "res/Cisco_Men_whisper_640x320_CABAC_Bframe_9.264");
+
+ dec->Uninitialize();
+ memset (¶m, 0, sizeof (param));
+ param.uiTargetDqLayer = UCHAR_MAX;
+ param.eEcActiveIdc = ERROR_CON_SLICE_COPY;
+ ASSERT_EQ (dec->Initialize (¶m), 0);
+ const std::vector<uint64_t> hashes2 =
+ DecodeBFrameStreamHashes (dec, "res/Cisco_Men_whisper_640x320_CABAC_Bframe_9.264");
+
+ dec->Uninitialize();
+ WelsDestroyDecoder (dec);
+
+ ASSERT_GT (hashes1.size(), 0u) << "no frames decoded in pass 1";
+ ASSERT_EQ (hashes1.size(), hashes2.size()) << "frame count differs between passes";
+ for (size_t i = 0; i < hashes1.size(); ++i) {
+ EXPECT_EQ (hashes1[i], hashes2[i]) << "stale/wrong frame at index " << i;
+ }
+}
+