diff options
author | posciak@chromium.org <posciak@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-06-26 08:13:25 +0000 |
---|---|---|
committer | posciak@chromium.org <posciak@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-06-26 08:13:25 +0000 |
commit | 036bcb03ec872b5053d90695207b234dae4ec913 (patch) | |
tree | 726b2ba1c833559416c504dd8a9ba60c69625e60 | |
parent | 597a1ab3ff262272a861c46cb98ce88902d12534 (diff) | |
download | chromium_src-036bcb03ec872b5053d90695207b234dae4ec913.zip chromium_src-036bcb03ec872b5053d90695207b234dae4ec913.tar.gz chromium_src-036bcb03ec872b5053d90695207b234dae4ec913.tar.bz2 |
Revert "Revert 279650 "Add VaapiVideoEncodeAccelerator for HW-accelerate...""
Fixing the cros_trunk builder compilation failure in
h264_bitstream_buffer_unittest, for which this was reverted.
> Revert 279650 "Add VaapiVideoEncodeAccelerator for HW-accelerate..."
>
> Broke the Chrome-on-Chrome OS compile
>
> http://master.chrome.corp.google.com:8011/builders/cros%20trunk/builds/21417
>
> > Add VaapiVideoEncodeAccelerator for HW-accelerated video encode.
> >
> > Add an implementation of VideoEncodeAccelerator utilizing VA-API for
> > hardware encode on Intel-based ChromeOS platforms.
> >
> > BUG=378962
> > TEST=video_encode_accelerator_unittest
> >
> > Review URL: https://codereview.chromium.org/333253002
>
> TBR=posciak@chromium.org
>
> Review URL: https://codereview.chromium.org/350413002
>
> git-svn-id: svn://svn.chromium.org/chrome/trunk/src@279733 0039d316-1c4b-4281-b951-d872f2087c98
TBR=jamescook@chromium.org
BUG=388778,378962
Review URL: https://codereview.chromium.org/356903002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@279958 0039d316-1c4b-4281-b951-d872f2087c98
26 files changed, 2186 insertions, 87 deletions
diff --git a/chrome/browser/chromeos/login/chrome_restart_request.cc b/chrome/browser/chromeos/login/chrome_restart_request.cc index 74c93b6..7492b44 100644 --- a/chrome/browser/chromeos/login/chrome_restart_request.cc +++ b/chrome/browser/chromeos/login/chrome_restart_request.cc @@ -158,6 +158,7 @@ std::string DeriveCommandLine(const GURL& start_url, ::switches::kDisableWebRtcHWEncoding, ::switches::kEnableWebRtcHWVp8Encoding, #endif + ::switches::kEnableVaapiAcceleratedVideoEncode, #if defined(USE_OZONE) ::switches::kOzonePlatform, #endif diff --git a/content/browser/gpu/gpu_process_host.cc b/content/browser/gpu/gpu_process_host.cc index 96a233e..69d7320 100644 --- a/content/browser/gpu/gpu_process_host.cc +++ b/content/browser/gpu/gpu_process_host.cc @@ -981,6 +981,9 @@ bool GpuProcessHost::LaunchGpuProcess(const std::string& channel_id) { #endif switches::kEnableLogging, switches::kEnableShareGroupAsyncTextureUpload, +#if defined(OS_CHROMEOS) + switches::kEnableVaapiAcceleratedVideoEncode, +#endif switches::kGpuStartupDialog, switches::kGpuSandboxAllowSysVShm, switches::kGpuSandboxFailuresFatal, diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc index a7738f6..1a1baa8 100644 --- a/content/browser/renderer_host/render_process_host_impl.cc +++ b/content/browser/renderer_host/render_process_host_impl.cc @@ -1256,6 +1256,9 @@ void RenderProcessHostImpl::PropagateBrowserCommandLineToRenderer( switches::kDisableDirectWrite, switches::kEnableHighResolutionTime, #endif +#if defined(OS_CHROMEOS) + switches::kEnableVaapiAcceleratedVideoEncode, +#endif }; renderer_cmd->CopySwitchesFrom(browser_cmd, kSwitchNames, arraysize(kSwitchNames)); diff --git a/content/common/gpu/media/gpu_video_encode_accelerator.cc b/content/common/gpu/media/gpu_video_encode_accelerator.cc index 12c7b10..52ecab8 100644 --- a/content/common/gpu/media/gpu_video_encode_accelerator.cc +++ b/content/common/gpu/media/gpu_video_encode_accelerator.cc @@ -5,18 +5,27 @@ #include "content/common/gpu/media/gpu_video_encode_accelerator.h" #include "base/callback.h" +#include "base/command_line.h" #include "base/logging.h" #include "base/memory/shared_memory.h" #include "base/message_loop/message_loop_proxy.h" #include "build/build_config.h" #include "content/common/gpu/gpu_channel.h" #include "content/common/gpu/gpu_messages.h" +#include "content/public/common/content_switches.h" #include "ipc/ipc_message_macros.h" #include "media/base/limits.h" #include "media/base/video_frame.h" -#if defined(OS_CHROMEOS) && defined(ARCH_CPU_ARMEL) && defined(USE_X11) +#if defined(OS_CHROMEOS) && defined(USE_X11) + +#if defined(ARCH_CPU_ARMEL) #include "content/common/gpu/media/v4l2_video_encode_accelerator.h" +#elif defined(ARCH_CPU_X86_FAMILY) +#include "content/common/gpu/media/vaapi_video_encode_accelerator.h" +#include "ui/gfx/x/x11_types.h" +#endif + #elif defined(OS_ANDROID) && defined(ENABLE_WEBRTC) #include "content/common/gpu/media/android_video_encode_accelerator.h" #endif @@ -158,8 +167,12 @@ std::vector<media::VideoEncodeAccelerator::SupportedProfile> GpuVideoEncodeAccelerator::GetSupportedProfiles() { std::vector<media::VideoEncodeAccelerator::SupportedProfile> profiles; -#if defined(OS_CHROMEOS) && defined(ARCH_CPU_ARMEL) && defined(USE_X11) +#if defined(OS_CHROMEOS) && defined(USE_X11) +#if defined(ARCH_CPU_ARMEL) profiles = V4L2VideoEncodeAccelerator::GetSupportedProfiles(); +#elif defined(ARCH_CPU_X86_FAMILY) + profiles = VaapiVideoEncodeAccelerator::GetSupportedProfiles(); +#endif #elif defined(OS_ANDROID) && defined(ENABLE_WEBRTC) profiles = AndroidVideoEncodeAccelerator::GetSupportedProfiles(); #endif @@ -170,12 +183,18 @@ GpuVideoEncodeAccelerator::GetSupportedProfiles() { void GpuVideoEncodeAccelerator::CreateEncoder() { DCHECK(!encoder_); -#if defined(OS_CHROMEOS) && defined(ARCH_CPU_ARMEL) && defined(USE_X11) +#if defined(OS_CHROMEOS) && defined(USE_X11) +#if defined(ARCH_CPU_ARMEL) scoped_ptr<V4L2Device> device = V4L2Device::Create(V4L2Device::kEncoder); if (!device.get()) return; encoder_.reset(new V4L2VideoEncodeAccelerator(device.Pass())); +#elif defined(ARCH_CPU_X86_FAMILY) + const CommandLine* cmd_line = CommandLine::ForCurrentProcess(); + if (cmd_line->HasSwitch(switches::kEnableVaapiAcceleratedVideoEncode)) + encoder_.reset(new VaapiVideoEncodeAccelerator(gfx::GetXDisplay())); +#endif #elif defined(OS_ANDROID) && defined(ENABLE_WEBRTC) encoder_.reset(new AndroidVideoEncodeAccelerator()); #endif diff --git a/content/common/gpu/media/h264_dpb.h b/content/common/gpu/media/h264_dpb.h index 29be0bc..366c47a 100644 --- a/content/common/gpu/media/h264_dpb.h +++ b/content/common/gpu/media/h264_dpb.h @@ -41,6 +41,7 @@ struct H264Picture { int frame_num_wrap; int long_term_frame_idx; + media::H264SliceHeader::Type type; bool idr; // IDR picture? bool ref; // reference picture? bool long_term; // long term reference picture? diff --git a/content/common/gpu/media/va.sigs b/content/common/gpu/media/va.sigs index b3cea38..9906f9f 100644 --- a/content/common/gpu/media/va.sigs +++ b/content/common/gpu/media/va.sigs @@ -22,7 +22,9 @@ const char *vaErrorStr(VAStatus error_status); VAStatus vaGetConfigAttributes(VADisplay dpy, VAProfile profile, VAEntrypoint entrypoint, VAConfigAttrib *attrib_list, int num_attribs); VAStatus vaInitialize(VADisplay dpy, int *major_version, int *minor_version); VAStatus vaMapBuffer(VADisplay dpy, VABufferID buf_id, void **pbuf); +int vaMaxNumEntrypoints (VADisplay dpy); int vaMaxNumProfiles(VADisplay dpy); +VAStatus vaQueryConfigEntrypoints (VADisplay dpy, VAProfile profile, VAEntrypoint *entrypoint_list, int *num_entrypoints); VAStatus vaQueryConfigProfiles(VADisplay dpy, VAProfile *profile_list, int *num_profiles); VAStatus vaRenderPicture(VADisplay dpy, VAContextID context, VABufferID *buffers, int num_buffers); VAStatus vaSetDisplayAttributes(VADisplay dpy, VADisplayAttribute *attr_list, int num_attributes); diff --git a/content/common/gpu/media/vaapi_h264_decoder.cc b/content/common/gpu/media/vaapi_h264_decoder.cc index 9654a3c..91e76af 100644 --- a/content/common/gpu/media/vaapi_h264_decoder.cc +++ b/content/common/gpu/media/vaapi_h264_decoder.cc @@ -488,8 +488,8 @@ bool VaapiH264Decoder::DecodePicture() { return false; } - if (!vaapi_wrapper_->DecodeAndDestroyPendingBuffers( - dec_surface->va_surface()->id())) { + if (!vaapi_wrapper_->ExecuteAndDestroyPendingBuffers( + dec_surface->va_surface()->id())) { DVLOG(1) << "Failed decoding picture"; return false; } diff --git a/content/common/gpu/media/vaapi_h264_decoder_unittest.cc b/content/common/gpu/media/vaapi_h264_decoder_unittest.cc index 6bd873e..b14c50b 100644 --- a/content/common/gpu/media/vaapi_h264_decoder_unittest.cc +++ b/content/common/gpu/media/vaapi_h264_decoder_unittest.cc @@ -127,7 +127,8 @@ bool VaapiH264DecoderLoop::Initialize(base::FilePath input_file, media::VideoCodecProfile profile = media::H264PROFILE_BASELINE; base::Closure report_error_cb = base::Bind(&LogOnError, VaapiH264Decoder::VAAPI_ERROR); - wrapper_ = VaapiWrapper::Create(profile, x_display_, report_error_cb); + wrapper_ = VaapiWrapper::Create( + VaapiWrapper::kDecode, profile, x_display_, report_error_cb); if (!wrapper_.get()) { LOG(ERROR) << "Can't create vaapi wrapper"; return false; diff --git a/content/common/gpu/media/vaapi_video_decode_accelerator.cc b/content/common/gpu/media/vaapi_video_decode_accelerator.cc index b68f5d4..afcfc8a 100644 --- a/content/common/gpu/media/vaapi_video_decode_accelerator.cc +++ b/content/common/gpu/media/vaapi_video_decode_accelerator.cc @@ -300,7 +300,9 @@ bool VaapiVideoDecodeAccelerator::Initialize(media::VideoCodecProfile profile, } vaapi_wrapper_ = VaapiWrapper::Create( - profile, x_display_, + VaapiWrapper::kDecode, + profile, + x_display_, base::Bind(&ReportToUMA, content::VaapiH264Decoder::VAAPI_ERROR)); if (!vaapi_wrapper_.get()) { diff --git a/content/common/gpu/media/vaapi_video_encode_accelerator.cc b/content/common/gpu/media/vaapi_video_encode_accelerator.cc new file mode 100644 index 0000000..6cfdd9d --- /dev/null +++ b/content/common/gpu/media/vaapi_video_encode_accelerator.cc @@ -0,0 +1,1061 @@ +// Copyright 2014 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/common/gpu/media/vaapi_video_encode_accelerator.h" + +#include "base/bind.h" +#include "base/callback.h" +#include "base/command_line.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/metrics/histogram.h" +#include "base/numerics/safe_conversions.h" +#include "content/common/gpu/media/h264_dpb.h" +#include "content/public/common/content_switches.h" +#include "media/base/bind_to_current_loop.h" +#include "third_party/libva/va/va_enc_h264.h" + +#define DVLOGF(level) DVLOG(level) << __FUNCTION__ << "(): " + +#define NOTIFY_ERROR(error, msg) \ + do { \ + SetState(kError); \ + DVLOGF(1) << msg; \ + DVLOGF(1) << "Calling NotifyError(" << error << ")"; \ + NotifyError(error); \ + } while (0) + +namespace content { + +namespace { +// Need 2 surfaces for each frame: one for input data and one for +// reconstructed picture, which is later used for reference. +const size_t kMinSurfacesToEncode = 2; + +// Subjectively chosen. +const size_t kNumInputBuffers = 4; +const size_t kMaxNumReferenceFrames = 4; + +// We need up to kMaxNumReferenceFrames surfaces for reference, plus one +// for input and one for encode (which will be added to the set of reference +// frames for subsequent frames). Actual execution of HW encode is done +// in parallel, and we want to process more frames in the meantime. +// To have kNumInputBuffers in flight, we need a full set of reference + +// encode surfaces (i.e. kMaxNumReferenceFrames + kMinSurfacesToEncode), and +// (kNumInputBuffers - 1) of kMinSurfacesToEncode for the remaining frames +// in flight. +const size_t kNumSurfaces = kMaxNumReferenceFrames + kMinSurfacesToEncode + + kMinSurfacesToEncode * (kNumInputBuffers - 1); + +// An IDR every 128 frames, an I frame every 30 and no B frames. +const int kIDRPeriod = 128; +const int kIPeriod = 30; +const int kIPPeriod = 1; + +const int kDefaultFramerate = 30; + +// HRD parameters (ch. E.2.2 in spec). +const int kBitRateScale = 0; // bit_rate_scale for SPS HRD parameters. +const int kCPBSizeScale = 0; // cpb_size_scale for SPS HRD parameters. + +const int kDefaultQP = 26; +// All Intel codecs can do at least 4.1. +const int kDefaultLevelIDC = 41; +const int kChromaFormatIDC = 1; // 4:2:0 + +// Arbitrarily chosen bitrate window size for rate control, in ms. +const int kCPBWindowSizeMs = 1500; + +// UMA errors that the VaapiVideoEncodeAccelerator class reports. +enum VAVEAEncoderFailure { + VAAPI_ERROR = 0, + VAVEA_ENCODER_FAILURES_MAX, +}; + +} + +// Round |value| up to |alignment|, which must be a power of 2. +static inline size_t RoundUpToPowerOf2(size_t value, size_t alignment) { + // Check that |alignment| is a power of 2. + DCHECK((alignment + (alignment - 1)) == (alignment | (alignment - 1))); + return ((value + (alignment - 1)) & ~(alignment - 1)); +} + +static void ReportToUMA(VAVEAEncoderFailure failure) { + UMA_HISTOGRAM_ENUMERATION( + "Media.VAVEA.EncoderFailure", + failure, + VAVEA_ENCODER_FAILURES_MAX); +} + +struct VaapiVideoEncodeAccelerator::InputFrameRef { + InputFrameRef(const scoped_refptr<media::VideoFrame>& frame, + bool force_keyframe) + : frame(frame), force_keyframe(force_keyframe) {} + const scoped_refptr<media::VideoFrame> frame; + const bool force_keyframe; +}; + +struct VaapiVideoEncodeAccelerator::BitstreamBufferRef { + BitstreamBufferRef(int32 id, scoped_ptr<base::SharedMemory> shm, size_t size) + : id(id), shm(shm.Pass()), size(size) {} + const int32 id; + const scoped_ptr<base::SharedMemory> shm; + const size_t size; +}; + +// static +std::vector<media::VideoEncodeAccelerator::SupportedProfile> +VaapiVideoEncodeAccelerator::GetSupportedProfiles() { + std::vector<SupportedProfile> profiles; + + const CommandLine* cmd_line = CommandLine::ForCurrentProcess(); + if (!cmd_line->HasSwitch(switches::kEnableVaapiAcceleratedVideoEncode)) + return profiles; + + SupportedProfile profile; + profile.profile = media::H264PROFILE_MAIN; + profile.max_resolution.SetSize(1920, 1088); + profile.max_framerate.numerator = kDefaultFramerate; + profile.max_framerate.denominator = 1; + profiles.push_back(profile); + + // This is actually only constrained (see crbug.com/345569). + profile.profile = media::H264PROFILE_BASELINE; + profiles.push_back(profile); + + profile.profile = media::H264PROFILE_HIGH; + profiles.push_back(profile); + + return profiles; +} + +static unsigned int Log2OfPowerOf2(unsigned int x) { + CHECK_GT(x, 0u); + DCHECK_EQ(x & (x - 1), 0u); + + int log = 0; + while (x) { + x >>= 1; + ++log; + } + return log; +} + +VaapiVideoEncodeAccelerator::VaapiVideoEncodeAccelerator(Display* x_display) + : profile_(media::VIDEO_CODEC_PROFILE_UNKNOWN), + mb_width_(0), + mb_height_(0), + output_buffer_byte_size_(0), + x_display_(x_display), + state_(kUninitialized), + frame_num_(0), + last_idr_frame_num_(0), + bitrate_(0), + framerate_(0), + cpb_size_(0), + encoding_parameters_changed_(false), + encoder_thread_("VAVEAEncoderThread"), + child_message_loop_proxy_(base::MessageLoopProxy::current()), + weak_this_ptr_factory_(this) { + DVLOGF(4); + weak_this_ = weak_this_ptr_factory_.GetWeakPtr(); + + max_ref_idx_l0_size_ = kMaxNumReferenceFrames; + qp_ = kDefaultQP; + idr_period_ = kIDRPeriod; + i_period_ = kIPeriod; + ip_period_ = kIPPeriod; +} + +VaapiVideoEncodeAccelerator::~VaapiVideoEncodeAccelerator() { + DVLOGF(4); + DCHECK(child_message_loop_proxy_->BelongsToCurrentThread()); + DCHECK(!encoder_thread_.IsRunning()); +} + +bool VaapiVideoEncodeAccelerator::Initialize( + media::VideoFrame::Format format, + const gfx::Size& input_visible_size, + media::VideoCodecProfile output_profile, + uint32 initial_bitrate, + Client* client) { + DCHECK(child_message_loop_proxy_->BelongsToCurrentThread()); + DCHECK(!encoder_thread_.IsRunning()); + DCHECK_EQ(state_, kUninitialized); + + DVLOGF(1) << "Initializing VAVEA, input_format: " + << media::VideoFrame::FormatToString(format) + << ", input_visible_size: " << input_visible_size.ToString() + << ", output_profile: " << output_profile + << ", initial_bitrate: " << initial_bitrate; + + client_ptr_factory_.reset(new base::WeakPtrFactory<Client>(client)); + client_ = client_ptr_factory_->GetWeakPtr(); + + if (output_profile < media::H264PROFILE_BASELINE || + output_profile > media::H264PROFILE_MAIN) { + DVLOGF(1) << "Unsupported output profile: " << output_profile; + return false; + } + + if (format != media::VideoFrame::I420) { + DVLOGF(1) << "Unsupported input format: " + << media::VideoFrame::FormatToString(format); + return false; + } + + profile_ = output_profile; + visible_size_ = input_visible_size; + // 4:2:0 format has to be 2-aligned. + DCHECK_EQ(visible_size_.width() % 2, 0); + DCHECK_EQ(visible_size_.height() % 2, 0); + coded_size_ = gfx::Size(RoundUpToPowerOf2(visible_size_.width(), 16), + RoundUpToPowerOf2(visible_size_.height(), 16)); + mb_width_ = coded_size_.width() / 16; + mb_height_ = coded_size_.height() / 16; + output_buffer_byte_size_ = coded_size_.GetArea(); + + UpdateRates(initial_bitrate, kDefaultFramerate); + + vaapi_wrapper_ = VaapiWrapper::Create(VaapiWrapper::kEncode, + output_profile, + x_display_, + base::Bind(&ReportToUMA, VAAPI_ERROR)); + if (!vaapi_wrapper_) { + DVLOGF(1) << "Failed initializing VAAPI"; + return false; + } + + if (!encoder_thread_.Start()) { + DVLOGF(1) << "Failed to start encoder thread"; + return false; + } + encoder_thread_proxy_ = encoder_thread_.message_loop_proxy(); + + // Finish the remaining initialization on the encoder thread. + encoder_thread_proxy_->PostTask( + FROM_HERE, + base::Bind(&VaapiVideoEncodeAccelerator::InitializeTask, + base::Unretained(this))); + + return true; +} + +void VaapiVideoEncodeAccelerator::InitializeTask() { + DCHECK(encoder_thread_proxy_->BelongsToCurrentThread()); + DCHECK_EQ(state_, kUninitialized); + DVLOGF(4); + + va_surface_release_cb_ = media::BindToCurrentLoop( + base::Bind(&VaapiVideoEncodeAccelerator::RecycleVASurfaceID, + base::Unretained(this))); + + if (!vaapi_wrapper_->CreateSurfaces( + coded_size_, kNumSurfaces, &available_va_surface_ids_)) { + NOTIFY_ERROR(kPlatformFailureError, "Failed creating VASurfaces"); + return; + } + + UpdateSPS(); + GeneratePackedSPS(); + + UpdatePPS(); + GeneratePackedPPS(); + + child_message_loop_proxy_->PostTask( + FROM_HERE, + base::Bind(&Client::RequireBitstreamBuffers, + client_, + kNumInputBuffers, + coded_size_, + output_buffer_byte_size_)); + + SetState(kEncoding); +} + +void VaapiVideoEncodeAccelerator::RecycleVASurfaceID( + VASurfaceID va_surface_id) { + DVLOGF(4) << "va_surface_id: " << va_surface_id; + DCHECK(encoder_thread_proxy_->BelongsToCurrentThread()); + + available_va_surface_ids_.push_back(va_surface_id); + EncodeFrameTask(); +} + +void VaapiVideoEncodeAccelerator::BeginFrame(bool force_keyframe) { + memset(¤t_pic_, 0, sizeof(current_pic_)); + + current_pic_.frame_num = frame_num_++; + frame_num_ %= idr_period_; + + if (current_pic_.frame_num % i_period_ == 0 || force_keyframe) + current_pic_.type = media::H264SliceHeader::kISlice; + else + current_pic_.type = media::H264SliceHeader::kPSlice; + + if (current_pic_.frame_num % idr_period_ == 0) { + current_pic_.idr = true; + last_idr_frame_num_ = current_pic_.frame_num; + ref_pic_list0_.clear(); + } + + if (current_pic_.type != media::H264SliceHeader::kBSlice) + current_pic_.ref = true; + + current_pic_.pic_order_cnt = current_pic_.frame_num * 2; + current_pic_.top_field_order_cnt = current_pic_.pic_order_cnt; + current_pic_.pic_order_cnt_lsb = current_pic_.pic_order_cnt; + + current_encode_job_->keyframe = + (current_pic_.type == media::H264SliceHeader::kISlice); + + DVLOGF(4) << "Starting a new frame, type: " << current_pic_.type + << (force_keyframe ? " (forced keyframe)" : "") + << " frame_num: " << current_pic_.frame_num + << " POC: " << current_pic_.pic_order_cnt; +} + +void VaapiVideoEncodeAccelerator::EndFrame() { + // Store the picture on the list of reference pictures and keep the list + // below maximum size, dropping oldest references. + if (current_pic_.ref) + ref_pic_list0_.push_front(current_encode_job_->recon_surface); + size_t max_num_ref_frames = + base::checked_cast<size_t>(current_sps_.max_num_ref_frames); + while (ref_pic_list0_.size() > max_num_ref_frames) + ref_pic_list0_.pop_back(); + + submitted_encode_jobs_.push(make_linked_ptr(current_encode_job_.release())); +} + +static void InitVAPicture(VAPictureH264* va_pic) { + memset(va_pic, 0, sizeof(*va_pic)); + va_pic->picture_id = VA_INVALID_ID; + va_pic->flags = VA_PICTURE_H264_INVALID; +} + +bool VaapiVideoEncodeAccelerator::SubmitFrameParameters() { + VAEncSequenceParameterBufferH264 seq_param; + memset(&seq_param, 0, sizeof(seq_param)); + +#define SPS_TO_SP(a) seq_param.a = current_sps_.a; + SPS_TO_SP(seq_parameter_set_id); + SPS_TO_SP(level_idc); + + seq_param.intra_period = i_period_; + seq_param.intra_idr_period = idr_period_; + seq_param.ip_period = ip_period_; + seq_param.bits_per_second = bitrate_; + + SPS_TO_SP(max_num_ref_frames); + seq_param.picture_width_in_mbs = mb_width_; + seq_param.picture_height_in_mbs = mb_height_; + +#define SPS_TO_SP_FS(a) seq_param.seq_fields.bits.a = current_sps_.a; + SPS_TO_SP_FS(chroma_format_idc); + SPS_TO_SP_FS(frame_mbs_only_flag); + SPS_TO_SP_FS(log2_max_frame_num_minus4); + SPS_TO_SP_FS(pic_order_cnt_type); + SPS_TO_SP_FS(log2_max_pic_order_cnt_lsb_minus4); +#undef SPS_TO_SP_FS + + SPS_TO_SP(bit_depth_luma_minus8); + SPS_TO_SP(bit_depth_chroma_minus8); + + SPS_TO_SP(frame_cropping_flag); + if (current_sps_.frame_cropping_flag) { + SPS_TO_SP(frame_crop_left_offset); + SPS_TO_SP(frame_crop_right_offset); + SPS_TO_SP(frame_crop_top_offset); + SPS_TO_SP(frame_crop_bottom_offset); + } + + SPS_TO_SP(vui_parameters_present_flag); +#define SPS_TO_SP_VF(a) seq_param.vui_fields.bits.a = current_sps_.a; + SPS_TO_SP_VF(timing_info_present_flag); +#undef SPS_TO_SP_VF + SPS_TO_SP(num_units_in_tick); + SPS_TO_SP(time_scale); +#undef SPS_TO_SP + + if (!vaapi_wrapper_->SubmitBuffer(VAEncSequenceParameterBufferType, + sizeof(seq_param), + &seq_param)) + return false; + + VAEncPictureParameterBufferH264 pic_param; + memset(&pic_param, 0, sizeof(pic_param)); + + pic_param.CurrPic.picture_id = current_encode_job_->recon_surface->id(); + pic_param.CurrPic.TopFieldOrderCnt = current_pic_.top_field_order_cnt; + pic_param.CurrPic.BottomFieldOrderCnt = current_pic_.bottom_field_order_cnt; + pic_param.CurrPic.flags = 0; + + for (size_t i = 0; i < arraysize(pic_param.ReferenceFrames); ++i) + InitVAPicture(&pic_param.ReferenceFrames[i]); + + DCHECK_LE(ref_pic_list0_.size(), arraysize(pic_param.ReferenceFrames)); + RefPicList::const_iterator iter = ref_pic_list0_.begin(); + for (size_t i = 0; + i < arraysize(pic_param.ReferenceFrames) && iter != ref_pic_list0_.end(); + ++iter, ++i) { + pic_param.ReferenceFrames[i].picture_id = (*iter)->id(); + pic_param.ReferenceFrames[i].flags = 0; + } + + pic_param.coded_buf = current_encode_job_->coded_buffer; + pic_param.pic_parameter_set_id = current_pps_.pic_parameter_set_id; + pic_param.seq_parameter_set_id = current_pps_.seq_parameter_set_id; + pic_param.frame_num = current_pic_.frame_num; + pic_param.pic_init_qp = qp_; + pic_param.num_ref_idx_l0_active_minus1 = max_ref_idx_l0_size_ - 1; + pic_param.pic_fields.bits.idr_pic_flag = current_pic_.idr; + pic_param.pic_fields.bits.reference_pic_flag = current_pic_.ref; +#define PPS_TO_PP_PF(a) pic_param.pic_fields.bits.a = current_pps_.a; + PPS_TO_PP_PF(entropy_coding_mode_flag); + PPS_TO_PP_PF(transform_8x8_mode_flag); + PPS_TO_PP_PF(deblocking_filter_control_present_flag); +#undef PPS_TO_PP_PF + + if (!vaapi_wrapper_->SubmitBuffer(VAEncPictureParameterBufferType, + sizeof(pic_param), + &pic_param)) + return false; + + VAEncSliceParameterBufferH264 slice_param; + memset(&slice_param, 0, sizeof(slice_param)); + + slice_param.num_macroblocks = mb_width_ * mb_height_; + slice_param.macroblock_info = VA_INVALID_ID; + slice_param.slice_type = current_pic_.type; + slice_param.pic_parameter_set_id = current_pps_.pic_parameter_set_id; + slice_param.idr_pic_id = last_idr_frame_num_; + slice_param.pic_order_cnt_lsb = current_pic_.pic_order_cnt_lsb; + slice_param.num_ref_idx_active_override_flag = true; + + for (size_t i = 0; i < arraysize(slice_param.RefPicList0); ++i) + InitVAPicture(&slice_param.RefPicList0[i]); + + for (size_t i = 0; i < arraysize(slice_param.RefPicList1); ++i) + InitVAPicture(&slice_param.RefPicList1[i]); + + DCHECK_LE(ref_pic_list0_.size(), arraysize(slice_param.RefPicList0)); + iter = ref_pic_list0_.begin(); + for (size_t i = 0; + i < arraysize(slice_param.RefPicList0) && iter != ref_pic_list0_.end(); + ++iter, ++i) { + InitVAPicture(&slice_param.RefPicList0[i]); + slice_param.RefPicList0[i].picture_id = (*iter)->id(); + slice_param.RefPicList0[i].flags = 0; + } + + if (!vaapi_wrapper_->SubmitBuffer(VAEncSliceParameterBufferType, + sizeof(slice_param), + &slice_param)) + return false; + + VAEncMiscParameterRateControl rate_control_param; + memset(&rate_control_param, 0, sizeof(rate_control_param)); + rate_control_param.bits_per_second = bitrate_; + rate_control_param.target_percentage = 90; + rate_control_param.window_size = kCPBWindowSizeMs; + rate_control_param.initial_qp = qp_; + rate_control_param.rc_flags.bits.disable_frame_skip = true; + + if (!vaapi_wrapper_->SubmitVAEncMiscParamBuffer( + VAEncMiscParameterTypeRateControl, + sizeof(rate_control_param), + &rate_control_param)) + return false; + + VAEncMiscParameterFrameRate framerate_param; + memset(&framerate_param, 0, sizeof(framerate_param)); + framerate_param.framerate = framerate_; + if (!vaapi_wrapper_->SubmitVAEncMiscParamBuffer( + VAEncMiscParameterTypeFrameRate, + sizeof(framerate_param), + &framerate_param)) + return false; + + VAEncMiscParameterHRD hrd_param; + memset(&hrd_param, 0, sizeof(hrd_param)); + hrd_param.buffer_size = cpb_size_; + hrd_param.initial_buffer_fullness = cpb_size_ / 2; + if (!vaapi_wrapper_->SubmitVAEncMiscParamBuffer(VAEncMiscParameterTypeHRD, + sizeof(hrd_param), + &hrd_param)) + return false; + + return true; +} + +bool VaapiVideoEncodeAccelerator::SubmitHeadersIfNeeded() { + if (current_pic_.type != media::H264SliceHeader::kISlice) + return true; + + // Submit PPS. + VAEncPackedHeaderParameterBuffer par_buffer; + memset(&par_buffer, 0, sizeof(par_buffer)); + par_buffer.type = VAEncPackedHeaderSequence; + par_buffer.bit_length = packed_sps_.BytesInBuffer() * 8; + + if (!vaapi_wrapper_->SubmitBuffer(VAEncPackedHeaderParameterBufferType, + sizeof(par_buffer), + &par_buffer)) + return false; + + if (!vaapi_wrapper_->SubmitBuffer(VAEncPackedHeaderDataBufferType, + packed_sps_.BytesInBuffer(), + packed_sps_.data())) + return false; + + // Submit PPS. + memset(&par_buffer, 0, sizeof(par_buffer)); + par_buffer.type = VAEncPackedHeaderPicture; + par_buffer.bit_length = packed_pps_.BytesInBuffer() * 8; + + if (!vaapi_wrapper_->SubmitBuffer(VAEncPackedHeaderParameterBufferType, + sizeof(par_buffer), + &par_buffer)) + return false; + + if (!vaapi_wrapper_->SubmitBuffer(VAEncPackedHeaderDataBufferType, + packed_pps_.BytesInBuffer(), + packed_pps_.data())) + return false; + + return true; +} + +bool VaapiVideoEncodeAccelerator::ExecuteEncode() { + DVLOGF(3) << "Encoding frame_num: " << current_pic_.frame_num; + return vaapi_wrapper_->ExecuteAndDestroyPendingBuffers( + current_encode_job_->input_surface->id()); +} + +bool VaapiVideoEncodeAccelerator::UploadFrame( + const scoped_refptr<media::VideoFrame>& frame) { + return vaapi_wrapper_->UploadVideoFrameToSurface( + frame, current_encode_job_->input_surface->id()); +} + +void VaapiVideoEncodeAccelerator::TryToReturnBitstreamBuffer() { + DCHECK(encoder_thread_proxy_->BelongsToCurrentThread()); + + if (state_ != kEncoding) + return; + + if (submitted_encode_jobs_.empty() || available_bitstream_buffers_.empty()) + return; + + linked_ptr<BitstreamBufferRef> buffer = available_bitstream_buffers_.front(); + available_bitstream_buffers_.pop(); + + uint8* target_data = reinterpret_cast<uint8*>(buffer->shm->memory()); + + linked_ptr<EncodeJob> encode_job = submitted_encode_jobs_.front(); + submitted_encode_jobs_.pop(); + + size_t data_size = 0; + if (!vaapi_wrapper_->DownloadAndDestroyCodedBuffer( + encode_job->coded_buffer, + encode_job->input_surface->id(), + target_data, + buffer->size, + &data_size)) { + NOTIFY_ERROR(kPlatformFailureError, "Failed downloading coded buffer"); + return; + } + + DVLOGF(3) << "Returning bitstream buffer " + << (encode_job->keyframe ? "(keyframe)" : "") + << " id: " << buffer->id << " size: " << data_size; + + child_message_loop_proxy_->PostTask(FROM_HERE, + base::Bind(&Client::BitstreamBufferReady, + client_, + buffer->id, + data_size, + encode_job->keyframe)); +} + +void VaapiVideoEncodeAccelerator::Encode( + const scoped_refptr<media::VideoFrame>& frame, + bool force_keyframe) { + DVLOGF(3) << "Frame timestamp: " << frame->timestamp().InMilliseconds() + << " force_keyframe: " << force_keyframe; + DCHECK(child_message_loop_proxy_->BelongsToCurrentThread()); + + encoder_thread_proxy_->PostTask( + FROM_HERE, + base::Bind(&VaapiVideoEncodeAccelerator::EncodeTask, + base::Unretained(this), + frame, + force_keyframe)); +} + +bool VaapiVideoEncodeAccelerator::PrepareNextJob() { + if (available_va_surface_ids_.size() < kMinSurfacesToEncode) + return false; + + DCHECK(!current_encode_job_); + current_encode_job_.reset(new EncodeJob()); + + if (!vaapi_wrapper_->CreateCodedBuffer(output_buffer_byte_size_, + ¤t_encode_job_->coded_buffer)) { + NOTIFY_ERROR(kPlatformFailureError, "Failed creating coded buffer"); + return false; + } + + current_encode_job_->input_surface = + new VASurface(available_va_surface_ids_.back(), va_surface_release_cb_); + available_va_surface_ids_.pop_back(); + + current_encode_job_->recon_surface = + new VASurface(available_va_surface_ids_.back(), va_surface_release_cb_); + available_va_surface_ids_.pop_back(); + + // Reference surfaces are needed until the job is done, but they get + // removed from ref_pic_list0_ when it's full at the end of job submission. + // Keep refs to them along with the job and only release after sync. + current_encode_job_->reference_surfaces = ref_pic_list0_; + + return true; +} + +void VaapiVideoEncodeAccelerator::EncodeTask( + const scoped_refptr<media::VideoFrame>& frame, + bool force_keyframe) { + DCHECK(encoder_thread_proxy_->BelongsToCurrentThread()); + DCHECK_NE(state_, kUninitialized); + + encoder_input_queue_.push( + make_linked_ptr(new InputFrameRef(frame, force_keyframe))); + EncodeFrameTask(); +} + +void VaapiVideoEncodeAccelerator::EncodeFrameTask() { + DCHECK(encoder_thread_proxy_->BelongsToCurrentThread()); + + if (state_ != kEncoding || encoder_input_queue_.empty()) + return; + + if (!PrepareNextJob()) { + DVLOGF(4) << "Not ready for next frame yet"; + return; + } + + linked_ptr<InputFrameRef> frame_ref = encoder_input_queue_.front(); + encoder_input_queue_.pop(); + + if (!UploadFrame(frame_ref->frame)) { + NOTIFY_ERROR(kPlatformFailureError, "Failed uploading source frame to HW."); + return; + } + + BeginFrame(frame_ref->force_keyframe || encoding_parameters_changed_); + encoding_parameters_changed_ = false; + + if (!SubmitFrameParameters()) { + NOTIFY_ERROR(kPlatformFailureError, "Failed submitting frame parameters."); + return; + } + + if (!SubmitHeadersIfNeeded()) { + NOTIFY_ERROR(kPlatformFailureError, "Failed submitting frame headers."); + return; + } + + if (!ExecuteEncode()) { + NOTIFY_ERROR(kPlatformFailureError, "Failed submitting encode job to HW."); + return; + } + + EndFrame(); + TryToReturnBitstreamBuffer(); +} + +void VaapiVideoEncodeAccelerator::UseOutputBitstreamBuffer( + const media::BitstreamBuffer& buffer) { + DVLOGF(4) << "id: " << buffer.id(); + DCHECK(child_message_loop_proxy_->BelongsToCurrentThread()); + + if (buffer.size() < output_buffer_byte_size_) { + NOTIFY_ERROR(kInvalidArgumentError, "Provided bitstream buffer too small"); + return; + } + + scoped_ptr<base::SharedMemory> shm( + new base::SharedMemory(buffer.handle(), false)); + if (!shm->Map(buffer.size())) { + NOTIFY_ERROR(kPlatformFailureError, "Failed mapping shared memory."); + return; + } + + scoped_ptr<BitstreamBufferRef> buffer_ref( + new BitstreamBufferRef(buffer.id(), shm.Pass(), buffer.size())); + + encoder_thread_proxy_->PostTask( + FROM_HERE, + base::Bind(&VaapiVideoEncodeAccelerator::UseOutputBitstreamBufferTask, + base::Unretained(this), + base::Passed(&buffer_ref))); +} + +void VaapiVideoEncodeAccelerator::UseOutputBitstreamBufferTask( + scoped_ptr<BitstreamBufferRef> buffer_ref) { + DCHECK(encoder_thread_proxy_->BelongsToCurrentThread()); + DCHECK_NE(state_, kUninitialized); + + available_bitstream_buffers_.push(make_linked_ptr(buffer_ref.release())); + TryToReturnBitstreamBuffer(); +} + +void VaapiVideoEncodeAccelerator::RequestEncodingParametersChange( + uint32 bitrate, + uint32 framerate) { + DVLOGF(2) << "bitrate: " << bitrate << " framerate: " << framerate; + DCHECK(child_message_loop_proxy_->BelongsToCurrentThread()); + + encoder_thread_proxy_->PostTask( + FROM_HERE, + base::Bind( + &VaapiVideoEncodeAccelerator::RequestEncodingParametersChangeTask, + base::Unretained(this), + bitrate, + framerate)); +} + +void VaapiVideoEncodeAccelerator::UpdateRates(uint32 bitrate, + uint32 framerate) { + if (encoder_thread_.IsRunning()) + DCHECK(encoder_thread_proxy_->BelongsToCurrentThread()); + DCHECK_NE(bitrate, 0u); + DCHECK_NE(framerate, 0u); + bitrate_ = bitrate; + framerate_ = framerate; + cpb_size_ = bitrate_ * kCPBWindowSizeMs / 1000; +} + +void VaapiVideoEncodeAccelerator::RequestEncodingParametersChangeTask( + uint32 bitrate, + uint32 framerate) { + DVLOGF(2) << "bitrate: " << bitrate << " framerate: " << framerate; + DCHECK(encoder_thread_proxy_->BelongsToCurrentThread()); + DCHECK_NE(state_, kUninitialized); + + UpdateRates(bitrate, framerate); + + UpdateSPS(); + GeneratePackedSPS(); + + // Submit new parameters along with next frame that will be processed. + encoding_parameters_changed_ = true; +} + +void VaapiVideoEncodeAccelerator::Destroy() { + DCHECK(child_message_loop_proxy_->BelongsToCurrentThread()); + + // Can't call client anymore after Destroy() returns. + client_ptr_factory_.reset(); + weak_this_ptr_factory_.InvalidateWeakPtrs(); + + // Early-exit encoder tasks if they are running and join the thread. + if (encoder_thread_.IsRunning()) { + encoder_thread_.message_loop()->PostTask( + FROM_HERE, + base::Bind(&VaapiVideoEncodeAccelerator::DestroyTask, + base::Unretained(this))); + encoder_thread_.Stop(); + } + + delete this; +} + +void VaapiVideoEncodeAccelerator::DestroyTask() { + DVLOGF(2); + DCHECK(encoder_thread_proxy_->BelongsToCurrentThread()); + SetState(kError); +} + +void VaapiVideoEncodeAccelerator::UpdateSPS() { + memset(¤t_sps_, 0, sizeof(media::H264SPS)); + + // Spec A.2 and A.3. + switch (profile_) { + case media::H264PROFILE_BASELINE: + // Due to crbug.com/345569, we don't distinguish between constrained + // and non-constrained baseline profiles. Since many codecs can't do + // non-constrained, and constrained is usually what we mean (and it's a + // subset of non-constrained), default to it. + current_sps_.profile_idc = media::H264SPS::kProfileIDCBaseline; + current_sps_.constraint_set0_flag = true; + break; + case media::H264PROFILE_MAIN: + current_sps_.profile_idc = media::H264SPS::kProfileIDCMain; + current_sps_.constraint_set1_flag = true; + break; + case media::H264PROFILE_HIGH: + current_sps_.profile_idc = media::H264SPS::kProfileIDCHigh; + break; + default: + NOTIMPLEMENTED(); + return; + } + + current_sps_.level_idc = kDefaultLevelIDC; + current_sps_.seq_parameter_set_id = 0; + current_sps_.chroma_format_idc = kChromaFormatIDC; + + DCHECK_GE(idr_period_, 1u << 4); + current_sps_.log2_max_frame_num_minus4 = Log2OfPowerOf2(idr_period_) - 4; + current_sps_.pic_order_cnt_type = 0; + current_sps_.log2_max_pic_order_cnt_lsb_minus4 = + Log2OfPowerOf2(idr_period_ * 2) - 4; + current_sps_.max_num_ref_frames = max_ref_idx_l0_size_; + + current_sps_.frame_mbs_only_flag = true; + + DCHECK_GT(mb_width_, 0u); + DCHECK_GT(mb_height_, 0u); + current_sps_.pic_width_in_mbs_minus1 = mb_width_ - 1; + DCHECK(current_sps_.frame_mbs_only_flag); + current_sps_.pic_height_in_map_units_minus1 = mb_height_ - 1; + + if (visible_size_ != coded_size_) { + // Visible size differs from coded size, fill crop information. + current_sps_.frame_cropping_flag = true; + DCHECK(!current_sps_.separate_colour_plane_flag); + // Spec table 6-1. Only 4:2:0 for now. + DCHECK_EQ(current_sps_.chroma_format_idc, 1); + // Spec 7.4.2.1.1. Crop is in crop units, which is 2 pixels for 4:2:0. + const unsigned int crop_unit_x = 2; + const unsigned int crop_unit_y = 2 * (2 - current_sps_.frame_mbs_only_flag); + current_sps_.frame_crop_left_offset = 0; + current_sps_.frame_crop_right_offset = + (coded_size_.width() - visible_size_.width()) / crop_unit_x; + current_sps_.frame_crop_top_offset = 0; + current_sps_.frame_crop_bottom_offset = + (coded_size_.height() - visible_size_.height()) / crop_unit_y; + } + + current_sps_.vui_parameters_present_flag = true; + current_sps_.timing_info_present_flag = true; + current_sps_.num_units_in_tick = 1; + current_sps_.time_scale = framerate_ * 2; // See equation D-2 in spec. + current_sps_.fixed_frame_rate_flag = true; + + current_sps_.nal_hrd_parameters_present_flag = true; + // H.264 spec ch. E.2.2. + current_sps_.cpb_cnt_minus1 = 0; + current_sps_.bit_rate_scale = kBitRateScale; + current_sps_.cpb_size_scale = kCPBSizeScale; + current_sps_.bit_rate_value_minus1[0] = + (bitrate_ >> + (kBitRateScale + media::H264SPS::kBitRateScaleConstantTerm)) - 1; + current_sps_.cpb_size_value_minus1[0] = + (cpb_size_ >> + (kCPBSizeScale + media::H264SPS::kCPBSizeScaleConstantTerm)) - 1; + current_sps_.cbr_flag[0] = true; + current_sps_.initial_cpb_removal_delay_length_minus_1 = + media::H264SPS::kDefaultInitialCPBRemovalDelayLength - 1; + current_sps_.cpb_removal_delay_length_minus1 = + media::H264SPS::kDefaultInitialCPBRemovalDelayLength - 1; + current_sps_.dpb_output_delay_length_minus1 = + media::H264SPS::kDefaultDPBOutputDelayLength - 1; + current_sps_.time_offset_length = media::H264SPS::kDefaultTimeOffsetLength; + current_sps_.low_delay_hrd_flag = false; +} + +void VaapiVideoEncodeAccelerator::GeneratePackedSPS() { + packed_sps_.Reset(); + + packed_sps_.BeginNALU(media::H264NALU::kSPS, 3); + + packed_sps_.AppendBits(8, current_sps_.profile_idc); + packed_sps_.AppendBool(current_sps_.constraint_set0_flag); + packed_sps_.AppendBool(current_sps_.constraint_set1_flag); + packed_sps_.AppendBool(current_sps_.constraint_set2_flag); + packed_sps_.AppendBool(current_sps_.constraint_set3_flag); + packed_sps_.AppendBool(current_sps_.constraint_set4_flag); + packed_sps_.AppendBool(current_sps_.constraint_set5_flag); + packed_sps_.AppendBits(2, 0); // reserved_zero_2bits + packed_sps_.AppendBits(8, current_sps_.level_idc); + packed_sps_.AppendUE(current_sps_.seq_parameter_set_id); + + if (current_sps_.profile_idc == media::H264SPS::kProfileIDCHigh) { + packed_sps_.AppendUE(current_sps_.chroma_format_idc); + if (current_sps_.chroma_format_idc == 3) + packed_sps_.AppendBool(current_sps_.separate_colour_plane_flag); + packed_sps_.AppendUE(current_sps_.bit_depth_luma_minus8); + packed_sps_.AppendUE(current_sps_.bit_depth_chroma_minus8); + packed_sps_.AppendBool(current_sps_.qpprime_y_zero_transform_bypass_flag); + packed_sps_.AppendBool(current_sps_.seq_scaling_matrix_present_flag); + CHECK(!current_sps_.seq_scaling_matrix_present_flag); + } + + packed_sps_.AppendUE(current_sps_.log2_max_frame_num_minus4); + packed_sps_.AppendUE(current_sps_.pic_order_cnt_type); + if (current_sps_.pic_order_cnt_type == 0) + packed_sps_.AppendUE(current_sps_.log2_max_pic_order_cnt_lsb_minus4); + else if (current_sps_.pic_order_cnt_type == 1) { + CHECK(1); + } + + packed_sps_.AppendUE(current_sps_.max_num_ref_frames); + packed_sps_.AppendBool(current_sps_.gaps_in_frame_num_value_allowed_flag); + packed_sps_.AppendUE(current_sps_.pic_width_in_mbs_minus1); + packed_sps_.AppendUE(current_sps_.pic_height_in_map_units_minus1); + + packed_sps_.AppendBool(current_sps_.frame_mbs_only_flag); + if (!current_sps_.frame_mbs_only_flag) + packed_sps_.AppendBool(current_sps_.mb_adaptive_frame_field_flag); + + packed_sps_.AppendBool(current_sps_.direct_8x8_inference_flag); + + packed_sps_.AppendBool(current_sps_.frame_cropping_flag); + if (current_sps_.frame_cropping_flag) { + packed_sps_.AppendUE(current_sps_.frame_crop_left_offset); + packed_sps_.AppendUE(current_sps_.frame_crop_right_offset); + packed_sps_.AppendUE(current_sps_.frame_crop_top_offset); + packed_sps_.AppendUE(current_sps_.frame_crop_bottom_offset); + } + + packed_sps_.AppendBool(current_sps_.vui_parameters_present_flag); + if (current_sps_.vui_parameters_present_flag) { + packed_sps_.AppendBool(false); // aspect_ratio_info_present_flag + packed_sps_.AppendBool(false); // overscan_info_present_flag + packed_sps_.AppendBool(false); // video_signal_type_present_flag + packed_sps_.AppendBool(false); // chroma_loc_info_present_flag + + packed_sps_.AppendBool(current_sps_.timing_info_present_flag); + if (current_sps_.timing_info_present_flag) { + packed_sps_.AppendBits(32, current_sps_.num_units_in_tick); + packed_sps_.AppendBits(32, current_sps_.time_scale); + packed_sps_.AppendBool(current_sps_.fixed_frame_rate_flag); + } + + packed_sps_.AppendBool(current_sps_.nal_hrd_parameters_present_flag); + if (current_sps_.nal_hrd_parameters_present_flag) { + packed_sps_.AppendUE(current_sps_.cpb_cnt_minus1); + packed_sps_.AppendBits(4, current_sps_.bit_rate_scale); + packed_sps_.AppendBits(4, current_sps_.cpb_size_scale); + CHECK_LT(base::checked_cast<size_t>(current_sps_.cpb_cnt_minus1), + arraysize(current_sps_.bit_rate_value_minus1)); + for (int i = 0; i <= current_sps_.cpb_cnt_minus1; ++i) { + packed_sps_.AppendUE(current_sps_.bit_rate_value_minus1[i]); + packed_sps_.AppendUE(current_sps_.cpb_size_value_minus1[i]); + packed_sps_.AppendBool(current_sps_.cbr_flag[i]); + } + packed_sps_.AppendBits( + 5, current_sps_.initial_cpb_removal_delay_length_minus_1); + packed_sps_.AppendBits(5, current_sps_.cpb_removal_delay_length_minus1); + packed_sps_.AppendBits(5, current_sps_.dpb_output_delay_length_minus1); + packed_sps_.AppendBits(5, current_sps_.time_offset_length); + } + + packed_sps_.AppendBool(false); // vcl_hrd_parameters_flag + if (current_sps_.nal_hrd_parameters_present_flag) + packed_sps_.AppendBool(current_sps_.low_delay_hrd_flag); + + packed_sps_.AppendBool(false); // pic_struct_present_flag + packed_sps_.AppendBool(false); // bitstream_restriction_flag + } + + packed_sps_.FinishNALU(); +} + +void VaapiVideoEncodeAccelerator::UpdatePPS() { + memset(¤t_pps_, 0, sizeof(media::H264PPS)); + + current_pps_.seq_parameter_set_id = current_sps_.seq_parameter_set_id; + current_pps_.pic_parameter_set_id = 0; + + current_pps_.entropy_coding_mode_flag = + current_sps_.profile_idc >= media::H264SPS::kProfileIDCMain; + + CHECK_GT(max_ref_idx_l0_size_, 0u); + current_pps_.num_ref_idx_l0_default_active_minus1 = max_ref_idx_l0_size_ - 1; + current_pps_.num_ref_idx_l1_default_active_minus1 = 0; + DCHECK_LE(qp_, 51u); + current_pps_.pic_init_qp_minus26 = qp_ - 26; + current_pps_.deblocking_filter_control_present_flag = true; + current_pps_.transform_8x8_mode_flag = + (current_sps_.profile_idc == media::H264SPS::kProfileIDCHigh); +} + +void VaapiVideoEncodeAccelerator::GeneratePackedPPS() { + packed_pps_.Reset(); + + packed_pps_.BeginNALU(media::H264NALU::kPPS, 3); + + packed_pps_.AppendUE(current_pps_.pic_parameter_set_id); + packed_pps_.AppendUE(current_pps_.seq_parameter_set_id); + packed_pps_.AppendBool(current_pps_.entropy_coding_mode_flag); + packed_pps_.AppendBool( + current_pps_.bottom_field_pic_order_in_frame_present_flag); + CHECK_EQ(current_pps_.num_slice_groups_minus1, 0); + packed_pps_.AppendUE(current_pps_.num_slice_groups_minus1); + + packed_pps_.AppendUE(current_pps_.num_ref_idx_l0_default_active_minus1); + packed_pps_.AppendUE(current_pps_.num_ref_idx_l1_default_active_minus1); + + packed_pps_.AppendBool(current_pps_.weighted_pred_flag); + packed_pps_.AppendBits(2, current_pps_.weighted_bipred_idc); + + packed_pps_.AppendSE(current_pps_.pic_init_qp_minus26); + packed_pps_.AppendSE(current_pps_.pic_init_qs_minus26); + packed_pps_.AppendSE(current_pps_.chroma_qp_index_offset); + + packed_pps_.AppendBool(current_pps_.deblocking_filter_control_present_flag); + packed_pps_.AppendBool(current_pps_.constrained_intra_pred_flag); + packed_pps_.AppendBool(current_pps_.redundant_pic_cnt_present_flag); + + packed_pps_.AppendBool(current_pps_.transform_8x8_mode_flag); + packed_pps_.AppendBool(current_pps_.pic_scaling_matrix_present_flag); + DCHECK(!current_pps_.pic_scaling_matrix_present_flag); + packed_pps_.AppendSE(current_pps_.second_chroma_qp_index_offset); + + packed_pps_.FinishNALU(); +} + +void VaapiVideoEncodeAccelerator::SetState(State state) { + // Only touch state on encoder thread, unless it's not running. + if (encoder_thread_.IsRunning() && + !encoder_thread_proxy_->BelongsToCurrentThread()) { + encoder_thread_proxy_->PostTask( + FROM_HERE, + base::Bind(&VaapiVideoEncodeAccelerator::SetState, + base::Unretained(this), + state)); + return; + } + + DVLOGF(1) << "setting state to: " << state; + state_ = state; +} + +void VaapiVideoEncodeAccelerator::NotifyError(Error error) { + if (!child_message_loop_proxy_->BelongsToCurrentThread()) { + child_message_loop_proxy_->PostTask( + FROM_HERE, + base::Bind( + &VaapiVideoEncodeAccelerator::NotifyError, weak_this_, error)); + return; + } + + if (client_) { + client_->NotifyError(error); + client_ptr_factory_.reset(); + } +} + +VaapiVideoEncodeAccelerator::EncodeJob::EncodeJob() + : coded_buffer(VA_INVALID_ID), keyframe(false) { +} + +VaapiVideoEncodeAccelerator::EncodeJob::~EncodeJob() { +} + +} // namespace content diff --git a/content/common/gpu/media/vaapi_video_encode_accelerator.h b/content/common/gpu/media/vaapi_video_encode_accelerator.h new file mode 100644 index 0000000..8a7811a --- /dev/null +++ b/content/common/gpu/media/vaapi_video_encode_accelerator.h @@ -0,0 +1,264 @@ +// Copyright 2014 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. + +#ifndef CONTENT_COMMON_GPU_MEDIA_VAAPI_VIDEO_ENCODE_ACCELERATOR_H_ +#define CONTENT_COMMON_GPU_MEDIA_VAAPI_VIDEO_ENCODE_ACCELERATOR_H_ + +#include <list> +#include <queue> + +#include "base/memory/linked_ptr.h" +#include "base/threading/thread.h" +#include "content/common/content_export.h" +#include "content/common/gpu/media/h264_dpb.h" +#include "content/common/gpu/media/va_surface.h" +#include "content/common/gpu/media/vaapi_wrapper.h" +#include "media/filters/h264_bitstream_buffer.h" +#include "media/video/video_encode_accelerator.h" + +namespace content { + +// A VideoEncodeAccelerator implementation that uses VA-API +// (http://www.freedesktop.org/wiki/Software/vaapi) for HW-accelerated +// video encode. +class CONTENT_EXPORT VaapiVideoEncodeAccelerator + : public media::VideoEncodeAccelerator { + public: + explicit VaapiVideoEncodeAccelerator(Display* x_display); + virtual ~VaapiVideoEncodeAccelerator(); + + // media::VideoEncodeAccelerator implementation. + virtual bool Initialize(media::VideoFrame::Format format, + const gfx::Size& input_visible_size, + media::VideoCodecProfile output_profile, + uint32 initial_bitrate, + Client* client) OVERRIDE; + virtual void Encode(const scoped_refptr<media::VideoFrame>& frame, + bool force_keyframe) OVERRIDE; + virtual void UseOutputBitstreamBuffer( + const media::BitstreamBuffer& buffer) OVERRIDE; + virtual void RequestEncodingParametersChange(uint32 bitrate, + uint32 framerate) OVERRIDE; + virtual void Destroy() OVERRIDE; + + static std::vector<media::VideoEncodeAccelerator::SupportedProfile> + GetSupportedProfiles(); + + private: + // Reference picture list. + typedef std::list<scoped_refptr<VASurface> > RefPicList; + + // Encode job for one frame. Created when an input frame is awaiting and + // enough resources are available to proceed. Once the job is prepared and + // submitted to the hardware, it awaits on the submitted_encode_jobs_ queue + // for an output bitstream buffer to become available. Once one is ready, + // the encoded bytes are downloaded to it and job resources are released + // and become available for reuse. + struct EncodeJob { + // Input surface for video frame data. + scoped_refptr<VASurface> input_surface; + // Surface for a reconstructed picture, which is used for reference + // for subsequent frames. + scoped_refptr<VASurface> recon_surface; + // Buffer that will contain output bitstream for this frame. + VABufferID coded_buffer; + // Reference surfaces required to encode this picture. We keep references + // to them here, because we may discard some of them from ref_pic_list* + // before the HW job is done. + RefPicList reference_surfaces; + // True if this job will produce a keyframe. Used to report + // to BitstreamBufferReady(). + bool keyframe; + + EncodeJob(); + ~EncodeJob(); + }; + + // Encoder state. + enum State { + kUninitialized, + kEncoding, + kError, + }; + + // Holds input frames coming from the client ready to be encoded. + struct InputFrameRef; + // Holds output buffers coming from the client ready to be filled. + struct BitstreamBufferRef; + + // Tasks for each of the VEA interface calls to be executed on the + // encoder thread. + void InitializeTask(); + void EncodeTask(const scoped_refptr<media::VideoFrame>& frame, + bool force_keyframe); + void UseOutputBitstreamBufferTask(scoped_ptr<BitstreamBufferRef> buffer_ref); + void RequestEncodingParametersChangeTask(uint32 bitrate, uint32 framerate); + void DestroyTask(); + + // Prepare and schedule an encode job if we have an input to encode + // and enough resources to proceed. + void EncodeFrameTask(); + + // Fill current_sps_/current_pps_ with current values. + void UpdateSPS(); + void UpdatePPS(); + void UpdateRates(uint32 bitrate, uint32 framerate); + + // Generate packed SPS and PPS in packed_sps_/packed_pps_, using + // values in current_sps_/current_pps_. + void GeneratePackedSPS(); + void GeneratePackedPPS(); + + // Check if we have sufficient resources for a new encode job, claim them and + // fill current_encode_job_ with them. + // Return false if we cannot start a new job yet, true otherwise. + bool PrepareNextJob(); + + // Begin a new frame, making it a keyframe if |force_keyframe| is true, + // updating current_pic_. + void BeginFrame(bool force_keyframe); + + // End current frame, updating reference picture lists and storing current + // job in the jobs awaiting completion on submitted_encode_jobs_. + void EndFrame(); + + // Submit parameters for the current frame to the hardware. + bool SubmitFrameParameters(); + // Submit keyframe headers to the hardware if the current frame is a keyframe. + bool SubmitHeadersIfNeeded(); + + // Upload image data from |frame| to the input surface for current job. + bool UploadFrame(const scoped_refptr<media::VideoFrame>& frame); + + // Execute encode in hardware. This does not block and will return before + // the job is finished. + bool ExecuteEncode(); + + // Callback that returns a no longer used VASurfaceID to + // available_va_surface_ids_ for reuse. + void RecycleVASurfaceID(VASurfaceID va_surface_id); + + // Tries to return a bitstream buffer if both a submitted job awaits to + // be completed and we have bitstream buffers from the client available + // to download the encoded data to. + void TryToReturnBitstreamBuffer(); + + // Puts the encoder into en error state and notifies client about the error. + void NotifyError(Error error); + + // Sets the encoder state on the correct thread. + void SetState(State state); + + // VaapiWrapper is the owner of all HW resources (surfaces and buffers) + // and will free them on destruction. + scoped_ptr<VaapiWrapper> vaapi_wrapper_; + + // Input profile and sizes. + media::VideoCodecProfile profile_; + gfx::Size visible_size_; + gfx::Size coded_size_; // Macroblock-aligned. + // Width/height in macroblocks. + unsigned int mb_width_; + unsigned int mb_height_; + + // Maximum size of the reference list 0. + unsigned int max_ref_idx_l0_size_; + + // Initial QP. + unsigned int qp_; + + // IDR frame period. + unsigned int idr_period_; + // I frame period. + unsigned int i_period_; + // IP period, i.e. how often do we need to have either an I or a P frame in + // the stream. Period of 1 means we can have no B frames. + unsigned int ip_period_; + + // Size in bytes required for input bitstream buffers. + size_t output_buffer_byte_size_; + + Display* x_display_; + + // All of the members below must be accessed on the encoder_thread_, + // while it is running. + + // Encoder state. Encode tasks will only run in kEncoding state. + State state_; + + // frame_num to be used for the next frame. + unsigned int frame_num_; + // frame_num of the previous IDR. + unsigned int last_idr_frame_num_; + + // Current bitrate in bps. + unsigned int bitrate_; + // Current fps. + unsigned int framerate_; + // CPB size in bits, i.e. bitrate in kbps * window size in ms/1000. + unsigned int cpb_size_; + // True if the parameters have changed and we need to submit a keyframe + // with updated parameters. + bool encoding_parameters_changed_; + + // Job currently being prepared for encode. + scoped_ptr<EncodeJob> current_encode_job_; + + // Current SPS, PPS and their packed versions. Packed versions are their NALUs + // in AnnexB format *without* emulation prevention three-byte sequences + // (those will be added by the driver). + media::H264SPS current_sps_; + media::H264BitstreamBuffer packed_sps_; + media::H264PPS current_pps_; + media::H264BitstreamBuffer packed_pps_; + + // Picture currently being prepared for encode. + H264Picture current_pic_; + + // VA surfaces available for reuse. + std::vector<VASurfaceID> available_va_surface_ids_; + + // VA buffers for coded frames. + std::vector<VABufferID> available_va_buffer_ids_; + + // Currently active reference surfaces. + RefPicList ref_pic_list0_; + + // Callback via which finished VA surfaces are returned to us. + VASurface::ReleaseCB va_surface_release_cb_; + + // VideoFrames passed from the client, waiting to be encoded. + std::queue<linked_ptr<InputFrameRef> > encoder_input_queue_; + + // BitstreamBuffers mapped, ready to be filled. + std::queue<linked_ptr<BitstreamBufferRef> > available_bitstream_buffers_; + + // Jobs submitted for encode, awaiting bitstream buffers to become available. + std::queue<linked_ptr<EncodeJob> > submitted_encode_jobs_; + + // Encoder thread. All tasks are executed on it. + base::Thread encoder_thread_; + scoped_refptr<base::MessageLoopProxy> encoder_thread_proxy_; + + const scoped_refptr<base::MessageLoopProxy> child_message_loop_proxy_; + + // To expose client callbacks from VideoEncodeAccelerator. + // NOTE: all calls to these objects *MUST* be executed on + // child_message_loop_proxy_. + scoped_ptr<base::WeakPtrFactory<Client> > client_ptr_factory_; + base::WeakPtr<Client> client_; + + // WeakPtr to post from the encoder thread back to the ChildThread, as it may + // outlive this. Posting from the ChildThread using base::Unretained(this) + // to the encoder thread is safe, because |this| always outlives the encoder + // thread (it's a member of this class). + base::WeakPtr<VaapiVideoEncodeAccelerator> weak_this_; + base::WeakPtrFactory<VaapiVideoEncodeAccelerator> weak_this_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(VaapiVideoEncodeAccelerator); +}; + +} // namespace content + +#endif // CONTENT_COMMON_GPU_MEDIA_VAAPI_VIDEO_ENCODE_ACCELERATOR_H_ diff --git a/content/common/gpu/media/vaapi_wrapper.cc b/content/common/gpu/media/vaapi_wrapper.cc index 5250e59..5e93b99 100644 --- a/content/common/gpu/media/vaapi_wrapper.cc +++ b/content/common/gpu/media/vaapi_wrapper.cc @@ -7,10 +7,12 @@ #include <dlfcn.h> #include "base/bind.h" +#include "base/callback_helpers.h" #include "base/logging.h" #include "base/numerics/safe_conversions.h" // Auto-generated for dlopen libva libraries #include "content/common/gpu/media/va_stubs.h" +#include "third_party/libyuv/include/libyuv.h" using content_common_gpu_media::kModuleVa; using content_common_gpu_media::InitializeStubs; @@ -43,6 +45,18 @@ static const base::FilePath::CharType kVaLib[] = namespace content { +// Config attributes common for both encode and decode. +static const VAConfigAttrib kCommonVAConfigAttribs[] = { + {VAConfigAttribRTFormat, VA_RT_FORMAT_YUV420}, +}; + +// Attributes required for encode. +static const VAConfigAttrib kEncodeVAConfigAttribs[] = { + {VAConfigAttribRateControl, VA_RC_CBR}, + {VAConfigAttribEncPackedHeaders, + VA_ENC_PACKED_HEADER_SEQUENCE | VA_ENC_PACKED_HEADER_PICTURE}, +}; + // Maps Profile enum values to VaProfile values. static VAProfile ProfileToVAProfile( media::VideoCodecProfile profile, @@ -106,17 +120,20 @@ VaapiWrapper::VaapiWrapper() VaapiWrapper::~VaapiWrapper() { DestroyPendingBuffers(); + DestroyCodedBuffers(); DestroySurfaces(); Deinitialize(); } scoped_ptr<VaapiWrapper> VaapiWrapper::Create( + CodecMode mode, media::VideoCodecProfile profile, Display* x_display, const base::Closure& report_error_to_uma_cb) { scoped_ptr<VaapiWrapper> vaapi_wrapper(new VaapiWrapper()); - if (!vaapi_wrapper->Initialize(profile, x_display, report_error_to_uma_cb)) + if (!vaapi_wrapper->Initialize( + mode, profile, x_display, report_error_to_uma_cb)) vaapi_wrapper.reset(); return vaapi_wrapper.Pass(); @@ -134,7 +151,8 @@ void VaapiWrapper::TryToSetVADisplayAttributeToLocalGPU() { DVLOG(2) << "vaSetDisplayAttributes unsupported, ignoring by default."; } -bool VaapiWrapper::Initialize(media::VideoCodecProfile profile, +bool VaapiWrapper::Initialize(CodecMode mode, + media::VideoCodecProfile profile, Display* x_display, const base::Closure& report_error_to_uma_cb) { static bool vaapi_functions_initialized = PostSandboxInitialization(); @@ -184,21 +202,73 @@ bool VaapiWrapper::Initialize(media::VideoCodecProfile profile, return false; } - VAConfigAttrib attrib = {VAConfigAttribRTFormat, 0}; - const VAEntrypoint kEntrypoint = VAEntrypointVLD; - va_res = vaGetConfigAttributes(va_display_, va_profile, kEntrypoint, - &attrib, 1); - VA_SUCCESS_OR_RETURN(va_res, "vaGetConfigAttributes failed", false); + // Query the driver for supported entrypoints. + int max_entrypoints = vaMaxNumEntrypoints(va_display_); + std::vector<VAEntrypoint> supported_entrypoints( + base::checked_cast<size_t>(max_entrypoints)); + + int num_supported_entrypoints; + va_res = vaQueryConfigEntrypoints(va_display_, + va_profile, + &supported_entrypoints[0], + &num_supported_entrypoints); + VA_SUCCESS_OR_RETURN(va_res, "vaQueryConfigEntrypoints failed", false); + if (num_supported_entrypoints < 0 || + num_supported_entrypoints > max_entrypoints) { + DVLOG(1) << "vaQueryConfigEntrypoints returned: " + << num_supported_entrypoints; + return false; + } - if (!(attrib.value & VA_RT_FORMAT_YUV420)) { - DVLOG(1) << "YUV420 not supported by this VAAPI implementation"; + VAEntrypoint entrypoint = + (mode == kEncode ? VAEntrypointEncSlice : VAEntrypointVLD); + + if (std::find(supported_entrypoints.begin(), + supported_entrypoints.end(), + entrypoint) == supported_entrypoints.end()) { + DVLOG(1) << "Unsupported entrypoint"; return false; } + // Query the driver for required attributes. + std::vector<VAConfigAttrib> required_attribs; + required_attribs.insert( + required_attribs.end(), + kCommonVAConfigAttribs, + kCommonVAConfigAttribs + arraysize(kCommonVAConfigAttribs)); + if (mode == kEncode) { + required_attribs.insert( + required_attribs.end(), + kEncodeVAConfigAttribs, + kEncodeVAConfigAttribs + arraysize(kEncodeVAConfigAttribs)); + } + + std::vector<VAConfigAttrib> attribs = required_attribs; + for (size_t i = 0; i < required_attribs.size(); ++i) + attribs[i].value = 0; + + va_res = vaGetConfigAttributes( + va_display_, va_profile, entrypoint, &attribs[0], attribs.size()); + VA_SUCCESS_OR_RETURN(va_res, "vaGetConfigAttributes failed", false); + + for (size_t i = 0; i < required_attribs.size(); ++i) { + if (attribs[i].type != required_attribs[i].type || + (attribs[i].value & required_attribs[i].value) != + required_attribs[i].value) { + DVLOG(1) << "Unsupported value " << required_attribs[i].value + << " for attribute type " << required_attribs[i].type; + return false; + } + } + TryToSetVADisplayAttributeToLocalGPU(); - va_res = vaCreateConfig(va_display_, va_profile, kEntrypoint, - &attrib, 1, &va_config_id_); + va_res = vaCreateConfig(va_display_, + va_profile, + entrypoint, + &required_attribs[0], + required_attribs.size(), + &va_config_id_); VA_SUCCESS_OR_RETURN(va_res, "vaCreateConfig failed", false); return true; @@ -299,6 +369,7 @@ bool VaapiWrapper::SubmitBuffer(VABufferType va_buffer_type, switch (va_buffer_type) { case VASliceParameterBufferType: case VASliceDataBufferType: + case VAEncSliceParameterBufferType: pending_slice_bufs_.push_back(buffer_id); break; @@ -310,6 +381,43 @@ bool VaapiWrapper::SubmitBuffer(VABufferType va_buffer_type, return true; } +bool VaapiWrapper::SubmitVAEncMiscParamBuffer( + VAEncMiscParameterType misc_param_type, + size_t size, + void* buffer) { + base::AutoLock auto_lock(va_lock_); + + VABufferID buffer_id; + VAStatus va_res = vaCreateBuffer(va_display_, + va_context_id_, + VAEncMiscParameterBufferType, + sizeof(VAEncMiscParameterBuffer) + size, + 1, + NULL, + &buffer_id); + VA_SUCCESS_OR_RETURN(va_res, "Failed to create a VA buffer", false); + + void* data_ptr = NULL; + va_res = vaMapBuffer(va_display_, buffer_id, &data_ptr); + VA_LOG_ON_ERROR(va_res, "vaMapBuffer failed"); + if (va_res != VA_STATUS_SUCCESS) { + vaDestroyBuffer(va_display_, buffer_id); + return false; + } + + DCHECK(data_ptr); + + VAEncMiscParameterBuffer* misc_param = + reinterpret_cast<VAEncMiscParameterBuffer*>(data_ptr); + misc_param->type = misc_param_type; + memcpy(misc_param->data, buffer, size); + va_res = vaUnmapBuffer(va_display_, buffer_id); + VA_LOG_ON_ERROR(va_res, "vaUnmapBuffer failed"); + + pending_va_bufs_.push_back(buffer_id); + return true; +} + void VaapiWrapper::DestroyPendingBuffers() { base::AutoLock auto_lock(va_lock_); @@ -327,38 +435,73 @@ void VaapiWrapper::DestroyPendingBuffers() { pending_slice_bufs_.clear(); } -bool VaapiWrapper::SubmitDecode(VASurfaceID va_surface_id) { +bool VaapiWrapper::CreateCodedBuffer(size_t size, VABufferID* buffer_id) { + base::AutoLock auto_lock(va_lock_); + VAStatus va_res = vaCreateBuffer(va_display_, + va_context_id_, + VAEncCodedBufferType, + size, + 1, + NULL, + buffer_id); + VA_SUCCESS_OR_RETURN(va_res, "Failed to create a coded buffer", false); + + DCHECK(coded_buffers_.insert(*buffer_id).second); + return true; +} + +void VaapiWrapper::DestroyCodedBuffers() { + base::AutoLock auto_lock(va_lock_); + + for (std::set<VABufferID>::const_iterator iter = coded_buffers_.begin(); + iter != coded_buffers_.end(); + ++iter) { + VAStatus va_res = vaDestroyBuffer(va_display_, *iter); + VA_LOG_ON_ERROR(va_res, "vaDestroyBuffer failed"); + } + + coded_buffers_.clear(); +} + +bool VaapiWrapper::Execute(VASurfaceID va_surface_id) { base::AutoLock auto_lock(va_lock_); DVLOG(4) << "Pending VA bufs to commit: " << pending_va_bufs_.size(); DVLOG(4) << "Pending slice bufs to commit: " << pending_slice_bufs_.size(); - DVLOG(4) << "Decoding into VA surface " << va_surface_id; + DVLOG(4) << "Target VA surface " << va_surface_id; - // Get ready to decode into surface. + // Get ready to execute for given surface. VAStatus va_res = vaBeginPicture(va_display_, va_context_id_, va_surface_id); VA_SUCCESS_OR_RETURN(va_res, "vaBeginPicture failed", false); - // Commit parameter and slice buffers. - va_res = vaRenderPicture(va_display_, va_context_id_, - &pending_va_bufs_[0], pending_va_bufs_.size()); - VA_SUCCESS_OR_RETURN(va_res, "vaRenderPicture for va_bufs failed", false); + if (pending_va_bufs_.size() > 0) { + // Commit parameter and slice buffers. + va_res = vaRenderPicture(va_display_, + va_context_id_, + &pending_va_bufs_[0], + pending_va_bufs_.size()); + VA_SUCCESS_OR_RETURN(va_res, "vaRenderPicture for va_bufs failed", false); + } - va_res = vaRenderPicture(va_display_, va_context_id_, - &pending_slice_bufs_[0], - pending_slice_bufs_.size()); - VA_SUCCESS_OR_RETURN(va_res, "vaRenderPicture for slices failed", false); + if (pending_slice_bufs_.size() > 0) { + va_res = vaRenderPicture(va_display_, + va_context_id_, + &pending_slice_bufs_[0], + pending_slice_bufs_.size()); + VA_SUCCESS_OR_RETURN(va_res, "vaRenderPicture for slices failed", false); + } - // Instruct HW decoder to start processing committed buffers (decode this - // picture). This does not block until the end of decode. + // Instruct HW codec to start processing committed buffers. + // Does not block and the job is not finished after this returns. va_res = vaEndPicture(va_display_, va_context_id_); VA_SUCCESS_OR_RETURN(va_res, "vaEndPicture failed", false); return true; } -bool VaapiWrapper::DecodeAndDestroyPendingBuffers(VASurfaceID va_surface_id) { - bool result = SubmitDecode(va_surface_id); +bool VaapiWrapper::ExecuteAndDestroyPendingBuffers(VASurfaceID va_surface_id) { + bool result = Execute(va_surface_id); DestroyPendingBuffers(); return result; } @@ -378,8 +521,7 @@ bool VaapiWrapper::PutSurfaceIntoPixmap(VASurfaceID va_surface_id, 0, 0, dest_size.width(), dest_size.height(), 0, 0, dest_size.width(), dest_size.height(), NULL, 0, 0); - VA_SUCCESS_OR_RETURN(va_res, "Failed putting decode surface to pixmap", - false); + VA_SUCCESS_OR_RETURN(va_res, "Failed putting surface to pixmap", false); return true; } @@ -403,15 +545,124 @@ bool VaapiWrapper::GetVaImageForTesting(VASurfaceID va_surface_id, if (va_res == VA_STATUS_SUCCESS) return true; - vaDestroyImage(va_display_, image->image_id); + va_res = vaDestroyImage(va_display_, image->image_id); + VA_LOG_ON_ERROR(va_res, "vaDestroyImage failed"); + return false; } void VaapiWrapper::ReturnVaImageForTesting(VAImage* image) { base::AutoLock auto_lock(va_lock_); - vaUnmapBuffer(va_display_, image->buf); - vaDestroyImage(va_display_, image->image_id); + VAStatus va_res = vaUnmapBuffer(va_display_, image->buf); + VA_LOG_ON_ERROR(va_res, "vaUnmapBuffer failed"); + + va_res = vaDestroyImage(va_display_, image->image_id); + VA_LOG_ON_ERROR(va_res, "vaDestroyImage failed"); +} + +static void DestroyVAImage(VADisplay va_display, VAImage image) { + if (image.image_id != VA_INVALID_ID) + vaDestroyImage(va_display, image.image_id); +} + +bool VaapiWrapper::UploadVideoFrameToSurface( + const scoped_refptr<media::VideoFrame>& frame, + VASurfaceID va_surface_id) { + base::AutoLock auto_lock(va_lock_); + + VAImage image; + VAStatus va_res = vaDeriveImage(va_display_, va_surface_id, &image); + VA_SUCCESS_OR_RETURN(va_res, "vaDeriveImage failed", false); + base::ScopedClosureRunner vaimage_deleter( + base::Bind(&DestroyVAImage, va_display_, image)); + + if (image.format.fourcc != VA_FOURCC_NV12) { + DVLOG(1) << "Unsupported image format: " << image.format.fourcc; + return false; + } + + if (gfx::Rect(image.width, image.height) < gfx::Rect(frame->coded_size())) { + DVLOG(1) << "Buffer too small to fit the frame."; + return false; + } + + void* image_ptr = NULL; + va_res = vaMapBuffer(va_display_, image.buf, &image_ptr); + VA_SUCCESS_OR_RETURN(va_res, "vaMapBuffer failed", false); + DCHECK(image_ptr); + + int ret = 0; + { + base::AutoUnlock auto_unlock(va_lock_); + ret = libyuv::I420ToNV12(frame->data(media::VideoFrame::kYPlane), + frame->stride(media::VideoFrame::kYPlane), + frame->data(media::VideoFrame::kUPlane), + frame->stride(media::VideoFrame::kUPlane), + frame->data(media::VideoFrame::kVPlane), + frame->stride(media::VideoFrame::kVPlane), + static_cast<uint8*>(image_ptr) + image.offsets[0], + image.pitches[0], + static_cast<uint8*>(image_ptr) + image.offsets[1], + image.pitches[1], + image.width, + image.height); + } + + va_res = vaUnmapBuffer(va_display_, image.buf); + VA_LOG_ON_ERROR(va_res, "vaUnmapBuffer failed"); + + return ret == 0; +} + +bool VaapiWrapper::DownloadAndDestroyCodedBuffer(VABufferID buffer_id, + VASurfaceID sync_surface_id, + uint8* target_ptr, + size_t target_size, + size_t* coded_data_size) { + base::AutoLock auto_lock(va_lock_); + + VAStatus va_res = vaSyncSurface(va_display_, sync_surface_id); + VA_SUCCESS_OR_RETURN(va_res, "Failed syncing surface", false); + + VACodedBufferSegment* buffer_segment = NULL; + va_res = vaMapBuffer( + va_display_, buffer_id, reinterpret_cast<void**>(&buffer_segment)); + VA_SUCCESS_OR_RETURN(va_res, "vaMapBuffer failed", false); + DCHECK(target_ptr); + + { + base::AutoUnlock auto_unlock(va_lock_); + *coded_data_size = 0; + + while (buffer_segment) { + DCHECK(buffer_segment->buf); + + if (buffer_segment->size > target_size) { + DVLOG(1) << "Insufficient output buffer size"; + break; + } + + memcpy(target_ptr, buffer_segment->buf, buffer_segment->size); + + target_ptr += buffer_segment->size; + *coded_data_size += buffer_segment->size; + target_size -= buffer_segment->size; + + buffer_segment = + reinterpret_cast<VACodedBufferSegment*>(buffer_segment->next); + } + } + + va_res = vaUnmapBuffer(va_display_, buffer_id); + VA_LOG_ON_ERROR(va_res, "vaUnmapBuffer failed"); + + va_res = vaDestroyBuffer(va_display_, buffer_id); + VA_LOG_ON_ERROR(va_res, "vaDestroyBuffer failed"); + + DCHECK(coded_buffers_.erase(buffer_id)); + + return buffer_segment == NULL; } // static diff --git a/content/common/gpu/media/vaapi_wrapper.h b/content/common/gpu/media/vaapi_wrapper.h index 5570360..f600cdf 100644 --- a/content/common/gpu/media/vaapi_wrapper.h +++ b/content/common/gpu/media/vaapi_wrapper.h @@ -3,12 +3,16 @@ // found in the LICENSE file. // // This file contains an implementation of VaapiWrapper, used by -// VaapiVideoDecodeAccelerator and VaapiH264Decoder to interface -// with libva (VA-API library for hardware video decode). +// VaapiVideoDecodeAccelerator and VaapiH264Decoder for decode, +// and VaapiVideoEncodeAccelerator for encode, to interface +// with libva (VA-API library for hardware video codec). #ifndef CONTENT_COMMON_GPU_MEDIA_VAAPI_WRAPPER_H_ #define CONTENT_COMMON_GPU_MEDIA_VAAPI_WRAPPER_H_ +#include <set> +#include <vector> + #include "base/callback.h" #include "base/memory/ref_counted.h" #include "base/synchronization/lock.h" @@ -22,20 +26,26 @@ namespace content { // This class handles VA-API calls and ensures proper locking of VA-API calls -// to libva, the userspace shim to the HW decoder driver. libva is not +// to libva, the userspace shim to the HW codec driver. libva is not // thread-safe, so we have to perform locking ourselves. This class is fully // synchronous and its methods can be called from any thread and may wait on // the va_lock_ while other, concurrent calls run. // // This class is responsible for managing VAAPI connection, contexts and state. // It is also responsible for managing and freeing VABuffers (not VASurfaces), -// which are used to queue decode parameters and slice data to the HW decoder, +// which are used to queue parameters and slice data to the HW codec, // as well as underlying memory for VASurfaces themselves. class CONTENT_EXPORT VaapiWrapper { public: + enum CodecMode { + kDecode, + kEncode, + }; + // |report_error_to_uma_cb| will be called independently from reporting // errors to clients via method return values. static scoped_ptr<VaapiWrapper> Create( + CodecMode mode, media::VideoCodecProfile profile, Display* x_display, const base::Closure& report_error_to_uma_cb); @@ -57,20 +67,30 @@ class CONTENT_EXPORT VaapiWrapper { void DestroySurfaces(); // Submit parameters or slice data of |va_buffer_type|, copying them from - // |buffer| of size |size|, into HW decoder. The data in |buffer| is no + // |buffer| of size |size|, into HW codec. The data in |buffer| is no // longer needed and can be freed after this method returns. - // Data submitted via this method awaits in the HW decoder until - // DecodeAndDestroyPendingBuffers is called to execute or - // DestroyPendingBuffers is used to cancel a pending decode. + // Data submitted via this method awaits in the HW codec until + // ExecuteAndDestroyPendingBuffers() is called to execute or + // DestroyPendingBuffers() is used to cancel a pending job. bool SubmitBuffer(VABufferType va_buffer_type, size_t size, void* buffer); - // Cancel and destroy all buffers queued to the HW decoder via SubmitBuffer. - // Useful when a pending decode is to be cancelled (on reset or error). + // Submit a VAEncMiscParameterBuffer of given |misc_param_type|, copying its + // data from |buffer| of size |size|, into HW codec. The data in |buffer| is + // no longer needed and can be freed after this method returns. + // Data submitted via this method awaits in the HW codec until + // ExecuteAndDestroyPendingBuffers() is called to execute or + // DestroyPendingBuffers() is used to cancel a pending job. + bool SubmitVAEncMiscParamBuffer(VAEncMiscParameterType misc_param_type, + size_t size, + void* buffer); + + // Cancel and destroy all buffers queued to the HW codec via SubmitBuffer(). + // Useful when a pending job is to be cancelled (on reset or error). void DestroyPendingBuffers(); - // Execute decode in hardware into |va_surface_id} and destroy pending - // buffers. Return false if SubmitDecode() fails. - bool DecodeAndDestroyPendingBuffers(VASurfaceID va_surface_id); + // Execute job in hardware on target |va_surface_id| and destroy pending + // buffers. Return false if Execute() fails. + bool ExecuteAndDestroyPendingBuffers(VASurfaceID va_surface_id); // Put data from |va_surface_id| into |x_pixmap| of size |size|, // converting/scaling to it. @@ -92,18 +112,41 @@ class CONTENT_EXPORT VaapiWrapper { // GetVaImage(). This is intended for testing only. void ReturnVaImageForTesting(VAImage* image); + // Upload contents of |frame| into |va_surface_id| for encode. + bool UploadVideoFrameToSurface(const scoped_refptr<media::VideoFrame>& frame, + VASurfaceID va_surface_id); + + // Create a buffer of |size| bytes to be used as encode output. + bool CreateCodedBuffer(size_t size, VABufferID* buffer_id); + + // Download the contents of the buffer with given |buffer_id| into a buffer of + // size |target_size|, pointed to by |target_ptr|. The number of bytes + // downloaded will be returned in |coded_data_size|. |sync_surface_id| will + // be used as a sync point, i.e. it will have to become idle before starting + // the download. |sync_surface_id| should be the source surface passed + // to the encode job. + bool DownloadAndDestroyCodedBuffer(VABufferID buffer_id, + VASurfaceID sync_surface_id, + uint8* target_ptr, + size_t target_size, + size_t* coded_data_size); + + // Destroy all previously-allocated (and not yet destroyed) coded buffers. + void DestroyCodedBuffers(); + private: VaapiWrapper(); - bool Initialize(media::VideoCodecProfile profile, + bool Initialize(CodecMode mode, + media::VideoCodecProfile profile, Display* x_display, const base::Closure& report_error__to_uma_cb); void Deinitialize(); - // Execute decode in hardware and destroy pending buffers. Return false if - // vaapi driver refuses to accept parameter or slice buffers submitted - // by client or if decode fails in hardware. - bool SubmitDecode(VASurfaceID va_surface_id); + // Execute pending job in hardware and destroy pending buffers. Return false + // if vaapi driver refuses to accept parameter or slice buffers submitted + // by client, or if execution fails in hardware. + bool Execute(VASurfaceID va_surface_id); // Attempt to set render mode to "render to texture.". Failure is non-fatal. void TryToSetVADisplayAttributeToLocalGPU(); @@ -114,7 +157,7 @@ class CONTENT_EXPORT VaapiWrapper { // Libva is not thread safe, so we have to do locking for it ourselves. // This lock is to be taken for the duration of all VA-API calls and for - // the entire decode execution sequence in DecodeAndDestroyPendingBuffers(). + // the entire job submission sequence in ExecuteAndDestroyPendingBuffers(). base::Lock va_lock_; // Allocated ids for VASurfaces. @@ -131,11 +174,14 @@ class CONTENT_EXPORT VaapiWrapper { // valid until DestroySurfaces(). VAContextID va_context_id_; - // Data queued up for HW decoder, to be committed on next HW decode. + // Data queued up for HW codec, to be committed on next execution. std::vector<VABufferID> pending_slice_bufs_; std::vector<VABufferID> pending_va_bufs_; - // Called to report decoding errors to UMA. Errors to clients are reported via + // Bitstream buffers for encode. + std::set<VABufferID> coded_buffers_; + + // Called to report codec errors to UMA. Errors to clients are reported via // return values from public methods. base::Closure report_error_to_uma_cb_; diff --git a/content/common/gpu/media/video_encode_accelerator_unittest.cc b/content/common/gpu/media/video_encode_accelerator_unittest.cc index 5f60634..b07211d 100644 --- a/content/common/gpu/media/video_encode_accelerator_unittest.cc +++ b/content/common/gpu/media/video_encode_accelerator_unittest.cc @@ -13,7 +13,6 @@ #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" #include "base/time/time.h" -#include "content/common/gpu/media/v4l2_video_encode_accelerator.h" #include "content/common/gpu/media/video_accelerator_unittest_helpers.h" #include "media/base/bind_to_current_loop.h" #include "media/base/bitstream_buffer.h" @@ -22,6 +21,18 @@ #include "media/video/video_encode_accelerator.h" #include "testing/gtest/include/gtest/gtest.h" +#if defined(USE_X11) +#include "ui/gfx/x/x11_types.h" +#endif + +#if defined(OS_CHROMEOS) && defined(ARCH_CPU_ARMEL) +#include "content/common/gpu/media/v4l2_video_encode_accelerator.h" +#elif defined(OS_CHROMEOS) && defined(ARCH_CPU_X86_FAMILY) && defined(USE_X11) +#include "content/common/gpu/media/vaapi_video_encode_accelerator.h" +#else +#error The VideoEncodeAcceleratorUnittest is not supported on this platform. +#endif + using media::VideoEncodeAccelerator; namespace content { @@ -244,28 +255,29 @@ class H264Validator : public StreamValidator { seen_pps_(false), seen_idr_(false) {} - void ProcessStreamBuffer(const uint8* stream, size_t size) OVERRIDE; + virtual void ProcessStreamBuffer(const uint8* stream, size_t size) OVERRIDE; private: // Set to true when encoder provides us with the corresponding NALU type. bool seen_sps_; bool seen_pps_; bool seen_idr_; + + media::H264Parser h264_parser_; }; void H264Validator::ProcessStreamBuffer(const uint8* stream, size_t size) { - media::H264Parser h264_parser; - h264_parser.SetStream(stream, size); + h264_parser_.SetStream(stream, size); while (1) { media::H264NALU nalu; media::H264Parser::Result result; - result = h264_parser.AdvanceToNextNALU(&nalu); + result = h264_parser_.AdvanceToNextNALU(&nalu); if (result == media::H264Parser::kEOStream) break; - ASSERT_EQ(result, media::H264Parser::kOk); + ASSERT_EQ(media::H264Parser::kOk, result); bool keyframe = false; @@ -273,22 +285,35 @@ void H264Validator::ProcessStreamBuffer(const uint8* stream, size_t size) { case media::H264NALU::kIDRSlice: ASSERT_TRUE(seen_sps_); ASSERT_TRUE(seen_pps_); - seen_idr_ = keyframe = true; + seen_idr_ = true; // fallthrough - case media::H264NALU::kNonIDRSlice: + case media::H264NALU::kNonIDRSlice: { ASSERT_TRUE(seen_idr_); + + media::H264SliceHeader shdr; + ASSERT_EQ(media::H264Parser::kOk, + h264_parser_.ParseSliceHeader(nalu, &shdr)); + keyframe = shdr.IsISlice() || shdr.IsSISlice(); + if (!frame_cb_.Run(keyframe)) return; break; + } - case media::H264NALU::kSPS: + case media::H264NALU::kSPS: { + int sps_id; + ASSERT_EQ(media::H264Parser::kOk, h264_parser_.ParseSPS(&sps_id)); seen_sps_ = true; break; + } - case media::H264NALU::kPPS: + case media::H264NALU::kPPS: { ASSERT_TRUE(seen_sps_); + int pps_id; + ASSERT_EQ(media::H264Parser::kOk, h264_parser_.ParsePPS(&pps_id)); seen_pps_ = true; break; + } default: break; @@ -302,7 +327,7 @@ class VP8Validator : public StreamValidator { : StreamValidator(frame_cb), seen_keyframe_(false) {} - void ProcessStreamBuffer(const uint8* stream, size_t size) OVERRIDE; + virtual void ProcessStreamBuffer(const uint8* stream, size_t size) OVERRIDE; private: // Have we already got a keyframe in the stream? @@ -357,13 +382,13 @@ class VEAClient : public VideoEncodeAccelerator::Client { double frames_per_second(); // VideoDecodeAccelerator::Client implementation. - void RequireBitstreamBuffers(unsigned int input_count, - const gfx::Size& input_coded_size, - size_t output_buffer_size) OVERRIDE; - void BitstreamBufferReady(int32 bitstream_buffer_id, - size_t payload_size, - bool key_frame) OVERRIDE; - void NotifyError(VideoEncodeAccelerator::Error error) OVERRIDE; + virtual void RequireBitstreamBuffers(unsigned int input_count, + const gfx::Size& input_coded_size, + size_t output_buffer_size) OVERRIDE; + virtual void BitstreamBufferReady(int32 bitstream_buffer_id, + size_t payload_size, + bool key_frame) OVERRIDE; + virtual void NotifyError(VideoEncodeAccelerator::Error error) OVERRIDE; private: bool has_encoder() { return encoder_.get(); } @@ -559,8 +584,13 @@ void VEAClient::CreateEncoder() { DCHECK(thread_checker_.CalledOnValidThread()); CHECK(!has_encoder()); +#if defined(OS_CHROMEOS) && defined(ARCH_CPU_ARMEL) scoped_ptr<V4L2Device> device = V4L2Device::Create(V4L2Device::kEncoder); encoder_.reset(new V4L2VideoEncodeAccelerator(device.Pass())); +#elif defined(OS_CHROMEOS) && defined(ARCH_CPU_X86_FAMILY) && defined(USE_X11) + encoder_.reset(new VaapiVideoEncodeAccelerator(gfx::GetXDisplay())); +#endif + SetState(CS_ENCODER_SET); DVLOG(1) << "Profile: " << test_stream_.requested_profile diff --git a/content/common/sandbox_linux/bpf_gpu_policy_linux.cc b/content/common/sandbox_linux/bpf_gpu_policy_linux.cc index d6b54f8..14eab49 100644 --- a/content/common/sandbox_linux/bpf_gpu_policy_linux.cc +++ b/content/common/sandbox_linux/bpf_gpu_policy_linux.cc @@ -72,9 +72,15 @@ inline bool IsArchitectureArm() { #endif } -bool IsAcceleratedVideoDecodeEnabled() { +bool IsAcceleratedVideoEnabled() { const CommandLine& command_line = *CommandLine::ForCurrentProcess(); - return !command_line.HasSwitch(switches::kDisableAcceleratedVideoDecode); + bool accelerated_encode_enabled = false; +#if defined(OS_CHROMEOS) + accelerated_encode_enabled = + command_line.HasSwitch(switches::kEnableVaapiAcceleratedVideoEncode); +#endif + return !command_line.HasSwitch(switches::kDisableAcceleratedVideoDecode) || + accelerated_encode_enabled; } intptr_t GpuSIGSYS_Handler(const struct arch_seccomp_data& args, @@ -214,9 +220,9 @@ bool GpuProcessPolicy::PreSandboxHook() { std::vector<std::string>()); if (IsArchitectureX86_64() || IsArchitectureI386()) { - // Accelerated video decode dlopen()'s some shared objects + // Accelerated video dlopen()'s some shared objects // inside the sandbox, so preload them now. - if (IsAcceleratedVideoDecodeEnabled()) { + if (IsAcceleratedVideoEnabled()) { const char* I965DrvVideoPath = NULL; if (IsArchitectureX86_64()) { diff --git a/content/content_common.gypi b/content/content_common.gypi index e3cda4e..bfaf016 100644 --- a/content/content_common.gypi +++ b/content/content_common.gypi @@ -723,6 +723,10 @@ ], }], ['target_arch != "arm" and chromeos == 1 and use_x11 == 1', { + 'dependencies': [ + '../media/media.gyp:media', + '../third_party/libyuv/libyuv.gyp:libyuv', + ], 'sources': [ 'common/gpu/media/h264_dpb.cc', 'common/gpu/media/h264_dpb.h', @@ -731,6 +735,8 @@ 'common/gpu/media/vaapi_h264_decoder.h', 'common/gpu/media/vaapi_video_decode_accelerator.cc', 'common/gpu/media/vaapi_video_decode_accelerator.h', + 'common/gpu/media/vaapi_video_encode_accelerator.cc', + 'common/gpu/media/vaapi_video_encode_accelerator.h', 'common/gpu/media/vaapi_wrapper.cc', 'common/gpu/media/vaapi_wrapper.h', ], @@ -746,6 +752,7 @@ }, 'include_dirs': [ '<(DEPTH)/third_party/libva', + '<(DEPTH)/third_party/libyuv', '<(output_root)', ], 'actions': [ diff --git a/content/content_tests.gypi b/content/content_tests.gypi index 66f6dda..01af316 100644 --- a/content/content_tests.gypi +++ b/content/content_tests.gypi @@ -1467,7 +1467,7 @@ }, ] }], - ['chromeos==1 and target_arch == "arm"', { + ['chromeos==1 and (target_arch == "arm" or use_x11 == 1)', { 'targets': [ { 'target_name': 'video_encode_accelerator_unittest', @@ -1487,6 +1487,17 @@ 'common/gpu/media/video_accelerator_unittest_helpers.h', 'common/gpu/media/video_encode_accelerator_unittest.cc', ], + 'include_dirs': [ + '<(DEPTH)/third_party/libva', + '<(DEPTH)/third_party/libyuv', + ], + 'conditions': [ + ['use_x11==1', { + 'dependencies': [ + '../ui/gfx/x/gfx_x11.gyp:gfx_x11', + ], + }], + ], }, ] }], diff --git a/content/public/common/content_switches.cc b/content/public/common/content_switches.cc index 4242799..50e77f5 100644 --- a/content/public/common/content_switches.cc +++ b/content/public/common/content_switches.cc @@ -923,6 +923,10 @@ const char kDisableWebAudio[] = "disable-webaudio"; #if defined(OS_CHROMEOS) // Disables panel fitting (used for mirror mode). const char kDisablePanelFitting[] = "disable-panel-fitting"; + +// Enables VA-API accelerated video encode. +const char kEnableVaapiAcceleratedVideoEncode[] = + "enable-vaapi-accelerated-video-encode"; #endif #if defined(OS_MACOSX) && !defined(OS_IOS) diff --git a/content/public/common/content_switches.h b/content/public/common/content_switches.h index b64d134..4639bff 100644 --- a/content/public/common/content_switches.h +++ b/content/public/common/content_switches.h @@ -266,6 +266,7 @@ CONTENT_EXPORT extern const char kDisableWebAudio[]; #if defined(OS_CHROMEOS) CONTENT_EXPORT extern const char kDisablePanelFitting[]; +CONTENT_EXPORT extern const char kEnableVaapiAcceleratedVideoEncode[]; #endif #if defined(OS_MACOSX) && !defined(OS_IOS) diff --git a/media/filters/h264_bitstream_buffer.cc b/media/filters/h264_bitstream_buffer.cc new file mode 100644 index 0000000..48463a5 --- /dev/null +++ b/media/filters/h264_bitstream_buffer.cc @@ -0,0 +1,152 @@ +// Copyright 2014 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 "media/filters/h264_bitstream_buffer.h" + +#include "base/sys_byteorder.h" + +namespace media { + +H264BitstreamBuffer::H264BitstreamBuffer() : data_(NULL) { + Reset(); +} + +H264BitstreamBuffer::~H264BitstreamBuffer() { + free(data_); + data_ = NULL; +} + +void H264BitstreamBuffer::Reset() { + free(data_); + data_ = NULL; + + capacity_ = 0; + pos_ = 0; + reg_ = 0; + + Grow(); + + bits_left_in_reg_ = kRegBitSize; +} + +void H264BitstreamBuffer::Grow() { + data_ = static_cast<uint8*>(realloc(data_, capacity_ + kGrowBytes)); + CHECK(data_) << "Failed growing the buffer"; + capacity_ += kGrowBytes; +} + +void H264BitstreamBuffer::FlushReg() { + // Flush all bytes that have at least one bit cached, but not more + // (on Flush(), reg_ may not be full). + size_t bits_in_reg = kRegBitSize - bits_left_in_reg_; + if (bits_in_reg == 0) + return; + + size_t bytes_in_reg = (bits_in_reg + 7) / 8; + reg_ <<= (kRegBitSize - bits_in_reg); + + // Convert to MSB and append as such to the stream. + reg_ = base::HostToNet64(reg_); + + // Make sure we have enough space. Grow() will CHECK() on allocation failure. + if (pos_ + bytes_in_reg < capacity_) + Grow(); + + memcpy(data_ + pos_, ®_, bytes_in_reg); + pos_ += bytes_in_reg; + + reg_ = 0; + bits_left_in_reg_ = kRegBitSize; +} + +void H264BitstreamBuffer::AppendU64(size_t num_bits, uint64 val) { + CHECK_LE(num_bits, kRegBitSize); + + while (num_bits > 0) { + if (bits_left_in_reg_ == 0) + FlushReg(); + + uint64 bits_to_write = + num_bits > bits_left_in_reg_ ? bits_left_in_reg_ : num_bits; + uint64 val_to_write = (val >> (num_bits - bits_to_write)); + if (bits_to_write < 64) + val_to_write &= ((1ull << bits_to_write) - 1); + reg_ <<= bits_to_write; + reg_ |= val_to_write; + num_bits -= bits_to_write; + bits_left_in_reg_ -= bits_to_write; + } +} + +void H264BitstreamBuffer::AppendBool(bool val) { + if (bits_left_in_reg_ == 0) + FlushReg(); + + reg_ <<= 1; + reg_ |= (static_cast<uint64>(val) & 1); + --bits_left_in_reg_; +} + +void H264BitstreamBuffer::AppendSE(int val) { + if (val > 0) + AppendUE(val * 2 - 1); + else + AppendUE(-val * 2); +} + +void H264BitstreamBuffer::AppendUE(unsigned int val) { + size_t num_zeros = 0; + unsigned int v = val + 1; + + while (v > 1) { + v >>= 1; + ++num_zeros; + } + + AppendBits(num_zeros, 0); + AppendBits(num_zeros + 1, val + 1); +} + +#define DCHECK_FINISHED() \ + DCHECK_EQ(bits_left_in_reg_, kRegBitSize) << "Pending bits not yet written " \ + "to the buffer, call " \ + "FinishNALU() first." + +void H264BitstreamBuffer::BeginNALU(H264NALU::Type nalu_type, int nal_ref_idc) { + DCHECK_FINISHED(); + + DCHECK_LE(nalu_type, H264NALU::kEOStream); + DCHECK_GE(nal_ref_idc, 0); + DCHECK_LE(nal_ref_idc, 3); + + AppendBits(32, 0x00000001); + AppendBits(1, 0); // forbidden_zero_bit + AppendBits(2, nal_ref_idc); + AppendBits(5, nalu_type); +} + +void H264BitstreamBuffer::FinishNALU() { + // RBSP stop one bit. + AppendBits(1, 1); + + // Byte-alignment zero bits. + AppendBits(bits_left_in_reg_ % 8, 0); + + if (bits_left_in_reg_ != kRegBitSize) + FlushReg(); +} + +size_t H264BitstreamBuffer::BytesInBuffer() { + DCHECK_FINISHED(); + return pos_; +} + +uint8* H264BitstreamBuffer::data() { + DCHECK(data_); + DCHECK_FINISHED(); + + return data_; +} + +} // namespace media diff --git a/media/filters/h264_bitstream_buffer.h b/media/filters/h264_bitstream_buffer.h new file mode 100644 index 0000000..4b0511d --- /dev/null +++ b/media/filters/h264_bitstream_buffer.h @@ -0,0 +1,120 @@ +// Copyright 2014 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. +// +// This file contains an implementation of a H264BitstreamBuffer class for +// constructing raw bitstream buffers containing NAL units in +// H.264 Annex-B stream format. +// See H.264 spec Annex B and chapter 7for more details. + +#ifndef MEDIA_FILTERS_H264_BITSTREAM_BUFFER_H_ +#define MEDIA_FILTERS_H264_BITSTREAM_BUFFER_H_ + +#include "base/gtest_prod_util.h" +#include "base/numerics/safe_conversions.h" +#include "media/base/media_export.h" +#include "media/base/video_frame.h" +#include "media/filters/h264_parser.h" + +namespace media { + +// Holds one or more NALUs as a raw bitstream buffer in H.264 Annex-B format. +// Note that this class currently does NOT insert emulation prevention +// three-byte sequences (spec 7.3.1). +class MEDIA_EXPORT H264BitstreamBuffer { + public: + H264BitstreamBuffer(); + ~H264BitstreamBuffer(); + + // Discard all data and reset the buffer for reuse. + void Reset(); + + // Append |num_bits| bits to the stream from |val|. + // |val| is interpreted in the host endianness. + template <typename T> + void AppendBits(size_t num_bits, T val) { + AppendU64(num_bits, static_cast<uint64>(val)); + } + + void AppendBits(size_t num_bits, bool val) { + DCHECK_EQ(num_bits, 1ul); + AppendBool(val); + } + + // Append a one-bit bool/flag value |val| to the stream. + void AppendBool(bool val); + + // Append a signed value in |val| in Exp-Golomb code. + void AppendSE(int val); + + // Append an unsigned value in |val| in Exp-Golomb code. + void AppendUE(unsigned int val); + + // Start a new NALU of type |nalu_type| and with given |nal_ref_idc| + // (see spec). Note, that until FinishNALU() is called, some of the bits + // may not be flushed into the buffer and the data will not be correctly + // aligned with trailing bits. + void BeginNALU(H264NALU::Type nalu_type, int nal_ref_idc); + + // Finish current NALU. This will flush any cached bits and correctly align + // the buffer with RBSP trailing bits. This MUST be called for the stream + // returned by data() to be correct. + void FinishNALU(); + + // Return number of full bytes in the stream. Note that FinishNALU() has to + // be called to flush cached bits, or the return value will not include them. + size_t BytesInBuffer(); + + // Return a pointer to the stream. FinishNALU() must be called before + // accessing the stream, otherwise some bits may still be cached and not + // in the buffer. + uint8* data(); + + private: + FRIEND_TEST_ALL_PREFIXES(H264BitstreamBufferAppendBitsTest, + AppendAndVerifyBits); + + // Allocate additional memory (kGrowBytes bytes) for the buffer. + void Grow(); + + // Append |num_bits| bits from U64 value |val| (in host endianness). + void AppendU64(size_t num_bits, uint64 val); + + // Flush any cached bits in the reg with byte granularity, i.e. enough + // bytes to flush all pending bits, but not more. + void FlushReg(); + + typedef uint64 RegType; + enum { + // Sizes of reg_. + kRegByteSize = sizeof(RegType), + kRegBitSize = kRegByteSize * 8, + // Amount of bytes to grow the buffer by when we run out of + // previously-allocated memory for it. + kGrowBytes = 4096, + }; + + COMPILE_ASSERT(kGrowBytes >= kRegByteSize, + kGrowBytes_must_be_larger_than_kRegByteSize); + + // Unused bits left in reg_. + size_t bits_left_in_reg_; + + // Cache for appended bits. Bits are flushed to data_ with kRegByteSize + // granularity, i.e. when reg_ becomes full, or when an explicit FlushReg() + // is called. + RegType reg_; + + // Current capacity of data_, in bytes. + size_t capacity_; + + // Current byte offset in data_ (points to the start of unwritten bits). + size_t pos_; + + // Buffer for stream data. + uint8* data_; +}; + +} // namespace media + +#endif // MEDIA_FILTERS_H264_BITSTREAM_BUFFER_H_ diff --git a/media/filters/h264_bitstream_buffer_unittest.cc b/media/filters/h264_bitstream_buffer_unittest.cc new file mode 100644 index 0000000..c959427 --- /dev/null +++ b/media/filters/h264_bitstream_buffer_unittest.cc @@ -0,0 +1,56 @@ +// Copyright 2014 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 "media/filters/h264_bitstream_buffer.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +namespace { +const uint64 kTestPattern = 0xfedcba0987654321; +} + +class H264BitstreamBufferAppendBitsTest + : public ::testing::TestWithParam<size_t> {}; + +// TODO(posciak): More tests! + +TEST_P(H264BitstreamBufferAppendBitsTest, AppendAndVerifyBits) { + H264BitstreamBuffer b; + uint64 num_bits = GetParam(); + // TODO(posciak): Tests for >64 bits. + ASSERT_LE(num_bits, 64u); + uint64 num_bytes = (num_bits + 7) / 8; + + b.AppendBits(num_bits, kTestPattern); + b.FlushReg(); + + EXPECT_EQ(b.BytesInBuffer(), num_bytes); + + uint8* ptr = b.data(); + uint64 got = 0; + uint64 expected = kTestPattern; + + if (num_bits < 64) + expected &= ((1ull << num_bits) - 1); + + while (num_bits > 8) { + got |= (*ptr & 0xff); + num_bits -= 8; + got <<= (num_bits > 8 ? 8 : num_bits); + ptr++; + } + if (num_bits > 0) { + uint64 temp = (*ptr & 0xff); + temp >>= (8 - num_bits); + got |= temp; + } + EXPECT_EQ(got, expected) << std::hex << "0x" << got << " vs 0x" << expected; +} + +INSTANTIATE_TEST_CASE_P(AppendNumBits, + H264BitstreamBufferAppendBitsTest, + ::testing::Range(static_cast<uint64>(1), + static_cast<uint64>(65))); +} // namespace media diff --git a/media/filters/h264_parser.cc b/media/filters/h264_parser.cc index 4cdc695..ee21ab8 100644 --- a/media/filters/h264_parser.cc +++ b/media/filters/h264_parser.cc @@ -106,10 +106,6 @@ H264SEIMessage::H264SEIMessage() { } \ } while (0) -enum AspectRatioIdc { - kExtendedSar = 255, -}; - // ISO 14496 part 10 // VUI parameters: Table E-1 "Meaning of sample aspect ratio indicator" static const int kTableSarWidth[] = { @@ -608,7 +604,7 @@ H264Parser::Result H264Parser::ParseVUIParameters(H264SPS* sps) { if (aspect_ratio_info_present_flag) { int aspect_ratio_idc; READ_BITS_OR_RETURN(8, &aspect_ratio_idc); - if (aspect_ratio_idc == kExtendedSar) { + if (aspect_ratio_idc == H264SPS::kExtendedSar) { READ_BITS_OR_RETURN(16, &sps->sar_width); READ_BITS_OR_RETURN(16, &sps->sar_height); } else { diff --git a/media/filters/h264_parser.h b/media/filters/h264_parser.h index 3a60dce..45020af 100644 --- a/media/filters/h264_parser.h +++ b/media/filters/h264_parser.h @@ -63,6 +63,26 @@ enum { struct MEDIA_EXPORT H264SPS { H264SPS(); + enum H264ProfileIDC { + kProfileIDCBaseline = 66, + kProfileIDCConstrainedBaseline = kProfileIDCBaseline, + kProfileIDCMain = 77, + kProfileIDCHigh = 100, + }; + + enum AspectRatioIdc { + kExtendedSar = 255, + }; + + enum { + // Constants for HRD parameters (spec ch. E.2.2). + kBitRateScaleConstantTerm = 6, // Equation E-37. + kCPBSizeScaleConstantTerm = 4, // Equation E-38. + kDefaultInitialCPBRemovalDelayLength = 24, + kDefaultDPBOutputDelayLength = 24, + kDefaultTimeOffsetLength = 24, + }; + int profile_idc; bool constraint_set0_flag; bool constraint_set1_flag; @@ -111,6 +131,25 @@ struct MEDIA_EXPORT H264SPS { bool bitstream_restriction_flag; int max_num_reorder_frames; int max_dec_frame_buffering; + bool timing_info_present_flag; + int num_units_in_tick; + int time_scale; + bool fixed_frame_rate_flag; + + // TODO(posciak): actually parse these instead of ParseAndIgnoreHRDParameters. + bool nal_hrd_parameters_present_flag; + int cpb_cnt_minus1; + int bit_rate_scale; + int cpb_size_scale; + int bit_rate_value_minus1[32]; + int cpb_size_value_minus1[32]; + bool cbr_flag[32]; + int initial_cpb_removal_delay_length_minus_1; + int cpb_removal_delay_length_minus1; + int dpb_output_delay_length_minus1; + int time_offset_length; + + bool low_delay_hrd_flag; int chroma_array_type; }; diff --git a/media/media.gyp b/media/media.gyp index 7a38a4f..1b3987a 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -659,6 +659,13 @@ 'formats/webm/chromeos/webm_encoder.h', ], }], + # For VaapiVideoEncodeAccelerator. + ['target_arch != "arm" and chromeos == 1 and use_x11 == 1', { + 'sources': [ + 'filters/h264_bitstream_buffer.cc', + 'filters/h264_bitstream_buffer.h', + ], + }], ['OS!="ios"', { 'dependencies': [ '../third_party/libyuv/libyuv.gyp:libyuv', @@ -1223,6 +1230,11 @@ }], ], }], + ['target_arch != "arm" and chromeos == 1 and use_x11 == 1', { + 'sources': [ + 'filters/h264_bitstream_buffer_unittest.cc', + ], + }], ['use_alsa==0', { 'sources!': [ 'audio/alsa/alsa_output_unittest.cc', diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml index e35ae92..677b07f 100644 --- a/tools/metrics/histograms/histograms.xml +++ b/tools/metrics/histograms/histograms.xml @@ -10312,6 +10312,13 @@ Therefore, the affected-histogram name has to have at least one dot in it. </summary> </histogram> +<histogram name="Media.VAVEA.EncoderFailure" enum="VAVEAEncoderFailure"> + <owner>posciak@chromium.org</owner> + <summary> + Error codes reported by video encode using VA-API hardware video encoder. + </summary> +</histogram> + <histogram name="Media.VideoCapture.AspectRatio"> <owner>Please list the metric's owners. Add more owner tags as needed.</owner> <summary> @@ -47026,6 +47033,10 @@ Therefore, the affected-histogram name has to have at least one dot in it. <int value="4" label="VAAPI_ERROR"/> </enum> +<enum name="VAVEAEncoderFailure" type="int"> + <int value="0" label="VAAPI_ERROR"/> +</enum> + <enum name="VideoCodec" type="int"> <int value="0" label="kUnknownVideoCodec"/> <int value="1" label="kCodecH264"/> |