xref: /third_party/ffmpeg/libavdevice/xv.c (revision cabdff1a)
1/*
2 * Copyright (c) 2013 Jeff Moguillansky
3 *
4 * This file is part of FFmpeg.
5 *
6 * FFmpeg is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * FFmpeg is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with FFmpeg; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21/**
22 * @file
23 * XVideo output device
24 *
25 * TODO:
26 * - add support to more formats
27 */
28
29#include <X11/Xlib.h>
30#include <X11/extensions/Xv.h>
31#include <X11/extensions/XShm.h>
32#include <X11/extensions/Xvlib.h>
33#include <sys/shm.h>
34
35#include "libavutil/opt.h"
36#include "libavutil/pixdesc.h"
37#include "libavutil/imgutils.h"
38#include "libavformat/mux.h"
39#include "avdevice.h"
40
41typedef struct {
42    AVClass *class;
43    GC gc;
44
45    Window window;
46    int64_t window_id;
47    char *window_title;
48    int window_width, window_height;
49    int window_x, window_y;
50    int dest_x, dest_y;          /**< display area position */
51    unsigned int dest_w, dest_h; /**< display area dimensions */
52
53    Display* display;
54    char *display_name;
55
56    XvImage* yuv_image;
57    enum AVPixelFormat image_format;
58    int image_width, image_height;
59    XShmSegmentInfo yuv_shminfo;
60    int xv_port;
61    Atom wm_delete_message;
62} XVContext;
63
64typedef struct XVTagFormatMap
65{
66    int tag;
67    enum AVPixelFormat format;
68} XVTagFormatMap;
69
70static const XVTagFormatMap tag_codec_map[] = {
71    { MKTAG('I','4','2','0'), AV_PIX_FMT_YUV420P },
72    { MKTAG('U','Y','V','Y'), AV_PIX_FMT_UYVY422 },
73    { MKTAG('Y','U','Y','2'), AV_PIX_FMT_YUYV422 },
74    { 0,                      AV_PIX_FMT_NONE }
75};
76
77static int xv_get_tag_from_format(enum AVPixelFormat format)
78{
79    const XVTagFormatMap *m = tag_codec_map;
80    int i;
81    for (i = 0; m->tag; m = &tag_codec_map[++i]) {
82        if (m->format == format)
83            return m->tag;
84    }
85    return 0;
86}
87
88static int xv_write_trailer(AVFormatContext *s)
89{
90    XVContext *xv = s->priv_data;
91    if (xv->display) {
92        XShmDetach(xv->display, &xv->yuv_shminfo);
93        if (xv->yuv_image)
94            shmdt(xv->yuv_image->data);
95        XFree(xv->yuv_image);
96        if (xv->gc)
97            XFreeGC(xv->display, xv->gc);
98        XCloseDisplay(xv->display);
99    }
100    return 0;
101}
102
103static int xv_write_header(AVFormatContext *s)
104{
105    XVContext *xv = s->priv_data;
106    unsigned int num_adaptors;
107    XvAdaptorInfo *ai;
108    XvImageFormatValues *fv;
109    XColor fgcolor;
110    XWindowAttributes window_attrs;
111    int num_formats = 0, j, tag, ret;
112    AVCodecParameters *par = s->streams[0]->codecpar;
113
114    if (   s->nb_streams > 1
115        || par->codec_type != AVMEDIA_TYPE_VIDEO
116        || (par->codec_id != AV_CODEC_ID_WRAPPED_AVFRAME && par->codec_id != AV_CODEC_ID_RAWVIDEO)) {
117        av_log(s, AV_LOG_ERROR, "Only a single raw or wrapped avframe video stream is supported.\n");
118        return AVERROR(EINVAL);
119    }
120
121    if (!(tag = xv_get_tag_from_format(par->format))) {
122        av_log(s, AV_LOG_ERROR,
123               "Unsupported pixel format '%s', only yuv420p, uyvy422, yuyv422 are currently supported\n",
124               av_get_pix_fmt_name(par->format));
125        return AVERROR_PATCHWELCOME;
126    }
127    xv->image_format = par->format;
128
129    xv->display = XOpenDisplay(xv->display_name);
130    if (!xv->display) {
131        av_log(s, AV_LOG_ERROR, "Could not open the X11 display '%s'\n", xv->display_name);
132        return AVERROR(EINVAL);
133    }
134
135    xv->image_width  = par->width;
136    xv->image_height = par->height;
137    if (!xv->window_width && !xv->window_height) {
138        AVRational sar = par->sample_aspect_ratio;
139        xv->window_width  = par->width;
140        xv->window_height = par->height;
141        if (sar.num) {
142            if (sar.num > sar.den)
143                xv->window_width = av_rescale(xv->window_width, sar.num, sar.den);
144            if (sar.num < sar.den)
145                xv->window_height = av_rescale(xv->window_height, sar.den, sar.num);
146        }
147    }
148    if (!xv->window_id) {
149        xv->window = XCreateSimpleWindow(xv->display, DefaultRootWindow(xv->display),
150                                         xv->window_x, xv->window_y,
151                                         xv->window_width, xv->window_height,
152                                         0, 0, 0);
153        if (!xv->window_title) {
154            if (!(xv->window_title = av_strdup(s->url))) {
155                ret = AVERROR(ENOMEM);
156                goto fail;
157            }
158        }
159        XStoreName(xv->display, xv->window, xv->window_title);
160        xv->wm_delete_message = XInternAtom(xv->display, "WM_DELETE_WINDOW", False);
161        XSetWMProtocols(xv->display, xv->window, &xv->wm_delete_message, 1);
162        XMapWindow(xv->display, xv->window);
163    } else
164        xv->window = xv->window_id;
165
166    if (XvQueryAdaptors(xv->display, DefaultRootWindow(xv->display), &num_adaptors, &ai) != Success) {
167        ret = AVERROR_EXTERNAL;
168        goto fail;
169    }
170    if (!num_adaptors) {
171        av_log(s, AV_LOG_ERROR, "No X-Video adaptors present\n");
172        return AVERROR(ENODEV);
173    }
174    xv->xv_port = ai[0].base_id;
175    XvFreeAdaptorInfo(ai);
176
177    fv = XvListImageFormats(xv->display, xv->xv_port, &num_formats);
178    if (!fv) {
179        ret = AVERROR_EXTERNAL;
180        goto fail;
181    }
182    for (j = 0; j < num_formats; j++) {
183        if (fv[j].id == tag) {
184            break;
185        }
186    }
187    XFree(fv);
188
189    if (j >= num_formats) {
190        av_log(s, AV_LOG_ERROR,
191               "Device does not support pixel format %s, aborting\n",
192               av_get_pix_fmt_name(par->format));
193        ret = AVERROR(EINVAL);
194        goto fail;
195    }
196
197    xv->gc = XCreateGC(xv->display, xv->window, 0, 0);
198    xv->image_width  = par->width;
199    xv->image_height = par->height;
200    xv->yuv_image = XvShmCreateImage(xv->display, xv->xv_port, tag, 0,
201                                     xv->image_width, xv->image_height, &xv->yuv_shminfo);
202    xv->yuv_shminfo.shmid = shmget(IPC_PRIVATE, xv->yuv_image->data_size,
203                                   IPC_CREAT | 0777);
204    xv->yuv_shminfo.shmaddr = (char *)shmat(xv->yuv_shminfo.shmid, 0, 0);
205    xv->yuv_image->data = xv->yuv_shminfo.shmaddr;
206    xv->yuv_shminfo.readOnly = False;
207
208    XShmAttach(xv->display, &xv->yuv_shminfo);
209    XSync(xv->display, False);
210    shmctl(xv->yuv_shminfo.shmid, IPC_RMID, 0);
211
212    XGetWindowAttributes(xv->display, xv->window, &window_attrs);
213    fgcolor.red = fgcolor.green = fgcolor.blue = 0;
214    fgcolor.flags = DoRed | DoGreen | DoBlue;
215    XAllocColor(xv->display, window_attrs.colormap, &fgcolor);
216    XSetForeground(xv->display, xv->gc, fgcolor.pixel);
217    //force display area recalculation at first frame
218    xv->window_width = xv->window_height = 0;
219
220    return 0;
221  fail:
222    xv_write_trailer(s);
223    return ret;
224}
225
226static void compute_display_area(AVFormatContext *s)
227{
228    XVContext *xv = s->priv_data;
229    AVRational sar, dar; /* sample and display aspect ratios */
230    AVStream *st = s->streams[0];
231    AVCodecParameters *par = st->codecpar;
232
233    /* compute overlay width and height from the codec context information */
234    sar = st->sample_aspect_ratio.num ? st->sample_aspect_ratio : (AVRational){ 1, 1 };
235    dar = av_mul_q(sar, (AVRational){ par->width, par->height });
236
237    /* we suppose the screen has a 1/1 sample aspect ratio */
238    /* fit in the window */
239    if (av_cmp_q(dar, (AVRational){ xv->dest_w, xv->dest_h }) > 0) {
240        /* fit in width */
241        xv->dest_y = xv->dest_h;
242        xv->dest_x = 0;
243        xv->dest_h = av_rescale(xv->dest_w, dar.den, dar.num);
244        xv->dest_y -= xv->dest_h;
245        xv->dest_y /= 2;
246    } else {
247        /* fit in height */
248        xv->dest_x = xv->dest_w;
249        xv->dest_y = 0;
250        xv->dest_w = av_rescale(xv->dest_h, dar.num, dar.den);
251        xv->dest_x -= xv->dest_w;
252        xv->dest_x /= 2;
253    }
254}
255
256static int xv_repaint(AVFormatContext *s)
257{
258    XVContext *xv = s->priv_data;
259    XWindowAttributes window_attrs;
260
261    XGetWindowAttributes(xv->display, xv->window, &window_attrs);
262    if (window_attrs.width != xv->window_width || window_attrs.height != xv->window_height) {
263        XRectangle rect[2];
264        xv->dest_w = window_attrs.width;
265        xv->dest_h = window_attrs.height;
266        compute_display_area(s);
267        if (xv->dest_x) {
268            rect[0].width  = rect[1].width  = xv->dest_x;
269            rect[0].height = rect[1].height = window_attrs.height;
270            rect[0].y      = rect[1].y      = 0;
271            rect[0].x = 0;
272            rect[1].x = xv->dest_w + xv->dest_x;
273            XFillRectangles(xv->display, xv->window, xv->gc, rect, 2);
274        }
275        if (xv->dest_y) {
276            rect[0].width  = rect[1].width  = window_attrs.width;
277            rect[0].height = rect[1].height = xv->dest_y;
278            rect[0].x      = rect[1].x      = 0;
279            rect[0].y = 0;
280            rect[1].y = xv->dest_h + xv->dest_y;
281            XFillRectangles(xv->display, xv->window, xv->gc, rect, 2);
282        }
283    }
284
285    if (XvShmPutImage(xv->display, xv->xv_port, xv->window, xv->gc,
286                      xv->yuv_image, 0, 0, xv->image_width, xv->image_height,
287                      xv->dest_x, xv->dest_y, xv->dest_w, xv->dest_h, True) != Success) {
288        av_log(s, AV_LOG_ERROR, "Could not copy image to XV shared memory buffer\n");
289        return AVERROR_EXTERNAL;
290    }
291    return 0;
292}
293
294static int write_picture(AVFormatContext *s, uint8_t *input_data[4],
295                         int linesize[4])
296{
297    XVContext *xv = s->priv_data;
298    XvImage *img = xv->yuv_image;
299    uint8_t *data[4] = {
300        img->data + img->offsets[0],
301        img->data + img->offsets[1],
302        img->data + img->offsets[2]
303    };
304
305    /* Check messages. Window might get closed. */
306    if (!xv->window_id) {
307        XEvent event;
308        while (XPending(xv->display)) {
309            XNextEvent(xv->display, &event);
310            if (event.type == ClientMessage && event.xclient.data.l[0] == xv->wm_delete_message) {
311                av_log(xv, AV_LOG_DEBUG, "Window close event.\n");
312                return AVERROR(EPIPE);
313            }
314        }
315    }
316
317    av_image_copy(data, img->pitches, (const uint8_t **)input_data, linesize,
318                  xv->image_format, img->width, img->height);
319    return xv_repaint(s);
320}
321
322static int xv_write_packet(AVFormatContext *s, AVPacket *pkt)
323{
324    AVCodecParameters *par = s->streams[0]->codecpar;
325
326    if (par->codec_id == AV_CODEC_ID_WRAPPED_AVFRAME) {
327        AVFrame *frame = (AVFrame *)pkt->data;
328        return write_picture(s, frame->data, frame->linesize);
329    } else {
330        uint8_t *data[4];
331        int linesize[4];
332
333        av_image_fill_arrays(data, linesize, pkt->data, par->format,
334                             par->width, par->height, 1);
335        return write_picture(s, data, linesize);
336    }
337}
338
339static int xv_write_frame(AVFormatContext *s, int stream_index, AVFrame **frame,
340                          unsigned flags)
341{
342    /* xv_write_header() should have accepted only supported formats */
343    if ((flags & AV_WRITE_UNCODED_FRAME_QUERY))
344        return 0;
345    return write_picture(s, (*frame)->data, (*frame)->linesize);
346}
347
348static int xv_control_message(AVFormatContext *s, int type, void *data, size_t data_size)
349{
350    switch(type) {
351    case AV_APP_TO_DEV_WINDOW_REPAINT:
352        return xv_repaint(s);
353    default:
354        break;
355    }
356    return AVERROR(ENOSYS);
357}
358
359#define OFFSET(x) offsetof(XVContext, x)
360static const AVOption options[] = {
361    { "display_name", "set display name",       OFFSET(display_name), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, AV_OPT_FLAG_ENCODING_PARAM },
362    { "window_id",    "set existing window id", OFFSET(window_id),    AV_OPT_TYPE_INT64,  {.i64 = 0 }, 0, INT64_MAX, AV_OPT_FLAG_ENCODING_PARAM },
363    { "window_size",  "set window forced size", OFFSET(window_width), AV_OPT_TYPE_IMAGE_SIZE, {.str = NULL}, 0, 0, AV_OPT_FLAG_ENCODING_PARAM },
364    { "window_title", "set window title",       OFFSET(window_title), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, AV_OPT_FLAG_ENCODING_PARAM },
365    { "window_x",     "set window x offset",    OFFSET(window_x),     AV_OPT_TYPE_INT,    {.i64 = 0 }, -INT_MAX, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
366    { "window_y",     "set window y offset",    OFFSET(window_y),     AV_OPT_TYPE_INT,    {.i64 = 0 }, -INT_MAX, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
367    { NULL }
368
369};
370
371static const AVClass xv_class = {
372    .class_name = "xvideo outdev",
373    .item_name  = av_default_item_name,
374    .option     = options,
375    .version    = LIBAVUTIL_VERSION_INT,
376    .category   = AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT,
377};
378
379const AVOutputFormat ff_xv_muxer = {
380    .name           = "xv",
381    .long_name      = NULL_IF_CONFIG_SMALL("XV (XVideo) output device"),
382    .priv_data_size = sizeof(XVContext),
383    .audio_codec    = AV_CODEC_ID_NONE,
384    .video_codec    = AV_CODEC_ID_WRAPPED_AVFRAME,
385    .write_header   = xv_write_header,
386    .write_packet   = xv_write_packet,
387    .write_uncoded_frame = xv_write_frame,
388    .write_trailer  = xv_write_trailer,
389    .control_message = xv_control_message,
390    .flags          = AVFMT_NOFILE | AVFMT_VARIABLE_FPS | AVFMT_NOTIMESTAMPS,
391    .priv_class     = &xv_class,
392};
393