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