Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 29 additions & 6 deletions MediaStreamContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Comment on lines +76 to +80
expectedBandwidth = mActiveDownloadInfo->bandwidth;
}

PopulateCommonMetadata(cachedFragment, fragmentUrl, actualType, 0, initSegment, discontinuity);
cachedFragment->timeScale = fragmentDescriptor.TimeScale;
Expand Down Expand Up @@ -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)
{
Expand All @@ -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)
{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Comment on lines +1069 to +1072
}
// 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)
{
Expand Down Expand Up @@ -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;
}
168 changes: 168 additions & 0 deletions test/utests/tests/CacheFragmentTests/CacheFragmentTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ using ::testing::StrictMock;
using ::testing::SetArgReferee;
using ::testing::AtLeast;
using ::testing::DoAll;
using ::testing::SetArgPointee;

AampConfig *gpGlobalConfig{nullptr};
struct TestParams
Expand Down Expand Up @@ -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<DownloadInfo>(
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<uint32_t>(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<DownloadInfo>(
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<uint8_t> 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<uint32_t>(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<DownloadInfo>(
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<uint8_t> 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<uint32_t>(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";
}


Loading