blob: 1c0e4149034c44a0421b2934085f039416027727 [file] [log] [blame]
// 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_recorder/video_track_recorder.h"
#include <utility>
#include "base/bind.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/sys_info.h"
#include "base/task_runner_util.h"
#include "base/threading/thread.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "cc/paint/paint_canvas.h"
#include "cc/paint/paint_surface.h"
#include "content/renderer/media/renderer_gpu_video_accelerator_factories.h"
#include "content/renderer/render_thread_impl.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/video_frame.h"
#include "media/base/video_util.h"
#include "media/filters/context_3d.h"
#include "media/renderers/skcanvas_video_renderer.h"
#include "services/ui/public/cpp/gpu/context_provider_command_buffer.h"
#include "skia/ext/platform_canvas.h"
#include "third_party/libyuv/include/libyuv.h"
#include "ui/gfx/geometry/size.h"
#if defined(OS_WIN)
#include "base/win/windows_version.h"
#endif
#if BUILDFLAG(RTC_USE_H264)
#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"
#endif // #if BUILDFLAG(RTC_USE_H264)
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;
using video_track_recorder::kVEAEncoderMinResolutionWidth;
using video_track_recorder::kVEAEncoderMinResolutionHeight;
namespace content {
namespace {
// HW encoders expect a nonzero bitrate, so |kVEADefaultBitratePerPixel| is used
// to estimate bits per second for ~30 fps with ~1/16 compression rate.
const int kVEADefaultBitratePerPixel = 2;
// Number of output buffers used to copy the encoded data coming from HW
// encoders.
const int kVEAEncoderOutputBufferCount = 4;
using CodecId = VideoTrackRecorder::CodecId;
static const struct {
CodecId codec_id;
media::VideoCodecProfile min_profile;
media::VideoCodecProfile max_profile;
} kPreferredCodecIdAndVEAProfiles[] = {
{CodecId::VP8, media::VP8PROFILE_MIN, media::VP8PROFILE_MAX},
{CodecId::VP9, media::VP9PROFILE_MIN, media::VP9PROFILE_MAX},
#if BUILDFLAG(RTC_USE_H264)
{CodecId::H264, media::H264PROFILE_MIN, media::H264PROFILE_MAX}
#endif
};
static_assert(arraysize(kPreferredCodecIdAndVEAProfiles) ==
static_cast<int>(CodecId::LAST),
"|kPreferredCodecIdAndVEAProfiles| should consider all CodecIds");
// Class to encapsulate the enumeration of CodecIds/VideoCodecProfiles supported
// by the VEA underlying platform. Provides methods to query the preferred
// CodecId and to check if a given CodecId is supported.
class CodecEnumerator {
public:
CodecEnumerator();
~CodecEnumerator() = default;
// Returns the first CodecId that has an associated VEA VideoCodecProfile, or
// VP8 if none available.
CodecId GetPreferredCodecId();
// Returns the VEA VideoCodedProfile for a given CodecId, if supported, or
// VIDEO_CODEC_PROFILE_UNKNOWN otherwise.
media::VideoCodecProfile CodecIdToVEAProfile(CodecId codec);
private:
// A map of VEA-supported CodecId-and-VEA-profile pairs.
std::map<CodecId, media::VideoCodecProfile> codec_id_to_profile_;
DISALLOW_COPY_AND_ASSIGN(CodecEnumerator);
};
CodecEnumerator* GetCodecEnumerator() {
static CodecEnumerator* enumerator = new CodecEnumerator();
return enumerator;
}
CodecEnumerator::CodecEnumerator() {
#if defined(OS_CHROMEOS)
// See https://crbug.com/616659.
return;
#endif
#if defined(OS_ANDROID)
// See https://crbug.com/653864.
return;
#endif
#if defined(OS_WIN)
// See https://crbug.com/698441.
if (base::win::GetVersion() < base::win::VERSION_WIN10)
return;
#endif
content::RenderThreadImpl* const render_thread_impl =
content::RenderThreadImpl::current();
if (!render_thread_impl) {
DVLOG(2) << "Couldn't access the render thread";
return;
}
media::GpuVideoAcceleratorFactories* const gpu_factories =
render_thread_impl->GetGpuFactories();
if (!gpu_factories || !gpu_factories->IsGpuVideoAcceleratorEnabled()) {
DVLOG(2) << "Couldn't initialize GpuVideoAcceleratorFactories";
return;
}
const auto vea_supported_profiles =
gpu_factories->GetVideoEncodeAcceleratorSupportedProfiles();
for (const auto& supported_profile : vea_supported_profiles) {
for (auto& codec_id_and_profile : kPreferredCodecIdAndVEAProfiles) {
if (supported_profile.profile >= codec_id_and_profile.min_profile &&
supported_profile.profile <= codec_id_and_profile.max_profile) {
DVLOG(2) << "Accelerated codec found: "
<< media::GetProfileName(supported_profile.profile);
codec_id_to_profile_.insert(std::make_pair(
codec_id_and_profile.codec_id, supported_profile.profile));
}
}
}
}
CodecId CodecEnumerator::GetPreferredCodecId() {
if (codec_id_to_profile_.empty())
return CodecId::VP8;
return codec_id_to_profile_.begin()->first;
}
media::VideoCodecProfile CodecEnumerator::CodecIdToVEAProfile(CodecId codec) {
const auto profile = codec_id_to_profile_.find(codec);
return profile == codec_id_to_profile_.end()
? media::VIDEO_CODEC_PROFILE_UNKNOWN
: profile->second;
}
} // anonymous namespace
// Base class to describe a generic Encoder, encapsulating all actual encoder
// (re)configurations, encoding and delivery of received frames. This class is
// ref-counted to allow the MediaStreamVideoTrack to hold a reference to it (via
// the callback that MediaStreamVideoSink passes along) and to jump back and
// forth to an internal encoder thread. Moreover, this class:
// - is created on its parent's thread (usually the main Render thread),
// that is, |main_task_runner_|.
// - receives VideoFrames on |origin_task_runner_| and runs OnEncodedVideoCB on
// that thread as well. This task runner is cached on first frame arrival, and
// is supposed to be the render IO thread (but this is not enforced);
// - uses an internal |encoding_task_runner_| for actual encoder interactions,
// namely configuration, encoding (which might take some time) and destruction.
// This task runner can be passed on the creation. If nothing is passed, a new
// encoding thread is created and used.
class VideoTrackRecorder::Encoder : public base::RefCountedThreadSafe<Encoder> {
public:
Encoder(const OnEncodedVideoCB& on_encoded_video_callback,
int32_t bits_per_second,
scoped_refptr<base::SingleThreadTaskRunner> encoding_task_runner =
nullptr)
: main_task_runner_(base::ThreadTaskRunnerHandle::Get()),
encoding_task_runner_(encoding_task_runner),
paused_(false),
on_encoded_video_callback_(on_encoded_video_callback),
bits_per_second_(bits_per_second) {
DCHECK(!on_encoded_video_callback_.is_null());
if (encoding_task_runner_)
return;
encoding_thread_.reset(new base::Thread("EncodingThread"));
encoding_thread_->Start();
encoding_task_runner_ = encoding_thread_->task_runner();
}
// Start encoding |frame|, returning via |on_encoded_video_callback_|. This
// call will also trigger an encode configuration upon first frame arrival
// or parameter change, and an EncodeOnEncodingTaskRunner() to actually
// encode the frame. If the |frame|'s data is not directly available (e.g.
// it's a texture) then RetrieveFrameOnMainThread() is called, and if even
// that fails, black frames are sent instead.
void StartFrameEncode(const scoped_refptr<VideoFrame>& frame,
base::TimeTicks capture_timestamp);
void RetrieveFrameOnMainThread(const scoped_refptr<VideoFrame>& video_frame,
base::TimeTicks capture_timestamp);
void SetPaused(bool paused);
virtual bool CanEncodeAlphaChannel() { return false; }
protected:
friend class base::RefCountedThreadSafe<Encoder>;
virtual ~Encoder() {
main_task_runner_->DeleteSoon(FROM_HERE, video_renderer_.release());
}
virtual void EncodeOnEncodingTaskRunner(
scoped_refptr<VideoFrame> frame,
base::TimeTicks capture_timestamp) = 0;
// 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_;
// Task runner where encoding interactions happen.
scoped_refptr<base::SingleThreadTaskRunner> encoding_task_runner_;
// Optional thread for encoding. Active for the lifetime of VpxEncoder.
std::unique_ptr<base::Thread> encoding_thread_;
// While |paused_|, frames are not encoded. Used only from |encoding_thread_|.
bool paused_;
// This callback should be exercised on IO thread.
const OnEncodedVideoCB on_encoded_video_callback_;
// Target bitrate for video encoding. If 0, a standard bitrate is used.
const int32_t bits_per_second_;
// Used to retrieve incoming opaque VideoFrames (i.e. VideoFrames backed by
// textures). Created on-demand on |main_task_runner_|.
std::unique_ptr<media::SkCanvasVideoRenderer> video_renderer_;
sk_sp<cc::PaintSurface> surface_;
DISALLOW_COPY_AND_ASSIGN(Encoder);
};
void VideoTrackRecorder::Encoder::StartFrameEncode(
const scoped_refptr<VideoFrame>& video_frame,
base::TimeTicks capture_timestamp) {
// Cache the thread sending frames on first frame arrival.
if (!origin_task_runner_.get())
origin_task_runner_ = base::ThreadTaskRunnerHandle::Get();
DCHECK(origin_task_runner_->BelongsToCurrentThread());
if (paused_)
return;
if (!(video_frame->format() == media::PIXEL_FORMAT_I420 ||
video_frame->format() == media::PIXEL_FORMAT_YV12 ||
video_frame->format() == media::PIXEL_FORMAT_ARGB ||
video_frame->format() == media::PIXEL_FORMAT_YV12A)) {
NOTREACHED() << media::VideoPixelFormatToString(video_frame->format());
return;
}
if (video_frame->HasTextures()) {
main_task_runner_->PostTask(
FROM_HERE, base::Bind(&Encoder::RetrieveFrameOnMainThread, this,
video_frame, capture_timestamp));
return;
}
scoped_refptr<media::VideoFrame> frame = video_frame;
// Drop alpha channel if the encoder does not support it yet.
if (!CanEncodeAlphaChannel() && frame->format() == media::PIXEL_FORMAT_YV12A)
frame = media::WrapAsI420VideoFrame(video_frame);
encoding_task_runner_->PostTask(
FROM_HERE, base::Bind(&Encoder::EncodeOnEncodingTaskRunner, this, frame,
capture_timestamp));
}
void VideoTrackRecorder::Encoder::RetrieveFrameOnMainThread(
const scoped_refptr<VideoFrame>& video_frame,
base::TimeTicks capture_timestamp) {
DCHECK(main_task_runner_->BelongsToCurrentThread());
scoped_refptr<media::VideoFrame> frame;
// |context_provider| is null if the GPU process has crashed or isn't there.
ui::ContextProviderCommandBuffer* const context_provider =
RenderThreadImpl::current()->SharedMainThreadContextProvider().get();
if (!context_provider) {
// Send black frames (yuv = {0, 127, 127}).
frame = media::VideoFrame::CreateColorFrame(
video_frame->visible_rect().size(), 0u, 0x80, 0x80,
video_frame->timestamp());
} else {
// Accelerated decoders produce ARGB/ABGR texture-backed frames (see
// https://crbug.com/585242), fetch them using a SkCanvasVideoRenderer.
DCHECK(video_frame->HasTextures());
DCHECK_EQ(media::PIXEL_FORMAT_ARGB, video_frame->format());
frame = media::VideoFrame::CreateFrame(
media::PIXEL_FORMAT_I420, video_frame->coded_size(),
video_frame->visible_rect(), video_frame->natural_size(),
video_frame->timestamp());
const SkImageInfo info = SkImageInfo::MakeN32(
frame->visible_rect().width(), frame->visible_rect().height(),
kOpaque_SkAlphaType);
// Create |surface_| if it doesn't exist or incoming resolution has changed.
if (!surface_ || surface_->width() != info.width() ||
surface_->height() != info.height()) {
surface_ = cc::PaintSurface::MakeRaster(info);
}
if (!video_renderer_)
video_renderer_.reset(new media::SkCanvasVideoRenderer);
DCHECK(context_provider->ContextGL());
video_renderer_->Copy(video_frame.get(), surface_->getCanvas(),
media::Context3D(context_provider->ContextGL(),
context_provider->GrContext()));
SkPixmap pixmap;
if (!cc::ToPixmap(surface_->getCanvas(), &pixmap)) {
DLOG(ERROR) << "Error trying to map PaintSurface's pixels";
return;
}
// TODO(mcasas): Use the incoming frame's rotation when
// https://bugs.chromium.org/p/webrtc/issues/detail?id=6069 is closed.
const libyuv::RotationMode source_rotation = libyuv::kRotate0;
const uint32 source_pixel_format =
(kN32_SkColorType == kRGBA_8888_SkColorType) ? libyuv::FOURCC_ABGR
: libyuv::FOURCC_ARGB;
if (libyuv::ConvertToI420(static_cast<uint8*>(pixmap.writable_addr()),
pixmap.getSafeSize(),
frame->visible_data(media::VideoFrame::kYPlane),
frame->stride(media::VideoFrame::kYPlane),
frame->visible_data(media::VideoFrame::kUPlane),
frame->stride(media::VideoFrame::kUPlane),
frame->visible_data(media::VideoFrame::kVPlane),
frame->stride(media::VideoFrame::kVPlane),
0 /* crop_x */, 0 /* crop_y */,
pixmap.width(), pixmap.height(),
frame->visible_rect().width(),
frame->visible_rect().height(),
source_rotation,
source_pixel_format) != 0) {
DLOG(ERROR) << "Error converting frame to I420";
return;
}
}
encoding_task_runner_->PostTask(
FROM_HERE, base::Bind(&Encoder::EncodeOnEncodingTaskRunner, this, frame,
capture_timestamp));
}
void VideoTrackRecorder::Encoder::SetPaused(bool paused) {
if (!encoding_task_runner_->BelongsToCurrentThread()) {
encoding_task_runner_->PostTask(
FROM_HERE, base::Bind(&Encoder::SetPaused, this, paused));
return;
}
paused_ = paused;
}
namespace {
// Originally from remoting/codec/scoped_vpx_codec.h.
// TODO(mcasas): Refactor into a common location.
struct VpxCodecDeleter {
void operator()(vpx_codec_ctx_t* codec) {
if (!codec)
return;
vpx_codec_err_t ret = vpx_codec_destroy(codec);
CHECK_EQ(ret, VPX_CODEC_OK);
delete codec;
}
};
typedef std::unique_ptr<vpx_codec_ctx_t, VpxCodecDeleter> ScopedVpxCodecCtxPtr;
static void OnFrameEncodeCompleted(
const VideoTrackRecorder::OnEncodedVideoCB& on_encoded_video_cb,
const media::WebmMuxer::VideoParameters& params,
std::unique_ptr<std::string> data,
std::unique_ptr<std::string> alpha_data,
base::TimeTicks capture_timestamp,
bool keyframe) {
DVLOG(1) << (keyframe ? "" : "non ") << "keyframe "<< data->length() << "B, "
<< capture_timestamp << " ms";
on_encoded_video_cb.Run(params, std::move(data), std::move(alpha_data),
capture_timestamp, keyframe);
}
static int GetNumberOfThreadsForEncoding() {
// 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.
return std::min(8, (base::SysInfo::NumberOfProcessors() + 1) / 2);
}
// Class encapsulating VideoEncodeAccelerator interactions.
// This class is created and destroyed in its owner thread. All other methods
// operate on the task runner pointed by GpuFactories.
class VEAEncoder final : public VideoTrackRecorder::Encoder,
public media::VideoEncodeAccelerator::Client {
public:
VEAEncoder(
const VideoTrackRecorder::OnEncodedVideoCB& on_encoded_video_callback,
int32_t bits_per_second,
media::VideoCodecProfile codec,
const gfx::Size& size);
// media::VideoEncodeAccelerator::Client implementation.
void RequireBitstreamBuffers(unsigned int input_count,
const gfx::Size& input_coded_size,
size_t output_buffer_size) override;
void BitstreamBufferReady(int32_t bitstream_buffer_id,
size_t payload_size,
bool key_frame,
base::TimeDelta timestamp) override;
void NotifyError(media::VideoEncodeAccelerator::Error error) override;
private:
using VideoFrameAndTimestamp =
std::pair<scoped_refptr<media::VideoFrame>, base::TimeTicks>;
using VideoParamsAndTimestamp =
std::pair<media::WebmMuxer::VideoParameters, base::TimeTicks>;
void UseOutputBitstreamBufferId(int32_t bitstream_buffer_id);
void FrameFinished(std::unique_ptr<base::SharedMemory> shm);
// VideoTrackRecorder::Encoder implementation.
~VEAEncoder() override;
void EncodeOnEncodingTaskRunner(scoped_refptr<VideoFrame> frame,
base::TimeTicks capture_timestamp) override;
void ConfigureEncoderOnEncodingTaskRunner(const gfx::Size& size);
media::GpuVideoAcceleratorFactories* const gpu_factories_;
const media::VideoCodecProfile codec_;
// The underlying VEA to perform encoding on.
std::unique_ptr<media::VideoEncodeAccelerator> video_encoder_;
// Shared memory buffers for output with the VEA.
std::vector<std::unique_ptr<base::SharedMemory>> output_buffers_;
// Shared memory buffers for output with the VEA as FIFO.
std::queue<std::unique_ptr<base::SharedMemory>> input_buffers_;
// Tracks error status.
bool error_notified_;
// Tracks the last frame that we delay the encode.
std::unique_ptr<VideoFrameAndTimestamp> last_frame_;
// Size used to initialize encoder.
gfx::Size input_size_;
// Coded size that encoder requests as input.
gfx::Size vea_requested_input_size_;
// Frames and corresponding timestamps in encode as FIFO.
std::queue<VideoParamsAndTimestamp> frames_in_encode_;
};
// Class encapsulating all libvpx interactions for VP8/VP9 encoding.
class VpxEncoder final : public VideoTrackRecorder::Encoder {
public:
static void ShutdownEncoder(std::unique_ptr<base::Thread> encoding_thread,
ScopedVpxCodecCtxPtr encoder);
VpxEncoder(
bool use_vp9,
const VideoTrackRecorder::OnEncodedVideoCB& on_encoded_video_callback,
int32_t bits_per_second);
private:
// VideoTrackRecorder::Encoder implementation.
~VpxEncoder() override;
void EncodeOnEncodingTaskRunner(scoped_refptr<VideoFrame> frame,
base::TimeTicks capture_timestamp) override;
bool CanEncodeAlphaChannel() override { return true; }
void ConfigureEncoderOnEncodingTaskRunner(const gfx::Size& size,
vpx_codec_enc_cfg_t* codec_config,
ScopedVpxCodecCtxPtr* encoder);
void DoEncode(vpx_codec_ctx_t* const encoder,
const gfx::Size& frame_size,
uint8_t* const data,
uint8_t* const y_plane,
int y_stride,
uint8_t* const u_plane,
int u_stride,
uint8_t* const v_plane,
int v_stride,
const base::TimeDelta& duration,
bool force_keyframe,
std::string* const output_data,
bool* const keyframe);
// Returns true if |codec_config| has been filled in at least once.
bool IsInitialized(const vpx_codec_enc_cfg_t& codec_config) const;
// Estimate the frame duration from |frame| and |last_frame_timestamp_|.
base::TimeDelta EstimateFrameDuration(const scoped_refptr<VideoFrame>& frame);
// Force usage of VP9 for encoding, instead of VP8 which is the default.
const bool use_vp9_;
// VPx internal objects: configuration and encoder. |encoder_| is a special
// scoped pointer to guarantee proper destruction, particularly when
// reconfiguring due to parameters change. Only used on |encoding_thread_|.
vpx_codec_enc_cfg_t codec_config_;
ScopedVpxCodecCtxPtr encoder_;
vpx_codec_enc_cfg_t alpha_codec_config_;
ScopedVpxCodecCtxPtr alpha_encoder_;
std::vector<uint8_t> alpha_dummy_planes_;
size_t v_plane_offset_;
size_t u_plane_stride_;
size_t v_plane_stride_;
bool last_frame_had_alpha_ = false;
// The |VideoFrame::timestamp()| of the last encoded frame. This is used to
// predict the duration of the next frame. Only used on |encoding_thread_|.
base::TimeDelta last_frame_timestamp_;
DISALLOW_COPY_AND_ASSIGN(VpxEncoder);
};
#if BUILDFLAG(RTC_USE_H264)
struct ISVCEncoderDeleter {
void operator()(ISVCEncoder* codec) {
if (!codec)
return;
const int uninit_ret = codec->Uninitialize();
CHECK_EQ(cmResultSuccess, uninit_ret);
WelsDestroySVCEncoder(codec);
}
};
typedef std::unique_ptr<ISVCEncoder, ISVCEncoderDeleter> ScopedISVCEncoderPtr;
// Class encapsulating all openh264 interactions for H264 encoding.
class H264Encoder final : public VideoTrackRecorder::Encoder {
public:
static void ShutdownEncoder(std::unique_ptr<base::Thread> encoding_thread,
ScopedISVCEncoderPtr encoder);
H264Encoder(
const VideoTrackRecorder::OnEncodedVideoCB& on_encoded_video_callback,
int32_t bits_per_second);
private:
// VideoTrackRecorder::Encoder implementation.
~H264Encoder() override;
void EncodeOnEncodingTaskRunner(scoped_refptr<VideoFrame> frame,
base::TimeTicks capture_timestamp) override;
void ConfigureEncoderOnEncodingTaskRunner(const gfx::Size& size);
// |openh264_encoder_| is a special scoped pointer to guarantee proper
// destruction, also when reconfiguring due to parameters change. Only used on
// |encoding_thread_|.
gfx::Size configured_size_;
ScopedISVCEncoderPtr openh264_encoder_;
// The |VideoFrame::timestamp()| of the first received frame. Only used on
// |encoding_thread_|.
base::TimeTicks first_frame_timestamp_;
DISALLOW_COPY_AND_ASSIGN(H264Encoder);
};
#endif // #if BUILDFLAG(RTC_USE_H264)
VEAEncoder::VEAEncoder(
const VideoTrackRecorder::OnEncodedVideoCB& on_encoded_video_callback,
int32_t bits_per_second,
media::VideoCodecProfile codec,
const gfx::Size& size)
: Encoder(on_encoded_video_callback,
bits_per_second > 0 ? bits_per_second
: size.GetArea() * kVEADefaultBitratePerPixel,
RenderThreadImpl::current()->GetGpuFactories()->GetTaskRunner()),
gpu_factories_(RenderThreadImpl::current()->GetGpuFactories()),
codec_(codec),
error_notified_(false) {
DCHECK(gpu_factories_);
DCHECK_GE(size.width(), kVEAEncoderMinResolutionWidth);
DCHECK_GE(size.height(), kVEAEncoderMinResolutionHeight);
encoding_task_runner_->PostTask(
FROM_HERE, base::Bind(&VEAEncoder::ConfigureEncoderOnEncodingTaskRunner,
this, size));
}
VEAEncoder::~VEAEncoder() {
encoding_task_runner_->PostTask(
FROM_HERE, base::Bind(&media::VideoEncodeAccelerator::Destroy,
base::Unretained(video_encoder_.release())));
}
void VEAEncoder::RequireBitstreamBuffers(unsigned int /*input_count*/,
const gfx::Size& input_coded_size,
size_t output_buffer_size) {
DVLOG(3) << __func__;
DCHECK(encoding_task_runner_->BelongsToCurrentThread());
vea_requested_input_size_ = input_coded_size;
output_buffers_.clear();
std::queue<std::unique_ptr<base::SharedMemory>>().swap(input_buffers_);
for (int i = 0; i < kVEAEncoderOutputBufferCount; ++i) {
std::unique_ptr<base::SharedMemory> shm =
gpu_factories_->CreateSharedMemory(output_buffer_size);
if (shm)
output_buffers_.push_back(base::WrapUnique(shm.release()));
}
for (size_t i = 0; i < output_buffers_.size(); ++i)
UseOutputBitstreamBufferId(i);
}
void VEAEncoder::BitstreamBufferReady(int32_t bitstream_buffer_id,
size_t payload_size,
bool keyframe,
base::TimeDelta timestamp) {
DVLOG(3) << __func__;
DCHECK(encoding_task_runner_->BelongsToCurrentThread());
base::SharedMemory* output_buffer =
output_buffers_[bitstream_buffer_id].get();
std::unique_ptr<std::string> data(new std::string);
data->append(reinterpret_cast<char*>(output_buffer->memory()), payload_size);
const auto front_frame = frames_in_encode_.front();
frames_in_encode_.pop();
origin_task_runner_->PostTask(
FROM_HERE, base::Bind(OnFrameEncodeCompleted, on_encoded_video_callback_,
front_frame.first, base::Passed(&data), nullptr,
front_frame.second, keyframe));
UseOutputBitstreamBufferId(bitstream_buffer_id);
}
void VEAEncoder::NotifyError(media::VideoEncodeAccelerator::Error error) {
DVLOG(3) << __func__;
DCHECK(encoding_task_runner_->BelongsToCurrentThread());
// TODO(emircan): Notify the owner via a callback.
error_notified_ = true;
}
void VEAEncoder::UseOutputBitstreamBufferId(int32_t bitstream_buffer_id) {
DVLOG(3) << __func__;
DCHECK(encoding_task_runner_->BelongsToCurrentThread());
video_encoder_->UseOutputBitstreamBuffer(media::BitstreamBuffer(
bitstream_buffer_id, output_buffers_[bitstream_buffer_id]->handle(),
output_buffers_[bitstream_buffer_id]->mapped_size()));
}
void VEAEncoder::FrameFinished(std::unique_ptr<base::SharedMemory> shm) {
DVLOG(3) << __func__;
DCHECK(encoding_task_runner_->BelongsToCurrentThread());
input_buffers_.push(std::move(shm));
}
void VEAEncoder::EncodeOnEncodingTaskRunner(
scoped_refptr<VideoFrame> frame,
base::TimeTicks capture_timestamp) {
DVLOG(3) << __func__;
DCHECK(encoding_task_runner_->BelongsToCurrentThread());
if (input_size_ != frame->visible_rect().size() && video_encoder_) {
video_encoder_->Destroy();
video_encoder_.reset();
}
if (!video_encoder_)
ConfigureEncoderOnEncodingTaskRunner(frame->visible_rect().size());
if (error_notified_) {
DVLOG(3) << "An error occurred in VEA encoder";
return;
}
// Drop frames if there is no output buffers available.
if (output_buffers_.empty()) {
// TODO(emircan): Investigate if resetting encoder would help.
DVLOG(3) << "Might drop frame.";
last_frame_.reset(
new std::pair<scoped_refptr<VideoFrame>, base::TimeTicks>(
frame, capture_timestamp));
return;
}
// If first frame hasn't been encoded, do it first.
if (last_frame_) {
std::unique_ptr<VideoFrameAndTimestamp> last_frame(last_frame_.release());
EncodeOnEncodingTaskRunner(last_frame->first, last_frame->second);
}
// Lower resolutions may fall back to SW encoder in some platforms, i.e. Mac.
// In that case, the encoder expects more frames before returning result.
// Therefore, a copy is necessary to release the current frame.
// Only STORAGE_SHMEM backed frames can be shared with GPU process, therefore
// a copy is required for other storage types.
scoped_refptr<media::VideoFrame> video_frame = frame;
if (video_frame->storage_type() != VideoFrame::STORAGE_SHMEM ||
vea_requested_input_size_ != input_size_ ||
input_size_.width() < kVEAEncoderMinResolutionWidth ||
input_size_.height() < kVEAEncoderMinResolutionHeight) {
// Create SharedMemory backed input buffers as necessary. These SharedMemory
// instances will be shared with GPU process.
std::unique_ptr<base::SharedMemory> input_buffer;
const size_t desired_mapped_size = media::VideoFrame::AllocationSize(
media::PIXEL_FORMAT_I420, vea_requested_input_size_);
if (input_buffers_.empty()) {
input_buffer = gpu_factories_->CreateSharedMemory(desired_mapped_size);
} else {
do {
input_buffer = std::move(input_buffers_.front());
input_buffers_.pop();
} while (!input_buffers_.empty() &&
input_buffer->mapped_size() < desired_mapped_size);
if (!input_buffer || input_buffer->mapped_size() < desired_mapped_size)
return;
}
video_frame = media::VideoFrame::WrapExternalSharedMemory(
media::PIXEL_FORMAT_I420, vea_requested_input_size_,
gfx::Rect(input_size_), input_size_,
reinterpret_cast<uint8_t*>(input_buffer->memory()),
input_buffer->mapped_size(), input_buffer->handle(), 0,
frame->timestamp());
video_frame->AddDestructionObserver(media::BindToCurrentLoop(
base::Bind(&VEAEncoder::FrameFinished, this,
base::Passed(std::move(input_buffer)))));
libyuv::I420Copy(frame->visible_data(media::VideoFrame::kYPlane),
frame->stride(media::VideoFrame::kYPlane),
frame->visible_data(media::VideoFrame::kUPlane),
frame->stride(media::VideoFrame::kUPlane),
frame->visible_data(media::VideoFrame::kVPlane),
frame->stride(media::VideoFrame::kVPlane),
video_frame->visible_data(media::VideoFrame::kYPlane),
video_frame->stride(media::VideoFrame::kYPlane),
video_frame->visible_data(media::VideoFrame::kUPlane),
video_frame->stride(media::VideoFrame::kUPlane),
video_frame->visible_data(media::VideoFrame::kVPlane),
video_frame->stride(media::VideoFrame::kVPlane),
input_size_.width(), input_size_.height());
}
frames_in_encode_.push(std::make_pair(
media::WebmMuxer::VideoParameters(frame), capture_timestamp));
encoding_task_runner_->PostTask(
FROM_HERE,
base::Bind(&media::VideoEncodeAccelerator::Encode,
base::Unretained(video_encoder_.get()), video_frame, false));
}
void VEAEncoder::ConfigureEncoderOnEncodingTaskRunner(const gfx::Size& size) {
DVLOG(3) << __func__;
DCHECK(encoding_task_runner_->BelongsToCurrentThread());
DCHECK(gpu_factories_->GetTaskRunner()->BelongsToCurrentThread());
DCHECK_GT(bits_per_second_, 0);
input_size_ = size;
video_encoder_ = gpu_factories_->CreateVideoEncodeAccelerator();
if (!video_encoder_ ||
!video_encoder_->Initialize(media::PIXEL_FORMAT_I420, input_size_, codec_,
bits_per_second_, this)) {
NotifyError(media::VideoEncodeAccelerator::kPlatformFailureError);
}
}
// static
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.
}
VpxEncoder::VpxEncoder(
bool use_vp9,
const VideoTrackRecorder::OnEncodedVideoCB& on_encoded_video_callback,
int32_t bits_per_second)
: Encoder(on_encoded_video_callback, bits_per_second),
use_vp9_(use_vp9) {
codec_config_.g_timebase.den = 0; // Not initialized.
DCHECK(encoding_thread_->IsRunning());
}
VpxEncoder::~VpxEncoder() {
main_task_runner_->PostTask(FROM_HERE,
base::Bind(&VpxEncoder::ShutdownEncoder,
base::Passed(&encoding_thread_),
base::Passed(&encoder_)));
}
void VpxEncoder::EncodeOnEncodingTaskRunner(
scoped_refptr<VideoFrame> frame,
base::TimeTicks capture_timestamp) {
TRACE_EVENT0("video", "VpxEncoder::EncodeOnEncodingTaskRunner");
DCHECK(encoding_task_runner_->BelongsToCurrentThread());
const gfx::Size frame_size = frame->visible_rect().size();
const base::TimeDelta duration = EstimateFrameDuration(frame);
const media::WebmMuxer::VideoParameters video_params(frame);
if (!IsInitialized(codec_config_) ||
gfx::Size(codec_config_.g_w, codec_config_.g_h) != frame_size) {
ConfigureEncoderOnEncodingTaskRunner(frame_size, &codec_config_, &encoder_);
}
const bool frame_has_alpha = frame->format() == media::PIXEL_FORMAT_YV12A;
if (frame_has_alpha && (!IsInitialized(alpha_codec_config_) ||
gfx::Size(alpha_codec_config_.g_w,
alpha_codec_config_.g_h) != frame_size)) {
ConfigureEncoderOnEncodingTaskRunner(frame_size, &alpha_codec_config_,
&alpha_encoder_);
u_plane_stride_ = media::VideoFrame::RowBytes(
VideoFrame::kUPlane, frame->format(), frame_size.width());
v_plane_stride_ = media::VideoFrame::RowBytes(
VideoFrame::kVPlane, frame->format(), frame_size.width());
v_plane_offset_ = media::VideoFrame::PlaneSize(
frame->format(), VideoFrame::kUPlane, frame_size)
.GetArea();
alpha_dummy_planes_.resize(
v_plane_offset_ + media::VideoFrame::PlaneSize(
frame->format(), VideoFrame::kVPlane, frame_size)
.GetArea());
// It is more expensive to encode 0x00, so use 0x80 instead.
std::fill(alpha_dummy_planes_.begin(), alpha_dummy_planes_.end(), 0x80);
}
// If we introduced a new alpha frame, force keyframe.
const bool force_keyframe = frame_has_alpha && !last_frame_had_alpha_;
last_frame_had_alpha_ = frame_has_alpha;
std::unique_ptr<std::string> data(new std::string);
bool keyframe = false;
DoEncode(encoder_.get(), frame_size, frame->data(VideoFrame::kYPlane),
frame->visible_data(VideoFrame::kYPlane),
frame->stride(VideoFrame::kYPlane),
frame->visible_data(VideoFrame::kUPlane),
frame->stride(VideoFrame::kUPlane),
frame->visible_data(VideoFrame::kVPlane),
frame->stride(VideoFrame::kVPlane), duration, force_keyframe,
data.get(), &keyframe);
std::unique_ptr<std::string> alpha_data(new std::string);
if (frame_has_alpha) {
bool alpha_keyframe = false;
DoEncode(alpha_encoder_.get(), frame_size, frame->data(VideoFrame::kAPlane),
frame->visible_data(VideoFrame::kAPlane),
frame->stride(VideoFrame::kAPlane), alpha_dummy_planes_.data(),
u_plane_stride_, alpha_dummy_planes_.data() + v_plane_offset_,
v_plane_stride_, duration, keyframe, alpha_data.get(),
&alpha_keyframe);
DCHECK_EQ(keyframe, alpha_keyframe);
}
frame = nullptr;
origin_task_runner_->PostTask(
FROM_HERE,
base::Bind(OnFrameEncodeCompleted, on_encoded_video_callback_,
video_params, base::Passed(&data), base::Passed(&alpha_data),
capture_timestamp, keyframe));
}
void VpxEncoder::DoEncode(vpx_codec_ctx_t* const encoder,
const gfx::Size& frame_size,
uint8_t* const data,
uint8_t* const y_plane,
int y_stride,
uint8_t* const u_plane,
int u_stride,
uint8_t* const v_plane,
int v_stride,
const base::TimeDelta& duration,
bool force_keyframe,
std::string* const output_data,
bool* const keyframe) {
DCHECK(encoding_task_runner_->BelongsToCurrentThread());
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 */, data);
DCHECK_EQ(result, &vpx_image);
vpx_image.planes[VPX_PLANE_Y] = y_plane;
vpx_image.planes[VPX_PLANE_U] = u_plane;
vpx_image.planes[VPX_PLANE_V] = v_plane;
vpx_image.stride[VPX_PLANE_Y] = y_stride;
vpx_image.stride[VPX_PLANE_U] = u_stride;
vpx_image.stride[VPX_PLANE_V] = v_stride;
const vpx_codec_flags_t flags = force_keyframe ? VPX_EFLAG_FORCE_KF : 0;
// 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(), flags, VPX_DL_REALTIME);
DCHECK_EQ(ret, VPX_CODEC_OK)
<< vpx_codec_err_to_string(ret) << ", #" << vpx_codec_error(encoder)
<< " -" << vpx_codec_error_detail(encoder);
*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;
output_data->assign(static_cast<char*>(pkt->data.frame.buf),
pkt->data.frame.sz);
*keyframe = (pkt->data.frame.flags & VPX_FRAME_IS_KEY) != 0;
break;
}
}
void VpxEncoder::ConfigureEncoderOnEncodingTaskRunner(
const gfx::Size& size,
vpx_codec_enc_cfg_t* codec_config,
ScopedVpxCodecCtxPtr* encoder) {
DCHECK(encoding_task_runner_->BelongsToCurrentThread());
if (IsInitialized(*codec_config)) {
// 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() << (use_vp9_ ? " vp9" : " vp8");
encoder->reset();
}
const vpx_codec_iface_t* codec_interface =
use_vp9_ ? vpx_codec_vp9_cx() : vpx_codec_vp8_cx();
vpx_codec_err_t result = vpx_codec_enc_config_default(
codec_interface, codec_config, 0 /* reserved */);
DCHECK_EQ(VPX_CODEC_OK, result);
DCHECK_EQ(320u, codec_config->g_w);
DCHECK_EQ(240u, codec_config->g_h);
DCHECK_EQ(256u, codec_config->rc_target_bitrate);
// Use the selected bitrate or adjust default bit rate to account for the
// actual size. Note: |rc_target_bitrate| units are kbit per second.
if (bits_per_second_ > 0) {
codec_config->rc_target_bitrate = bits_per_second_ / 1000;
} else {
codec_config->rc_target_bitrate = size.GetArea() *
codec_config->rc_target_bitrate /
codec_config->g_w / codec_config->g_h;
}
// Both VP8/VP9 configuration should be Variable BitRate by default.
DCHECK_EQ(VPX_VBR, codec_config->rc_end_usage);
if (use_vp9_) {
// Number of frames to consume before producing output.
codec_config->g_lag_in_frames = 0;
// DCHECK that the profile selected by default is I420 (magic number 0).
DCHECK_EQ(0u, codec_config->g_profile);
} else {
// VP8 always produces frames instantaneously.
DCHECK_EQ(0u, codec_config->g_lag_in_frames);
}
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.
// Forcing a keyframe in regular intervals also allows seeking in the
// resulting recording with decent performance.
codec_config->kf_mode = VPX_KF_AUTO;
codec_config->kf_min_dist = 0;
codec_config->kf_max_dist = 100;
codec_config->g_threads = GetNumberOfThreadsForEncoding();
// Number of frames to consume before producing output.
codec_config->g_lag_in_frames = 0;
encoder->reset(new vpx_codec_ctx_t);
const vpx_codec_err_t ret = vpx_codec_enc_init(
encoder->get(), codec_interface, codec_config, 0 /* flags */);
DCHECK_EQ(VPX_CODEC_OK, ret);
if (use_vp9_) {
// Values of VP8E_SET_CPUUSED greater than 0 will increase encoder speed at
// the expense of quality up to a maximum value of 8 for VP9, by tuning the
// target time spent encoding the frame. Go from 8 to 5 (values for real
// time encoding) depending on the amount of cores available in the system.
const int kCpuUsed =
std::max(5, 8 - base::SysInfo::NumberOfProcessors() / 2);
result = vpx_codec_control(encoder->get(), VP8E_SET_CPUUSED, kCpuUsed);
DLOG_IF(WARNING, VPX_CODEC_OK != result) << "VP8E_SET_CPUUSED failed";
}
}
bool VpxEncoder::IsInitialized(const vpx_codec_enc_cfg_t& codec_config) const {
DCHECK(encoding_task_runner_->BelongsToCurrentThread());
return codec_config.g_timebase.den != 0;
}
base::TimeDelta VpxEncoder::EstimateFrameDuration(
const scoped_refptr<VideoFrame>& frame) {
DCHECK(encoding_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));
}
#if BUILDFLAG(RTC_USE_H264)
// static
void H264Encoder::ShutdownEncoder(std::unique_ptr<base::Thread> encoding_thread,
ScopedISVCEncoderPtr encoder) {
DCHECK(encoding_thread->IsRunning());
encoding_thread->Stop();
// Both |encoding_thread| and |encoder| will be destroyed at end-of-scope.
}
H264Encoder::H264Encoder(
const VideoTrackRecorder::OnEncodedVideoCB& on_encoded_video_callback,
int32_t bits_per_second)
: Encoder(on_encoded_video_callback, bits_per_second) {
DCHECK(encoding_thread_->IsRunning());
}
H264Encoder::~H264Encoder() {
main_task_runner_->PostTask(FROM_HERE,
base::Bind(&H264Encoder::ShutdownEncoder,
base::Passed(&encoding_thread_),
base::Passed(&openh264_encoder_)));
}
void H264Encoder::EncodeOnEncodingTaskRunner(
scoped_refptr<VideoFrame> frame,
base::TimeTicks capture_timestamp) {
TRACE_EVENT0("video", "H264Encoder::EncodeOnEncodingTaskRunner");
DCHECK(encoding_task_runner_->BelongsToCurrentThread());
const gfx::Size frame_size = frame->visible_rect().size();
if (!openh264_encoder_ || configured_size_ != frame_size) {
ConfigureEncoderOnEncodingTaskRunner(frame_size);
first_frame_timestamp_ = capture_timestamp;
}
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] = frame->stride(VideoFrame::kYPlane);
picture.iStride[1] = frame->stride(VideoFrame::kUPlane);
picture.iStride[2] = frame->stride(VideoFrame::kVPlane);
picture.pData[0] = frame->visible_data(VideoFrame::kYPlane);
picture.pData[1] = frame->visible_data(VideoFrame::kUPlane);
picture.pData[2] = frame->visible_data(VideoFrame::kVPlane);
SFrameBSInfo info = {};
if (openh264_encoder_->EncodeFrame(&picture, &info) != cmResultSuccess) {
NOTREACHED() << "OpenH264 encoding failed";
return;
}
const media::WebmMuxer::VideoParameters video_params(frame);
frame = nullptr;
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) {
// The following DCHECKs make sure that the header of each NAL unit is OK.
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 NAL 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_,
video_params, base::Passed(&data), nullptr,
capture_timestamp, is_key_frame));
}
void H264Encoder::ConfigureEncoderOnEncodingTaskRunner(const gfx::Size& size) {
DCHECK(encoding_task_runner_->BelongsToCurrentThread());
ISVCEncoder* temp_encoder = nullptr;
if (WelsCreateSVCEncoder(&temp_encoder) != 0) {
NOTREACHED() << "Failed to create OpenH264 encoder";
return;
}
openh264_encoder_.reset(temp_encoder);
configured_size_ = size;
#if DCHECK_IS_ON()
int trace_level = WELS_LOG_INFO;
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;
DCHECK_EQ(AUTO_REF_PIC_COUNT, init_params.iNumRefFrame);
DCHECK(!init_params.bSimulcastAVC);
init_params.uiIntraPeriod = 100; // Same as for VpxEncoder.
init_params.iPicWidth = size.width();
init_params.iPicHeight = size.height();
DCHECK_EQ(RC_QUALITY_MODE, init_params.iRCMode);
DCHECK_EQ(0, init_params.iPaddingFlag);
DCHECK_EQ(UNSPECIFIED_BIT_RATE, init_params.iTargetBitrate);
DCHECK_EQ(UNSPECIFIED_BIT_RATE, init_params.iMaxBitrate);
if (bits_per_second_ > 0) {
init_params.iRCMode = RC_BITRATE_MODE;
init_params.iTargetBitrate = bits_per_second_;
} else {
init_params.iRCMode = RC_OFF_MODE;
}
// Threading model: Set to 1 due to https://crbug.com/583348.
init_params.iMultipleThreadIdc = 1;
// TODO(mcasas): consider reducing complexity if there are few CPUs available.
init_params.iComplexityMode = MEDIUM_COMPLEXITY;
DCHECK(!init_params.bEnableDenoise);
DCHECK(init_params.bEnableFrameSkip);
// 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;
// When uiSliceMode = SM_FIXEDSLCNUM_SLICE, uiSliceNum = 0 means auto design
// it with cpu core number.
// TODO(sprang): Set to 0 when we understand why the rate controller borks
// when uiSliceNum > 1. See https://github.com/cisco/openh264/issues/2591
init_params.sSpatialLayers[0].sSliceArgument.uiSliceNum = 1;
init_params.sSpatialLayers[0].sSliceArgument.uiSliceMode =
SM_FIXEDSLCNUM_SLICE;
if (openh264_encoder_->InitializeExt(&init_params) != cmResultSuccess) {
NOTREACHED() << "Failed to initialize OpenH264 encoder";
return;
}
int pixel_format = EVideoFormatType::videoFormatI420;
openh264_encoder_->SetOption(ENCODER_OPTION_DATAFORMAT, &pixel_format);
}
#endif //#if BUILDFLAG(RTC_USE_H264)
} // anonymous namespace
// static
VideoTrackRecorder::CodecId VideoTrackRecorder::GetPreferredCodecId() {
return GetCodecEnumerator()->GetPreferredCodecId();
}
VideoTrackRecorder::VideoTrackRecorder(
CodecId codec,
const blink::WebMediaStreamTrack& track,
const OnEncodedVideoCB& on_encoded_video_callback,
int32_t bits_per_second)
: track_(track),
paused_before_init_(false),
weak_ptr_factory_(this) {
DCHECK(main_render_thread_checker_.CalledOnValidThread());
DCHECK(!track_.isNull());
DCHECK(track_.getTrackData());
initialize_encoder_callback_ = base::Bind(
&VideoTrackRecorder::InitializeEncoder, weak_ptr_factory_.GetWeakPtr(),
codec, on_encoded_video_callback, bits_per_second);
// InitializeEncoder() will be called on Render Main thread.
MediaStreamVideoSink::ConnectToTrack(
track_, media::BindToCurrentLoop(initialize_encoder_callback_), false);
}
VideoTrackRecorder::~VideoTrackRecorder() {
DCHECK(main_render_thread_checker_.CalledOnValidThread());
MediaStreamVideoSink::DisconnectFromTrack();
track_.reset();
}
void VideoTrackRecorder::Pause() {
DCHECK(main_render_thread_checker_.CalledOnValidThread());
if (encoder_)
encoder_->SetPaused(true);
else
paused_before_init_ = true;
}
void VideoTrackRecorder::Resume() {
DCHECK(main_render_thread_checker_.CalledOnValidThread());
if (encoder_)
encoder_->SetPaused(false);
else
paused_before_init_ = false;
}
void VideoTrackRecorder::OnVideoFrameForTesting(
const scoped_refptr<media::VideoFrame>& frame,
base::TimeTicks timestamp) {
DVLOG(3) << __func__;
if (!encoder_) {
DCHECK(!initialize_encoder_callback_.is_null());
initialize_encoder_callback_.Run(frame, timestamp);
}
encoder_->StartFrameEncode(frame, timestamp);
}
void VideoTrackRecorder::InitializeEncoder(
CodecId codec,
const OnEncodedVideoCB& on_encoded_video_callback,
int32_t bits_per_second,
const scoped_refptr<media::VideoFrame>& frame,
base::TimeTicks capture_time) {
DVLOG(3) << __func__ << frame->visible_rect().size().ToString();
DCHECK(main_render_thread_checker_.CalledOnValidThread());
MediaStreamVideoSink::DisconnectFromTrack();
const gfx::Size& input_size = frame->visible_rect().size();
const auto& vea_supported_profile =
GetCodecEnumerator()->CodecIdToVEAProfile(codec);
if (vea_supported_profile != media::VIDEO_CODEC_PROFILE_UNKNOWN &&
input_size.width() >= kVEAEncoderMinResolutionWidth &&
input_size.height() >= kVEAEncoderMinResolutionHeight) {
encoder_ = new VEAEncoder(on_encoded_video_callback, bits_per_second,
vea_supported_profile, input_size);
} else {
switch (codec) {
#if BUILDFLAG(RTC_USE_H264)
case CodecId::H264:
encoder_ =
new H264Encoder(on_encoded_video_callback, bits_per_second);
break;
#endif
case CodecId::VP8:
case CodecId::VP9:
encoder_ = new VpxEncoder(codec == CodecId::VP9,
on_encoded_video_callback, bits_per_second);
break;
default:
NOTREACHED() << "Unsupported codec " << static_cast<int>(codec);
}
}
if (paused_before_init_)
encoder_->SetPaused(paused_before_init_);
// StartFrameEncode() will be called on Render IO thread.
MediaStreamVideoSink::ConnectToTrack(
track_,
base::Bind(&VideoTrackRecorder::Encoder::StartFrameEncode, encoder_),
false);
}
bool VideoTrackRecorder::CanEncodeAlphaChannelForTesting() {
DCHECK(encoder_);
return encoder_->CanEncodeAlphaChannel();
}
} // namespace content