fbff: with no arguments % seeks to the beginning
[fbff.git] / fbff.c
blob4ba5ee73ae6112a544d76e5da358b548dfe8bc8c
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 <fcntl.h>
9 #include <pty.h>
10 #include <ctype.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 "config.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];
34 static struct termios termios;
36 static float zoom = 1;
37 static int magnify = 1;
38 static int jump = 0;
39 static int fullscreen = 0;
40 static int video = 1; /* video stream; 0=none, 1=auto, >2=idx */
41 static int audio = 1; /* audio stream; 0=none, 1=auto, >2=idx */
42 static int rjust = 0; /* justify video to the right */
43 static int bjust = 0; /* justify video to the bottom */
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_frame(void *img, int linelen)
65 int w, h;
66 fbval_t buf[1 << 14];
67 int nr, nc, cb, rb;
68 int i, r, c;
69 ffs_vinfo(vffs, &w, &h);
70 nr = MIN(h * zoom, fb_rows() / magnify);
71 nc = MIN(w * zoom, fb_cols() / magnify);
72 cb = rjust ? fb_cols() - nc * magnify : 0;
73 rb = bjust ? fb_rows() - nr * magnify : 0;
74 for (r = 0; r < nr; r++) {
75 fbval_t *row = img + r * linelen;
76 if (magnify == 1) {
77 fb_set(rb + r, cb, row, nc);
78 continue;
80 for (c = 0; c < nc; c++)
81 for (i = 0; i < magnify; i++)
82 buf[c * magnify + i] = row[c];
83 for (i = 0; i < magnify; i++)
84 fb_set((rb + r) * magnify + i, cb, buf, nc * magnify);
88 static int oss_open(void)
90 int rate, ch, bps;
91 afd = open("/dev/dsp", O_WRONLY);
92 if (afd < 0)
93 return 1;
94 ffs_ainfo(affs, &rate, &bps, &ch);
95 ioctl(afd, SOUND_PCM_WRITE_CHANNELS, &ch);
96 ioctl(afd, SOUND_PCM_WRITE_BITS, &bps);
97 ioctl(afd, SOUND_PCM_WRITE_RATE, &rate);
98 return 0;
101 static void oss_close(void)
103 if (afd > 0)
104 close(afd);
105 afd = 0;
108 #define ABUFSZ (1 << 18)
110 static int a_cons;
111 static int a_prod;
112 static char a_buf[AUDIOBUFS][ABUFSZ];
113 static int a_len[AUDIOBUFS];
114 static int a_reset;
116 static int a_conswait(void)
118 return a_cons == a_prod;
121 static int a_prodwait(void)
123 return ((a_prod + 1) & (AUDIOBUFS - 1)) == a_cons;
126 static void a_doreset(int pause)
128 a_reset = 1 + pause;
129 while (audio && a_reset)
130 stroll();
133 static int cmdread(void)
135 char b;
136 if (read(0, &b, 1) <= 0)
137 return -1;
138 return b;
141 static void cmdwait(void)
143 struct pollfd ufds[1];
144 ufds[0].fd = 0;
145 ufds[0].events = POLLIN;
146 poll(ufds, 1, -1);
149 static void cmdjmp(int n, int rel)
151 struct ffs *ffs = video ? vffs : affs;
152 long pos = (rel ? ffs_pos(ffs) : 0) + n * 1000;
153 a_doreset(0);
154 sync_cur = sync_cnt;
155 if (pos < 0)
156 pos = 0;
157 if (!rel)
158 mark['\''] = ffs_pos(ffs);
159 if (audio)
160 ffs_seek(affs, ffs, pos);
161 if (video)
162 ffs_seek(vffs, ffs, pos);
165 static void cmdinfo(void)
167 struct ffs *ffs = video ? vffs : affs;
168 long pos = ffs_pos(ffs);
169 long percent = ffs_duration(ffs) ? pos * 1000 / ffs_duration(ffs) : 0;
170 printf("%c %3ld.%01ld%% %3ld:%02ld.%01ld (AV:%4d) [%s] \r",
171 paused ? (afd < 0 ? '*' : ' ') : '>',
172 percent / 10, percent % 10,
173 pos / 60000, (pos % 60000) / 1000, (pos % 1000) / 100,
174 video && audio ? ffs_avdiff(vffs, affs) : 0,
175 filename);
176 fflush(stdout);
179 static int cmdarg(int def)
181 int n = arg;
182 arg = 0;
183 return n ? n : def;
186 static void cmdexec(void)
188 int c;
189 while ((c = cmdread()) >= 0) {
190 if (domark) {
191 domark = 0;
192 mark[c] = ffs_pos(video ? vffs : affs);
193 continue;
195 if (dojump) {
196 dojump = 0;
197 if (mark[c] > 0)
198 cmdjmp(mark[c] / 1000, 0);
199 continue;
201 switch (c) {
202 case 'q':
203 exited = 1;
204 break;
205 case 'l':
206 cmdjmp(cmdarg(1) * 10, 1);
207 break;
208 case 'h':
209 cmdjmp(-cmdarg(1) * 10, 1);
210 break;
211 case 'j':
212 cmdjmp(cmdarg(1) * 60, 1);
213 break;
214 case 'k':
215 cmdjmp(-cmdarg(1) * 60, 1);
216 break;
217 case 'J':
218 cmdjmp(cmdarg(1) * 600, 1);
219 break;
220 case 'K':
221 cmdjmp(-cmdarg(1) * 600, 1);
222 break;
223 case 'G':
224 cmdjmp(cmdarg(0) * 60, 0);
225 break;
226 case '%':
227 cmdjmp(cmdarg(0) * ffs_duration(vffs ? vffs : affs) / 100000, 0);
228 break;
229 case 'm':
230 domark = 1;
231 break;
232 case '\'':
233 dojump = 1;
234 break;
235 case 'i':
236 cmdinfo();
237 break;
238 case ' ':
239 case 'p':
240 if (audio && paused)
241 if (oss_open())
242 break;
243 if (audio && !paused)
244 oss_close();
245 paused = !paused;
246 sync_cur = sync_cnt;
247 break;
248 case '-':
249 sync_diff = -cmdarg(0);
250 break;
251 case '+':
252 sync_diff = cmdarg(0);
253 break;
254 case 'a':
255 sync_diff = ffs_avdiff(vffs, affs);
256 break;
257 case 'c':
258 sync_cnt = cmdarg(0);
259 break;
260 case 's':
261 sync_cur = cmdarg(sync_cnt);
262 break;
263 case 27:
264 arg = 0;
265 break;
266 default:
267 if (isdigit(c))
268 arg = arg * 10 + c - '0';
273 /* return nonzero if one more video frame can be decoded */
274 static int vsync(void)
276 if (sync_period && sync_since++ >= sync_period) {
277 sync_cur = sync_cnt;
278 sync_since = 0;
280 if (sync_first) {
281 sync_cur = 0;
282 if (sync_first < vnum) {
283 sync_first = 0;
284 sync_diff = ffs_avdiff(vffs, affs);
287 if (sync_cur > 0) {
288 sync_cur--;
289 return ffs_avdiff(vffs, affs) >= sync_diff;
291 ffs_wait(vffs);
292 return 1;
295 static void mainloop(void)
297 int eof = 0;
298 while (eof < audio + video) {
299 cmdexec();
300 if (exited)
301 break;
302 if (paused) {
303 a_doreset(1);
304 cmdwait();
305 continue;
307 while (audio && !eof && !a_prodwait()) {
308 int ret = ffs_adec(affs, a_buf[a_prod], ABUFSZ);
309 if (ret < 0)
310 eof++;
311 if (ret > 0) {
312 a_len[a_prod] = ret;
313 a_prod = (a_prod + 1) & (AUDIOBUFS - 1);
316 if (video && (!audio || eof || vsync())) {
317 int ignore = jump && (vnum % (jump + 1));
318 void *buf;
319 int ret = ffs_vdec(vffs, ignore ? NULL : &buf);
320 vnum++;
321 if (ret < 0)
322 eof++;
323 if (ret > 0)
324 draw_frame((void *) buf, ret);
325 } else {
326 stroll();
329 exited = 1;
332 static void *process_audio(void *dat)
334 while (1) {
335 while (!a_reset && (a_conswait() || paused) && !exited)
336 stroll();
337 if (exited)
338 return NULL;
339 if (a_reset) {
340 if (a_reset == 1)
341 a_cons = a_prod;
342 a_reset = 0;
343 continue;
345 if (afd > 0) {
346 write(afd, a_buf[a_cons], a_len[a_cons]);
347 a_cons = (a_cons + 1) & (AUDIOBUFS - 1);
350 return NULL;
353 static void term_setup(void)
355 struct termios newtermios;
356 tcgetattr(0, &termios);
357 newtermios = termios;
358 newtermios.c_lflag &= ~ICANON;
359 newtermios.c_lflag &= ~ECHO;
360 tcsetattr(0, TCSAFLUSH, &newtermios);
361 fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);
364 static void term_cleanup(void)
366 tcsetattr(0, 0, &termios);
369 static void sigcont(int sig)
371 term_setup();
374 static char *usage = "usage: fbff [options] file\n"
375 "\noptions:\n"
376 " -z x zoom the video\n"
377 " -m x magnify the video by duplicating pixels\n"
378 " -j x jump every x video frames; for slow machines\n"
379 " -f start full screen\n"
380 " -v x select video stream; '-' disables video\n"
381 " -a x select audio stream; '-' disables audio\n"
382 " -s always synchronize (-sx for every x frames)\n"
383 " -u record A/V delay after the first few frames\n"
384 " -r adjust the video to the right of the screen\n"
385 " -b adjust the video to the bottom of the screen\n\n";
387 static void read_args(int argc, char *argv[])
389 int i = 1;
390 while (i < argc) {
391 char *c = argv[i];
392 if (c[0] != '-')
393 break;
394 if (c[1] == 'm')
395 magnify = c[2] ? atoi(c + 2) : atoi(argv[++i]);
396 if (c[1] == 'z')
397 zoom = c[2] ? atof(c + 2) : atof(argv[++i]);
398 if (c[1] == 'j')
399 jump = c[2] ? atoi(c + 2) : atoi(argv[++i]);
400 if (c[1] == 'f')
401 fullscreen = 1;
402 if (c[1] == 's')
403 sync_period = c[2] ? atoi(c + 2) : 1;
404 if (c[1] == 'h')
405 printf(usage);
406 if (c[1] == 'r')
407 rjust = 1;
408 if (c[1] == 'b')
409 bjust = 1;
410 if (c[1] == 'u')
411 sync_first = 32;
412 if (c[1] == 'v') {
413 char *arg = c[2] ? c + 2 : argv[++i];
414 video = arg[0] == '-' ? 0 : atoi(arg) + 2;
416 if (c[1] == 'a') {
417 char *arg = c[2] ? c + 2 : argv[++i];
418 audio = arg[0] == '-' ? 0 : atoi(arg) + 2;
420 i++;
424 int main(int argc, char *argv[])
426 pthread_t a_thread;
427 char *path = argv[argc - 1];
428 if (argc < 2) {
429 printf("usage: %s [options] filename\n", argv[0]);
430 return 1;
432 read_args(argc, argv);
433 ffs_globinit();
434 snprintf(filename, sizeof(filename), "%s", path);
435 if (video && !(vffs = ffs_alloc(path, FFS_VIDEO | (video - 1))))
436 video = 0;
437 if (audio && !(affs = ffs_alloc(path, FFS_AUDIO | (audio - 1))))
438 audio = 0;
439 if (!video && !audio)
440 return 1;
441 if (audio) {
442 ffs_aconf(affs);
443 if (oss_open()) {
444 fprintf(stderr, "fbff: /dev/dsp busy?\n");
445 return 1;
447 pthread_create(&a_thread, NULL, process_audio, NULL);
449 if (video) {
450 int w, h;
451 if (fb_init())
452 return 1;
453 ffs_vinfo(vffs, &w, &h);
454 if (magnify != 1 && sizeof(fbval_t) != FBM_BPP(fb_mode()))
455 fprintf(stderr, "fbff: magnify != 1 and fbval_t doesn't match\n");
456 if (fullscreen) {
457 float hz = (float) fb_rows() / h / magnify;
458 float wz = (float) fb_cols() / w / magnify;
459 zoom = hz < wz ? hz : wz;
461 ffs_vconf(vffs, zoom, fb_mode());
463 term_setup();
464 signal(SIGCONT, sigcont);
465 mainloop();
466 term_cleanup();
467 printf("\n");
469 if (video) {
470 fb_free();
471 ffs_free(vffs);
473 if (audio) {
474 pthread_join(a_thread, NULL);
475 oss_close();
476 ffs_free(affs);
478 return 0;