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,