1/*
2 * This file is part of FFmpeg.
3 *
4 * FFmpeg is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * FFmpeg is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with FFmpeg; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 */
18
19#include "config.h"
20
21#include <stdint.h>
22#include <string.h>
23
24#include <vdpau/vdpau.h>
25
26#include "buffer.h"
27#include "common.h"
28#include "hwcontext.h"
29#include "hwcontext_internal.h"
30#include "hwcontext_vdpau.h"
31#include "mem.h"
32#include "pixfmt.h"
33#include "pixdesc.h"
34
35typedef struct VDPAUPixFmtMap {
36    VdpYCbCrFormat vdpau_fmt;
37    enum AVPixelFormat pix_fmt;
38} VDPAUPixFmtMap;
39
40static const VDPAUPixFmtMap pix_fmts_420[] = {
41    { VDP_YCBCR_FORMAT_NV12, AV_PIX_FMT_NV12    },
42    { VDP_YCBCR_FORMAT_YV12, AV_PIX_FMT_YUV420P },
43#ifdef VDP_YCBCR_FORMAT_P016
44    { VDP_YCBCR_FORMAT_P016, AV_PIX_FMT_P016    },
45    { VDP_YCBCR_FORMAT_P010, AV_PIX_FMT_P010    },
46#endif
47    { 0,                     AV_PIX_FMT_NONE,   },
48};
49
50static const VDPAUPixFmtMap pix_fmts_422[] = {
51    { VDP_YCBCR_FORMAT_NV12, AV_PIX_FMT_NV16    },
52    { VDP_YCBCR_FORMAT_YV12, AV_PIX_FMT_YUV422P },
53    { VDP_YCBCR_FORMAT_UYVY, AV_PIX_FMT_UYVY422 },
54    { VDP_YCBCR_FORMAT_YUYV, AV_PIX_FMT_YUYV422 },
55    { 0,                     AV_PIX_FMT_NONE,   },
56};
57
58static const VDPAUPixFmtMap pix_fmts_444[] = {
59#ifdef VDP_YCBCR_FORMAT_Y_U_V_444
60    { VDP_YCBCR_FORMAT_Y_U_V_444, AV_PIX_FMT_YUV444P },
61#endif
62#ifdef VDP_YCBCR_FORMAT_P016
63    {VDP_YCBCR_FORMAT_Y_U_V_444_16, AV_PIX_FMT_YUV444P16},
64#endif
65    { 0,                          AV_PIX_FMT_NONE,   },
66};
67
68static const struct {
69    VdpChromaType chroma_type;
70    enum AVPixelFormat frames_sw_format;
71    const VDPAUPixFmtMap *map;
72} vdpau_pix_fmts[] = {
73    { VDP_CHROMA_TYPE_420, AV_PIX_FMT_YUV420P, pix_fmts_420 },
74    { VDP_CHROMA_TYPE_422, AV_PIX_FMT_YUV422P, pix_fmts_422 },
75    { VDP_CHROMA_TYPE_444, AV_PIX_FMT_YUV444P, pix_fmts_444 },
76#ifdef VDP_YCBCR_FORMAT_P016
77    { VDP_CHROMA_TYPE_420_16, AV_PIX_FMT_YUV420P10, pix_fmts_420 },
78    { VDP_CHROMA_TYPE_420_16, AV_PIX_FMT_YUV420P12, pix_fmts_420 },
79    { VDP_CHROMA_TYPE_422_16, AV_PIX_FMT_YUV422P10, pix_fmts_422 },
80    { VDP_CHROMA_TYPE_444_16, AV_PIX_FMT_YUV444P10, pix_fmts_444 },
81    { VDP_CHROMA_TYPE_444_16, AV_PIX_FMT_YUV444P12, pix_fmts_444 },
82#endif
83};
84
85typedef struct VDPAUDeviceContext {
86    VdpVideoSurfaceQueryGetPutBitsYCbCrCapabilities *get_transfer_caps;
87    VdpVideoSurfaceGetBitsYCbCr                     *get_data;
88    VdpVideoSurfacePutBitsYCbCr                     *put_data;
89    VdpVideoSurfaceCreate                           *surf_create;
90    VdpVideoSurfaceDestroy                          *surf_destroy;
91
92    enum AVPixelFormat *pix_fmts[FF_ARRAY_ELEMS(vdpau_pix_fmts)];
93    int              nb_pix_fmts[FF_ARRAY_ELEMS(vdpau_pix_fmts)];
94} VDPAUDeviceContext;
95
96typedef struct VDPAUFramesContext {
97    VdpVideoSurfaceGetBitsYCbCr *get_data;
98    VdpVideoSurfacePutBitsYCbCr *put_data;
99    VdpChromaType chroma_type;
100    int chroma_idx;
101
102    const enum AVPixelFormat *pix_fmts;
103    int                       nb_pix_fmts;
104} VDPAUFramesContext;
105
106static int count_pixfmts(const VDPAUPixFmtMap *map)
107{
108    int count = 0;
109    while (map->pix_fmt != AV_PIX_FMT_NONE) {
110        map++;
111        count++;
112    }
113    return count;
114}
115
116static int vdpau_init_pixmfts(AVHWDeviceContext *ctx)
117{
118    AVVDPAUDeviceContext *hwctx = ctx->hwctx;
119    VDPAUDeviceContext    *priv = ctx->internal->priv;
120    int i;
121
122    for (i = 0; i < FF_ARRAY_ELEMS(priv->pix_fmts); i++) {
123        const VDPAUPixFmtMap *map = vdpau_pix_fmts[i].map;
124        int nb_pix_fmts;
125
126        nb_pix_fmts = count_pixfmts(map);
127        priv->pix_fmts[i] = av_malloc_array(nb_pix_fmts + 1, sizeof(*priv->pix_fmts[i]));
128        if (!priv->pix_fmts[i])
129            return AVERROR(ENOMEM);
130
131        nb_pix_fmts = 0;
132        while (map->pix_fmt != AV_PIX_FMT_NONE) {
133            VdpBool supported;
134            VdpStatus err = priv->get_transfer_caps(hwctx->device, vdpau_pix_fmts[i].chroma_type,
135                                                    map->vdpau_fmt, &supported);
136            if (err == VDP_STATUS_OK && supported)
137                priv->pix_fmts[i][nb_pix_fmts++] = map->pix_fmt;
138            map++;
139        }
140        priv->pix_fmts[i][nb_pix_fmts++] = AV_PIX_FMT_NONE;
141        priv->nb_pix_fmts[i]             = nb_pix_fmts;
142    }
143
144    return 0;
145}
146
147#define GET_CALLBACK(id, result)                                                \
148do {                                                                            \
149    void *tmp;                                                                  \
150    err = hwctx->get_proc_address(hwctx->device, id, &tmp);                     \
151    if (err != VDP_STATUS_OK) {                                                 \
152        av_log(ctx, AV_LOG_ERROR, "Error getting the " #id " callback.\n");     \
153        return AVERROR_UNKNOWN;                                                 \
154    }                                                                           \
155    result = tmp;                                                               \
156} while (0)
157
158static int vdpau_device_init(AVHWDeviceContext *ctx)
159{
160    AVVDPAUDeviceContext *hwctx = ctx->hwctx;
161    VDPAUDeviceContext   *priv  = ctx->internal->priv;
162    VdpStatus             err;
163    int                   ret;
164
165    GET_CALLBACK(VDP_FUNC_ID_VIDEO_SURFACE_QUERY_GET_PUT_BITS_Y_CB_CR_CAPABILITIES,
166                 priv->get_transfer_caps);
167    GET_CALLBACK(VDP_FUNC_ID_VIDEO_SURFACE_GET_BITS_Y_CB_CR, priv->get_data);
168    GET_CALLBACK(VDP_FUNC_ID_VIDEO_SURFACE_PUT_BITS_Y_CB_CR, priv->put_data);
169    GET_CALLBACK(VDP_FUNC_ID_VIDEO_SURFACE_CREATE,           priv->surf_create);
170    GET_CALLBACK(VDP_FUNC_ID_VIDEO_SURFACE_DESTROY,          priv->surf_destroy);
171
172    ret = vdpau_init_pixmfts(ctx);
173    if (ret < 0) {
174        av_log(ctx, AV_LOG_ERROR, "Error querying the supported pixel formats\n");
175        return ret;
176    }
177
178    return 0;
179}
180
181static void vdpau_device_uninit(AVHWDeviceContext *ctx)
182{
183    VDPAUDeviceContext *priv = ctx->internal->priv;
184    int i;
185
186    for (i = 0; i < FF_ARRAY_ELEMS(priv->pix_fmts); i++)
187        av_freep(&priv->pix_fmts[i]);
188}
189
190static int vdpau_frames_get_constraints(AVHWDeviceContext *ctx,
191                                        const void *hwconfig,
192                                        AVHWFramesConstraints *constraints)
193{
194    VDPAUDeviceContext   *priv  = ctx->internal->priv;
195    int nb_sw_formats = 0;
196    int i;
197
198    constraints->valid_sw_formats = av_malloc_array(FF_ARRAY_ELEMS(vdpau_pix_fmts) + 1,
199                                                    sizeof(*constraints->valid_sw_formats));
200    if (!constraints->valid_sw_formats)
201        return AVERROR(ENOMEM);
202
203    for (i = 0; i < FF_ARRAY_ELEMS(vdpau_pix_fmts); i++) {
204        if (priv->nb_pix_fmts[i] > 1)
205            constraints->valid_sw_formats[nb_sw_formats++] = vdpau_pix_fmts[i].frames_sw_format;
206    }
207    constraints->valid_sw_formats[nb_sw_formats] = AV_PIX_FMT_NONE;
208
209    constraints->valid_hw_formats = av_malloc_array(2, sizeof(*constraints->valid_hw_formats));
210    if (!constraints->valid_hw_formats)
211        return AVERROR(ENOMEM);
212
213    constraints->valid_hw_formats[0] = AV_PIX_FMT_VDPAU;
214    constraints->valid_hw_formats[1] = AV_PIX_FMT_NONE;
215
216    return 0;
217}
218
219static void vdpau_buffer_free(void *opaque, uint8_t *data)
220{
221    AVHWFramesContext          *ctx = opaque;
222    VDPAUDeviceContext *device_priv = ctx->device_ctx->internal->priv;
223    VdpVideoSurface            surf = (VdpVideoSurface)(uintptr_t)data;
224
225    device_priv->surf_destroy(surf);
226}
227
228static AVBufferRef *vdpau_pool_alloc(void *opaque, size_t size)
229{
230    AVHWFramesContext             *ctx = opaque;
231    VDPAUFramesContext           *priv = ctx->internal->priv;
232    AVVDPAUDeviceContext *device_hwctx = ctx->device_ctx->hwctx;
233    VDPAUDeviceContext    *device_priv = ctx->device_ctx->internal->priv;
234
235    AVBufferRef *ret;
236    VdpVideoSurface surf;
237    VdpStatus err;
238
239    err = device_priv->surf_create(device_hwctx->device, priv->chroma_type,
240                                   ctx->width, ctx->height, &surf);
241    if (err != VDP_STATUS_OK) {
242        av_log(ctx, AV_LOG_ERROR, "Error allocating a VDPAU video surface\n");
243        return NULL;
244    }
245
246    ret = av_buffer_create((uint8_t*)(uintptr_t)surf, sizeof(surf),
247                           vdpau_buffer_free, ctx, AV_BUFFER_FLAG_READONLY);
248    if (!ret) {
249        device_priv->surf_destroy(surf);
250        return NULL;
251    }
252
253    return ret;
254}
255
256static int vdpau_frames_init(AVHWFramesContext *ctx)
257{
258    VDPAUDeviceContext *device_priv = ctx->device_ctx->internal->priv;
259    VDPAUFramesContext        *priv = ctx->internal->priv;
260
261    int i;
262
263    for (i = 0; i < FF_ARRAY_ELEMS(vdpau_pix_fmts); i++) {
264        if (vdpau_pix_fmts[i].frames_sw_format == ctx->sw_format) {
265            priv->chroma_type = vdpau_pix_fmts[i].chroma_type;
266            priv->chroma_idx  = i;
267            priv->pix_fmts    = device_priv->pix_fmts[i];
268            priv->nb_pix_fmts = device_priv->nb_pix_fmts[i];
269            break;
270        }
271    }
272    if (priv->nb_pix_fmts < 2) {
273        av_log(ctx, AV_LOG_ERROR, "Unsupported sw format: %s\n",
274               av_get_pix_fmt_name(ctx->sw_format));
275        return AVERROR(ENOSYS);
276    }
277
278    if (!ctx->pool) {
279        ctx->internal->pool_internal = av_buffer_pool_init2(sizeof(VdpVideoSurface), ctx,
280                                                            vdpau_pool_alloc, NULL);
281        if (!ctx->internal->pool_internal)
282            return AVERROR(ENOMEM);
283    }
284
285    priv->get_data = device_priv->get_data;
286    priv->put_data = device_priv->put_data;
287
288    return 0;
289}
290
291static int vdpau_get_buffer(AVHWFramesContext *ctx, AVFrame *frame)
292{
293    frame->buf[0] = av_buffer_pool_get(ctx->pool);
294    if (!frame->buf[0])
295        return AVERROR(ENOMEM);
296
297    frame->data[3] = frame->buf[0]->data;
298    frame->format  = AV_PIX_FMT_VDPAU;
299    frame->width   = ctx->width;
300    frame->height  = ctx->height;
301
302    return 0;
303}
304
305static int vdpau_transfer_get_formats(AVHWFramesContext *ctx,
306                                      enum AVHWFrameTransferDirection dir,
307                                      enum AVPixelFormat **formats)
308{
309    VDPAUFramesContext *priv  = ctx->internal->priv;
310
311    enum AVPixelFormat *fmts;
312
313    if (priv->nb_pix_fmts == 1) {
314        av_log(ctx, AV_LOG_ERROR,
315               "No target formats are supported for this chroma type\n");
316        return AVERROR(ENOSYS);
317    }
318
319    fmts = av_malloc_array(priv->nb_pix_fmts, sizeof(*fmts));
320    if (!fmts)
321        return AVERROR(ENOMEM);
322
323    memcpy(fmts, priv->pix_fmts, sizeof(*fmts) * (priv->nb_pix_fmts));
324    *formats = fmts;
325
326    return 0;
327}
328
329static int vdpau_transfer_data_from(AVHWFramesContext *ctx, AVFrame *dst,
330                                    const AVFrame *src)
331{
332    VDPAUFramesContext *priv = ctx->internal->priv;
333    VdpVideoSurface     surf = (VdpVideoSurface)(uintptr_t)src->data[3];
334
335    void *data[3];
336    uint32_t linesize[3];
337
338    const VDPAUPixFmtMap *map;
339    VdpYCbCrFormat vdpau_format;
340    VdpStatus err;
341    int i;
342
343    for (i = 0; i< FF_ARRAY_ELEMS(data) && dst->data[i]; i++) {
344        data[i] = dst->data[i];
345        if (dst->linesize[i] < 0 || dst->linesize[i] > UINT32_MAX) {
346            av_log(ctx, AV_LOG_ERROR,
347                   "The linesize %d cannot be represented as uint32\n",
348                   dst->linesize[i]);
349            return AVERROR(ERANGE);
350        }
351        linesize[i] = dst->linesize[i];
352    }
353
354    map = vdpau_pix_fmts[priv->chroma_idx].map;
355    for (i = 0; map[i].pix_fmt != AV_PIX_FMT_NONE; i++) {
356        if (map[i].pix_fmt == dst->format) {
357            vdpau_format = map[i].vdpau_fmt;
358            break;
359        }
360    }
361    if (map[i].pix_fmt == AV_PIX_FMT_NONE) {
362        av_log(ctx, AV_LOG_ERROR,
363               "Unsupported target pixel format: %s\n",
364               av_get_pix_fmt_name(dst->format));
365        return AVERROR(EINVAL);
366    }
367
368    if ((vdpau_format == VDP_YCBCR_FORMAT_YV12)
369#ifdef VDP_YCBCR_FORMAT_Y_U_V_444
370            || (vdpau_format == VDP_YCBCR_FORMAT_Y_U_V_444)
371#endif
372#ifdef VDP_YCBCR_FORMAT_P016
373            || (vdpau_format == VDP_YCBCR_FORMAT_Y_U_V_444_16)
374#endif
375            )
376        FFSWAP(void*, data[1], data[2]);
377
378    err = priv->get_data(surf, vdpau_format, data, linesize);
379    if (err != VDP_STATUS_OK) {
380        av_log(ctx, AV_LOG_ERROR, "Error retrieving the data from a VDPAU surface\n");
381        return AVERROR_UNKNOWN;
382    }
383
384    return 0;
385}
386
387static int vdpau_transfer_data_to(AVHWFramesContext *ctx, AVFrame *dst,
388                                  const AVFrame *src)
389{
390    VDPAUFramesContext *priv = ctx->internal->priv;
391    VdpVideoSurface     surf = (VdpVideoSurface)(uintptr_t)dst->data[3];
392
393    const void *data[3];
394    uint32_t linesize[3];
395
396    const VDPAUPixFmtMap *map;
397    VdpYCbCrFormat vdpau_format;
398    VdpStatus err;
399    int i;
400
401    for (i = 0; i< FF_ARRAY_ELEMS(data) && src->data[i]; i++) {
402        data[i] = src->data[i];
403        if (src->linesize[i] < 0 || src->linesize[i] > UINT32_MAX) {
404            av_log(ctx, AV_LOG_ERROR,
405                   "The linesize %d cannot be represented as uint32\n",
406                   src->linesize[i]);
407            return AVERROR(ERANGE);
408        }
409        linesize[i] = src->linesize[i];
410    }
411
412    map = vdpau_pix_fmts[priv->chroma_idx].map;
413    for (i = 0; map[i].pix_fmt != AV_PIX_FMT_NONE; i++) {
414        if (map[i].pix_fmt == src->format) {
415            vdpau_format = map[i].vdpau_fmt;
416            break;
417        }
418    }
419    if (map[i].pix_fmt == AV_PIX_FMT_NONE) {
420        av_log(ctx, AV_LOG_ERROR,
421               "Unsupported source pixel format: %s\n",
422               av_get_pix_fmt_name(src->format));
423        return AVERROR(EINVAL);
424    }
425
426    if ((vdpau_format == VDP_YCBCR_FORMAT_YV12)
427#ifdef VDP_YCBCR_FORMAT_Y_U_V_444
428            || (vdpau_format == VDP_YCBCR_FORMAT_Y_U_V_444)
429#endif
430            )
431        FFSWAP(const void*, data[1], data[2]);
432
433    err = priv->put_data(surf, vdpau_format, data, linesize);
434    if (err != VDP_STATUS_OK) {
435        av_log(ctx, AV_LOG_ERROR, "Error uploading the data to a VDPAU surface\n");
436        return AVERROR_UNKNOWN;
437    }
438
439    return 0;
440}
441
442#if HAVE_VDPAU_X11
443#include <vdpau/vdpau_x11.h>
444#include <X11/Xlib.h>
445
446typedef struct VDPAUDevicePriv {
447    VdpDeviceDestroy *device_destroy;
448    Display *dpy;
449} VDPAUDevicePriv;
450
451static void vdpau_device_free(AVHWDeviceContext *ctx)
452{
453    AVVDPAUDeviceContext *hwctx = ctx->hwctx;
454    VDPAUDevicePriv       *priv = ctx->user_opaque;
455
456    if (priv->device_destroy)
457        priv->device_destroy(hwctx->device);
458    if (priv->dpy)
459        XCloseDisplay(priv->dpy);
460    av_freep(&priv);
461}
462
463static int vdpau_device_create(AVHWDeviceContext *ctx, const char *device,
464                               AVDictionary *opts, int flags)
465{
466    AVVDPAUDeviceContext *hwctx = ctx->hwctx;
467
468    VDPAUDevicePriv *priv;
469    VdpStatus err;
470    VdpGetInformationString *get_information_string;
471    const char *display, *vendor;
472
473    priv = av_mallocz(sizeof(*priv));
474    if (!priv)
475        return AVERROR(ENOMEM);
476
477    ctx->user_opaque = priv;
478    ctx->free        = vdpau_device_free;
479
480    priv->dpy = XOpenDisplay(device);
481    if (!priv->dpy) {
482        av_log(ctx, AV_LOG_ERROR, "Cannot open the X11 display %s.\n",
483               XDisplayName(device));
484        return AVERROR_UNKNOWN;
485    }
486    display = XDisplayString(priv->dpy);
487
488    err = vdp_device_create_x11(priv->dpy, XDefaultScreen(priv->dpy),
489                                &hwctx->device, &hwctx->get_proc_address);
490    if (err != VDP_STATUS_OK) {
491        av_log(ctx, AV_LOG_ERROR, "VDPAU device creation on X11 display %s failed.\n",
492               display);
493        return AVERROR_UNKNOWN;
494    }
495
496    GET_CALLBACK(VDP_FUNC_ID_GET_INFORMATION_STRING, get_information_string);
497    GET_CALLBACK(VDP_FUNC_ID_DEVICE_DESTROY,         priv->device_destroy);
498
499    get_information_string(&vendor);
500    av_log(ctx, AV_LOG_VERBOSE, "Successfully created a VDPAU device (%s) on "
501           "X11 display %s\n", vendor, display);
502
503    return 0;
504}
505#endif
506
507const HWContextType ff_hwcontext_type_vdpau = {
508    .type                 = AV_HWDEVICE_TYPE_VDPAU,
509    .name                 = "VDPAU",
510
511    .device_hwctx_size    = sizeof(AVVDPAUDeviceContext),
512    .device_priv_size     = sizeof(VDPAUDeviceContext),
513    .frames_priv_size     = sizeof(VDPAUFramesContext),
514
515#if HAVE_VDPAU_X11
516    .device_create        = vdpau_device_create,
517#endif
518    .device_init          = vdpau_device_init,
519    .device_uninit        = vdpau_device_uninit,
520    .frames_get_constraints = vdpau_frames_get_constraints,
521    .frames_init          = vdpau_frames_init,
522    .frames_get_buffer    = vdpau_get_buffer,
523    .transfer_get_formats = vdpau_transfer_get_formats,
524    .transfer_data_to     = vdpau_transfer_data_to,
525    .transfer_data_from   = vdpau_transfer_data_from,
526
527    .pix_fmts = (const enum AVPixelFormat[]){ AV_PIX_FMT_VDPAU, AV_PIX_FMT_NONE },
528};
529