From c6a2c810501c995044fe895c4db44078b491d2d3 Mon Sep 17 00:00:00 2001 From: varshnie Date: Mon, 6 Jul 2026 15:49:48 +0530 Subject: [PATCH] VPAAMP-684:Tech Fault error leads to Blue Screen when rewinding from live edge into CDAI ads Reason for change:Init frag failure due to mismatched bandwitdh Risks: p1 Signed-off-by: varshnie --- MediaStreamContext.cpp | 35 +++- .../CacheFragmentTests/CacheFragmentTests.cpp | 168 ++++++++++++++++++ 2 files changed, 197 insertions(+), 6 deletions(-) diff --git a/MediaStreamContext.cpp b/MediaStreamContext.cpp index 476920dcbe..267dcb31fd 100644 --- a/MediaStreamContext.cpp +++ b/MediaStreamContext.cpp @@ -73,8 +73,13 @@ bool MediaStreamContext::CacheFragment(std::string fragmentUrl, unsigned int cur mStagingFragment.Clear(); CachedFragment *cachedFragment = &mStagingFragment; BitsPerSecond bitrate = 0; + BitsPerSecond expectedBandwidth = fragmentDescriptor.Bandwidth; double downloadTimeS = 0; AampMediaType actualType = (AampMediaType)(initSegment ? (eMEDIATYPE_INIT_VIDEO + mediaType) : mediaType); // Need to revisit the logic + if (mActiveDownloadInfo && mActiveDownloadInfo->bandwidth > 0) + { + expectedBandwidth = mActiveDownloadInfo->bandwidth; + } PopulateCommonMetadata(cachedFragment, fragmentUrl, actualType, 0, initSegment, discontinuity); cachedFragment->timeScale = fragmentDescriptor.TimeScale; @@ -109,7 +114,11 @@ bool MediaStreamContext::CacheFragment(std::string fragmentUrl, unsigned int cur bool bReadfromcache = false; if (initSegment) { - ret = bReadfromcache = aamp->getAampCacheHandler()->RetrieveFromInitFragmentCache(fragmentUrl,cachedFragment->fragment,effectiveUrl); + auto *cacheHandler = aamp->getAampCacheHandler(); + if (cacheHandler) + { + ret = bReadfromcache = cacheHandler->RetrieveFromInitFragmentCache(fragmentUrl, cachedFragment->fragment, effectiveUrl); + } } if (!bReadfromcache) { @@ -131,7 +140,11 @@ bool MediaStreamContext::CacheFragment(std::string fragmentUrl, unsigned int cur ret = aamp->GetFile(fragmentUrl, actualType, mTempFragment, effectiveUrl, httpErrorCode, &downloadTimeS, range, curlInstance, true/*resetBuffer*/, &bitrate, &iFogError, fragmentDurationS, bucketType, maxInitDownloadTimeMS); if (initSegment && ret) { - aamp->getAampCacheHandler()->InsertToInitFragCache(fragmentUrl, mTempFragment, effectiveUrl, actualType); + auto *cacheHandler = aamp->getAampCacheHandler(); + if (cacheHandler) + { + cacheHandler->InsertToInitFragCache(fragmentUrl, mTempFragment, effectiveUrl, actualType); + } } if (ret) { @@ -181,10 +194,10 @@ bool MediaStreamContext::CacheFragment(std::string fragmentUrl, unsigned int cur } mCheckForRampdown = false; - if (ret && (bitrate > 0 && bitrate != fragmentDescriptor.Bandwidth)) + if (ret && (bitrate > 0 && bitrate != expectedBandwidth)) { AAMPLOG_INFO("Bitrate changed from %" BITSPERSECOND_FORMAT " to %" BITSPERSECOND_FORMAT "", - fragmentDescriptor.Bandwidth, bitrate); + expectedBandwidth, bitrate); fragmentDescriptor.Bandwidth = (uint32_t)bitrate; context->SetTsbBandwidth(bitrate); context->mUpdateReason = true; @@ -1047,10 +1060,21 @@ bool MediaStreamContext::DownloadFragment(DownloadInfoPtr dlInfo) URIInfo uriInfo; if (dlInfo->uriList.size() > 0) { - // Asses the current bandwidth and get the appropriate URIInfo from the map with resolved URLs + // Assess the current bandwidth and get the appropriate URIInfo from the map with resolved URLs. if (dlInfo->uriList.find(fragmentDescriptor.Bandwidth) != dlInfo->uriList.end()) { uriInfo = dlInfo->uriList[fragmentDescriptor.Bandwidth]; + // Sync dlInfo->bandwidth to the selected bandwidth to avoid mismatch in downstream comparisons + // (e.g. bitrate check in CacheFragment) when a period/ad-state change updated fragmentDescriptor.Bandwidth. + if (dlInfo->bandwidth != fragmentDescriptor.Bandwidth) + { + dlInfo->bandwidth = fragmentDescriptor.Bandwidth; + } + } + // Fallback to job submission bandwidth if descriptor bandwidth is not present in the map. + else if ((dlInfo->bandwidth > 0) && (dlInfo->uriList.find(dlInfo->bandwidth) != dlInfo->uriList.end())) + { + uriInfo = dlInfo->uriList[dlInfo->bandwidth]; } if (uriInfo.url.empty() && dlInfo->uriList.size() > 0) { @@ -1206,6 +1230,5 @@ bool MediaStreamContext::DownloadFragment(DownloadInfoPtr dlInfo) retval = CacheFragment(dlInfo->url, dlInfo->curlInstance, dlInfo->pts, dlInfo->fragmentDurationSec, dlInfo->range.c_str(), dlInfo->isInitSegment, dlInfo->isDiscontinuity, dlInfo->isPlayingAd, dlInfo->timeScale); } } - return retval; } diff --git a/test/utests/tests/CacheFragmentTests/CacheFragmentTests.cpp b/test/utests/tests/CacheFragmentTests/CacheFragmentTests.cpp index 10101af330..9483f3c09c 100644 --- a/test/utests/tests/CacheFragmentTests/CacheFragmentTests.cpp +++ b/test/utests/tests/CacheFragmentTests/CacheFragmentTests.cpp @@ -43,6 +43,7 @@ using ::testing::StrictMock; using ::testing::SetArgReferee; using ::testing::AtLeast; using ::testing::DoAll; +using ::testing::SetArgPointee; AampConfig *gpGlobalConfig{nullptr}; struct TestParams @@ -388,4 +389,171 @@ INSTANTIATE_TEST_SUITE_P( ::testing::ValuesIn(testCases) ); +/** + * @brief Verifies that an init segment download succeeds when fragmentDescriptor.Bandwidth + * has advanced to the ad period's bandwidth while a source-content init segment job + * is still in-flight. + * + * The fix introduces expectedBandwidth, overridden with mActiveDownloadInfo->bandwidth + * (captured at job-submission time). When FOG returns the correct content bitrate, it + * matches expectedBandwidth and no false rampdown fires. + */ +TEST_F(MediaStreamContextTest, CacheFragment_InitSegment_FogBitrateMismatch_ReturnsSuccess) +{ + // --- Arrange --- + static constexpr BitsPerSecond kJobBandwidth = 2000000; // BW at job-submission (source content) + static constexpr BitsPerSecond kAdPeriodBandwidth = 5000000; // ad period's bandwidth — fragmentDescriptor advances to this during the period transition + + // Create the MediaStreamContext (TEST_F does not call Initialize()) + mMediaStreamContext = new MediaStreamContext(eTRACK_VIDEO, mStreamAbstractionAAMP_MPD, mPrivateInstanceAAMP, "SAMPLETEXT"); + + // Simulate the race: period transitioned to the ad, updating fragmentDescriptor.Bandwidth to the ad period's BW + mMediaStreamContext->fragmentDescriptor.Bandwidth = kAdPeriodBandwidth; + + URIInfo uriInfo; + uriInfo.url = "http://example.com/init.mp4"; + URLBitrateMap urlList = { { kJobBandwidth, uriInfo } }; + + // dlInfo->bandwidth = job-submission BW (2Mbps) — this overrides expectedBandwidth + mMediaStreamContext->mActiveDownloadInfo = std::make_shared( + eMEDIATYPE_VIDEO, eCURLINSTANCE_VIDEO, + 10.0, 0.0, "", -1, 0, + /*isInitSegment=*/true, false, false, false, + 0.0, 0, 1, kJobBandwidth, AampTime{}, urlList); + + // FOG serves and reports the correct 2Mbps content bandwidth + EXPECT_CALL(*g_mockPrivateInstanceAAMP, GetFile(_, _, _, _, _, _, _, _, _, _, _, _, _, _)) + .WillOnce(DoAll(SetArgPointee<9>(kJobBandwidth), Return(true))); + EXPECT_CALL(*g_mockPrivateInstanceAAMP, GetTSBSessionManager()) + .WillRepeatedly(Return(nullptr)); + EXPECT_CALL(*g_mockPrivateInstanceAAMP, GetLLDashChunkMode()) + .WillRepeatedly(Return(false)); + + // --- Act --- + bool result = mMediaStreamContext->CacheFragment( + uriInfo.url, 0, 10.0, 0.0, nullptr, /*initSegment=*/true, false, false, 0); + + // --- Assert --- + // Init segment must succeed. + // expectedBandwidth = dlInfo->bandwidth (2Mbps) == bitrate from FOG (2Mbps) → no rampdown. + EXPECT_TRUE(result) << "Init segment must succeed when expectedBandwidth fix prevents false rampdown"; + + // fragmentDescriptor.Bandwidth must not be touched — rampdown never fired. + EXPECT_EQ(mMediaStreamContext->fragmentDescriptor.Bandwidth, static_cast(kAdPeriodBandwidth)) + << "fragmentDescriptor.Bandwidth must not be updated when rampdown does not fire"; + + // No bytes stashed for reschedule (download succeeded). + EXPECT_TRUE(mMediaStreamContext->mDownloadedFragment.empty()) + << "mDownloadedFragment must be empty when init segment succeeds"; +} + +/** + * @brief Verifies that rampdown still fires for media segments when FOG reports a + * genuinely different bitrate — ensuring the expectedBandwidth fix does not + * suppress legitimate ABR rampdown for media fragments. + */ +TEST_F(MediaStreamContextTest, CacheFragment_MediaSegment_FogBitrateMismatch_RampdownTriggered) +{ + // --- Arrange --- + static constexpr BitsPerSecond kJobBandwidth = 2000000; + static constexpr BitsPerSecond kFogReportedBitrate = 5000000; + + // Create the MediaStreamContext (TEST_F does not call Initialize()) + mMediaStreamContext = new MediaStreamContext(eTRACK_VIDEO, mStreamAbstractionAAMP_MPD, mPrivateInstanceAAMP, "SAMPLETEXT"); + + mMediaStreamContext->fragmentDescriptor.Bandwidth = kJobBandwidth; + + URIInfo uriInfo; + uriInfo.url = "http://example.com/segment.mp4"; + URLBitrateMap urlList = { { kJobBandwidth, uriInfo } }; + + mMediaStreamContext->mActiveDownloadInfo = std::make_shared( + eMEDIATYPE_VIDEO, eCURLINSTANCE_VIDEO, + 10.0, 2.0, "", -1, 0, + /*isInitSegment=*/false, false, false, false, + 0.0, 0, 1, kJobBandwidth, AampTime{}, urlList); + + // FOG reports mismatched bitrate for a media segment → rampdown must fire. + static const std::vector kSegmentBytes{0xAA, 0xBB, 0xCC, 0xDD}; + EXPECT_CALL(*g_mockPrivateInstanceAAMP, GetFile(_, _, _, _, _, _, _, _, _, _, _, _, _, _)) + .WillOnce(DoAll(SetArgReferee<2>(kSegmentBytes), SetArgPointee<9>(kFogReportedBitrate), Return(true))); + EXPECT_CALL(*g_mockPrivateInstanceAAMP, GetTSBSessionManager()) + .WillRepeatedly(Return(nullptr)); + EXPECT_CALL(*g_mockPrivateInstanceAAMP, GetLLDashChunkMode()) + .WillRepeatedly(Return(false)); + + // --- Act --- + bool result = mMediaStreamContext->CacheFragment( + uriInfo.url, 0, 10.0, 2.0, nullptr, /*initSegment=*/false, false, false, 0); + + // --- Assert --- + // Rampdown must still trigger for media segments (existing behavior preserved). + EXPECT_FALSE(result) << "Media segment rampdown must trigger on FOG bitrate mismatch"; + + // Descriptor updated to FOG-reported bitrate for the next download attempt. + EXPECT_EQ(mMediaStreamContext->fragmentDescriptor.Bandwidth, static_cast(kFogReportedBitrate)) + << "fragmentDescriptor.Bandwidth must be updated to FOG-reported bitrate on rampdown"; + + // Downloaded bytes saved in mDownloadedFragment for the reschedule/reuse path. + EXPECT_FALSE(mMediaStreamContext->mDownloadedFragment.empty()) + << "mDownloadedFragment must hold the downloaded bytes for reschedule reuse"; +} + +/** + * @brief Verifies that rampdown fires for init segments when FOG reports a genuinely + * different bitrate — confirming the rampdown check applies equally to init + * segments, not only media segments. + * + * Unlike the period-transition race scenario, here fragmentDescriptor.Bandwidth equals + * the job-submission bandwidth. FOG returning a different bitrate is a genuine mismatch + * and rampdown must trigger. + */ +TEST_F(MediaStreamContextTest, CacheFragment_InitSegment_GenuineFogBitrateMismatch_RampdownTriggered) +{ + // --- Arrange --- + static constexpr BitsPerSecond kJobBandwidth = 2000000; + static constexpr BitsPerSecond kFogReportedBitrate = 5000000; + + // Create the MediaStreamContext (TEST_F does not call Initialize()) + mMediaStreamContext = new MediaStreamContext(eTRACK_VIDEO, mStreamAbstractionAAMP_MPD, mPrivateInstanceAAMP, "SAMPLETEXT"); + + // No period transition: descriptor BW matches the job-submission BW + mMediaStreamContext->fragmentDescriptor.Bandwidth = kJobBandwidth; + + URIInfo uriInfo; + uriInfo.url = "http://example.com/init.mp4"; + URLBitrateMap urlList = { { kJobBandwidth, uriInfo } }; + + mMediaStreamContext->mActiveDownloadInfo = std::make_shared( + eMEDIATYPE_VIDEO, eCURLINSTANCE_VIDEO, + 10.0, 0.0, "", -1, 0, + /*isInitSegment=*/true, false, false, false, + 0.0, 0, 1, kJobBandwidth, AampTime{}, urlList); + + // FOG reports a genuinely different bitrate for the init segment → rampdown must fire. + static const std::vector kInitSegmentBytes{0x00, 0x00, 0x00, 0x20}; + EXPECT_CALL(*g_mockPrivateInstanceAAMP, GetFile(_, _, _, _, _, _, _, _, _, _, _, _, _, _)) + .WillOnce(DoAll(SetArgReferee<2>(kInitSegmentBytes), SetArgPointee<9>(kFogReportedBitrate), Return(true))); + EXPECT_CALL(*g_mockPrivateInstanceAAMP, GetTSBSessionManager()) + .WillRepeatedly(Return(nullptr)); + EXPECT_CALL(*g_mockPrivateInstanceAAMP, GetLLDashChunkMode()) + .WillRepeatedly(Return(false)); + + // --- Act --- + bool result = mMediaStreamContext->CacheFragment( + uriInfo.url, 0, 10.0, 0.0, nullptr, /*initSegment=*/true, false, false, 0); + + // --- Assert --- + // Rampdown must trigger for init segments on genuine FOG bitrate mismatch. + EXPECT_FALSE(result) << "Init segment rampdown must trigger on genuine FOG bitrate mismatch"; + + // Descriptor updated to FOG-reported bitrate for the next download attempt. + EXPECT_EQ(mMediaStreamContext->fragmentDescriptor.Bandwidth, static_cast(kFogReportedBitrate)) + << "fragmentDescriptor.Bandwidth must be updated to FOG-reported bitrate on rampdown"; + + // Downloaded bytes saved in mDownloadedFragment for the reschedule/reuse path. + EXPECT_FALSE(mMediaStreamContext->mDownloadedFragment.empty()) + << "mDownloadedFragment must hold the downloaded bytes for reschedule reuse"; +} +