Skip to content

Commit 7134431

Browse files
committed
VPLAY-12682:SoC-independent underflow detection - follow-up improvements
Reason for change:AampUnderflowMonitor Optimization Risks: Medium Signed-off-by: varshnie <varshniblue14@gmail.com>
1 parent 859e69e commit 7134431

File tree

8 files changed

+126
-149
lines changed

8 files changed

+126
-149
lines changed

AampDefine.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,12 @@
154154

155155
#define DEFAULT_UNDERFLOW_DETECT_THRESHOLD_SEC 0.0 /**< Threshold to detect underflow in seconds */
156156
#define DEFAULT_UNDERFLOW_RESUME_THRESHOLD_SEC 1.0 /**< Threshold to resume from underflow in seconds */
157+
157158
#define DEFAULT_UNDERFLOW_LOW_BUFFER_SEC 5.0 /**< Low buffer threshold in seconds */
159+
#define UNDERFLOW_LOW_BUFFER_SEC_FOR_LLD 1.0 /**< Low buffer threshold in seconds for LLD stream */
160+
158161
#define DEFAULT_UNDERFLOW_HIGH_BUFFER_SEC 10.0 /**< High buffer threshold in seconds */
162+
#define UNDERFLOW_HIGH_BUFFER_SEC_FOR_LLD 4.0 /**< High buffer threshold in seconds for LLD stream */
159163

160164
#define DEFAULT_BUFFER_LEVEL_TO_ENABLE_LATENCY_SEC 0.0 /*< Default is 0.0 means latency correction is enabled at all buffer values */
161165
#define DEFAULT_REBUFFER_LATENCY_STEP_SEC 1.0 /*< Step value for latency increase when rebuffering occurs in seconds */

AampUnderflowMonitor.cpp

Lines changed: 60 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -48,60 +48,51 @@ AampUnderflowMonitor::~AampUnderflowMonitor() {
4848
Stop();
4949
}
5050

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

55-
// If a previous thread exists but is not running, join it before starting a new
56-
if (mThread.joinable())
55+
if (mUnderflowMonitorThread.joinable())
5756
{
5857
if (mRunning.load())
5958
{
6059
AAMPLOG_INFO("AampUnderflowMonitor already running; skipping start");
6160
return;
6261
}
63-
lock.unlock();
64-
mThread.join();
62+
mUnderflowMonitorThread.join();
6563
AAMPLOG_INFO("AampUnderflowMonitor previous thread joined before restart");
66-
lock.lock();
6764
}
6865

6966
try
7067
{
68+
{
69+
std::lock_guard<std::mutex> sleepLock(mSleepMutex);
70+
mWakeupSignalled = false;
71+
}
7172
mRunning.store(true);
72-
mThread = std::thread(&AampUnderflowMonitor::Run, this);
73-
AAMPLOG_INFO("AampUnderflowMonitor thread created [%zx]", GetPrintableThreadID(mThread));
73+
mUnderflowMonitorThread = std::thread(&AampUnderflowMonitor::Run, this);
74+
AAMPLOG_INFO("AampUnderflowMonitor thread created [%zx]", GetPrintableThreadID(mUnderflowMonitorThread));
7475
}
7576
catch(const std::exception& e)
7677
{
7778
mRunning.store(false);
7879
AAMPLOG_WARN("Failed to create AampUnderflowMonitor thread : %s", e.what());
79-
return;
80-
}
81-
82-
// If the thread exited immediately (e.g., due to player state), ensure we join it to avoid a dangling joinable thread
83-
if (!mRunning.load() && mThread.joinable())
84-
{
85-
lock.unlock();
86-
mThread.join();
87-
AAMPLOG_WARN("AampUnderflowMonitor thread exited immediately after start; joined");
8880
}
8981
}
9082

9183
void AampUnderflowMonitor::Stop()
9284
{
93-
// Use unique_lock so we can release the mutex before joining
94-
std::unique_lock<std::mutex> lock(mMutex);
95-
// Signal thread to stop
85+
std::lock_guard<std::mutex> lock(mStartStopMutex);
9686
mRunning.store(false);
97-
98-
// If a thread is joinable, release the lock before joining to avoid deadlock
99-
if (mThread.joinable())
10087
{
101-
lock.unlock();
102-
mThread.join();
88+
std::lock_guard<std::mutex> sleepLock(mSleepMutex);
89+
mWakeupSignalled = true;
90+
mSleepCv.notify_all();
91+
}
92+
if (mUnderflowMonitorThread.joinable())
93+
{
94+
mUnderflowMonitorThread.join();
10395
AAMPLOG_INFO("AampUnderflowMonitor thread joined");
104-
lock.lock();
10596
}
10697
}
10798

@@ -116,136 +107,76 @@ void AampUnderflowMonitor::Run()
116107
const int kMediumBufferPollMs = mAamp->mConfig->GetConfigValue(eAAMPConfig_UnderflowMediumBufferPollMs);
117108
const int kHighBufferPollMs = mAamp->mConfig->GetConfigValue(eAAMPConfig_UnderflowHighBufferPollMs);
118109

119-
// Wait until playback enters PLAYING state or underflow becomes active; exit if playback stops
120-
while (mRunning.load()) {
121-
AAMPPlayerState state;
122-
bool shouldBreak = false;
123-
bool underflowStatus = false;
124-
125-
{
126-
std::lock_guard<std::mutex> lock(mMutex);
127-
state = mAamp->GetState();
128-
if (state == eSTATE_STOPPED || state == eSTATE_RELEASED || state == eSTATE_ERROR) {
129-
mRunning.store(false);
130-
return;
131-
}
132-
underflowStatus = mAamp->GetBufUnderFlowStatus();
133-
shouldBreak = (state == eSTATE_PLAYING || underflowStatus);
134-
}
135-
136-
if (shouldBreak) {
137-
break;
138-
}
139-
mAamp->interruptibleMsSleep(100);
140-
}
110+
while (mRunning.load())
111+
{
112+
AAMPPlayerState playerState = mAamp->GetState();
113+
bool underflowActive = mAamp->GetBufUnderFlowStatus();
114+
double bufferedTimeSec = 0.0;
141115

142-
while (mRunning.load()) {
143-
// Check player state and underflow status under mutex
144-
bool underflowActive;
145-
AAMPPlayerState playerState;
146-
float currentRate;
147-
double bufferedTimeSec;
148-
116+
const bool isPlayingOrUnderflow = (playerState == eSTATE_PLAYING) || underflowActive;
117+
if (isPlayingOrUnderflow)
149118
{
150-
std::lock_guard<std::mutex> lock(mMutex);
151-
152-
underflowActive = mAamp->GetBufUnderFlowStatus();
153-
playerState = mAamp->GetState();
154-
// Exit when playback transitions to stopped/released/error/idle
155-
if (playerState == eSTATE_STOPPED || playerState == eSTATE_RELEASED || playerState == eSTATE_ERROR || playerState == eSTATE_IDLE) {
156-
break;
157-
}
158-
159-
// Skip buffer-based underflow checks during trickplay or seeking
160-
currentRate = mAamp->rate;
161-
162-
// Query buffered duration once and reuse for detection and sleep cadence
163119
bufferedTimeSec = mStream->GetBufferedVideoDurationSec();
164120
if (bufferedTimeSec < 0.0) bufferedTimeSec = 0.0;
165-
}
166-
167-
const bool inPlayOrUnderflow = (playerState == eSTATE_PLAYING) || underflowActive;
168-
const bool isTrickplay = (currentRate != AAMP_NORMAL_PLAY_RATE && currentRate != AAMP_SLOWMOTION_RATE && currentRate != AAMP_RATE_PAUSE);
169-
const bool isSeekingState = (playerState == eSTATE_SEEKING);
170-
171-
if (inPlayOrUnderflow) {
172-
// Video underflow detection/resume (query under mutex)
173-
bool trackDownloadsEnabled;
174-
bool sinkCacheEmpty;
175-
176-
{
177-
std::lock_guard<std::mutex> lock(mMutex);
178-
trackDownloadsEnabled = mAamp->TrackDownloadsAreEnabled(eMEDIATYPE_VIDEO);
179-
sinkCacheEmpty = mAamp->IsSinkCacheEmpty(eMEDIATYPE_VIDEO);
180-
}
181121

182-
// Only evaluate buffer threshold when not in trickplay/seeking; still honor sink cache emptiness
122+
const float currentRate = mAamp->rate;
123+
const bool isTrickplay = (currentRate != AAMP_NORMAL_PLAY_RATE && currentRate != AAMP_SLOWMOTION_RATE && currentRate != AAMP_RATE_PAUSE);
124+
const bool isSeekingState = (playerState == eSTATE_SEEKING);
125+
126+
const bool sinkCacheEmpty = mAamp->IsSinkCacheEmpty(eMEDIATYPE_VIDEO);
183127
const bool allowBufferCheck = (!isTrickplay && !isSeekingState);
184-
if (((allowBufferCheck && bufferedTimeSec <= kUnderflowDetectThresholdSec && trackDownloadsEnabled)) || sinkCacheEmpty)
128+
129+
// Detection: only when not in trickplay/seeking
130+
if (allowBufferCheck && (bufferedTimeSec <= kUnderflowDetectThresholdSec || sinkCacheEmpty))
185131
{
186132
if (!underflowActive)
187133
{
188-
AAMPLOG_INFO("[video] underflow detected. buffered=%.3f cacheEmpty=%d (rate=%.2f, trickplay=%d, seeking=%d)", bufferedTimeSec, (int)sinkCacheEmpty, currentRate, (int)isTrickplay, (int)isSeekingState);
189-
190-
std::lock_guard<std::mutex> lock(mMutex);
134+
AAMPLOG_INFO("[video] underflow detected. buffered=%.3f cacheEmpty=%d (rate=%.2f)", bufferedTimeSec, (int)sinkCacheEmpty, currentRate);
191135
mAamp->SetBufferingState(true);
192-
PlaybackErrorType errorType = eGST_ERROR_UNDERFLOW;
193-
mAamp->SendAnomalyEvent(ANOMALY_WARNING, "%s %s", GetMediaTypeName(eMEDIATYPE_VIDEO), mAamp->getStringForPlaybackError(errorType));
194-
}
195-
else
196-
{
197-
if (!trackDownloadsEnabled && sinkCacheEmpty)
198-
{
199-
AAMPLOG_WARN("[video] downloads blocked with empty cache during underflow; resuming");
200-
std::lock_guard<std::mutex> lock(mMutex);
201-
mAamp->ResumeTrackDownloads(eMEDIATYPE_VIDEO);
202-
}
203136
}
204137
}
205-
else
138+
else if (!allowBufferCheck)
206139
{
207-
if (!allowBufferCheck)
208-
{
209-
// Informational: buffer-based underflow checks suppressed during trickplay/seeking
210-
AAMPLOG_TRACE("[video] skipping buffer-based underflow check (rate=%.2f, trickplay=%d, seeking=%d). cacheEmpty=%d buffered=%.3f",
211-
currentRate, (int)isTrickplay, (int)isSeekingState, (int)sinkCacheEmpty, bufferedTimeSec);
212-
}
213-
214-
bool pipelinePaused = false;
215-
{
216-
std::lock_guard<std::mutex> lock(mMutex);
217-
if (!mAamp) return;
218-
pipelinePaused = mAamp->mSinkPaused.load();
219-
}
220-
221-
if (underflowActive && pipelinePaused)
140+
AAMPLOG_TRACE("[video] skipping underflow detection (rate=%.2f, trickplay=%d, seeking=%d). buffered=%.3f",
141+
currentRate, (int)isTrickplay, (int)isSeekingState, bufferedTimeSec);
142+
}
143+
144+
// Resume: always evaluate when underflow is active so it can clear during trickplay/seeking
145+
if (underflowActive)
146+
{
147+
const bool pipelinePaused = mAamp->mSinkPaused.load();
148+
if (pipelinePaused)
222149
{
223150
if (bufferedTimeSec >= kUnderflowResumeThresholdSec && !sinkCacheEmpty)
224151
{
225152
AAMPLOG_INFO("[video] underflow ended. buffered=%.3f cacheEmpty=%d", bufferedTimeSec, (int)sinkCacheEmpty);
226-
std::lock_guard<std::mutex> lock(mMutex);
227153
mAamp->SetBufferingState(false);
228154
}
229155
else
230156
{
231157
AAMPLOG_INFO("[video] waiting to end underflow. buffered=%.3f cacheEmpty=%d", bufferedTimeSec, (int)sinkCacheEmpty);
232158
}
233159
}
234-
else if (underflowActive && !trackDownloadsEnabled && sinkCacheEmpty)
235-
{
236-
AAMPLOG_WARN("[video] underflow ongoing, downloads blocked and cache empty; resuming track downloads");
237-
mAamp->ResumeTrackDownloads(eMEDIATYPE_VIDEO);
238-
}
239160
}
240161
// Audio underflow is not handled currently as we are aligning with the existing behavior
241162
}
242163

243-
// Choose sleep interval based on buffer level (branchless style)
244-
const int sleepMs = (bufferedTimeSec < kLowBufferSec) ? kLowBufferPollMs
245-
: (bufferedTimeSec >= kHighBufferSec) ? kHighBufferPollMs
246-
: kMediumBufferPollMs;
247-
mAamp->interruptibleMsSleep(sleepMs);
164+
const int sleepMs = (bufferedTimeSec < kLowBufferSec) ? kLowBufferPollMs
165+
: (bufferedTimeSec >= kHighBufferSec) ? kHighBufferPollMs
166+
: kMediumBufferPollMs;
167+
WaitMs(sleepMs);
248168
}
249169
mRunning.store(false);
250170
}
251171

172+
void AampUnderflowMonitor::WaitMs(int ms)
173+
{
174+
std::unique_lock<std::mutex> lk(mSleepMutex);
175+
mSleepCv.wait_for(lk,
176+
std::chrono::milliseconds(ms),
177+
[this]() {
178+
return mWakeupSignalled || !mRunning.load();
179+
});
180+
mWakeupSignalled = false;
181+
}
182+

AampUnderflowMonitor.h

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,29 +30,45 @@
3030
#include <atomic>
3131
#include <memory>
3232
#include <mutex>
33+
#include <condition_variable>
34+
#include <chrono>
3335

3436
class StreamAbstractionAAMP;
3537
class PrivateInstanceAAMP;
3638

39+
/**
40+
* @class AampUnderflowMonitor
41+
* @brief Monitors video buffer underflow and drives coordinated pipeline resume.
42+
*
43+
* ## Purpose
44+
* Polls the buffered video duration at a configurable interval and sets/clears
45+
* the buffering state on the AAMP instance when underflow thresholds are crossed.
46+
*
47+
* ## State machine
48+
* @code
49+
* stopped --Start()--> running --Stop()--> stopped
50+
* @endcode
51+
*
52+
* ## Thread safety
53+
* Start() and Stop() are serialised by mStartStopMutex and may safely be
54+
* called from any thread, including concurrently.
55+
* Stop() blocks until the monitor thread has exited.
56+
*/
3757
class AampUnderflowMonitor {
3858
public:
3959
/**
40-
* @brief Construct an `AampUnderflowMonitor`.
41-
* @param[in] stream Stream abstraction used to query buffered video duration
42-
* and playback state relevant to underflow detection.
43-
* @param[in] aamp AAMP instance used for coordination (downloads, buffering
44-
* state) and event emission.
45-
* @note Ownership: The caller retains ownership of both `stream` and `aamp`.
46-
* All pointer accesses are protected by an internal mutex to prevent
47-
* TOCTOU races during shutdown.
48-
* @note Thread Safety: `Stop()` safely terminates the monitoring thread and
49-
* prevents further pointer access. Call `Stop()` before destroying the
50-
* `StreamAbstractionAAMP` or `PrivateInstanceAAMP` instances.
60+
* @brief Constructor.
61+
* @param[in] stream Stream abstraction used to query buffered video duration.
62+
* Must remain valid until Stop() returns.
63+
* (The monitor does not take ownership of the pointer.)
64+
* @param[in] aamp AAMP instance used to query state and control buffering.
65+
* Must remain valid until Stop() returns.
66+
* (The monitor does not take ownership of the pointer.)
5167
*/
5268
AampUnderflowMonitor(StreamAbstractionAAMP* stream, PrivateInstanceAAMP* aamp);
5369

5470
/**
55-
* @brief Destructor. Ensures monitoring has been stopped.
71+
* @brief Destructor. Calls Stop() to ensure the monitor thread exits.
5672
*/
5773
~AampUnderflowMonitor();
5874

@@ -81,11 +97,20 @@ class AampUnderflowMonitor {
8197
*/
8298
void Run();
8399

100+
/**
101+
* @brief Sleep for up to @p ms milliseconds, returning early if Stop() signals.
102+
* @param[in] ms Maximum wait duration in milliseconds.
103+
*/
104+
void WaitMs(int ms);
105+
84106
StreamAbstractionAAMP* mStream; /** Stream abstraction used to query buffered duration and playback state. */
85107
PrivateInstanceAAMP* mAamp; /** AAMP instance used to emit events, control downloads, and query state. */
86-
std::thread mThread; /** Background thread that performs underflow monitoring. */
108+
std::thread mUnderflowMonitorThread; /** Background thread that performs underflow monitoring. */
87109
std::atomic<bool> mRunning{false}; /** Atomic running flag indicating thread active state. */
88-
std::mutex mMutex; /** Protects pointer access in Run() and serializes Start/Stop. */
110+
std::mutex mStartStopMutex; /** Serializes concurrent Start/Stop calls. */
111+
std::mutex mSleepMutex; /** Guards mSleepCv waits inside Run(). */
112+
std::condition_variable mSleepCv; /** Signalled by Stop() to unblock WaitMs() immediately. */
113+
bool mWakeupSignalled{false}; /** Explicit wakeup flag, set under mSleepMutex before notify. */
89114
};
90115

91116
#endif // AAMP_UNDERFLOW_MONITOR_H

priv_aamp.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3246,6 +3246,14 @@ void PrivateInstanceAAMP::SendBufferChangeEvent(bool bufferingStart)
32463246
BufferingChangedEventPtr e = std::make_shared<BufferingChangedEvent>(bufferAvailable, GetSessionId());
32473247

32483248
SetBufUnderFlowStatus(bufferingStart);
3249+
if (bufferingStart)
3250+
{
3251+
SendAnomalyEvent(ANOMALY_WARNING, "Buffering started");
3252+
}
3253+
else
3254+
{
3255+
SendAnomalyEvent(ANOMALY_WARNING, "Buffering stopped");
3256+
}
32493257
AAMPLOG_INFO("PrivateInstanceAAMP: Sending Buffer Change event status (Buffering): %s", (e->buffering() ? "End": "Start"));
32503258
#ifdef AAMP_TELEMETRY_SUPPORT
32513259
AAMPTelemetry2 at2(mAppName);
@@ -14648,6 +14656,8 @@ void PrivateInstanceAAMP::SetLLDashChunkMode(bool enable)
1464814656
if(enable)
1464914657
{
1465014658
mMPDDownloaderInstance->SetNetworkTimeout(MANIFEST_TIMEOUT_FOR_LLD);
14659+
SETCONFIGVALUE_PRIV(AAMP_TUNE_SETTING,eAAMPConfig_UnderflowLowBufferSec,UNDERFLOW_LOW_BUFFER_SEC_FOR_LLD);
14660+
SETCONFIGVALUE_PRIV(AAMP_TUNE_SETTING,eAAMPConfig_UnderflowHighBufferSec,UNDERFLOW_HIGH_BUFFER_SEC_FOR_LLD);
1465114661
SETCONFIGVALUE_PRIV(AAMP_TUNE_SETTING,eAAMPConfig_ManifestTimeout,MANIFEST_TIMEOUT_FOR_LLD);
1465214662
SETCONFIGVALUE_PRIV(AAMP_TUNE_SETTING,eAAMPConfig_MinABRNWBufferRampDown,AAMP_LOW_BUFFER_BEFORE_RAMPDOWN_FOR_LLD);
1465314663
SETCONFIGVALUE_PRIV(AAMP_TUNE_SETTING,eAAMPConfig_MaxABRNWBufferRampUp,AAMP_HIGH_BUFFER_BEFORE_RAMPUP_FOR_LLD);

streamabstraction.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2183,7 +2183,7 @@ StreamAbstractionAAMP::StreamAbstractionAAMP(PrivateInstanceAAMP* aamp, id3_call
21832183

21842184
if(GETCONFIGVALUE(eAAMPConfig_EnableAampUnderflowMonitor))
21852185
{
2186-
if(!mUnderflowMonitor)
2186+
if(!mUnderflowMonitor && (aamp->rate == AAMP_NORMAL_PLAY_RATE))
21872187
{
21882188
try
21892189
{

0 commit comments

Comments
 (0)