Ported from OSS to ALSA, via tinyalsa.
[fbff.git] / fbff.c
blob4d221d078b367ed2389128b706edde3a04ce16e4
1 /*
2 * fbff - a small ffmpeg-based framebuffer/oss media player
4 * Copyright (C) 2009-2015 Ali Gholami Rudi
6 * This program is released under the Modified BSD license.
7 */
8 #include <ctype.h>
9 #include <fcntl.h>
10 #include <pty.h>
11 #include <stdint.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <termios.h>
16 #include <unistd.h>
17 #include <sys/poll.h>
18 #include <pthread.h>
19 #include <tinyalsa/asoundlib.h>
20 #include "ffs.h"
21 #include "draw.h"
23 #define MIN(a, b) ((a) < (b) ? (a) : (b))
24 #define MAX(a, b) ((a) > (b) ? (a) : (b))
26 typedef unsigned int fbval_t; /* framebuffer depth */
28 static int paused;
29 static int exited;
30 static int domark;
31 static int dojump;
32 static int arg;
33 static char filename[32];
35 static float zoom = 1;
36 static int magnify = 1;
37 static int jump = 0;
38 static int fullscreen = 0;
39 static int video = 1; /* video stream; 0:none, 1:auto, >1:idx */
40 static int audio = 1; /* audio stream; 0:none, 1:auto, >1:idx */
41 static int posx, posy; /* video position */
42 static int rjust, bjust; /* justify video to screen right/bottom */
44 static struct ffs *affs; /* audio ffmpeg stream */
45 static struct ffs *vffs; /* video ffmpeg stream */
46 struct pcm *card; /* alsa card */
47 static int vnum; /* decoded video frame count */
48 static long mark[256]; /* marks */
50 static int sync_diff; /* audio/video frame position diff */
51 static int sync_period; /* sync after every this many number of frames */
52 static int sync_since; /* frames since th last sync */
53 static int sync_cnt = 32; /* synchronization steps */
54 static int sync_cur; /* synchronization steps left */
55 static int sync_first; /* first frame to record sync_diff */
57 static void stroll(void)
59 usleep(10000);
62 static void draw_row(int rb, int cb, void *img, int cn)
64 if (rb < 0 || rb >= fb_rows())
65 return;
66 if (cb < 0) {
67 cn = -cb < cn ? cn + cb : 0;
68 img += -cb;
69 cb = 0;
71 if (cb + cn >= fb_cols())
72 cn = cb < fb_cols() ? fb_cols() - cb : 0;
73 fb_set(rb, cb, img, cn);
76 static void draw_frame(void *img, int linelen)
78 int w, h, rn, cn, cb, rb;
79 int i, r, c;
80 ffs_vinfo(vffs, &w, &h);
81 rn = h * zoom;
82 cn = w * zoom;
83 cb = rjust ? fb_cols() - cn * magnify + posx : posx;
84 rb = bjust ? fb_rows() - rn * magnify + posy : posy;
85 if (magnify == 1) {
86 for (r = 0; r < rn; r++)
87 draw_row(rb + r, cb, img + r * linelen, cn);
88 } else {
89 fbval_t *brow = malloc(cn * magnify * sizeof(fbval_t));
90 for (r = 0; r < rn; r++) {
91 fbval_t *row = img + r * linelen;
92 for (c = 0; c < cn; c++)
93 for (i = 0; i < magnify; i++)
94 brow[c * magnify + i] = row[c];
95 for (i = 0; i < magnify; i++)
96 draw_row((rb + r) * magnify + i, cb, brow, cn * magnify);
98 free(brow);
102 static int oss_open(void)
104 struct pcm_config cfg;
105 int rate, ch, bps;
107 ffs_ainfo(affs, &rate, &bps, &ch);
109 cfg.channels = (unsigned int) ch;
110 cfg.rate = (unsigned int)rate;
111 cfg.period_size = 1024;
112 cfg.period_count = 4;
113 switch (bps) {
114 case 8:
115 cfg.format = PCM_FORMAT_S8;
116 break;
117 case 16:
118 cfg.format = PCM_FORMAT_S16_LE;
119 break;
120 case 24:
121 cfg.format = PCM_FORMAT_S24_LE;
122 break;
123 case 32:
124 cfg.format = PCM_FORMAT_S32_LE;
125 break;
126 default:
127 return 1;
129 cfg.start_threshold = 0;
130 cfg.stop_threshold = 0;
131 cfg.silence_threshold = 0;
132 card = pcm_open(0, 0, PCM_OUT, &cfg);
133 if (!card)
134 return 1;
136 return 0;
139 static void oss_close(void)
141 if (card)
142 pcm_close(card);
143 card = NULL;
146 /* audio buffers */
148 #define ABUFCNT (1 << 3) /* number of audio buffers */
149 #define ABUFLEN (1 << 18) /* audio buffer length */
151 static int a_cons;
152 static int a_prod;
153 static char a_buf[ABUFCNT][ABUFLEN];
154 static int a_len[ABUFCNT];
155 static int a_reset;
157 static int a_conswait(void)
159 return a_cons == a_prod;
162 static int a_prodwait(void)
164 return ((a_prod + 1) & (ABUFCNT - 1)) == a_cons;
167 static void a_doreset(int pause)
169 a_reset = 1 + pause;
170 while (audio && a_reset)
171 stroll();
174 /* subtitle handling */
176 #define SUBSCNT 2048 /* number of subtitles */
177 #define SUBSLEN 80 /* maximum subtitle length */
179 static char *sub_path; /* subtitles file */
180 static char sub_text[SUBSCNT][SUBSLEN]; /* subtitle text */
181 static long sub_beg[SUBSCNT]; /* printing position */
182 static long sub_end[SUBSCNT]; /* hiding position */
183 static int sub_n; /* subtitle count */
184 static int sub_last; /* last printed subtitle */
186 static void sub_read(void)
188 struct ffs *sffs = ffs_alloc(sub_path, FFS_SUBTS);
189 if (!sffs)
190 return;
191 while (sub_n < SUBSCNT && !ffs_sdec(sffs, &sub_text[sub_n][0], SUBSLEN,
192 &sub_beg[sub_n], &sub_end[sub_n])) {
193 sub_n++;
195 ffs_free(sffs);
198 static void sub_print(void)
200 struct ffs *ffs = video ? vffs : affs;
201 int l = 0;
202 int h = sub_n;
203 long pos = ffs_pos(ffs);
204 while (l < h) {
205 int m = (l + h) >> 1;
206 if (pos >= sub_beg[m] && pos <= sub_end[m]) {
207 if (sub_last != m)
208 printf("\r\33[K%s", sub_text[m]);
209 sub_last = m;
210 fflush(stdout);
211 return;
213 if (pos < sub_beg[m])
214 h = m;
215 else
216 l = m + 1;
218 if (sub_last >= 0) {
219 printf("\r\33[K");
220 fflush(stdout);
221 sub_last = -1;
225 /* fbff commands */
227 static int cmdread(void)
229 char b;
230 if (read(0, &b, 1) <= 0)
231 return -1;
232 return b;
235 static void cmdwait(void)
237 struct pollfd ufds[1];
238 ufds[0].fd = 0;
239 ufds[0].events = POLLIN;
240 poll(ufds, 1, -1);
243 static void cmdjmp(int n, int rel)
245 struct ffs *ffs = video ? vffs : affs;
246 long pos = (rel ? ffs_pos(ffs) : 0) + n * 1000;
247 a_doreset(0);
248 sync_cur = sync_cnt;
249 if (pos < 0)
250 pos = 0;
251 if (!rel)
252 mark['\''] = ffs_pos(ffs);
253 if (audio)
254 ffs_seek(affs, ffs, pos);
255 if (video)
256 ffs_seek(vffs, ffs, pos);
259 static void cmdinfo(void)
261 struct ffs *ffs = video ? vffs : affs;
262 long pos = ffs_pos(ffs);
263 long percent = ffs_duration(ffs) ? pos * 10 / (ffs_duration(ffs) / 100) : 0;
264 printf("\r\33[K%c %3ld.%01ld%% %3ld:%02ld.%01ld (AV:%4d) [%s] \r",
265 paused ? (card ? '*' : ' ') : '>',
266 percent / 10, percent % 10,
267 pos / 60000, (pos % 60000) / 1000, (pos % 1000) / 100,
268 video && audio ? ffs_avdiff(vffs, affs) : 0,
269 filename);
270 fflush(stdout);
273 static int cmdarg(int def)
275 int n = arg;
276 arg = 0;
277 return n ? n : def;
280 static void cmdexec(void)
282 int c;
283 while ((c = cmdread()) >= 0) {
284 if (domark) {
285 domark = 0;
286 mark[c] = ffs_pos(video ? vffs : affs);
287 continue;
289 if (dojump) {
290 dojump = 0;
291 if (mark[c] > 0)
292 cmdjmp(mark[c] / 1000, 0);
293 continue;
295 switch (c) {
296 case 'q':
297 exited = 1;
298 break;
299 case 'l':
300 cmdjmp(cmdarg(1) * 10, 1);
301 break;
302 case 'h':
303 cmdjmp(-cmdarg(1) * 10, 1);
304 break;
305 case 'j':
306 cmdjmp(cmdarg(1) * 60, 1);
307 break;
308 case 'k':
309 cmdjmp(-cmdarg(1) * 60, 1);
310 break;
311 case 'J':
312 cmdjmp(cmdarg(1) * 600, 1);
313 break;
314 case 'K':
315 cmdjmp(-cmdarg(1) * 600, 1);
316 break;
317 case 'G':
318 cmdjmp(cmdarg(0) * 60, 0);
319 break;
320 case '%':
321 cmdjmp(cmdarg(0) * ffs_duration(vffs ? vffs : affs) / 100000, 0);
322 break;
323 case 'm':
324 domark = 1;
325 break;
326 case '\'':
327 dojump = 1;
328 break;
329 case 'i':
330 cmdinfo();
331 break;
332 case ' ':
333 case 'p':
334 if (audio && paused)
335 if (oss_open())
336 break;
337 if (audio && !paused)
338 oss_close();
339 paused = !paused;
340 sync_cur = sync_cnt;
341 break;
342 case '-':
343 sync_diff = -cmdarg(0);
344 break;
345 case '+':
346 sync_diff = cmdarg(0);
347 break;
348 case 'a':
349 sync_diff = ffs_avdiff(vffs, affs);
350 break;
351 case 'c':
352 sync_cnt = cmdarg(0);
353 break;
354 case 's':
355 sync_cur = cmdarg(sync_cnt);
356 break;
357 case 27:
358 arg = 0;
359 break;
360 default:
361 if (isdigit(c))
362 arg = arg * 10 + c - '0';
367 /* return nonzero if one more video frame can be decoded */
368 static int vsync(void)
370 if (sync_period && sync_since++ >= sync_period) {
371 sync_cur = sync_cnt;
372 sync_since = 0;
374 if (sync_first) {
375 sync_cur = 0;
376 if (sync_first < vnum) {
377 sync_first = 0;
378 sync_diff = ffs_avdiff(vffs, affs);
381 if (sync_cur > 0) {
382 sync_cur--;
383 return ffs_avdiff(vffs, affs) >= sync_diff;
385 ffs_wait(vffs);
386 return 1;
389 static void mainloop(void)
391 int eof = 0;
392 while (eof < audio + video) {
393 cmdexec();
394 if (exited)
395 break;
396 if (paused) {
397 a_doreset(1);
398 cmdwait();
399 continue;
401 while (audio && !eof && !a_prodwait()) {
402 int ret = ffs_adec(affs, a_buf[a_prod], ABUFLEN);
403 if (ret < 0)
404 eof++;
405 if (ret > 0) {
406 a_len[a_prod] = ret;
407 a_prod = (a_prod + 1) & (ABUFCNT - 1);
410 if (video && (!audio || eof || vsync())) {
411 int ignore = jump && (vnum % (jump + 1));
412 void *buf;
413 int ret = ffs_vdec(vffs, ignore ? NULL : &buf);
414 vnum++;
415 if (ret < 0)
416 eof++;
417 if (ret > 0)
418 draw_frame((void *) buf, ret);
419 sub_print();
420 } else {
421 stroll();
424 exited = 1;
427 static void *process_audio(void *dat)
429 while (1) {
430 while (!a_reset && (a_conswait() || paused) && !exited)
431 stroll();
432 if (exited)
433 return NULL;
434 if (a_reset) {
435 if (a_reset == 1)
436 a_cons = a_prod;
437 a_reset = 0;
438 continue;
440 if (card) {
441 pcm_write(card, a_buf[a_cons], (unsigned int) a_len[a_cons]);
442 a_cons = (a_cons + 1) & (ABUFCNT - 1);
445 return NULL;
448 static char *usage = "usage: fbff [options] file\n"
449 "\noptions:\n"
450 " -z n zoom the video\n"
451 " -m n magnify the video by duplicating pixels\n"
452 " -j n jump every n video frames; for slow machines\n"
453 " -f start full screen\n"
454 " -v n select video stream; '-' disables video\n"
455 " -a n select audio stream; '-' disables audio\n"
456 " -s always synchronize (-sx for every x frames)\n"
457 " -u record A/V delay after the first few frames\n"
458 " -t path subtitles file\n"
459 " -x n horizontal video position\n"
460 " -y n vertical video position\n"
461 " -r adjust the video to the right of the screen\n"
462 " -b adjust the video to the bottom of the screen\n\n";
464 static void read_args(int argc, char *argv[])
466 int i = 1;
467 while (i < argc) {
468 char *c = argv[i];
469 if (c[0] != '-')
470 break;
471 if (c[1] == 'm')
472 magnify = c[2] ? atoi(c + 2) : atoi(argv[++i]);
473 if (c[1] == 'z')
474 zoom = c[2] ? atof(c + 2) : atof(argv[++i]);
475 if (c[1] == 'j')
476 jump = c[2] ? atoi(c + 2) : atoi(argv[++i]);
477 if (c[1] == 'f')
478 fullscreen = 1;
479 if (c[1] == 's')
480 sync_period = c[2] ? atoi(c + 2) : 1;
481 if (c[1] == 't')
482 sub_path = c[2] ? c + 2 : argv[++i];
483 if (c[1] == 'h')
484 printf(usage);
485 if (c[1] == 'x')
486 posx = c[2] ? atoi(c + 2) : atoi(argv[++i]);
487 if (c[1] == 'y')
488 posy = c[2] ? atoi(c + 2) : atoi(argv[++i]);
489 if (c[1] == 'r')
490 rjust = 1;
491 if (c[1] == 'b')
492 bjust = 1;
493 if (c[1] == 'u')
494 sync_first = 32;
495 if (c[1] == 'v') {
496 char *arg = c[2] ? c + 2 : argv[++i];
497 video = arg[0] == '-' ? 0 : atoi(arg) + 2;
499 if (c[1] == 'a') {
500 char *arg = c[2] ? c + 2 : argv[++i];
501 audio = arg[0] == '-' ? 0 : atoi(arg) + 2;
503 i++;
507 static void term_init(struct termios *termios)
509 struct termios newtermios;
510 tcgetattr(0, termios);
511 newtermios = *termios;
512 newtermios.c_lflag &= ~ICANON;
513 newtermios.c_lflag &= ~ECHO;
514 tcsetattr(0, TCSAFLUSH, &newtermios);
515 fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);
518 static void term_done(struct termios *termios)
520 tcsetattr(0, 0, termios);
523 int main(int argc, char *argv[])
525 struct termios termios;
526 pthread_t a_thread;
527 char *path = argv[argc - 1];
528 if (argc < 2) {
529 printf("usage: %s [-u -s60 ...] file\n", argv[0]);
530 return 1;
532 read_args(argc, argv);
533 ffs_globinit();
534 snprintf(filename, sizeof(filename), "%s", path);
535 if (video && !(vffs = ffs_alloc(path, FFS_VIDEO | (video - 1))))
536 video = 0;
537 if (audio && !(affs = ffs_alloc(path, FFS_AUDIO | (audio - 1))))
538 audio = 0;
539 if (!video && !audio)
540 return 1;
541 if (sub_path)
542 sub_read();
543 if (audio) {
544 ffs_aconf(affs);
545 if (oss_open()) {
546 fprintf(stderr, "fbff: /dev/dsp busy?\n");
547 return 1;
549 pthread_create(&a_thread, NULL, process_audio, NULL);
551 if (video) {
552 int w, h;
553 if (fb_init())
554 return 1;
555 ffs_vinfo(vffs, &w, &h);
556 if (magnify != 1 && sizeof(fbval_t) != FBM_BPP(fb_mode()))
557 fprintf(stderr, "fbff: fbval_t does not match\n");
558 if (fullscreen) {
559 float hz = (float) fb_rows() / h / magnify;
560 float wz = (float) fb_cols() / w / magnify;
561 zoom = hz < wz ? hz : wz;
563 ffs_vconf(vffs, zoom, fb_mode());
565 term_init(&termios);
566 mainloop();
567 term_done(&termios);
568 printf("\n");
570 if (video) {
571 fb_free();
572 ffs_free(vffs);
574 if (audio) {
575 pthread_join(a_thread, NULL);
576 oss_close();
577 ffs_free(affs);
579 return 0;