Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(78)

Unified Diff: content/renderer/media/video_track_recorder.cc

Issue 1233033002: MediaStream: Adding VideoTrackRecorder class and unittests (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: dalecurtis@ comments Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: content/renderer/media/video_track_recorder.cc
diff --git a/content/renderer/media/video_track_recorder.cc b/content/renderer/media/video_track_recorder.cc
new file mode 100644
index 0000000000000000000000000000000000000000..a4975a768d289681be4cb8ec90a8b4389bd45281
--- /dev/null
+++ b/content/renderer/media/video_track_recorder.cc
@@ -0,0 +1,313 @@
+// Copyright 2015 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/video_track_recorder.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/sys_info.h"
+#include "base/threading/thread.h"
+#include "base/time/time.h"
+#include "base/trace_event/trace_event.h"
+#include "content/child/child_process.h"
+#include "media/base/bind_to_current_loop.h"
+#include "media/base/video_frame.h"
+
+extern "C" {
+// VPX_CODEC_DISABLE_COMPAT excludes parts of the libvpx API that provide
+// backwards compatibility for legacy applications using the library.
+#define VPX_CODEC_DISABLE_COMPAT 1
+#include "third_party/libvpx/source/libvpx/vpx/vp8cx.h"
+#include "third_party/libvpx/source/libvpx/vpx/vpx_encoder.h"
+}
+
+using media::VideoFrame;
+using media::VideoFrameMetadata;
+
+namespace content {
+
+namespace {
+const vpx_codec_flags_t kNoFlags = 0;
+
+void OnFrameEncodeCompleted(
+ const content::VideoTrackRecorder::OnEncodedVideoCB& on_encoded_video_cb,
+ const scoped_refptr<VideoFrame>& frame,
+ scoped_ptr<std::string> data,
+ base::TimeTicks capture_timestamp,
+ bool keyframe) {
+ DVLOG(1) << (keyframe ? "" : "non ") << "keyframe "
+ << capture_timestamp << " ms - " << data->length() << "B ";
+ on_encoded_video_cb.Run(frame, base::StringPiece(*data), capture_timestamp,
+ keyframe);
+}
+
+} // anonymous namespace
+
+// Inner class encapsulating all libvpx interactions and the encoding+delivery
+// of received frames. This class is:
+// - created and destroyed on its parent's thread (usually the main render
+// thread),
+// - receives VideoFrames and Run()s the callbacks on another thread (supposedly
+// the render IO thread), which is cached on first frame arrival,
+// - uses an internal |encoding_thread_| for libvpx interactions, notably for
+// encoding (which might take some time).
+// Only VP8 is supported for the time being.
+class VideoTrackRecorder::VpxEncoder final {
+ public:
+ explicit VpxEncoder(const OnEncodedVideoCB& on_encoded_video_callback);
+ ~VpxEncoder();
+
+ void StartFrameEncode(const scoped_refptr<VideoFrame>& frame,
+ base::TimeTicks capture_timestamp);
+
+ private:
+ void EncodeOnEncodingThread(const scoped_refptr<VideoFrame>& frame,
+ base::TimeTicks capture_timestamp);
+
+ void ConfigureVp8Encoding(const gfx::Size& size);
+
+ // Returns true if |codec_config_| has been filled in at least once.
+ bool IsInitialized() const;
+
+ // Estimate the frame duration from |frame| and |last_frame_timestamp_|.
+ base::TimeDelta CalculateFrameDuration(
+ const scoped_refptr<VideoFrame>& frame);
+
+ // Used to check that we are destroyed on the same thread we were created.
+ base::ThreadChecker main_render_thread_checker_;
+
+ // Task runner where frames to encode and reply callbacks must happen.
+ scoped_refptr<base::SingleThreadTaskRunner> origin_task_runner_;
+
+ // This callback should be exercised on IO thread.
+ const OnEncodedVideoCB on_encoded_video_callback_;
+
+ // Thread for encoding. Active as long as VpxEncoder exists. All variables
+ // below this are used in this thread.
+ base::Thread encoding_thread_;
+ // VP8 internal objects: configuration, encoder and Vpx Image wrapper.
+ vpx_codec_enc_cfg_t codec_config_;
+ vpx_codec_ctx_t encoder_;
+
+ // The |VideoFrame::timestamp()| of the last encoded frame. This is used to
+ // predict the duration of the next frame.
+ base::TimeDelta last_frame_timestamp_;
+
+ DISALLOW_COPY_AND_ASSIGN(VpxEncoder);
+};
+
+VideoTrackRecorder::VpxEncoder::VpxEncoder(
+ const OnEncodedVideoCB& on_encoded_video_callback)
+ : on_encoded_video_callback_(on_encoded_video_callback),
+ encoding_thread_("EncodingThread") {
+ DCHECK(!on_encoded_video_callback_.is_null());
+
+ codec_config_.g_timebase.den = 0; // Not initialized.
+
+ DCHECK(!encoding_thread_.IsRunning());
+ encoding_thread_.Start();
+}
+
+VideoTrackRecorder::VpxEncoder::~VpxEncoder() {
+ DCHECK(main_render_thread_checker_.CalledOnValidThread());
+ DCHECK(encoding_thread_.IsRunning());
+ encoding_thread_.Stop();
+ vpx_codec_destroy(&encoder_);
+}
+
+void VideoTrackRecorder::VpxEncoder::StartFrameEncode(
+ const scoped_refptr<VideoFrame>& frame,
+ base::TimeTicks capture_timestamp) {
+ // Cache the thread sending frames on first frame arrival.
+ if (!origin_task_runner_.get())
+ origin_task_runner_ = base::MessageLoop::current()->task_runner();
+ DCHECK(origin_task_runner_->BelongsToCurrentThread());
+
+ encoding_thread_.task_runner()->PostTask(
+ FROM_HERE, base::Bind(&VpxEncoder::EncodeOnEncodingThread,
+ base::Unretained(this), frame, capture_timestamp));
+}
+
+void VideoTrackRecorder::VpxEncoder::EncodeOnEncodingThread(
+ const scoped_refptr<VideoFrame>& frame,
+ base::TimeTicks capture_timestamp) {
+ TRACE_EVENT0("video",
+ "VideoTrackRecorder::VpxEncoder::EncodeOnEncodingThread");
+ DCHECK(encoding_thread_.task_runner()->BelongsToCurrentThread());
+
+ const gfx::Size frame_size = frame->visible_rect().size();
+ if (!IsInitialized() ||
+ gfx::Size(codec_config_.g_w, codec_config_.g_h) != frame_size) {
+ ConfigureVp8Encoding(frame_size);
+ }
+
+ vpx_image_t vpx_image;
+ vpx_image_t* const result = vpx_img_wrap(&vpx_image,
+ VPX_IMG_FMT_I420,
+ frame_size.width(),
+ frame_size.height(),
+ 1 /* align */,
+ frame->data(VideoFrame::kYPlane));
+ DCHECK_EQ(result, &vpx_image);
+ vpx_image.planes[VPX_PLANE_Y] = frame->visible_data(VideoFrame::kYPlane);
+ vpx_image.planes[VPX_PLANE_U] = frame->visible_data(VideoFrame::kUPlane);
+ vpx_image.planes[VPX_PLANE_V] = frame->visible_data(VideoFrame::kVPlane);
+ vpx_image.stride[VPX_PLANE_Y] = frame->stride(VideoFrame::kYPlane);
+ vpx_image.stride[VPX_PLANE_U] = frame->stride(VideoFrame::kUPlane);
+ vpx_image.stride[VPX_PLANE_V] = frame->stride(VideoFrame::kVPlane);
+
+ const base::TimeDelta duration = CalculateFrameDuration(frame);
+ // Encode the frame. The presentation time stamp argument here is fixed to
+ // zero to force the encoder to base its single-frame bandwidth calculations
+ // entirely on |predicted_frame_duration|.
+ const vpx_codec_err_t ret = vpx_codec_encode(&encoder_,
+ &vpx_image,
+ 0 /* pts */,
+ duration.InMicroseconds(),
+ kNoFlags,
+ VPX_DL_REALTIME);
+ DCHECK_EQ(ret, VPX_CODEC_OK) << vpx_codec_err_to_string(ret) << ", #"
+ << vpx_codec_error(&encoder_) << " -"
+ << vpx_codec_error_detail(&encoder_);
+
+ scoped_ptr<std::string> data(new std::string);
+ bool keyframe = false;
+ vpx_codec_iter_t iter = NULL;
+ const vpx_codec_cx_pkt_t* pkt = NULL;
+ while ((pkt = vpx_codec_get_cx_data(&encoder_, &iter)) != NULL) {
+ if (pkt->kind != VPX_CODEC_CX_FRAME_PKT)
+ continue;
+ data->assign(static_cast<char*>(pkt->data.frame.buf), pkt->data.frame.sz);
+ keyframe = (pkt->data.frame.flags & VPX_FRAME_IS_KEY) != 0;
+ break;
+ }
+ origin_task_runner_->PostTask(FROM_HERE,
+ base::Bind(&OnFrameEncodeCompleted,
+ on_encoded_video_callback_,
+ frame,
+ base::Passed(&data),
+ capture_timestamp,
+ keyframe));
+}
+
+void VideoTrackRecorder::VpxEncoder::ConfigureVp8Encoding(
+ const gfx::Size& size) {
+ if (IsInitialized()) {
+ // TODO(mcasas) VP8 quirk/optimisation: If the new |size| is strictly less-
+ // than-or-equal than the old size, in terms of area, the existing encoder
+ // instance could be reused after changing |codec_config_.{g_w,g_h}|.
+ DVLOG(1) << "Destroying/Re-Creating encoder for new frame size: "
+ << gfx::Size(codec_config_.g_w, codec_config_.g_h).ToString()
+ << " --> " << size.ToString();
+ vpx_codec_destroy(&encoder_);
+ }
+
+ const vpx_codec_iface_t* interface = vpx_codec_vp8_cx();
+ const vpx_codec_err_t result = vpx_codec_enc_config_default(interface,
+ &codec_config_,
+ 0 /* reserved */);
+ DCHECK_EQ(VPX_CODEC_OK, result);
+
+ // Adjust default bit rate to account for the actual size.
+ DCHECK_EQ(320u, codec_config_.g_w);
+ DCHECK_EQ(240u, codec_config_.g_h);
+ DCHECK_EQ(256u, codec_config_.rc_target_bitrate);
+ codec_config_.rc_target_bitrate = size.GetArea() *
+ codec_config_.rc_target_bitrate / codec_config_.g_w / codec_config_.g_h;
+
+ DCHECK(size.width());
+ DCHECK(size.height());
+ codec_config_.g_w = size.width();
+ codec_config_.g_h = size.height();
+ codec_config_.g_pass = VPX_RC_ONE_PASS;
+
+ // Timebase is the smallest interval used by the stream, can be set to the
+ // frame rate or to e.g. microseconds.
+ codec_config_.g_timebase.num = 1;
+ codec_config_.g_timebase.den = base::Time::kMicrosecondsPerSecond;
+
+ // Let the encoder decide where to place the Keyframes, between min and max.
+ // In VPX_KF_AUTO mode libvpx will sometimes emit keyframes regardless of min/
+ // max distance out of necessity.
+ // Note that due to http://crbug.com/440223, it might be necessary to force a
+ // key frame after 10,000frames since decoding fails after 30,000 non-key
+ // frames.
+ codec_config_.kf_mode = VPX_KF_AUTO;
+ codec_config_.kf_min_dist = 0;
+ codec_config_.kf_max_dist = 30000;
+
+ // Do not saturate CPU utilization just for encoding. On a lower-end system
+ // with only 1 or 2 cores, use only one thread for encoding. On systems with
+ // more cores, allow half of the cores to be used for encoding.
+ codec_config_.g_threads =
+ std::min(8, (base::SysInfo::NumberOfProcessors() + 1) / 2);
+
+ // Number of frames to consume before producing output.
+ codec_config_.g_lag_in_frames = 0;
+
+ const vpx_codec_err_t ret = vpx_codec_enc_init(&encoder_, interface,
+ &codec_config_, kNoFlags);
+ DCHECK_EQ(VPX_CODEC_OK, ret);
+}
+
+bool VideoTrackRecorder::VpxEncoder::IsInitialized() const {
+ DCHECK(encoding_thread_.task_runner()->BelongsToCurrentThread());
+ return codec_config_.g_timebase.den != 0;
+}
+
+base::TimeDelta VideoTrackRecorder::VpxEncoder::CalculateFrameDuration(
+ const scoped_refptr<VideoFrame>& frame) {
+ DCHECK(encoding_thread_.task_runner()->BelongsToCurrentThread());
+
+ using base::TimeDelta;
+ TimeDelta predicted_frame_duration;
+ if (!frame->metadata()->GetTimeDelta(VideoFrameMetadata::FRAME_DURATION,
+ &predicted_frame_duration) ||
+ predicted_frame_duration <= TimeDelta()) {
+ // The source of the video frame did not provide the frame duration. Use
+ // the actual amount of time between the current and previous frame as a
+ // prediction for the next frame's duration.
+ // TODO(mcasas): This duration estimation could lead to artifacts if the
+ // cadence of the received stream is compromised (e.g. camera freeze, pause,
+ // remote packet loss). Investigate using GetFrameRate() in this case.
+ predicted_frame_duration = frame->timestamp() - last_frame_timestamp_;
+ }
+ last_frame_timestamp_ = frame->timestamp();
+ // Make sure |predicted_frame_duration| is in a safe range of values.
+ const TimeDelta kMaxFrameDuration = TimeDelta::FromSecondsD(1.0 / 8);
+ const TimeDelta kMinFrameDuration = TimeDelta::FromMilliseconds(1);
+ return std::min(kMaxFrameDuration, std::max(predicted_frame_duration,
+ kMinFrameDuration));
+}
+
+VideoTrackRecorder::VideoTrackRecorder(
+ const blink::WebMediaStreamTrack& track,
+ const OnEncodedVideoCB& on_encoded_video_callback)
+ : track_(track),
+ encoder_(new VpxEncoder(on_encoded_video_callback)),
+ weak_factory_(this) {
+ DCHECK(main_render_thread_checker_.CalledOnValidThread());
+ DCHECK(!track_.isNull());
+ DCHECK(track.extraData());
+ AddToVideoTrack(this,
+ media::BindToCurrentLoop(
+ base::Bind(&VideoTrackRecorder::OnVideoFrame,
+ weak_factory_.GetWeakPtr())),
+ track_);
+}
+
+VideoTrackRecorder::~VideoTrackRecorder() {
+ DCHECK(main_render_thread_checker_.CalledOnValidThread());
+ RemoveFromVideoTrack(this, track_);
+ weak_factory_.InvalidateWeakPtrs();
+ track_.reset();
+}
+
+void VideoTrackRecorder::OnVideoFrame(const scoped_refptr<VideoFrame>& frame,
+ const base::TimeTicks& timestamp) {
+ DCHECK(main_render_thread_checker_.CalledOnValidThread());
+ encoder_->StartFrameEncode(frame, timestamp);
+}
+
+} // namespace content
« no previous file with comments | « content/renderer/media/video_track_recorder.h ('k') | content/renderer/media/video_track_recorder_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698