| 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..9e49baa5a1ed180c474980db82924f598d347cd6
|
| --- /dev/null
|
| +++ b/content/renderer/media/rtc_video_decoder_bridge_tv.cc
|
| @@ -0,0 +1,525 @@
|
| +// 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/synchronization/lock.h"
|
| +#include "base/time.h"
|
| +#include "media/base/bind_to_loop.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 QueueBuffer(scoped_refptr<media::DecoderBuffer> buffer,
|
| + const base::Closure& done_cb,
|
| + const gfx::Size& size);
|
| +
|
| + private:
|
| + struct BufferEntry {
|
| + BufferEntry(const scoped_refptr<media::DecoderBuffer>& decoder_buffer_param,
|
| + const base::Closure& done_cb_param,
|
| + const gfx::Size& size_param)
|
| + : decoder_buffer(decoder_buffer_param),
|
| + done_cb(done_cb_param),
|
| + size(size_param) {}
|
| +
|
| + scoped_refptr<media::DecoderBuffer> decoder_buffer;
|
| + base::Closure done_cb;
|
| + // When |!size.isEmpty()|, it means that config change with new size |size|
|
| + // happened.
|
| + gfx::Size size;
|
| + };
|
| +
|
| + void RunReadCallback_Locked();
|
| +
|
| + base::Lock lock_;
|
| + std::queue<BufferEntry> buffer_queue_;
|
| + ReadCB read_cb_;
|
| + base::Closure pending_done_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) {
|
| +}
|
| +
|
| +RTCDemuxerStream::~RTCDemuxerStream() {}
|
| +
|
| +const media::AudioDecoderConfig& RTCDemuxerStream::audio_decoder_config() {
|
| + LOG(FATAL) << "Does not support audio.";
|
| + return dummy_audio_decoder_config_;
|
| +}
|
| +
|
| +const media::VideoDecoderConfig& RTCDemuxerStream::video_decoder_config() {
|
| + base::AutoLock lock(lock_);
|
| + return video_decoder_config_;
|
| +}
|
| +
|
| +media::DemuxerStream::Type RTCDemuxerStream::type() {
|
| + return media::DemuxerStream::VIDEO;
|
| +}
|
| +
|
| +void RTCDemuxerStream::EnableBitstreamConverter() {
|
| + LOG(FATAL) << "Not reachable.";
|
| +}
|
| +
|
| +void RTCDemuxerStream::QueueBuffer(scoped_refptr<media::DecoderBuffer> buffer,
|
| + const base::Closure& done_cb,
|
| + const gfx::Size& size) {
|
| + base::AutoLock lock(lock_);
|
| + buffer_queue_.push(BufferEntry(buffer, done_cb, size));
|
| + if (buffer)
|
| + frame_rate_tracker_.Update(1);
|
| + DLOG(INFO) << "frame rate received : " << frame_rate_tracker_.units_second();
|
| + RunReadCallback_Locked();
|
| +}
|
| +
|
| +void RTCDemuxerStream::Read(const ReadCB& read_cb) {
|
| + base::AutoLock lock(lock_);
|
| + CHECK(read_cb_.is_null());
|
| + // A call to |Read| operation means that |MediaSourceDelegate| is done with
|
| + // the previous buffer.
|
| + if (!pending_done_cb_.is_null())
|
| + base::ResetAndReturn(&pending_done_cb_).Run();
|
| + read_cb_ = media::BindToLoop(base::MessageLoopProxy::current(), read_cb);
|
| + RunReadCallback_Locked();
|
| +}
|
| +
|
| +void RTCDemuxerStream::RunReadCallback_Locked() {
|
| + if (!read_cb_.is_null() && !buffer_queue_.empty()) {
|
| + BufferEntry& front = buffer_queue_.front();
|
| + DemuxerStream::Status status = DemuxerStream::kOk;
|
| + if (!front.size.IsEmpty()) {
|
| + DCHECK(!front.decoder_buffer);
|
| + // No VideoFrame actually reaches cc in Google TV case. We just make
|
| + // coded_size == visible_rect == natural_size here.
|
| + video_decoder_config_.Initialize(media::kCodecVP8,
|
| + media::VP8PROFILE_MAIN,
|
| + media::VideoFrame::NATIVE_TEXTURE,
|
| + front.size,
|
| + gfx::Rect(front.size),
|
| + front.size,
|
| + NULL, 0, false, false);
|
| + status = DemuxerStream::kConfigChanged;
|
| + }
|
| +
|
| + DCHECK(pending_done_cb_.is_null());
|
| + pending_done_cb_ = front.done_cb;
|
| + base::ResetAndReturn(&read_cb_).Run(status, front.decoder_buffer);
|
| + buffer_queue_.pop();
|
| + }
|
| +}
|
| +
|
| +// 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,
|
| + const gfx::Size& size);
|
| +
|
| + 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;
|
| + }
|
| + DCHECK(!stream_);
|
| + 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,
|
| + const gfx::Size& size) {
|
| + if (!loop_proxy_->BelongsToCurrentThread()) {
|
| + loop_proxy_->PostTask(FROM_HERE, base::Bind(
|
| + &RTCDemuxerProxy::QueueBuffer, this, buffer, done_cb, size));
|
| + return;
|
| + }
|
| + if (stream_)
|
| + stream_->QueueBuffer(buffer, done_cb, size);
|
| + else
|
| + done_cb.Run();
|
| +}
|
| +
|
| +// RTCDemuxer ------------------------------------------------------------------
|
| +
|
| +class RTCDemuxer : public media::Demuxer {
|
| + public:
|
| + RTCDemuxer(const scoped_refptr<base::MessageLoopProxy>& message_loop);
|
| + virtual ~RTCDemuxer();
|
| +
|
| + // media::Demuxer implementation.
|
| + virtual void Initialize(media::DemuxerHost* host,
|
| + const media::PipelineStatusCB& cb) OVERRIDE;
|
| + virtual media::DemuxerStream* GetStream(
|
| + media::DemuxerStream::Type type) OVERRIDE;
|
| + virtual base::TimeDelta GetStartTime() const OVERRIDE;
|
| +
|
| + void UpdateSize(const gfx::Size& size);
|
| + void QueueBuffer(scoped_refptr<media::DecoderBuffer> buffer,
|
| + const base::Closure& done_cb,
|
| + const gfx::Size& size);
|
| +
|
| + private:
|
| + friend class RTCVideoDecoderBridgeTv;
|
| +
|
| + scoped_refptr<RTCDemuxerProxy> proxy_;
|
| +};
|
| +
|
| +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,
|
| + const gfx::Size& size) {
|
| + proxy_->QueueBuffer(buffer, done_cb, size);
|
| +}
|
| +
|
| +// RTCVideoDecoderBridgeTvImpl -------------------------------------------------
|
| +
|
| +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;
|
| +
|
| + virtual media::Demuxer* CreateDemuxer(
|
| + const MediaStreamDependencyFactory* media_stream_dependency_factory,
|
| + const scoped_refptr<base::MessageLoopProxy>& message_loop) OVERRIDE;
|
| + virtual void DestroyDemuxer(const media::Demuxer* demuxer) OVERRIDE;
|
| + virtual bool AcquireOwnership(
|
| + const MediaStreamDependencyFactory* media_stream_dependency_factory)
|
| + OVERRIDE;
|
| + virtual void ReleaseOwnership(
|
| + const MediaStreamDependencyFactory* media_stream_dependency_factory)
|
| + OVERRIDE;
|
| +
|
| + static void RunDecodeCompleteCallback(webrtc::DecodedImageCallback* callback,
|
| + WebRtc_Word64 timestamp);
|
| +
|
| + private:
|
| + friend struct DefaultSingletonTraits<RTCVideoDecoderBridgeTv>;
|
| +
|
| + static const base::TimeDelta kDecoderTimeOut;
|
| + enum Status {
|
| + kNoInit,
|
| + kInitialized,
|
| + };
|
| +
|
| + // Lock protected, since these can be accessed in CreateDemuxer,
|
| + // DestroyDemuxer, AcquireOwnership, and ReleaseOwnership. No guarantee of
|
| + // calling thread for these methods.
|
| + base::Lock lock_;
|
| + const MediaStreamDependencyFactory* ownership_tag_;
|
| + const MediaStreamDependencyFactory* demuxer_tag_;
|
| + scoped_ptr<RTCDemuxer> demuxer_;
|
| + gfx::Size size_;
|
| + Status status_;
|
| +
|
| + // Only used by decoder thread.
|
| + bool first_frame_;
|
| + webrtc::DecodedImageCallback* decode_complete_callback_;
|
| + WebRtc_Word64 timestamp_offset_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(RTCVideoDecoderBridgeTvImpl);
|
| +};
|
| +
|
| +RTCVideoDecoderBridgeTvImpl::RTCVideoDecoderBridgeTvImpl()
|
| + : ownership_tag_(NULL),
|
| + demuxer_tag_(NULL),
|
| + status_(kNoInit),
|
| + first_frame_(true),
|
| + decode_complete_callback_(NULL) {}
|
| +
|
| +RTCVideoDecoderBridgeTvImpl::~RTCVideoDecoderBridgeTvImpl() {}
|
| +
|
| +media::Demuxer* RTCVideoDecoderBridgeTvImpl::CreateDemuxer(
|
| + const MediaStreamDependencyFactory* media_stream_dependency_factory,
|
| + const scoped_refptr<base::MessageLoopProxy>& message_loop) {
|
| + base::AutoLock lock(lock_);
|
| + if (demuxer_tag_ != NULL ||
|
| + (ownership_tag_ != NULL &&
|
| + ownership_tag_ != media_stream_dependency_factory))
|
| + return NULL;
|
| + DCHECK(!demuxer_);
|
| + demuxer_tag_ = media_stream_dependency_factory;
|
| + demuxer_.reset(new RTCDemuxer(message_loop));
|
| + // If this is initialized before demuxer is set, update the demuxer with size
|
| + // information.
|
| + if (status_ == kInitialized)
|
| + demuxer_->UpdateSize(size_);
|
| + return demuxer_.get();
|
| +}
|
| +
|
| +void RTCVideoDecoderBridgeTvImpl::DestroyDemuxer(
|
| + const media::Demuxer* demuxer) {
|
| + base::AutoLock lock(lock_);
|
| + DCHECK(demuxer_.get() == demuxer);
|
| + demuxer_.reset();
|
| + demuxer_tag_ = NULL;
|
| +}
|
| +
|
| +bool RTCVideoDecoderBridgeTvImpl::AcquireOwnership(
|
| + const MediaStreamDependencyFactory* media_stream_dependency_factory) {
|
| + base::AutoLock lock(lock_);
|
| + if (ownership_tag_ != NULL ||
|
| + (demuxer_tag_ != NULL && demuxer_tag_ != media_stream_dependency_factory))
|
| + return false;
|
| +
|
| + ownership_tag_ = media_stream_dependency_factory;
|
| + return true;
|
| +}
|
| +
|
| +void RTCVideoDecoderBridgeTvImpl::ReleaseOwnership(
|
| + const MediaStreamDependencyFactory* media_stream_dependency_factory) {
|
| + base::AutoLock lock(lock_);
|
| + DCHECK(ownership_tag_ == media_stream_dependency_factory);
|
| + ownership_tag_ = NULL;
|
| +}
|
| +
|
| +WebRtc_Word32 RTCVideoDecoderBridgeTvImpl::InitDecode(
|
| + const webrtc::VideoCodec* codecSettings,
|
| + WebRtc_Word32 numberOfCores) {
|
| + if (codecSettings->codecType != webrtc::kVideoCodecVP8)
|
| + return WEBRTC_VIDEO_CODEC_ERROR;
|
| + // We don't support feedback mode.
|
| + if (codecSettings->codecSpecific.VP8.feedbackModeOn)
|
| + return WEBRTC_VIDEO_CODEC_ERROR;
|
| +
|
| + base::AutoLock lock(lock_);
|
| + if (status_ != kNoInit)
|
| + return WEBRTC_VIDEO_CODEC_ERROR;
|
| + size_ = gfx::Size(codecSettings->width, codecSettings->height);
|
| + status_ = kInitialized;
|
| + first_frame_ = true;
|
| + 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) {
|
| + // 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.
|
| + if (missingFrames || !inputImage._completeFrame)
|
| + return WEBRTC_VIDEO_CODEC_ERROR;
|
| +
|
| + base::AutoLock lock(lock_);
|
| + if (status_ == kNoInit || decode_complete_callback_ == NULL)
|
| + return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
|
| + if (status_ != kInitialized)
|
| + return WEBRTC_VIDEO_CODEC_ERROR;
|
| + // Drop frames until demuxer is up and running. We'll request key frame
|
| + // once demuxer is ready.
|
| + if (!demuxer_)
|
| + 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;
|
| + gfx::Size new_size;
|
| + if (inputImage._frameType == webrtc::kKeyFrame &&
|
| + inputImage._encodedWidth != 0 && inputImage._encodedHeight != 0) {
|
| + // Only key frame has the size.
|
| + new_size.SetSize(inputImage._encodedWidth, inputImage._encodedHeight);
|
| + size_ = 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_));
|
| + }
|
| +
|
| + if (!new_size.IsEmpty()) {
|
| + demuxer_->QueueBuffer(
|
| + NULL,
|
| + base::Bind(&base::DoNothing),
|
| + new_size);
|
| + }
|
| + demuxer_->QueueBuffer(
|
| + buffer,
|
| + base::Bind(&RTCVideoDecoderBridgeTvImpl::RunDecodeCompleteCallback,
|
| + decode_complete_callback_,
|
| + renderTimeMs),
|
| + gfx::Size());
|
| +
|
| + 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() {
|
| + base::AutoLock lock(lock_);
|
| + status_ = kNoInit;
|
| + return WEBRTC_VIDEO_CODEC_OK;
|
| +}
|
| +
|
| +WebRtc_Word32 RTCVideoDecoderBridgeTvImpl::Reset() {
|
| + first_frame_ = true;
|
| + return WEBRTC_VIDEO_CODEC_OK;
|
| +}
|
| +
|
| +// static
|
| +void RTCVideoDecoderBridgeTvImpl::RunDecodeCompleteCallback(
|
| + webrtc::DecodedImageCallback* callback, WebRtc_Word64 timestamp) {
|
| + // We call the decode complete callback function to notify libjingle that
|
| + // decoding is finished.
|
| + 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
|
|
|