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