fbff: show a more specific error message if OSSDSP is missing
[fbff.git] / fbff.c
blobed64124605777c1d2d68fadbd2db931565f2cb5f
1 /*
2 * fbff - a small ffmpeg-based framebuffer/oss media player
4 * Copyright (C) 2009-2024 Ali Gholami Rudi
6 * This program is released under the Modified BSD license.
7 */
8 #include <ctype.h>
9 #include <errno.h>
10 #include <fcntl.h>
11 #include <poll.h>
12 #include <pty.h>
13 #include <signal.h>
14 #include <stdint.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <termios.h>
19 #include <unistd.h>
20 #include <sys/soundcard.h>
21 #include <pthread.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 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 */
43 static int nodraw; /* stop drawing */
44 static char *ossdsp; /* OSS device */
46 static struct ffs *affs; /* audio ffmpeg stream */
47 static struct ffs *vffs; /* video ffmpeg stream */
48 static int afd; /* oss fd */
49 static int vnum; /* decoded video frame count */
50 static long mark[256]; /* marks */
52 static int sync_diff; /* audio/video frame position diff */
53 static int sync_period; /* sync after every this many number of frames */
54 static int sync_since; /* frames since th last sync */
55 static int sync_cnt = 32; /* synchronization steps */
56 static int sync_cur; /* synchronization steps left */
57 static int sync_first; /* first frame to record sync_diff */
59 static void stroll(void)
61 usleep(10000);
64 static void draw_row(int rb, int cb, void *img, int cn)
66 int bpp = FBM_BPP(fb_mode());
67 if (rb < 0 || rb >= fb_rows())
68 return;
69 if (cb < 0) {
70 cn = -cb < cn ? cn + cb : 0;
71 img += -cb;
72 cb = 0;
74 if (cb + cn >= fb_cols())
75 cn = cb < fb_cols() ? fb_cols() - cb : 0;
76 memcpy(fb_mem(rb) + cb * bpp, img, cn * bpp);
79 static void draw_frame(void *img, int linelen)
81 int w, h, rn, cn, cb, rb;
82 int i, r, c, k;
83 int bpp = FBM_BPP(fb_mode());
84 if (nodraw)
85 return;
86 ffs_vinfo(vffs, &w, &h);
87 rn = h * zoom;
88 cn = w * zoom;
89 cb = rjust ? fb_cols() - cn * magnify + posx : posx;
90 rb = bjust ? fb_rows() - rn * magnify + posy : posy;
91 if (magnify == 1) {
92 for (r = 0; r < rn; r++)
93 draw_row(rb + r, cb, img + r * linelen, cn);
94 } else {
95 char *brow = malloc(cn * magnify * bpp);
96 for (r = 0; r < rn; r++) {
97 char *src = img + r * linelen;
98 char *dst = brow;
99 for (c = 0; c < cn; c++)
100 for (i = 0; i < magnify; i++)
101 for (k = 0; k < bpp; k++)
102 *dst++ = src[c * bpp + k];
103 for (i = 0; i < magnify; i++)
104 draw_row((rb + r) * magnify + i, cb, brow, cn * magnify);
106 free(brow);
110 static int oss_open(void)
112 int rate, ch, bps;
113 int frag = 0x0003000b; /* 0xmmmmssss: 2^m fragments of size 2^s each */
114 afd = open(ossdsp, O_WRONLY);
115 if (afd < 0)
116 return errno;
117 ffs_ainfo(affs, &rate, &bps, &ch);
118 ioctl(afd, SOUND_PCM_WRITE_CHANNELS, &ch);
119 ioctl(afd, SOUND_PCM_WRITE_BITS, &bps);
120 ioctl(afd, SOUND_PCM_WRITE_RATE, &rate);
121 ioctl(afd, SOUND_PCM_SETFRAGMENT, &frag);
122 return 0;
125 static void oss_close(void)
127 if (afd > 0)
128 close(afd);
129 afd = 0;
132 /* audio buffers */
134 #define ABUFCNT (1 << 3) /* number of audio buffers */
135 #define ABUFLEN (1 << 18) /* audio buffer length */
137 static int a_cons;
138 static int a_prod;
139 static char a_buf[ABUFCNT][ABUFLEN];
140 static int a_len[ABUFCNT];
141 static int a_reset;
143 static int a_conswait(void)
145 return a_cons == a_prod;
148 static int a_prodwait(void)
150 return ((a_prod + 1) & (ABUFCNT - 1)) == a_cons;
153 static void a_doreset(int pause)
155 a_reset = 1 + pause;
156 while (audio && a_reset)
157 stroll();
160 /* subtitle handling */
162 #define SUBSCNT 2048 /* number of subtitles */
163 #define SUBSLEN 80 /* maximum subtitle length */
165 static char *sub_path; /* subtitles file */
166 static char sub_text[SUBSCNT][SUBSLEN]; /* subtitle text */
167 static long sub_beg[SUBSCNT]; /* printing position */
168 static long sub_end[SUBSCNT]; /* hiding position */
169 static int sub_n; /* subtitle count */
170 static int sub_last; /* last printed subtitle */
172 static void sub_read(void)
174 struct ffs *sffs = ffs_alloc(sub_path, FFS_SUBTS);
175 if (!sffs)
176 return;
177 while (sub_n < SUBSCNT && !ffs_sdec(sffs, sub_text[sub_n], SUBSLEN,
178 &sub_beg[sub_n], &sub_end[sub_n])) {
179 sub_n++;
181 ffs_free(sffs);
184 static void sub_print(void)
186 struct ffs *ffs = video ? vffs : affs;
187 int l = 0;
188 int h = sub_n;
189 long pos = ffs_pos(ffs);
190 while (l < h) {
191 int m = (l + h) >> 1;
192 if (pos >= sub_beg[m] && pos <= sub_end[m]) {
193 if (sub_last != m)
194 printf("\r\33[K%s", sub_text[m]);
195 sub_last = m;
196 fflush(stdout);
197 return;
199 if (pos < sub_beg[m])
200 h = m;
201 else
202 l = m + 1;
204 if (sub_last >= 0) {
205 printf("\r\33[K");
206 fflush(stdout);
207 sub_last = -1;
211 /* fbff commands */
213 static int cmdread(void)
215 char b;
216 if (read(0, &b, 1) <= 0)
217 return -1;
218 return b;
221 static void cmdwait(void)
223 struct pollfd ufds[1];
224 ufds[0].fd = 0;
225 ufds[0].events = POLLIN;
226 poll(ufds, 1, -1);
229 static void cmdjmp(int n, int rel)
231 struct ffs *ffs = video ? vffs : affs;
232 long pos = (rel ? ffs_pos(ffs) : 0) + n * 1000;
233 a_doreset(0);
234 sync_cur = sync_cnt;
235 if (pos < 0)
236 pos = 0;
237 if (!rel)
238 mark['\''] = ffs_pos(ffs);
239 if (audio)
240 ffs_seek(affs, ffs, pos);
241 if (video)
242 ffs_seek(vffs, ffs, pos);
245 static void cmdinfo(void)
247 struct ffs *ffs = video ? vffs : affs;
248 long pos = ffs_pos(ffs);
249 long percent = ffs_duration(ffs) ? pos * 10 / (ffs_duration(ffs) / 100) : 0;
250 printf("\r\33[K%c %3ld.%01ld%% %3ld:%02ld.%01ld (AV:%4d) [%s] \r",
251 paused ? (afd < 0 ? '*' : ' ') : '>',
252 percent / 10, percent % 10,
253 pos / 60000, (pos % 60000) / 1000, (pos % 1000) / 100,
254 video && audio ? ffs_avdiff(vffs, affs) : 0,
255 filename);
256 fflush(stdout);
259 static int cmdarg(int def)
261 int n = arg;
262 arg = 0;
263 return n ? n : def;
266 static void cmdexec(void)
268 int c;
269 while ((c = cmdread()) >= 0) {
270 if (domark) {
271 domark = 0;
272 mark[c] = ffs_pos(video ? vffs : affs);
273 continue;
275 if (dojump) {
276 dojump = 0;
277 if (mark[c] > 0)
278 cmdjmp(mark[c] / 1000, 0);
279 continue;
281 switch (c) {
282 case 'q':
283 exited = 1;
284 break;
285 case 'l':
286 cmdjmp(cmdarg(1) * 10, 1);
287 break;
288 case 'h':
289 cmdjmp(-cmdarg(1) * 10, 1);
290 break;
291 case 'j':
292 cmdjmp(cmdarg(1) * 60, 1);
293 break;
294 case 'k':
295 cmdjmp(-cmdarg(1) * 60, 1);
296 break;
297 case 'J':
298 cmdjmp(cmdarg(1) * 600, 1);
299 break;
300 case 'K':
301 cmdjmp(-cmdarg(1) * 600, 1);
302 break;
303 case 'G':
304 cmdjmp(cmdarg(0) * 60, 0);
305 break;
306 case '%':
307 cmdjmp(cmdarg(0) * ffs_duration(vffs ? vffs : affs) / 100000, 0);
308 break;
309 case 'm':
310 domark = 1;
311 break;
312 case '\'':
313 dojump = 1;
314 break;
315 case 'i':
316 cmdinfo();
317 break;
318 case ' ':
319 case 'p':
320 if (audio && paused)
321 if (oss_open())
322 break;
323 if (audio && !paused)
324 oss_close();
325 paused = !paused;
326 sync_cur = sync_cnt;
327 break;
328 case '-':
329 sync_diff = -cmdarg(0);
330 break;
331 case '+':
332 sync_diff = cmdarg(0);
333 break;
334 case 'a':
335 sync_diff = ffs_avdiff(vffs, affs);
336 break;
337 case 'c':
338 sync_cnt = cmdarg(0);
339 break;
340 case 's':
341 sync_cur = cmdarg(sync_cnt);
342 break;
343 case 27:
344 arg = 0;
345 break;
346 default:
347 if (isdigit(c))
348 arg = arg * 10 + c - '0';
353 /* return nonzero if one more video frame can be decoded */
354 static int vsync(void)
356 if (sync_period && sync_since++ >= sync_period) {
357 sync_cur = sync_cnt;
358 sync_since = 0;
360 if (sync_first) {
361 sync_cur = 0;
362 if (sync_first < vnum) {
363 sync_first = 0;
364 sync_diff = ffs_avdiff(vffs, affs);
367 if (sync_cur > 0) {
368 sync_cur--;
369 return ffs_avdiff(vffs, affs) >= sync_diff;
371 ffs_wait(vffs);
372 return 1;
375 static void mainloop(void)
377 int eof = 0;
378 while (eof < audio + video) {
379 cmdexec();
380 if (exited)
381 break;
382 if (paused) {
383 a_doreset(1);
384 cmdwait();
385 continue;
387 while (audio && !eof && !a_prodwait()) {
388 int ret = ffs_adec(affs, a_buf[a_prod], ABUFLEN);
389 if (ret < 0)
390 eof++;
391 if (ret > 0) {
392 a_len[a_prod] = ret;
393 a_prod = (a_prod + 1) & (ABUFCNT - 1);
396 if (video && (!audio || eof || vsync())) {
397 int ignore = jump && (vnum % (jump + 1));
398 void *buf;
399 int ret = ffs_vdec(vffs, ignore ? NULL : &buf);
400 vnum++;
401 if (ret < 0)
402 eof++;
403 if (ret > 0)
404 draw_frame((void *) buf, ret);
405 sub_print();
406 } else {
407 stroll();
410 exited = 1;
413 static void *process_audio(void *dat)
415 while (1) {
416 while (!a_reset && (a_conswait() || paused) && !exited)
417 stroll();
418 if (exited)
419 return NULL;
420 if (a_reset) {
421 if (a_reset == 1)
422 a_cons = a_prod;
423 a_reset = 0;
424 continue;
426 if (afd > 0) {
427 write(afd, a_buf[a_cons], a_len[a_cons]);
428 a_cons = (a_cons + 1) & (ABUFCNT - 1);
431 return NULL;
434 static char *usage = "usage: fbff [options] file\n"
435 "\noptions:\n"
436 " -z n zoom the video\n"
437 " -m n magnify the video by duplicating pixels\n"
438 " -j n jump every n video frames; for slow machines\n"
439 " -f start full screen\n"
440 " -v n select video stream; '-' disables video\n"
441 " -a n select audio stream; '-' disables audio\n"
442 " -s always synchronize (-sx for every x frames)\n"
443 " -u record A/V delay after the first few frames\n"
444 " -t path subtitles file\n"
445 " -x n horizontal video position\n"
446 " -y n vertical video position\n"
447 " -r adjust the video to the right of the screen\n"
448 " -b adjust the video to the bottom of the screen\n\n";
450 static void read_args(int argc, char *argv[])
452 int i = 1;
453 while (i < argc) {
454 char *c = argv[i];
455 if (c[0] != '-')
456 break;
457 if (c[1] == 'm')
458 magnify = c[2] ? atoi(c + 2) : atoi(argv[++i]);
459 if (c[1] == 'z')
460 zoom = c[2] ? atof(c + 2) : atof(argv[++i]);
461 if (c[1] == 'j')
462 jump = c[2] ? atoi(c + 2) : atoi(argv[++i]);
463 if (c[1] == 'f')
464 fullscreen = 1;
465 if (c[1] == 's')
466 sync_period = c[2] ? atoi(c + 2) : 1;
467 if (c[1] == 't')
468 sub_path = c[2] ? c + 2 : argv[++i];
469 if (c[1] == 'h')
470 printf(usage);
471 if (c[1] == 'x')
472 posx = c[2] ? atoi(c + 2) : atoi(argv[++i]);
473 if (c[1] == 'y')
474 posy = c[2] ? atoi(c + 2) : atoi(argv[++i]);
475 if (c[1] == 'r')
476 rjust = 1;
477 if (c[1] == 'b')
478 bjust = 1;
479 if (c[1] == 'u')
480 sync_first = 32;
481 if (c[1] == 'v') {
482 char *arg = c[2] ? c + 2 : argv[++i];
483 video = arg[0] == '-' ? 0 : atoi(arg) + 2;
485 if (c[1] == 'a') {
486 char *arg = c[2] ? c + 2 : argv[++i];
487 audio = arg[0] == '-' ? 0 : atoi(arg) + 2;
489 i++;
493 static void term_init(struct termios *termios)
495 struct termios newtermios;
496 tcgetattr(0, termios);
497 newtermios = *termios;
498 newtermios.c_lflag &= ~ICANON;
499 newtermios.c_lflag &= ~ECHO;
500 tcsetattr(0, TCSAFLUSH, &newtermios);
501 fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);
504 static void term_done(struct termios *termios)
506 tcsetattr(0, 0, termios);
509 static void signalreceived(int sig)
511 if (sig == SIGUSR1)
512 nodraw = 1;
513 if (sig == SIGUSR2)
514 nodraw = 0;
517 int main(int argc, char *argv[])
519 struct termios termios;
520 pthread_t a_thread;
521 char *path = argv[argc - 1];
522 char *fbdev = getenv("FBDEV");
523 if (argc < 2) {
524 printf("usage: %s [-u -s60 ...] file\n", argv[0]);
525 return 1;
527 ossdsp = getenv("OSSDSP") ? getenv("OSSDSP") : "/dev/dsp";
528 read_args(argc, argv);
529 ffs_globinit();
530 snprintf(filename, sizeof(filename), "%s", path);
531 if (video && !(vffs = ffs_alloc(path, FFS_VIDEO | (video - 1))))
532 video = 0;
533 if (audio && !(affs = ffs_alloc(path, FFS_AUDIO | (audio - 1))))
534 audio = 0;
535 if (!video && !audio)
536 return 1;
537 if (sub_path)
538 sub_read();
539 if (audio) {
540 int err = oss_open();
541 ffs_aconf(affs);
542 if (err == ENOENT)
543 fprintf(stderr, "fbff: %s missing?\n", ossdsp);
544 else
545 fprintf(stderr, "fbff: %s busy?\n", ossdsp);
546 if (err != 0)
547 return 1;
548 pthread_create(&a_thread, NULL, process_audio, NULL);
550 if (video) {
551 int w, h;
552 if (fb_init(fbdev))
553 return 1;
554 ffs_vinfo(vffs, &w, &h);
555 if (fullscreen) {
556 float hz = (float) fb_rows() / h / magnify;
557 float wz = (float) fb_cols() / w / magnify;
558 zoom = hz < wz ? hz : wz;
560 ffs_vconf(vffs, zoom, fb_mode());
562 if (getenv("TERM_PGID") != NULL && atoi(getenv("TERM_PGID")) == getppid())
563 if (tcsetpgrp(0, getppid()) == 0)
564 setpgid(0, getppid());
565 term_init(&termios);
566 signal(SIGUSR1, signalreceived);
567 signal(SIGUSR2, signalreceived);
568 mainloop();
569 term_done(&termios);
570 printf("\n");
572 if (video) {
573 fb_free();
574 ffs_free(vffs);
576 if (audio) {
577 pthread_join(a_thread, NULL);
578 oss_close();
579 ffs_free(affs);
581 return 0;