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;