From d55e4a8bacd81731a26921a44a8c354ff71433ac Mon Sep 17 00:00:00 2001 From: cobalt-github-releaser-bot <95661244+cobalt-github-releaser-bot@users.noreply.github.com> Date: Fri, 12 Apr 2024 14:29:01 -0700 Subject: [PATCH] Cherry pick PR #2287: [android] Improve video/audio seeking via flush MediaCodec (#2916) Refer to the original PR: https://github.com/youtube/cobalt/pull/2287 1. Currently, Cobalt resets MediaCodec during seek, which is very slow on some atv devices. 2. This eliminates the visible black screen that appears during seeking, caused by resetting MediaCodec, an issue especially common in DRM-protected content and on older devices. 3. Adjusting the process to flush MediaCodec rather than dismantling and re-establishing it during seek operations prevents the platform from mistakenly concluding that playback has ended (b/296530538). 4. This improvement cuts down the InBufferSeek duration by approximately 45%. b/320568573 Co-authored-by: Bo-Rong Chen --- starboard/android/shared/audio_decoder.cc | 19 ++- starboard/android/shared/audio_decoder.h | 4 +- .../shared/audio_renderer_passthrough.cc | 7 +- .../shared/audio_renderer_passthrough.h | 3 +- .../shared/audio_track_audio_sink_type.h | 2 + .../android/shared/media_codec_bridge.cc | 5 + starboard/android/shared/media_codec_bridge.h | 1 + starboard/android/shared/media_decoder.cc | 70 ++++++++++ starboard/android/shared/media_decoder.h | 2 + .../shared/player_components_factory.h | 68 ++++++++-- starboard/android/shared/video_decoder.cc | 26 +++- starboard/android/shared/video_decoder.h | 3 + .../filter/adaptive_audio_decoder_internal.cc | 6 +- .../filter/adaptive_audio_decoder_internal.h | 1 + .../filter/testing/audio_decoder_test.cc | 122 ++++++++++++++++++ 15 files changed, 311 insertions(+), 28 deletions(-) diff --git a/starboard/android/shared/audio_decoder.cc b/starboard/android/shared/audio_decoder.cc index 60e7a44a88e6..5af005957887 100644 --- a/starboard/android/shared/audio_decoder.cc +++ b/starboard/android/shared/audio_decoder.cc @@ -70,12 +70,14 @@ void* IncrementPointerByBytes(void* pointer, int offset) { } // namespace AudioDecoder::AudioDecoder(const AudioStreamInfo& audio_stream_info, - SbDrmSystem drm_system) + SbDrmSystem drm_system, + bool enable_flush_during_seek) : audio_stream_info_(audio_stream_info), sample_type_(GetSupportedSampleType()), output_sample_rate_(audio_stream_info.samples_per_second), output_channel_count_(audio_stream_info.number_of_channels), - drm_system_(static_cast(drm_system)) { + drm_system_(static_cast(drm_system)), + enable_flush_during_seek_(enable_flush_during_seek) { if (!InitializeCodec()) { SB_LOG(ERROR) << "Failed to initialize audio decoder."; } @@ -167,13 +169,16 @@ void AudioDecoder::Reset() { SB_DCHECK(BelongsToCurrentThread()); SB_DCHECK(output_cb_); - media_decoder_.reset(); - audio_frame_discarder_.Reset(); + // If fail to flush |media_decoder_|, then re-create |media_decoder_|. + if (!enable_flush_during_seek_ || !media_decoder_->Flush()) { + media_decoder_.reset(); - if (!InitializeCodec()) { - // TODO: Communicate this failure to our clients somehow. - SB_LOG(ERROR) << "Failed to initialize codec after reset."; + if (!InitializeCodec()) { + // TODO: Communicate this failure to our clients somehow. + SB_LOG(ERROR) << "Failed to initialize codec after reset."; + } } + audio_frame_discarder_.Reset(); consumed_cb_ = nullptr; diff --git a/starboard/android/shared/audio_decoder.h b/starboard/android/shared/audio_decoder.h index a57f0ff22fb2..1ee32d63c5d1 100644 --- a/starboard/android/shared/audio_decoder.h +++ b/starboard/android/shared/audio_decoder.h @@ -45,7 +45,8 @@ class AudioDecoder AudioStreamInfo; AudioDecoder(const AudioStreamInfo& audio_stream_info, - SbDrmSystem drm_system); + SbDrmSystem drm_system, + bool enable_flush_during_seek); ~AudioDecoder() override; void Initialize(const OutputCB& output_cb, const ErrorCB& error_cb) override; @@ -77,6 +78,7 @@ class AudioDecoder const AudioStreamInfo audio_stream_info_; const SbMediaAudioSampleType sample_type_; + const bool enable_flush_during_seek_; jint output_sample_rate_; jint output_channel_count_; diff --git a/starboard/android/shared/audio_renderer_passthrough.cc b/starboard/android/shared/audio_renderer_passthrough.cc index 1a2f1b8aa1ba..4dbf55e4df40 100644 --- a/starboard/android/shared/audio_renderer_passthrough.cc +++ b/starboard/android/shared/audio_renderer_passthrough.cc @@ -74,14 +74,15 @@ int ParseAc3SyncframeAudioSampleCount(const uint8_t* buffer, int size) { AudioRendererPassthrough::AudioRendererPassthrough( const AudioStreamInfo& audio_stream_info, - SbDrmSystem drm_system) + SbDrmSystem drm_system, + bool enable_flush_during_seek) : audio_stream_info_(audio_stream_info) { SB_DCHECK(audio_stream_info_.codec == kSbMediaAudioCodecAc3 || audio_stream_info_.codec == kSbMediaAudioCodecEac3); if (SbDrmSystemIsValid(drm_system)) { SB_LOG(INFO) << "Creating AudioDecoder as decryptor."; - scoped_ptr audio_decoder( - new AudioDecoder(audio_stream_info, drm_system)); + scoped_ptr audio_decoder(new AudioDecoder( + audio_stream_info, drm_system, enable_flush_during_seek)); if (audio_decoder->is_valid()) { decoder_.reset(audio_decoder.release()); } diff --git a/starboard/android/shared/audio_renderer_passthrough.h b/starboard/android/shared/audio_renderer_passthrough.h index 28b8370bf462..f7a0ef2e2632 100644 --- a/starboard/android/shared/audio_renderer_passthrough.h +++ b/starboard/android/shared/audio_renderer_passthrough.h @@ -54,7 +54,8 @@ class AudioRendererPassthrough AudioStreamInfo; AudioRendererPassthrough(const AudioStreamInfo& audio_stream_info, - SbDrmSystem drm_system); + SbDrmSystem drm_system, + bool enable_flush_during_seek); ~AudioRendererPassthrough() override; bool is_valid() const { return decoder_ != nullptr; } diff --git a/starboard/android/shared/audio_track_audio_sink_type.h b/starboard/android/shared/audio_track_audio_sink_type.h index 6a9bc942f144..06bb6db58af8 100644 --- a/starboard/android/shared/audio_track_audio_sink_type.h +++ b/starboard/android/shared/audio_track_audio_sink_type.h @@ -77,6 +77,8 @@ class AudioTrackAudioSinkType : public SbAudioSinkPrivate::Type { } void Destroy(SbAudioSink audio_sink) override { + // TODO(b/330793785): Use audio_sink.flush() instead of re-creating a new + // audio_sink. if (audio_sink != kSbAudioSinkInvalid && !IsValid(audio_sink)) { SB_LOG(WARNING) << "audio_sink is invalid."; return; diff --git a/starboard/android/shared/media_codec_bridge.cc b/starboard/android/shared/media_codec_bridge.cc index 41466eb7fe73..eb14a60334ee 100644 --- a/starboard/android/shared/media_codec_bridge.cc +++ b/starboard/android/shared/media_codec_bridge.cc @@ -446,6 +446,11 @@ void MediaCodecBridge::SetPlaybackRate(double playback_rate) { j_media_codec_bridge_, "setPlaybackRate", "(D)V", playback_rate); } +bool MediaCodecBridge::Start() { + return JniEnvExt::Get()->CallBooleanMethodOrAbort(j_media_codec_bridge_, + "start", "()Z") == JNI_TRUE; +} + jint MediaCodecBridge::Flush() { return JniEnvExt::Get()->CallIntMethodOrAbort(j_media_codec_bridge_, "flush", "()I"); diff --git a/starboard/android/shared/media_codec_bridge.h b/starboard/android/shared/media_codec_bridge.h index 9b4ce441eb91..a7b3d369db0d 100644 --- a/starboard/android/shared/media_codec_bridge.h +++ b/starboard/android/shared/media_codec_bridge.h @@ -198,6 +198,7 @@ class MediaCodecBridge { void ReleaseOutputBufferAtTimestamp(jint index, jlong render_timestamp_ns); void SetPlaybackRate(double playback_rate); + bool Start(); jint Flush(); FrameSize GetOutputSize(); AudioOutputFormatResult GetAudioOutputFormat(); diff --git a/starboard/android/shared/media_decoder.cc b/starboard/android/shared/media_decoder.cc index aed5f7347e7c..ac3cd8fdb5b1 100644 --- a/starboard/android/shared/media_decoder.cc +++ b/starboard/android/shared/media_decoder.cc @@ -265,6 +265,9 @@ void MediaDecoder::DecoderThreadFunc() { } } SB_DCHECK(dequeue_output_results.empty()); + if (destroying_.load()) { + break; + } CollectPendingData_Locked(&pending_tasks, &input_buffer_indices, &dequeue_output_results); } @@ -312,6 +315,9 @@ void MediaDecoder::DecoderThreadFunc() { collect_pending_data = !has_input || !has_output; } + if (destroying_.load()) { + break; + } if (collect_pending_data) { ScopedLock scoped_lock(mutex_); CollectPendingData_Locked(&pending_tasks, &input_buffer_indices, @@ -616,6 +622,12 @@ void MediaDecoder::OnMediaCodecOutputBufferAvailable( SB_DCHECK(media_codec_bridge_); SB_DCHECK(buffer_index >= 0); + // TODO(b/291959069): After |decoder_thread_| is destroyed, it may still + // receive output buffer, discard this invalid output buffer. + if (destroying_.load() || !SbThreadIsValid(decoder_thread_)) { + return; + } + DequeueOutputResult dequeue_output_result; dequeue_output_result.status = 0; dequeue_output_result.index = buffer_index; @@ -644,6 +656,64 @@ void MediaDecoder::OnMediaCodecFrameRendered(SbTime frame_timestamp) { frame_rendered_cb_(frame_timestamp); } +bool MediaDecoder::Flush() { + // Try to flush if we can, otherwise return |false| to recreate the codec + // completely. Flush() is called by `player_worker` thread, + // but MediaDecoder is on `audio_decoder` and `video_decoder` + // threads, let `player_worker` destroy `audio_decoder` and + // `video_decoder` threads to clean up all pending tasks, + // and Flush()/Start() |media_codec_bridge_|. + + // 1. Destroy `audio_decoder` and `video_decoder` threads. + destroying_.store(true); + { + ScopedLock scoped_lock(mutex_); + condition_variable_.Signal(); + } + if (SbThreadIsValid(decoder_thread_)) { + SbThreadJoin(decoder_thread_, NULL); + decoder_thread_ = kSbThreadInvalid; + } + + // 2. Flush()/Start() |media_codec_bridge_| and clean up pending tasks. + if (is_valid()) { + // 2.1. Flush() |media_codec_bridge_|. + host_->OnFlushing(); + jint status = media_codec_bridge_->Flush(); + if (status != MEDIA_CODEC_OK) { + SB_LOG(ERROR) << "Failed to flush media codec."; + return false; + } + + // 2.2. Clean up pending_tasks and input_buffer/output_buffer indices. + number_of_pending_tasks_.store(0); + pending_tasks_.clear(); + input_buffer_indices_.clear(); + dequeue_output_results_.clear(); + pending_queue_input_buffer_task_ = nullopt_t(); + + // 2.3. Add OutputFormatChanged to get current output format after Flush(). + DequeueOutputResult dequeue_output_result = {}; + dequeue_output_result.index = -1; + dequeue_output_results_.push_back(dequeue_output_result); + + // 2.4. Start() |media_codec_bridge_|. As the codec is configured in + // asynchronous mode, call Start() after Flush() has returned to + // resume codec operations. After Start(), input_buffer_index should + // start with 0. + if (!media_codec_bridge_->Start()) { + SB_LOG(ERROR) << "Failed to start media codec."; + return false; + } + } + + // 3. Recreate `audio_decoder` and `video_decoder` threads in + // WriteInputBuffers(). + stream_ended_.store(false); + destroying_.store(false); + return true; +} + } // namespace shared } // namespace android } // namespace starboard diff --git a/starboard/android/shared/media_decoder.h b/starboard/android/shared/media_decoder.h index 52a2a5e352db..17a4d10b5c57 100644 --- a/starboard/android/shared/media_decoder.h +++ b/starboard/android/shared/media_decoder.h @@ -113,6 +113,8 @@ class MediaDecoder bool is_valid() const { return media_codec_bridge_ != NULL; } + bool Flush(); + private: struct Event { enum Type { diff --git a/starboard/android/shared/player_components_factory.h b/starboard/android/shared/player_components_factory.h index 5d56b3d204b5..86ad847135a6 100644 --- a/starboard/android/shared/player_components_factory.h +++ b/starboard/android/shared/player_components_factory.h @@ -70,6 +70,11 @@ constexpr bool kForceSecurePipelineInTunnelModeWhenRequired = true; // video distortion on some platforms. constexpr bool kForceResetSurfaceUnderTunnelMode = true; +// By default, Cobalt recreates MediaCodec when Reset() during Seek(). +// Set the following variable to true to force it Flush() MediaCodec +// during Seek(). +constexpr bool kForceFlushDecoderDuringReset = false; + // This class allows us to force int16 sample type when tunnel mode is enabled. class AudioRendererSinkAndroid : public ::starboard::shared::starboard::player:: filter::AudioRendererSinkImpl { @@ -221,12 +226,28 @@ class PlayerComponentsFactory : public starboard::shared::starboard::player:: } } + bool enable_flush_during_seek = false; + if (!creation_parameters.video_mime().empty()) { + MimeType video_mime_type(creation_parameters.video_mime()); + if (video_mime_type.ValidateBoolParameter("enableflushduringseek")) { + enable_flush_during_seek = + video_mime_type.GetParamBoolValue("enableflushduringseek", false); + } + } + + if (kForceFlushDecoderDuringReset && !enable_flush_during_seek) { + SB_LOG(INFO) + << "`kForceFlushDecoderDuringReset` is set to true, force flushing" + << " audio passthrough decoder during Reset()."; + enable_flush_during_seek = true; + } + SB_LOG(INFO) << "Creating passthrough components."; // TODO: Enable tunnel mode for passthrough scoped_ptr audio_renderer; - audio_renderer.reset( - new AudioRendererPassthrough(creation_parameters.audio_stream_info(), - creation_parameters.drm_system())); + audio_renderer.reset(new AudioRendererPassthrough( + creation_parameters.audio_stream_info(), + creation_parameters.drm_system(), enable_flush_during_seek)); if (!audio_renderer->is_valid()) { return scoped_ptr(); } @@ -298,7 +319,8 @@ class PlayerComponentsFactory : public starboard::shared::starboard::player:: MimeType video_mime_type(video_mime); if (!video_mime.empty()) { if (!video_mime_type.is_valid() || - !video_mime_type.ValidateBoolParameter("tunnelmode")) { + !video_mime_type.ValidateBoolParameter("tunnelmode") || + !video_mime_type.ValidateBoolParameter("enableflushduringseek")) { *error_message = "Invalid video MIME: '" + std::string(video_mime) + "'"; return false; @@ -359,13 +381,30 @@ class PlayerComponentsFactory : public starboard::shared::starboard::player:: << tunnel_mode_audio_session_id << '.'; } + bool enable_flush_during_seek = + video_mime_type.GetParamBoolValue("enableflushduringseek", false); + SB_LOG(INFO) << "Flush MediaCodec during Reset(): " + << (enable_flush_during_seek ? "enabled. " : "disabled. ") + << "Video mime parameter \"enableflushduringseek\" value: " + << video_mime_type.GetParamStringValue("enableflushduringseek", + "") + << "."; + + if (kForceFlushDecoderDuringReset && !enable_flush_during_seek) { + SB_LOG(INFO) + << "`kForceFlushDecoderDuringReset` is set to true, force flushing" + << " audio decoder during Reset()."; + enable_flush_during_seek = true; + } + if (creation_parameters.audio_codec() != kSbMediaAudioCodecNone) { SB_DCHECK(audio_decoder); SB_DCHECK(audio_renderer_sink); using starboard::shared::starboard::media::AudioStreamInfo; - auto decoder_creator = [](const AudioStreamInfo& audio_stream_info, - SbDrmSystem drm_system) { + auto decoder_creator = [enable_flush_during_seek]( + const AudioStreamInfo& audio_stream_info, + SbDrmSystem drm_system) { bool use_libopus_decoder = audio_stream_info.codec == kSbMediaAudioCodecOpus && !SbDrmSystemIsValid(drm_system) && !kForcePlatformOpusDecoder; @@ -377,8 +416,8 @@ class PlayerComponentsFactory : public starboard::shared::starboard::player:: } } else if (audio_stream_info.codec == kSbMediaAudioCodecAac || audio_stream_info.codec == kSbMediaAudioCodecOpus) { - scoped_ptr audio_decoder_impl( - new AudioDecoder(audio_stream_info, drm_system)); + scoped_ptr audio_decoder_impl(new AudioDecoder( + audio_stream_info, drm_system, enable_flush_during_seek)); if (audio_decoder_impl->is_valid()) { return audio_decoder_impl.PassAs(); } @@ -471,6 +510,7 @@ class PlayerComponentsFactory : public starboard::shared::starboard::player:: int max_video_input_size, std::string* error_message) { bool force_big_endian_hdr_metadata = false; + bool enable_flush_during_seek = false; if (!creation_parameters.video_mime().empty()) { // Use mime param to determine endianness of HDR metadata. If param is // missing or invalid it defaults to Little Endian. @@ -481,6 +521,16 @@ class PlayerComponentsFactory : public starboard::shared::starboard::player:: video_mime_type.GetParamStringValue("hdrinfoendianness", /*default=*/"little"); force_big_endian_hdr_metadata = hdr_info_endianness == "big"; + + video_mime_type.ValidateBoolParameter("enableflushduringseek"); + enable_flush_during_seek = + video_mime_type.GetParamBoolValue("enableflushduringseek", false); + } + if (kForceFlushDecoderDuringReset && !enable_flush_during_seek) { + SB_LOG(INFO) + << "`kForceFlushDecoderDuringReset` is set to true, force flushing" + << " video decoder during Reset()."; + enable_flush_during_seek = true; } scoped_ptr video_decoder(new VideoDecoder( @@ -490,7 +540,7 @@ class PlayerComponentsFactory : public starboard::shared::starboard::player:: creation_parameters.max_video_capabilities(), tunnel_mode_audio_session_id, force_secure_pipeline_under_tunnel_mode, kForceResetSurfaceUnderTunnelMode, force_big_endian_hdr_metadata, - max_video_input_size, error_message)); + max_video_input_size, enable_flush_during_seek, error_message)); if (creation_parameters.video_codec() == kSbMediaVideoCodecAv1 || video_decoder->is_decoder_created()) { return video_decoder.Pass(); diff --git a/starboard/android/shared/video_decoder.cc b/starboard/android/shared/video_decoder.cc index 9e0ba665edb4..dbe824cba023 100644 --- a/starboard/android/shared/video_decoder.cc +++ b/starboard/android/shared/video_decoder.cc @@ -357,6 +357,7 @@ VideoDecoder::VideoDecoder(const VideoStreamInfo& video_stream_info, bool force_reset_surface_under_tunnel_mode, bool force_big_endian_hdr_metadata, int max_video_input_size, + bool enable_flush_during_seek, std::string* error_message) : video_codec_(video_stream_info.codec), drm_system_(static_cast(drm_system)), @@ -374,7 +375,8 @@ VideoDecoder::VideoDecoder(const VideoStreamInfo& video_stream_info, surface_condition_variable_(surface_destroy_mutex_), require_software_codec_(IsSoftwareDecodeRequired(max_video_capabilities)), force_big_endian_hdr_metadata_(force_big_endian_hdr_metadata), - number_of_preroll_frames_(kInitialPrerollFrameCount) { + number_of_preroll_frames_(kInitialPrerollFrameCount), + enable_flush_during_seek_(enable_flush_during_seek) { SB_DCHECK(error_message); if (force_secure_pipeline_under_tunnel_mode) { @@ -599,16 +601,28 @@ void VideoDecoder::WriteEndOfStream() { void VideoDecoder::Reset() { SB_DCHECK(BelongsToCurrentThread()); - TeardownCodec(); + // If fail to flush |media_decoder_|, then re-create |media_decoder_|. + if (!enable_flush_during_seek_ || !media_decoder_->Flush()) { + TeardownCodec(); + + input_buffer_written_ = 0; + + // If the codec is kSbMediaVideoCodecAv1, + // set video_fps_ to 0 will call InitializeCodec(), + // which we do not need if flush the codec. + video_fps_ = 0; + } CancelPendingJobs(); - tunnel_mode_prerolling_.store(true); - tunnel_mode_frame_rendered_.store(false); - input_buffer_written_ = 0; + // TODO(b/291959069): After flush |media_decoder_|, the output buffers + // may have invalid frames. Reset |output_format_| to null here to skip max + // output buffers check. decoded_output_frames_ = 0; output_format_ = starboard::nullopt; + + tunnel_mode_prerolling_.store(true); + tunnel_mode_frame_rendered_.store(false); end_of_stream_written_ = false; - video_fps_ = 0; pending_input_buffers_.clear(); // TODO: We rely on VideoRenderAlgorithmTunneled::Seek() to be called inside diff --git a/starboard/android/shared/video_decoder.h b/starboard/android/shared/video_decoder.h index 1d8ab435f64a..db2750e95088 100644 --- a/starboard/android/shared/video_decoder.h +++ b/starboard/android/shared/video_decoder.h @@ -71,6 +71,7 @@ class VideoDecoder bool force_reset_surface_under_tunnel_mode, bool force_big_endian_hdr_metadata, int max_input_size, + bool enable_flush_during_seek, std::string* error_message); ~VideoDecoder() override; @@ -151,6 +152,8 @@ class VideoDecoder // Set the maximum size in bytes of an input buffer for video. const int max_video_input_size_; + const bool enable_flush_during_seek_; + // Force resetting the video surface after tunnel mode playback, which // prevents video distortion on some devices. const bool force_reset_surface_under_tunnel_mode_; diff --git a/starboard/shared/starboard/player/filter/adaptive_audio_decoder_internal.cc b/starboard/shared/starboard/player/filter/adaptive_audio_decoder_internal.cc index 40c77b2cf2c8..19b99b8f0c8a 100644 --- a/starboard/shared/starboard/player/filter/adaptive_audio_decoder_internal.cc +++ b/starboard/shared/starboard/player/filter/adaptive_audio_decoder_internal.cc @@ -80,6 +80,9 @@ void AdaptiveAudioDecoder::Decode(const InputBuffers& input_buffers, SB_DCHECK(input_buffers.front()->audio_stream_info().codec != kSbMediaAudioCodecNone); + if (!first_input_written_) { + first_input_written_ = true; + } if (!audio_decoder_) { InitializeAudioDecoder(input_buffers.front()->audio_stream_info()); if (audio_decoder_) { @@ -118,7 +121,7 @@ void AdaptiveAudioDecoder::WriteEndOfStream() { SB_DCHECK(!pending_consumed_cb_); stream_ended_ = true; - if (audio_decoder_) { + if (first_input_written_) { audio_decoder_->WriteEndOfStream(); } else { decoded_audios_.push(new DecodedAudio); @@ -183,6 +186,7 @@ void AdaptiveAudioDecoder::TeardownAudioDecoder() { audio_decoder_.reset(); resampler_.reset(); channel_mixer_.reset(); + first_input_written_ = false; } void AdaptiveAudioDecoder::ResetInternal() { diff --git a/starboard/shared/starboard/player/filter/adaptive_audio_decoder_internal.h b/starboard/shared/starboard/player/filter/adaptive_audio_decoder_internal.h index f128e8826127..60a301bdcc94 100644 --- a/starboard/shared/starboard/player/filter/adaptive_audio_decoder_internal.h +++ b/starboard/shared/starboard/player/filter/adaptive_audio_decoder_internal.h @@ -90,6 +90,7 @@ class AdaptiveAudioDecoder : public AudioDecoder, private JobQueue::JobOwner { bool stream_ended_ = false; bool first_output_received_ = false; bool output_format_checked_ = false; + bool first_input_written_ = false; }; } // namespace filter diff --git a/starboard/shared/starboard/player/filter/testing/audio_decoder_test.cc b/starboard/shared/starboard/player/filter/testing/audio_decoder_test.cc index 8becb18f1fc1..d1d742acf186 100644 --- a/starboard/shared/starboard/player/filter/testing/audio_decoder_test.cc +++ b/starboard/shared/starboard/player/filter/testing/audio_decoder_test.cc @@ -378,6 +378,40 @@ class AudioDecoderTest } } + void WaitForDecoderInRunningState() { + // Write multiple inputs to avoid reset codec too soon after start. + const size_t kMaxNumberOfInputsToWrite = 50; + size_t start_index = 0; + size_t number_of_inputs_to_write = std::min( + kMaxNumberOfInputsToWrite, dmp_reader_.number_of_audio_buffers()); + + // Wait for at least one output available to ensure codec is in running + // state. + ASSERT_NO_FATAL_FAILURE(WriteSingleInput(start_index)); + ++start_index; + --number_of_inputs_to_write; + + while (number_of_inputs_to_write > 0) { + Event event = kError; + ASSERT_NO_FATAL_FAILURE(WaitForNextEvent(&event)); + if (event == kConsumed) { + ASSERT_NO_FATAL_FAILURE(WriteSingleInput(start_index)); + ++start_index; + --number_of_inputs_to_write; + continue; + } + if (event == kError) { + FAIL(); + } + ASSERT_EQ(kOutput, event); + scoped_refptr decoded_audio; + ASSERT_NO_FATAL_FAILURE(ReadFromDecoder(&decoded_audio)); + ASSERT_TRUE(decoded_audio); + ASSERT_FALSE(decoded_audio->is_end_of_stream()); + break; + } + } + scoped_refptr GetAudioInputBuffer(size_t index) { auto input_buffer = testing::GetAudioInputBuffer(&dmp_reader_, index); auto iter = invalid_inputs_.find(index); @@ -849,6 +883,94 @@ TEST_P(AudioDecoderTest, PartialAudio) { } } +TEST_P(AudioDecoderTest, ResetAfterEndOfStream) { + ASSERT_NO_FATAL_FAILURE(WriteSingleInput(0)); + WriteEndOfStream(); + ASSERT_NO_FATAL_FAILURE(DrainOutputs()); + + ResetDecoder(); + WriteEndOfStream(); + ASSERT_NO_FATAL_FAILURE(DrainOutputs()); + ASSERT_TRUE(decoded_audios_.empty()); +} + +TEST_P(AudioDecoderTest, ResetAfterEndOfStreamWithSingleInput) { + ASSERT_NO_FATAL_FAILURE(WriteSingleInput(0)); + WriteEndOfStream(); + ASSERT_NO_FATAL_FAILURE(DrainOutputs()); + int decoded_audio_frames_before_reset = GetTotalFrames(decoded_audios_); + + ResetDecoder(); + + ASSERT_NO_FATAL_FAILURE(WriteSingleInput(0)); + WriteEndOfStream(); + ASSERT_NO_FATAL_FAILURE(DrainOutputs()); + ASSERT_FALSE(decoded_audios_.empty()); + ASSERT_NO_FATAL_FAILURE(AssertOutputFormatValid()); + + int decoded_audio_frames_after_reset = GetTotalFrames(decoded_audios_); + ASSERT_EQ(decoded_audio_frames_before_reset, + decoded_audio_frames_after_reset); +} + +TEST_P(AudioDecoderTest, ResetAfterEndOfStreamWithMultipleInputs) { + const size_t kMaxNumberOfInputsToWrite = 5; + const size_t number_of_inputs_to_write = std::min( + kMaxNumberOfInputsToWrite, dmp_reader_.number_of_audio_buffers()); + + ASSERT_NO_FATAL_FAILURE(WriteMultipleInputs(0, number_of_inputs_to_write)); + WriteEndOfStream(); + ASSERT_NO_FATAL_FAILURE(DrainOutputs()); + int decoded_audio_frames_before_reset = GetTotalFrames(decoded_audios_); + + ResetDecoder(); + + ASSERT_NO_FATAL_FAILURE(WriteMultipleInputs(0, number_of_inputs_to_write)); + WriteEndOfStream(); + ASSERT_NO_FATAL_FAILURE(DrainOutputs()); + ASSERT_FALSE(decoded_audios_.empty()); + ASSERT_NO_FATAL_FAILURE(AssertOutputFormatValid()); + + int decoded_audio_frames_after_reset = GetTotalFrames(decoded_audios_); + ASSERT_EQ(decoded_audio_frames_before_reset, + decoded_audio_frames_after_reset); +} + +TEST_P(AudioDecoderTest, ResetInRunningStateWithInput) { + const size_t kMaxNumberOfInputsToWrite = 5; + const size_t number_of_inputs_to_write = std::min( + kMaxNumberOfInputsToWrite, dmp_reader_.number_of_audio_buffers()); + + ASSERT_NO_FATAL_FAILURE(WriteMultipleInputs(0, number_of_inputs_to_write)); + WriteEndOfStream(); + ASSERT_NO_FATAL_FAILURE(DrainOutputs()); + int decoded_audio_frames_before_reset = GetTotalFrames(decoded_audios_); + ResetDecoder(); + + WaitForDecoderInRunningState(); + + ResetDecoder(); + ASSERT_NO_FATAL_FAILURE(WriteMultipleInputs(0, number_of_inputs_to_write)); + WriteEndOfStream(); + ASSERT_NO_FATAL_FAILURE(DrainOutputs()); + ASSERT_FALSE(decoded_audios_.empty()); + ASSERT_NO_FATAL_FAILURE(AssertOutputFormatValid()); + + int decoded_audio_frames_after_reset = GetTotalFrames(decoded_audios_); + ASSERT_EQ(decoded_audio_frames_before_reset, + decoded_audio_frames_after_reset); +} + +TEST_P(AudioDecoderTest, ResetInRunningStateWithEndOfStream) { + WaitForDecoderInRunningState(); + + ResetDecoder(); + WriteEndOfStream(); + ASSERT_NO_FATAL_FAILURE(DrainOutputs()); + ASSERT_TRUE(decoded_audios_.empty()); + ASSERT_NO_FATAL_FAILURE(AssertOutputFormatValid()); +} + INSTANTIATE_TEST_CASE_P( AudioDecoderTests, AudioDecoderTest,