Upstream version 11.39.250.0
[platform/framework/web/crosswalk.git] / src / media / base / android / media_decoder_job.cc
index b7eef0c..13206ec 100644 (file)
@@ -9,6 +9,7 @@
 #include "base/debug/trace_event.h"
 #include "base/message_loop/message_loop_proxy.h"
 #include "media/base/android/media_codec_bridge.h"
+#include "media/base/android/media_drm_bridge.h"
 #include "media/base/bind_to_current_loop.h"
 #include "media/base/buffers.h"
 
@@ -21,28 +22,35 @@ static const int kMediaCodecTimeoutInMilliseconds = 250;
 
 MediaDecoderJob::MediaDecoderJob(
     const scoped_refptr<base::SingleThreadTaskRunner>& decoder_task_runner,
-    MediaCodecBridge* media_codec_bridge,
-    const base::Closure& request_data_cb)
-    : ui_task_runner_(base::MessageLoopProxy::current()),
+    const base::Closure& request_data_cb,
+    const base::Closure& config_changed_cb)
+    : need_to_reconfig_decoder_job_(false),
+      ui_task_runner_(base::MessageLoopProxy::current()),
       decoder_task_runner_(decoder_task_runner),
-      media_codec_bridge_(media_codec_bridge),
       needs_flush_(false),
       input_eos_encountered_(false),
       output_eos_encountered_(false),
       skip_eos_enqueue_(true),
       prerolling_(true),
       request_data_cb_(request_data_cb),
+      config_changed_cb_(config_changed_cb),
       current_demuxer_data_index_(0),
       input_buf_index_(-1),
+      is_content_encrypted_(false),
       stop_decode_pending_(false),
       destroy_pending_(false),
       is_requesting_demuxer_data_(false),
       is_incoming_data_invalid_(false),
-      weak_factory_(this) {
+      release_resources_pending_(false),
+      drm_bridge_(NULL),
+      drain_decoder_(false) {
   InitializeReceivedData();
+  eos_unit_.end_of_stream = true;
 }
 
-MediaDecoderJob::~MediaDecoderJob() {}
+MediaDecoderJob::~MediaDecoderJob() {
+  ReleaseMediaCodecBridge();
+}
 
 void MediaDecoderJob::OnDataReceived(const DemuxerData& data) {
   DVLOG(1) << __FUNCTION__ << ": " << data.access_units.size() << " units";
@@ -59,7 +67,7 @@ void MediaDecoderJob::OnDataReceived(const DemuxerData& data) {
 
     // If there is a pending callback, need to request the data again to get
     // valid data.
-    if (!on_data_received_cb_.is_null())
+    if (!data_received_cb_.is_null())
       request_data_cb_.Run();
     else
       is_requesting_demuxer_data_ = false;
@@ -71,19 +79,25 @@ void MediaDecoderJob::OnDataReceived(const DemuxerData& data) {
   access_unit_index_[next_demuxer_data_index] = 0;
   is_requesting_demuxer_data_ = false;
 
-  base::Closure done_cb = base::ResetAndReturn(&on_data_received_cb_);
+  base::Closure done_cb = base::ResetAndReturn(&data_received_cb_);
+
+  // If this data request is for the inactive chunk, or |data_received_cb_|
+  // was set to null by Flush() or Release(), do nothing.
+  if (done_cb.is_null())
+    return;
+
   if (stop_decode_pending_) {
-    OnDecodeCompleted(MEDIA_CODEC_STOPPED, kNoTimestamp(), 0);
+    DCHECK(is_decoding());
+    OnDecodeCompleted(MEDIA_CODEC_STOPPED, kNoTimestamp(), kNoTimestamp());
     return;
   }
 
-  if (!done_cb.is_null())
-    done_cb.Run();
+  done_cb.Run();
 }
 
 void MediaDecoderJob::Prefetch(const base::Closure& prefetch_cb) {
   DCHECK(ui_task_runner_->BelongsToCurrentThread());
-  DCHECK(on_data_received_cb_.is_null());
+  DCHECK(data_received_cb_.is_null());
   DCHECK(decode_cb_.is_null());
 
   if (HasData()) {
@@ -101,9 +115,23 @@ bool MediaDecoderJob::Decode(
     base::TimeDelta start_presentation_timestamp,
     const DecoderCallback& callback) {
   DCHECK(decode_cb_.is_null());
-  DCHECK(on_data_received_cb_.is_null());
+  DCHECK(data_received_cb_.is_null());
   DCHECK(ui_task_runner_->BelongsToCurrentThread());
 
+  if (!media_codec_bridge_ || need_to_reconfig_decoder_job_) {
+    need_to_reconfig_decoder_job_ = !CreateMediaCodecBridge();
+    if (drain_decoder_) {
+      // Decoder has been recreated, stop draining.
+      drain_decoder_ = false;
+      input_eos_encountered_ = false;
+      output_eos_encountered_ = false;
+      access_unit_index_[current_demuxer_data_index_]++;
+    }
+    skip_eos_enqueue_ = true;
+    if (need_to_reconfig_decoder_job_)
+      return false;
+  }
+
   decode_cb_ = callback;
 
   if (!HasData()) {
@@ -114,13 +142,6 @@ bool MediaDecoderJob::Decode(
     return true;
   }
 
-  if (DemuxerStream::kConfigChanged == CurrentAccessUnit().status) {
-    // Clear received data because we need to handle a config change.
-    decode_cb_.Reset();
-    ClearData();
-    return false;
-  }
-
   DecodeCurrentAccessUnit(start_time_ticks, start_presentation_timestamp);
   return true;
 }
@@ -131,12 +152,32 @@ void MediaDecoderJob::StopDecode() {
   stop_decode_pending_ = true;
 }
 
+bool MediaDecoderJob::OutputEOSReached() const {
+  return !drain_decoder_ && output_eos_encountered_;
+}
+
+void MediaDecoderJob::SetDrmBridge(MediaDrmBridge* drm_bridge) {
+  drm_bridge_ = drm_bridge;
+  need_to_reconfig_decoder_job_ = true;
+}
+
 void MediaDecoderJob::Flush() {
+  DVLOG(1) << __FUNCTION__;
+  DCHECK(ui_task_runner_->BelongsToCurrentThread());
+  DCHECK(data_received_cb_.is_null());
   DCHECK(decode_cb_.is_null());
 
+  // Clean up the received data.
+  current_demuxer_data_index_ = 0;
+  InitializeReceivedData();
+  if (is_requesting_demuxer_data_)
+    is_incoming_data_invalid_ = true;
+  input_eos_encountered_ = false;
+  output_eos_encountered_ = false;
+  drain_decoder_ = false;
+
   // Do nothing, flush when the next Decode() happens.
   needs_flush_ = true;
-  ClearData();
 }
 
 void MediaDecoderJob::BeginPrerolling(base::TimeDelta preroll_timestamp) {
@@ -148,16 +189,37 @@ void MediaDecoderJob::BeginPrerolling(base::TimeDelta preroll_timestamp) {
   prerolling_ = true;
 }
 
+void MediaDecoderJob::ReleaseDecoderResources() {
+  DVLOG(1) << __FUNCTION__;
+  DCHECK(ui_task_runner_->BelongsToCurrentThread());
+  if (decode_cb_.is_null()) {
+    DCHECK(!drain_decoder_);
+    // Since the decoder job is not decoding data, we can safely destroy
+    // |media_codec_bridge_|.
+    ReleaseMediaCodecBridge();
+    return;
+  }
+
+  // Release |media_codec_bridge_| once decoding is completed.
+  release_resources_pending_ = true;
+}
+
+base::android::ScopedJavaLocalRef<jobject> MediaDecoderJob::GetMediaCrypto() {
+  base::android::ScopedJavaLocalRef<jobject> media_crypto;
+  if (drm_bridge_)
+    media_crypto = drm_bridge_->GetMediaCrypto();
+  return media_crypto;
+}
+
 void MediaDecoderJob::Release() {
   DCHECK(ui_task_runner_->BelongsToCurrentThread());
   DVLOG(1) << __FUNCTION__;
 
-  // If the decoder job is not waiting for data, and is still decoding, we
-  // cannot delete the job immediately.
-  destroy_pending_ = on_data_received_cb_.is_null() && is_decoding();
+  // If the decoder job is still decoding, we cannot delete the job immediately.
+  destroy_pending_ = is_decoding();
 
   request_data_cb_.Reset();
-  on_data_received_cb_.Reset();
+  data_received_cb_.Reset();
   decode_cb_.Reset();
 
   if (destroy_pending_) {
@@ -221,8 +283,9 @@ MediaCodecStatus MediaDecoderJob::QueueInputBuffer(const AccessUnit& unit) {
 bool MediaDecoderJob::HasData() const {
   DCHECK(ui_task_runner_->BelongsToCurrentThread());
   // When |input_eos_encountered_| is set, |access_unit_index_| and
-  // |current_demuxer_data_index_| must be pointing to an EOS unit.
-  // We'll reuse this unit to flush the decoder until we hit output EOS.
+  // |current_demuxer_data_index_| must be pointing to an EOS unit,
+  // or a |kConfigChanged| unit if |drain_decoder_| is true. In both cases,
+  // we'll feed an EOS input unit to drain the decoder until we hit output EOS.
   DCHECK(!input_eos_encountered_ || !NoAccessUnitsRemainingInChunk(true));
   return !NoAccessUnitsRemainingInChunk(true) ||
       !NoAccessUnitsRemainingInChunk(false);
@@ -231,13 +294,13 @@ bool MediaDecoderJob::HasData() const {
 void MediaDecoderJob::RequestData(const base::Closure& done_cb) {
   DVLOG(1) << __FUNCTION__;
   DCHECK(ui_task_runner_->BelongsToCurrentThread());
-  DCHECK(on_data_received_cb_.is_null());
+  DCHECK(data_received_cb_.is_null());
   DCHECK(!input_eos_encountered_);
   DCHECK(NoAccessUnitsRemainingInChunk(false));
 
   TRACE_EVENT_ASYNC_BEGIN0("media", "MediaDecoderJob::RequestData", this);
 
-  on_data_received_cb_ = done_cb;
+  data_received_cb_ = done_cb;
 
   // If we are already expecting new data, just set the callback and do
   // nothing.
@@ -262,21 +325,34 @@ void MediaDecoderJob::DecodeCurrentAccessUnit(
 
   RequestCurrentChunkIfEmpty();
   const AccessUnit& access_unit = CurrentAccessUnit();
-  // If the first access unit is a config change, request the player to dequeue
-  // the input buffer again so that it can request config data.
-  if (access_unit.status == DemuxerStream::kConfigChanged) {
-    ui_task_runner_->PostTask(FROM_HERE,
-                              base::Bind(&MediaDecoderJob::OnDecodeCompleted,
-                                         base::Unretained(this),
-                                         MEDIA_CODEC_DEQUEUE_INPUT_AGAIN_LATER,
-                                         kNoTimestamp(),
-                                         0));
-    return;
+  if (CurrentAccessUnit().status == DemuxerStream::kConfigChanged) {
+    int index = CurrentReceivedDataChunkIndex();
+    const DemuxerConfigs& configs = received_data_[index].demuxer_configs[0];
+    bool reconfigure_needed = IsCodecReconfigureNeeded(configs);
+    SetDemuxerConfigs(configs);
+    if (!drain_decoder_) {
+      // If we haven't decoded any data yet, just skip the current access unit
+      // and request the MediaCodec to be recreated on next Decode().
+      if (skip_eos_enqueue_ || !reconfigure_needed) {
+        need_to_reconfig_decoder_job_ =
+            need_to_reconfig_decoder_job_ || reconfigure_needed;
+        // Report MEDIA_CODEC_OK status so decoder will continue decoding and
+        // MEDIA_CODEC_OUTPUT_FORMAT_CHANGED status will come later.
+        ui_task_runner_->PostTask(FROM_HERE, base::Bind(
+            &MediaDecoderJob::OnDecodeCompleted, base::Unretained(this),
+            MEDIA_CODEC_OK, kNoTimestamp(), kNoTimestamp()));
+        return;
+      }
+      // Start draining the decoder so that all the remaining frames are
+      // rendered.
+      drain_decoder_ = true;
+    }
   }
 
+  DCHECK(!(needs_flush_ && drain_decoder_));
   decoder_task_runner_->PostTask(FROM_HERE, base::Bind(
       &MediaDecoderJob::DecodeInternal, base::Unretained(this),
-      access_unit,
+      drain_decoder_ ? eos_unit_ : access_unit,
       start_time_ticks, start_presentation_timestamp, needs_flush_,
       media::BindToCurrentLoop(base::Bind(
           &MediaDecoderJob::OnDecodeCompleted, base::Unretained(this)))));
@@ -299,7 +375,7 @@ void MediaDecoderJob::DecodeInternal(
     output_eos_encountered_ = false;
     MediaCodecStatus reset_status = media_codec_bridge_->Reset();
     if (MEDIA_CODEC_OK != reset_status) {
-      callback.Run(reset_status, kNoTimestamp(), 0);
+      callback.Run(reset_status, kNoTimestamp(), kNoTimestamp());
       return;
     }
   }
@@ -312,7 +388,7 @@ void MediaDecoderJob::DecodeInternal(
   // For aborted access unit, just skip it and inform the player.
   if (unit.status == DemuxerStream::kAborted) {
     // TODO(qinmin): use a new enum instead of MEDIA_CODEC_STOPPED.
-    callback.Run(MEDIA_CODEC_STOPPED, kNoTimestamp(), 0);
+    callback.Run(MEDIA_CODEC_STOPPED, kNoTimestamp(), kNoTimestamp());
     return;
   }
 
@@ -320,7 +396,8 @@ void MediaDecoderJob::DecodeInternal(
     if (unit.end_of_stream || unit.data.empty()) {
       input_eos_encountered_ = true;
       output_eos_encountered_ = true;
-      callback.Run(MEDIA_CODEC_OUTPUT_END_OF_STREAM, kNoTimestamp(), 0);
+      callback.Run(MEDIA_CODEC_OUTPUT_END_OF_STREAM, kNoTimestamp(),
+                   kNoTimestamp());
       return;
     }
 
@@ -333,7 +410,7 @@ void MediaDecoderJob::DecodeInternal(
     if (input_status == MEDIA_CODEC_INPUT_END_OF_STREAM) {
       input_eos_encountered_ = true;
     } else if (input_status != MEDIA_CODEC_OK) {
-      callback.Run(input_status, kNoTimestamp(), 0);
+      callback.Run(input_status, kNoTimestamp(), kNoTimestamp());
       return;
     }
   }
@@ -346,29 +423,40 @@ void MediaDecoderJob::DecodeInternal(
   base::TimeDelta timeout = base::TimeDelta::FromMilliseconds(
       kMediaCodecTimeoutInMilliseconds);
 
-  MediaCodecStatus status =
-      media_codec_bridge_->DequeueOutputBuffer(timeout,
-                                               &buffer_index,
-                                               &offset,
-                                               &size,
-                                               &presentation_timestamp,
-                                               &output_eos_encountered_,
-                                               NULL);
-
-  if (status != MEDIA_CODEC_OK) {
+  MediaCodecStatus status = MEDIA_CODEC_OK;
+  bool has_format_change = false;
+  // Dequeue the output buffer until a MEDIA_CODEC_OK, MEDIA_CODEC_ERROR or
+  // MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER is received.
+  do {
+    status = media_codec_bridge_->DequeueOutputBuffer(
+        timeout,
+        &buffer_index,
+        &offset,
+        &size,
+        &presentation_timestamp,
+        &output_eos_encountered_,
+        NULL);
     if (status == MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED &&
         !media_codec_bridge_->GetOutputBuffers()) {
       status = MEDIA_CODEC_ERROR;
+    } else if (status == MEDIA_CODEC_OUTPUT_FORMAT_CHANGED) {
+      // TODO(qinmin): instead of waiting for the next output buffer to be
+      // dequeued, post a task on the UI thread to signal the format change.
+      has_format_change = true;
     }
-    callback.Run(status, kNoTimestamp(), 0);
+  } while (status != MEDIA_CODEC_OK && status != MEDIA_CODEC_ERROR &&
+           status != MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER);
+
+  if (status != MEDIA_CODEC_OK) {
+    callback.Run(status, kNoTimestamp(), kNoTimestamp());
     return;
   }
 
   // TODO(xhwang/qinmin): This logic is correct but strange. Clean it up.
   if (output_eos_encountered_)
     status = MEDIA_CODEC_OUTPUT_END_OF_STREAM;
-  else if (input_status == MEDIA_CODEC_INPUT_END_OF_STREAM)
-    status = MEDIA_CODEC_INPUT_END_OF_STREAM;
+  else if (has_format_change)
+    status = MEDIA_CODEC_OUTPUT_FORMAT_CHANGED;
 
   bool render_output  = presentation_timestamp >= preroll_timestamp_ &&
       (status != MEDIA_CODEC_OUTPUT_END_OF_STREAM || size != 0u);
@@ -383,11 +471,12 @@ void MediaDecoderJob::DecodeInternal(
     decoder_task_runner_->PostDelayedTask(
         FROM_HERE,
         base::Bind(&MediaDecoderJob::ReleaseOutputBuffer,
-                   weak_factory_.GetWeakPtr(),
+                   base::Unretained(this),
                    buffer_index,
                    size,
                    render_output,
-                   base::Bind(callback, status, presentation_timestamp)),
+                   presentation_timestamp,
+                   base::Bind(callback, status)),
         time_to_render);
     return;
   }
@@ -406,13 +495,14 @@ void MediaDecoderJob::DecodeInternal(
     presentation_timestamp = kNoTimestamp();
   }
   ReleaseOutputCompletionCallback completion_callback = base::Bind(
-      callback, status, presentation_timestamp);
-  ReleaseOutputBuffer(buffer_index, size, render_output, completion_callback);
+      callback, status);
+  ReleaseOutputBuffer(buffer_index, size, render_output, presentation_timestamp,
+                      completion_callback);
 }
 
 void MediaDecoderJob::OnDecodeCompleted(
-    MediaCodecStatus status, base::TimeDelta presentation_timestamp,
-    size_t audio_output_bytes) {
+    MediaCodecStatus status, base::TimeDelta current_presentation_timestamp,
+    base::TimeDelta max_presentation_timestamp) {
   DCHECK(ui_task_runner_->BelongsToCurrentThread());
 
   if (destroy_pending_) {
@@ -421,20 +511,25 @@ void MediaDecoderJob::OnDecodeCompleted(
     return;
   }
 
+  if (status == MEDIA_CODEC_OUTPUT_END_OF_STREAM)
+    output_eos_encountered_ = true;
+
   DCHECK(!decode_cb_.is_null());
 
   // If output was queued for rendering, then we have completed prerolling.
-  if (presentation_timestamp != kNoTimestamp())
+  if (current_presentation_timestamp != kNoTimestamp())
     prerolling_ = false;
 
   switch (status) {
     case MEDIA_CODEC_OK:
     case MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER:
-    case MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED:
     case MEDIA_CODEC_OUTPUT_FORMAT_CHANGED:
     case MEDIA_CODEC_OUTPUT_END_OF_STREAM:
-      if (!input_eos_encountered_)
+      if (!input_eos_encountered_) {
+        CurrentDataConsumed(
+            CurrentAccessUnit().status == DemuxerStream::kConfigChanged);
         access_unit_index_[current_demuxer_data_index_]++;
+      }
       break;
 
     case MEDIA_CODEC_DEQUEUE_INPUT_AGAIN_LATER:
@@ -444,21 +539,47 @@ void MediaDecoderJob::OnDecodeCompleted(
     case MEDIA_CODEC_ERROR:
       // Do nothing.
       break;
+
+    case MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED:
+      DCHECK(false) << "Invalid output status";
+      break;
   };
 
+  if (status == MEDIA_CODEC_OUTPUT_END_OF_STREAM && drain_decoder_) {
+    OnDecoderDrained();
+    status = MEDIA_CODEC_OK;
+  }
+
+  if (status == MEDIA_CODEC_OUTPUT_FORMAT_CHANGED) {
+    if (UpdateOutputFormat())
+      config_changed_cb_.Run();
+    status = MEDIA_CODEC_OK;
+  }
+
+  if (release_resources_pending_) {
+    ReleaseMediaCodecBridge();
+    release_resources_pending_ = false;
+    if (drain_decoder_)
+      OnDecoderDrained();
+  }
+
   stop_decode_pending_ = false;
-  base::ResetAndReturn(&decode_cb_).Run(status, presentation_timestamp,
-                                        audio_output_bytes);
+  base::ResetAndReturn(&decode_cb_).Run(
+      status, current_presentation_timestamp, max_presentation_timestamp);
 }
 
 const AccessUnit& MediaDecoderJob::CurrentAccessUnit() const {
   DCHECK(ui_task_runner_->BelongsToCurrentThread());
   DCHECK(HasData());
-  int index = NoAccessUnitsRemainingInChunk(true) ?
-      inactive_demuxer_data_index() : current_demuxer_data_index_;
+  size_t index = CurrentReceivedDataChunkIndex();
   return received_data_[index].access_units[access_unit_index_[index]];
 }
 
+size_t MediaDecoderJob::CurrentReceivedDataChunkIndex() const {
+  return NoAccessUnitsRemainingInChunk(true) ?
+      inactive_demuxer_data_index() : current_demuxer_data_index_;
+}
+
 bool MediaDecoderJob::NoAccessUnitsRemainingInChunk(
     bool is_active_chunk) const {
   DCHECK(ui_task_runner_->BelongsToCurrentThread());
@@ -467,16 +588,6 @@ bool MediaDecoderJob::NoAccessUnitsRemainingInChunk(
   return received_data_[index].access_units.size() <= access_unit_index_[index];
 }
 
-void MediaDecoderJob::ClearData() {
-  DCHECK(ui_task_runner_->BelongsToCurrentThread());
-  current_demuxer_data_index_ = 0;
-  InitializeReceivedData();
-  on_data_received_cb_.Reset();
-  if (is_requesting_demuxer_data_)
-    is_incoming_data_invalid_ = true;
-  input_eos_encountered_ = false;
-}
-
 void MediaDecoderJob::RequestCurrentChunkIfEmpty() {
   DCHECK(ui_task_runner_->BelongsToCurrentThread());
   DCHECK(HasData());
@@ -488,7 +599,6 @@ void MediaDecoderJob::RequestCurrentChunkIfEmpty() {
   const AccessUnit last_access_unit =
       received_data_[current_demuxer_data_index_].access_units.back();
   if (!last_access_unit.end_of_stream &&
-      last_access_unit.status != DemuxerStream::kConfigChanged &&
       last_access_unit.status != DemuxerStream::kAborted) {
     RequestData(base::Closure());
   }
@@ -501,4 +611,62 @@ void MediaDecoderJob::InitializeReceivedData() {
   }
 }
 
+void MediaDecoderJob::OnDecoderDrained() {
+  DVLOG(1) << __FUNCTION__;
+  DCHECK(ui_task_runner_->BelongsToCurrentThread());
+  DCHECK(drain_decoder_);
+
+  input_eos_encountered_ = false;
+  output_eos_encountered_ = false;
+  drain_decoder_ = false;
+  ReleaseMediaCodecBridge();
+  // Increase the access unit index so that the new decoder will not handle
+  // the config change again.
+  access_unit_index_[current_demuxer_data_index_]++;
+  CurrentDataConsumed(true);
+}
+
+bool MediaDecoderJob::CreateMediaCodecBridge() {
+  DVLOG(1) << __FUNCTION__;
+  DCHECK(ui_task_runner_->BelongsToCurrentThread());
+  DCHECK(decode_cb_.is_null());
+
+  if (!HasStream()) {
+    ReleaseMediaCodecBridge();
+    return false;
+  }
+
+  // Create |media_codec_bridge_| only if config changes.
+  if (media_codec_bridge_ && !need_to_reconfig_decoder_job_)
+    return true;
+
+  base::android::ScopedJavaLocalRef<jobject> media_crypto = GetMediaCrypto();
+  if (is_content_encrypted_ && media_crypto.is_null())
+    return false;
+
+  ReleaseMediaCodecBridge();
+  DVLOG(1) << __FUNCTION__ << " : creating new media codec bridge";
+
+  return CreateMediaCodecBridgeInternal();
+}
+
+bool MediaDecoderJob::IsCodecReconfigureNeeded(
+    const DemuxerConfigs& configs) const {
+  if (!AreDemuxerConfigsChanged(configs))
+    return false;
+  return true;
+}
+
+bool MediaDecoderJob::UpdateOutputFormat() {
+  return false;
+}
+
+void MediaDecoderJob::ReleaseMediaCodecBridge() {
+  if (!media_codec_bridge_)
+    return;
+
+  media_codec_bridge_.reset();
+  input_buf_index_ = -1;
+}
+
 }  // namespace media