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.
20 #include <sys/soundcard.h>
25 #define MIN(a, b) ((a) < (b) ? (a) : (b))
26 #define MAX(a, b) ((a) > (b) ? (a) : (b))
33 static char filename
[32];
35 static float zoom
= 1;
36 static int magnify
= 1;
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)
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())
70 cn
= -cb
< cn
? cn
+ 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
;
83 int bpp
= FBM_BPP(fb_mode());
86 ffs_vinfo(vffs
, &w
, &h
);
89 cb
= rjust
? fb_cols() - cn
* magnify
+ posx
: posx
;
90 rb
= bjust
? fb_rows() - rn
* magnify
+ posy
: posy
;
92 for (r
= 0; r
< rn
; r
++)
93 draw_row(rb
+ r
, cb
, img
+ r
* linelen
, cn
);
95 char *brow
= malloc(cn
* magnify
* bpp
);
96 for (r
= 0; r
< rn
; r
++) {
97 char *src
= img
+ r
* linelen
;
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
);
110 static int oss_open(void)
113 int frag
= 0x0003000b; /* 0xmmmmssss: 2^m fragments of size 2^s each */
114 afd
= open(ossdsp
, O_WRONLY
);
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
);
125 static void oss_close(void)
134 #define ABUFCNT (1 << 3) /* number of audio buffers */
135 #define ABUFLEN (1 << 18) /* audio buffer length */
139 static char a_buf
[ABUFCNT
][ABUFLEN
];
140 static int a_len
[ABUFCNT
];
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
)
156 while (audio
&& a_reset
)
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
);
177 while (sub_n
< SUBSCNT
&& !ffs_sdec(sffs
, sub_text
[sub_n
], SUBSLEN
,
178 &sub_beg
[sub_n
], &sub_end
[sub_n
])) {
184 static void sub_print(void)
186 struct ffs
*ffs
= video
? vffs
: affs
;
189 long pos
= ffs_pos(ffs
);
191 int m
= (l
+ h
) >> 1;
192 if (pos
>= sub_beg
[m
] && pos
<= sub_end
[m
]) {
194 printf("\r\33[K%s", sub_text
[m
]);
199 if (pos
< sub_beg
[m
])
213 static int cmdread(void)
216 if (read(0, &b
, 1) <= 0)
221 static void cmdwait(void)
223 struct pollfd ufds
[1];
225 ufds
[0].events
= POLLIN
;
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;
238 mark
['\''] = ffs_pos(ffs
);
240 ffs_seek(affs
, ffs
, pos
);
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,
259 static int cmdarg(int def
)
266 static void cmdexec(void)
269 while ((c
= cmdread()) >= 0) {
272 mark
[c
] = ffs_pos(video
? vffs
: affs
);
278 cmdjmp(mark
[c
] / 1000, 0);
286 cmdjmp(cmdarg(1) * 10, 1);
289 cmdjmp(-cmdarg(1) * 10, 1);
292 cmdjmp(cmdarg(1) * 60, 1);
295 cmdjmp(-cmdarg(1) * 60, 1);
298 cmdjmp(cmdarg(1) * 600, 1);
301 cmdjmp(-cmdarg(1) * 600, 1);
304 cmdjmp(cmdarg(0) * 60, 0);
307 cmdjmp(cmdarg(0) * ffs_duration(vffs
? vffs
: affs
) / 100000, 0);
323 if (audio
&& !paused
)
329 sync_diff
= -cmdarg(0);
332 sync_diff
= cmdarg(0);
335 sync_diff
= ffs_avdiff(vffs
, affs
);
338 sync_cnt
= cmdarg(0);
341 sync_cur
= cmdarg(sync_cnt
);
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
) {
362 if (sync_first
< vnum
) {
364 sync_diff
= ffs_avdiff(vffs
, affs
);
369 return ffs_avdiff(vffs
, affs
) >= sync_diff
;
375 static void mainloop(void)
378 while (eof
< audio
+ video
) {
387 while (audio
&& !eof
&& !a_prodwait()) {
388 int ret
= ffs_adec(affs
, a_buf
[a_prod
], ABUFLEN
);
393 a_prod
= (a_prod
+ 1) & (ABUFCNT
- 1);
396 if (video
&& (!audio
|| eof
|| vsync())) {
397 int ignore
= jump
&& (vnum
% (jump
+ 1));
399 int ret
= ffs_vdec(vffs
, ignore
? NULL
: &buf
);
404 draw_frame((void *) buf
, ret
);
413 static void *process_audio(void *dat
)
416 while (!a_reset
&& (a_conswait() || paused
) && !exited
)
427 write(afd
, a_buf
[a_cons
], a_len
[a_cons
]);
428 a_cons
= (a_cons
+ 1) & (ABUFCNT
- 1);
434 static char *usage
= "usage: fbff [options] file\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
[])
458 magnify
= c
[2] ? atoi(c
+ 2) : atoi(argv
[++i
]);
460 zoom
= c
[2] ? atof(c
+ 2) : atof(argv
[++i
]);
462 jump
= c
[2] ? atoi(c
+ 2) : atoi(argv
[++i
]);
466 sync_period
= c
[2] ? atoi(c
+ 2) : 1;
468 sub_path
= c
[2] ? c
+ 2 : argv
[++i
];
472 posx
= c
[2] ? atoi(c
+ 2) : atoi(argv
[++i
]);
474 posy
= c
[2] ? atoi(c
+ 2) : atoi(argv
[++i
]);
482 char *arg
= c
[2] ? c
+ 2 : argv
[++i
];
483 video
= arg
[0] == '-' ? 0 : atoi(arg
) + 2;
486 char *arg
= c
[2] ? c
+ 2 : argv
[++i
];
487 audio
= arg
[0] == '-' ? 0 : atoi(arg
) + 2;
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
)
517 int main(int argc
, char *argv
[])
519 struct termios termios
;
521 char *path
= argv
[argc
- 1];
522 char *fbdev
= getenv("FBDEV");
524 printf("usage: %s [-u -s60 ...] file\n", argv
[0]);
527 ossdsp
= getenv("OSSDSP") ? getenv("OSSDSP") : "/dev/dsp";
528 read_args(argc
, argv
);
530 snprintf(filename
, sizeof(filename
), "%s", path
);
531 if (video
&& !(vffs
= ffs_alloc(path
, FFS_VIDEO
| (video
- 1))))
533 if (audio
&& !(affs
= ffs_alloc(path
, FFS_AUDIO
| (audio
- 1))))
535 if (!video
&& !audio
)
540 int err
= oss_open();
544 fprintf(stderr
, "fbff: %s missing?\n", ossdsp
);
546 fprintf(stderr
, "fbff: %s busy?\n", ossdsp
);
549 pthread_create(&a_thread
, NULL
, process_audio
, NULL
);
555 ffs_vinfo(vffs
, &w
, &h
);
557 float hz
= (float) fb_rows() / h
/ magnify
;
558 float wz
= (float) fb_cols() / w
/ magnify
;
559 zoom
= hz
< wz
? hz
: wz
;
561 ffs_vconf(vffs
, zoom
, fb_mode());
563 if (getenv("TERM_PGID") != NULL
&& atoi(getenv("TERM_PGID")) == getppid())
564 if (tcsetpgrp(0, getppid()) == 0)
565 setpgid(0, getppid());
567 signal(SIGUSR1
, signalreceived
);
568 signal(SIGUSR2
, signalreceived
);
578 pthread_join(a_thread
, NULL
);