2 * Copyright (c) 2013 Jeff Moguillansky
4 * This file is part of FFmpeg.
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.
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.
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
23 * XVideo output device
26 * - add support to more formats
30 #include <X11/extensions/Xv.h>
31 #include <X11/extensions/XShm.h>
32 #include <X11/extensions/Xvlib.h>
35 #include "libavutil/opt.h"
36 #include "libavutil/pixdesc.h"
37 #include "libavutil/imgutils.h"
38 #include "libavformat/internal.h"
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 */
57 enum AVPixelFormat image_format
;
58 int image_width
, image_height
;
59 XShmSegmentInfo yuv_shminfo
;
61 Atom wm_delete_message
;
64 typedef struct XVTagFormatMap
67 enum AVPixelFormat format
;
70 static 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
}
77 static int xv_get_tag_from_format(enum AVPixelFormat format
)
79 const XVTagFormatMap
*m
= tag_codec_map
;
81 for (i
= 0; m
->tag
; m
= &tag_codec_map
[++i
]) {
82 if (m
->format
== format
)
88 static int xv_write_trailer(AVFormatContext
*s
)
90 XVContext
*xv
= s
->priv_data
;
92 XShmDetach(xv
->display
, &xv
->yuv_shminfo
);
94 shmdt(xv
->yuv_image
->data
);
97 XFreeGC(xv
->display
, xv
->gc
);
98 XCloseDisplay(xv
->display
);
103 static int xv_write_header(AVFormatContext
*s
)
105 XVContext
*xv
= s
->priv_data
;
106 unsigned int num_adaptors
;
108 XvImageFormatValues
*fv
;
110 XWindowAttributes window_attrs
;
111 int num_formats
= 0, j
, tag
, ret
;
112 AVCodecContext
*encctx
= s
->streams
[0]->codec
;
114 if ( s
->nb_streams
> 1
115 || encctx
->codec_type
!= AVMEDIA_TYPE_VIDEO
116 || encctx
->codec_id
!= AV_CODEC_ID_RAWVIDEO
) {
117 av_log(s
, AV_LOG_ERROR
, "Only supports one rawvideo stream\n");
118 return AVERROR(EINVAL
);
121 if (!(tag
= xv_get_tag_from_format(encctx
->pix_fmt
))) {
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(encctx
->pix_fmt
));
125 return AVERROR_PATCHWELCOME
;
127 xv
->image_format
= encctx
->pix_fmt
;
129 xv
->display
= XOpenDisplay(xv
->display_name
);
131 av_log(s
, AV_LOG_ERROR
, "Could not open the X11 display '%s'\n", xv
->display_name
);
132 return AVERROR(EINVAL
);
135 xv
->image_width
= encctx
->width
;
136 xv
->image_height
= encctx
->height
;
137 if (!xv
->window_width
&& !xv
->window_height
) {
138 AVRational sar
= encctx
->sample_aspect_ratio
;
139 xv
->window_width
= encctx
->width
;
140 xv
->window_height
= encctx
->height
;
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
);
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
,
153 if (!xv
->window_title
) {
154 if (!(xv
->window_title
= av_strdup(s
->filename
))) {
155 ret
= AVERROR(ENOMEM
);
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
);
164 xv
->window
= xv
->window_id
;
166 if (XvQueryAdaptors(xv
->display
, DefaultRootWindow(xv
->display
), &num_adaptors
, &ai
) != Success
) {
167 ret
= AVERROR_EXTERNAL
;
171 av_log(s
, AV_LOG_ERROR
, "No X-Video adaptors present\n");
172 return AVERROR(ENODEV
);
174 xv
->xv_port
= ai
[0].base_id
;
175 XvFreeAdaptorInfo(ai
);
177 fv
= XvListImageFormats(xv
->display
, xv
->xv_port
, &num_formats
);
179 ret
= AVERROR_EXTERNAL
;
182 for (j
= 0; j
< num_formats
; j
++) {
183 if (fv
[j
].id
== tag
) {
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(encctx
->pix_fmt
));
193 ret
= AVERROR(EINVAL
);
197 xv
->gc
= XCreateGC(xv
->display
, xv
->window
, 0, 0);
198 xv
->image_width
= encctx
->width
;
199 xv
->image_height
= encctx
->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
,
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
;
208 XShmAttach(xv
->display
, &xv
->yuv_shminfo
);
209 XSync(xv
->display
, False
);
210 shmctl(xv
->yuv_shminfo
.shmid
, IPC_RMID
, 0);
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;
226 static void compute_display_area(AVFormatContext
*s
)
228 XVContext
*xv
= s
->priv_data
;
229 AVRational sar
, dar
; /* sample and display aspect ratios */
230 AVStream
*st
= s
->streams
[0];
231 AVCodecContext
*encctx
= st
->codec
;
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
){ encctx
->width
, encctx
->height
});
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) {
241 xv
->dest_y
= xv
->dest_h
;
243 xv
->dest_h
= av_rescale(xv
->dest_w
, dar
.den
, dar
.num
);
244 xv
->dest_y
-= xv
->dest_h
;
248 xv
->dest_x
= xv
->dest_w
;
250 xv
->dest_w
= av_rescale(xv
->dest_h
, dar
.num
, dar
.den
);
251 xv
->dest_x
-= xv
->dest_w
;
256 static int xv_repaint(AVFormatContext
*s
)
258 XVContext
*xv
= s
->priv_data
;
259 XWindowAttributes window_attrs
;
261 XGetWindowAttributes(xv
->display
, xv
->window
, &window_attrs
);
262 if (window_attrs
.width
!= xv
->window_width
|| window_attrs
.height
!= xv
->window_height
) {
264 xv
->dest_w
= window_attrs
.width
;
265 xv
->dest_h
= window_attrs
.height
;
266 compute_display_area(s
);
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;
272 rect
[1].x
= xv
->dest_w
+ xv
->dest_x
;
273 XFillRectangles(xv
->display
, xv
->window
, xv
->gc
, rect
, 2);
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;
280 rect
[1].y
= xv
->dest_h
+ xv
->dest_y
;
281 XFillRectangles(xv
->display
, xv
->window
, xv
->gc
, rect
, 2);
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
;
294 static int write_picture(AVFormatContext
*s
, AVPicture
*pict
)
296 XVContext
*xv
= s
->priv_data
;
297 XvImage
*img
= xv
->yuv_image
;
299 img
->data
+ img
->offsets
[0],
300 img
->data
+ img
->offsets
[1],
301 img
->data
+ img
->offsets
[2]
304 /* Check messages. Window might get closed. */
305 if (!xv
->window_id
) {
307 while (XPending(xv
->display
)) {
308 XNextEvent(xv
->display
, &event
);
309 if (event
.type
== ClientMessage
&& event
.xclient
.data
.l
[0] == xv
->wm_delete_message
) {
310 av_log(xv
, AV_LOG_DEBUG
, "Window close event.\n");
311 return AVERROR(EPIPE
);
316 av_image_copy(data
, img
->pitches
, (const uint8_t **)pict
->data
, pict
->linesize
,
317 xv
->image_format
, img
->width
, img
->height
);
318 return xv_repaint(s
);
321 static int xv_write_packet(AVFormatContext
*s
, AVPacket
*pkt
)
324 AVCodecContext
*ctx
= s
->streams
[0]->codec
;
326 avpicture_fill(&pict
, pkt
->data
, ctx
->pix_fmt
, ctx
->width
, ctx
->height
);
327 return write_picture(s
, &pict
);
330 static int xv_write_frame(AVFormatContext
*s
, int stream_index
, AVFrame
**frame
,
333 /* xv_write_header() should have accepted only supported formats */
334 if ((flags
& AV_WRITE_UNCODED_FRAME_QUERY
))
336 return write_picture(s
, (AVPicture
*)*frame
);
339 static int xv_control_message(AVFormatContext
*s
, int type
, void *data
, size_t data_size
)
342 case AV_APP_TO_DEV_WINDOW_REPAINT
:
343 return xv_repaint(s
);
347 return AVERROR(ENOSYS
);
350 #define OFFSET(x) offsetof(XVContext, x)
351 static const AVOption options
[] = {
352 { "display_name", "set display name", OFFSET(display_name
), AV_OPT_TYPE_STRING
, {.str
= NULL
}, 0, 0, AV_OPT_FLAG_ENCODING_PARAM
},
353 { "window_id", "set existing window id", OFFSET(window_id
), AV_OPT_TYPE_INT64
, {.i64
= 0 }, 0, INT64_MAX
, AV_OPT_FLAG_ENCODING_PARAM
},
354 { "window_size", "set window forced size", OFFSET(window_width
), AV_OPT_TYPE_IMAGE_SIZE
, {.str
= NULL
}, 0, 0, AV_OPT_FLAG_ENCODING_PARAM
},
355 { "window_title", "set window title", OFFSET(window_title
), AV_OPT_TYPE_STRING
, {.str
= NULL
}, 0, 0, AV_OPT_FLAG_ENCODING_PARAM
},
356 { "window_x", "set window x offset", OFFSET(window_x
), AV_OPT_TYPE_INT
, {.i64
= 0 }, -INT_MAX
, INT_MAX
, AV_OPT_FLAG_ENCODING_PARAM
},
357 { "window_y", "set window y offset", OFFSET(window_y
), AV_OPT_TYPE_INT
, {.i64
= 0 }, -INT_MAX
, INT_MAX
, AV_OPT_FLAG_ENCODING_PARAM
},
362 static const AVClass xv_class
= {
363 .class_name
= "xvideo outdev",
364 .item_name
= av_default_item_name
,
366 .version
= LIBAVUTIL_VERSION_INT
,
367 .category
= AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT
,
370 AVOutputFormat ff_xv_muxer
= {
372 .long_name
= NULL_IF_CONFIG_SMALL("XV (XVideo) output device"),
373 .priv_data_size
= sizeof(XVContext
),
374 .audio_codec
= AV_CODEC_ID_NONE
,
375 .video_codec
= AV_CODEC_ID_RAWVIDEO
,
376 .write_header
= xv_write_header
,
377 .write_packet
= xv_write_packet
,
378 .write_uncoded_frame
= xv_write_frame
,
379 .write_trailer
= xv_write_trailer
,
380 .control_message
= xv_control_message
,
381 .flags
= AVFMT_NOFILE
| AVFMT_VARIABLE_FPS
| AVFMT_NOTIMESTAMPS
,
382 .priv_class
= &xv_class
,