diff options
author | acolwell@chromium.org <acolwell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-08-23 02:16:10 +0000 |
---|---|---|
committer | acolwell@chromium.org <acolwell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-08-23 02:16:10 +0000 |
commit | b506845ad558d82326d37336a4801ae8dc25f2da (patch) | |
tree | 64f5306d4d200fd9dd2d0b4180c7cc42c856d835 /media/base | |
parent | d791ad1ba1cd9246b83ecf3a8622006706fb9605 (diff) | |
download | chromium_src-b506845ad558d82326d37336a4801ae8dc25f2da.zip chromium_src-b506845ad558d82326d37336a4801ae8dc25f2da.tar.gz chromium_src-b506845ad558d82326d37336a4801ae8dc25f2da.tar.bz2 |
Move MediaDecoderJob functionality out into separate files and refactor.
Minor code changes were also made to move audio or video
specific functionality out of the base class and into the
derived classes. No functionality changes were introduced.
Code is just being moved around.
TEST=All existing tests still pass.
Review URL: https://chromiumcodereview.appspot.com/23003019
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@219188 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media/base')
-rw-r--r-- | media/base/android/audio_decoder_job.cc | 73 | ||||
-rw-r--r-- | media/base/android/audio_decoder_job.h | 52 | ||||
-rw-r--r-- | media/base/android/media_decoder_job.cc | 201 | ||||
-rw-r--r-- | media/base/android/media_decoder_job.h | 121 | ||||
-rw-r--r-- | media/base/android/media_source_player.cc | 299 | ||||
-rw-r--r-- | media/base/android/media_source_player.h | 105 | ||||
-rw-r--r-- | media/base/android/video_decoder_job.cc | 63 | ||||
-rw-r--r-- | media/base/android/video_decoder_job.h | 48 |
8 files changed, 564 insertions, 398 deletions
diff --git a/media/base/android/audio_decoder_job.cc b/media/base/android/audio_decoder_job.cc new file mode 100644 index 0000000..5c6842a --- /dev/null +++ b/media/base/android/audio_decoder_job.cc @@ -0,0 +1,73 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/base/android/audio_decoder_job.h" + +#include "base/bind.h" +#include "base/lazy_instance.h" +#include "base/threading/thread.h" +#include "media/base/android/media_codec_bridge.h" + +namespace media { + +class AudioDecoderThread : public base::Thread { + public: + AudioDecoderThread() : base::Thread("MediaSource_AudioDecoderThread") { + Start(); + } +}; + +// TODO(qinmin): Check if it is tolerable to use worker pool to handle all the +// decoding tasks so that we don't need a global thread here. +// http://crbug.com/245750 +base::LazyInstance<AudioDecoderThread>::Leaky + g_audio_decoder_thread = LAZY_INSTANCE_INITIALIZER; + +AudioDecoderJob* AudioDecoderJob::Create( + const AudioCodec audio_codec, + int sample_rate, + int channel_count, + const uint8* extra_data, + size_t extra_data_size, + jobject media_crypto) { + scoped_ptr<AudioCodecBridge> codec(AudioCodecBridge::Create(audio_codec)); + if (codec && codec->Start(audio_codec, sample_rate, channel_count, extra_data, + extra_data_size, true, media_crypto)) { + return new AudioDecoderJob(codec.Pass()); + } + return NULL; +} + +AudioDecoderJob::AudioDecoderJob( + scoped_ptr<AudioCodecBridge> audio_codec_bridge) + : MediaDecoderJob(g_audio_decoder_thread.Pointer()->message_loop_proxy(), + audio_codec_bridge.get()), + audio_codec_bridge_(audio_codec_bridge.Pass()) { +} + +AudioDecoderJob::~AudioDecoderJob() { +} + +void AudioDecoderJob::SetVolume(double volume) { + audio_codec_bridge_->SetVolume(volume); +} + +void AudioDecoderJob::ReleaseOutputBuffer( + int outputBufferIndex, size_t size, + const base::TimeDelta& presentation_timestamp, + const MediaDecoderJob::DecoderCallback& callback, + DecodeStatus status) { + audio_codec_bridge_->PlayOutputBuffer(outputBufferIndex, size); + + if (status != DECODE_OUTPUT_END_OF_STREAM || size != 0u) + audio_codec_bridge_->ReleaseOutputBuffer(outputBufferIndex, false); + + callback.Run(status, presentation_timestamp, size); +} + +bool AudioDecoderJob::ComputeTimeToRender() const { + return false; +} + +} // namespace media diff --git a/media/base/android/audio_decoder_job.h b/media/base/android/audio_decoder_job.h new file mode 100644 index 0000000..171dc4e --- /dev/null +++ b/media/base/android/audio_decoder_job.h @@ -0,0 +1,52 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_BASE_ANDROID_AUDIO_DECODER_JOB_H_ +#define MEDIA_BASE_ANDROID_AUDIO_DECODER_JOB_H_ + +#include <jni.h> + +#include "media/base/android/media_decoder_job.h" + +namespace media { + +class AudioCodecBridge; + +// Class for managing audio decoding jobs. +class AudioDecoderJob : public MediaDecoderJob { + public: + virtual ~AudioDecoderJob(); + + // Creates a new AudioDecoderJob instance for decoding audio. + // |audio_codec| - The audio format the object needs to decode. + // |sample_rate| - The sample rate of the decoded output. + // |channel_count| - The number of channels in the decoded output. + // |extra_data|, |extra_data_size| - Extra data buffer needed for initializing + // the decoder. + // |media_crypto| - Handle to a Java object that handles the encryption for + // the audio data. + static AudioDecoderJob* Create( + const AudioCodec audio_codec, int sample_rate, int channel_count, + const uint8* extra_data, size_t extra_data_size, jobject media_crypto); + + void SetVolume(double volume); + + private: + AudioDecoderJob(scoped_ptr<AudioCodecBridge> audio_decoder_bridge); + + // MediaDecoderJob implementation. + virtual void ReleaseOutputBuffer( + int outputBufferIndex, size_t size, + const base::TimeDelta& presentation_timestamp, + const MediaDecoderJob::DecoderCallback& callback, + DecodeStatus status) OVERRIDE; + + virtual bool ComputeTimeToRender() const OVERRIDE; + + scoped_ptr<AudioCodecBridge> audio_codec_bridge_; +}; + +} // namespace media + +#endif // MEDIA_BASE_ANDROID_AUDIO_DECODER_JOB_H_ diff --git a/media/base/android/media_decoder_job.cc b/media/base/android/media_decoder_job.cc new file mode 100644 index 0000000..c29eaf9 --- /dev/null +++ b/media/base/android/media_decoder_job.cc @@ -0,0 +1,201 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/base/android/media_decoder_job.h" + +#include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "media/base/android/media_codec_bridge.h" +#include "media/base/bind_to_loop.h" + +namespace media { + +// Timeout value for media codec operations. Because the first +// DequeInputBuffer() can take about 150 milliseconds, use 250 milliseconds +// here. See http://b/9357571. +static const int kMediaCodecTimeoutInMilliseconds = 250; + +MediaDecoderJob::MediaDecoderJob( + const scoped_refptr<base::MessageLoopProxy>& decoder_loop, + MediaCodecBridge* media_codec_bridge) + : ui_loop_(base::MessageLoopProxy::current()), + decoder_loop_(decoder_loop), + media_codec_bridge_(media_codec_bridge), + needs_flush_(false), + input_eos_encountered_(false), + weak_this_(this), + is_decoding_(false) { +} + +MediaDecoderJob::~MediaDecoderJob() {} + +void MediaDecoderJob::Decode( + const AccessUnit& unit, + const base::TimeTicks& start_time_ticks, + const base::TimeDelta& start_presentation_timestamp, + const MediaDecoderJob::DecoderCallback& callback) { + DCHECK(!is_decoding_); + DCHECK(ui_loop_->BelongsToCurrentThread()); + is_decoding_ = true; + decoder_loop_->PostTask(FROM_HERE, base::Bind( + &MediaDecoderJob::DecodeInternal, base::Unretained(this), unit, + start_time_ticks, start_presentation_timestamp, needs_flush_, + media::BindToLoop(ui_loop_, callback))); + needs_flush_ = false; +} + +MediaDecoderJob::DecodeStatus MediaDecoderJob::QueueInputBuffer( + const AccessUnit& unit) { + base::TimeDelta timeout = base::TimeDelta::FromMilliseconds( + kMediaCodecTimeoutInMilliseconds); + int input_buf_index = media_codec_bridge_->DequeueInputBuffer(timeout); + if (input_buf_index == MediaCodecBridge::INFO_MEDIA_CODEC_ERROR) + return DECODE_FAILED; + if (input_buf_index == MediaCodecBridge::INFO_TRY_AGAIN_LATER) + return DECODE_TRY_ENQUEUE_INPUT_AGAIN_LATER; + + // TODO(qinmin): skip frames if video is falling far behind. + DCHECK(input_buf_index >= 0); + if (unit.end_of_stream || unit.data.empty()) { + media_codec_bridge_->QueueEOS(input_buf_index); + return DECODE_INPUT_END_OF_STREAM; + } + if (unit.key_id.empty()) { + media_codec_bridge_->QueueInputBuffer( + input_buf_index, &unit.data[0], unit.data.size(), unit.timestamp); + } else { + if (unit.iv.empty() || unit.subsamples.empty()) { + LOG(ERROR) << "The access unit doesn't have iv or subsamples while it " + << "has key IDs!"; + return DECODE_FAILED; + } + media_codec_bridge_->QueueSecureInputBuffer( + input_buf_index, &unit.data[0], unit.data.size(), + reinterpret_cast<const uint8*>(&unit.key_id[0]), unit.key_id.size(), + reinterpret_cast<const uint8*>(&unit.iv[0]), unit.iv.size(), + &unit.subsamples[0], unit.subsamples.size(), unit.timestamp); + } + + return DECODE_SUCCEEDED; +} + +void MediaDecoderJob::DecodeInternal( + const AccessUnit& unit, + const base::TimeTicks& start_time_ticks, + const base::TimeDelta& start_presentation_timestamp, + bool needs_flush, + const MediaDecoderJob::DecoderCallback& callback) { + if (needs_flush) { + DVLOG(1) << "DecodeInternal needs flush."; + input_eos_encountered_ = false; + media_codec_bridge_->Reset(); + } + + DecodeStatus decode_status = DECODE_INPUT_END_OF_STREAM; + if (!input_eos_encountered_) { + decode_status = QueueInputBuffer(unit); + if (decode_status == DECODE_INPUT_END_OF_STREAM) { + input_eos_encountered_ = true; + } else if (decode_status != DECODE_SUCCEEDED) { + callback.Run(decode_status, start_presentation_timestamp, 0); + return; + } + } + + size_t offset = 0; + size_t size = 0; + base::TimeDelta presentation_timestamp; + bool end_of_stream = false; + + base::TimeDelta timeout = base::TimeDelta::FromMilliseconds( + kMediaCodecTimeoutInMilliseconds); + int output_buffer_index = media_codec_bridge_->DequeueOutputBuffer( + timeout, &offset, &size, &presentation_timestamp, &end_of_stream); + + if (end_of_stream) + decode_status = DECODE_OUTPUT_END_OF_STREAM; + + if (output_buffer_index < 0) { + MediaCodecBridge::DequeueBufferInfo buffer_info = + static_cast<MediaCodecBridge::DequeueBufferInfo>(output_buffer_index); + switch (buffer_info) { + case MediaCodecBridge::INFO_OUTPUT_BUFFERS_CHANGED: + DCHECK_NE(decode_status, DECODE_INPUT_END_OF_STREAM); + media_codec_bridge_->GetOutputBuffers(); + break; + case MediaCodecBridge::INFO_OUTPUT_FORMAT_CHANGED: + DCHECK_NE(decode_status, DECODE_INPUT_END_OF_STREAM); + // TODO(qinmin): figure out what we should do if format changes. + decode_status = DECODE_FORMAT_CHANGED; + break; + case MediaCodecBridge::INFO_TRY_AGAIN_LATER: + decode_status = DECODE_TRY_DEQUEUE_OUTPUT_AGAIN_LATER; + break; + case MediaCodecBridge::INFO_MEDIA_CODEC_ERROR: + decode_status = DECODE_FAILED; + break; + } + } else { + base::TimeDelta time_to_render; + DCHECK(!start_time_ticks.is_null()); + if (ComputeTimeToRender()) { + time_to_render = presentation_timestamp - (base::TimeTicks::Now() - + start_time_ticks + start_presentation_timestamp); + } + + // TODO(acolwell): Change to > since the else will never run for audio. + if (time_to_render >= base::TimeDelta()) { + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&MediaDecoderJob::ReleaseOutputBuffer, + weak_this_.GetWeakPtr(), output_buffer_index, size, + presentation_timestamp, callback, decode_status), + time_to_render); + } else { + // TODO(qinmin): The codec is lagging behind, need to recalculate the + // |start_presentation_timestamp_| and |start_time_ticks_|. + DVLOG(1) << "codec is lagging behind :" + << time_to_render.InMicroseconds(); + ReleaseOutputBuffer(output_buffer_index, size, presentation_timestamp, + callback, decode_status); + } + + return; + } + callback.Run(decode_status, start_presentation_timestamp, 0); +} + +void MediaDecoderJob::OnDecodeCompleted() { + DCHECK(ui_loop_->BelongsToCurrentThread()); + is_decoding_ = false; +} + +void MediaDecoderJob::Flush() { + // Do nothing, flush when the next Decode() happens. + needs_flush_ = true; +} + +void MediaDecoderJob::Release() { + // If |decoding_| is false, there is nothing running on the decoder thread. + // So it is safe to delete the MediaDecoderJob on the UI thread. However, + // if we post a task to the decoder thread to delete object, then we cannot + // immediately pass the surface to a new MediaDecoderJob instance because + // the java surface is still owned by the old object. New decoder creation + // will be blocked on the UI thread until the previous decoder gets deleted. + // This introduces extra latency during config changes, and makes the logic in + // MediaSourcePlayer more complicated. + // + // TODO(qinmin): Figure out the logic to passing the surface to a new + // MediaDecoderJob instance after the previous one gets deleted on the decoder + // thread. + if (is_decoding_ && !decoder_loop_->BelongsToCurrentThread()) { + DCHECK(ui_loop_->BelongsToCurrentThread()); + decoder_loop_->DeleteSoon(FROM_HERE, this); + return; + } + + delete this; +} + +} // namespace media diff --git a/media/base/android/media_decoder_job.h b/media/base/android/media_decoder_job.h new file mode 100644 index 0000000..4a3bd87 --- /dev/null +++ b/media/base/android/media_decoder_job.h @@ -0,0 +1,121 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_BASE_ANDROID_MEDIA_DECODER_JOB_H_ +#define MEDIA_BASE_ANDROID_MEDIA_DECODER_JOB_H_ + +#include "base/callback.h" +#include "base/memory/weak_ptr.h" +#include "base/time/time.h" +#include "media/base/android/demuxer_stream_player_params.h" + +namespace base { +class MessageLoopProxy; +} + +namespace media { + +class MediaCodecBridge; + +// Class for managing all the decoding tasks. Each decoding task will be posted +// onto the same thread. The thread will be stopped once Stop() is called. +class MediaDecoderJob { + public: + enum DecodeStatus { + DECODE_SUCCEEDED, + DECODE_TRY_ENQUEUE_INPUT_AGAIN_LATER, + DECODE_TRY_DEQUEUE_OUTPUT_AGAIN_LATER, + DECODE_FORMAT_CHANGED, + DECODE_INPUT_END_OF_STREAM, + DECODE_OUTPUT_END_OF_STREAM, + DECODE_FAILED, + }; + + struct Deleter { + inline void operator()(MediaDecoderJob* ptr) const { ptr->Release(); } + }; + + virtual ~MediaDecoderJob(); + + // Callback when a decoder job finishes its work. Args: whether decode + // finished successfully, presentation time, audio output bytes. + typedef base::Callback<void(DecodeStatus, const base::TimeDelta&, + size_t)> DecoderCallback; + + // Called by MediaSourcePlayer to decode some data. + void Decode(const AccessUnit& unit, + const base::TimeTicks& start_time_ticks, + const base::TimeDelta& start_presentation_timestamp, + const MediaDecoderJob::DecoderCallback& callback); + + // Flush the decoder. + void Flush(); + + // Called on the UI thread to indicate that one decode cycle has completed. + void OnDecodeCompleted(); + + bool is_decoding() const { return is_decoding_; } + + protected: + MediaDecoderJob(const scoped_refptr<base::MessageLoopProxy>& decoder_loop, + MediaCodecBridge* media_codec_bridge); + + // Release the output buffer and render it. + virtual void ReleaseOutputBuffer( + int outputBufferIndex, size_t size, + const base::TimeDelta& presentation_timestamp, + const MediaDecoderJob::DecoderCallback& callback, + DecodeStatus status) = 0; + + // Returns true if the "time to render" needs to be computed for frames in + // this decoder job. + virtual bool ComputeTimeToRender() const = 0; + + private: + // Causes this instance to be deleted on the thread it is bound to. + void Release(); + + DecodeStatus QueueInputBuffer(const AccessUnit& unit); + + // Helper function to decoder data on |thread_|. |unit| contains all the data + // to be decoded. |start_time_ticks| and |start_presentation_timestamp| + // represent the system time and the presentation timestamp when the first + // frame is rendered. We use these information to estimate when the current + // frame should be rendered. If |needs_flush| is true, codec needs to be + // flushed at the beginning of this call. + void DecodeInternal(const AccessUnit& unit, + const base::TimeTicks& start_time_ticks, + const base::TimeDelta& start_presentation_timestamp, + bool needs_flush, + const MediaDecoderJob::DecoderCallback& callback); + + // The UI message loop where callbacks should be dispatched. + scoped_refptr<base::MessageLoopProxy> ui_loop_; + + // The message loop that decoder job runs on. + scoped_refptr<base::MessageLoopProxy> decoder_loop_; + + // The media codec bridge used for decoding. Owned by derived class. + // NOTE: This MUST NOT be accessed in the destructor. + MediaCodecBridge* media_codec_bridge_; + + // Whether the decoder needs to be flushed. + bool needs_flush_; + + // Whether input EOS is encountered. + bool input_eos_encountered_; + + // Weak pointer passed to media decoder jobs for callbacks. It is bounded to + // the decoder thread. + base::WeakPtrFactory<MediaDecoderJob> weak_this_; + + // Whether the decoder is actively decoding data. + bool is_decoding_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(MediaDecoderJob); +}; + +} // namespace media + +#endif // MEDIA_BASE_ANDROID_MEDIA_DECODER_JOB_H_ diff --git a/media/base/android/media_source_player.cc b/media/base/android/media_source_player.cc index 19dc446..e0fd9c7 100644 --- a/media/base/android/media_source_player.cc +++ b/media/base/android/media_source_player.cc @@ -8,317 +8,22 @@ #include "base/android/jni_string.h" #include "base/basictypes.h" #include "base/bind.h" -#include "base/lazy_instance.h" #include "base/logging.h" -#include "base/message_loop/message_loop.h" -#include "base/threading/thread.h" -#include "media/base/android/media_codec_bridge.h" +#include "media/base/android/audio_decoder_job.h" #include "media/base/android/media_drm_bridge.h" #include "media/base/android/media_player_manager.h" +#include "media/base/android/video_decoder_job.h" #include "media/base/audio_timestamp_helper.h" namespace { -// Timeout value for media codec operations. Because the first -// DequeInputBuffer() can take about 150 milliseconds, use 250 milliseconds -// here. See b/9357571. -const int kMediaCodecTimeoutInMilliseconds = 250; - // Use 16bit PCM for audio output. Keep this value in sync with the output // format we passed to AudioTrack in MediaCodecBridge. const int kBytesPerAudioOutputSample = 2; - -class DecoderThread : public base::Thread { - public: - virtual ~DecoderThread() {} - protected: - DecoderThread(const char* name) : base::Thread(name) { Start(); } -}; - -class AudioDecoderThread : public DecoderThread { - public: - AudioDecoderThread() : DecoderThread("MediaSource_AudioDecoderThread") {} -}; - -class VideoDecoderThread : public DecoderThread { - public: - VideoDecoderThread() : DecoderThread("MediaSource_VideoDecoderThread") {} -}; - -// TODO(qinmin): Check if it is tolerable to use worker pool to handle all the -// decoding tasks so that we don't need the global threads here. -// http://crbug.com/245750 -base::LazyInstance<AudioDecoderThread>::Leaky - g_audio_decoder_thread = LAZY_INSTANCE_INITIALIZER; - -base::LazyInstance<VideoDecoderThread>::Leaky - g_video_decoder_thread = LAZY_INSTANCE_INITIALIZER; - } namespace media { -MediaDecoderJob::MediaDecoderJob( - const scoped_refptr<base::MessageLoopProxy>& decoder_loop, - MediaCodecBridge* media_codec_bridge, - bool is_audio) - : ui_loop_(base::MessageLoopProxy::current()), - decoder_loop_(decoder_loop), - media_codec_bridge_(media_codec_bridge), - needs_flush_(false), - is_audio_(is_audio), - input_eos_encountered_(false), - weak_this_(this), - is_decoding_(false) { -} - -MediaDecoderJob::~MediaDecoderJob() {} - -// Class for managing audio decoding jobs. -class AudioDecoderJob : public MediaDecoderJob { - public: - virtual ~AudioDecoderJob() {} - - static AudioDecoderJob* Create( - const AudioCodec audio_codec, int sample_rate, int channel_count, - const uint8* extra_data, size_t extra_data_size, jobject media_crypto); - - void SetVolume(double volume); - - private: - AudioDecoderJob(MediaCodecBridge* media_codec_bridge); -}; - -// Class for managing video decoding jobs. -class VideoDecoderJob : public MediaDecoderJob { - public: - virtual ~VideoDecoderJob() {} - - static VideoDecoderJob* Create( - const VideoCodec video_codec, const gfx::Size& size, jobject surface, - jobject media_crypto); - - private: - VideoDecoderJob(MediaCodecBridge* media_codec_bridge); -}; - -void MediaDecoderJob::Decode( - const AccessUnit& unit, - const base::TimeTicks& start_time_ticks, - const base::TimeDelta& start_presentation_timestamp, - const MediaDecoderJob::DecoderCallback& callback) { - DCHECK(!is_decoding_); - DCHECK(ui_loop_->BelongsToCurrentThread()); - is_decoding_ = true; - decoder_loop_->PostTask(FROM_HERE, base::Bind( - &MediaDecoderJob::DecodeInternal, base::Unretained(this), unit, - start_time_ticks, start_presentation_timestamp, needs_flush_, - callback)); - needs_flush_ = false; -} - -MediaDecoderJob::DecodeStatus MediaDecoderJob::QueueInputBuffer( - const AccessUnit& unit) { - base::TimeDelta timeout = base::TimeDelta::FromMilliseconds( - kMediaCodecTimeoutInMilliseconds); - int input_buf_index = media_codec_bridge_->DequeueInputBuffer(timeout); - if (input_buf_index == MediaCodecBridge::INFO_MEDIA_CODEC_ERROR) - return DECODE_FAILED; - if (input_buf_index == MediaCodecBridge::INFO_TRY_AGAIN_LATER) - return DECODE_TRY_ENQUEUE_INPUT_AGAIN_LATER; - - // TODO(qinmin): skip frames if video is falling far behind. - DCHECK(input_buf_index >= 0); - if (unit.end_of_stream || unit.data.empty()) { - media_codec_bridge_->QueueEOS(input_buf_index); - return DECODE_INPUT_END_OF_STREAM; - } - if (unit.key_id.empty()) { - media_codec_bridge_->QueueInputBuffer( - input_buf_index, &unit.data[0], unit.data.size(), unit.timestamp); - } else { - if (unit.iv.empty() || unit.subsamples.empty()) { - LOG(ERROR) << "The access unit doesn't have iv or subsamples while it " - << "has key IDs!"; - return DECODE_FAILED; - } - media_codec_bridge_->QueueSecureInputBuffer( - input_buf_index, &unit.data[0], unit.data.size(), - reinterpret_cast<const uint8*>(&unit.key_id[0]), unit.key_id.size(), - reinterpret_cast<const uint8*>(&unit.iv[0]), unit.iv.size(), - &unit.subsamples[0], unit.subsamples.size(), unit.timestamp); - } - - return DECODE_SUCCEEDED; -} - -void MediaDecoderJob::DecodeInternal( - const AccessUnit& unit, - const base::TimeTicks& start_time_ticks, - const base::TimeDelta& start_presentation_timestamp, - bool needs_flush, - const MediaDecoderJob::DecoderCallback& callback) { - if (needs_flush) { - DVLOG(1) << "DecodeInternal needs flush."; - input_eos_encountered_ = false; - media_codec_bridge_->Reset(); - } - - DecodeStatus decode_status = DECODE_INPUT_END_OF_STREAM; - if (!input_eos_encountered_) { - decode_status = QueueInputBuffer(unit); - if (decode_status == DECODE_INPUT_END_OF_STREAM) { - input_eos_encountered_ = true; - } else if (decode_status != DECODE_SUCCEEDED) { - ui_loop_->PostTask(FROM_HERE, - base::Bind(callback, decode_status, - start_presentation_timestamp, 0)); - return; - } - } - - size_t offset = 0; - size_t size = 0; - base::TimeDelta presentation_timestamp; - bool end_of_stream = false; - - base::TimeDelta timeout = base::TimeDelta::FromMilliseconds( - kMediaCodecTimeoutInMilliseconds); - int outputBufferIndex = media_codec_bridge_->DequeueOutputBuffer( - timeout, &offset, &size, &presentation_timestamp, &end_of_stream); - - if (end_of_stream) - decode_status = DECODE_OUTPUT_END_OF_STREAM; - switch (outputBufferIndex) { - case MediaCodecBridge::INFO_OUTPUT_BUFFERS_CHANGED: - DCHECK(decode_status != DECODE_INPUT_END_OF_STREAM); - media_codec_bridge_->GetOutputBuffers(); - break; - case MediaCodecBridge::INFO_OUTPUT_FORMAT_CHANGED: - DCHECK(decode_status != DECODE_INPUT_END_OF_STREAM); - // TODO(qinmin): figure out what we should do if format changes. - decode_status = DECODE_FORMAT_CHANGED; - break; - case MediaCodecBridge::INFO_TRY_AGAIN_LATER: - decode_status = DECODE_TRY_DEQUEUE_OUTPUT_AGAIN_LATER; - break; - case MediaCodecBridge::INFO_MEDIA_CODEC_ERROR: - decode_status = DECODE_FAILED; - break; - default: - DCHECK_LE(0, outputBufferIndex); - base::TimeDelta time_to_render; - DCHECK(!start_time_ticks.is_null()); - if (!is_audio_) { - time_to_render = presentation_timestamp - (base::TimeTicks::Now() - - start_time_ticks + start_presentation_timestamp); - } - if (time_to_render >= base::TimeDelta()) { - base::MessageLoop::current()->PostDelayedTask( - FROM_HERE, - base::Bind(&MediaDecoderJob::ReleaseOutputBuffer, - weak_this_.GetWeakPtr(), outputBufferIndex, size, - presentation_timestamp, callback, decode_status), - time_to_render); - } else { - // TODO(qinmin): The codec is lagging behind, need to recalculate the - // |start_presentation_timestamp_| and |start_time_ticks_|. - DVLOG(1) << (is_audio_ ? "audio " : "video ") - << "codec is lagging behind :" << time_to_render.InMicroseconds(); - ReleaseOutputBuffer(outputBufferIndex, size, presentation_timestamp, - callback, decode_status); - } - return; - } - ui_loop_->PostTask(FROM_HERE, base::Bind( - callback, decode_status, start_presentation_timestamp, 0)); -} - -void MediaDecoderJob::ReleaseOutputBuffer( - int outputBufferIndex, size_t size, - const base::TimeDelta& presentation_timestamp, - const MediaDecoderJob::DecoderCallback& callback, DecodeStatus status) { - // TODO(qinmin): Refactor this function. Maybe AudioDecoderJob should provide - // its own ReleaseOutputBuffer(). - if (is_audio_) { - static_cast<AudioCodecBridge*>(media_codec_bridge_.get())->PlayOutputBuffer( - outputBufferIndex, size); - } - if (status != DECODE_OUTPUT_END_OF_STREAM || size != 0u) - media_codec_bridge_->ReleaseOutputBuffer(outputBufferIndex, !is_audio_); - ui_loop_->PostTask(FROM_HERE, base::Bind( - callback, status, presentation_timestamp, is_audio_ ? size : 0)); -} - -void MediaDecoderJob::OnDecodeCompleted() { - DCHECK(ui_loop_->BelongsToCurrentThread()); - is_decoding_ = false; -} - -void MediaDecoderJob::Flush() { - // Do nothing, flush when the next Decode() happens. - needs_flush_ = true; -} - -void MediaDecoderJob::Release() { - // If |decoding_| is false, there is nothing running on the decoder thread. - // So it is safe to delete the MediaDecoderJob on the UI thread. However, - // if we post a task to the decoder thread to delete object, then we cannot - // immediately pass the surface to a new MediaDecoderJob instance because - // the java surface is still owned by the old object. New decoder creation - // will be blocked on the UI thread until the previous decoder gets deleted. - // This introduces extra latency during config changes, and makes the logic in - // MediaSourcePlayer more complicated. - // - // TODO(qinmin): Figure out the logic to passing the surface to a new - // MediaDecoderJob instance after the previous one gets deleted on the decoder - // thread. - if (is_decoding_ && !decoder_loop_->BelongsToCurrentThread()) { - DCHECK(ui_loop_->BelongsToCurrentThread()); - decoder_loop_->DeleteSoon(FROM_HERE, this); - } else { - delete this; - } -} - -VideoDecoderJob* VideoDecoderJob::Create( - const VideoCodec video_codec, const gfx::Size& size, jobject surface, - jobject media_crypto) { - scoped_ptr<VideoCodecBridge> codec(VideoCodecBridge::Create(video_codec)); - if (codec && codec->Start(video_codec, size, surface, media_crypto)) - return new VideoDecoderJob(codec.release()); - return NULL; -} - -VideoDecoderJob::VideoDecoderJob(MediaCodecBridge* media_codec_bridge) - : MediaDecoderJob(g_video_decoder_thread.Pointer()->message_loop_proxy(), - media_codec_bridge, - false) {} - -AudioDecoderJob* AudioDecoderJob::Create( - const AudioCodec audio_codec, - int sample_rate, - int channel_count, - const uint8* extra_data, - size_t extra_data_size, - jobject media_crypto) { - scoped_ptr<AudioCodecBridge> codec(AudioCodecBridge::Create(audio_codec)); - if (codec && codec->Start(audio_codec, sample_rate, channel_count, extra_data, - extra_data_size, true, media_crypto)) { - return new AudioDecoderJob(codec.release()); - } - return NULL; -} - -AudioDecoderJob::AudioDecoderJob(MediaCodecBridge* media_codec_bridge) - : MediaDecoderJob(g_audio_decoder_thread.Pointer()->message_loop_proxy(), - media_codec_bridge, - true) {} - -void AudioDecoderJob::SetVolume(double volume) { - static_cast<AudioCodecBridge*>(media_codec_bridge_.get())->SetVolume(volume); -} - MediaSourcePlayer::MediaSourcePlayer( int player_id, MediaPlayerManager* manager) diff --git a/media/base/android/media_source_player.h b/media/base/android/media_source_player.h index 05fd224..c475762 100644 --- a/media/base/android/media_source_player.h +++ b/media/base/android/media_source_player.h @@ -18,115 +18,18 @@ #include "base/threading/thread.h" #include "base/time/default_tick_clock.h" #include "base/time/time.h" -#include "media/base/android/demuxer_stream_player_params.h" #include "media/base/android/media_codec_bridge.h" +#include "media/base/android/media_decoder_job.h" #include "media/base/android/media_player_android.h" #include "media/base/clock.h" #include "media/base/media_export.h" -namespace base { -class MessageLoopProxy; -} - namespace media { class AudioDecoderJob; class AudioTimestampHelper; class VideoDecoderJob; -// Class for managing all the decoding tasks. Each decoding task will be posted -// onto the same thread. The thread will be stopped once Stop() is called. -class MediaDecoderJob { - public: - enum DecodeStatus { - DECODE_SUCCEEDED, - DECODE_TRY_ENQUEUE_INPUT_AGAIN_LATER, - DECODE_TRY_DEQUEUE_OUTPUT_AGAIN_LATER, - DECODE_FORMAT_CHANGED, - DECODE_INPUT_END_OF_STREAM, - DECODE_OUTPUT_END_OF_STREAM, - DECODE_FAILED, - }; - - virtual ~MediaDecoderJob(); - - // Callback when a decoder job finishes its work. Args: whether decode - // finished successfully, presentation time, audio output bytes. - typedef base::Callback<void(DecodeStatus, const base::TimeDelta&, - size_t)> DecoderCallback; - - // Called by MediaSourcePlayer to decode some data. - void Decode(const AccessUnit& unit, - const base::TimeTicks& start_time_ticks, - const base::TimeDelta& start_presentation_timestamp, - const MediaDecoderJob::DecoderCallback& callback); - - // Flush the decoder. - void Flush(); - - // Causes this instance to be deleted on the thread it is bound to. - void Release(); - - // Called on the UI thread to indicate that one decode cycle has completed. - void OnDecodeCompleted(); - - bool is_decoding() const { return is_decoding_; } - - protected: - MediaDecoderJob(const scoped_refptr<base::MessageLoopProxy>& decoder_loop, - MediaCodecBridge* media_codec_bridge, - bool is_audio); - - // Release the output buffer and render it. - void ReleaseOutputBuffer( - int outputBufferIndex, size_t size, - const base::TimeDelta& presentation_timestamp, - const MediaDecoderJob::DecoderCallback& callback, DecodeStatus status); - - DecodeStatus QueueInputBuffer(const AccessUnit& unit); - - // Helper function to decoder data on |thread_|. |unit| contains all the data - // to be decoded. |start_time_ticks| and |start_presentation_timestamp| - // represent the system time and the presentation timestamp when the first - // frame is rendered. We use these information to estimate when the current - // frame should be rendered. If |needs_flush| is true, codec needs to be - // flushed at the beginning of this call. - void DecodeInternal(const AccessUnit& unit, - const base::TimeTicks& start_time_ticks, - const base::TimeDelta& start_presentation_timestamp, - bool needs_flush, - const MediaDecoderJob::DecoderCallback& callback); - - // The UI message loop where callbacks should be dispatched. - scoped_refptr<base::MessageLoopProxy> ui_loop_; - - // The message loop that decoder job runs on. - scoped_refptr<base::MessageLoopProxy> decoder_loop_; - - // The media codec bridge used for decoding. - scoped_ptr<MediaCodecBridge> media_codec_bridge_; - - // Whether the decoder needs to be flushed. - bool needs_flush_; - - // Whether this is an audio decoder. - bool is_audio_; - - // Whether input EOS is encountered. - bool input_eos_encountered_; - - // Weak pointer passed to media decoder jobs for callbacks. It is bounded to - // the decoder thread. - base::WeakPtrFactory<MediaDecoderJob> weak_this_; - - // Whether the decoder is actively decoding data. - bool is_decoding_; -}; - -struct DecoderJobDeleter { - inline void operator()(MediaDecoderJob* ptr) const { ptr->Release(); } -}; - // This class handles media source extensions on Android. It uses Android // MediaCodec to decode audio and video streams in two separate threads. // IPC is being used to send data from the render process to this object. @@ -271,9 +174,9 @@ class MEDIA_EXPORT MediaSourcePlayer : public MediaPlayerAndroid { // The surface object currently owned by the player. gfx::ScopedJavaSurface surface_; - // Decoder jobs - scoped_ptr<AudioDecoderJob, DecoderJobDeleter> audio_decoder_job_; - scoped_ptr<VideoDecoderJob, DecoderJobDeleter> video_decoder_job_; + // Decoder jobs. + scoped_ptr<AudioDecoderJob, MediaDecoderJob::Deleter> audio_decoder_job_; + scoped_ptr<VideoDecoderJob, MediaDecoderJob::Deleter> video_decoder_job_; bool reconfig_audio_decoder_; bool reconfig_video_decoder_; diff --git a/media/base/android/video_decoder_job.cc b/media/base/android/video_decoder_job.cc new file mode 100644 index 0000000..0cd7386 --- /dev/null +++ b/media/base/android/video_decoder_job.cc @@ -0,0 +1,63 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/base/android/video_decoder_job.h" + +#include "base/bind.h" +#include "base/lazy_instance.h" +#include "base/threading/thread.h" +#include "media/base/android/media_codec_bridge.h" + +namespace media { + +class VideoDecoderThread : public base::Thread { + public: + VideoDecoderThread() : base::Thread("MediaSource_VideoDecoderThread") { + Start(); + } +}; + +// TODO(qinmin): Check if it is tolerable to use worker pool to handle all the +// decoding tasks so that we don't need a global thread here. +// http://crbug.com/245750 +base::LazyInstance<VideoDecoderThread>::Leaky + g_video_decoder_thread = LAZY_INSTANCE_INITIALIZER; + + +VideoDecoderJob* VideoDecoderJob::Create( + const VideoCodec video_codec, const gfx::Size& size, jobject surface, + jobject media_crypto) { + scoped_ptr<VideoCodecBridge> codec(VideoCodecBridge::Create(video_codec)); + if (codec && codec->Start(video_codec, size, surface, media_crypto)) + return new VideoDecoderJob(codec.Pass()); + return NULL; +} + +VideoDecoderJob::VideoDecoderJob( + scoped_ptr<VideoCodecBridge> video_codec_bridge) + : MediaDecoderJob(g_video_decoder_thread.Pointer()->message_loop_proxy(), + video_codec_bridge.get()), + video_codec_bridge_(video_codec_bridge.Pass()) { +} + +VideoDecoderJob::~VideoDecoderJob() { +} + +void VideoDecoderJob::ReleaseOutputBuffer( + int outputBufferIndex, size_t size, + const base::TimeDelta& presentation_timestamp, + const MediaDecoderJob::DecoderCallback& callback, + DecodeStatus status) { + + if (status != DECODE_OUTPUT_END_OF_STREAM || size != 0u) + video_codec_bridge_->ReleaseOutputBuffer(outputBufferIndex, true); + + callback.Run(status, presentation_timestamp, 0); +} + +bool VideoDecoderJob::ComputeTimeToRender() const { + return true; +} + +} // namespace media diff --git a/media/base/android/video_decoder_job.h b/media/base/android/video_decoder_job.h new file mode 100644 index 0000000..16f3229 --- /dev/null +++ b/media/base/android/video_decoder_job.h @@ -0,0 +1,48 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_BASE_ANDROID_VIDEO_DECODER_JOB_H_ +#define MEDIA_BASE_ANDROID_VIDEO_DECODER_JOB_H_ + +#include <jni.h> + +#include "media/base/android/media_decoder_job.h" + +namespace media { + +class VideoCodecBridge; + +// Class for managing video decoding jobs. +class VideoDecoderJob : public MediaDecoderJob { + public: + virtual ~VideoDecoderJob(); + + // Create a new VideoDecoderJob instance. + // |video_codec| - The video format the object needs to decode. + // |size| - The natrual size of the output frames. + // |surface| - The surface to render the frames to. + // |media_crypto| - Handle to a Java object responsible for decrypting the + // video data. + static VideoDecoderJob* Create( + const VideoCodec video_codec, const gfx::Size& size, jobject surface, + jobject media_crypto); + + private: + VideoDecoderJob(scoped_ptr<VideoCodecBridge> video_codec_bridge); + + // MediaDecoderJob implementation. + virtual void ReleaseOutputBuffer( + int outputBufferIndex, size_t size, + const base::TimeDelta& presentation_timestamp, + const MediaDecoderJob::DecoderCallback& callback, + DecodeStatus status) OVERRIDE; + + virtual bool ComputeTimeToRender() const OVERRIDE; + + scoped_ptr<VideoCodecBridge> video_codec_bridge_; +}; + +} // namespace media + +#endif // MEDIA_BASE_ANDROID_VIDEO_DECODER_JOB_H_ |