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

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

Issue 1886123002: [experimental] MR: add support for H264 recording (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 8 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
index 2dc0b667c7251257fe3eb7c8a6b618483bf9576e..86ef0265270b1d7bdba26a4cff2e78eb36910479 100644
--- a/content/renderer/media/video_track_recorder.cc
+++ b/content/renderer/media/video_track_recorder.cc
@@ -25,6 +25,10 @@ extern "C" {
#include "third_party/libvpx/source/libvpx/vpx/vpx_encoder.h"
}
+#include "third_party/openh264/src/codec/api/svc/codec_api.h"
+#include "third_party/openh264/src/codec/api/svc/codec_app_def.h"
+#include "third_party/openh264/src/codec/api/svc/codec_def.h"
+
using media::VideoFrame;
using media::VideoFrameMetadata;
@@ -61,6 +65,20 @@ void OnFrameEncodeCompleted(
} // anonymous namespace
+class VideoTrackRecorder::Encoder : public base::RefCountedThreadSafe<Encoder> {
+ public:
+ virtual void StartFrameEncode(const scoped_refptr<VideoFrame>& frame,
+ base::TimeTicks capture_timestamp) = 0;
+
+ virtual void set_paused(bool paused) = 0;
+
+ protected:
+ friend class base::RefCountedThreadSafe<Encoder>;
+ virtual ~Encoder() {}
+};
+
+namespace {
+
// Inner class encapsulating all libvpx interactions and the encoding+delivery
// of received frames. Limitation: Only VP8 is supported for the time being.
// This class must be ref-counted because the MediaStreamVideoTrack will hold a
@@ -73,24 +91,23 @@ void OnFrameEncodeCompleted(
// thread, but this is not enforced;
// - uses an internal |encoding_thread_| for libvpx interactions, notably for
// encoding (which might take some time).
-class VideoTrackRecorder::VpxEncoder final
- : public base::RefCountedThreadSafe<VpxEncoder> {
+class VpxEncoder final : public VideoTrackRecorder::Encoder {
public:
static void ShutdownEncoder(std::unique_ptr<base::Thread> encoding_thread,
ScopedVpxCodecCtxPtr encoder);
- VpxEncoder(bool use_vp9,
- const OnEncodedVideoCB& on_encoded_video_callback,
- int32_t bits_per_second);
+ VpxEncoder(
+ bool use_vp9,
+ const VideoTrackRecorder::OnEncodedVideoCB& on_encoded_video_callback,
+ int32_t bits_per_second);
void StartFrameEncode(const scoped_refptr<VideoFrame>& frame,
- base::TimeTicks capture_timestamp);
+ base::TimeTicks capture_timestamp) override;
- void set_paused(bool paused) { paused_ = paused; }
+ void set_paused(bool paused) override { paused_ = paused; }
private:
- friend class base::RefCountedThreadSafe<VpxEncoder>;
- ~VpxEncoder();
+ ~VpxEncoder() override;
void EncodeOnEncodingThread(const scoped_refptr<VideoFrame>& frame,
base::TimeTicks capture_timestamp);
@@ -111,7 +128,7 @@ class VideoTrackRecorder::VpxEncoder final
const bool use_vp9_;
// This callback should be exercised on IO thread.
- const OnEncodedVideoCB on_encoded_video_callback_;
+ const VideoTrackRecorder::OnEncodedVideoCB on_encoded_video_callback_;
// Target bitrate or video encoding. If 0, a standard bitrate is used.
const int32_t bits_per_second_;
@@ -131,6 +148,7 @@ class VideoTrackRecorder::VpxEncoder final
// Again, it should only be accessed on |encoding_thread_|.
ScopedVpxCodecCtxPtr 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_;
@@ -138,18 +156,254 @@ class VideoTrackRecorder::VpxEncoder final
DISALLOW_COPY_AND_ASSIGN(VpxEncoder);
};
+class H264Encoder final : public VideoTrackRecorder::Encoder {
+ public:
+ static void ShutdownEncoder(std::unique_ptr<base::Thread> encoding_thread,
+ scoped_ptr<ISVCEncoder> encoder);
+
+ H264Encoder(
+ const VideoTrackRecorder::OnEncodedVideoCB& on_encoded_video_callback,
+ int32_t bits_per_second);
+
+ void StartFrameEncode(const scoped_refptr<VideoFrame>& frame,
+ base::TimeTicks capture_timestamp) override;
+
+ void set_paused(bool paused) override { paused_ = paused; }
+
+ private:
+ ~H264Encoder() override;
+
+ void EncodeOnEncodingThread(const scoped_refptr<VideoFrame>& frame,
+ base::TimeTicks capture_timestamp);
+
+ void ConfigureEncoding(const gfx::Size& size);
+
+ // While |paused_|, frames are not encoded.
+ bool paused_;
+
+ // This callback should be exercised on IO thread.
+ const VideoTrackRecorder::OnEncodedVideoCB on_encoded_video_callback_;
+
+ // Target bitrate or video encoding. If 0, a standard bitrate is used.
+ const int32_t bits_per_second_;
+
+ // Used to shutdown properly on the same thread we were created.
+ const scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
+
+ // Task runner where frames to encode and reply callbacks must happen.
+ scoped_refptr<base::SingleThreadTaskRunner> origin_task_runner_;
+
+ // Thread for encoding. Active for the lifetime of VpxEncoder. All variables
+ // below this are used in this thread.
+ std::unique_ptr<base::Thread> encoding_thread_;
+
+ scoped_ptr<ISVCEncoder> openh264_encoder_;
+ gfx::Size configured_size_;
+
+ // The |VideoFrame::timestamp()| of the first received frame.
+ base::TimeTicks first_frame_timestamp_;
+
+ DISALLOW_COPY_AND_ASSIGN(H264Encoder);
+};
+
+// static
+void H264Encoder::ShutdownEncoder(std::unique_ptr<base::Thread> encoding_thread,
+ scoped_ptr<ISVCEncoder> encoder) {
+ DVLOG(1) << __FUNCTION__;
+ DCHECK(encoding_thread->IsRunning());
+ encoding_thread->Stop();
+
+ if (encoder) {
+ const int uninit_ret = encoder->Uninitialize();
+ DLOG_IF(ERROR, uninit_ret != 0) << "OpenH264 Uninitialize()";
+
+ WelsDestroySVCEncoder(encoder.release());
+ }
+}
+
+H264Encoder::H264Encoder(
+ const VideoTrackRecorder::OnEncodedVideoCB& on_encoded_video_callback,
+ int32_t bits_per_second)
+ : paused_(false),
+ on_encoded_video_callback_(on_encoded_video_callback),
+ bits_per_second_(bits_per_second),
+ main_task_runner_(base::MessageLoop::current()->task_runner()),
+ encoding_thread_(new base::Thread("EncodingThread")) {
+ DCHECK(!on_encoded_video_callback_.is_null());
+
+ DCHECK(!encoding_thread_->IsRunning());
+ encoding_thread_->Start();
+}
+
+void H264Encoder::StartFrameEncode(const scoped_refptr<VideoFrame>& frame,
+ base::TimeTicks capture_timestamp) {
+ DVLOG(1) << __FUNCTION__;
+ // 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());
+ if (paused_)
+ return;
+ encoding_thread_->task_runner()->PostTask(
+ FROM_HERE, base::Bind(&H264Encoder::EncodeOnEncodingThread,
+ this, frame, capture_timestamp));
+}
+
+
+void H264Encoder::EncodeOnEncodingThread(
+ const scoped_refptr<VideoFrame>& video_frame,
+ base::TimeTicks capture_timestamp) {
+ DVLOG(1) << __FUNCTION__;
+ DCHECK(encoding_thread_->task_runner()->BelongsToCurrentThread());
+
+ if (!(video_frame->format() == media::PIXEL_FORMAT_I420 ||
+ video_frame->format() == media::PIXEL_FORMAT_YV12 ||
+ video_frame->format() == media::PIXEL_FORMAT_YV12A)) {
+ NOTREACHED();
+ return;
+ }
+ scoped_refptr<media::VideoFrame> frame = video_frame;
+ // Drop alpha channel since we do not support it yet.
+ if (frame->format() == media::PIXEL_FORMAT_YV12A)
+ frame = media::WrapAsI420VideoFrame(video_frame);
+
+ const gfx::Size frame_size = frame->visible_rect().size();
+ if (!openh264_encoder_ || configured_size_ != frame_size) {
+ ConfigureEncoding(frame_size);
+ first_frame_timestamp_ = capture_timestamp;
+ }
+
+ DCHECK(openh264_encoder_);
+
+ // EncodeFrame input.
+ SSourcePicture picture = {};
+ picture.iPicWidth = frame_size.width();
+ picture.iPicHeight = frame_size.height();
+ picture.iColorFormat = EVideoFormatType::videoFormatI420;
+ picture.uiTimeStamp =
+ (capture_timestamp - first_frame_timestamp_).InMilliseconds();
+ picture.iStride[0] = video_frame->stride(VideoFrame::kYPlane);
+ picture.iStride[1] = video_frame->stride(VideoFrame::kUPlane);
+ picture.iStride[2] = video_frame->stride(VideoFrame::kVPlane);
+ picture.pData[0] = video_frame->data(VideoFrame::kYPlane);
+ picture.pData[1] = video_frame->data(VideoFrame::kUPlane);
+ picture.pData[2] = video_frame->data(VideoFrame::kVPlane);
+
+ // EncodeFrame output.
+ SFrameBSInfo info;
+ memset(&info, 0, sizeof(SFrameBSInfo));
+
+ // Encode!
+ int enc_ret = openh264_encoder_->EncodeFrame(&picture, &info);
+ if (enc_ret != cmResultSuccess) {
+ DLOG(ERROR) << "OpenH264 encoding failed, EncodeFrame says " << enc_ret;
+ NOTREACHED();
+ return;
+ }
+
+ std::unique_ptr<std::string> data(new std::string);
+ const uint8_t kNALStartCode[4] = {0, 0, 0, 1};
+ for (int layer = 0; layer < info.iLayerNum; ++layer) {
+ const SLayerBSInfo& layerInfo = info.sLayerInfo[layer];
+ // Iterate NAL units making up this layer, noting fragments.
+ size_t layer_len = 0;
+ for (int nal = 0; nal < layerInfo.iNalCount; ++nal) {
+
+ DCHECK_GE(layerInfo.pNalLengthInByte[nal], 4);
+ DCHECK_EQ(kNALStartCode[0], layerInfo.pBsBuf[layer_len+0]);
+ DCHECK_EQ(kNALStartCode[1], layerInfo.pBsBuf[layer_len+1]);
+ DCHECK_EQ(kNALStartCode[2], layerInfo.pBsBuf[layer_len+2]);
+ DCHECK_EQ(kNALStartCode[3], layerInfo.pBsBuf[layer_len+3]);
+
+ layer_len += layerInfo.pNalLengthInByte[nal];
+ }
+ // Copy the entire layer's data (including start codes).
+ data->append(reinterpret_cast<char*>(layerInfo.pBsBuf), layer_len);
+ }
+
+ const bool is_key_frame = info.eFrameType == videoFrameTypeIDR;
+
+ origin_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(OnFrameEncodeCompleted, on_encoded_video_callback_, frame,
+ base::Passed(&data), capture_timestamp, is_key_frame));
+}
+
+void H264Encoder::ConfigureEncoding(const gfx::Size& size) {
+ DVLOG(1) << __FUNCTION__;
+ configured_size_ = size;
+
+ ISVCEncoder* temp_encoder = nullptr;
+ int result = WelsCreateSVCEncoder(&temp_encoder);
+ if (result != 0) {
+ DLOG(ERROR) << "Failed to create OpenH264 encoder";
+ NOTREACHED();
+ return;
+ }
+ openh264_encoder_.reset(temp_encoder);
+
+#if DCHECK_IS_ON()
+ int trace_level = WELS_LOG_DETAIL;
+ openh264_encoder_->SetOption(ENCODER_OPTION_TRACE_LEVEL, &trace_level);
+#endif
+
+ SEncParamExt init_params;
+ openh264_encoder_->GetDefaultParams(&init_params);
+ init_params.iUsageType = CAMERA_VIDEO_REAL_TIME;
+
+ init_params.iPicWidth = size.width();
+ init_params.iPicHeight = size.height();
+ if (bits_per_second_ > 0) {
+ init_params.iRCMode = RC_BITRATE_MODE;
+ init_params.iTargetBitrate = bits_per_second_;
+ } else {
+ init_params.iRCMode = RC_OFF_MODE;
+ }
+
+ // 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.
+ init_params.iMultipleThreadIdc =
+ std::min(8, (base::SysInfo::NumberOfProcessors() + 1) / 2);
+
+ // The base spatial layer 0 is the only one we use.
+ DCHECK_EQ(1, init_params.iSpatialLayerNum);
+ init_params.sSpatialLayers[0].iVideoWidth = init_params.iPicWidth;
+ init_params.sSpatialLayers[0].iVideoHeight = init_params.iPicHeight;
+ init_params.sSpatialLayers[0].iSpatialBitrate = init_params.iTargetBitrate;
+
+ result = openh264_encoder_->InitializeExt(&init_params);
+ if (result != cmResultSuccess) {
+ DLOG(ERROR) << "Failed to initialize OpenH264 encoder";
+ NOTREACHED();
+ return;
+ }
+
+ int video_format = EVideoFormatType::videoFormatI420;
+ openh264_encoder_->SetOption(ENCODER_OPTION_DATAFORMAT,
+ &video_format);
+}
+
+H264Encoder::~H264Encoder() {
+ main_task_runner_->PostTask(FROM_HERE,
+ base::Bind(&H264Encoder::ShutdownEncoder,
+ base::Passed(&encoding_thread_),
+ base::Passed(&openh264_encoder_)));
+}
+
+//
+//
// static
-void VideoTrackRecorder::VpxEncoder::ShutdownEncoder(
- std::unique_ptr<base::Thread> encoding_thread,
- ScopedVpxCodecCtxPtr encoder) {
+void VpxEncoder::ShutdownEncoder(std::unique_ptr<base::Thread> encoding_thread,
+ ScopedVpxCodecCtxPtr encoder) {
DCHECK(encoding_thread->IsRunning());
encoding_thread->Stop();
// Both |encoding_thread| and |encoder| will be destroyed at end-of-scope.
}
-VideoTrackRecorder::VpxEncoder::VpxEncoder(
+VpxEncoder::VpxEncoder(
bool use_vp9,
- const OnEncodedVideoCB& on_encoded_video_callback,
+ const VideoTrackRecorder::OnEncodedVideoCB& on_encoded_video_callback,
int32_t bits_per_second)
: paused_(false),
use_vp9_(use_vp9),
@@ -165,16 +419,15 @@ VideoTrackRecorder::VpxEncoder::VpxEncoder(
encoding_thread_->Start();
}
-VideoTrackRecorder::VpxEncoder::~VpxEncoder() {
+VpxEncoder::~VpxEncoder() {
main_task_runner_->PostTask(FROM_HERE,
base::Bind(&VpxEncoder::ShutdownEncoder,
base::Passed(&encoding_thread_),
base::Passed(&encoder_)));
}
-void VideoTrackRecorder::VpxEncoder::StartFrameEncode(
- const scoped_refptr<VideoFrame>& frame,
- base::TimeTicks capture_timestamp) {
+void 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();
@@ -186,11 +439,11 @@ void VideoTrackRecorder::VpxEncoder::StartFrameEncode(
this, frame, capture_timestamp));
}
-void VideoTrackRecorder::VpxEncoder::EncodeOnEncodingThread(
+void VpxEncoder::EncodeOnEncodingThread(
const scoped_refptr<VideoFrame>& video_frame,
base::TimeTicks capture_timestamp) {
TRACE_EVENT0("video",
- "VideoTrackRecorder::VpxEncoder::EncodeOnEncodingThread");
+ "VpxEncoder::EncodeOnEncodingThread");
DCHECK(encoding_thread_->task_runner()->BelongsToCurrentThread());
if (!(video_frame->format() == media::PIXEL_FORMAT_I420 ||
@@ -259,7 +512,7 @@ void VideoTrackRecorder::VpxEncoder::EncodeOnEncodingThread(
keyframe));
}
-void VideoTrackRecorder::VpxEncoder::ConfigureEncoding(const gfx::Size& size) {
+void VpxEncoder::ConfigureEncoding(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
@@ -351,12 +604,12 @@ void VideoTrackRecorder::VpxEncoder::ConfigureEncoding(const gfx::Size& size) {
}
}
-bool VideoTrackRecorder::VpxEncoder::IsInitialized() const {
+bool VpxEncoder::IsInitialized() const {
DCHECK(encoding_thread_->task_runner()->BelongsToCurrentThread());
return codec_config_.g_timebase.den != 0;
}
-base::TimeDelta VideoTrackRecorder::VpxEncoder::CalculateFrameDuration(
+base::TimeDelta VpxEncoder::CalculateFrameDuration(
const scoped_refptr<VideoFrame>& frame) {
DCHECK(encoding_thread_->task_runner()->BelongsToCurrentThread());
@@ -381,22 +634,35 @@ base::TimeDelta VideoTrackRecorder::VpxEncoder::CalculateFrameDuration(
kMinFrameDuration));
}
+} // anonymous namespace
+
VideoTrackRecorder::VideoTrackRecorder(
- bool use_vp9,
+ CodecId codec,
const blink::WebMediaStreamTrack& track,
const OnEncodedVideoCB& on_encoded_video_callback,
int32_t bits_per_second)
- : track_(track),
- encoder_(
- new VpxEncoder(use_vp9, on_encoded_video_callback, bits_per_second)) {
+ : track_(track) {
DCHECK(main_render_thread_checker_.CalledOnValidThread());
DCHECK(!track_.isNull());
DCHECK(track_.getExtraData());
+ switch (codec) {
+ case CodecId::H264:
+ encoder_ = new H264Encoder(on_encoded_video_callback, bits_per_second);
+ break;
+ case CodecId::VP8:
+ case CodecId::VP9:
+ encoder_ = new VpxEncoder(codec == CodecId::VP9,
+ on_encoded_video_callback, bits_per_second);
+ break;
+ default:
+ NOTREACHED();
+ }
+
// StartFrameEncode() will be called on Render IO thread.
MediaStreamVideoSink::ConnectToTrack(
track_,
- base::Bind(&VideoTrackRecorder::VpxEncoder::StartFrameEncode, encoder_));
+ base::Bind(&VideoTrackRecorder::Encoder::StartFrameEncode, encoder_));
}
VideoTrackRecorder::~VideoTrackRecorder() {
« 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