Chromium Code Reviews| Index: content/renderer/media/rtc_video_decoder_bridge_tv.cc |
| diff --git a/content/renderer/media/rtc_video_decoder_bridge_tv.cc b/content/renderer/media/rtc_video_decoder_bridge_tv.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..01b9b2144c520a8dce7e5096165bd46c74b69db9 |
| --- /dev/null |
| +++ b/content/renderer/media/rtc_video_decoder_bridge_tv.cc |
| @@ -0,0 +1,450 @@ |
| +// Copyright (c) 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 "content/renderer/media/rtc_video_decoder_bridge_tv.h" |
| + |
| +#include <queue> |
| + |
| +#include "base/bind.h" |
| +#include "base/callback_helpers.h" |
| +#include "base/location.h" |
| +#include "base/logging.h" |
| +#include "base/memory/ref_counted.h" |
| +#include "base/memory/singleton.h" |
| +#include "base/message_loop_proxy.h" |
| +#include "base/time.h" |
| +#include "media/base/decoder_buffer.h" |
| +#include "third_party/libjingle/source/talk/base/ratetracker.h" |
| + |
| +namespace content { |
| + |
| +// Helper function that makes sure |read_cb| runs on |message_loop_proxy|. |
| +static void RunOnMessageLoop( |
| + media::DemuxerStream::ReadCB read_cb, |
| + scoped_refptr<base::MessageLoopProxy> message_loop_proxy, |
| + media::DemuxerStream::Status status, |
| + const scoped_refptr<media::DecoderBuffer>& buffer) { |
| + if (!message_loop_proxy->BelongsToCurrentThread()) { |
| + message_loop_proxy->PostTask(FROM_HERE, base::Bind( |
| + &RunOnMessageLoop, read_cb, message_loop_proxy, status, buffer)); |
| + return; |
| + } |
| + read_cb.Run(status, buffer); |
| +} |
| + |
| +// RTCDemuxerStream ------------------------------------------------------------ |
| + |
| +namespace { |
| + |
| +class RTCDemuxerStream : public media::DemuxerStream { |
| + public: |
| + explicit RTCDemuxerStream(const gfx::Size& size); |
| + virtual ~RTCDemuxerStream(); |
| + // media::DemuxerStream implementation. |
| + virtual void Read(const ReadCB& read_cb) OVERRIDE; |
| + virtual const media::AudioDecoderConfig& audio_decoder_config() OVERRIDE; |
| + virtual const media::VideoDecoderConfig& video_decoder_config() OVERRIDE; |
| + virtual Type type() OVERRIDE; |
| + virtual void EnableBitstreamConverter() OVERRIDE; |
| + |
| + void UpdateSize(const gfx::Size& size); |
| + void QueueBuffer(scoped_refptr<media::DecoderBuffer> buffer, |
| + const base::Closure& done_cb); |
| + |
| + void RunDoneCallback(); |
| + |
| + private: |
| + void RunReadCallback(); |
| + |
| + media::DecoderBufferQueue buffer_queue_; |
| + std::queue<base::Closure> done_cb_queue_; |
| + ReadCB read_cb_; |
| + |
| + media::AudioDecoderConfig dummy_audio_decoder_config_; |
| + media::VideoDecoderConfig video_decoder_config_; |
| + talk_base::RateTracker frame_rate_tracker_; |
| +}; |
| + |
| +RTCDemuxerStream::RTCDemuxerStream(const gfx::Size& size) |
| + : video_decoder_config_( |
| + media::kCodecVP8, |
| + media::VP8PROFILE_MAIN, |
| + media::VideoFrame::NATIVE_TEXTURE, |
| + size, gfx::Rect(size), size, NULL, 0, false) { |
|
Ami GONE FROM CHROMIUM
2013/04/29 22:11:09
I think many of the comments I made in https://chr
wonsik
2013/05/03 18:31:19
Went through the review and applied a few.
|
| +} |
| + |
| +RTCDemuxerStream::~RTCDemuxerStream() {} |
| + |
| +const media::AudioDecoderConfig& RTCDemuxerStream::audio_decoder_config() { |
| + DCHECK(false) << "Does not support audio."; |
| + return dummy_audio_decoder_config_; |
| +} |
| + |
| +const media::VideoDecoderConfig& RTCDemuxerStream::video_decoder_config() { |
| + return video_decoder_config_; |
| +} |
| + |
| +media::DemuxerStream::Type RTCDemuxerStream::type() { |
| + return media::DemuxerStream::VIDEO; |
| +} |
| + |
| +void RTCDemuxerStream::EnableBitstreamConverter() { |
| +} |
| + |
| +void RTCDemuxerStream::UpdateSize(const gfx::Size& size) { |
| + video_decoder_config_.Initialize( |
| + media::kCodecVP8, |
| + media::VP8PROFILE_MAIN, |
| + media::VideoFrame::NATIVE_TEXTURE, |
| + size, gfx::Rect(size), size, NULL, 0, false, false); |
| +} |
| + |
| +void RTCDemuxerStream::QueueBuffer(scoped_refptr<media::DecoderBuffer> buffer, |
| + const base::Closure& done_cb) { |
| + buffer_queue_.Push(buffer); |
| + done_cb_queue_.push(done_cb); |
| + frame_rate_tracker_.Update(1); |
| + DLOG(INFO) << "frame rate received : " << frame_rate_tracker_.units_second(); |
| + RunReadCallback(); |
| +} |
| + |
| +void RTCDemuxerStream::Read(const ReadCB& read_cb) { |
| + CHECK(read_cb_.is_null()); |
| + // A call to |Read| operation means that |MediaSourceDelegate| is done with |
| + // the previous buffer. |
| + if (!done_cb_queue_.empty()) |
| + RunDoneCallback(); |
| + read_cb_ = base::Bind(&RunOnMessageLoop, read_cb, |
| + base::MessageLoopProxy::current()); |
| + RunReadCallback(); |
| +} |
| + |
| +void RTCDemuxerStream::RunReadCallback() { |
| + if (!read_cb_.is_null() && !buffer_queue_.IsEmpty()) { |
| + base::ResetAndReturn(&read_cb_).Run( |
| + DemuxerStream::kOk, buffer_queue_.Pop()); |
| + } |
| +} |
| + |
| +void RTCDemuxerStream::RunDoneCallback() { |
| + DCHECK(!done_cb_queue_.empty()); |
| + done_cb_queue_.front().Run(); |
| + done_cb_queue_.pop(); |
| +} |
| + |
| +} // anonymous namespace |
| + |
| +// RTCDemuxerProxy ------------------------------------------------------------- |
| + |
| +class RTCDemuxerProxy : public base::RefCountedThreadSafe<RTCDemuxerProxy> { |
| + public: |
| + RTCDemuxerProxy(const scoped_refptr<base::MessageLoopProxy>& message_loop); |
| + |
| + void Initialize(media::DemuxerHost* host, |
| + const media::PipelineStatusCB& cb); |
| + media::DemuxerStream* GetStream(media::DemuxerStream::Type type); |
| + void UpdateSize(const gfx::Size& size); |
| + void QueueBuffer(scoped_refptr<media::DecoderBuffer> buffer, |
| + const base::Closure& done_cb); |
| + |
| + protected: |
| + friend class base::RefCountedThreadSafe<RTCDemuxerProxy>; |
| + |
| + virtual ~RTCDemuxerProxy(); |
| + |
| + private: |
| + scoped_refptr<base::MessageLoopProxy> loop_proxy_; |
| + scoped_ptr<RTCDemuxerStream> stream_; |
| + media::DemuxerHost* host_; |
| + media::PipelineStatusCB init_cb_; |
| +}; |
| + |
| +RTCDemuxerProxy::RTCDemuxerProxy( |
| + const scoped_refptr<base::MessageLoopProxy>& message_loop) |
| + : loop_proxy_(message_loop), |
| + host_(NULL) { |
| +} |
| + |
| +RTCDemuxerProxy::~RTCDemuxerProxy() {} |
| + |
| +void RTCDemuxerProxy::Initialize(media::DemuxerHost* host, |
| + const media::PipelineStatusCB& cb) { |
| + host_ = host; |
| + init_cb_ = cb; |
| +} |
| + |
| +media::DemuxerStream* RTCDemuxerProxy::GetStream( |
| + media::DemuxerStream::Type type) { |
| + if (type == media::DemuxerStream::VIDEO) |
| + return stream_.get(); |
| + return NULL; |
| +} |
| + |
| +void RTCDemuxerProxy::UpdateSize(const gfx::Size& size) { |
| + if (!loop_proxy_->BelongsToCurrentThread()) { |
| + loop_proxy_->PostTask(FROM_HERE, base::Bind( |
| + &RTCDemuxerProxy::UpdateSize, this, size)); |
| + return; |
| + } |
| + if (stream_) { |
| + stream_->UpdateSize(size); |
| + } else { |
| + stream_.reset(new RTCDemuxerStream(size)); |
| + if (!init_cb_.is_null()) |
| + base::ResetAndReturn(&init_cb_).Run(media::PIPELINE_OK); |
| + } |
| +} |
| + |
| +void RTCDemuxerProxy::QueueBuffer(scoped_refptr<media::DecoderBuffer> buffer, |
| + const base::Closure& done_cb) { |
| + if (!loop_proxy_->BelongsToCurrentThread()) { |
| + loop_proxy_->PostTask(FROM_HERE, base::Bind( |
| + &RTCDemuxerProxy::QueueBuffer, this, buffer, done_cb)); |
| + return; |
| + } |
| + if (stream_) |
| + stream_->QueueBuffer(buffer, done_cb); |
| + else |
| + done_cb.Run(); |
| +} |
| + |
| +// RTCDemuxer ------------------------------------------------------------------ |
| + |
| +RTCDemuxer::RTCDemuxer( |
| + const scoped_refptr<base::MessageLoopProxy>& message_loop) |
| + : proxy_(new RTCDemuxerProxy(message_loop)) {} |
| + |
| +RTCDemuxer::~RTCDemuxer() {} |
| + |
| +void RTCDemuxer::Initialize(media::DemuxerHost* host, |
| + const media::PipelineStatusCB& cb) { |
| + proxy_->Initialize(host, cb); |
| +} |
| + |
| +media::DemuxerStream* RTCDemuxer::GetStream(media::DemuxerStream::Type type) { |
| + return proxy_->GetStream(type); |
| +} |
| + |
| +base::TimeDelta RTCDemuxer::GetStartTime() const { |
| + return base::TimeDelta(); |
| +} |
| + |
| +void RTCDemuxer::UpdateSize(const gfx::Size& size) { |
| + proxy_->UpdateSize(size); |
| +} |
| + |
| +void RTCDemuxer::QueueBuffer(scoped_refptr<media::DecoderBuffer> buffer, |
| + const base::Closure& done_cb) { |
| + proxy_->QueueBuffer(buffer, done_cb); |
| +} |
| + |
| +// RTCVideoDecoderBridgeTvImpl ------------------------------------------------- |
| + |
| +namespace { |
| + |
| +class RTCVideoDecoderBridgeTvImpl : public RTCVideoDecoderBridgeTv { |
| + public: |
| + static RTCVideoDecoderBridgeTvImpl* GetInstance() { |
| + return Singleton<RTCVideoDecoderBridgeTvImpl>::get(); |
| + } |
| + RTCVideoDecoderBridgeTvImpl(); |
| + virtual ~RTCVideoDecoderBridgeTvImpl(); |
| + |
| + // webrtc::VideoDecoder implementation. |
| + virtual WebRtc_Word32 InitDecode( |
| + const webrtc::VideoCodec* codecSettings, |
| + WebRtc_Word32 numberOfCores) OVERRIDE; |
| + virtual WebRtc_Word32 Decode( |
| + const webrtc::EncodedImage& inputImage, |
| + bool missingFrames, |
| + const webrtc::RTPFragmentationHeader* fragmentation, |
| + const webrtc::CodecSpecificInfo* codecSpecificInfo = NULL, |
| + WebRtc_Word64 renderTimeMs = -1) OVERRIDE; |
| + virtual WebRtc_Word32 RegisterDecodeCompleteCallback( |
| + webrtc::DecodedImageCallback* callback) OVERRIDE; |
| + virtual WebRtc_Word32 Release() OVERRIDE; |
| + virtual WebRtc_Word32 Reset() OVERRIDE; |
| + |
| + bool RegisterDemuxer(RTCDemuxer* demuxer) OVERRIDE; |
| + bool AcquireOwnership() OVERRIDE; |
| + void ReleaseOwnership() OVERRIDE; |
| + |
| + static void RunDecodeCompleteCallback(webrtc::DecodedImageCallback* callback, |
| + WebRtc_Word64 timestamp); |
| + |
| + private: |
| + friend struct DefaultSingletonTraits<RTCVideoDecoderBridgeTv>; |
| + |
| + static const base::TimeDelta kDecoderTimeOut; |
| + enum Status { |
| + kNoInit, |
| + kInitialized, |
| + }; |
| + |
| + base::TimeDelta first_render_wall_time_; |
| + |
| + webrtc::DecodedImageCallback* decode_complete_callback_; |
| + RTCDemuxer* demuxer_; |
| + gfx::Size size_; |
| + Status status_; |
| + bool being_used_; |
| + bool first_frame_; |
| + WebRtc_Word64 timestamp_offset_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(RTCVideoDecoderBridgeTvImpl); |
| +}; |
| + |
| +RTCVideoDecoderBridgeTvImpl::RTCVideoDecoderBridgeTvImpl() |
| + : decode_complete_callback_(NULL), |
| + demuxer_(NULL), |
| + status_(kNoInit), |
| + being_used_(false), |
| + first_frame_(true) { |
| +} |
| + |
| +RTCVideoDecoderBridgeTvImpl::~RTCVideoDecoderBridgeTvImpl() { |
| +} |
| + |
| +bool RTCVideoDecoderBridgeTvImpl::AcquireOwnership() { |
| + if (being_used_) { |
| + return false; |
| + } |
| + being_used_ = true; |
| + return true; |
| +} |
| + |
| +void RTCVideoDecoderBridgeTvImpl::ReleaseOwnership() { |
| + DCHECK(being_used_); |
| + being_used_ = false; |
| +} |
| + |
| +WebRtc_Word32 RTCVideoDecoderBridgeTvImpl::InitDecode( |
| + const webrtc::VideoCodec* codecSettings, |
| + WebRtc_Word32 numberOfCores) { |
| + if (status_ != kNoInit) { |
| + return WEBRTC_VIDEO_CODEC_ERROR; |
| + } |
| + if (codecSettings->codecType != webrtc::kVideoCodecVP8) { |
| + return WEBRTC_VIDEO_CODEC_ERROR; |
| + } |
| + if (codecSettings->codecSpecific.VP8.feedbackModeOn) { |
| + // We don't support feedback mode. |
| + return WEBRTC_VIDEO_CODEC_ERROR; |
| + } |
| + size_ = gfx::Size(codecSettings->width, codecSettings->height); |
| + status_ = kInitialized; |
| + if (demuxer_) |
| + demuxer_->UpdateSize(size_); |
| + |
| + return WEBRTC_VIDEO_CODEC_OK; |
| +} |
| + |
| +WebRtc_Word32 RTCVideoDecoderBridgeTvImpl::Decode( |
| + const webrtc::EncodedImage& inputImage, |
| + bool missingFrames, |
| + const webrtc::RTPFragmentationHeader* fragmentation, |
| + const webrtc::CodecSpecificInfo* codecSpecificInfo, |
| + WebRtc_Word64 renderTimeMs) { |
| + if (status_ == kNoInit || decode_complete_callback_ == NULL) { |
| + return WEBRTC_VIDEO_CODEC_UNINITIALIZED; |
| + } |
| + if (!status_ == kInitialized) { |
| + return WEBRTC_VIDEO_CODEC_ERROR; |
| + } |
| + if (missingFrames || !inputImage._completeFrame) { |
| + // Unlike the SW decoder in libvpx, hw decoder can not handle broken frames. |
| + // Here, we return an error in order to request a key frame. |
| + return WEBRTC_VIDEO_CODEC_ERROR; |
| + } |
| + if (!demuxer_) { |
| + // Drop frames until demuxer is up and running. We'll request key frame |
| + // once demuxer is ready. |
| + return WEBRTC_VIDEO_CODEC_NO_OUTPUT; |
| + } |
| + |
| + if (first_frame_) { |
| + // If the first frame is not the key frame, return an error to request a key |
| + // frame. |
| + if (inputImage._frameType != webrtc::kKeyFrame) |
| + return WEBRTC_VIDEO_CODEC_ERROR; |
| + |
| + // Google TV expects timestamp from 0. |
| + timestamp_offset_ = renderTimeMs; |
| + } |
| + first_frame_ = false; |
| + if (inputImage._frameType == webrtc::kKeyFrame && |
| + inputImage._encodedWidth != 0 && inputImage._encodedHeight != 0) { |
| + // Only key frame has the size. |
| + gfx::Size new_size(inputImage._encodedWidth, inputImage._encodedHeight); |
| + if (size_ != new_size) { |
| + size_ = new_size; |
| + demuxer_->UpdateSize(new_size); |
| + } |
| + } |
| + scoped_refptr<media::DecoderBuffer> buffer; |
| + buffer = media::DecoderBuffer::CopyFrom( |
| + inputImage._buffer, inputImage._length); |
| + if (renderTimeMs != -1) { |
| + buffer->SetTimestamp( |
| + base::TimeDelta::FromMilliseconds(renderTimeMs - timestamp_offset_)); |
| + } |
| + |
| + demuxer_->QueueBuffer( |
| + buffer, |
| + base::Bind(&RTCVideoDecoderBridgeTvImpl::RunDecodeCompleteCallback, |
| + decode_complete_callback_, |
| + renderTimeMs)); |
| + |
| + return WEBRTC_VIDEO_CODEC_OK; |
| +} |
| + |
| +WebRtc_Word32 RTCVideoDecoderBridgeTvImpl::RegisterDecodeCompleteCallback( |
| + webrtc::DecodedImageCallback* callback) { |
| + decode_complete_callback_ = callback; |
| + return WEBRTC_VIDEO_CODEC_OK; |
| +} |
| + |
| +WebRtc_Word32 RTCVideoDecoderBridgeTvImpl::Release() { |
| + demuxer_ = NULL; |
| + status_ = kNoInit; |
| + return WEBRTC_VIDEO_CODEC_OK; |
| +} |
| + |
| +WebRtc_Word32 RTCVideoDecoderBridgeTvImpl::Reset() { |
| + return WEBRTC_VIDEO_CODEC_OK; |
| +} |
| + |
| +bool RTCVideoDecoderBridgeTvImpl::RegisterDemuxer( |
| + RTCDemuxer* demuxer) { |
| + if (demuxer_) { |
| + return false; |
| + } |
| + demuxer_ = demuxer; |
| + // If this is initialized before demuxer is set, update the demuxer with size |
| + // information. |
| + if (status_ == kInitialized) |
| + demuxer_->UpdateSize(size_); |
| + first_frame_ = true; |
| + return true; |
| +} |
| + |
| +// static |
| +void RTCVideoDecoderBridgeTvImpl::RunDecodeCompleteCallback( |
| + webrtc::DecodedImageCallback* callback, WebRtc_Word64 timestamp) { |
| + webrtc::I420VideoFrame dummy_video_frame; |
| + dummy_video_frame.CreateEmptyFrame(2, 1, 2, 1, 1); |
| + dummy_video_frame.set_timestamp(timestamp); |
| + callback->Decoded(dummy_video_frame); |
| +} |
| + |
| +} // anonymous namespace |
| + |
| +// RTCVideoDecoderBridgeTv ----------------------------------------------------- |
| + |
| +// static |
| +RTCVideoDecoderBridgeTv* RTCVideoDecoderBridgeTv::Get() { |
| + return RTCVideoDecoderBridgeTvImpl::GetInstance(); |
| +} |
| + |
| +} // namespace content |