Skip to content
Draft
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
3 changes: 2 additions & 1 deletion MediaStreamContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1186,7 +1186,8 @@ bool MediaStreamContext::DownloadFragment(DownloadInfoPtr dlInfo)
// Wait for a free cache slot before starting the download.
// IsFragmentCacheFull() checks the unified fragment chunk cache usage, so
// this wait throttles downloads until shared cache capacity is available.
if (IsFragmentCacheFull())
// Skip the wait when playing from local TSB
if (IsFragmentCacheFull() && !aamp->IsLocalAAMPTsbInjection())
{
while (DownloadsEnabled() && !WaitForFreeFragmentAvailable(MAX_WAIT_TIMEOUT_MS))
{
Expand Down
5 changes: 0 additions & 5 deletions StreamAbstractionAAMP.h
Original file line number Diff line number Diff line change
Expand Up @@ -722,9 +722,6 @@ class MediaTrack
double GetLastInjectedFragmentPosition() { return lastInjectedPosition; }

private:
bool gotLocalTime;
bool ptsRollover;
long long currentLocalTimeMs;

/**
* @fn GetBufferHealthStatusString
Expand All @@ -742,8 +739,6 @@ class MediaTrack
*/
void TrickModePtsRestamp(CachedFragment* cachedFragment);

std::string RestampSubtitle( const char* buffer, size_t bufferLen, double position, double duration, double pts_offset );

/**
* @fn TrickModePtsRestamp
*
Expand Down
31 changes: 26 additions & 5 deletions fragmentcollector_hls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1807,12 +1807,21 @@ void TrackState::InjectFragmentInternal(CachedFragment* cachedFragment, bool &fr
else
{
fragmentDiscarded = false;
aamp->SendStreamCopy(
// For HLS-TS audio and video, the PTS/DTS have already been re-stamped
// (if restamping enabled) so PTSOffsetSec is 0, initFragment is
// always false and the discontinuity is false if PTS restamping is
// enabled.
// For HLS webvtt subtitles, the PTSOffset is set if PTS restamping
// is enabled and is passed downstream to the subtitles renderer
aamp->SendStreamTransfer(
(AampMediaType)type,
cachedFragment->fragment,
cachedFragment->position,
cachedFragment->position,
cachedFragment->duration);
cachedFragment->duration,
cachedFragment->PTSOffsetSec,
cachedFragment->initFragment,
cachedFragment->discontinuity);
}
} // InjectFragmentInternal

Expand Down Expand Up @@ -5486,9 +5495,21 @@ void StreamAbstractionAAMP_HLS::NotifyFirstVideoPTS(unsigned long long pts, unsi
StreamSink *sink = AampStreamSinkManager::GetInstance().GetStreamSink(aamp);
if (sink)
{
// The pts_offset is expected to be in seconds for RialtoSink, so we convert it to GstClockTime (nanoseconds).
// For non-Rialto sinks, we need to convert the pts_offset to milliseconds to maintain consistency.
sink->SetSubtitlePtsOffset(mFirstPTS.inSeconds());
uint64_t ptsOffsetSecs = mFirstPTS.inSeconds();
if (ISCONFIGSET(eAAMPConfig_HlsTsEnablePTSReStamp))
{
// When PTS restamping is active the subtitle MPEGTS values are set to
// m_total × 90000 (session-relative, starting at 0). The Rialto subtitle
// renderer subtracts the pts-offset from media_PTS to produce its display
// time, so the offset must also be in session-relative space. At session
// start m_total = 0 always (m_totalDurationForPtsRestamping is initialized
// 0.0 in the MediaTrack base constructor), so pass 0 unconditionally.
ptsOffsetSecs = 0U;
}
// ptsOffsetSecs is in seconds; SetSubtitlePtsOffset handles the
// unit conversion (GstClockTime/nanoseconds for RialtoSink,
// milliseconds for other sinks).
sink->SetSubtitlePtsOffset(ptsOffsetSecs);
}
}

Expand Down
1 change: 1 addition & 0 deletions isobmff/isobmffhelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ bool IsoBmffHelper::ConvertToKeyFrame(std::vector<uint8_t> &buffer)

isoBmffBuffer.truncate();
buffer.resize(isoBmffBuffer.getSize());
buffer.shrink_to_fit(); // GCC (libstdc++), Clang (libc++), and MSVC (STL) all reallocate to fit.
return true;
}

Expand Down
12 changes: 11 additions & 1 deletion middleware/subtec/subtecparser/WebVttSubtecParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

#include "WebVttSubtecParser.hpp"
#include "TextStyleAttributes.h"
#include <cmath>

WebVTTSubtecParser::WebVTTSubtecParser(SubtitleMimeType type, int width, int height) : SubtitleParser(type, width, height), m_channel(nullptr)
{
Expand Down Expand Up @@ -70,11 +71,20 @@ bool WebVTTSubtecParser::processData(const char* buffer, size_t bufferLen, doubl
std::string str(const_cast<const char*>(buffer), bufferLen);
std::vector<uint8_t> data(str.begin(), str.end());

m_channel->SendDataPacket(std::move(data), 0);
m_channel->SendDataPacket(std::move(data), time_offset_ms_);

return true;
}

void WebVTTSubtecParser::setPtsOffset(double ptsOffsetSec)
{
// Subtec's display_time = media_PTS - time_offset_ms (subtraction
// convention shared with Rialto SetSubtitlePtsOffset). The HLS
// restamped video PTS is media_PTS + ptsOffsetSec, so we negate
// here to make subtec add the offset on the subtitle path.
time_offset_ms_ = -static_cast<std::int64_t>(std::llround(ptsOffsetSec * 1000.0));
}

void WebVTTSubtecParser::mute(bool mute)
{
if (mute)
Expand Down
3 changes: 2 additions & 1 deletion middleware/subtec/subtecparser/WebVttSubtecParser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ class WebVTTSubtecParser : public SubtitleParser
void pause(bool pause) override;
void mute(bool mute) override;
void setTextStyle(const std::string &options) override;
void setPtsOffset(double ptsOffsetSec) override;
protected:
std::unique_ptr<SubtecChannel> m_channel;
private:
std::uint64_t time_offset_ms_ = 0;
std::int64_t time_offset_ms_ = 0;
std::uint64_t start_ms_ = 0;
};
9 changes: 9 additions & 0 deletions middleware/subtitle/subtitleParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,15 @@ class SubtitleParser
virtual void mute(bool mute) {}
virtual void isLinear(bool isLinear) {}
virtual void setTextStyle(const std::string &options){}
/**
* @brief Set a per-fragment PTS offset (seconds) that the parser
* applies when forwarding cue data to its subtitle sink.
*
* Used by the HLS PTS-restamp path to align subtitle display time
* with the restamped video PTS without rewriting MPEGTS in the
* VTT header. Default no-op; subtec-based parsers override.
*/
virtual void setPtsOffset(double ptsOffsetSec) {}
void RegisterCallback(const PlayerCallbacks& playerCallBack)
{
playerResumeTrackDownloads_CB = playerCallBack.resumeTrackDownloads_CB;
Expand Down
6 changes: 2 additions & 4 deletions mp4demux/AampMp4Demuxer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,14 @@ bool AampMp4Demuxer::sendSegment(std::vector<uint8_t>&& buffer, double position,
{
for (auto&& sample : samples)
{
// Apply PTS offset if restamping is enabled. This modifies the sample timestamps before sending them to AAMP, which will use the adjusted values for playback timing.
if (mEnablePtsRestamp)
{
double beforeDTS = sample.mDts;
const double beforeDTS = sample.mDts;
sample.mPts += fragmentPTSoffset;
sample.mDts += fragmentPTSoffset;
// Log the restamping if enabled. This can be helpful for debugging and verifying correct behavior, but may cause log flooding for large segments.
if (mEnablePtsRestampLogging)
{
uint32_t timeScale = mMp4Demux->GetTimeScale();
const uint32_t timeScale = mMp4Demux->GetTimeScale();
AAMPLOG_INFO("[RestampPts][%s] timeScale %u beforeDTS %.3f afterDTS %.3f duration %.3f",
GetMediaTypeName(mMediaType),
timeScale,
Expand Down
102 changes: 16 additions & 86 deletions streamabstraction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@
#include <inttypes.h>
#include <math.h>
#include <iterator>
#include <charconv>
#include <optional>
#include <string_view>
#include <sys/time.h>
#include <cmath>
#include "AampTSBSessionManager.h"
Expand Down Expand Up @@ -1233,81 +1236,6 @@ static bool isWebVttSegment( const char *buffer, size_t bufferLen )
return bufferLen>=6 && memcmp(buffer,"WEBVTT",6)==0;
}

std::string MediaTrack::RestampSubtitle( const char* buffer, size_t bufferLen, double position, double duration, double pts_offset_s )
{
long long pts_offset_ms = pts_offset_s*1000;
std::string str;
if( ISCONFIGSET(eAAMPConfig_HlsTsEnablePTSReStamp) && isWebVttSegment(buffer,bufferLen) )
{
const char *fin = &buffer[bufferLen];
const char *prev = buffer;
bool processedHeader = false;
while( prev<fin )
{
const char *line_start = mystrstr( prev, fin, "\n\n" );
if( line_start )
{
if( !processedHeader )
{
const char *localTimePtr = mystrstr(prev,line_start,"LOCAL:");
long long localTimeMs = localTimePtr?convertHHMMSSToTime(localTimePtr+6):0;
const char *mpegtsPtr = mystrstr(prev,line_start,"MPEGTS:");
long long mpegts = mpegtsPtr?atoll(mpegtsPtr+7):0;
pts_offset_ms -= localTimeMs;
if( localTimeMs != currentLocalTimeMs )
{
if( gotLocalTime )
{
AAMPLOG_MIL( "webvtt pts rollover" );
ptsRollover = true;
}
currentLocalTimeMs = localTimeMs;
gotLocalTime = true;
}
line_start += 2; // advance past \n\n
str += "WEBVTT\nX-TIMESTAMP-MAP=LOCAL:00:00:00.000,MPEGTS:";
str += std::to_string(mpegts);
str += "\n\n";
processedHeader = true;
if( ptsRollover )
{ // adjust by max pts ms
pts_offset_ms += 95443717; // 0x1ffffffff/90
}
}
else
{
line_start += 2; // advance past \n\n
str += std::string(prev,line_start-prev);
}
prev = line_start;
const char *line_end = mystrstr(line_start, fin, "\n" );
if( line_end )
{
const char *line_delim = mystrstr( line_start, line_end, " --> " );
if( line_delim )
{ // apply pts offset by rewriting inline begin/end times
prev = line_end;
str += convertTimeToHHMMSS( convertHHMMSSToTime(line_start) + pts_offset_ms );
str += " --> ";
str += convertTimeToHHMMSS( convertHHMMSSToTime(line_delim+5) + pts_offset_ms );
}
}
}
else
{ // trailing
str += std::string(prev,fin-prev);
prev = fin;
}
}
}
else
{
str = std::string(buffer,bufferLen);
}
printf( "***restamped caption: %s\n", str.c_str() );
return str;
}

void MediaTrack::ClearMediaHeaderDuration(CachedFragment *fragment)
{
(void)mIsoBmffHelper->ClearMediaHeaderDuration(fragment->fragment);
Expand Down Expand Up @@ -1386,22 +1314,23 @@ void MediaTrack::ProcessAndInjectFragment(CachedFragment *cachedFragment, bool f
{
if( pContext->mPtsOffsetMap.count(cachedFragment->discontinuityIndex)==0 )
{
AAMPLOG_WARN( "blocking subtitle track injection\n" );
AAMPLOG_WARN( "blocking subtitle track injection waiting for pts_offset[%" PRIu64 "]", cachedFragment->discontinuityIndex );
pContext->aamp->interruptibleMsSleep(1000);
}
else
{
auto firstElement = *pContext->mPtsOffsetMap.begin();
cachedFragment->PTSOffsetSec = pContext->mPtsOffsetMap[cachedFragment->discontinuityIndex] - firstElement.second;
std::string str = RestampSubtitle(
ptr,len,
cachedFragment->position,
cachedFragment->duration,
cachedFragment->PTSOffsetSec );
cachedFragment->fragment.assign(str.begin(), str.end());
// Video and subtitle segments from the same discontinuity share the same
// firstPts (same CDN stream). Apply ptsOffset[N] directly — no normalisation.
cachedFragment->PTSOffsetSec = pContext->mPtsOffsetMap[cachedFragment->discontinuityIndex];
if(mSubtitleParser)
{
mSubtitleParser->processData(str.data(), str.size(), cachedFragment->position, cachedFragment->duration);
// DASH-style PTS-offset propagation: rather than rewriting MPEGTS in
// the VTT header (RestampSubtitle), push the per-fragment pts offset
// down into the subtec parser and forward the buffer unchanged. The
// subtec channel applies the offset to media_PTS so cue display time
// aligns with the restamped video PTS.
mSubtitleParser->setPtsOffset(cachedFragment->PTSOffsetSec);
mSubtitleParser->processData(ptr, len, cachedFragment->position, cachedFragment->duration);
}
break;
}
Expand Down Expand Up @@ -1959,7 +1888,7 @@ MediaTrack::MediaTrack(TrackType type, PrivateInstanceAAMP* aamp, const char* na
,mIsoBmffHelper(std::make_shared<IsoBmffHelper>())
,mLastFragmentPts(0), mRestampedPts(0), mRestampedDuration(0), mTrickmodeState(TrickmodeState::UNDEF)
,mTrackParamsMutex(), mCheckForRampdown(false), mTimeBasedBufferManager(nullptr)
,gotLocalTime(false),ptsRollover(false),currentLocalTimeMs(0)
,m_totalDurationForPtsRestamping(0.0)
{
const int sldCacheSize = GETCONFIGVALUE(eAAMPConfig_MaxFragmentCached);

Expand Down Expand Up @@ -4766,3 +4695,4 @@ void StreamAbstractionAAMP::ReinitializeInjection(double rate)
SetVideoPlaybackRate(rate);
}
}

2 changes: 1 addition & 1 deletion test/gstTestHarness/tsdemux.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ class TsDemux

void parseOptionalPesHeader( void )
{
TsPart part;
TsPart part{}; // zero-initialise: pts=0.0, dts=0.0 when PTS flag absent
part.start = bytes_written;

int marker_bits = readBits(2);
Expand Down
10 changes: 9 additions & 1 deletion test/utests/fakes/FakeStreamAbstractionAamp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -303,8 +303,12 @@ void MediaTrack::UpdateTSAfterFetchStats(CachedFragment* cachedFragment, bool is
{
}

bool MediaTrack::WaitForFreeFragmentAvailable( int timeoutMs)
bool MediaTrack::WaitForFreeFragmentAvailable(int timeoutMs)
{
if (g_mockMediaTrack != nullptr)
{
return g_mockMediaTrack->WaitForFreeFragmentAvailable(timeoutMs);
}
return true;
}

Expand Down Expand Up @@ -576,6 +580,10 @@ void MediaTrack::NotifyCachedAudioFragmentAvailable()

bool MediaTrack::IsFragmentCacheFull()
{
if (g_mockMediaTrack != nullptr)
{
return g_mockMediaTrack->IsFragmentCacheFull();
}
return false;
}

Expand Down
4 changes: 4 additions & 0 deletions test/utests/fakes/FakeWebVTTSubtecParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,7 @@ void WebVTTSubtecParser::pause(bool pause)
void WebVTTSubtecParser::setTextStyle(const std::string &options)
{
}

void WebVTTSubtecParser::setPtsOffset(double ptsOffsetSec)
{
}
2 changes: 2 additions & 0 deletions test/utests/mocks/MockMediaTrack.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class MockMediaTrack : public MediaTrack
MOCK_METHOD(void, UpdateTSAfterFetch, ());
MOCK_METHOD(void, SetLocalTSBInjection, (bool value));
MOCK_METHOD(bool, IsLocalTSBInjection, ());
MOCK_METHOD(bool, IsFragmentCacheFull, ());
MOCK_METHOD(bool, WaitForFreeFragmentAvailable, (int timeoutMs));
MOCK_METHOD(bool, Enabled, ());
MOCK_METHOD(void, ProcessPlaylist, (std::vector<uint8_t>& newPlaylist, int http_error), (override));
MOCK_METHOD(std::string&, GetPlaylistUrl, (), (override));
Expand Down
1 change: 1 addition & 0 deletions test/utests/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,4 @@ add_subdirectory(AampDRMLicPreFetcherTests)
add_subdirectory(AampDRMLicManagerTests)
add_subdirectory(AampLatencyMonitorTests)
add_subdirectory(NetPersonaFitterTests)
add_subdirectory(TsDemuxerTests)
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ TEST_P(IsoBmffConvertToKeyFrameTestsP, converToIFrame)

EXPECT_TRUE(helper->ConvertToKeyFrame(src_data));
EXPECT_EQ(src_data.size(), td.expected_data_len);
EXPECT_EQ(src_data.capacity(), td.expected_data_len);
auto memcmp_actual_vs_expected = std::memcmp(src_data.data(), td.expected_data, td.expected_data_len);
EXPECT_EQ(0, memcmp_actual_vs_expected);
if (memcmp_actual_vs_expected)
Expand Down
Loading
Loading