fbff: change process group if TERM_PGID is defined
[fbff.git] / fbff.c
blob7b2330bd06e581fcbd53923077d43d165fba3cc9
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 <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 "ffs.h"
22 #include "draw.h"
24 #define MIN(a, b) ((a) < (b) ? (a) : (b))
25 #define MAX(a, b) ((a) > (b) ? (a) : (b))
27 static int paused;
28 static int exited;
29 static int domark;
30 static int dojump;
31 static int arg;
32 static char filename[32];
34 static float zoom = 1;
35 static int magnify = 1;
36 static int jump = 0;
37 static int fullscreen = 0;
38 static int video = 1; /* video stream; 0:none, 1:auto, >1:idx */
39 static int audio = 1; /* audio stream; 0:none, 1:auto, >1:idx */
40 static int posx, posy; /* video position */
41 static int rjust, bjust; /* justify video to screen right/bottom */
42 static int nodraw; /* stop drawing */
43 static char *ossdsp; /* OSS device */
45 static struct ffs *affs; /* audio ffmpeg stream */
46 static struct ffs *vffs; /* video ffmpeg stream */
47 static int afd; /* oss fd */
48 static int vnum; /* decoded video frame count */
49 static long mark[256]; /* marks */
51 static int sync_diff; /* audio/video frame position diff */
52 static int sync_period; /* sync after every this many number of frames */
53 static int sync_since; /* frames since th last sync */
54 static int sync_cnt = 32; /* synchronization steps */
55 static int sync_cur; /* synchronization steps left */
56 static int sync_first; /* first frame to record sync_diff */
58 static void stroll(void)
60 usleep(10000);
63 static void draw_row(int rb, int cb, void *img, int cn)
65 int bpp = FBM_BPP(fb_mode());
66 if (rb < 0 || rb >= fb_rows())
67 return;
68 if (cb < 0) {
69 cn = -cb < cn ? cn + cb : 0;
70 img += -cb;
71 cb = 0;
73 if (cb + cn >= fb_cols())
74 cn = cb < fb_cols() ? fb_cols() - cb : 0;
75 memcpy(fb_mem(rb) + cb * bpp, img, cn * bpp);
78 static void draw_frame(void *img, int linelen)
80 int w, h, rn, cn, cb, rb;
81 int i, r, c, k;
82 int bpp = FBM_BPP(fb_mode());
83 if (nodraw)
84 return;
85 ffs_vinfo(vffs, &w, &h);
86 rn = h * zoom;
87 cn = w * zoom;
88 cb = rjust ? fb_cols() - cn * magnify + posx : posx;
89 rb = bjust ? fb_rows() - rn * magnify + posy : posy;
90 if (magnify == 1) {
91 for (r = 0; r < rn; r++)
92 draw_row(rb + r, cb, img + r * linelen, cn);
93 } else {
94 char *brow = malloc(cn * magnify * bpp);
95 for (r = 0; r < rn; r++) {
96 char *src = img + r * linelen;
97 char *dst = brow;
98 for (c = 0; c < cn; c++)
99 for (i = 0; i < magnify; i++)
100 for (k = 0; k < bpp; k++)
101 *dst++ = src[c * bpp + k];
102 for (i = 0; i < magnify; i++)
103 draw_row((rb + r) * magnify + i, cb, brow, cn * magnify);
105 free(brow);
109 static int oss_open(void)
111 int rate, ch, bps;
112 int frag = 0x0003000b; /* 0xmmmmssss: 2^m fragments of size 2^s each */
113 afd = open(ossdsp ? ossdsp : "/dev/dsp", O_WRONLY);
114 if (afd < 0)
115 return 1;
116 ffs_ainfo(affs, &rate, &bps, &ch);
117 ioctl(afd, SOUND_PCM_WRITE_CHANNELS, &ch);
118 ioctl(afd, SOUND_PCM_WRITE_BITS, &bps);
119 ioctl(afd, SOUND_PCM_WRITE_RATE, &rate);
120 ioctl(afd, SOUND_PCM_SETFRAGMENT, &frag);
121 return 0;
124 static void oss_close(void)
126 if (afd > 0)
127 close(afd);
128 afd = 0;
131 /* audio buffers */
133 #define ABUFCNT (1 << 3) /* number of audio buffers */
134 #define ABUFLEN (1 << 18) /* audio buffer length */
136 static int a_cons;
137 static int a_prod;
138 static char a_buf[ABUFCNT][ABUFLEN];
139 static int a_len[ABUFCNT];
140 static int a_reset;
142 static int a_conswait(void)
144 return a_cons == a_prod;
147 static int a_prodwait(void)
149 return ((a_prod + 1) & (ABUFCNT - 1)) == a_cons;
152 static void a_doreset(int pause)
154 a_reset = 1 + pause;
155 while (audio && a_reset)
156 stroll();
159 /* subtitle handling */
161 #define SUBSCNT 2048 /* number of subtitles */
162 #define SUBSLEN 80 /* maximum subtitle length */
164 static char *sub_path; /* subtitles file */
165 static char sub_text[SUBSCNT][SUBSLEN]; /* subtitle text */
166 static long sub_beg[SUBSCNT]; /* printing position */
167 static long sub_end[SUBSCNT]; /* hiding position */
168 static int sub_n; /* subtitle count */
169 static int sub_last; /* last printed subtitle */
171 static void sub_read(void)
173 struct ffs *sffs = ffs_alloc(sub_path, FFS_SUBTS);
174 if (!sffs)
175 return;
176 while (sub_n < SUBSCNT && !ffs_sdec(sffs, sub_text[sub_n], SUBSLEN,
177 &sub_beg[sub_n], &sub_end[sub_n])) {
178 sub_n++;
180 ffs_free(sffs);
183 static void sub_print(void)
185 struct ffs *ffs = video ? vffs : affs;
186 int l = 0;
187 int h = sub_n;
188 long pos = ffs_pos(ffs);
189 while (l < h) {
190 int m = (l + h) >> 1;
191 if (pos >= sub_beg[m] && pos <= sub_end[m]) {
192 if (sub_last != m)
193 printf("\r\33[K%s", sub_text[m]);
194 sub_last = m;
195 fflush(stdout);
196 return;
198 if (pos < sub_beg[m])
199 h = m;
200 else
201 l = m + 1;
203 if (sub_last >= 0) {
204 printf("\r\33[K");
205 fflush(stdout);
206 sub_last = -1;
210 /* fbff commands */
212 static int cmdread(void)
214 char b;
215 if (read(0, &b, 1) <= 0)
216 return -1;
217 return b;
220 static void cmdwait(void)
222 struct pollfd ufds[1];
223 ufds[0].fd = 0;
224 ufds[0].events = POLLIN;
225 poll(ufds, 1, -1);
228 static void cmdjmp(int n, int rel)
230 struct ffs *ffs = video ? vffs : affs;
231 long pos = (rel ? ffs_pos(ffs) : 0) + n * 1000;
232 a_doreset(0);
233 sync_cur = sync_cnt;
234 if (pos < 0)
235 pos = 0;
236 if (!rel)
237 mark['\''] = ffs_pos(ffs);
238 if (audio)
239 ffs_seek(affs, ffs, pos);
240 if (video)
241 ffs_seek(vffs, ffs, pos);
244 static void cmdinfo(void)
246 struct ffs *ffs = video ? vffs : affs;
247 long pos = ffs_pos(ffs);
248 long percent = ffs_duration(ffs) ? pos * 10 / (ffs_duration(ffs) / 100) : 0;
249 printf("\r\33[K%c %3ld.%01ld%% %3ld:%02ld.%01ld (AV:%4d) [%s] \r",
250 paused ? (afd < 0 ? '*' : ' ') : '>',
251 percent / 10, percent % 10,
252 pos / 60000, (pos % 60000) / 1000, (pos % 1000) / 100,
253 video && audio ? ffs_avdiff(vffs, affs) : 0,
254 filename);
255 fflush(stdout);
258 static int cmdarg(int def)
260 int n = arg;
261 arg = 0;
262 return n ? n : def;
265 static void cmdexec(void)
267 int c;
268 while ((c = cmdread()) >= 0) {
269 if (domark) {
270 domark = 0;
271 mark[c] = ffs_pos(video ? vffs : affs);
272 continue;
274 if (dojump) {
275 dojump = 0;
276 if (mark[c] > 0)
277 cmdjmp(mark[c] / 1000, 0);
278 continue;
280 switch (c) {
281 case 'q':
282 exited = 1;
283 break;
284 case 'l':
285 cmdjmp(cmdarg(1) * 10, 1);
286 break;
287 case 'h':
288 cmdjmp(-cmdarg(1) * 10, 1);
289 break;
290 case 'j':
291 cmdjmp(cmdarg(1) * 60, 1);
292 break;
293 case 'k':
294 cmdjmp(-cmdarg(1) * 60, 1);
295 break;
296 case 'J':
297 cmdjmp(cmdarg(1) * 600, 1);
298 break;
299 case 'K':
300 cmdjmp(-cmdarg(1) * 600, 1);
301 break;
302 case 'G':
303 cmdjmp(cmdarg(0) * 60, 0);
304 break;
305 case '%':
306 cmdjmp(cmdarg(0) * ffs_duration(vffs ? vffs : affs) / 100000, 0);
307 break;
308 case 'm':
309 domark = 1;
310 break;
311 case '\'':
312 dojump = 1;
313 break;
314 case 'i':
315 cmdinfo();
316 break;
317 case ' ':
318 case 'p':
319 if (audio && paused)
320 if (oss_open())
321 break;
322 if (audio && !paused)
323 oss_close();
324 paused = !paused;
325 sync_cur = sync_cnt;
326 break;
327 case '-':
328 sync_diff = -cmdarg(0);
329 break;
330 case '+':
331 sync_diff = cmdarg(0);
332 break;
333 case 'a':
334 sync_diff = ffs_avdiff(vffs, affs);
335 break;
336 case 'c':
337 sync_cnt = cmdarg(0);
338 break;
339 case 's':
340 sync_cur = cmdarg(sync_cnt);
341 break;
342 case 27:
343 arg = 0;
344 break;
345 default:
346 if (isdigit(c))
347 arg = arg * 10 + c - '0';
352 /* return nonzero if one more video frame can be decoded */
353 static int vsync(void)
355 if (sync_period && sync_since++ >= sync_period) {
356 sync_cur = sync_cnt;
357 sync_since = 0;
359 if (sync_first) {
360 sync_cur = 0;
361 if (sync_first < vnum) {
362 sync_first = 0;
363 sync_diff = ffs_avdiff(vffs, affs);
366 if (sync_cur > 0) {
367 sync_cur--;
368 return ffs_avdiff(vffs, affs) >= sync_diff;
370 ffs_wait(vffs);
371 return 1;
374 static void mainloop(void)
376 int eof = 0;
377 while (eof < audio + video) {
378 cmdexec();
379 if (exited)
380 break;
381 if (paused) {
382 a_doreset(1);
383 cmdwait();
384 continue;
386 while (audio && !eof && !a_prodwait()) {
387 int ret = ffs_adec(affs, a_buf[a_prod], ABUFLEN);
388 if (ret < 0)
389 eof++;
390 if (ret > 0) {
391 a_len[a_prod] = ret;
392 a_prod = (a_prod + 1) & (ABUFCNT - 1);
395 if (video && (!audio || eof || vsync())) {
396 int ignore = jump && (vnum % (jump + 1));
397 void *buf;
398 int ret = ffs_vdec(vffs, ignore ? NULL : &buf);
399 vnum++;
400 if (ret < 0)
401 eof++;
402 if (ret > 0)
403 draw_frame((void *) buf, ret);
404 sub_print();
405 } else {
406 stroll();
409 exited = 1;
412 static void *process_audio(void *dat)
414 while (1) {
415 while (!a_reset && (a_conswait() || paused) && !exited)
416 stroll();
417 if (exited)
418 return NULL;
419 if (a_reset) {
420 if (a_reset == 1)
421 a_cons = a_prod;
422 a_reset = 0;
423 continue;
425 if (afd > 0) {
426 write(afd, a_buf[a_cons], a_len[a_cons]);
427 a_cons = (a_cons + 1) & (ABUFCNT - 1);
430 return NULL;
433 static char *usage = "usage: fbff [options] file\n"
434 "\noptions:\n"
435 " -z n zoom the video\n"
436 " -m n magnify the video by duplicating pixels\n"
437 " -j n jump every n video frames; for slow machines\n"
438 " -f start full screen\n"
439 " -v n select video stream; '-' disables video\n"
440 " -a n select audio stream; '-' disables audio\n"
441 " -s always synchronize (-sx for every x frames)\n"
442 " -u record A/V delay after the first few frames\n"
443 " -t path subtitles file\n"
444 " -x n horizontal video position\n"
445 " -y n vertical video position\n"
446 " -r adjust the video to the right of the screen\n"
447 " -b adjust the video to the bottom of the screen\n\n";
449 static void read_args(int argc, char *argv[])
451 int i = 1;
452 while (i < argc) {
453 char *c = argv[i];
454 if (c[0] != '-')
455 break;
456 if (c[1] == 'm')
457 magnify = c[2] ? atoi(c + 2) : atoi(argv[++i]);
458 if (c[1] == 'z')
459 zoom = c[2] ? atof(c + 2) : atof(argv[++i]);
460 if (c[1] == 'j')
461 jump = c[2] ? atoi(c + 2) : atoi(argv[++i]);
462 if (c[1] == 'f')
463 fullscreen = 1;
464 if (c[1] == 's')
465 sync_period = c[2] ? atoi(c + 2) : 1;
466 if (c[1] == 't')
467 sub_path = c[2] ? c + 2 : argv[++i];
468 if (c[1] == 'h')
469 printf(usage);
470 if (c[1] == 'x')
471 posx = c[2] ? atoi(c + 2) : atoi(argv[++i]);
472 if (c[1] == 'y')
473 posy = c[2] ? atoi(c + 2) : atoi(argv[++i]);
474 if (c[1] == 'r')
475 rjust = 1;
476 if (c[1] == 'b')
477 bjust = 1;
478 if (c[1] == 'u')
479 sync_first = 32;
480 if (c[1] == 'v') {
481 char *arg = c[2] ? c + 2 : argv[++i];
482 video = arg[0] == '-' ? 0 : atoi(arg) + 2;
484 if (c[1] == 'a') {
485 char *arg = c[2] ? c + 2 : argv[++i];
486 audio = arg[0] == '-' ? 0 : atoi(arg) + 2;
488 i++;
492 static void term_init(struct termios *termios)
494 struct termios newtermios;
495 tcgetattr(0, termios);
496 newtermios = *termios;
497 newtermios.c_lflag &= ~ICANON;
498 newtermios.c_lflag &= ~ECHO;
499 tcsetattr(0, TCSAFLUSH, &newtermios);
500 fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);
503 static void term_done(struct termios *termios)
505 tcsetattr(0, 0, termios);
508 static void signalreceived(int sig)
510 if (sig == SIGUSR1)
511 nodraw = 1;
512 if (sig == SIGUSR2)
513 nodraw = 0;
516 int main(int argc, char *argv[])
518 struct termios termios;
519 pthread_t a_thread;
520 char *path = argv[argc - 1];
521 char *fbdev = getenv("FBDEV");
522 if (argc < 2) {
523 printf("usage: %s [-u -s60 ...] file\n", argv[0]);
524 return 1;
526 ossdsp = getenv("OSSDSP");
527 read_args(argc, argv);
528 ffs_globinit();
529 snprintf(filename, sizeof(filename), "%s", path);
530 if (video && !(vffs = ffs_alloc(path, FFS_VIDEO | (video - 1))))
531 video = 0;
532 if (audio && !(affs = ffs_alloc(path, FFS_AUDIO | (audio - 1))))
533 audio = 0;
534 if (!video && !audio)
535 return 1;
536 if (sub_path)
537 sub_read();
538 if (audio) {
539 ffs_aconf(affs);
540 if (oss_open()) {
541 fprintf(stderr, "fbff: /dev/dsp busy?\n");
542 return 1;
544 pthread_create(&a_thread, NULL, process_audio, NULL);
546 if (video) {
547 int w, h;
548 if (fb_init(fbdev))
549 return 1;
550 ffs_vinfo(vffs, &w, &h);
551 if (fullscreen) {
552 float hz = (float) fb_rows() / h / magnify;
553 float wz = (float) fb_cols() / w / magnify;
554 zoom = hz < wz ? hz : wz;
556 ffs_vconf(vffs, zoom, fb_mode());
558 if (getenv("TERM_PGID") != NULL && atoi(getenv("TERM_PGID")) == getppid())
559 if (tcsetpgrp(0, getppid()) == 0)
560 setpgid(0, getppid());
561 term_init(&termios);
562 signal(SIGUSR1, signalreceived);
563 signal(SIGUSR2, signalreceived);
564 mainloop();
565 term_done(&termios);
566 printf("\n");
568 if (video) {
569 fb_free();
570 ffs_free(vffs);
572 if (audio) {
573 pthread_join(a_thread, NULL);
574 oss_close();
575 ffs_free(affs);
577 return 0;