ffs: decoding subtitles
[fbff.git] / fbff.c
blob6b595aeca2df210906e536d353f1e168b8ac33aa
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 <sys/soundcard.h>
19 #include <pthread.h>
20 #include "config.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, >2=idx */
39 static int audio = 1; /* audio stream; 0=none, 1=auto, >2=idx */
40 static int rjust = 0; /* justify video to the right */
41 static int bjust = 0; /* justify video to the bottom */
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_frame(void *img, int linelen)
63 int w, h;
64 fbval_t buf[1 << 14];
65 int nr, nc, cb, rb;
66 int i, r, c;
67 ffs_vinfo(vffs, &w, &h);
68 nr = MIN(h * zoom, fb_rows() / magnify);
69 nc = MIN(w * zoom, fb_cols() / magnify);
70 cb = rjust ? fb_cols() - nc * magnify : 0;
71 rb = bjust ? fb_rows() - nr * magnify : 0;
72 for (r = 0; r < nr; r++) {
73 fbval_t *row = img + r * linelen;
74 if (magnify == 1) {
75 fb_set(rb + r, cb, row, nc);
76 continue;
78 for (c = 0; c < nc; c++)
79 for (i = 0; i < magnify; i++)
80 buf[c * magnify + i] = row[c];
81 for (i = 0; i < magnify; i++)
82 fb_set((rb + r) * magnify + i, cb, buf, nc * magnify);
86 static int oss_open(void)
88 int rate, ch, bps;
89 afd = open("/dev/dsp", O_WRONLY);
90 if (afd < 0)
91 return 1;
92 ffs_ainfo(affs, &rate, &bps, &ch);
93 ioctl(afd, SOUND_PCM_WRITE_CHANNELS, &ch);
94 ioctl(afd, SOUND_PCM_WRITE_BITS, &bps);
95 ioctl(afd, SOUND_PCM_WRITE_RATE, &rate);
96 return 0;
99 static void oss_close(void)
101 if (afd > 0)
102 close(afd);
103 afd = 0;
106 #define ABUFSZ (1 << 18)
108 static int a_cons;
109 static int a_prod;
110 static char a_buf[AUDIOBUFS][ABUFSZ];
111 static int a_len[AUDIOBUFS];
112 static int a_reset;
114 static int a_conswait(void)
116 return a_cons == a_prod;
119 static int a_prodwait(void)
121 return ((a_prod + 1) & (AUDIOBUFS - 1)) == a_cons;
124 static void a_doreset(int pause)
126 a_reset = 1 + pause;
127 while (audio && a_reset)
128 stroll();
131 static int cmdread(void)
133 char b;
134 if (read(0, &b, 1) <= 0)
135 return -1;
136 return b;
139 static void cmdwait(void)
141 struct pollfd ufds[1];
142 ufds[0].fd = 0;
143 ufds[0].events = POLLIN;
144 poll(ufds, 1, -1);
147 static void cmdjmp(int n, int rel)
149 struct ffs *ffs = video ? vffs : affs;
150 long pos = (rel ? ffs_pos(ffs) : 0) + n * 1000;
151 a_doreset(0);
152 sync_cur = sync_cnt;
153 if (pos < 0)
154 pos = 0;
155 if (!rel)
156 mark['\''] = ffs_pos(ffs);
157 if (audio)
158 ffs_seek(affs, ffs, pos);
159 if (video)
160 ffs_seek(vffs, ffs, pos);
163 static void cmdinfo(void)
165 struct ffs *ffs = video ? vffs : affs;
166 long pos = ffs_pos(ffs);
167 long percent = ffs_duration(ffs) ? pos * 1000 / ffs_duration(ffs) : 0;
168 printf("%c %3ld.%01ld%% %3ld:%02ld.%01ld (AV:%4d) [%s] \r",
169 paused ? (afd < 0 ? '*' : ' ') : '>',
170 percent / 10, percent % 10,
171 pos / 60000, (pos % 60000) / 1000, (pos % 1000) / 100,
172 video && audio ? ffs_avdiff(vffs, affs) : 0,
173 filename);
174 fflush(stdout);
177 static int cmdarg(int def)
179 int n = arg;
180 arg = 0;
181 return n ? n : def;
184 static void cmdexec(void)
186 int c;
187 while ((c = cmdread()) >= 0) {
188 if (domark) {
189 domark = 0;
190 mark[c] = ffs_pos(video ? vffs : affs);
191 continue;
193 if (dojump) {
194 dojump = 0;
195 if (mark[c] > 0)
196 cmdjmp(mark[c] / 1000, 0);
197 continue;
199 switch (c) {
200 case 'q':
201 exited = 1;
202 break;
203 case 'l':
204 cmdjmp(cmdarg(1) * 10, 1);
205 break;
206 case 'h':
207 cmdjmp(-cmdarg(1) * 10, 1);
208 break;
209 case 'j':
210 cmdjmp(cmdarg(1) * 60, 1);
211 break;
212 case 'k':
213 cmdjmp(-cmdarg(1) * 60, 1);
214 break;
215 case 'J':
216 cmdjmp(cmdarg(1) * 600, 1);
217 break;
218 case 'K':
219 cmdjmp(-cmdarg(1) * 600, 1);
220 break;
221 case 'G':
222 cmdjmp(cmdarg(0) * 60, 0);
223 break;
224 case '%':
225 cmdjmp(cmdarg(0) * ffs_duration(vffs ? vffs : affs) / 100000, 0);
226 break;
227 case 'm':
228 domark = 1;
229 break;
230 case '\'':
231 dojump = 1;
232 break;
233 case 'i':
234 cmdinfo();
235 break;
236 case ' ':
237 case 'p':
238 if (audio && paused)
239 if (oss_open())
240 break;
241 if (audio && !paused)
242 oss_close();
243 paused = !paused;
244 sync_cur = sync_cnt;
245 break;
246 case '-':
247 sync_diff = -cmdarg(0);
248 break;
249 case '+':
250 sync_diff = cmdarg(0);
251 break;
252 case 'a':
253 sync_diff = ffs_avdiff(vffs, affs);
254 break;
255 case 'c':
256 sync_cnt = cmdarg(0);
257 break;
258 case 's':
259 sync_cur = cmdarg(sync_cnt);
260 break;
261 case 27:
262 arg = 0;
263 break;
264 default:
265 if (isdigit(c))
266 arg = arg * 10 + c - '0';
271 /* return nonzero if one more video frame can be decoded */
272 static int vsync(void)
274 if (sync_period && sync_since++ >= sync_period) {
275 sync_cur = sync_cnt;
276 sync_since = 0;
278 if (sync_first) {
279 sync_cur = 0;
280 if (sync_first < vnum) {
281 sync_first = 0;
282 sync_diff = ffs_avdiff(vffs, affs);
285 if (sync_cur > 0) {
286 sync_cur--;
287 return ffs_avdiff(vffs, affs) >= sync_diff;
289 ffs_wait(vffs);
290 return 1;
293 static void mainloop(void)
295 int eof = 0;
296 while (eof < audio + video) {
297 cmdexec();
298 if (exited)
299 break;
300 if (paused) {
301 a_doreset(1);
302 cmdwait();
303 continue;
305 while (audio && !eof && !a_prodwait()) {
306 int ret = ffs_adec(affs, a_buf[a_prod], ABUFSZ);
307 if (ret < 0)
308 eof++;
309 if (ret > 0) {
310 a_len[a_prod] = ret;
311 a_prod = (a_prod + 1) & (AUDIOBUFS - 1);
314 if (video && (!audio || eof || vsync())) {
315 int ignore = jump && (vnum % (jump + 1));
316 void *buf;
317 int ret = ffs_vdec(vffs, ignore ? NULL : &buf);
318 vnum++;
319 if (ret < 0)
320 eof++;
321 if (ret > 0)
322 draw_frame((void *) buf, ret);
323 } else {
324 stroll();
327 exited = 1;
330 static void *process_audio(void *dat)
332 while (1) {
333 while (!a_reset && (a_conswait() || paused) && !exited)
334 stroll();
335 if (exited)
336 return NULL;
337 if (a_reset) {
338 if (a_reset == 1)
339 a_cons = a_prod;
340 a_reset = 0;
341 continue;
343 if (afd > 0) {
344 write(afd, a_buf[a_cons], a_len[a_cons]);
345 a_cons = (a_cons + 1) & (AUDIOBUFS - 1);
348 return NULL;
351 static char *usage = "usage: fbff [options] file\n"
352 "\noptions:\n"
353 " -z x zoom the video\n"
354 " -m x magnify the video by duplicating pixels\n"
355 " -j x jump every x video frames; for slow machines\n"
356 " -f start full screen\n"
357 " -v x select video stream; '-' disables video\n"
358 " -a x select audio stream; '-' disables audio\n"
359 " -s always synchronize (-sx for every x frames)\n"
360 " -u record A/V delay after the first few frames\n"
361 " -r adjust the video to the right of the screen\n"
362 " -b adjust the video to the bottom of the screen\n\n";
364 static void read_args(int argc, char *argv[])
366 int i = 1;
367 while (i < argc) {
368 char *c = argv[i];
369 if (c[0] != '-')
370 break;
371 if (c[1] == 'm')
372 magnify = c[2] ? atoi(c + 2) : atoi(argv[++i]);
373 if (c[1] == 'z')
374 zoom = c[2] ? atof(c + 2) : atof(argv[++i]);
375 if (c[1] == 'j')
376 jump = c[2] ? atoi(c + 2) : atoi(argv[++i]);
377 if (c[1] == 'f')
378 fullscreen = 1;
379 if (c[1] == 's')
380 sync_period = c[2] ? atoi(c + 2) : 1;
381 if (c[1] == 'h')
382 printf(usage);
383 if (c[1] == 'r')
384 rjust = 1;
385 if (c[1] == 'b')
386 bjust = 1;
387 if (c[1] == 'u')
388 sync_first = 32;
389 if (c[1] == 'v') {
390 char *arg = c[2] ? c + 2 : argv[++i];
391 video = arg[0] == '-' ? 0 : atoi(arg) + 2;
393 if (c[1] == 'a') {
394 char *arg = c[2] ? c + 2 : argv[++i];
395 audio = arg[0] == '-' ? 0 : atoi(arg) + 2;
397 i++;
401 static void term_init(struct termios *termios)
403 struct termios newtermios;
404 tcgetattr(0, termios);
405 newtermios = *termios;
406 newtermios.c_lflag &= ~ICANON;
407 newtermios.c_lflag &= ~ECHO;
408 tcsetattr(0, TCSAFLUSH, &newtermios);
409 fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);
412 static void term_done(struct termios *termios)
414 tcsetattr(0, 0, termios);
417 int main(int argc, char *argv[])
419 struct termios termios;
420 pthread_t a_thread;
421 char *path = argv[argc - 1];
422 if (argc < 2) {
423 printf("usage: %s [-u -s60 ...] file\n", argv[0]);
424 return 1;
426 read_args(argc, argv);
427 ffs_globinit();
428 snprintf(filename, sizeof(filename), "%s", path);
429 if (video && !(vffs = ffs_alloc(path, FFS_VIDEO | (video - 1))))
430 video = 0;
431 if (audio && !(affs = ffs_alloc(path, FFS_AUDIO | (audio - 1))))
432 audio = 0;
433 if (!video && !audio)
434 return 1;
435 if (audio) {
436 ffs_aconf(affs);
437 if (oss_open()) {
438 fprintf(stderr, "fbff: /dev/dsp busy?\n");
439 return 1;
441 pthread_create(&a_thread, NULL, process_audio, NULL);
443 if (video) {
444 int w, h;
445 if (fb_init())
446 return 1;
447 ffs_vinfo(vffs, &w, &h);
448 if (magnify != 1 && sizeof(fbval_t) != FBM_BPP(fb_mode()))
449 fprintf(stderr, "fbff: magnify != 1 and fbval_t doesn't match\n");
450 if (fullscreen) {
451 float hz = (float) fb_rows() / h / magnify;
452 float wz = (float) fb_cols() / w / magnify;
453 zoom = hz < wz ? hz : wz;
455 ffs_vconf(vffs, zoom, fb_mode());
457 term_init(&termios);
458 mainloop();
459 term_done(&termios);
460 printf("\n");
462 if (video) {
463 fb_free();
464 ffs_free(vffs);
466 if (audio) {
467 pthread_join(a_thread, NULL);
468 oss_close();
469 ffs_free(affs);
471 return 0;