|  | // Copyright 2017 The Cobalt Authors. All Rights Reserved. | 
|  | // | 
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | // you may not use this file except in compliance with the License. | 
|  | // You may obtain a copy of the License at | 
|  | // | 
|  | //     http://www.apache.org/licenses/LICENSE-2.0 | 
|  | // | 
|  | // Unless required by applicable law or agreed to in writing, software | 
|  | // distributed under the License is distributed on an "AS IS" BASIS, | 
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | // See the License for the specific language governing permissions and | 
|  | // limitations under the License. | 
|  |  | 
|  | #include "starboard/android/shared/media_decoder.h" | 
|  |  | 
|  | #include "starboard/android/shared/jni_env_ext.h" | 
|  | #include "starboard/android/shared/jni_utils.h" | 
|  | #include "starboard/android/shared/media_common.h" | 
|  | #include "starboard/audio_sink.h" | 
|  | #include "starboard/common/log.h" | 
|  | #include "starboard/common/string.h" | 
|  | #include "starboard/shared/pthread/thread_create_priority.h" | 
|  |  | 
|  | namespace starboard { | 
|  | namespace android { | 
|  | namespace shared { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | const jlong kDequeueTimeout = 0; | 
|  |  | 
|  | const jint kNoOffset = 0; | 
|  | const jlong kNoPts = 0; | 
|  | const jint kNoSize = 0; | 
|  | const jint kNoBufferFlags = 0; | 
|  |  | 
|  | const char* GetNameForMediaCodecStatus(jint status) { | 
|  | switch (status) { | 
|  | case MEDIA_CODEC_OK: | 
|  | return "MEDIA_CODEC_OK"; | 
|  | case MEDIA_CODEC_DEQUEUE_INPUT_AGAIN_LATER: | 
|  | return "MEDIA_CODEC_DEQUEUE_INPUT_AGAIN_LATER"; | 
|  | case MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER: | 
|  | return "MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER"; | 
|  | case MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED: | 
|  | return "MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED"; | 
|  | case MEDIA_CODEC_OUTPUT_FORMAT_CHANGED: | 
|  | return "MEDIA_CODEC_OUTPUT_FORMAT_CHANGED"; | 
|  | case MEDIA_CODEC_INPUT_END_OF_STREAM: | 
|  | return "MEDIA_CODEC_INPUT_END_OF_STREAM"; | 
|  | case MEDIA_CODEC_OUTPUT_END_OF_STREAM: | 
|  | return "MEDIA_CODEC_OUTPUT_END_OF_STREAM"; | 
|  | case MEDIA_CODEC_NO_KEY: | 
|  | return "MEDIA_CODEC_NO_KEY"; | 
|  | case MEDIA_CODEC_INSUFFICIENT_OUTPUT_PROTECTION: | 
|  | return "MEDIA_CODEC_INSUFFICIENT_OUTPUT_PROTECTION"; | 
|  | case MEDIA_CODEC_ABORT: | 
|  | return "MEDIA_CODEC_ABORT"; | 
|  | case MEDIA_CODEC_ERROR: | 
|  | return "MEDIA_CODEC_ERROR"; | 
|  | default: | 
|  | SB_NOTREACHED(); | 
|  | return "MEDIA_CODEC_ERROR_UNKNOWN"; | 
|  | } | 
|  | } | 
|  |  | 
|  | const char* GetDecoderName(SbMediaType media_type) { | 
|  | return media_type == kSbMediaTypeAudio ? "audio_decoder" : "video_decoder"; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | MediaDecoder::MediaDecoder(Host* host, | 
|  | SbMediaAudioCodec audio_codec, | 
|  | const SbMediaAudioSampleInfo& audio_sample_info, | 
|  | SbDrmSystem drm_system) | 
|  | : media_type_(kSbMediaTypeAudio), | 
|  | host_(host), | 
|  | drm_system_(static_cast<DrmSystem*>(drm_system)), | 
|  | condition_variable_(mutex_), | 
|  | tunnel_mode_enabled_(false) { | 
|  | SB_DCHECK(host_); | 
|  |  | 
|  | jobject j_media_crypto = drm_system_ ? drm_system_->GetMediaCrypto() : NULL; | 
|  | SB_DCHECK(!drm_system_ || j_media_crypto); | 
|  | media_codec_bridge_ = MediaCodecBridge::CreateAudioMediaCodecBridge( | 
|  | audio_codec, audio_sample_info, this, j_media_crypto); | 
|  | if (!media_codec_bridge_) { | 
|  | SB_LOG(ERROR) << "Failed to create audio media codec bridge."; | 
|  | return; | 
|  | } | 
|  | if (audio_sample_info.audio_specific_config_size > 0) { | 
|  | // |audio_sample_info.audio_specific_config| is guaranteed to be outlived | 
|  | // the decoder as it is stored in |FilterBasedPlayerWorkerHandler|. | 
|  | pending_tasks_.push_back(Event( | 
|  | static_cast<const int8_t*>(audio_sample_info.audio_specific_config), | 
|  | audio_sample_info.audio_specific_config_size)); | 
|  | number_of_pending_tasks_.increment(); | 
|  | } | 
|  | } | 
|  |  | 
|  | MediaDecoder::MediaDecoder(Host* host, | 
|  | SbMediaVideoCodec video_codec, | 
|  | int width, | 
|  | int height, | 
|  | int fps, | 
|  | jobject j_output_surface, | 
|  | SbDrmSystem drm_system, | 
|  | const SbMediaColorMetadata* color_metadata, | 
|  | bool require_software_codec, | 
|  | const FrameRenderedCB& frame_rendered_cb, | 
|  | int tunnel_mode_audio_session_id, | 
|  | std::string* error_message) | 
|  | : media_type_(kSbMediaTypeVideo), | 
|  | host_(host), | 
|  | drm_system_(static_cast<DrmSystem*>(drm_system)), | 
|  | frame_rendered_cb_(frame_rendered_cb), | 
|  | tunnel_mode_enabled_(tunnel_mode_audio_session_id != -1), | 
|  | condition_variable_(mutex_) { | 
|  | SB_DCHECK(frame_rendered_cb_); | 
|  |  | 
|  | jobject j_media_crypto = drm_system_ ? drm_system_->GetMediaCrypto() : NULL; | 
|  | SB_DCHECK(!drm_system_ || j_media_crypto); | 
|  | media_codec_bridge_ = MediaCodecBridge::CreateVideoMediaCodecBridge( | 
|  | video_codec, width, height, fps, this, j_output_surface, j_media_crypto, | 
|  | color_metadata, require_software_codec, tunnel_mode_audio_session_id, | 
|  | error_message); | 
|  | if (!media_codec_bridge_) { | 
|  | SB_LOG(ERROR) << "Failed to create video media codec bridge with error: " | 
|  | << *error_message; | 
|  | } | 
|  | } | 
|  |  | 
|  | MediaDecoder::~MediaDecoder() { | 
|  | SB_DCHECK(thread_checker_.CalledOnValidThread()); | 
|  |  | 
|  | destroying_.store(true); | 
|  | condition_variable_.Signal(); | 
|  |  | 
|  | if (SbThreadIsValid(decoder_thread_)) { | 
|  | SbThreadJoin(decoder_thread_, NULL); | 
|  | decoder_thread_ = kSbThreadInvalid; | 
|  | } | 
|  |  | 
|  | if (is_valid()) { | 
|  | host_->OnFlushing(); | 
|  | // After |decoder_thread_| is ended and before |media_codec_bridge_| is | 
|  | // flushed, OnMediaCodecOutputBufferAvailable() would still be called. | 
|  | // So that, |dequeue_output_results_| may not be empty. As | 
|  | // DequeueOutputResult is consisted of plain data, it's fine to let | 
|  | // destructor delete |dequeue_output_results_|. | 
|  | jint status = media_codec_bridge_->Flush(); | 
|  | if (status != MEDIA_CODEC_OK) { | 
|  | SB_LOG(ERROR) << "Failed to flush media codec."; | 
|  | } | 
|  | host_ = NULL; | 
|  | } | 
|  | } | 
|  |  | 
|  | void MediaDecoder::Initialize(const ErrorCB& error_cb) { | 
|  | SB_DCHECK(thread_checker_.CalledOnValidThread()); | 
|  | SB_DCHECK(error_cb); | 
|  | SB_DCHECK(!error_cb_); | 
|  |  | 
|  | error_cb_ = error_cb; | 
|  | } | 
|  |  | 
|  | void MediaDecoder::WriteInputBuffer( | 
|  | const scoped_refptr<InputBuffer>& input_buffer) { | 
|  | SB_DCHECK(thread_checker_.CalledOnValidThread()); | 
|  | SB_DCHECK(input_buffer); | 
|  |  | 
|  | if (stream_ended_.load()) { | 
|  | SB_LOG(ERROR) << "Decode() is called after WriteEndOfStream() is called."; | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!SbThreadIsValid(decoder_thread_)) { | 
|  | decoder_thread_ = SbThreadCreate( | 
|  | 0, | 
|  | media_type_ == kSbMediaTypeAudio ? kSbThreadPriorityNormal | 
|  | : kSbThreadPriorityHigh, | 
|  | kSbThreadNoAffinity, true, GetDecoderName(media_type_), | 
|  | &MediaDecoder::DecoderThreadEntryPoint, this); | 
|  | SB_DCHECK(SbThreadIsValid(decoder_thread_)); | 
|  | } | 
|  |  | 
|  | ScopedLock scoped_lock(mutex_); | 
|  | pending_tasks_.push_back(Event(input_buffer)); | 
|  | number_of_pending_tasks_.increment(); | 
|  | if (pending_tasks_.size() == 1) { | 
|  | condition_variable_.Signal(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void MediaDecoder::WriteEndOfStream() { | 
|  | SB_DCHECK(thread_checker_.CalledOnValidThread()); | 
|  |  | 
|  | stream_ended_.store(true); | 
|  | ScopedLock scoped_lock(mutex_); | 
|  | pending_tasks_.push_back(Event(Event::kWriteEndOfStream)); | 
|  | number_of_pending_tasks_.increment(); | 
|  | if (pending_tasks_.size() == 1) { | 
|  | condition_variable_.Signal(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void MediaDecoder::SetPlaybackRate(double playback_rate) { | 
|  | SB_DCHECK(media_type_ == kSbMediaTypeVideo); | 
|  | SB_DCHECK(media_codec_bridge_); | 
|  | media_codec_bridge_->SetPlaybackRate(playback_rate); | 
|  | } | 
|  |  | 
|  | // static | 
|  | void* MediaDecoder::DecoderThreadEntryPoint(void* context) { | 
|  | SB_DCHECK(context); | 
|  | MediaDecoder* decoder = static_cast<MediaDecoder*>(context); | 
|  | decoder->DecoderThreadFunc(); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | void MediaDecoder::DecoderThreadFunc() { | 
|  | SB_DCHECK(error_cb_); | 
|  |  | 
|  | if (media_type_ == kSbMediaTypeAudio) { | 
|  | std::deque<Event> pending_tasks; | 
|  | std::vector<int> input_buffer_indices; | 
|  |  | 
|  | while (!destroying_.load()) { | 
|  | std::vector<DequeueOutputResult> dequeue_output_results; | 
|  | { | 
|  | ScopedLock scoped_lock(mutex_); | 
|  | bool has_input = !pending_tasks.empty() || !pending_tasks_.empty(); | 
|  | bool has_input_buffer = | 
|  | !input_buffer_indices.empty() || !input_buffer_indices_.empty(); | 
|  | bool can_process_input = | 
|  | pending_queue_input_buffer_task_ || (has_input && has_input_buffer); | 
|  | if (dequeue_output_results_.empty() && !can_process_input) { | 
|  | if (!condition_variable_.WaitTimed(5 * kSbTimeSecond)) { | 
|  | SB_LOG_IF(ERROR, !stream_ended_.load()) | 
|  | << GetDecoderName(media_type_) << ": Wait() hits timeout."; | 
|  | } | 
|  | } | 
|  | SB_DCHECK(dequeue_output_results.empty()); | 
|  | CollectPendingData_Locked(&pending_tasks, &input_buffer_indices, | 
|  | &dequeue_output_results); | 
|  | } | 
|  |  | 
|  | for (auto dequeue_output_result : dequeue_output_results) { | 
|  | if (dequeue_output_result.index < 0) { | 
|  | host_->RefreshOutputFormat(media_codec_bridge_.get()); | 
|  | } else { | 
|  | host_->ProcessOutputBuffer(media_codec_bridge_.get(), | 
|  | dequeue_output_result); | 
|  | } | 
|  | } | 
|  |  | 
|  | for (;;) { | 
|  | bool can_process_input = | 
|  | pending_queue_input_buffer_task_ || | 
|  | (!pending_tasks.empty() && !input_buffer_indices.empty()); | 
|  | if (!can_process_input) { | 
|  | break; | 
|  | } | 
|  | if (!ProcessOneInputBuffer(&pending_tasks, &input_buffer_indices)) { | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | } else { | 
|  | // While it is possible to consolidate the logic for audio and video | 
|  | // decoders, it is easy to fine tune the behavior of video decoder if they | 
|  | // are separated. | 
|  | std::deque<Event> pending_tasks; | 
|  | std::vector<int> input_buffer_indices; | 
|  | std::vector<DequeueOutputResult> dequeue_output_results; | 
|  |  | 
|  | while (!destroying_.load()) { | 
|  | bool has_input = | 
|  | pending_queue_input_buffer_task_ || | 
|  | (!pending_tasks.empty() && !input_buffer_indices.empty()); | 
|  | bool has_output = !dequeue_output_results.empty(); | 
|  | bool collect_pending_data = false; | 
|  |  | 
|  | if (tunnel_mode_enabled_) { | 
|  | // We don't explicitly process output in tunnel mode. | 
|  | collect_pending_data = !has_input; | 
|  | } else { | 
|  | collect_pending_data = !has_input || !has_output; | 
|  | } | 
|  |  | 
|  | if (collect_pending_data) { | 
|  | ScopedLock scoped_lock(mutex_); | 
|  | CollectPendingData_Locked(&pending_tasks, &input_buffer_indices, | 
|  | &dequeue_output_results); | 
|  | } | 
|  |  | 
|  | if (!tunnel_mode_enabled_) { | 
|  | // Output is only processed when tunnel mode is disabled. | 
|  | if (!dequeue_output_results.empty()) { | 
|  | auto& dequeue_output_result = dequeue_output_results.front(); | 
|  | if (dequeue_output_result.index < 0) { | 
|  | host_->RefreshOutputFormat(media_codec_bridge_.get()); | 
|  | } else { | 
|  | host_->ProcessOutputBuffer(media_codec_bridge_.get(), | 
|  | dequeue_output_result); | 
|  | } | 
|  | dequeue_output_results.erase(dequeue_output_results.begin()); | 
|  | } | 
|  | host_->Tick(media_codec_bridge_.get()); | 
|  | } | 
|  |  | 
|  | bool can_process_input = | 
|  | pending_queue_input_buffer_task_ || | 
|  | (!pending_tasks.empty() && !input_buffer_indices.empty()); | 
|  | if (can_process_input) { | 
|  | ProcessOneInputBuffer(&pending_tasks, &input_buffer_indices); | 
|  | } | 
|  |  | 
|  | bool ticked = false; | 
|  | if (!tunnel_mode_enabled_) { | 
|  | // Output is only processed when tunnel mode is disabled. | 
|  | ticked = host_->Tick(media_codec_bridge_.get()); | 
|  | } | 
|  |  | 
|  | can_process_input = | 
|  | pending_queue_input_buffer_task_ || | 
|  | (!pending_tasks.empty() && !input_buffer_indices.empty()); | 
|  | if (!ticked && !can_process_input && dequeue_output_results.empty()) { | 
|  | ScopedLock scoped_lock(mutex_); | 
|  | CollectPendingData_Locked(&pending_tasks, &input_buffer_indices, | 
|  | &dequeue_output_results); | 
|  | can_process_input = | 
|  | !pending_tasks.empty() && !input_buffer_indices.empty(); | 
|  | if (!can_process_input && dequeue_output_results.empty()) { | 
|  | condition_variable_.WaitTimed(kSbTimeMillisecond); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | SB_LOG(INFO) << "Destroying decoder thread."; | 
|  | } | 
|  |  | 
|  | void MediaDecoder::CollectPendingData_Locked( | 
|  | std::deque<Event>* pending_tasks, | 
|  | std::vector<int>* input_buffer_indices, | 
|  | std::vector<DequeueOutputResult>* dequeue_output_results) { | 
|  | SB_DCHECK(pending_tasks); | 
|  | SB_DCHECK(input_buffer_indices); | 
|  | SB_DCHECK(dequeue_output_results); | 
|  | mutex_.DCheckAcquired(); | 
|  |  | 
|  | pending_tasks->insert(pending_tasks->end(), pending_tasks_.begin(), | 
|  | pending_tasks_.end()); | 
|  | pending_tasks_.clear(); | 
|  |  | 
|  | input_buffer_indices->insert(input_buffer_indices->end(), | 
|  | input_buffer_indices_.begin(), | 
|  | input_buffer_indices_.end()); | 
|  | input_buffer_indices_.clear(); | 
|  |  | 
|  | dequeue_output_results->insert(dequeue_output_results->end(), | 
|  | dequeue_output_results_.begin(), | 
|  | dequeue_output_results_.end()); | 
|  | dequeue_output_results_.clear(); | 
|  | } | 
|  |  | 
|  | bool MediaDecoder::ProcessOneInputBuffer( | 
|  | std::deque<Event>* pending_tasks, | 
|  | std::vector<int>* input_buffer_indices) { | 
|  | SB_DCHECK(media_codec_bridge_); | 
|  |  | 
|  | // During secure playback, and only secure playback, it is possible that our | 
|  | // attempt to enqueue an input buffer will be rejected by MediaCodec because | 
|  | // we do not have a key yet.  In this case, we hold on to the input buffer | 
|  | // that we have already set up, and repeatedly attempt to enqueue it until | 
|  | // it works.  Ideally, we would just wait until MediaDrm was ready, however | 
|  | // the shared starboard player framework assumes that it is possible to | 
|  | // perform decryption and decoding as separate steps, so from its | 
|  | // perspective, having made it to this point implies that we ready to | 
|  | // decode.  It is not possible to do them as separate steps on Android. From | 
|  | // the perspective of user application, decryption and decoding are one | 
|  | // atomic step. | 
|  | DequeueInputResult dequeue_input_result; | 
|  | Event event; | 
|  | bool input_buffer_already_written = false; | 
|  | if (pending_queue_input_buffer_task_) { | 
|  | dequeue_input_result = | 
|  | pending_queue_input_buffer_task_->dequeue_input_result; | 
|  | SB_DCHECK(dequeue_input_result.index >= 0); | 
|  | event = pending_queue_input_buffer_task_->event; | 
|  | pending_queue_input_buffer_task_ = nullopt_t(); | 
|  | input_buffer_already_written = true; | 
|  | } else { | 
|  | dequeue_input_result.index = input_buffer_indices->front(); | 
|  | input_buffer_indices->erase(input_buffer_indices->begin()); | 
|  | event = pending_tasks->front(); | 
|  | pending_tasks->pop_front(); | 
|  | number_of_pending_tasks_.decrement(); | 
|  | } | 
|  |  | 
|  | SB_DCHECK(event.type == Event::kWriteCodecConfig || | 
|  | event.type == Event::kWriteInputBuffer || | 
|  | event.type == Event::kWriteEndOfStream); | 
|  | const scoped_refptr<InputBuffer>& input_buffer = event.input_buffer; | 
|  | if (event.type == Event::kWriteEndOfStream) { | 
|  | SB_DCHECK(pending_tasks->empty()); | 
|  | } | 
|  | const void* data = NULL; | 
|  | int size = 0; | 
|  | if (event.type == Event::kWriteCodecConfig) { | 
|  | SB_DCHECK(media_type_ == kSbMediaTypeAudio); | 
|  | data = event.codec_config; | 
|  | size = event.codec_config_size; | 
|  | } else if (event.type == Event::kWriteInputBuffer) { | 
|  | data = input_buffer->data(); | 
|  | size = input_buffer->size(); | 
|  | } else if (event.type == Event::kWriteEndOfStream) { | 
|  | data = NULL; | 
|  | size = 0; | 
|  | } | 
|  |  | 
|  | // Don't bother rewriting the same data if we already did it last time we | 
|  | // were called and had it stored in |pending_queue_input_buffer_task_|. | 
|  | if (!input_buffer_already_written && event.type != Event::kWriteEndOfStream) { | 
|  | ScopedJavaByteBuffer byte_buffer( | 
|  | media_codec_bridge_->GetInputBuffer(dequeue_input_result.index)); | 
|  | if (byte_buffer.IsNull()) { | 
|  | SB_LOG(ERROR) << "Unable to write to MediaCodec buffer, |byte_buffer| is" | 
|  | << " null."; | 
|  | // TODO: Stop the decoding loop and call error_cb_ on fatal error. | 
|  | return false; | 
|  | } | 
|  | if (byte_buffer.capacity() < size) { | 
|  | auto error_message = FormatString( | 
|  | "Unable to write to MediaCodec buffer, input buffer size (%d) is" | 
|  | " greater than |byte_buffer.capacity()| (%d).", | 
|  | size, static_cast<int>(byte_buffer.capacity())); | 
|  | SB_LOG(ERROR) << error_message; | 
|  | error_cb_(kSbPlayerErrorDecode, error_message); | 
|  | return false; | 
|  | } | 
|  | byte_buffer.CopyInto(data, size); | 
|  | } | 
|  |  | 
|  | jint status; | 
|  | if (event.type == Event::kWriteCodecConfig) { | 
|  | status = media_codec_bridge_->QueueInputBuffer(dequeue_input_result.index, | 
|  | kNoOffset, size, kNoPts, | 
|  | BUFFER_FLAG_CODEC_CONFIG); | 
|  | } else if (event.type == Event::kWriteInputBuffer) { | 
|  | jlong pts_us = input_buffer->timestamp(); | 
|  | if (drm_system_ && input_buffer->drm_info()) { | 
|  | status = media_codec_bridge_->QueueSecureInputBuffer( | 
|  | dequeue_input_result.index, kNoOffset, *input_buffer->drm_info(), | 
|  | pts_us); | 
|  | } else { | 
|  | status = media_codec_bridge_->QueueInputBuffer( | 
|  | dequeue_input_result.index, kNoOffset, size, pts_us, kNoBufferFlags); | 
|  | } | 
|  | } else { | 
|  | status = media_codec_bridge_->QueueInputBuffer(dequeue_input_result.index, | 
|  | kNoOffset, size, kNoPts, | 
|  | BUFFER_FLAG_END_OF_STREAM); | 
|  | host_->OnEndOfStreamWritten(media_codec_bridge_.get()); | 
|  | } | 
|  |  | 
|  | if (status != MEDIA_CODEC_OK) { | 
|  | HandleError("queue(Secure)?InputBuffer", status); | 
|  | // TODO: Stop the decoding loop and call error_cb_ on fatal error. | 
|  | SB_DCHECK(!pending_queue_input_buffer_task_); | 
|  | pending_queue_input_buffer_task_ = {dequeue_input_result, event}; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | is_output_restricted_ = false; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void MediaDecoder::HandleError(const char* action_name, jint status) { | 
|  | SB_DCHECK(status != MEDIA_CODEC_OK); | 
|  |  | 
|  | bool retry = false; | 
|  |  | 
|  | if (status != MEDIA_CODEC_INSUFFICIENT_OUTPUT_PROTECTION) { | 
|  | is_output_restricted_ = false; | 
|  | } | 
|  |  | 
|  | if (status == MEDIA_CODEC_DEQUEUE_INPUT_AGAIN_LATER) { | 
|  | // Don't bother logging a try again later status, it happens a lot. | 
|  | return; | 
|  | } else if (status == MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER) { | 
|  | // Don't bother logging a try again later status, it will happen a lot. | 
|  | return; | 
|  | } else if (status == MEDIA_CODEC_NO_KEY) { | 
|  | retry = true; | 
|  | } else if (status == MEDIA_CODEC_INSUFFICIENT_OUTPUT_PROTECTION) { | 
|  | // TODO: Reduce the retry frequency when output is restricted, or when | 
|  | // queueSecureInputBuffer() is failed in general. | 
|  | if (is_output_restricted_) { | 
|  | return; | 
|  | } | 
|  | is_output_restricted_ = true; | 
|  | drm_system_->OnInsufficientOutputProtection(); | 
|  | } else { | 
|  | if (media_type_ == kSbMediaTypeAudio) { | 
|  | error_cb_(kSbPlayerErrorDecode, | 
|  | FormatString("%s failed with status %d (audio).", action_name, | 
|  | status)); | 
|  | } else { | 
|  | error_cb_(kSbPlayerErrorDecode, | 
|  | FormatString("%s failed with status %d (video).", action_name, | 
|  | status)); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (retry) { | 
|  | SB_LOG(INFO) << "|" << action_name << "| failed with status: " | 
|  | << GetNameForMediaCodecStatus(status) | 
|  | << ", will try again after a delay."; | 
|  | } else { | 
|  | SB_LOG(ERROR) << "|" << action_name << "| failed with status: " | 
|  | << GetNameForMediaCodecStatus(status) << "."; | 
|  | } | 
|  | } | 
|  |  | 
|  | void MediaDecoder::OnMediaCodecError(bool is_recoverable, | 
|  | bool is_transient, | 
|  | const std::string& diagnostic_info) { | 
|  | SB_LOG(WARNING) << "MediaDecoder encountered " | 
|  | << (is_recoverable ? "recoverable, " : "unrecoverable, ") | 
|  | << (is_transient ? "transient " : "intransient ") | 
|  | << " error with message: " << diagnostic_info; | 
|  |  | 
|  | if (!is_transient) { | 
|  | if (media_type_ == kSbMediaTypeAudio) { | 
|  | error_cb_(kSbPlayerErrorDecode, | 
|  | "OnMediaCodecError (audio): " + diagnostic_info + | 
|  | (is_recoverable ? ", recoverable " : ", unrecoverable ")); | 
|  | } else { | 
|  | error_cb_(kSbPlayerErrorDecode, | 
|  | "OnMediaCodecError (video): " + diagnostic_info + | 
|  | (is_recoverable ? ", recoverable " : ", unrecoverable ")); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void MediaDecoder::OnMediaCodecInputBufferAvailable(int buffer_index) { | 
|  | if (media_type_ == kSbMediaTypeVideo && first_call_on_handler_thread_) { | 
|  | // Set the thread priority of the Handler thread to dispatch the async | 
|  | // decoder callbacks to high. | 
|  | ::starboard::shared::pthread::ThreadSetPriority(kSbThreadPriorityHigh); | 
|  | first_call_on_handler_thread_ = false; | 
|  | } | 
|  | ScopedLock scoped_lock(mutex_); | 
|  | input_buffer_indices_.push_back(buffer_index); | 
|  | if (input_buffer_indices_.size() == 1) { | 
|  | condition_variable_.Signal(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void MediaDecoder::OnMediaCodecOutputBufferAvailable( | 
|  | int buffer_index, | 
|  | int flags, | 
|  | int offset, | 
|  | int64_t presentation_time_us, | 
|  | int size) { | 
|  | SB_DCHECK(media_codec_bridge_); | 
|  | SB_DCHECK(buffer_index >= 0); | 
|  |  | 
|  | DequeueOutputResult dequeue_output_result; | 
|  | dequeue_output_result.status = 0; | 
|  | dequeue_output_result.index = buffer_index; | 
|  | dequeue_output_result.flags = flags; | 
|  | dequeue_output_result.offset = offset; | 
|  | dequeue_output_result.presentation_time_microseconds = presentation_time_us; | 
|  | dequeue_output_result.num_bytes = size; | 
|  |  | 
|  | ScopedLock scoped_lock(mutex_); | 
|  | dequeue_output_results_.push_back(dequeue_output_result); | 
|  | condition_variable_.Signal(); | 
|  | } | 
|  |  | 
|  | void MediaDecoder::OnMediaCodecOutputFormatChanged() { | 
|  | SB_DCHECK(media_codec_bridge_); | 
|  |  | 
|  | DequeueOutputResult dequeue_output_result = {}; | 
|  | dequeue_output_result.index = -1; | 
|  |  | 
|  | ScopedLock scoped_lock(mutex_); | 
|  | dequeue_output_results_.push_back(dequeue_output_result); | 
|  | condition_variable_.Signal(); | 
|  | } | 
|  |  | 
|  | void MediaDecoder::OnMediaCodecFrameRendered(SbTime frame_timestamp) { | 
|  | SB_DCHECK(tunnel_mode_enabled_); | 
|  | frame_rendered_cb_(frame_timestamp); | 
|  | } | 
|  |  | 
|  | }  // namespace shared | 
|  | }  // namespace android | 
|  | }  // namespace starboard |