Home | History | Annotate | Download | only in media
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "content/common/gpu/media/vaapi_wrapper.h"
      6 
      7 #include <dlfcn.h>
      8 
      9 #include "base/bind.h"
     10 #include "base/logging.h"
     11 #include "base/numerics/safe_conversions.h"
     12 // Auto-generated for dlopen libva libraries
     13 #include "content/common/gpu/media/va_stubs.h"
     14 
     15 using content_common_gpu_media::kModuleVa;
     16 using content_common_gpu_media::InitializeStubs;
     17 using content_common_gpu_media::StubPathMap;
     18 
     19 // libva-x11 depends on libva, so dlopen libva-x11 is enough
     20 static const base::FilePath::CharType kVaLib[] =
     21     FILE_PATH_LITERAL("libva-x11.so.1");
     22 
     23 #define LOG_VA_ERROR_AND_REPORT(va_error, err_msg)         \
     24   do {                                                     \
     25     DVLOG(1) << err_msg                                    \
     26              << " VA error: " << vaErrorStr(va_error);     \
     27     report_error_to_uma_cb_.Run();                         \
     28   } while (0)
     29 
     30 #define VA_LOG_ON_ERROR(va_error, err_msg)                 \
     31   do {                                                     \
     32     if ((va_error) != VA_STATUS_SUCCESS)                   \
     33       LOG_VA_ERROR_AND_REPORT(va_error, err_msg);          \
     34   } while (0)
     35 
     36 #define VA_SUCCESS_OR_RETURN(va_error, err_msg, ret)       \
     37   do {                                                     \
     38     if ((va_error) != VA_STATUS_SUCCESS) {                 \
     39       LOG_VA_ERROR_AND_REPORT(va_error, err_msg);          \
     40       return (ret);                                        \
     41     }                                                      \
     42   } while (0)
     43 
     44 namespace content {
     45 
     46 // Maps Profile enum values to VaProfile values.
     47 static VAProfile ProfileToVAProfile(
     48     media::VideoCodecProfile profile,
     49     const std::vector<VAProfile>& supported_profiles) {
     50 
     51   VAProfile va_profile = VAProfileNone;
     52 
     53   switch (profile) {
     54     case media::H264PROFILE_BASELINE:
     55       va_profile = VAProfileH264Baseline;
     56       break;
     57     case media::H264PROFILE_MAIN:
     58       va_profile = VAProfileH264Main;
     59       break;
     60     // TODO(posciak): See if we can/want support other variants
     61     // of media::H264PROFILE_HIGH*.
     62     case media::H264PROFILE_HIGH:
     63       va_profile = VAProfileH264High;
     64       break;
     65     default:
     66       break;
     67   }
     68 
     69   bool supported = std::find(supported_profiles.begin(),
     70                              supported_profiles.end(),
     71                              va_profile) != supported_profiles.end();
     72 
     73   if (!supported && va_profile == VAProfileH264Baseline) {
     74     // crbug.com/345569: media::ProfileIDToVideoCodecProfile() currently strips
     75     // the information whether the profile is constrained or not, so we have no
     76     // way to know here. Try for baseline first, but if it is not supported,
     77     // try constrained baseline and hope this is what it actually is
     78     // (which in practice is true for a great majority of cases).
     79     if (std::find(supported_profiles.begin(),
     80                   supported_profiles.end(),
     81                   VAProfileH264ConstrainedBaseline) !=
     82         supported_profiles.end()) {
     83       va_profile = VAProfileH264ConstrainedBaseline;
     84       DVLOG(1) << "Falling back to constrained baseline profile.";
     85     }
     86   }
     87 
     88   return va_profile;
     89 }
     90 
     91 VASurface::VASurface(VASurfaceID va_surface_id, const ReleaseCB& release_cb)
     92     : va_surface_id_(va_surface_id),
     93       release_cb_(release_cb) {
     94   DCHECK(!release_cb_.is_null());
     95 }
     96 
     97 VASurface::~VASurface() {
     98   release_cb_.Run(va_surface_id_);
     99 }
    100 
    101 VaapiWrapper::VaapiWrapper()
    102     : va_display_(NULL),
    103       va_config_id_(VA_INVALID_ID),
    104       va_context_id_(VA_INVALID_ID) {
    105 }
    106 
    107 VaapiWrapper::~VaapiWrapper() {
    108   DestroyPendingBuffers();
    109   DestroySurfaces();
    110   Deinitialize();
    111 }
    112 
    113 scoped_ptr<VaapiWrapper> VaapiWrapper::Create(
    114     media::VideoCodecProfile profile,
    115     Display* x_display,
    116     const base::Closure& report_error_to_uma_cb) {
    117   scoped_ptr<VaapiWrapper> vaapi_wrapper(new VaapiWrapper());
    118 
    119   if (!vaapi_wrapper->Initialize(profile, x_display, report_error_to_uma_cb))
    120     vaapi_wrapper.reset();
    121 
    122   return vaapi_wrapper.Pass();
    123 }
    124 
    125 void VaapiWrapper::TryToSetVADisplayAttributeToLocalGPU() {
    126   VADisplayAttribute item = {VADisplayAttribRenderMode,
    127                              1,  // At least support '_LOCAL_OVERLAY'.
    128                              -1,  // The maximum possible support 'ALL'.
    129                              VA_RENDER_MODE_LOCAL_GPU,
    130                              VA_DISPLAY_ATTRIB_SETTABLE};
    131 
    132   VAStatus va_res = vaSetDisplayAttributes(va_display_, &item, 1);
    133   if (va_res != VA_STATUS_SUCCESS)
    134     DVLOG(2) << "vaSetDisplayAttributes unsupported, ignoring by default.";
    135 }
    136 
    137 bool VaapiWrapper::Initialize(media::VideoCodecProfile profile,
    138                               Display* x_display,
    139                               const base::Closure& report_error_to_uma_cb) {
    140   static bool vaapi_functions_initialized = PostSandboxInitialization();
    141   if (!vaapi_functions_initialized) {
    142     DVLOG(1) << "Failed to initialize VAAPI libs";
    143     return false;
    144   }
    145 
    146   report_error_to_uma_cb_ = report_error_to_uma_cb;
    147 
    148   base::AutoLock auto_lock(va_lock_);
    149 
    150   va_display_ = vaGetDisplay(x_display);
    151   if (!vaDisplayIsValid(va_display_)) {
    152     DVLOG(1) << "Could not get a valid VA display";
    153     return false;
    154   }
    155 
    156   VAStatus va_res = vaInitialize(va_display_, &major_version_, &minor_version_);
    157   VA_SUCCESS_OR_RETURN(va_res, "vaInitialize failed", false);
    158   DVLOG(1) << "VAAPI version: " << major_version_ << "." << minor_version_;
    159 
    160   if (VAAPIVersionLessThan(0, 34)) {
    161     DVLOG(1) << "VAAPI version < 0.34 is not supported.";
    162     return false;
    163   }
    164 
    165   // Query the driver for supported profiles.
    166   int max_profiles = vaMaxNumProfiles(va_display_);
    167   std::vector<VAProfile> supported_profiles(
    168       base::checked_cast<size_t>(max_profiles));
    169 
    170   int num_supported_profiles;
    171   va_res = vaQueryConfigProfiles(
    172       va_display_, &supported_profiles[0], &num_supported_profiles);
    173   VA_SUCCESS_OR_RETURN(va_res, "vaQueryConfigProfiles failed", false);
    174   if (num_supported_profiles < 0 || num_supported_profiles > max_profiles) {
    175     DVLOG(1) << "vaQueryConfigProfiles returned: " << num_supported_profiles;
    176     return false;
    177   }
    178 
    179   supported_profiles.resize(base::checked_cast<size_t>(num_supported_profiles));
    180 
    181   VAProfile va_profile = ProfileToVAProfile(profile, supported_profiles);
    182   if (va_profile == VAProfileNone) {
    183     DVLOG(1) << "Unsupported profile";
    184     return false;
    185   }
    186 
    187   VAConfigAttrib attrib = {VAConfigAttribRTFormat, 0};
    188   const VAEntrypoint kEntrypoint = VAEntrypointVLD;
    189   va_res = vaGetConfigAttributes(va_display_, va_profile, kEntrypoint,
    190                                  &attrib, 1);
    191   VA_SUCCESS_OR_RETURN(va_res, "vaGetConfigAttributes failed", false);
    192 
    193   if (!(attrib.value & VA_RT_FORMAT_YUV420)) {
    194     DVLOG(1) << "YUV420 not supported by this VAAPI implementation";
    195     return false;
    196   }
    197 
    198   TryToSetVADisplayAttributeToLocalGPU();
    199 
    200   va_res = vaCreateConfig(va_display_, va_profile, kEntrypoint,
    201                           &attrib, 1, &va_config_id_);
    202   VA_SUCCESS_OR_RETURN(va_res, "vaCreateConfig failed", false);
    203 
    204   return true;
    205 }
    206 
    207 void VaapiWrapper::Deinitialize() {
    208   base::AutoLock auto_lock(va_lock_);
    209 
    210   if (va_config_id_ != VA_INVALID_ID) {
    211     VAStatus va_res = vaDestroyConfig(va_display_, va_config_id_);
    212     VA_LOG_ON_ERROR(va_res, "vaDestroyConfig failed");
    213   }
    214 
    215   if (va_display_) {
    216     VAStatus va_res = vaTerminate(va_display_);
    217     VA_LOG_ON_ERROR(va_res, "vaTerminate failed");
    218   }
    219 
    220   va_config_id_ = VA_INVALID_ID;
    221   va_display_ = NULL;
    222 }
    223 
    224 bool VaapiWrapper::VAAPIVersionLessThan(int major, int minor) {
    225   return (major_version_ < major) ||
    226       (major_version_ == major && minor_version_ < minor);
    227 }
    228 
    229 bool VaapiWrapper::CreateSurfaces(gfx::Size size,
    230                                   size_t num_surfaces,
    231                                   std::vector<VASurfaceID>* va_surfaces) {
    232   base::AutoLock auto_lock(va_lock_);
    233   DVLOG(2) << "Creating " << num_surfaces << " surfaces";
    234 
    235   DCHECK(va_surfaces->empty());
    236   DCHECK(va_surface_ids_.empty());
    237   va_surface_ids_.resize(num_surfaces);
    238 
    239   // Allocate surfaces in driver.
    240   VAStatus va_res = vaCreateSurfaces(va_display_,
    241                                      VA_RT_FORMAT_YUV420,
    242                                      size.width(), size.height(),
    243                                      &va_surface_ids_[0],
    244                                      va_surface_ids_.size(),
    245                                      NULL, 0);
    246 
    247   VA_LOG_ON_ERROR(va_res, "vaCreateSurfaces failed");
    248   if (va_res != VA_STATUS_SUCCESS) {
    249     va_surface_ids_.clear();
    250     return false;
    251   }
    252 
    253   // And create a context associated with them.
    254   va_res = vaCreateContext(va_display_, va_config_id_,
    255                            size.width(), size.height(), VA_PROGRESSIVE,
    256                            &va_surface_ids_[0], va_surface_ids_.size(),
    257                            &va_context_id_);
    258 
    259   VA_LOG_ON_ERROR(va_res, "vaCreateContext failed");
    260   if (va_res != VA_STATUS_SUCCESS) {
    261     DestroySurfaces();
    262     return false;
    263   }
    264 
    265   *va_surfaces = va_surface_ids_;
    266   return true;
    267 }
    268 
    269 void VaapiWrapper::DestroySurfaces() {
    270   base::AutoLock auto_lock(va_lock_);
    271   DVLOG(2) << "Destroying " << va_surface_ids_.size()  << " surfaces";
    272 
    273   if (va_context_id_ != VA_INVALID_ID) {
    274     VAStatus va_res = vaDestroyContext(va_display_, va_context_id_);
    275     VA_LOG_ON_ERROR(va_res, "vaDestroyContext failed");
    276   }
    277 
    278   if (!va_surface_ids_.empty()) {
    279     VAStatus va_res = vaDestroySurfaces(va_display_, &va_surface_ids_[0],
    280                                         va_surface_ids_.size());
    281     VA_LOG_ON_ERROR(va_res, "vaDestroySurfaces failed");
    282   }
    283 
    284   va_surface_ids_.clear();
    285   va_context_id_ = VA_INVALID_ID;
    286 }
    287 
    288 bool VaapiWrapper::SubmitBuffer(VABufferType va_buffer_type,
    289                                 size_t size,
    290                                 void* buffer) {
    291   base::AutoLock auto_lock(va_lock_);
    292 
    293   VABufferID buffer_id;
    294   VAStatus va_res = vaCreateBuffer(va_display_, va_context_id_,
    295                                    va_buffer_type, size,
    296                                    1, buffer, &buffer_id);
    297   VA_SUCCESS_OR_RETURN(va_res, "Failed to create a VA buffer", false);
    298 
    299   switch (va_buffer_type) {
    300     case VASliceParameterBufferType:
    301     case VASliceDataBufferType:
    302       pending_slice_bufs_.push_back(buffer_id);
    303       break;
    304 
    305     default:
    306       pending_va_bufs_.push_back(buffer_id);
    307       break;
    308   }
    309 
    310   return true;
    311 }
    312 
    313 void VaapiWrapper::DestroyPendingBuffers() {
    314   base::AutoLock auto_lock(va_lock_);
    315 
    316   for (size_t i = 0; i < pending_va_bufs_.size(); ++i) {
    317     VAStatus va_res = vaDestroyBuffer(va_display_, pending_va_bufs_[i]);
    318     VA_LOG_ON_ERROR(va_res, "vaDestroyBuffer failed");
    319   }
    320 
    321   for (size_t i = 0; i < pending_slice_bufs_.size(); ++i) {
    322     VAStatus va_res = vaDestroyBuffer(va_display_, pending_slice_bufs_[i]);
    323     VA_LOG_ON_ERROR(va_res, "vaDestroyBuffer failed");
    324   }
    325 
    326   pending_va_bufs_.clear();
    327   pending_slice_bufs_.clear();
    328 }
    329 
    330 bool VaapiWrapper::SubmitDecode(VASurfaceID va_surface_id) {
    331   base::AutoLock auto_lock(va_lock_);
    332 
    333   DVLOG(4) << "Pending VA bufs to commit: " << pending_va_bufs_.size();
    334   DVLOG(4) << "Pending slice bufs to commit: " << pending_slice_bufs_.size();
    335   DVLOG(4) << "Decoding into VA surface " << va_surface_id;
    336 
    337   // Get ready to decode into surface.
    338   VAStatus va_res = vaBeginPicture(va_display_, va_context_id_,
    339                                    va_surface_id);
    340   VA_SUCCESS_OR_RETURN(va_res, "vaBeginPicture failed", false);
    341 
    342   // Commit parameter and slice buffers.
    343   va_res = vaRenderPicture(va_display_, va_context_id_,
    344                            &pending_va_bufs_[0], pending_va_bufs_.size());
    345   VA_SUCCESS_OR_RETURN(va_res, "vaRenderPicture for va_bufs failed", false);
    346 
    347   va_res = vaRenderPicture(va_display_, va_context_id_,
    348                            &pending_slice_bufs_[0],
    349                            pending_slice_bufs_.size());
    350   VA_SUCCESS_OR_RETURN(va_res, "vaRenderPicture for slices failed", false);
    351 
    352   // Instruct HW decoder to start processing committed buffers (decode this
    353   // picture). This does not block until the end of decode.
    354   va_res = vaEndPicture(va_display_, va_context_id_);
    355   VA_SUCCESS_OR_RETURN(va_res, "vaEndPicture failed", false);
    356 
    357   return true;
    358 }
    359 
    360 bool VaapiWrapper::DecodeAndDestroyPendingBuffers(VASurfaceID va_surface_id) {
    361   bool result = SubmitDecode(va_surface_id);
    362   DestroyPendingBuffers();
    363   return result;
    364 }
    365 
    366 bool VaapiWrapper::PutSurfaceIntoPixmap(VASurfaceID va_surface_id,
    367                                         Pixmap x_pixmap,
    368                                         gfx::Size dest_size) {
    369   base::AutoLock auto_lock(va_lock_);
    370 
    371   VAStatus va_res = vaSyncSurface(va_display_, va_surface_id);
    372   VA_SUCCESS_OR_RETURN(va_res, "Failed syncing surface", false);
    373 
    374   // Put the data into an X Pixmap.
    375   va_res = vaPutSurface(va_display_,
    376                         va_surface_id,
    377                         x_pixmap,
    378                         0, 0, dest_size.width(), dest_size.height(),
    379                         0, 0, dest_size.width(), dest_size.height(),
    380                         NULL, 0, 0);
    381   VA_SUCCESS_OR_RETURN(va_res, "Failed putting decode surface to pixmap",
    382                        false);
    383   return true;
    384 }
    385 
    386 bool VaapiWrapper::GetVaImageForTesting(VASurfaceID va_surface_id,
    387                                         VAImage* image,
    388                                         void** mem) {
    389   base::AutoLock auto_lock(va_lock_);
    390 
    391   VAStatus va_res = vaSyncSurface(va_display_, va_surface_id);
    392   VA_SUCCESS_OR_RETURN(va_res, "Failed syncing surface", false);
    393 
    394   // Derive a VAImage from the VASurface
    395   va_res = vaDeriveImage(va_display_, va_surface_id, image);
    396   VA_LOG_ON_ERROR(va_res, "vaDeriveImage failed");
    397   if (va_res != VA_STATUS_SUCCESS)
    398     return false;
    399 
    400   // Map the VAImage into memory
    401   va_res = vaMapBuffer(va_display_, image->buf, mem);
    402   VA_LOG_ON_ERROR(va_res, "vaMapBuffer failed");
    403   if (va_res == VA_STATUS_SUCCESS)
    404     return true;
    405 
    406   vaDestroyImage(va_display_, image->image_id);
    407   return false;
    408 }
    409 
    410 void VaapiWrapper::ReturnVaImageForTesting(VAImage* image) {
    411   base::AutoLock auto_lock(va_lock_);
    412 
    413   vaUnmapBuffer(va_display_, image->buf);
    414   vaDestroyImage(va_display_, image->image_id);
    415 }
    416 
    417 // static
    418 bool VaapiWrapper::PostSandboxInitialization() {
    419   StubPathMap paths;
    420   paths[kModuleVa].push_back(kVaLib);
    421 
    422   return InitializeStubs(paths);
    423 }
    424 
    425 }  // namespace content
    426