Commit 0bab7fe8 for openh264
commit 0bab7fe805b6aa56a7886deb7de357e514268688
Author: BenzhengZhang <140143892+BenzhengZhang@users.noreply.github.com>
Date: Tue Sep 8 14:40:48 2026 +0800
decoder: guard threaded DPB reallocation (#4002)
* decoder: guard threaded DPB reallocation
* decoder: harden threaded DPB realloc ownership and add ref-queue regression
---------
Co-authored-by: benzzhan <benzzhan@cisco.com>
diff --git a/codec/decoder/core/inc/pic_queue.h b/codec/decoder/core/inc/pic_queue.h
index 473f80ff..b41bb916 100644
--- a/codec/decoder/core/inc/pic_queue.h
+++ b/codec/decoder/core/inc/pic_queue.h
@@ -37,6 +37,10 @@
#include "picture.h"
+namespace WelsCommon {
+class CMemoryAlign;
+}
+
namespace WelsDec {
#define PICTURE_RESOLUTION_ALIGNMENT 32
@@ -46,6 +50,7 @@ typedef struct TagPicBuff {
PPicture* ppPic;
int32_t iCapacity; // capacity size of queue
int32_t iCurrentIdx;
+ WelsCommon::CMemoryAlign* pMa; // allocator that owns this buffer's memory; used to free it
} SPicBuff, *PPicBuff;
/*
diff --git a/codec/decoder/core/src/decoder.cpp b/codec/decoder/core/src/decoder.cpp
index 198b3e5a..9d05a9b0 100644
--- a/codec/decoder/core/src/decoder.cpp
+++ b/codec/decoder/core/src/decoder.cpp
@@ -76,6 +76,7 @@ static int32_t CreatePicBuff (PWelsDecoderContext pCtx, PPicBuff* ppPicBuf, cons
if (NULL == pPicBuf) {
return ERR_INFO_OUT_OF_MEMORY;
}
+ pPicBuf->pMa = pMa;
pPicBuf->ppPic = (PPicture*)pMa->WelsMallocz (kiSize * sizeof (PPicture), "PPicture*");
@@ -114,11 +115,13 @@ static int32_t IncreasePicBuff (PWelsDecoderContext pCtx, PPicBuff* ppPicBuf, co
}
CMemoryAlign* pMa = pCtx->pMemAlign;
+ CMemoryAlign* pOldMa = (pPicOldBuf != NULL && pPicOldBuf->pMa != NULL) ? pPicOldBuf->pMa : pMa;
pPicNewBuf = (PPicBuff)pMa->WelsMallocz (sizeof (SPicBuff), "PPicBuff");
if (NULL == pPicNewBuf) {
return ERR_INFO_OUT_OF_MEMORY;
}
+ pPicNewBuf->pMa = pMa;
pPicNewBuf->ppPic = (PPicture*)pMa->WelsMallocz (kiNewSize * sizeof (PPicture), "PPicture*");
@@ -158,12 +161,12 @@ static int32_t IncreasePicBuff (PWelsDecoderContext pCtx, PPicBuff* ppPicBuf, co
}
// remove old PicBuf
if (pPicOldBuf->ppPic != NULL) {
- pMa->WelsFree (pPicOldBuf->ppPic, "pPicOldBuf->queue");
+ pOldMa->WelsFree (pPicOldBuf->ppPic, "pPicOldBuf->queue");
pPicOldBuf->ppPic = NULL;
}
pPicOldBuf->iCapacity = 0;
pPicOldBuf->iCurrentIdx = 0;
- pMa->WelsFree (pPicOldBuf, "pPicOldBuf");
+ pOldMa->WelsFree (pPicOldBuf, "pPicOldBuf");
pPicOldBuf = NULL;
return ERR_NONE;
}
@@ -178,12 +181,14 @@ static int32_t DecreasePicBuff (PWelsDecoderContext pCtx, PPicBuff* ppPicBuf, co
}
CMemoryAlign* pMa = pCtx->pMemAlign;
+ CMemoryAlign* pOldMa = (pPicOldBuf != NULL && pPicOldBuf->pMa != NULL) ? pPicOldBuf->pMa : pMa;
pPicNewBuf = (PPicBuff)pMa->WelsMallocz (sizeof (SPicBuff), "PPicBuff");
if (NULL == pPicNewBuf) {
return ERR_INFO_OUT_OF_MEMORY;
}
+ pPicNewBuf->pMa = pMa;
pPicNewBuf->ppPic = (PPicture*)pMa->WelsMallocz (kiNewSize * sizeof (PPicture), "PPicture*");
@@ -228,7 +233,7 @@ static int32_t DecreasePicBuff (PWelsDecoderContext pCtx, PPicBuff* ppPicBuf, co
for (iPicIdx = iDelIdx; iPicIdx < kiOldSize; iPicIdx++) {
if (iPrevPicIdx != iPicIdx) {
if (pPicOldBuf->ppPic[iPicIdx] != NULL) {
- FreePicture (pPicOldBuf->ppPic[iPicIdx], pMa);
+ FreePicture (pPicOldBuf->ppPic[iPicIdx], pOldMa);
pPicOldBuf->ppPic[iPicIdx] = NULL;
}
}
@@ -247,12 +252,12 @@ static int32_t DecreasePicBuff (PWelsDecoderContext pCtx, PPicBuff* ppPicBuf, co
}
// remove old PicBuf
if (pPicOldBuf->ppPic != NULL) {
- pMa->WelsFree (pPicOldBuf->ppPic, "pPicOldBuf->queue");
+ pOldMa->WelsFree (pPicOldBuf->ppPic, "pPicOldBuf->queue");
pPicOldBuf->ppPic = NULL;
}
pPicOldBuf->iCapacity = 0;
pPicOldBuf->iCurrentIdx = 0;
- pMa->WelsFree (pPicOldBuf, "pPicOldBuf");
+ pOldMa->WelsFree (pPicOldBuf, "pPicOldBuf");
pPicOldBuf = NULL;
return ERR_NONE;
@@ -267,6 +272,9 @@ void DestroyPicBuff (PWelsDecoderContext pCtx, PPicBuff* ppPicBuf, CMemoryAlign*
return;
pPicBuf = *ppPicBuf;
+ // Free through the allocator that created this buffer.
+ if (pPicBuf->pMa != NULL)
+ pMa = pPicBuf->pMa;
while (pPicBuf->ppPic != NULL) {
int32_t iPicIdx = 0;
while (iPicIdx < pPicBuf->iCapacity) {
diff --git a/codec/decoder/plus/src/welsDecoderExt.cpp b/codec/decoder/plus/src/welsDecoderExt.cpp
index 73ead68d..80a27913 100644
--- a/codec/decoder/plus/src/welsDecoderExt.cpp
+++ b/codec/decoder/plus/src/welsDecoderExt.cpp
@@ -1373,12 +1373,43 @@ DECODING_STATE CWelsDecoder::ParseAccessUnit (SWelsDecoderThreadCTX& sThreadCtx)
int32_t iRet = DecodeFrame2WithCtx (sThreadCtx.pCtx, sThreadCtx.kpSrc, sThreadCtx.kiSrcLen, sThreadCtx.ppDst,
&sThreadCtx.sDstInfo);
- int32_t iErr = InitConstructAccessUnit (sThreadCtx.pCtx, &sThreadCtx.sDstInfo);
+ int32_t iErr = WelsDecodeInitAccessUnitStart (sThreadCtx.pCtx, &sThreadCtx.sDstInfo);
if (ERR_NONE != iErr) {
return (DECODING_STATE) (iRet | iErr);
}
if (sThreadCtx.pCtx->bNewSeqBegin) {
+ if (GetThreadCount (sThreadCtx.pCtx) > 1) {
+ // Wait for older workers before replacing shared DPB storage.
+ for (int32_t i = 0; i < m_DecCtxActiveCount; ++i) {
+ if (m_pDecThrCtxActive[i] != NULL && m_pDecThrCtxActive[i] != &sThreadCtx) {
+ WAIT_SEMAPHORE (&m_pDecThrCtxActive[i]->sThreadInfo.sIsIdle, WELS_DEC_THREAD_WAIT_INFINITE);
+ RELEASE_SEMAPHORE (&m_pDecThrCtxActive[i]->sThreadInfo.sIsIdle);
+ }
+ }
+ sThreadCtx.pCtx->pLastThreadCtx = NULL;
+ }
+ iErr = AllocPicBuffOnNewSeqBegin (sThreadCtx.pCtx);
+ if (ERR_NONE != iErr) {
+ return (DECODING_STATE) (iRet | iErr);
+ }
m_pPicBuff = sThreadCtx.pCtx->pPicBuff;
+ // Keep sibling contexts from carrying stale DPB references.
+ for (int32_t i = 0; i < m_iCtxCount; ++i) {
+ if (&m_pDecThrCtx[i] != &sThreadCtx && m_pDecThrCtx[i].pCtx != NULL) {
+ m_pDecThrCtx[i].pCtx->pPicBuff = m_pPicBuff;
+ m_pDecThrCtx[i].pCtx->bHaveGotMemory = sThreadCtx.pCtx->bHaveGotMemory;
+ m_pDecThrCtx[i].pCtx->iPicQueueNumber = sThreadCtx.pCtx->iPicQueueNumber;
+ m_pDecThrCtx[i].pCtx->iImgWidthInPixel = sThreadCtx.pCtx->iImgWidthInPixel;
+ m_pDecThrCtx[i].pCtx->iImgHeightInPixel = sThreadCtx.pCtx->iImgHeightInPixel;
+ m_pDecThrCtx[i].pCtx->pDec = NULL;
+ m_pDecThrCtx[i].pCtx->pLastThreadCtx = NULL;
+ iErr = InitialDqLayersContext (m_pDecThrCtx[i].pCtx, sThreadCtx.pCtx->iImgWidthInPixel,
+ sThreadCtx.pCtx->iImgHeightInPixel);
+ if (ERR_NONE != iErr) {
+ return (DECODING_STATE) (iRet | iErr);
+ }
+ }
+ }
} else if (bPicBuffChanged) {
InitialDqLayersContext (sThreadCtx.pCtx, sThreadCtx.pCtx->pSps->iMbWidth << 4, sThreadCtx.pCtx->pSps->iMbHeight << 4);
}
diff --git a/test/BaseThreadDecoderTest.h b/test/BaseThreadDecoderTest.h
index f9c317ea..7263b323 100644
--- a/test/BaseThreadDecoderTest.h
+++ b/test/BaseThreadDecoderTest.h
@@ -38,6 +38,8 @@ class BaseThreadDecoderTest {
int32_t SetUp();
void TearDown();
bool ThreadDecodeFile (const char* fileName, Callback* cbk);
+ // Decode alternating IDR frames from two streams to force repeated sequence changes.
+ bool ThreadDecodeResolutionSwitch (const char* fileName1, const char* fileName2, int32_t iterations, Callback* cbk);
bool Open (const char* fileName);
ISVCDecoder* decoder_;
diff --git a/test/api/BaseThreadDecoderTest.cpp b/test/api/BaseThreadDecoderTest.cpp
index 52403f7d..487de5c8 100644
--- a/test/api/BaseThreadDecoderTest.cpp
+++ b/test/api/BaseThreadDecoderTest.cpp
@@ -307,6 +307,58 @@ bool BaseThreadDecoderTest::ThreadDecodeFile (const char* fileName, Callback* cb
return true;
}
+static bool ReadFileToBuffer (const char* fileName, BufferedData& buf) {
+ std::ifstream file (fileName, std::ios::in | std::ios::binary);
+ if (!file.is_open())
+ return false;
+ char b;
+ for (;;) {
+ file.read (&b, 1);
+ if (file.gcount() != 1) // end of file
+ break;
+ if (!buf.PushBack (b))
+ return false;
+ }
+ return true;
+}
+
+bool BaseThreadDecoderTest::ThreadDecodeResolutionSwitch (const char* fileName1, const char* fileName2,
+ int32_t iterations, Callback* cbk) {
+ BufferedData buf1;
+ BufferedData buf2;
+ if (!ReadFileToBuffer (fileName1, buf1) || !ReadFileToBuffer (fileName2, buf2))
+ return false;
+
+ int32_t len1 = (int32_t) buf1.Length();
+ int32_t len2 = (int32_t) buf2.Length();
+ int32_t pos1 = 0;
+ int32_t pos2 = 0;
+ int32_t frameSize1 = ReadFrame (buf1.data(), len1, pos1);
+ int32_t frameSize2 = ReadFrame (buf2.data(), len2, pos2);
+ if (frameSize1 <= 0 || frameSize2 <= 0)
+ return false;
+
+ uiTimeStamp = 0;
+ memset (&sBufInfo, 0, sizeof (SBufferInfo));
+ for (int32_t i = 0; i < iterations; ++i) {
+ DecodeFrame (buf1.data(), frameSize1, cbk);
+ if (::testing::Test::HasFatalFailure())
+ return false;
+ DecodeFrame (buf2.data(), frameSize2, cbk);
+ if (::testing::Test::HasFatalFailure())
+ return false;
+ }
+
+ int32_t iEndOfStreamFlag = 1;
+ decoder_->SetOption (DECODER_OPTION_END_OF_STREAM, &iEndOfStreamFlag);
+ int32_t num_of_frames_in_buffer = 0;
+ decoder_->GetOption (DECODER_OPTION_NUM_OF_FRAMES_REMAINING_IN_BUFFER, &num_of_frames_in_buffer);
+ for (int32_t i = 0; i < num_of_frames_in_buffer; ++i) {
+ FlushFrame (cbk);
+ }
+ return true;
+}
+
bool BaseThreadDecoderTest::Open (const char* fileName) {
if (decodeStatus_ == OpenFile) {
file_.open (fileName, std::ios_base::out | std::ios_base::binary);
diff --git a/test/api/thread_decoder_test.cpp b/test/api/thread_decoder_test.cpp
index 852acb42..3e181b96 100644
--- a/test/api/thread_decoder_test.cpp
+++ b/test/api/thread_decoder_test.cpp
@@ -119,6 +119,329 @@ static int32_t ReadFrameForHangRegression (uint8_t* pBuf, const int32_t& iFileSi
return bytesAvailable;
}
+struct SpsInfoForRefQueueSwitch {
+ int32_t iWidth;
+ int32_t iHeight;
+ int32_t iNumRefFrames;
+};
+
+class CBitReaderForRefQueueSwitch {
+ public:
+ CBitReaderForRefQueueSwitch (const uint8_t* pData, const size_t iDataSize)
+ : pData_ (pData), iBitLength_ (iDataSize * 8), iBitPos_ (0) {
+ }
+
+ bool ReadBit (uint32_t* pBit) {
+ if (pBit == NULL || iBitPos_ >= iBitLength_) {
+ return false;
+ }
+
+ const size_t iByteOffset = iBitPos_ >> 3;
+ const int32_t iBitOffset = 7 - static_cast<int32_t> (iBitPos_ & 7);
+ *pBit = (pData_[iByteOffset] >> iBitOffset) & 1;
+ ++iBitPos_;
+ return true;
+ }
+
+ bool ReadBits (const int32_t iNumBits, uint32_t* pValue) {
+ if (pValue == NULL || iNumBits <= 0 || iNumBits > 32) {
+ return false;
+ }
+
+ uint32_t uiValue = 0;
+ for (int32_t i = 0; i < iNumBits; ++i) {
+ uint32_t uiBit = 0;
+ if (!ReadBit (&uiBit)) {
+ return false;
+ }
+ uiValue = (uiValue << 1) | uiBit;
+ }
+
+ *pValue = uiValue;
+ return true;
+ }
+
+ bool ReadUe (uint32_t* pValue) {
+ if (pValue == NULL) {
+ return false;
+ }
+
+ int32_t iLeadingZeros = 0;
+ uint32_t uiBit = 0;
+ while (true) {
+ if (!ReadBit (&uiBit)) {
+ return false;
+ }
+ if (uiBit == 1) {
+ break;
+ }
+ ++iLeadingZeros;
+ if (iLeadingZeros > 31) {
+ return false;
+ }
+ }
+
+ if (iLeadingZeros == 0) {
+ *pValue = 0;
+ return true;
+ }
+
+ uint32_t uiInfoBits = 0;
+ if (!ReadBits (iLeadingZeros, &uiInfoBits)) {
+ return false;
+ }
+
+ *pValue = ((1u << iLeadingZeros) - 1u) + uiInfoBits;
+ return true;
+ }
+
+ bool ReadSe (int32_t* pValue) {
+ if (pValue == NULL) {
+ return false;
+ }
+
+ uint32_t uiUeValue = 0;
+ if (!ReadUe (&uiUeValue)) {
+ return false;
+ }
+
+ if (uiUeValue & 1) {
+ *pValue = static_cast<int32_t> ((uiUeValue + 1) >> 1);
+ } else {
+ *pValue = -static_cast<int32_t> (uiUeValue >> 1);
+ }
+ return true;
+ }
+
+ private:
+ const uint8_t* pData_;
+ size_t iBitLength_;
+ size_t iBitPos_;
+};
+
+static bool SkipScalingListForRefQueueSwitch (CBitReaderForRefQueueSwitch& sBitReader, const int32_t iListSize) {
+ int32_t iLastScale = 8;
+ int32_t iNextScale = 8;
+
+ for (int32_t i = 0; i < iListSize; ++i) {
+ if (iNextScale != 0) {
+ int32_t iDeltaScale = 0;
+ if (!sBitReader.ReadSe (&iDeltaScale)) {
+ return false;
+ }
+ iNextScale = (iLastScale + iDeltaScale + 256) % 256;
+ }
+ iLastScale = (iNextScale == 0) ? iLastScale : iNextScale;
+ }
+ return true;
+}
+
+static size_t GetStartCodeLengthForRefQueueSwitch (const uint8_t* pData, const size_t iDataSize) {
+ if (iDataSize >= 4 && pData[0] == 0 && pData[1] == 0 && pData[2] == 0 && pData[3] == 1) {
+ return 4;
+ }
+ if (iDataSize >= 3 && pData[0] == 0 && pData[1] == 0 && pData[2] == 1) {
+ return 3;
+ }
+ return 0;
+}
+
+static bool ExtractFirstSpsRbspForRefQueueSwitch (const std::vector<uint8_t>& bitstream, std::vector<uint8_t>* pRbsp) {
+ if (pRbsp == NULL || bitstream.empty()) {
+ return false;
+ }
+
+ const size_t iBitstreamSize = bitstream.size();
+ size_t i = 0;
+ while (i + 3 < iBitstreamSize) {
+ const size_t iStartCodeLength = GetStartCodeLengthForRefQueueSwitch (&bitstream[i], iBitstreamSize - i);
+ if (iStartCodeLength == 0) {
+ ++i;
+ continue;
+ }
+
+ const size_t iNalStart = i + iStartCodeLength;
+ size_t iNalEnd = iBitstreamSize;
+ for (size_t j = iNalStart; j + 3 < iBitstreamSize; ++j) {
+ if (GetStartCodeLengthForRefQueueSwitch (&bitstream[j], iBitstreamSize - j) != 0) {
+ iNalEnd = j;
+ break;
+ }
+ }
+
+ if (iNalStart < iNalEnd && (bitstream[iNalStart] & 0x1F) == 7) {
+ pRbsp->clear();
+ for (size_t k = iNalStart + 1; k < iNalEnd; ++k) {
+ if (k + 2 < iNalEnd && bitstream[k] == 0 && bitstream[k + 1] == 0 && bitstream[k + 2] == 3) {
+ pRbsp->push_back (0);
+ pRbsp->push_back (0);
+ k += 2;
+ continue;
+ }
+ pRbsp->push_back (bitstream[k]);
+ }
+ return !pRbsp->empty();
+ }
+
+ i = iNalEnd;
+ }
+
+ return false;
+}
+
+static bool ParseSpsForRefQueueSwitch (const std::vector<uint8_t>& rbsp, SpsInfoForRefQueueSwitch* pSpsInfo) {
+ if (pSpsInfo == NULL || rbsp.empty()) {
+ return false;
+ }
+
+ CBitReaderForRefQueueSwitch sBitReader (rbsp.data(), rbsp.size());
+
+ uint32_t uiProfileIdc = 0;
+ uint32_t uiTmp = 0;
+ if (!sBitReader.ReadBits (8, &uiProfileIdc) || !sBitReader.ReadBits (8, &uiTmp)
+ || !sBitReader.ReadBits (8, &uiTmp) || !sBitReader.ReadUe (&uiTmp)) {
+ return false;
+ }
+
+ uint32_t uiChromaFormatIdc = 1;
+ if (uiProfileIdc == 100 || uiProfileIdc == 110 || uiProfileIdc == 122 || uiProfileIdc == 244
+ || uiProfileIdc == 44 || uiProfileIdc == 83 || uiProfileIdc == 86 || uiProfileIdc == 118
+ || uiProfileIdc == 128 || uiProfileIdc == 138 || uiProfileIdc == 139 || uiProfileIdc == 134
+ || uiProfileIdc == 135) {
+ if (!sBitReader.ReadUe (&uiChromaFormatIdc)) {
+ return false;
+ }
+ if (uiChromaFormatIdc == 3 && !sBitReader.ReadBit (&uiTmp)) {
+ return false;
+ }
+ if (!sBitReader.ReadUe (&uiTmp) || !sBitReader.ReadUe (&uiTmp) || !sBitReader.ReadBit (&uiTmp)) {
+ return false;
+ }
+
+ uint32_t uiScalingMatrixPresent = 0;
+ if (!sBitReader.ReadBit (&uiScalingMatrixPresent)) {
+ return false;
+ }
+ if (uiScalingMatrixPresent != 0) {
+ const int32_t iScalingListCount = (uiChromaFormatIdc != 3) ? 8 : 12;
+ for (int32_t i = 0; i < iScalingListCount; ++i) {
+ uint32_t uiScalingListPresent = 0;
+ if (!sBitReader.ReadBit (&uiScalingListPresent)) {
+ return false;
+ }
+ if (uiScalingListPresent != 0 && !SkipScalingListForRefQueueSwitch (sBitReader, i < 6 ? 16 : 64)) {
+ return false;
+ }
+ }
+ }
+ }
+
+ uint32_t uiPicOrderCntType = 0;
+ if (!sBitReader.ReadUe (&uiTmp) || !sBitReader.ReadUe (&uiPicOrderCntType)) {
+ return false;
+ }
+ if (uiPicOrderCntType == 0) {
+ if (!sBitReader.ReadUe (&uiTmp)) {
+ return false;
+ }
+ } else if (uiPicOrderCntType == 1) {
+ int32_t iTmpSe = 0;
+ uint32_t uiCycleCount = 0;
+ if (!sBitReader.ReadBit (&uiTmp) || !sBitReader.ReadSe (&iTmpSe)
+ || !sBitReader.ReadSe (&iTmpSe) || !sBitReader.ReadUe (&uiCycleCount)) {
+ return false;
+ }
+ for (uint32_t i = 0; i < uiCycleCount; ++i) {
+ if (!sBitReader.ReadSe (&iTmpSe)) {
+ return false;
+ }
+ }
+ }
+
+ uint32_t uiNumRefFrames = 0;
+ if (!sBitReader.ReadUe (&uiNumRefFrames) || !sBitReader.ReadBit (&uiTmp)) {
+ return false;
+ }
+
+ uint32_t uiPicWidthInMbsMinus1 = 0;
+ uint32_t uiPicHeightInMapUnitsMinus1 = 0;
+ uint32_t uiFrameMbsOnlyFlag = 0;
+ if (!sBitReader.ReadUe (&uiPicWidthInMbsMinus1) || !sBitReader.ReadUe (&uiPicHeightInMapUnitsMinus1)
+ || !sBitReader.ReadBit (&uiFrameMbsOnlyFlag)) {
+ return false;
+ }
+
+ if (uiFrameMbsOnlyFlag == 0 && !sBitReader.ReadBit (&uiTmp)) {
+ return false;
+ }
+ if (!sBitReader.ReadBit (&uiTmp)) {
+ return false;
+ }
+
+ uint32_t uiFrameCroppingFlag = 0;
+ uint32_t uiCropLeft = 0;
+ uint32_t uiCropRight = 0;
+ uint32_t uiCropTop = 0;
+ uint32_t uiCropBottom = 0;
+ if (!sBitReader.ReadBit (&uiFrameCroppingFlag)) {
+ return false;
+ }
+ if (uiFrameCroppingFlag != 0) {
+ if (!sBitReader.ReadUe (&uiCropLeft) || !sBitReader.ReadUe (&uiCropRight)
+ || !sBitReader.ReadUe (&uiCropTop) || !sBitReader.ReadUe (&uiCropBottom)) {
+ return false;
+ }
+ }
+
+ const int32_t iFrameMbsFactor = 2 - static_cast<int32_t> (uiFrameMbsOnlyFlag);
+ int32_t iCropUnitX = 1;
+ int32_t iCropUnitY = iFrameMbsFactor;
+ if (uiChromaFormatIdc == 1) {
+ iCropUnitX = 2;
+ iCropUnitY = 2 * iFrameMbsFactor;
+ } else if (uiChromaFormatIdc == 2) {
+ iCropUnitX = 2;
+ } else if (uiChromaFormatIdc == 3) {
+ iCropUnitY = iFrameMbsFactor;
+ }
+
+ int32_t iWidth = static_cast<int32_t> ((uiPicWidthInMbsMinus1 + 1) * 16);
+ int32_t iHeight = static_cast<int32_t> (iFrameMbsFactor * (uiPicHeightInMapUnitsMinus1 + 1) * 16);
+ iWidth -= static_cast<int32_t> ((uiCropLeft + uiCropRight) * iCropUnitX);
+ iHeight -= static_cast<int32_t> ((uiCropTop + uiCropBottom) * iCropUnitY);
+ if (iWidth <= 0 || iHeight <= 0) {
+ return false;
+ }
+
+ pSpsInfo->iWidth = iWidth;
+ pSpsInfo->iHeight = iHeight;
+ pSpsInfo->iNumRefFrames = static_cast<int32_t> (uiNumRefFrames);
+ return true;
+}
+
+static bool ReadSpsInfoFromBitstreamForRefQueueSwitch (const char* pFileName, SpsInfoForRefQueueSwitch* pSpsInfo) {
+ if (pFileName == NULL || pSpsInfo == NULL) {
+ return false;
+ }
+
+ std::ifstream file (pFileName, std::ios::in | std::ios::binary);
+ if (!file.is_open()) {
+ return false;
+ }
+
+ std::vector<uint8_t> bitstream ((std::istreambuf_iterator<char> (file)), std::istreambuf_iterator<char> ());
+ if (bitstream.empty()) {
+ return false;
+ }
+
+ std::vector<uint8_t> spsRbsp;
+ if (!ExtractFirstSpsRbspForRefQueueSwitch (bitstream, &spsRbsp)) {
+ return false;
+ }
+
+ return ParseSpsForRefQueueSwitch (spsRbsp, pSpsInfo);
+}
+
class ThreadDecoderHangRegressionTest : public ::testing::Test {
};
@@ -220,6 +543,43 @@ class ThreadDecoderInitTest : public ::testing::Test, public BaseThreadDecoderTe
};
TEST_F (ThreadDecoderInitTest, JustInit) {}
+
+// Regression test for SPARK-801586: repeated threaded sequence changes must
+// not trigger use-after-free in DPB reallocation paths.
+TEST_F (ThreadDecoderInitTest, ThreadedResolutionSwitchNoUseAfterFree) {
+#if defined(ANDROID_NDK)
+ const std::string kPrefix ("/sdcard/");
+#else
+ const std::string kPrefix ("");
+#endif
+ ASSERT_TRUE (ThreadDecodeResolutionSwitch (
+ (kPrefix + "res/VID_1920x1080_cabac_temporal_direct.264").c_str(),
+ (kPrefix + "res/QCIF_2P_I_allIPCM.264").c_str(), 100, NULL));
+}
+
+// Regression guard for the same-resolution DPB queue resize path. The selected
+// streams are both 176x144 but have different SPS num_ref_frames values.
+TEST_F (ThreadDecoderInitTest, ThreadedSameResolutionRefQueueResizeNoUseAfterFree) {
+#if defined(ANDROID_NDK)
+ const std::string kPrefix ("/sdcard/");
+#else
+ const std::string kPrefix ("");
+#endif
+
+ const std::string kLowRefStream = kPrefix + "res/BA1_Sony_D.jsv";
+ const std::string kHighRefStream = kPrefix + "res/BA_MW_D.264";
+
+ SpsInfoForRefQueueSwitch sLowRef = {0, 0, 0};
+ SpsInfoForRefQueueSwitch sHighRef = {0, 0, 0};
+ ASSERT_TRUE (ReadSpsInfoFromBitstreamForRefQueueSwitch (kLowRefStream.c_str(), &sLowRef));
+ ASSERT_TRUE (ReadSpsInfoFromBitstreamForRefQueueSwitch (kHighRefStream.c_str(), &sHighRef));
+ ASSERT_EQ (sLowRef.iWidth, sHighRef.iWidth);
+ ASSERT_EQ (sLowRef.iHeight, sHighRef.iHeight);
+ ASSERT_NE (sLowRef.iNumRefFrames, sHighRef.iNumRefFrames);
+
+ ASSERT_TRUE (ThreadDecodeResolutionSwitch (kLowRefStream.c_str(), kHighRefStream.c_str(), 300, NULL));
+}
+
struct FileParam {
const char* fileName;
const char* hashStr;