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
4 changes: 4 additions & 0 deletions AampDefine.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,12 @@

#define DEFAULT_UNDERFLOW_DETECT_THRESHOLD_SEC 0.0 /**< Threshold to detect underflow in seconds */
#define DEFAULT_UNDERFLOW_RESUME_THRESHOLD_SEC 1.0 /**< Threshold to resume from underflow in seconds */

#define DEFAULT_UNDERFLOW_LOW_BUFFER_SEC 5.0 /**< Low buffer threshold in seconds */
#define UNDERFLOW_LOW_BUFFER_SEC_FOR_LLD 1.0 /**< Low buffer threshold in seconds for LLD stream */

#define DEFAULT_UNDERFLOW_HIGH_BUFFER_SEC 10.0 /**< High buffer threshold in seconds */
#define UNDERFLOW_HIGH_BUFFER_SEC_FOR_LLD 4.0 /**< High buffer threshold in seconds for LLD stream */

#define DEFAULT_BUFFER_LEVEL_TO_ENABLE_LATENCY_SEC 0.0 /*< Default is 0.0 means latency correction is enabled at all buffer values */
#define DEFAULT_REBUFFER_LATENCY_STEP_SEC 1.0 /*< Step value for latency increase when rebuffering occurs in seconds */
Expand Down
207 changes: 59 additions & 148 deletions AampUnderflowMonitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,62 +48,52 @@ AampUnderflowMonitor::~AampUnderflowMonitor() {
Stop();
}

void AampUnderflowMonitor::Start() {
// Use unique_lock to allow unlock around join operations
std::unique_lock<std::mutex> lock(mMutex);
void AampUnderflowMonitor::Start()
{
std::lock_guard<std::mutex> lock(mStartStopMutex);

// If a previous thread exists but is not running, join it before starting a new
if (mThread.joinable())
if (mUnderflowMonitorThread.joinable())
{
if (mRunning.load())
{
AAMPLOG_INFO("AampUnderflowMonitor already running; skipping start");
return;
}
lock.unlock();
mThread.join();
mUnderflowMonitorThread.join();
AAMPLOG_INFO("AampUnderflowMonitor previous thread joined before restart");
lock.lock();
}

try
{
{
std::lock_guard<std::mutex> sleepLock(mSleepMutex);
mWakeupSignalled = false;
}
mRunning.store(true);
mThread = std::thread(&AampUnderflowMonitor::Run, this);
AAMPLOG_INFO("AampUnderflowMonitor thread created [%zx]", GetPrintableThreadID(mThread));
mUnderflowMonitorThread = std::thread(&AampUnderflowMonitor::Run, this);
AAMPLOG_INFO("AampUnderflowMonitor thread created [%zx]", GetPrintableThreadID(mUnderflowMonitorThread));
}
catch(const std::exception& e)
{
mRunning.store(false);
AAMPLOG_WARN("Failed to create AampUnderflowMonitor thread : %s", e.what());
return;
}

// If the thread exited immediately (e.g., due to player state), ensure we join it to avoid a dangling joinable thread
if (!mRunning.load() && mThread.joinable())
{
lock.unlock();
mThread.join();
AAMPLOG_WARN("AampUnderflowMonitor thread exited immediately after start; joined");
}
}

void AampUnderflowMonitor::Stop()
{
// Signal thread to stop
std::lock_guard<std::mutex> lock(mStartStopMutex);
mRunning.store(false);

// Wait for thread to terminate
if (mThread.joinable())
{
mThread.join();
std::lock_guard<std::mutex> sleepLock(mSleepMutex);
mWakeupSignalled = true;
mSleepCv.notify_all();
}
if (mUnderflowMonitorThread.joinable())
{
mUnderflowMonitorThread.join();
AAMPLOG_INFO("AampUnderflowMonitor thread joined");
}

// Nullify pointers under mutex to prevent any race with thread cleanup
std::lock_guard<std::mutex> lock(mMutex);
mAamp = nullptr;
mStream = nullptr;
}

void AampUnderflowMonitor::Run()
Expand All @@ -117,155 +107,76 @@ void AampUnderflowMonitor::Run()
const int kMediumBufferPollMs = mAamp->mConfig->GetConfigValue(eAAMPConfig_UnderflowMediumBufferPollMs);
const int kHighBufferPollMs = mAamp->mConfig->GetConfigValue(eAAMPConfig_UnderflowHighBufferPollMs);

// Wait until playback enters PLAYING state or underflow becomes active; exit if playback stops
while (mRunning.load()) {
AAMPPlayerState state;
bool shouldBreak = false;
bool underflowStatus = false;

{
std::lock_guard<std::mutex> lock(mMutex);
if (!mAamp) return; // Stop() was called

state = mAamp->GetState();
if (state == eSTATE_STOPPED || state == eSTATE_RELEASED || state == eSTATE_ERROR) {
mRunning.store(false);
return;
}
underflowStatus = mAamp->GetBufUnderFlowStatus();
shouldBreak = (state == eSTATE_PLAYING || underflowStatus);
}

if (shouldBreak) {
break;
}

{
std::lock_guard<std::mutex> lock(mMutex);
if (!mAamp) return;
mAamp->interruptibleMsSleep(100);
}
}
while (mRunning.load())
{
AAMPPlayerState playerState = mAamp->GetState();
bool underflowActive = mAamp->GetBufUnderFlowStatus();
double bufferedTimeSec = 0.0;

while (mRunning.load()) {
// Check player state and underflow status under mutex
bool underflowActive;
AAMPPlayerState playerState;
float currentRate;
double bufferedTimeSec;

const bool isPlayingOrUnderflow = (playerState == eSTATE_PLAYING) || underflowActive;
if (isPlayingOrUnderflow)
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mAamp || !mStream) return; // Stop() was called

underflowActive = mAamp->GetBufUnderFlowStatus();
playerState = mAamp->GetState();
// Exit when playback transitions to stopped/released/error/idle
if (playerState == eSTATE_STOPPED || playerState == eSTATE_RELEASED || playerState == eSTATE_ERROR || playerState == eSTATE_IDLE) {
break;
}

// Skip buffer-based underflow checks during trickplay or seeking
currentRate = mAamp->rate;

// Query buffered duration once and reuse for detection and sleep cadence
bufferedTimeSec = mStream->GetBufferedVideoDurationSec();
if (bufferedTimeSec < 0.0) bufferedTimeSec = 0.0;
}

const bool inPlayOrUnderflow = (playerState == eSTATE_PLAYING) || underflowActive;
const bool isTrickplay = (currentRate != AAMP_NORMAL_PLAY_RATE && currentRate != AAMP_SLOWMOTION_RATE && currentRate != AAMP_RATE_PAUSE);
const bool isSeekingState = (playerState == eSTATE_SEEKING);

if (inPlayOrUnderflow) {
// Video underflow detection/resume (query under mutex)
bool trackDownloadsEnabled;
bool sinkCacheEmpty;

{
std::lock_guard<std::mutex> lock(mMutex);
if (!mAamp) return;
trackDownloadsEnabled = mAamp->TrackDownloadsAreEnabled(eMEDIATYPE_VIDEO);
sinkCacheEmpty = mAamp->IsSinkCacheEmpty(eMEDIATYPE_VIDEO);
}
const float currentRate = mAamp->rate;
const bool isTrickplay = (currentRate != AAMP_NORMAL_PLAY_RATE && currentRate != AAMP_SLOWMOTION_RATE && currentRate != AAMP_RATE_PAUSE);
const bool isSeekingState = (playerState == eSTATE_SEEKING);

// Only evaluate buffer threshold when not in trickplay/seeking; still honor sink cache emptiness
const bool sinkCacheEmpty = mAamp->IsSinkCacheEmpty(eMEDIATYPE_VIDEO);
const bool allowBufferCheck = (!isTrickplay && !isSeekingState);
if (((allowBufferCheck && bufferedTimeSec <= kUnderflowDetectThresholdSec && trackDownloadsEnabled)) || sinkCacheEmpty)

// Detection: only when not in trickplay/seeking
if (allowBufferCheck && (bufferedTimeSec <= kUnderflowDetectThresholdSec || sinkCacheEmpty))
{
if (!underflowActive)
{
AAMPLOG_INFO("[video] underflow detected. buffered=%.3f cacheEmpty=%d (rate=%.2f, trickplay=%d, seeking=%d)", bufferedTimeSec, (int)sinkCacheEmpty, currentRate, (int)isTrickplay, (int)isSeekingState);

std::lock_guard<std::mutex> lock(mMutex);
if (!mAamp) return;
AAMPLOG_INFO("[video] underflow detected. buffered=%.3f cacheEmpty=%d (rate=%.2f)", bufferedTimeSec, (int)sinkCacheEmpty, currentRate);
mAamp->SetBufferingState(true);
PlaybackErrorType errorType = eGST_ERROR_UNDERFLOW;
mAamp->SendAnomalyEvent(ANOMALY_WARNING, "%s %s", GetMediaTypeName(eMEDIATYPE_VIDEO), mAamp->getStringForPlaybackError(errorType));
}
else
{
if (!trackDownloadsEnabled && sinkCacheEmpty)
{
AAMPLOG_WARN("[video] downloads blocked with empty cache during underflow; resuming");
std::lock_guard<std::mutex> lock(mMutex);
if (!mAamp) return;
mAamp->ResumeTrackDownloads(eMEDIATYPE_VIDEO);
}
}
}
else
else if (!allowBufferCheck)
{
if (!allowBufferCheck)
{
// Informational: buffer-based underflow checks suppressed during trickplay/seeking
AAMPLOG_TRACE("[video] skipping buffer-based underflow check (rate=%.2f, trickplay=%d, seeking=%d). cacheEmpty=%d buffered=%.3f",
currentRate, (int)isTrickplay, (int)isSeekingState, (int)sinkCacheEmpty, bufferedTimeSec);
}

bool pipelinePaused = false;
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mAamp) return;
pipelinePaused = mAamp->mSinkPaused.load();
}

if (underflowActive && pipelinePaused)
AAMPLOG_TRACE("[video] skipping underflow detection (rate=%.2f, trickplay=%d, seeking=%d). buffered=%.3f",
currentRate, (int)isTrickplay, (int)isSeekingState, bufferedTimeSec);
}

// Resume: always evaluate when underflow is active so it can clear during trickplay/seeking
if (underflowActive)
{
const bool pipelinePaused = mAamp->mSinkPaused.load();
if (pipelinePaused)
{
if (bufferedTimeSec >= kUnderflowResumeThresholdSec && !sinkCacheEmpty)
{
AAMPLOG_INFO("[video] underflow ended. buffered=%.3f cacheEmpty=%d", bufferedTimeSec, (int)sinkCacheEmpty);
std::lock_guard<std::mutex> lock(mMutex);
if (!mAamp) return;
mAamp->SetBufferingState(false);
}
else
{
AAMPLOG_INFO("[video] waiting to end underflow. buffered=%.3f cacheEmpty=%d", bufferedTimeSec, (int)sinkCacheEmpty);
}
}
else if (underflowActive && !trackDownloadsEnabled && sinkCacheEmpty)
{
AAMPLOG_WARN("[video] underflow ongoing, downloads blocked and cache empty; resuming track downloads");
std::lock_guard<std::mutex> lock(mMutex);
if (!mAamp) return;
mAamp->ResumeTrackDownloads(eMEDIATYPE_VIDEO);
}
}
// Audio underflow is not handled currently as we are aligning with the existing behavior
}

// Choose sleep interval based on buffer level (branchless style)
const int sleepMs = (bufferedTimeSec < kLowBufferSec) ? kLowBufferPollMs
: (bufferedTimeSec >= kHighBufferSec) ? kHighBufferPollMs
: kMediumBufferPollMs;

{
std::lock_guard<std::mutex> lock(mMutex);
if (!mAamp) return;
mAamp->interruptibleMsSleep(sleepMs);
}
const int sleepMs = (bufferedTimeSec < kLowBufferSec) ? kLowBufferPollMs
: (bufferedTimeSec >= kHighBufferSec) ? kHighBufferPollMs
: kMediumBufferPollMs;
WaitMs(sleepMs);
}
mRunning.store(false);
}

void AampUnderflowMonitor::WaitMs(int ms)
{
std::unique_lock<std::mutex> lk(mSleepMutex);
mSleepCv.wait_for(lk,
std::chrono::milliseconds(ms),
[this]() {
return mWakeupSignalled || !mRunning.load();
});
mWakeupSignalled = false;
}

Loading
Loading