fbff: ignore oss device in pause command when no audio
[fbff.git] / fbff.c
blobf3cf085a02aaa9e301bd0ea517af09bdcc39f3cb
1 /*
2 * fbff - a small ffmpeg-based framebuffer/oss media player
4 * Copyright (C) 2009-2013 Ali Gholami Rudi
6 * This program is released under the modified BSD license.
7 */
8 #include <fcntl.h>
9 #include <pty.h>
10 #include <ctype.h>
11 #include <signal.h>
12 #include <stdint.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <termios.h>
17 #include <unistd.h>
18 #include <sys/poll.h>
19 #include <sys/soundcard.h>
20 #include <pthread.h>
21 #include "config.h"
22 #include "ffs.h"
23 #include "draw.h"
25 #define MIN(a, b) ((a) < (b) ? (a) : (b))
26 #define MAX(a, b) ((a) > (b) ? (a) : (b))
28 static int arg;
29 static struct termios termios;
30 static int paused;
31 static int exited;
33 static float zoom = 1;
34 static int magnify = 1;
35 static int jump = 0;
36 static int fullscreen = 0;
37 static int video = 1; /* video stream; 0=none, 1=auto, >2=idx */
38 static int audio = 1; /* audio stream; 0=none, 1=auto, >2=idx */
39 static int rjust = 0; /* justify video to the right */
40 static int bjust = 0; /* justify video to the bottom */
41 static int frame_jmp = 1; /* the changes to pos_cur for each frame */
43 static struct ffs *affs; /* audio ffmpeg stream */
44 static struct ffs *vffs; /* video ffmpeg stream */
45 static int afd; /* oss fd */
46 static int vnum; /* decoded video frame count */
48 static int sync_diff; /* audio/video frame position diff */
49 static int sync_period; /* sync after every this many number of frames */
50 static int sync_since; /* frames since th last sync */
51 static int sync_cnt = 32; /* synchronization steps */
52 static int sync_cur; /* synchronization steps left */
53 static int sync_first; /* first frame to record sync_diff */
55 static void stroll(void)
57 usleep(10000);
60 static void draw_frame(void *img, int linelen)
62 int w, h;
63 fbval_t buf[1 << 14];
64 int nr, nc, cb, rb;
65 int i, r, c;
66 ffs_vinfo(vffs, &w, &h);
67 nr = MIN(h * zoom, fb_rows() / magnify);
68 nc = MIN(w * zoom, fb_cols() / magnify);
69 cb = rjust ? fb_cols() - nc * magnify : 0;
70 rb = bjust ? fb_rows() - nr * magnify : 0;
71 for (r = 0; r < nr; r++) {
72 fbval_t *row = img + r * linelen;
73 if (magnify == 1) {
74 fb_set(rb + r, cb, row, nc);
75 continue;
77 for (c = 0; c < nc; c++)
78 for (i = 0; i < magnify; i++)
79 buf[c * magnify + i] = row[c];
80 for (i = 0; i < magnify; i++)
81 fb_set((rb + r) * magnify + i, cb, buf, nc * magnify);
85 static int oss_open(void)
87 int rate, ch, bps;
88 afd = open("/dev/dsp", O_WRONLY);
89 if (afd < 0)
90 return 1;
91 ffs_ainfo(affs, &rate, &bps, &ch);
92 ioctl(afd, SOUND_PCM_WRITE_CHANNELS, &ch);
93 ioctl(afd, SOUND_PCM_WRITE_BITS, &bps);
94 ioctl(afd, SOUND_PCM_WRITE_RATE, &rate);
95 return 0;
98 static void oss_close(void)
100 if (afd > 0)
101 close(afd);
102 afd = 0;
105 #define ABUFSZ (1 << 18)
107 static int a_cons;
108 static int a_prod;
109 static char a_buf[AUDIOBUFS][ABUFSZ];
110 static int a_len[AUDIOBUFS];
111 static int a_reset;
113 static int a_conswait(void)
115 return a_cons == a_prod;
118 static int a_prodwait(void)
120 return ((a_prod + 1) & (AUDIOBUFS - 1)) == a_cons;
123 static void a_doreset(int pause)
125 a_reset = 1 + pause;
126 while (audio && a_reset)
127 stroll();
130 static int readkey(void)
132 char b;
133 if (read(0, &b, 1) <= 0)
134 return -1;
135 return b;
138 static void waitkey(void)
140 struct pollfd ufds[1];
141 ufds[0].fd = 0;
142 ufds[0].events = POLLIN;
143 poll(ufds, 1, -1);
146 static int ffarg(int def)
148 int n = arg;
149 arg = 0;
150 return n ? n : def;
153 static void ffjmp(int n, int rel)
155 struct ffs *ffs = video ? vffs : affs;
156 long pos = ffs_pos(ffs, n);
157 a_doreset(0);
158 sync_cur = sync_cnt;
159 if (audio)
160 ffs_seek(affs, pos, frame_jmp);
161 if (video)
162 ffs_seek(vffs, pos, frame_jmp);
165 static void printinfo(void)
167 struct ffs *ffs = video ? vffs : affs;
168 printf("fbff: %8lx \t (AV: %d)\r",
169 ffs_pos(ffs, 0), video && audio ? ffs_avdiff(vffs, affs) : 0);
170 fflush(stdout);
173 #define JMP1 (1 << 5)
174 #define JMP2 (JMP1 << 3)
175 #define JMP3 (JMP2 << 5)
177 static void execkey(void)
179 int c;
180 while ((c = readkey()) != -1) {
181 switch (c) {
182 case 'q':
183 exited = 1;
184 break;
185 case 'l':
186 ffjmp(ffarg(1) * JMP1, 1);
187 break;
188 case 'h':
189 ffjmp(-ffarg(1) * JMP1, 1);
190 break;
191 case 'j':
192 ffjmp(ffarg(1) * JMP2, 1);
193 break;
194 case 'k':
195 ffjmp(-ffarg(1) * JMP2, 1);
196 break;
197 case 'J':
198 ffjmp(ffarg(1) * JMP3, 1);
199 break;
200 case 'K':
201 ffjmp(-ffarg(1) * JMP3, 1);
202 break;
203 case '%':
204 if (arg)
205 ffjmp(100, 0);
206 break;
207 case 'i':
208 printinfo();
209 break;
210 case ' ':
211 case 'p':
212 if (audio && paused)
213 if (oss_open())
214 break;
215 if (audio && !paused)
216 oss_close();
217 paused = !paused;
218 sync_cur = sync_cnt;
219 break;
220 case '-':
221 sync_diff = -ffarg(0);
222 break;
223 case '+':
224 sync_diff = ffarg(0);
225 break;
226 case 'a':
227 sync_diff = ffs_avdiff(vffs, affs);
228 break;
229 case 'c':
230 sync_cnt = ffarg(0);
231 break;
232 case 's':
233 sync_cur = ffarg(sync_cnt);
234 break;
235 case 27:
236 arg = 0;
237 break;
238 default:
239 if (isdigit(c))
240 arg = arg * 10 + c - '0';
245 /* return nonzero if one more video frame can be decoded */
246 static int vsync(void)
248 if (sync_period && sync_since++ >= sync_period) {
249 sync_cur = sync_cnt;
250 sync_since = 0;
252 if (sync_first) {
253 sync_cur = 0;
254 if (sync_first < vnum) {
255 sync_first = 0;
256 sync_diff = ffs_avdiff(vffs, affs);
259 if (sync_cur > 0) {
260 sync_cur--;
261 return ffs_avdiff(vffs, affs) >= sync_diff;
263 ffs_wait(vffs);
264 return 1;
267 static void mainloop(void)
269 int eof = 0;
270 while (eof < audio + video) {
271 execkey();
272 if (exited)
273 break;
274 if (paused) {
275 a_doreset(1);
276 waitkey();
277 continue;
279 while (audio && !eof && !a_prodwait()) {
280 int ret = ffs_adec(affs, a_buf[a_prod], ABUFSZ);
281 if (ret < 0)
282 eof++;
283 if (ret > 0) {
284 a_len[a_prod] = ret;
285 a_prod = (a_prod + 1) & (AUDIOBUFS - 1);
288 if (video && (!audio || eof || vsync())) {
289 int ignore = jump && (vnum % (jump + 1));
290 void *buf;
291 int ret = ffs_vdec(vffs, ignore ? NULL : &buf);
292 vnum++;
293 if (ret < 0)
294 eof++;
295 if (ret > 0)
296 draw_frame((void *) buf, ret);
297 } else {
298 stroll();
301 exited = 1;
304 static void *process_audio(void *dat)
306 while (1) {
307 while (!a_reset && (a_conswait() || paused) && !exited)
308 stroll();
309 if (exited)
310 return NULL;
311 if (a_reset) {
312 if (a_reset == 1)
313 a_cons = a_prod;
314 a_reset = 0;
315 continue;
317 if (afd > 0) {
318 write(afd, a_buf[a_cons], a_len[a_cons]);
319 a_cons = (a_cons + 1) & (AUDIOBUFS - 1);
322 return NULL;
325 static void term_setup(void)
327 struct termios newtermios;
328 tcgetattr(0, &termios);
329 newtermios = termios;
330 newtermios.c_lflag &= ~ICANON;
331 newtermios.c_lflag &= ~ECHO;
332 tcsetattr(0, TCSAFLUSH, &newtermios);
333 fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);
336 static void term_cleanup(void)
338 tcsetattr(0, 0, &termios);
341 static void sigcont(int sig)
343 term_setup();
346 static char *usage = "usage: fbff [options] file\n"
347 "\noptions:\n"
348 " -z x zoom the screen using ffmpeg\n"
349 " -m x magnify the screen by repeating pixels\n"
350 " -j x jump every x video frames; for slow machines\n"
351 " -f start full screen\n"
352 " -v x select video stream; '-' disables video\n"
353 " -a x select audio stream; '-' disables audio\n"
354 " -s always synchronize; useful for files with bad video framerate\n"
355 " -u record avdiff after a few frames\n"
356 " -t use time based seeking; only if the default does't work\n"
357 " -r adjust the video to the right of the screen\n"
358 " -b adjust the video to the bottom of the screen\n\n";
360 static void read_args(int argc, char *argv[])
362 int i = 1;
363 while (i < argc) {
364 char *c = argv[i];
365 if (c[0] != '-')
366 break;
367 if (c[1] == 'm')
368 magnify = c[2] ? atoi(c + 2) : atoi(argv[++i]);
369 if (c[1] == 'z')
370 zoom = c[2] ? atof(c + 2) : atof(argv[++i]);
371 if (c[1] == 'j')
372 jump = c[2] ? atoi(c + 2) : atoi(argv[++i]);
373 if (c[1] == 'f')
374 fullscreen = 1;
375 if (c[1] == 's')
376 sync_period = c[2] ? atoi(c + 2) : 1;
377 if (c[1] == 't')
378 frame_jmp = 1024;
379 if (c[1] == 'h')
380 printf(usage);
381 if (c[1] == 'r')
382 rjust = 1;
383 if (c[1] == 'b')
384 bjust = 1;
385 if (c[1] == 'u')
386 sync_first = 32;
387 if (c[1] == 'v') {
388 char *arg = c[2] ? c + 2 : argv[++i];
389 video = arg[0] == '-' ? 0 : atoi(arg) + 2;
391 if (c[1] == 'a') {
392 char *arg = c[2] ? c + 2 : argv[++i];
393 audio = arg[0] == '-' ? 0 : atoi(arg) + 2;
395 i++;
399 int main(int argc, char *argv[])
401 pthread_t a_thread;
402 char *path = argv[argc - 1];
403 if (argc < 2) {
404 printf("usage: %s [options] filename\n", argv[0]);
405 return 1;
407 read_args(argc, argv);
408 ffs_globinit();
409 if (video && !(vffs = ffs_alloc(path, FFS_VIDEO | (video - 1))))
410 video = 0;
411 if (audio && !(affs = ffs_alloc(path, FFS_AUDIO | (audio - 1))))
412 audio = 0;
413 if (!video && !audio)
414 return 1;
415 if (audio) {
416 ffs_aconf(affs);
417 if (oss_open()) {
418 fprintf(stderr, "fbff: /dev/dsp busy?\n");
419 return 1;
421 pthread_create(&a_thread, NULL, process_audio, NULL);
423 if (video) {
424 int w, h;
425 if (fb_init())
426 return 1;
427 ffs_vinfo(vffs, &w, &h);
428 if (magnify != 1 && sizeof(fbval_t) != FBM_BPP(fb_mode()))
429 fprintf(stderr, "fbff: magnify != 1 and fbval_t doesn't match\n");
430 if (fullscreen) {
431 float hz = (float) fb_rows() / h / magnify;
432 float wz = (float) fb_cols() / w / magnify;
433 zoom = hz < wz ? hz : wz;
435 ffs_vconf(vffs, zoom, fb_mode());
437 term_setup();
438 signal(SIGCONT, sigcont);
439 mainloop();
440 term_cleanup();
442 if (video) {
443 fb_free();
444 ffs_free(vffs);
446 if (audio) {
447 pthread_join(a_thread, NULL);
448 oss_close();
449 ffs_free(affs);
451 return 0;