fbff: add periodic syncs
[fbff.git] / fbff.c
blob3fe06f2225aa238e0c40e7c52cd59ca850153e48
1 /*
2 * fbff - a small ffmpeg-based framebuffer/oss media player
4 * Copyright (C) 2009-2012 Ali Gholami Rudi
6 * This program is released under GNU GPL version 2.
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 arg;
29 static struct termios termios;
30 static int paused;
31 static int exited;
33 static float zoom = 1;
34 static int magnify = 1;
35 static int jump = 0;
36 static int fullscreen = 0;
37 static int video = 1; /* video stream; 0=none, 1=auto, >2=idx */
38 static int audio = 1; /* audio stream; 0=none, 1=auto, >2=idx */
39 static int rjust = 0; /* justify video to the right */
40 static int bjust = 0; /* justify video to the bottom */
41 static int frame_jmp = 1; /* the changes to pos_cur for each frame */
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 */
48 static int sync_diff; /* audio/video frame position diff */
49 static int sync_period; /* sync after every this many number of frames */
50 static int sync_since; /* frames since th last sync */
51 static int sync_cnt = 32; /* synchronization steps */
52 static int sync_cur; /* synchronization steps left */
54 static void stroll(void)
56 usleep(10000);
59 static void draw_frame(void *img, int linelen)
61 int w, h;
62 fbval_t buf[1 << 14];
63 int nr, nc, cb, rb;
64 int i, r, c;
65 ffs_vinfo(vffs, &w, &h);
66 nr = MIN(h * zoom, fb_rows() / magnify);
67 nc = MIN(w * zoom, fb_cols() / magnify);
68 cb = rjust ? fb_cols() - nc * magnify : 0;
69 rb = bjust ? fb_rows() - nr * magnify : 0;
70 for (r = 0; r < nr; r++) {
71 fbval_t *row = img + r * linelen;
72 if (magnify == 1) {
73 fb_set(rb + r, cb, row, nc);
74 continue;
76 for (c = 0; c < nc; c++)
77 for (i = 0; i < magnify; i++)
78 buf[c * magnify + i] = row[c];
79 for (i = 0; i < magnify; i++)
80 fb_set((rb + r) * magnify + i, cb, buf, nc * magnify);
84 #define ABUFSZ (1 << 18)
86 static int a_cons;
87 static int a_prod;
88 static char a_buf[AUDIOBUFS][ABUFSZ];
89 static int a_len[AUDIOBUFS];
90 static int a_reset;
92 static int a_conswait(void)
94 return a_cons == a_prod;
97 static int a_prodwait(void)
99 return ((a_prod + 1) & (AUDIOBUFS - 1)) == a_cons;
102 static void a_doreset(int pause)
104 a_reset = 1 + pause;
105 while (audio && a_reset)
106 stroll();
109 static int readkey(void)
111 char b;
112 if (read(STDIN_FILENO, &b, 1) <= 0)
113 return -1;
114 return b;
117 static void waitkey(void)
119 struct pollfd ufds[1];
120 ufds[0].fd = STDIN_FILENO;
121 ufds[0].events = POLLIN;
122 poll(ufds, 1, -1);
125 static int ffarg(int def)
127 int n = arg;
128 arg = 0;
129 return n ? n : def;
132 static void ffjmp(int n, int rel)
134 struct ffs *ffs = video ? vffs : affs;
135 long pos = ffs_pos(ffs, n);
136 a_doreset(0);
137 sync_cur = sync_cnt;
138 if (audio)
139 ffs_seek(affs, pos, frame_jmp);
140 if (video)
141 ffs_seek(vffs, pos, frame_jmp);
144 static void printinfo(void)
146 struct ffs *ffs = video ? vffs : affs;
147 printf("fbff: %8lx \t (AV: %d)\r",
148 ffs_pos(ffs, 0), video && audio ? ffs_avdiff(vffs, affs) : 0);
149 fflush(stdout);
152 #define JMP1 (1 << 5)
153 #define JMP2 (JMP1 << 3)
154 #define JMP3 (JMP2 << 5)
156 static void execkey(void)
158 int c;
159 while ((c = readkey()) != -1) {
160 switch (c) {
161 case 'q':
162 exited = 1;
163 break;
164 case 'l':
165 ffjmp(ffarg(1) * JMP1, 1);
166 break;
167 case 'h':
168 ffjmp(-ffarg(1) * JMP1, 1);
169 break;
170 case 'j':
171 ffjmp(ffarg(1) * JMP2, 1);
172 break;
173 case 'k':
174 ffjmp(-ffarg(1) * JMP2, 1);
175 break;
176 case 'J':
177 ffjmp(ffarg(1) * JMP3, 1);
178 break;
179 case 'K':
180 ffjmp(-ffarg(1) * JMP3, 1);
181 break;
182 case '%':
183 if (arg)
184 ffjmp(100, 0);
185 break;
186 case 'i':
187 printinfo();
188 break;
189 case ' ':
190 case 'p':
191 paused = !paused;
192 sync_cur = sync_cnt;
193 break;
194 case '-':
195 sync_diff = -ffarg(0);
196 break;
197 case '+':
198 sync_diff = ffarg(0);
199 break;
200 case 'a':
201 sync_diff = ffs_avdiff(vffs, affs);
202 break;
203 case 'c':
204 sync_cnt = ffarg(0);
205 break;
206 case 's':
207 sync_cur = ffarg(sync_cnt);
208 break;
209 case 27:
210 arg = 0;
211 break;
212 default:
213 if (isdigit(c))
214 arg = arg * 10 + c - '0';
219 /* return nonzero if one more video frame can be decoded */
220 static int vsync(void)
222 if (sync_period && sync_since++ >= sync_period) {
223 sync_cur = sync_cnt;
224 sync_since = 0;
226 if (sync_cur > 0) {
227 sync_cur--;
228 return ffs_avdiff(vffs, affs) >= sync_diff;
230 ffs_wait(vffs);
231 return 1;
234 static void mainloop(void)
236 int eof = 0;
237 while (eof < audio + video) {
238 execkey();
239 if (exited)
240 break;
241 if (paused) {
242 a_doreset(1);
243 waitkey();
244 continue;
246 while (audio && !eof && !a_prodwait()) {
247 int ret = ffs_adec(affs, a_buf[a_prod], ABUFSZ);
248 if (ret < 0)
249 eof++;
250 if (ret > 0) {
251 a_len[a_prod] = ret;
252 a_prod = (a_prod + 1) & (AUDIOBUFS - 1);
255 if (video && (!audio || eof || vsync())) {
256 int ignore = jump && (vnum++ % (jump + 1));
257 void *buf;
258 int ret = ffs_vdec(vffs, ignore ? NULL : &buf);
259 if (ret < 0)
260 eof++;
261 if (ret > 0)
262 draw_frame((void *) buf, ret);
263 } else {
264 stroll();
267 exited = 1;
270 static void oss_init(void)
272 int rate, ch, bps;
273 afd = open("/dev/dsp", O_RDWR);
274 if (afd < 0) {
275 fprintf(stderr, "cannot open /dev/dsp\n");
276 exit(1);
278 ffs_ainfo(affs, &rate, &bps, &ch);
279 ioctl(afd, SOUND_PCM_WRITE_RATE, &rate);
280 ioctl(afd, SOUND_PCM_WRITE_CHANNELS, &ch);
281 ioctl(afd, SOUND_PCM_WRITE_BITS, &bps);
284 static void oss_close(void)
286 close(afd);
289 static void *process_audio(void *dat)
291 oss_init();
292 while (1) {
293 while (!a_reset && (a_conswait() || paused) && !exited)
294 stroll();
295 if (exited)
296 goto ret;
297 if (a_reset) {
298 if (a_reset == 1)
299 a_cons = a_prod;
300 a_reset = 0;
301 continue;
303 write(afd, a_buf[a_cons], a_len[a_cons]);
304 a_cons = (a_cons + 1) & (AUDIOBUFS - 1);
306 ret:
307 oss_close();
308 return NULL;
311 static void term_setup(void)
313 struct termios newtermios;
314 tcgetattr(STDIN_FILENO, &termios);
315 newtermios = termios;
316 newtermios.c_lflag &= ~ICANON;
317 newtermios.c_lflag &= ~ECHO;
318 tcsetattr(STDIN_FILENO, TCSAFLUSH, &newtermios);
319 fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | O_NONBLOCK);
322 static void term_cleanup(void)
324 tcsetattr(STDIN_FILENO, 0, &termios);
327 static void sigcont(int sig)
329 term_setup();
332 static char *usage = "usage: fbff [options] file\n"
333 "\noptions:\n"
334 " -z x zoom the screen using ffmpeg\n"
335 " -m x magnify the screen by repeating pixels\n"
336 " -j x jump every x video frames; for slow machines\n"
337 " -f start full screen\n"
338 " -v x select video stream; '-' disables video\n"
339 " -a x select audio stream; '-' disables audio\n"
340 " -s always synchronize; useful for files with bad video framerate\n"
341 " -t use time based seeking; only if the default does't work\n"
342 " -r adjust the video to the right of the screen\n"
343 " -b adjust the video to the bottom of the screen\n\n";
345 static void read_args(int argc, char *argv[])
347 int i = 1;
348 while (i < argc) {
349 if (!strcmp(argv[i], "-m"))
350 magnify = atoi(argv[++i]);
351 if (!strcmp(argv[i], "-z"))
352 zoom = atof(argv[++i]);
353 if (!strcmp(argv[i], "-j"))
354 jump = atoi(argv[++i]);
355 if (!strcmp(argv[i], "-f"))
356 fullscreen = 1;
357 if (!strncmp(argv[i], "-s", 2))
358 sync_period = isdigit(argv[i][2]) ? atoi(argv[i] + 2) : 1;
359 if (!strcmp(argv[i], "-v"))
360 video = argv[++i][0] == '-' ? 0 : atoi(argv[i]) + 2;
361 if (!strcmp(argv[i], "-a"))
362 audio = argv[++i][0] == '-' ? 0 : atoi(argv[i]) + 2;
363 if (!strcmp(argv[i], "-t"))
364 frame_jmp = 1024;
365 if (!strcmp(argv[i], "-h"))
366 printf(usage);
367 if (!strcmp(argv[i], "-r"))
368 rjust = 1;
369 if (!strcmp(argv[i], "-b"))
370 bjust = 1;
371 i++;
375 int main(int argc, char *argv[])
377 pthread_t a_thread;
378 char *path = argv[argc - 1];
379 if (argc < 2) {
380 printf("usage: %s [options] filename\n", argv[0]);
381 return 1;
383 read_args(argc, argv);
384 ffs_globinit();
385 if (video && !(vffs = ffs_alloc(path, FFS_VIDEO | (video - 1))))
386 video = 0;
387 if (audio && !(affs = ffs_alloc(path, FFS_AUDIO | (audio - 1))))
388 audio = 0;
389 if (!video && !audio)
390 return 1;
391 if (audio)
392 pthread_create(&a_thread, NULL, process_audio, NULL);
393 if (video) {
394 int w, h;
395 if (fb_init())
396 return 1;
397 ffs_vinfo(vffs, &w, &h);
398 if (magnify != 1 && sizeof(fbval_t) != FBM_BPP(fb_mode()))
399 fprintf(stderr, "fbff: magnify != 1 and fbval_t doesn't match\n");
400 if (fullscreen) {
401 float hz = (float) fb_rows() / h / magnify;
402 float wz = (float) fb_cols() / w / magnify;
403 zoom = hz < wz ? hz : wz;
405 ffs_vsetup(vffs, zoom, fb_mode());
407 term_setup();
408 signal(SIGCONT, sigcont);
409 mainloop();
410 term_cleanup();
412 if (video) {
413 fb_free();
414 ffs_free(vffs);
416 if (audio) {
417 pthread_join(a_thread, NULL);
418 ffs_free(affs);
420 return 0;