Skip to content

Commit b093791

Browse files
committed
VPAAMP-31: Fix VOD CDAI issue with mp4demux
Reason for change: Consturct an encrypted pipeline before ad starts Test Procedure: updated in ticket Risks: Medium Signed-off-by: Reshma-JO07 <sreshmaraphaelk@gmail.com>
1 parent 7a1741f commit b093791

6 files changed

Lines changed: 265 additions & 7 deletions

File tree

StreamAbstractionAAMP.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,8 @@ class MediaTrack
808808
int noMDATCount; /**< MDAT Chunk Not Found count continuously while chunk buffer processing*/
809809
double m_totalDurationForPtsRestamping;
810810
std::shared_ptr<MediaProcessor> playContext; /**< state for s/w demuxer / pts/pcr restamper module */
811+
std::string mContentInitFragmentUrl; /**< URL of most-recent non-ad init fragment; used to
812+
prime encrypted pipeline before a pre-roll ad plays */
811813
bool seamlessAudioSwitchInProgress; /**< Flag to indicate seamless audio track switch in progress */
812814
bool seamlessSubtitleSwitchInProgress;
813815
bool mCheckForRampdown; /**< flag to indicate if the track is undergoing rampdown or not */

fragmentcollector_mpd.cpp

Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,6 @@ static bool IsAtmosAudio(const IMPDElement *nodePtr)
283283
}
284284
}
285285
}
286-
287286
return isAtmos;
288287
}
289288

@@ -634,6 +633,18 @@ bool StreamAbstractionAAMP_MPD::FetchFragment(MediaStreamContext *pMediaStreamCo
634633
AAMPLOG_TRACE("[%" BITSPERSECOND_FORMAT "] : %s,",url.first, url.second.url.c_str());
635634
}
636635

636+
// Cache the content init URL so InjectContentInitBeforeAd() can prime the encrypted
637+
// pipeline before a pre-roll ad init arrives. Only update when not inside an ad break.
638+
if (isInitializationSegment &&
639+
mCdaiObject->mAdState != AdState::IN_ADBREAK_AD_PLAYING &&
640+
!uriList.empty())
641+
{
642+
pMediaStreamContext->mContentInitFragmentUrl = uriList.begin()->second.url;
643+
AAMPLOG_INFO("[CDAI] Cached content init URL for track %d: %s",
644+
pMediaStreamContext->type,
645+
pMediaStreamContext->mContentInitFragmentUrl.c_str());
646+
}
647+
637648
double scaledPts = static_cast<double>(pMediaStreamContext->fragmentDescriptor.Time) / static_cast<double>(pMediaStreamContext->fragmentDescriptor.TimeScale);
638649
DownloadInfoPtr downloadInfo = std::make_shared<DownloadInfo>(
639650
static_cast<AampMediaType>(pMediaStreamContext->type),
@@ -9375,7 +9386,7 @@ bool StreamAbstractionAAMP_MPD::SelectSourceOrAdPeriod(bool &periodChanged, bool
93759386
MediaTrack *audio = GetMediaTrack(eTRACK_AUDIO);
93769387
audio->UpdateInjectedDuration((double)mAudioSurplus);
93779388
mAudioSurplus = 0;
9378-
}else if ( mVideoSurplus != 0 )
9389+
}
93799390
{
93809391
MediaTrack *video = GetMediaTrack(eTRACK_VIDEO);
93819392
video->UpdateInjectedDuration((double)mVideoSurplus);
@@ -9535,6 +9546,87 @@ bool StreamAbstractionAAMP_MPD::IndexSelectedPeriod(bool periodChanged, bool adS
95359546
return true;
95369547
}
95379548

9549+
/**
9550+
* @fn InjectContentInitBeforeAd
9551+
* @brief For a pre-roll ad (clear) following encrypted content, send the cached
9552+
* content init fragment through each track's demuxer before the ad init
9553+
* arrives. This causes SetStreamCaps(encrypted) to fire first so that
9554+
* AampMp4Demuxer's mEncryptedCapsPrimed guard suppresses the subsequent
9555+
* clear caps call, keeping the GStreamer pipeline in encrypted mode for
9556+
* the content that resumes after the ad.
9557+
*/
9558+
void StreamAbstractionAAMP_MPD::InjectContentInitBeforeAd()
9559+
{
9560+
AAMPLOG_MIL("[CDAI] Injecting content init fragments to prime encrypted pipeline "
9561+
"before pre-roll ad (basePeriodId:%s)", mBasePeriodId.c_str());
9562+
9563+
for (int i = 0; i < mNumberOfTracks; i++)
9564+
{
9565+
MediaStreamContext *ctx = mMediaStreamContext[i];
9566+
if (!ctx->enabled)
9567+
{
9568+
AAMPLOG_INFO("[CDAI] Track %d disabled, skipping content init prime", i);
9569+
continue;
9570+
}
9571+
if (ctx->mContentInitFragmentUrl.empty())
9572+
{
9573+
AAMPLOG_WARN("[CDAI] No cached content init URL for track %d (%s) - "
9574+
"encrypted pipeline may not be established correctly",
9575+
i, GetMediaTypeName(ctx->mediaType));
9576+
continue;
9577+
}
9578+
if (!ctx->playContext)
9579+
{
9580+
AAMPLOG_WARN("[CDAI] No playContext for track %d (%s), cannot inject content init",
9581+
i, GetMediaTypeName(ctx->mediaType));
9582+
continue;
9583+
}
9584+
9585+
std::vector<uint8_t> buffer;
9586+
std::string effectiveUrl;
9587+
bool found = aamp->getAampCacheHandler()->RetrieveFromInitFragmentCache(
9588+
ctx->mContentInitFragmentUrl, buffer, effectiveUrl);
9589+
if (!found || buffer.empty())
9590+
{
9591+
AAMPLOG_WARN("[CDAI] Content init not in cache for track %d (%s) url:%s - "
9592+
"skipping pipeline prime",
9593+
i, GetMediaTypeName(ctx->mediaType),
9594+
ctx->mContentInitFragmentUrl.c_str());
9595+
continue;
9596+
}
9597+
9598+
AAMPLOG_INFO("[CDAI] Sending content init to demuxer for track %d (%s) "
9599+
"url:%s size:%zu - will set encrypted caps before ad init arrives",
9600+
i, GetMediaTypeName(ctx->mediaType),
9601+
ctx->mContentInitFragmentUrl.c_str(), buffer.size());
9602+
9603+
MediaProcessor::process_fcn_t processor =
9604+
[](AampMediaType, SegmentInfo_t, std::vector<uint8_t>) {};
9605+
bool ptsErr = false;
9606+
bool sent = ctx->playContext->sendSegment(
9607+
std::move(buffer),
9608+
0.0 /*position*/,
9609+
0.0 /*duration*/,
9610+
0.0 /*PTSoffset*/,
9611+
false /*discontinuity*/,
9612+
true /*isInit*/,
9613+
std::move(processor),
9614+
ptsErr);
9615+
9616+
if (sent)
9617+
{
9618+
AAMPLOG_MIL("[CDAI] Encrypted pipeline primed for track %d (%s)",
9619+
i, GetMediaTypeName(ctx->mediaType));
9620+
}
9621+
else
9622+
{
9623+
AAMPLOG_ERR("[CDAI] sendSegment returned false for content init on track %d (%s) - "
9624+
"encrypted pipeline prime failed",
9625+
i, GetMediaTypeName(ctx->mediaType));
9626+
}
9627+
}
9628+
}
9629+
95389630
/**
95399631
* @fn DetectDiscontinuityAndFetchInit
95409632
*
@@ -9646,6 +9738,17 @@ void StreamAbstractionAAMP_MPD::DetectDiscontinuityAndFetchInit(bool periodChang
96469738
}
96479739
}
96489740
}
9741+
// For a pre-roll ad transitioning to IN_ADBREAK_AD_PLAYING, send the content's
9742+
// encrypted init fragment through the demuxer first. This causes SetStreamCaps()
9743+
// to establish an encrypted GStreamer pipeline; the subsequent clear ad init will
9744+
// then be suppressed by AampMp4Demuxer's mEncryptedCapsPrimed guard, ensuring the
9745+
// pipeline remains encrypted for the content that follows the ad.
9746+
if (mCdaiObject && mCdaiObject->mAdState == AdState::IN_ADBREAK_AD_PLAYING)
9747+
{
9748+
AAMPLOG_MIL("[CDAI] Ad state IN_ADBREAK_AD_PLAYING detected - priming encrypted "
9749+
"pipeline before ad init injection (basePeriodId:%s)", mBasePeriodId.c_str());
9750+
InjectContentInitBeforeAd();
9751+
}
96499752
FetchAndInjectInitFragments(discontinuity);
96509753
}
96519754

fragmentcollector_mpd.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,15 @@ class StreamAbstractionAAMP_MPD : public StreamAbstractionAAMP
816816
* @param trackIdx,discontinuity number of tracks and discontinuity true if discontinuous fragment
817817
*/
818818
void FetchAndInjectInitialization(int trackIdx, bool discontinuity = false);
819+
/**
820+
* @fn RefreshTrack
821+
/**
822+
* @fn InjectContentInitBeforeAd
823+
* @brief Retrieve the cached content-period init fragment for each enabled track and
824+
* send it through the demuxer so that an encrypted GStreamer pipeline is
825+
* established before the pre-roll ad's clear init fragment arrives.
826+
*/
827+
void InjectContentInitBeforeAd();
819828
/**
820829
* @fn RefreshTrack
821830
* @param type media type

mp4demux/AampMp4Demuxer.cpp

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,13 +117,29 @@ bool AampMp4Demuxer::sendSegment(std::vector<uint8_t>&& buffer, double position,
117117
if (codecInfo.mCodecFormat != GST_FORMAT_INVALID &&
118118
codecInfo.mCodecFormat != GST_FORMAT_UNKNOWN)
119119
{
120-
// Invoke SetStreamCaps for proper codec info
121-
AAMPLOG_INFO("Updating codecInfo with format:%d", codecInfo.mCodecFormat);
122-
mAamp->SetStreamCaps(mMediaType, std::move(codecInfo));
120+
// Suppress clear caps if an encrypted init has already been
121+
// processed first (pre-roll ad scenario: content init sent
122+
// before the ad to establish an encrypted GStreamer pipeline).
123+
if (mEncryptedCapsPrimed && !codecInfo.mIsEncrypted)
124+
{
125+
AAMPLOG_INFO("Suppressing clear caps for type:%d - encrypted pipeline already set",
126+
mMediaType);
127+
}
128+
else
129+
{
130+
if (codecInfo.mIsEncrypted)
131+
{
132+
mEncryptedCapsPrimed = true;
133+
}
134+
AAMPLOG_INFO("Updating codecInfo with format:%d encrypted:%d for type:%d",
135+
codecInfo.mCodecFormat, codecInfo.mIsEncrypted, mMediaType);
136+
mAamp->SetStreamCaps(mMediaType, std::move(codecInfo));
137+
}
123138
}
124139
else
125140
{
126-
AAMPLOG_ERR("No samples for type:%d and invalid codec format:%d", mMediaType, codecInfo.mCodecFormat);
141+
AAMPLOG_ERR("No samples for type:%d and invalid codec format:%d",
142+
mMediaType, codecInfo.mCodecFormat);
127143
ret = false;
128144
}
129145
}

mp4demux/AampMp4Demuxer.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ class AampMp4Demuxer : public MediaProcessor
111111
*
112112
* @return void
113113
*/
114-
void reset() override { }
114+
void reset() override { mEncryptedCapsPrimed = false; }
115115

116116
/**
117117
* @brief Function to abort wait for injecting the segment
@@ -143,6 +143,7 @@ class AampMp4Demuxer : public MediaProcessor
143143
bool mEnablePtsRestamp; // Flag to enable PTS restamping
144144
// A separate flag to enable logging for PTS restamping for better control.
145145
bool mEnablePtsRestampLogging {false}; // Flag to enable logging for PTS restamping
146+
bool mEncryptedCapsPrimed {false}; // True once an encrypted init has set caps
146147
};
147148

148149
#endif /* __AAMPMP4DEMUXER_H__ */

test/utests/tests/AampMp4DemuxTests/FunctionalTests.cpp

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,133 @@ TEST_F(AampMp4DemuxerTests, SendInitSegmentWithInvalidCodecInfo)
264264
EXPECT_FALSE(ptsError);
265265
}
266266

267+
/**
268+
* @brief Test that sending an encrypted init via normal sendSegment sets mEncryptedCapsPrimed
269+
* and subsequent clear init does not overwrite the encrypted pipeline caps.
270+
* This covers the pre-roll ad VOD scenario where the content init is fed first
271+
* so GStreamer creates an encrypted pipeline before the clear ad init arrives.
272+
*/
273+
TEST_F(AampMp4DemuxerTests, EncryptedInitFollowedByClearInitSuppressesClearCaps)
274+
{
275+
const char* encInitData = "encrypted_init";
276+
std::vector<uint8_t> encInitBuffer(encInitData, encInitData + strlen(encInitData));
277+
const char* clearInitData = "clear_init";
278+
std::vector<uint8_t> clearInitBuffer(clearInitData, clearInitData + strlen(clearInitData));
279+
280+
// First call: encrypted content init (sent before pre-roll ad starts)
281+
EXPECT_CALL(*g_mockMp4Demux, Parse(_))
282+
.WillOnce(Return(true)) // encrypted init
283+
.WillOnce(Return(true)); // clear ad init
284+
EXPECT_CALL(*g_mockMp4Demux, GetSamples())
285+
.WillOnce(Invoke([]() { return std::vector<AampMediaSample>(); }))
286+
.WillOnce(Invoke([]() { return std::vector<AampMediaSample>(); }));
287+
EXPECT_CALL(*g_mockMp4Demux, GetCodecInfo())
288+
.WillOnce(Invoke([]() {
289+
MediaCodecInfo codecInfo;
290+
codecInfo.mCodecFormat = GST_FORMAT_VIDEO_ES_H264;
291+
codecInfo.mIsEncrypted = true;
292+
return codecInfo;
293+
}))
294+
.WillOnce(Invoke([]() {
295+
MediaCodecInfo codecInfo;
296+
codecInfo.mCodecFormat = GST_FORMAT_VIDEO_ES_H264;
297+
codecInfo.mIsEncrypted = false;
298+
return codecInfo;
299+
}));
300+
// SetStreamCaps must be called exactly once (for the encrypted init only)
301+
EXPECT_CALL(*g_mockPrivateInstanceAAMP,
302+
SetStreamCaps(eMEDIATYPE_VIDEO, _)).Times(1);
303+
EXPECT_CALL(*g_mockPrivateInstanceAAMP, SendStreamTransfer(_, _)).Times(0);
304+
305+
bool ptsError = false;
306+
// Content init arrives first via CacheFragment path
307+
EXPECT_TRUE(mDemuxer->sendSegment(std::move(encInitBuffer), 0.0, 0.0, 0.0,
308+
false, true, nullptr, ptsError));
309+
EXPECT_FALSE(ptsError);
310+
311+
// Ad init arrives — clear caps must be suppressed
312+
EXPECT_TRUE(mDemuxer->sendSegment(std::move(clearInitBuffer), 0.0, 0.0, 0.0,
313+
false, true, nullptr, ptsError));
314+
EXPECT_FALSE(ptsError);
315+
}
316+
317+
/**
318+
* @brief Test that reset() clears the encrypted caps primed flag so a subsequent
319+
* init can update the pipeline normally (content restart after ad).
320+
*/
321+
TEST_F(AampMp4DemuxerTests, ResetClearsEncryptedCapsPrimedFlag)
322+
{
323+
const char* encInitData = "encrypted_init";
324+
std::vector<uint8_t> encInitBuffer(encInitData, encInitData + strlen(encInitData));
325+
const char* contentInitData = "content_init_restart";
326+
std::vector<uint8_t> contentRestartBuffer(
327+
contentInitData, contentInitData + strlen(contentInitData));
328+
329+
EXPECT_CALL(*g_mockMp4Demux, Parse(_))
330+
.WillOnce(Return(true)) // encrypted init
331+
.WillOnce(Return(true)); // content init after reset
332+
EXPECT_CALL(*g_mockMp4Demux, GetSamples())
333+
.WillOnce(Invoke([]() { return std::vector<AampMediaSample>(); }))
334+
.WillOnce(Invoke([]() { return std::vector<AampMediaSample>(); }));
335+
EXPECT_CALL(*g_mockMp4Demux, GetCodecInfo())
336+
.WillOnce(Invoke([]() {
337+
MediaCodecInfo codecInfo;
338+
codecInfo.mCodecFormat = GST_FORMAT_VIDEO_ES_H264;
339+
codecInfo.mIsEncrypted = true;
340+
return codecInfo;
341+
}))
342+
.WillOnce(Invoke([]() {
343+
MediaCodecInfo codecInfo;
344+
codecInfo.mCodecFormat = GST_FORMAT_VIDEO_ES_H264;
345+
codecInfo.mIsEncrypted = true;
346+
return codecInfo;
347+
}));
348+
// SetStreamCaps called for both: first priming, then content restart after reset
349+
EXPECT_CALL(*g_mockPrivateInstanceAAMP,
350+
SetStreamCaps(eMEDIATYPE_VIDEO, _)).Times(2);
351+
EXPECT_CALL(*g_mockPrivateInstanceAAMP, SendStreamTransfer(_, _)).Times(0);
352+
353+
bool ptsError = false;
354+
EXPECT_TRUE(mDemuxer->sendSegment(std::move(encInitBuffer), 0.0, 0.0, 0.0,
355+
false, true, nullptr, ptsError));
356+
357+
// reset() is called when content playback is restarted (ad ends)
358+
mDemuxer->reset();
359+
360+
// Content init re-sent by fragment collector — must not be suppressed
361+
EXPECT_TRUE(mDemuxer->sendSegment(std::move(contentRestartBuffer), 0.0, 0.0, 0.0,
362+
false, true, nullptr, ptsError));
363+
EXPECT_FALSE(ptsError);
364+
}
365+
366+
/**
367+
* @brief Test that a clear init sent without prior encrypted init proceeds normally.
368+
*/
369+
TEST_F(AampMp4DemuxerTests, ClearInitWithoutPriorEncryptedInitSetsClearCaps)
370+
{
371+
const char* clearInitData = "clear_init";
372+
std::vector<uint8_t> clearInitBuffer(clearInitData, clearInitData + strlen(clearInitData));
373+
374+
EXPECT_CALL(*g_mockMp4Demux, Parse(_)).WillOnce(Return(true));
375+
EXPECT_CALL(*g_mockMp4Demux, GetSamples())
376+
.WillOnce(Invoke([]() { return std::vector<AampMediaSample>(); }));
377+
EXPECT_CALL(*g_mockMp4Demux, GetCodecInfo())
378+
.WillOnce(Invoke([]() {
379+
MediaCodecInfo codecInfo;
380+
codecInfo.mCodecFormat = GST_FORMAT_VIDEO_ES_H264;
381+
codecInfo.mIsEncrypted = false;
382+
return codecInfo;
383+
}));
384+
EXPECT_CALL(*g_mockPrivateInstanceAAMP,
385+
SetStreamCaps(eMEDIATYPE_VIDEO, _)).Times(1);
386+
EXPECT_CALL(*g_mockPrivateInstanceAAMP, SendStreamTransfer(_, _)).Times(0);
387+
388+
bool ptsError = false;
389+
EXPECT_TRUE(mDemuxer->sendSegment(std::move(clearInitBuffer), 0.0, 0.0, 0.0,
390+
false, true, nullptr, ptsError));
391+
EXPECT_FALSE(ptsError);
392+
}
393+
267394
/**
268395
* @brief Test sendSegment with parse failure
269396
*/

0 commit comments

Comments
 (0)