Skip to content

Commit ad58342

Browse files
Cherry pick PR #8185: media: Implement flow control for MediaDecoder (#9585)
Refer to the original PR: youtube/cobalt#8185 Integrates `DecoderStateTracker` into the Android `MediaDecoder` to throttle input processing when the decoder has too many pending frames. This helps manage memory usage and prevents the decoder from falling too far behind. Key changes: * Instantiates `DecoderStateTracker` in `MediaDecoder` if enabled. * Blocks input processing in `DecoderThreadFunc` when `CanAcceptMore()` returns false. * Updates frame tracking via `SetFrameAdded` and `SetFrameDecoded`. Bug: 455938352 --------- Co-authored-by: Kyujung Youn <kjyoun@google.com>
1 parent 8c534a5 commit ad58342

10 files changed

Lines changed: 740 additions & 35 deletions

starboard/android/shared/media_decoder.cc

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -122,15 +122,16 @@ MediaCodecDecoder::CreateForVideo(
122122
int tunnel_mode_audio_session_id,
123123
bool force_big_endian_hdr_metadata,
124124
int max_video_input_size,
125-
int64_t flush_delay_usec) {
125+
int64_t flush_delay_usec,
126+
std::optional<int> initial_max_frames) {
126127
std::string error_message;
127128
auto decoder = std::make_unique<MediaCodecDecoder>(
128129
PassKey<MediaCodecDecoder>(), job_queue, host, video_codec,
129130
frame_size_hint, max_frame_size, fps, j_output_surface, drm_system,
130131
color_metadata, require_software_codec, frame_rendered_cb,
131132
first_tunnel_frame_ready_cb, tunnel_mode_audio_session_id,
132133
force_big_endian_hdr_metadata, max_video_input_size, flush_delay_usec,
133-
&error_message);
134+
initial_max_frames, &error_message);
134135
if (!decoder->media_codec_bridge_) {
135136
return Failure(error_message);
136137
}
@@ -191,6 +192,7 @@ MediaCodecDecoder::MediaCodecDecoder(
191192
bool force_big_endian_hdr_metadata,
192193
int max_video_input_size,
193194
int64_t flush_delay_usec,
195+
std::optional<int> initial_max_frames,
194196
std::string* error_message)
195197
: JobOwner(job_queue),
196198

@@ -200,7 +202,16 @@ MediaCodecDecoder::MediaCodecDecoder(
200202
frame_rendered_cb_(frame_rendered_cb),
201203
first_tunnel_frame_ready_cb_(first_tunnel_frame_ready_cb),
202204
tunnel_mode_enabled_(tunnel_mode_audio_session_id != -1),
203-
flush_delay_usec_(flush_delay_usec) {
205+
flush_delay_usec_(flush_delay_usec),
206+
decoder_state_tracker_([&]() -> std::unique_ptr<DecoderStateTracker> {
207+
if (!initial_max_frames || tunnel_mode_enabled_) {
208+
return nullptr;
209+
}
210+
return std::make_unique<DecoderStateTracker>(*initial_max_frames);
211+
}()) {
212+
if (initial_max_frames && tunnel_mode_enabled_) {
213+
SB_LOG(INFO) << "DecoderStateTracker is disabled for tunnel mode.";
214+
}
204215
SB_DCHECK(frame_rendered_cb_);
205216
SB_DCHECK(first_tunnel_frame_ready_cb_);
206217

@@ -368,6 +379,14 @@ void MediaCodecDecoder::DecoderThreadFunc() {
368379
std::vector<int> input_buffer_indices;
369380
std::vector<DequeueOutputResult> dequeue_output_results;
370381

382+
auto can_process_input = [this, &pending_inputs, &input_buffer_indices] {
383+
if (decoder_state_tracker_ && !decoder_state_tracker_->CanAcceptMore()) {
384+
return false;
385+
}
386+
return pending_input_to_retry_ ||
387+
(!pending_inputs.empty() && !input_buffer_indices.empty());
388+
};
389+
371390
while (!destroying_.load()) {
372391
// TODO(b/329686979): access to `ending_input_to_retry_` should be
373392
// synchronized.
@@ -408,10 +427,7 @@ void MediaCodecDecoder::DecoderThreadFunc() {
408427
host_->Tick(media_codec_bridge_.get());
409428
}
410429

411-
bool can_process_input =
412-
pending_input_to_retry_ ||
413-
(!pending_inputs.empty() && !input_buffer_indices.empty());
414-
if (can_process_input) {
430+
if (can_process_input()) {
415431
ProcessOneInputBuffer(&pending_inputs, &input_buffer_indices);
416432
}
417433

@@ -421,16 +437,11 @@ void MediaCodecDecoder::DecoderThreadFunc() {
421437
ticked = host_->Tick(media_codec_bridge_.get());
422438
}
423439

424-
can_process_input =
425-
pending_input_to_retry_ ||
426-
(!pending_inputs.empty() && !input_buffer_indices.empty());
427-
if (!ticked && !can_process_input && dequeue_output_results.empty()) {
440+
if (!ticked && !can_process_input() && dequeue_output_results.empty()) {
428441
std::unique_lock lock(mutex_);
429442
CollectPendingData_Locked(&pending_inputs, &input_buffer_indices,
430443
&dequeue_output_results);
431-
can_process_input =
432-
!pending_inputs.empty() && !input_buffer_indices.empty();
433-
if (!can_process_input && dequeue_output_results.empty()) {
444+
if (!can_process_input() && dequeue_output_results.empty()) {
434445
// Wait for a signal or a timeout. We don't use a predicate here
435446
// because the complex conditions are already checked by the
436447
// surrounding loop, which will re-evaluate the state when this wait
@@ -606,6 +617,14 @@ bool MediaCodecDecoder::ProcessOneInputBuffer(
606617
return false;
607618
}
608619

620+
if (decoder_state_tracker_) {
621+
if (pending_input.type == PendingInput::kWriteEndOfStream) {
622+
decoder_state_tracker_->MarkEosReached();
623+
} else {
624+
decoder_state_tracker_->TrackNewFrame(input_buffer->timestamp());
625+
}
626+
}
627+
609628
is_output_restricted_ = false;
610629
return true;
611630
}
@@ -727,6 +746,10 @@ void MediaCodecDecoder::OnMediaCodecOutputBufferAvailable(
727746
return;
728747
}
729748

749+
if (size > 0 && decoder_state_tracker_) {
750+
decoder_state_tracker_->MarkFrameDecoded(presentation_time_us);
751+
}
752+
730753
DequeueOutputResult dequeue_output_result;
731754
dequeue_output_result.status = 0;
732755
dequeue_output_result.index = buffer_index;
@@ -792,6 +815,10 @@ bool MediaCodecDecoder::Flush() {
792815
dequeue_output_results_.clear();
793816
pending_input_to_retry_ = std::nullopt;
794817

818+
if (decoder_state_tracker_) {
819+
decoder_state_tracker_->Reset();
820+
}
821+
795822
// 2.3. Add OutputFormatChanged to get current output format after Flush().
796823
DequeueOutputResult dequeue_output_result = {};
797824
dequeue_output_result.index = -1;

starboard/android/shared/media_decoder.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "starboard/common/thread.h"
3232
#include "starboard/media.h"
3333
#include "starboard/shared/internal_only.h"
34+
#include "starboard/shared/starboard/media/decoder_state_tracker.h"
3435
#include "starboard/shared/starboard/media/media_util.h"
3536
#include "starboard/shared/starboard/player/filter/common.h"
3637
#include "starboard/shared/starboard/player/input_buffer_internal.h"
@@ -97,7 +98,8 @@ class MediaCodecDecoder final : private MediaCodecBridge::Handler,
9798
int tunnel_mode_audio_session_id,
9899
bool force_big_endian_hdr_metadata,
99100
int max_video_input_size,
100-
int64_t flush_delay_usec);
101+
int64_t flush_delay_usec,
102+
std::optional<int> initial_max_frames);
101103

102104
MediaCodecDecoder(PassKey<MediaCodecDecoder>,
103105
JobQueue* job_queue,
@@ -125,6 +127,7 @@ class MediaCodecDecoder final : private MediaCodecBridge::Handler,
125127
bool force_big_endian_hdr_metadata,
126128
int max_video_input_size,
127129
int64_t flush_delay_usec,
130+
std::optional<int> initial_max_frames,
128131
std::string* error_message);
129132
~MediaCodecDecoder();
130133

@@ -140,6 +143,10 @@ class MediaCodecDecoder final : private MediaCodecBridge::Handler,
140143

141144
bool Flush();
142145

146+
DecoderStateTracker* decoder_state_tracker() {
147+
return decoder_state_tracker_.get();
148+
}
149+
143150
private:
144151
// Holding inputs to be processed. They are mostly InputBuffer objects, but
145152
// can also be codec configs or end of streams.
@@ -236,6 +243,8 @@ class MediaCodecDecoder final : private MediaCodecBridge::Handler,
236243
std::vector<int> input_buffer_indices_;
237244
std::vector<DequeueOutputResult> dequeue_output_results_;
238245

246+
const std::unique_ptr<DecoderStateTracker> decoder_state_tracker_;
247+
239248
bool is_output_restricted_ = false;
240249
bool first_call_on_handler_thread_ = true;
241250

starboard/android/shared/player_components_factory.cc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,8 @@ class PlayerComponentsFactory : public PlayerComponents::Factory {
501501
<< "`kResetDelayUsec` is set to > 0, force a delay of "
502502
<< reset_delay_usec << "us during Reset().";
503503

504+
// TODO: b/455938352 - Connect this options to h5vcc settings.
505+
MediaCodecVideoDecoder::FlowControlOptions flow_control_options;
504506
auto result = MediaCodecVideoDecoder::Create(
505507
creation_parameters.job_queue(),
506508
creation_parameters.video_stream_info(),
@@ -510,7 +512,8 @@ class PlayerComponentsFactory : public PlayerComponents::Factory {
510512
tunnel_mode_audio_session_id, force_secure_pipeline_under_tunnel_mode,
511513
force_reset_surface, force_big_endian_hdr_metadata,
512514
max_video_input_size, creation_parameters.surface_view(),
513-
enable_flush_during_seek, reset_delay_usec, flush_delay_usec);
515+
enable_flush_during_seek, reset_delay_usec, flush_delay_usec,
516+
flow_control_options);
514517
if (!result) {
515518
return Failure(result.error());
516519
}

starboard/android/shared/video_decoder.cc

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,8 @@ std::optional<Size> ParseMaxResolution(
162162

163163
class VideoFrameImpl : public VideoFrame {
164164
public:
165-
typedef std::function<void()> VideoFrameReleaseCallback;
165+
typedef std::function<void(int64_t pts_us, int64_t release_at_us)>
166+
VideoFrameReleaseCallback;
166167

167168
VideoFrameImpl(const DequeueOutputResult& dequeue_output_result,
168169
MediaCodecBridge* media_codec_bridge,
@@ -183,7 +184,7 @@ class VideoFrameImpl : public VideoFrame {
183184
media_codec_bridge_->ReleaseOutputBuffer(dequeue_output_result_.index,
184185
false);
185186
if (!is_end_of_stream()) {
186-
release_callback_();
187+
release_callback_(timestamp(), CurrentMonotonicTime());
187188
}
188189
}
189190
}
@@ -194,7 +195,7 @@ class VideoFrameImpl : public VideoFrame {
194195
released_ = true;
195196
media_codec_bridge_->ReleaseOutputBufferAtTimestamp(
196197
dequeue_output_result_.index, release_time_in_nanoseconds);
197-
release_callback_();
198+
release_callback_(timestamp(), release_time_in_nanoseconds / 1'000);
198199
}
199200

200201
private:
@@ -212,7 +213,7 @@ const int kNonInitialPrerollFrameCount = 1;
212213

213214
const int kSeekingPrerollPendingWorkSizeInTunnelMode =
214215
16 + kInitialPrerollFrameCount;
215-
const int kMaxPendingInputsSize = 128;
216+
const int kDefaultMaxPendingInputsSize = 128;
216217

217218
const int kFpsGuesstimateRequiredInputBufferCount = 3;
218219

@@ -344,7 +345,8 @@ MediaCodecVideoDecoder::Create(JobQueue* job_queue,
344345
void* surface_view,
345346
bool enable_flush_during_seek,
346347
int64_t reset_delay_usec,
347-
int64_t flush_delay_usec) {
348+
int64_t flush_delay_usec,
349+
const FlowControlOptions& flow_control_options) {
348350
std::string error_message;
349351
auto video_decoder = std::make_unique<MediaCodecVideoDecoder>(
350352
PassKey<MediaCodecVideoDecoder>(), job_queue, video_stream_info,
@@ -353,7 +355,7 @@ MediaCodecVideoDecoder::Create(JobQueue* job_queue,
353355
force_secure_pipeline_under_tunnel_mode, force_reset_surface,
354356
force_big_endian_hdr_metadata, max_input_size, surface_view,
355357
enable_flush_during_seek, reset_delay_usec, flush_delay_usec,
356-
&error_message);
358+
flow_control_options, &error_message);
357359

358360
if (!error_message.empty()) {
359361
return Failure(error_message);
@@ -387,6 +389,7 @@ MediaCodecVideoDecoder::MediaCodecVideoDecoder(
387389
bool enable_flush_during_seek,
388390
int64_t reset_delay_usec,
389391
int64_t flush_delay_usec,
392+
const FlowControlOptions& flow_control_options,
390393
std::string* error_message)
391394
: JobOwner(job_queue),
392395
video_codec_(video_stream_info.codec),
@@ -395,6 +398,11 @@ MediaCodecVideoDecoder::MediaCodecVideoDecoder(
395398
decode_target_graphics_context_provider_(
396399
decode_target_graphics_context_provider),
397400
max_video_capabilities_(max_video_capabilities),
401+
initial_max_frames_in_decoder_(
402+
flow_control_options.initial_max_frames_in_decoder),
403+
max_pending_inputs_size_(
404+
flow_control_options.max_pending_input_frames.value_or(
405+
kDefaultMaxPendingInputsSize)),
398406
require_software_codec_(IsSoftwareDecodeRequired(max_video_capabilities)),
399407
force_big_endian_hdr_metadata_(force_big_endian_hdr_metadata),
400408
tunnel_mode_audio_session_id_(tunnel_mode_audio_session_id),
@@ -428,7 +436,7 @@ MediaCodecVideoDecoder::MediaCodecVideoDecoder(
428436

429437
if (is_video_frame_tracker_enabled_) {
430438
video_frame_tracker_ =
431-
std::make_unique<VideoFrameTracker>(kMaxPendingInputsSize * 2);
439+
std::make_unique<VideoFrameTracker>(max_pending_inputs_size_ * 2);
432440
}
433441

434442
if (require_software_codec_) {
@@ -448,6 +456,7 @@ MediaCodecVideoDecoder::MediaCodecVideoDecoder(
448456
SB_LOG(INFO) << "Created VideoDecoder for codec "
449457
<< GetMediaVideoCodecName(video_codec_) << ", with output mode "
450458
<< GetPlayerOutputModeName(output_mode_)
459+
<< ", max pending input size " << max_pending_inputs_size_
451460
<< ", max video capabilities \"" << max_video_capabilities_
452461
<< "\", and tunnel mode audio session id "
453462
<< tunnel_mode_audio_session_id_;
@@ -780,7 +789,7 @@ Result<void> MediaCodecVideoDecoder::InitializeCodec(
780789
std::bind(&MediaCodecVideoDecoder::OnFrameRendered, this, _1),
781790
std::bind(&MediaCodecVideoDecoder::OnFirstTunnelFrameReady, this),
782791
tunnel_mode_audio_session_id_, force_big_endian_hdr_metadata_,
783-
max_video_input_size_, flush_delay_usec_);
792+
max_video_input_size_, flush_delay_usec_, initial_max_frames_in_decoder_);
784793
if (result) {
785794
media_decoder_ = std::move(result.value());
786795
if (error_cb_) {
@@ -884,7 +893,7 @@ void MediaCodecVideoDecoder::WriteInputBuffersInternal(
884893
}
885894

886895
media_decoder_->WriteInputBuffers(input_buffers);
887-
if (media_decoder_->GetNumberOfPendingInputs() < kMaxPendingInputsSize) {
896+
if (media_decoder_->GetNumberOfPendingInputs() < max_pending_inputs_size_) {
888897
decoder_status_cb_(kNeedMoreInput, NULL);
889898
} else if (tunnel_mode_audio_session_id_ != -1) {
890899
// In tunnel mode playback when need data is not signaled above, it is
@@ -921,8 +930,8 @@ void MediaCodecVideoDecoder::WriteInputBuffersInternal(
921930
max_timestamp >= video_frame_tracker_->seek_to_time();
922931
}
923932

924-
bool cache_full =
925-
media_decoder_->GetNumberOfPendingInputs() >= kMaxPendingInputsSize;
933+
bool cache_full = media_decoder_->GetNumberOfPendingInputs() >=
934+
max_pending_inputs_size_;
926935
bool prerolled = tunnel_mode_frame_rendered_.load() > 0 ||
927936
enough_buffers_written_to_media_codec || cache_full;
928937

@@ -960,9 +969,10 @@ void MediaCodecVideoDecoder::ProcessOutputBuffer(
960969
}
961970
decoder_status_cb_(
962971
is_end_of_stream ? kBufferFull : kNeedMoreInput,
963-
new VideoFrameImpl(
964-
dequeue_output_result, media_codec_bridge,
965-
std::bind(&MediaCodecVideoDecoder::OnVideoFrameRelease, this)));
972+
new VideoFrameImpl(dequeue_output_result, media_codec_bridge,
973+
[this](int64_t pts_us, int64_t release_at_us) {
974+
OnVideoFrameRelease(pts_us, release_at_us);
975+
}));
966976
}
967977

968978
void MediaCodecVideoDecoder::RefreshOutputFormat(
@@ -1279,7 +1289,7 @@ void MediaCodecVideoDecoder::OnTunnelModeCheckForNeedMoreInput() {
12791289
return;
12801290
}
12811291

1282-
if (media_decoder_->GetNumberOfPendingInputs() < kMaxPendingInputsSize) {
1292+
if (media_decoder_->GetNumberOfPendingInputs() < max_pending_inputs_size_) {
12831293
decoder_status_cb_(kNeedMoreInput, NULL);
12841294
return;
12851295
}
@@ -1289,11 +1299,17 @@ void MediaCodecVideoDecoder::OnTunnelModeCheckForNeedMoreInput() {
12891299
kNeedMoreInputCheckIntervalInTunnelMode);
12901300
}
12911301

1292-
void MediaCodecVideoDecoder::OnVideoFrameRelease() {
1302+
void MediaCodecVideoDecoder::OnVideoFrameRelease(int64_t pts_us,
1303+
int64_t release_at_us) {
12931304
if (output_format_) {
12941305
--buffered_output_frames_;
12951306
SB_DCHECK_GE(buffered_output_frames_, 0);
12961307
}
1308+
1309+
if (media_decoder_ && media_decoder_->decoder_state_tracker()) {
1310+
media_decoder_->decoder_state_tracker()->MarkFrameReleased(pts_us,
1311+
release_at_us);
1312+
}
12971313
}
12981314

12991315
void MediaCodecVideoDecoder::OnSurfaceDestroyed() {

0 commit comments

Comments
 (0)