Imported Debian version 2.4.3~trusty1
[deb_ffmpeg.git] / ffmpeg / libavdevice / sdl.c
CommitLineData
2ba45a60
DM
1/*
2 * Copyright (c) 2011 Stefano Sabatini
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 * libSDL output device
24 */
25
26#include <SDL.h>
27#include <SDL_thread.h>
28
29#include "libavutil/avstring.h"
30#include "libavutil/opt.h"
31#include "libavutil/parseutils.h"
32#include "libavutil/pixdesc.h"
33#include "libavutil/time.h"
34#include "avdevice.h"
35
36typedef struct {
37 AVClass *class;
38 SDL_Surface *surface;
39 SDL_Overlay *overlay;
40 char *window_title;
41 char *icon_title;
42 int window_width, window_height; /**< size of the window */
43 int window_fullscreen;
44
45 SDL_Rect overlay_rect;
46 int overlay_fmt;
47
48 int sdl_was_already_inited;
49 SDL_Thread *event_thread;
50 SDL_mutex *mutex;
51 SDL_cond *init_cond;
52 int init_ret; /* return code used to signal initialization errors */
53 int inited;
54 int quit;
55} SDLContext;
56
57static const struct sdl_overlay_pix_fmt_entry {
58 enum AVPixelFormat pix_fmt; int overlay_fmt;
59} sdl_overlay_pix_fmt_map[] = {
60 { AV_PIX_FMT_YUV420P, SDL_IYUV_OVERLAY },
61 { AV_PIX_FMT_YUYV422, SDL_YUY2_OVERLAY },
62 { AV_PIX_FMT_UYVY422, SDL_UYVY_OVERLAY },
63 { AV_PIX_FMT_NONE, 0 },
64};
65
66static int sdl_write_trailer(AVFormatContext *s)
67{
68 SDLContext *sdl = s->priv_data;
69
70 sdl->quit = 1;
71
72 if (sdl->overlay)
73 SDL_FreeYUVOverlay(sdl->overlay);
74 sdl->overlay = NULL;
75 if (sdl->event_thread)
76 SDL_WaitThread(sdl->event_thread, NULL);
77 sdl->event_thread = NULL;
78 if (sdl->mutex)
79 SDL_DestroyMutex(sdl->mutex);
80 sdl->mutex = NULL;
81 if (sdl->init_cond)
82 SDL_DestroyCond(sdl->init_cond);
83 sdl->init_cond = NULL;
84
85 if (!sdl->sdl_was_already_inited)
86 SDL_Quit();
87
88 return 0;
89}
90
91static void compute_overlay_rect(AVFormatContext *s)
92{
93 AVRational sar, dar; /* sample and display aspect ratios */
94 SDLContext *sdl = s->priv_data;
95 AVStream *st = s->streams[0];
96 AVCodecContext *encctx = st->codec;
97 SDL_Rect *overlay_rect = &sdl->overlay_rect;
98
99 /* compute overlay width and height from the codec context information */
100 sar = st->sample_aspect_ratio.num ? st->sample_aspect_ratio : (AVRational){ 1, 1 };
101 dar = av_mul_q(sar, (AVRational){ encctx->width, encctx->height });
102
103 /* we suppose the screen has a 1/1 sample aspect ratio */
104 if (sdl->window_width && sdl->window_height) {
105 /* fit in the window */
106 if (av_cmp_q(dar, (AVRational){ sdl->window_width, sdl->window_height }) > 0) {
107 /* fit in width */
108 overlay_rect->w = sdl->window_width;
109 overlay_rect->h = av_rescale(overlay_rect->w, dar.den, dar.num);
110 } else {
111 /* fit in height */
112 overlay_rect->h = sdl->window_height;
113 overlay_rect->w = av_rescale(overlay_rect->h, dar.num, dar.den);
114 }
115 } else {
116 if (sar.num > sar.den) {
117 overlay_rect->w = encctx->width;
118 overlay_rect->h = av_rescale(overlay_rect->w, dar.den, dar.num);
119 } else {
120 overlay_rect->h = encctx->height;
121 overlay_rect->w = av_rescale(overlay_rect->h, dar.num, dar.den);
122 }
123 sdl->window_width = overlay_rect->w;
124 sdl->window_height = overlay_rect->h;
125 }
126
127 overlay_rect->x = (sdl->window_width - overlay_rect->w) / 2;
128 overlay_rect->y = (sdl->window_height - overlay_rect->h) / 2;
129}
130
131#define SDL_BASE_FLAGS (SDL_SWSURFACE|SDL_RESIZABLE)
132
133static int event_thread(void *arg)
134{
135 AVFormatContext *s = arg;
136 SDLContext *sdl = s->priv_data;
137 int flags = SDL_BASE_FLAGS | (sdl->window_fullscreen ? SDL_FULLSCREEN : 0);
138 AVStream *st = s->streams[0];
139 AVCodecContext *encctx = st->codec;
140
141 /* initialization */
142 if (SDL_Init(SDL_INIT_VIDEO) != 0) {
143 av_log(s, AV_LOG_ERROR, "Unable to initialize SDL: %s\n", SDL_GetError());
144 sdl->init_ret = AVERROR(EINVAL);
145 goto init_end;
146 }
147
148 SDL_WM_SetCaption(sdl->window_title, sdl->icon_title);
149 sdl->surface = SDL_SetVideoMode(sdl->window_width, sdl->window_height,
150 24, flags);
151 if (!sdl->surface) {
152 av_log(sdl, AV_LOG_ERROR, "Unable to set video mode: %s\n", SDL_GetError());
153 sdl->init_ret = AVERROR(EINVAL);
154 goto init_end;
155 }
156
157 sdl->overlay = SDL_CreateYUVOverlay(encctx->width, encctx->height,
158 sdl->overlay_fmt, sdl->surface);
159 if (!sdl->overlay || sdl->overlay->pitches[0] < encctx->width) {
160 av_log(s, AV_LOG_ERROR,
161 "SDL does not support an overlay with size of %dx%d pixels\n",
162 encctx->width, encctx->height);
163 sdl->init_ret = AVERROR(EINVAL);
164 goto init_end;
165 }
166
167 sdl->init_ret = 0;
168 av_log(s, AV_LOG_VERBOSE, "w:%d h:%d fmt:%s -> w:%d h:%d\n",
169 encctx->width, encctx->height, av_get_pix_fmt_name(encctx->pix_fmt),
170 sdl->overlay_rect.w, sdl->overlay_rect.h);
171
172init_end:
173 SDL_LockMutex(sdl->mutex);
174 sdl->inited = 1;
175 SDL_UnlockMutex(sdl->mutex);
176 SDL_CondSignal(sdl->init_cond);
177
178 if (sdl->init_ret < 0)
179 return sdl->init_ret;
180
181 /* event loop */
182 while (!sdl->quit) {
183 int ret;
184 SDL_Event event;
185 SDL_PumpEvents();
186 ret = SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_ALLEVENTS);
187 if (ret < 0) {
188 av_log(s, AV_LOG_ERROR, "Error when getting SDL event: %s\n", SDL_GetError());
189 continue;
190 }
191 if (ret == 0) {
192 SDL_Delay(10);
193 continue;
194 }
195
196 switch (event.type) {
197 case SDL_KEYDOWN:
198 switch (event.key.keysym.sym) {
199 case SDLK_ESCAPE:
200 case SDLK_q:
201 sdl->quit = 1;
202 break;
203 }
204 break;
205 case SDL_QUIT:
206 sdl->quit = 1;
207 break;
208
209 case SDL_VIDEORESIZE:
210 sdl->window_width = event.resize.w;
211 sdl->window_height = event.resize.h;
212
213 SDL_LockMutex(sdl->mutex);
214 sdl->surface = SDL_SetVideoMode(sdl->window_width, sdl->window_height, 24, SDL_BASE_FLAGS);
215 if (!sdl->surface) {
216 av_log(s, AV_LOG_ERROR, "Failed to set SDL video mode: %s\n", SDL_GetError());
217 sdl->quit = 1;
218 } else {
219 compute_overlay_rect(s);
220 }
221 SDL_UnlockMutex(sdl->mutex);
222 break;
223
224 default:
225 break;
226 }
227 }
228
229 return 0;
230}
231
232static int sdl_write_header(AVFormatContext *s)
233{
234 SDLContext *sdl = s->priv_data;
235 AVStream *st = s->streams[0];
236 AVCodecContext *encctx = st->codec;
237 int i, ret;
238
239 if (!sdl->window_title)
240 sdl->window_title = av_strdup(s->filename);
241 if (!sdl->icon_title)
242 sdl->icon_title = av_strdup(sdl->window_title);
243
244 if (SDL_WasInit(SDL_INIT_VIDEO)) {
245 av_log(s, AV_LOG_ERROR,
246 "SDL video subsystem was already inited, aborting\n");
247 sdl->sdl_was_already_inited = 1;
248 ret = AVERROR(EINVAL);
249 goto fail;
250 }
251
252 if ( s->nb_streams > 1
253 || encctx->codec_type != AVMEDIA_TYPE_VIDEO
254 || encctx->codec_id != AV_CODEC_ID_RAWVIDEO) {
255 av_log(s, AV_LOG_ERROR, "Only supports one rawvideo stream\n");
256 ret = AVERROR(EINVAL);
257 goto fail;
258 }
259
260 for (i = 0; sdl_overlay_pix_fmt_map[i].pix_fmt != AV_PIX_FMT_NONE; i++) {
261 if (sdl_overlay_pix_fmt_map[i].pix_fmt == encctx->pix_fmt) {
262 sdl->overlay_fmt = sdl_overlay_pix_fmt_map[i].overlay_fmt;
263 break;
264 }
265 }
266
267 if (!sdl->overlay_fmt) {
268 av_log(s, AV_LOG_ERROR,
269 "Unsupported pixel format '%s', choose one of yuv420p, yuyv422, or uyvy422\n",
270 av_get_pix_fmt_name(encctx->pix_fmt));
271 ret = AVERROR(EINVAL);
272 goto fail;
273 }
274
275 /* compute overlay width and height from the codec context information */
276 compute_overlay_rect(s);
277
278 sdl->init_cond = SDL_CreateCond();
279 if (!sdl->init_cond) {
280 av_log(s, AV_LOG_ERROR, "Could not create SDL condition variable: %s\n", SDL_GetError());
281 ret = AVERROR_EXTERNAL;
282 goto fail;
283 }
284 sdl->mutex = SDL_CreateMutex();
285 if (!sdl->mutex) {
286 av_log(s, AV_LOG_ERROR, "Could not create SDL mutex: %s\n", SDL_GetError());
287 ret = AVERROR_EXTERNAL;
288 goto fail;
289 }
290 sdl->event_thread = SDL_CreateThread(event_thread, s);
291 if (!sdl->event_thread) {
292 av_log(s, AV_LOG_ERROR, "Could not create SDL event thread: %s\n", SDL_GetError());
293 ret = AVERROR_EXTERNAL;
294 goto fail;
295 }
296
297 /* wait until the video system has been inited */
298 SDL_LockMutex(sdl->mutex);
299 while (!sdl->inited) {
300 SDL_CondWait(sdl->init_cond, sdl->mutex);
301 }
302 SDL_UnlockMutex(sdl->mutex);
303 if (sdl->init_ret < 0) {
304 ret = sdl->init_ret;
305 goto fail;
306 }
307 return 0;
308
309fail:
310 sdl_write_trailer(s);
311 return ret;
312}
313
314static int sdl_write_packet(AVFormatContext *s, AVPacket *pkt)
315{
316 SDLContext *sdl = s->priv_data;
317 AVCodecContext *encctx = s->streams[0]->codec;
318 AVPicture pict;
319 int i;
320
321 if (sdl->quit) {
322 sdl_write_trailer(s);
323 return AVERROR(EIO);
324 }
325 avpicture_fill(&pict, pkt->data, encctx->pix_fmt, encctx->width, encctx->height);
326
327 SDL_LockMutex(sdl->mutex);
328 SDL_FillRect(sdl->surface, &sdl->surface->clip_rect,
329 SDL_MapRGB(sdl->surface->format, 0, 0, 0));
330 SDL_LockYUVOverlay(sdl->overlay);
331 for (i = 0; i < 3; i++) {
332 sdl->overlay->pixels [i] = pict.data [i];
333 sdl->overlay->pitches[i] = pict.linesize[i];
334 }
335 SDL_DisplayYUVOverlay(sdl->overlay, &sdl->overlay_rect);
336 SDL_UnlockYUVOverlay(sdl->overlay);
337
338 SDL_UpdateRect(sdl->surface,
339 sdl->overlay_rect.x, sdl->overlay_rect.y,
340 sdl->overlay_rect.w, sdl->overlay_rect.h);
341 SDL_UnlockMutex(sdl->mutex);
342
343 return 0;
344}
345
346#define OFFSET(x) offsetof(SDLContext,x)
347
348static const AVOption options[] = {
349 { "window_title", "set SDL window title", OFFSET(window_title), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, AV_OPT_FLAG_ENCODING_PARAM },
350 { "icon_title", "set SDL iconified window title", OFFSET(icon_title) , AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, AV_OPT_FLAG_ENCODING_PARAM },
351 { "window_size", "set SDL window forced size", OFFSET(window_width), AV_OPT_TYPE_IMAGE_SIZE, { .str = NULL }, 0, 0, AV_OPT_FLAG_ENCODING_PARAM },
352 { "window_fullscreen", "set SDL window fullscreen", OFFSET(window_fullscreen), AV_OPT_TYPE_INT, { .i64 = 0 }, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
353 { NULL },
354};
355
356static const AVClass sdl_class = {
357 .class_name = "sdl outdev",
358 .item_name = av_default_item_name,
359 .option = options,
360 .version = LIBAVUTIL_VERSION_INT,
361 .category = AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT,
362};
363
364AVOutputFormat ff_sdl_muxer = {
365 .name = "sdl",
366 .long_name = NULL_IF_CONFIG_SMALL("SDL output device"),
367 .priv_data_size = sizeof(SDLContext),
368 .audio_codec = AV_CODEC_ID_NONE,
369 .video_codec = AV_CODEC_ID_RAWVIDEO,
370 .write_header = sdl_write_header,
371 .write_packet = sdl_write_packet,
372 .write_trailer = sdl_write_trailer,
373 .flags = AVFMT_NOFILE | AVFMT_VARIABLE_FPS | AVFMT_NOTIMESTAMPS,
374 .priv_class = &sdl_class,
375};