fbff: OSSDSP environment variable for OSS device
[fbff.git] / fbff.c
blobf850f129bbbb87c0930b3ef06b4f1dddab93d0e8
1 /*
2 * fbff - a small ffmpeg-based framebuffer/oss media player
4 * Copyright (C) 2009-2021 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 <sys/soundcard.h>
19 #include <pthread.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 static int paused;
27 static int exited;
28 static int domark;
29 static int dojump;
30 static int arg;
31 static char filename[32];
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, >1:idx */
38 static int audio = 1; /* audio stream; 0:none, 1:auto, >1:idx */
39 static int posx, posy; /* video position */
40 static int rjust, bjust; /* justify video to screen right/bottom */
41 static char *ossdsp; /* OSS device */
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 */
47 static long mark[256]; /* marks */
49 static int sync_diff; /* audio/video frame position diff */
50 static int sync_period; /* sync after every this many number of frames */
51 static int sync_since; /* frames since th last sync */
52 static int sync_cnt = 32; /* synchronization steps */
53 static int sync_cur; /* synchronization steps left */
54 static int sync_first; /* first frame to record sync_diff */
56 static void stroll(void)
58 usleep(10000);
61 static void draw_row(int rb, int cb, void *img, int cn)
63 int bpp = FBM_BPP(fb_mode());
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 memcpy(fb_mem(rb) + cb * bpp, img, cn * bpp);
76 static void draw_frame(void *img, int linelen)
78 int w, h, rn, cn, cb, rb;
79 int i, r, c, k;
80 int bpp = FBM_BPP(fb_mode());
81 ffs_vinfo(vffs, &w, &h);
82 rn = h * zoom;
83 cn = w * zoom;
84 cb = rjust ? fb_cols() - cn * magnify + posx : posx;
85 rb = bjust ? fb_rows() - rn * magnify + posy : posy;
86 if (magnify == 1) {
87 for (r = 0; r < rn; r++)
88 draw_row(rb + r, cb, img + r * linelen, cn);
89 } else {
90 char *brow = malloc(cn * magnify * bpp);
91 for (r = 0; r < rn; r++) {
92 char *src = img + r * linelen;
93 char *dst = brow;
94 for (c = 0; c < cn; c++)
95 for (i = 0; i < magnify; i++)
96 for (k = 0; k < bpp; k++)
97 *dst++ = src[c * bpp + k];
98 for (i = 0; i < magnify; i++)
99 draw_row((rb + r) * magnify + i, cb, brow, cn * magnify);
101 free(brow);
105 static int oss_open(void)
107 int rate, ch, bps;
108 int frag = 0x0003000b; /* 0xmmmmssss: 2^m fragments of size 2^s each */
109 afd = open(ossdsp ? ossdsp : "/dev/dsp", O_WRONLY);
110 if (afd < 0)
111 return 1;
112 ffs_ainfo(affs, &rate, &bps, &ch);
113 ioctl(afd, SOUND_PCM_WRITE_CHANNELS, &ch);
114 ioctl(afd, SOUND_PCM_WRITE_BITS, &bps);
115 ioctl(afd, SOUND_PCM_WRITE_RATE, &rate);
116 ioctl(afd, SOUND_PCM_SETFRAGMENT, &frag);
117 return 0;
120 static void oss_close(void)
122 if (afd > 0)
123 close(afd);
124 afd = 0;
127 /* audio buffers */
129 #define ABUFCNT (1 << 3) /* number of audio buffers */
130 #define ABUFLEN (1 << 18) /* audio buffer length */
132 static int a_cons;
133 static int a_prod;
134 static char a_buf[ABUFCNT][ABUFLEN];
135 static int a_len[ABUFCNT];
136 static int a_reset;
138 static int a_conswait(void)
140 return a_cons == a_prod;
143 static int a_prodwait(void)
145 return ((a_prod + 1) & (ABUFCNT - 1)) == a_cons;
148 static void a_doreset(int pause)
150 a_reset = 1 + pause;
151 while (audio && a_reset)
152 stroll();
155 /* subtitle handling */
157 #define SUBSCNT 2048 /* number of subtitles */
158 #define SUBSLEN 80 /* maximum subtitle length */
160 static char *sub_path; /* subtitles file */
161 static char sub_text[SUBSCNT][SUBSLEN]; /* subtitle text */
162 static long sub_beg[SUBSCNT]; /* printing position */
163 static long sub_end[SUBSCNT]; /* hiding position */
164 static int sub_n; /* subtitle count */
165 static int sub_last; /* last printed subtitle */
167 static void sub_read(void)
169 struct ffs *sffs = ffs_alloc(sub_path, FFS_SUBTS);
170 if (!sffs)
171 return;
172 while (sub_n < SUBSCNT && !ffs_sdec(sffs, &sub_text[sub_n][0], SUBSLEN,
173 &sub_beg[sub_n], &sub_end[sub_n])) {
174 sub_n++;
176 ffs_free(sffs);
179 static void sub_print(void)
181 struct ffs *ffs = video ? vffs : affs;
182 int l = 0;
183 int h = sub_n;
184 long pos = ffs_pos(ffs);
185 while (l < h) {
186 int m = (l + h) >> 1;
187 if (pos >= sub_beg[m] && pos <= sub_end[m]) {
188 if (sub_last != m)
189 printf("\r\33[K%s", sub_text[m]);
190 sub_last = m;
191 fflush(stdout);
192 return;
194 if (pos < sub_beg[m])
195 h = m;
196 else
197 l = m + 1;
199 if (sub_last >= 0) {
200 printf("\r\33[K");
201 fflush(stdout);
202 sub_last = -1;
206 /* fbff commands */
208 static int cmdread(void)
210 char b;
211 if (read(0, &b, 1) <= 0)
212 return -1;
213 return b;
216 static void cmdwait(void)
218 struct pollfd ufds[1];
219 ufds[0].fd = 0;
220 ufds[0].events = POLLIN;
221 poll(ufds, 1, -1);
224 static void cmdjmp(int n, int rel)
226 struct ffs *ffs = video ? vffs : affs;
227 long pos = (rel ? ffs_pos(ffs) : 0) + n * 1000;
228 a_doreset(0);
229 sync_cur = sync_cnt;
230 if (pos < 0)
231 pos = 0;
232 if (!rel)
233 mark['\''] = ffs_pos(ffs);
234 if (audio)
235 ffs_seek(affs, ffs, pos);
236 if (video)
237 ffs_seek(vffs, ffs, pos);
240 static void cmdinfo(void)
242 struct ffs *ffs = video ? vffs : affs;
243 long pos = ffs_pos(ffs);
244 long percent = ffs_duration(ffs) ? pos * 10 / (ffs_duration(ffs) / 100) : 0;
245 printf("\r\33[K%c %3ld.%01ld%% %3ld:%02ld.%01ld (AV:%4d) [%s] \r",
246 paused ? (afd < 0 ? '*' : ' ') : '>',
247 percent / 10, percent % 10,
248 pos / 60000, (pos % 60000) / 1000, (pos % 1000) / 100,
249 video && audio ? ffs_avdiff(vffs, affs) : 0,
250 filename);
251 fflush(stdout);
254 static int cmdarg(int def)
256 int n = arg;
257 arg = 0;
258 return n ? n : def;
261 static void cmdexec(void)
263 int c;
264 while ((c = cmdread()) >= 0) {
265 if (domark) {
266 domark = 0;
267 mark[c] = ffs_pos(video ? vffs : affs);
268 continue;
270 if (dojump) {
271 dojump = 0;
272 if (mark[c] > 0)
273 cmdjmp(mark[c] / 1000, 0);
274 continue;
276 switch (c) {
277 case 'q':
278 exited = 1;
279 break;
280 case 'l':
281 cmdjmp(cmdarg(1) * 10, 1);
282 break;
283 case 'h':
284 cmdjmp(-cmdarg(1) * 10, 1);
285 break;
286 case 'j':
287 cmdjmp(cmdarg(1) * 60, 1);
288 break;
289 case 'k':
290 cmdjmp(-cmdarg(1) * 60, 1);
291 break;
292 case 'J':
293 cmdjmp(cmdarg(1) * 600, 1);
294 break;
295 case 'K':
296 cmdjmp(-cmdarg(1) * 600, 1);
297 break;
298 case 'G':
299 cmdjmp(cmdarg(0) * 60, 0);
300 break;
301 case '%':
302 cmdjmp(cmdarg(0) * ffs_duration(vffs ? vffs : affs) / 100000, 0);
303 break;
304 case 'm':
305 domark = 1;
306 break;
307 case '\'':
308 dojump = 1;
309 break;
310 case 'i':
311 cmdinfo();
312 break;
313 case ' ':
314 case 'p':
315 if (audio && paused)
316 if (oss_open())
317 break;
318 if (audio && !paused)
319 oss_close();
320 paused = !paused;
321 sync_cur = sync_cnt;
322 break;
323 case '-':
324 sync_diff = -cmdarg(0);
325 break;
326 case '+':
327 sync_diff = cmdarg(0);
328 break;
329 case 'a':
330 sync_diff = ffs_avdiff(vffs, affs);
331 break;
332 case 'c':
333 sync_cnt = cmdarg(0);
334 break;
335 case 's':
336 sync_cur = cmdarg(sync_cnt);
337 break;
338 case 27:
339 arg = 0;
340 break;
341 default:
342 if (isdigit(c))
343 arg = arg * 10 + c - '0';
348 /* return nonzero if one more video frame can be decoded */
349 static int vsync(void)
351 if (sync_period && sync_since++ >= sync_period) {
352 sync_cur = sync_cnt;
353 sync_since = 0;
355 if (sync_first) {
356 sync_cur = 0;
357 if (sync_first < vnum) {
358 sync_first = 0;
359 sync_diff = ffs_avdiff(vffs, affs);
362 if (sync_cur > 0) {
363 sync_cur--;
364 return ffs_avdiff(vffs, affs) >= sync_diff;
366 ffs_wait(vffs);
367 return 1;
370 static void mainloop(void)
372 int eof = 0;
373 while (eof < audio + video) {
374 cmdexec();
375 if (exited)
376 break;
377 if (paused) {
378 a_doreset(1);
379 cmdwait();
380 continue;
382 while (audio && !eof && !a_prodwait()) {
383 int ret = ffs_adec(affs, a_buf[a_prod], ABUFLEN);
384 if (ret < 0)
385 eof++;
386 if (ret > 0) {
387 a_len[a_prod] = ret;
388 a_prod = (a_prod + 1) & (ABUFCNT - 1);
391 if (video && (!audio || eof || vsync())) {
392 int ignore = jump && (vnum % (jump + 1));
393 void *buf;
394 int ret = ffs_vdec(vffs, ignore ? NULL : &buf);
395 vnum++;
396 if (ret < 0)
397 eof++;
398 if (ret > 0)
399 draw_frame((void *) buf, ret);
400 sub_print();
401 } else {
402 stroll();
405 exited = 1;
408 static void *process_audio(void *dat)
410 while (1) {
411 while (!a_reset && (a_conswait() || paused) && !exited)
412 stroll();
413 if (exited)
414 return NULL;
415 if (a_reset) {
416 if (a_reset == 1)
417 a_cons = a_prod;
418 a_reset = 0;
419 continue;
421 if (afd > 0) {
422 write(afd, a_buf[a_cons], a_len[a_cons]);
423 a_cons = (a_cons + 1) & (ABUFCNT - 1);
426 return NULL;
429 static char *usage = "usage: fbff [options] file\n"
430 "\noptions:\n"
431 " -z n zoom the video\n"
432 " -m n magnify the video by duplicating pixels\n"
433 " -j n jump every n video frames; for slow machines\n"
434 " -f start full screen\n"
435 " -v n select video stream; '-' disables video\n"
436 " -a n select audio stream; '-' disables audio\n"
437 " -s always synchronize (-sx for every x frames)\n"
438 " -u record A/V delay after the first few frames\n"
439 " -t path subtitles file\n"
440 " -x n horizontal video position\n"
441 " -y n vertical video position\n"
442 " -r adjust the video to the right of the screen\n"
443 " -b adjust the video to the bottom of the screen\n\n";
445 static void read_args(int argc, char *argv[])
447 int i = 1;
448 while (i < argc) {
449 char *c = argv[i];
450 if (c[0] != '-')
451 break;
452 if (c[1] == 'm')
453 magnify = c[2] ? atoi(c + 2) : atoi(argv[++i]);
454 if (c[1] == 'z')
455 zoom = c[2] ? atof(c + 2) : atof(argv[++i]);
456 if (c[1] == 'j')
457 jump = c[2] ? atoi(c + 2) : atoi(argv[++i]);
458 if (c[1] == 'f')
459 fullscreen = 1;
460 if (c[1] == 's')
461 sync_period = c[2] ? atoi(c + 2) : 1;
462 if (c[1] == 't')
463 sub_path = c[2] ? c + 2 : argv[++i];
464 if (c[1] == 'h')
465 printf(usage);
466 if (c[1] == 'x')
467 posx = c[2] ? atoi(c + 2) : atoi(argv[++i]);
468 if (c[1] == 'y')
469 posy = c[2] ? atoi(c + 2) : atoi(argv[++i]);
470 if (c[1] == 'r')
471 rjust = 1;
472 if (c[1] == 'b')
473 bjust = 1;
474 if (c[1] == 'u')
475 sync_first = 32;
476 if (c[1] == 'v') {
477 char *arg = c[2] ? c + 2 : argv[++i];
478 video = arg[0] == '-' ? 0 : atoi(arg) + 2;
480 if (c[1] == 'a') {
481 char *arg = c[2] ? c + 2 : argv[++i];
482 audio = arg[0] == '-' ? 0 : atoi(arg) + 2;
484 i++;
488 static void term_init(struct termios *termios)
490 struct termios newtermios;
491 tcgetattr(0, termios);
492 newtermios = *termios;
493 newtermios.c_lflag &= ~ICANON;
494 newtermios.c_lflag &= ~ECHO;
495 tcsetattr(0, TCSAFLUSH, &newtermios);
496 fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);
499 static void term_done(struct termios *termios)
501 tcsetattr(0, 0, termios);
504 int main(int argc, char *argv[])
506 struct termios termios;
507 pthread_t a_thread;
508 char *path = argv[argc - 1];
509 char *fbdev = getenv("FBDEV");
510 if (argc < 2) {
511 printf("usage: %s [-u -s60 ...] file\n", argv[0]);
512 return 1;
514 ossdsp = getenv("OSSDSP");
515 read_args(argc, argv);
516 ffs_globinit();
517 snprintf(filename, sizeof(filename), "%s", path);
518 if (video && !(vffs = ffs_alloc(path, FFS_VIDEO | (video - 1))))
519 video = 0;
520 if (audio && !(affs = ffs_alloc(path, FFS_AUDIO | (audio - 1))))
521 audio = 0;
522 if (!video && !audio)
523 return 1;
524 if (sub_path)
525 sub_read();
526 if (audio) {
527 ffs_aconf(affs);
528 if (oss_open()) {
529 fprintf(stderr, "fbff: /dev/dsp busy?\n");
530 return 1;
532 pthread_create(&a_thread, NULL, process_audio, NULL);
534 if (video) {
535 int w, h;
536 if (fb_init(fbdev))
537 return 1;
538 ffs_vinfo(vffs, &w, &h);
539 if (fullscreen) {
540 float hz = (float) fb_rows() / h / magnify;
541 float wz = (float) fb_cols() / w / magnify;
542 zoom = hz < wz ? hz : wz;
544 ffs_vconf(vffs, zoom, fb_mode());
546 term_init(&termios);
547 mainloop();
548 term_done(&termios);
549 printf("\n");
551 if (video) {
552 fb_free();
553 ffs_free(vffs);
555 if (audio) {
556 pthread_join(a_thread, NULL);
557 oss_close();
558 ffs_free(affs);
560 return 0;