]> git.sesse.net Git - ffmpeg/blob - libavdevice/xv.c
Merge commit '5ec6d152e26c570c0a16ec72c1f354db95708179'
[ffmpeg] / libavdevice / xv.c
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/internal.h"
39 #include "avdevice.h"
40
41 typedef 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 } XVContext;
62
63 typedef struct XVTagFormatMap
64 {
65     int tag;
66     enum AVPixelFormat format;
67 } XVTagFormatMap;
68
69 static XVTagFormatMap tag_codec_map[] = {
70     { MKTAG('I','4','2','0'), AV_PIX_FMT_YUV420P },
71     { MKTAG('U','Y','V','Y'), AV_PIX_FMT_UYVY422 },
72     { MKTAG('Y','U','Y','2'), AV_PIX_FMT_YUYV422 },
73     { 0,                      AV_PIX_FMT_NONE }
74 };
75
76 static int xv_get_tag_from_format(enum AVPixelFormat format)
77 {
78     XVTagFormatMap *m = tag_codec_map;
79     int i;
80     for (i = 0; m->tag; m = &tag_codec_map[++i]) {
81         if (m->format == format)
82             return m->tag;
83     }
84     return 0;
85 }
86
87 static int xv_write_trailer(AVFormatContext *s)
88 {
89     XVContext *xv = s->priv_data;
90     if (xv->display) {
91         XShmDetach(xv->display, &xv->yuv_shminfo);
92         if (xv->yuv_image)
93             shmdt(xv->yuv_image->data);
94         XFree(xv->yuv_image);
95         if (xv->gc)
96             XFreeGC(xv->display, xv->gc);
97         XCloseDisplay(xv->display);
98     }
99     return 0;
100 }
101
102 static int xv_write_header(AVFormatContext *s)
103 {
104     XVContext *xv = s->priv_data;
105     unsigned int num_adaptors;
106     XvAdaptorInfo *ai;
107     XvImageFormatValues *fv;
108     XColor fgcolor;
109     XWindowAttributes window_attrs;
110     int num_formats = 0, j, tag, ret;
111     AVCodecContext *encctx = s->streams[0]->codec;
112
113     if (   s->nb_streams > 1
114         || encctx->codec_type != AVMEDIA_TYPE_VIDEO
115         || encctx->codec_id   != AV_CODEC_ID_RAWVIDEO) {
116         av_log(s, AV_LOG_ERROR, "Only supports one rawvideo stream\n");
117         return AVERROR(EINVAL);
118     }
119
120     if (!(tag = xv_get_tag_from_format(encctx->pix_fmt))) {
121         av_log(s, AV_LOG_ERROR,
122                "Unsupported pixel format '%s', only yuv420p, uyvy422, yuyv422 are currently supported\n",
123                av_get_pix_fmt_name(encctx->pix_fmt));
124         return AVERROR_PATCHWELCOME;
125     }
126     xv->image_format = encctx->pix_fmt;
127
128     xv->display = XOpenDisplay(xv->display_name);
129     if (!xv->display) {
130         av_log(s, AV_LOG_ERROR, "Could not open the X11 display '%s'\n", xv->display_name);
131         return AVERROR(EINVAL);
132     }
133
134     xv->image_width  = encctx->width;
135     xv->image_height = encctx->height;
136     if (!xv->window_width && !xv->window_height) {
137         AVRational sar = encctx->sample_aspect_ratio;
138         xv->window_width  = encctx->width;
139         xv->window_height = encctx->height;
140         if (sar.num) {
141             if (sar.num > sar.den)
142                 xv->window_width = av_rescale(xv->window_width, sar.num, sar.den);
143             if (sar.num < sar.den)
144                 xv->window_height = av_rescale(xv->window_height, sar.den, sar.num);
145         }
146     }
147     if (!xv->window_id) {
148         xv->window = XCreateSimpleWindow(xv->display, DefaultRootWindow(xv->display),
149                                          xv->window_x, xv->window_y,
150                                          xv->window_width, xv->window_height,
151                                          0, 0, 0);
152         if (!xv->window_title) {
153             if (!(xv->window_title = av_strdup(s->filename))) {
154                 ret = AVERROR(ENOMEM);
155                 goto fail;
156             }
157         }
158         XStoreName(xv->display, xv->window, xv->window_title);
159         XMapWindow(xv->display, xv->window);
160     } else
161         xv->window = xv->window_id;
162
163     if (XvQueryAdaptors(xv->display, DefaultRootWindow(xv->display), &num_adaptors, &ai) != Success) {
164         ret = AVERROR_EXTERNAL;
165         goto fail;
166     }
167     if (!num_adaptors) {
168         av_log(s, AV_LOG_ERROR, "No X-Video adaptors present\n");
169         return AVERROR(ENODEV);
170     }
171     xv->xv_port = ai[0].base_id;
172     XvFreeAdaptorInfo(ai);
173
174     fv = XvListImageFormats(xv->display, xv->xv_port, &num_formats);
175     if (!fv) {
176         ret = AVERROR_EXTERNAL;
177         goto fail;
178     }
179     for (j = 0; j < num_formats; j++) {
180         if (fv[j].id == tag) {
181             break;
182         }
183     }
184     XFree(fv);
185
186     if (j >= num_formats) {
187         av_log(s, AV_LOG_ERROR,
188                "Device does not support pixel format %s, aborting\n",
189                av_get_pix_fmt_name(encctx->pix_fmt));
190         ret = AVERROR(EINVAL);
191         goto fail;
192     }
193
194     xv->gc = XCreateGC(xv->display, xv->window, 0, 0);
195     xv->image_width  = encctx->width;
196     xv->image_height = encctx->height;
197     xv->yuv_image = XvShmCreateImage(xv->display, xv->xv_port, tag, 0,
198                                      xv->image_width, xv->image_height, &xv->yuv_shminfo);
199     xv->yuv_shminfo.shmid = shmget(IPC_PRIVATE, xv->yuv_image->data_size,
200                                    IPC_CREAT | 0777);
201     xv->yuv_shminfo.shmaddr = (char *)shmat(xv->yuv_shminfo.shmid, 0, 0);
202     xv->yuv_image->data = xv->yuv_shminfo.shmaddr;
203     xv->yuv_shminfo.readOnly = False;
204
205     XShmAttach(xv->display, &xv->yuv_shminfo);
206     XSync(xv->display, False);
207     shmctl(xv->yuv_shminfo.shmid, IPC_RMID, 0);
208
209     XGetWindowAttributes(xv->display, xv->window, &window_attrs);
210     fgcolor.red = fgcolor.green = fgcolor.blue = 0;
211     fgcolor.flags = DoRed | DoGreen | DoBlue;
212     XAllocColor(xv->display, window_attrs.colormap, &fgcolor);
213     XSetForeground(xv->display, xv->gc, fgcolor.pixel);
214     //force display area recalculation at first frame
215     xv->window_width = xv->window_height = 0;
216
217     return 0;
218   fail:
219     xv_write_trailer(s);
220     return ret;
221 }
222
223 static void compute_display_area(AVFormatContext *s)
224 {
225     XVContext *xv = s->priv_data;
226     AVRational sar, dar; /* sample and display aspect ratios */
227     AVStream *st = s->streams[0];
228     AVCodecContext *encctx = st->codec;
229
230     /* compute overlay width and height from the codec context information */
231     sar = st->sample_aspect_ratio.num ? st->sample_aspect_ratio : (AVRational){ 1, 1 };
232     dar = av_mul_q(sar, (AVRational){ encctx->width, encctx->height });
233
234     /* we suppose the screen has a 1/1 sample aspect ratio */
235     /* fit in the window */
236     if (av_cmp_q(dar, (AVRational){ xv->dest_w, xv->dest_h }) > 0) {
237         /* fit in width */
238         xv->dest_y = xv->dest_h;
239         xv->dest_x = 0;
240         xv->dest_h = av_rescale(xv->dest_w, dar.den, dar.num);
241         xv->dest_y -= xv->dest_h;
242         xv->dest_y /= 2;
243     } else {
244         /* fit in height */
245         xv->dest_x = xv->dest_w;
246         xv->dest_y = 0;
247         xv->dest_w = av_rescale(xv->dest_h, dar.num, dar.den);
248         xv->dest_x -= xv->dest_w;
249         xv->dest_x /= 2;
250     }
251 }
252
253 static int xv_repaint(AVFormatContext *s)
254 {
255     XVContext *xv = s->priv_data;
256     XWindowAttributes window_attrs;
257
258     XGetWindowAttributes(xv->display, xv->window, &window_attrs);
259     if (window_attrs.width != xv->window_width || window_attrs.height != xv->window_height) {
260         XRectangle rect[2];
261         xv->dest_w = window_attrs.width;
262         xv->dest_h = window_attrs.height;
263         compute_display_area(s);
264         if (xv->dest_x) {
265             rect[0].width  = rect[1].width  = xv->dest_x;
266             rect[0].height = rect[1].height = window_attrs.height;
267             rect[0].y      = rect[1].y      = 0;
268             rect[0].x = 0;
269             rect[1].x = xv->dest_w + xv->dest_x;
270             XFillRectangles(xv->display, xv->window, xv->gc, rect, 2);
271         }
272         if (xv->dest_y) {
273             rect[0].width  = rect[1].width  = window_attrs.width;
274             rect[0].height = rect[1].height = xv->dest_y;
275             rect[0].x      = rect[1].x      = 0;
276             rect[0].y = 0;
277             rect[1].y = xv->dest_h + xv->dest_y;
278             XFillRectangles(xv->display, xv->window, xv->gc, rect, 2);
279         }
280     }
281
282     if (XvShmPutImage(xv->display, xv->xv_port, xv->window, xv->gc,
283                       xv->yuv_image, 0, 0, xv->image_width, xv->image_height,
284                       xv->dest_x, xv->dest_y, xv->dest_w, xv->dest_h, True) != Success) {
285         av_log(s, AV_LOG_ERROR, "Could not copy image to XV shared memory buffer\n");
286         return AVERROR_EXTERNAL;
287     }
288     return 0;
289 }
290
291 static int write_picture(AVFormatContext *s, AVPicture *pict)
292 {
293     XVContext *xv = s->priv_data;
294     XvImage *img = xv->yuv_image;
295     uint8_t *data[3] = {
296         img->data + img->offsets[0],
297         img->data + img->offsets[1],
298         img->data + img->offsets[2]
299     };
300     av_image_copy(data, img->pitches, (const uint8_t **)pict->data, pict->linesize,
301                   xv->image_format, img->width, img->height);
302     return xv_repaint(s);
303 }
304
305 static int xv_write_packet(AVFormatContext *s, AVPacket *pkt)
306 {
307     AVPicture pict;
308     AVCodecContext *ctx = s->streams[0]->codec;
309
310     avpicture_fill(&pict, pkt->data, ctx->pix_fmt, ctx->width, ctx->height);
311     return write_picture(s, &pict);
312 }
313
314 static int xv_write_frame(AVFormatContext *s, int stream_index, AVFrame **frame,
315                           unsigned flags)
316 {
317     /* xv_write_header() should have accepted only supported formats */
318     if ((flags & AV_WRITE_UNCODED_FRAME_QUERY))
319         return 0;
320     return write_picture(s, (AVPicture *)*frame);
321 }
322
323 static int xv_control_message(AVFormatContext *s, int type, void *data, size_t data_size)
324 {
325     switch(type) {
326     case AV_APP_TO_DEV_WINDOW_REPAINT:
327         return xv_repaint(s);
328     default:
329         break;
330     }
331     return AVERROR(ENOSYS);
332 }
333
334 #define OFFSET(x) offsetof(XVContext, x)
335 static const AVOption options[] = {
336     { "display_name", "set display name",       OFFSET(display_name), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, AV_OPT_FLAG_ENCODING_PARAM },
337     { "window_id",    "set existing window id", OFFSET(window_id),    AV_OPT_TYPE_INT64,  {.i64 = 0 }, 0, INT64_MAX, AV_OPT_FLAG_ENCODING_PARAM },
338     { "window_size",  "set window forced size", OFFSET(window_width), AV_OPT_TYPE_IMAGE_SIZE, {.str = NULL}, 0, 0, AV_OPT_FLAG_ENCODING_PARAM },
339     { "window_title", "set window title",       OFFSET(window_title), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, AV_OPT_FLAG_ENCODING_PARAM },
340     { "window_x",     "set window x offset",    OFFSET(window_x),     AV_OPT_TYPE_INT,    {.i64 = 0 }, -INT_MAX, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
341     { "window_y",     "set window y offset",    OFFSET(window_y),     AV_OPT_TYPE_INT,    {.i64 = 0 }, -INT_MAX, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
342     { NULL }
343
344 };
345
346 static const AVClass xv_class = {
347     .class_name = "xvideo outdev",
348     .item_name  = av_default_item_name,
349     .option     = options,
350     .version    = LIBAVUTIL_VERSION_INT,
351     .category   = AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT,
352 };
353
354 AVOutputFormat ff_xv_muxer = {
355     .name           = "xv",
356     .long_name      = NULL_IF_CONFIG_SMALL("XV (XVideo) output device"),
357     .priv_data_size = sizeof(XVContext),
358     .audio_codec    = AV_CODEC_ID_NONE,
359     .video_codec    = AV_CODEC_ID_RAWVIDEO,
360     .write_header   = xv_write_header,
361     .write_packet   = xv_write_packet,
362     .write_uncoded_frame = xv_write_frame,
363     .write_trailer  = xv_write_trailer,
364     .control_message = xv_control_message,
365     .flags          = AVFMT_NOFILE | AVFMT_VARIABLE_FPS | AVFMT_NOTIMESTAMPS,
366     .priv_class     = &xv_class,
367 };