• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
ProfileToVAProfile(media::VideoCodecProfile profile,const std::vector<VAProfile> & supported_profiles)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 
VASurface(VASurfaceID va_surface_id,const ReleaseCB & release_cb)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 
~VASurface()97 VASurface::~VASurface() {
98   release_cb_.Run(va_surface_id_);
99 }
100 
VaapiWrapper()101 VaapiWrapper::VaapiWrapper()
102     : va_display_(NULL),
103       va_config_id_(VA_INVALID_ID),
104       va_context_id_(VA_INVALID_ID) {
105 }
106 
~VaapiWrapper()107 VaapiWrapper::~VaapiWrapper() {
108   DestroyPendingBuffers();
109   DestroySurfaces();
110   Deinitialize();
111 }
112 
Create(media::VideoCodecProfile profile,Display * x_display,const base::Closure & report_error_to_uma_cb)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 
TryToSetVADisplayAttributeToLocalGPU()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 
Initialize(media::VideoCodecProfile profile,Display * x_display,const base::Closure & report_error_to_uma_cb)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 
Deinitialize()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 
VAAPIVersionLessThan(int major,int minor)224 bool VaapiWrapper::VAAPIVersionLessThan(int major, int minor) {
225   return (major_version_ < major) ||
226       (major_version_ == major && minor_version_ < minor);
227 }
228 
CreateSurfaces(gfx::Size size,size_t num_surfaces,std::vector<VASurfaceID> * va_surfaces)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 
DestroySurfaces()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 
SubmitBuffer(VABufferType va_buffer_type,size_t size,void * buffer)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 
DestroyPendingBuffers()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 
SubmitDecode(VASurfaceID va_surface_id)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 
DecodeAndDestroyPendingBuffers(VASurfaceID va_surface_id)360 bool VaapiWrapper::DecodeAndDestroyPendingBuffers(VASurfaceID va_surface_id) {
361   bool result = SubmitDecode(va_surface_id);
362   DestroyPendingBuffers();
363   return result;
364 }
365 
PutSurfaceIntoPixmap(VASurfaceID va_surface_id,Pixmap x_pixmap,gfx::Size dest_size)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 
GetVaImageForTesting(VASurfaceID va_surface_id,VAImage * image,void ** mem)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 
ReturnVaImageForTesting(VAImage * image)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
PostSandboxInitialization()418 bool VaapiWrapper::PostSandboxInitialization() {
419   StubPathMap paths;
420   paths[kModuleVa].push_back(kVaLib);
421 
422   return InitializeStubs(paths);
423 }
424 
425 }  // namespace content
426