change is_vsync() to prevent audio delay after a while
[fbff.git] / fbff.c
blobf085cdb258f3f7b365bab3aac02cb3ae3eaad25b
1 /*
2 * fbff - a small ffmpeg-based framebuffer/oss media player
4 * Copyright (C) 2009-2011 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 frame_jmp = 1; /* the changes to pos_cur for each frame */
29 static int afd; /* oss fd */
31 static int arg;
32 static struct termios termios;
33 static int paused;
34 static int exited;
36 static float zoom = 1;
37 static int magnify = 1;
38 static int jump = 0;
39 static int fullscreen = 0;
40 static int audio = 1;
41 static int video = 1;
42 static int just = 0;
44 static struct ffs *affs; /* audio ffmpeg stream */
45 static struct ffs *vffs; /* video ffmpeg stream */
47 static void stroll(void)
49 usleep(10000);
52 static void draw_frame(void *img, int linelen)
54 int w, h;
55 fbval_t buf[1 << 14];
56 int nr, nc, cb;
57 int i, r, c;
58 ffs_vinfo(vffs, &w, &h);
59 nr = MIN(h * zoom, fb_rows() / magnify);
60 nc = MIN(w * zoom, fb_cols() / magnify);
61 cb = just ? fb_cols() - nc * magnify : 0;
62 for (r = 0; r < nr; r++) {
63 fbval_t *row = img + r * linelen;
64 if (magnify == 1) {
65 fb_set(r, cb, row, nc);
66 continue;
68 for (c = 0; c < nc; c++)
69 for (i = 0; i < magnify; i++)
70 buf[c * magnify + i] = row[c];
71 for (i = 0; i < magnify; i++)
72 fb_set(r * magnify + i, cb, buf, nc * magnify);
76 #define ABUFSZ (1 << 18)
78 static int a_cons;
79 static int a_prod;
80 static char a_buf[AUDIOBUFS][ABUFSZ];
81 static int a_len[AUDIOBUFS];
82 static int a_reset;
84 static int a_conswait(void)
86 return a_cons == a_prod;
89 static int a_prodwait(void)
91 return ((a_prod + 1) & (AUDIOBUFS - 1)) == a_cons;
94 static void a_doreset(int pause)
96 a_reset = 1 + pause;
97 while (audio && a_reset)
98 stroll();
101 static int readkey(void)
103 char b;
104 if (read(STDIN_FILENO, &b, 1) <= 0)
105 return -1;
106 return b;
109 static void waitkey(void)
111 struct pollfd ufds[1];
112 ufds[0].fd = STDIN_FILENO;
113 ufds[0].events = POLLIN;
114 poll(ufds, 1, -1);
117 static int ffarg(void)
119 int n = arg;
120 arg = 0;
121 return n ? n : 1;
124 static void ffjmp(int n, int rel)
126 struct ffs *ffs = video ? vffs : affs;
127 long pos = ffs_pos(ffs, n);
128 a_doreset(0);
129 if (audio)
130 ffs_seek(affs, pos, frame_jmp);
131 if (video)
132 ffs_seek(vffs, pos, frame_jmp);
135 static void printinfo(void)
137 struct ffs *ffs = video ? vffs : affs;
138 printf("fbff: %8lx\r", ffs_pos(ffs, 0));
139 fflush(stdout);
142 #define JMP1 (1 << 5)
143 #define JMP2 (JMP1 << 3)
144 #define JMP3 (JMP2 << 5)
146 static void execkey(void)
148 int c;
149 while ((c = readkey()) != -1) {
150 switch (c) {
151 case 'q':
152 exited = 1;
153 break;
154 case 'l':
155 ffjmp(ffarg() * JMP1, 1);
156 break;
157 case 'h':
158 ffjmp(-ffarg() * JMP1, 1);
159 break;
160 case 'j':
161 ffjmp(ffarg() * JMP2, 1);
162 break;
163 case 'k':
164 ffjmp(-ffarg() * JMP2, 1);
165 break;
166 case 'J':
167 ffjmp(ffarg() * JMP3, 1);
168 break;
169 case 'K':
170 ffjmp(-ffarg() * JMP3, 1);
171 break;
172 case '%':
173 if (arg)
174 ffjmp(100, 0);
175 break;
176 case 'i':
177 printinfo();
178 break;
179 case ' ':
180 case 'p':
181 paused = !paused;
182 break;
183 case 27:
184 arg = 0;
185 break;
186 default:
187 if (isdigit(c))
188 arg = arg * 10 + c - '0';
193 /* can more video packets be read */
194 static int is_vsync(void)
196 int cur = ffs_seq(affs, 0);
197 int all = ffs_seq(affs, 1);
198 int video = cur ? (all - cur) * AUDIOBUFS / cur : AUDIOBUFS;
199 /* video ffs should wait for audio ffs (ignoring buffered packets) */
200 return ffs_seq(vffs, 1) + AUDIOBUFS + video < ffs_seq(affs, 1);
203 static void mainloop(void)
205 int eof = 0;
206 while (1) {
207 execkey();
208 if (exited)
209 break;
210 if (paused) {
211 a_doreset(1);
212 waitkey();
213 continue;
215 while (audio && !eof && !a_prodwait()) {
216 int ret = ffs_adec(affs, a_buf[a_prod], ABUFSZ);
217 if (ret < 0)
218 eof = 1;
219 if (ret > 0) {
220 a_len[a_prod] = ret;
221 a_prod = (a_prod + 1) & (AUDIOBUFS - 1);
224 if (video && (!audio || eof || is_vsync())) {
225 int ignore = jump && !(ffs_seq(vffs, 0) % (jump + 1));
226 char *buf;
227 int ret = ffs_vdec(vffs, ignore ? NULL : &buf);
228 if (ret < 0)
229 break;
230 if (ret > 0)
231 draw_frame((void *) buf, ret);
232 ffs_wait(vffs);
233 } else {
234 stroll();
237 exited = 1;
240 static void oss_init(void)
242 int rate, ch, bps;
243 afd = open("/dev/dsp", O_RDWR);
244 if (afd < 0) {
245 fprintf(stderr, "cannot open /dev/dsp\n");
246 exit(1);
248 ffs_ainfo(affs, &rate, &bps, &ch);
249 ioctl(afd, SOUND_PCM_WRITE_RATE, &rate);
250 ioctl(afd, SOUND_PCM_WRITE_CHANNELS, &ch);
251 ioctl(afd, SOUND_PCM_WRITE_BITS, &bps);
254 static void oss_close(void)
256 close(afd);
259 static void *process_audio(void *dat)
261 oss_init();
262 while (1) {
263 while (!a_reset && a_conswait() && !exited)
264 stroll();
265 if (exited)
266 goto ret;
267 if (a_reset) {
268 if (a_reset == 1)
269 a_cons = a_prod;
270 a_reset = 0;
271 continue;
273 write(afd, a_buf[a_cons], a_len[a_cons]);
274 a_cons = (a_cons + 1) & (AUDIOBUFS - 1);
276 ret:
277 oss_close();
278 return NULL;
281 static void term_setup(void)
283 struct termios newtermios;
284 tcgetattr(STDIN_FILENO, &termios);
285 newtermios = termios;
286 newtermios.c_lflag &= ~ICANON;
287 newtermios.c_lflag &= ~ECHO;
288 tcsetattr(STDIN_FILENO, TCSAFLUSH, &newtermios);
289 fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | O_NONBLOCK);
292 static void term_cleanup(void)
294 tcsetattr(STDIN_FILENO, 0, &termios);
297 static void sigcont(int sig)
299 term_setup();
302 static char *usage = "usage: fbff [options] file\n"
303 "\noptions:\n"
304 " -m x magnify the screen by repeating pixels\n"
305 " -z x zoom the screen using ffmpeg\n"
306 " -j x jump every x video frames; for slow machines\n"
307 " -f start full screen\n"
308 " -v video only playback\n"
309 " -a audio only playback\n"
310 " -t use time based seeking; only if the default does't work\n"
311 " -R adjust the video to the right of the screen\n\n";
313 static void read_args(int argc, char *argv[])
315 int i = 1;
316 while (i < argc) {
317 if (!strcmp(argv[i], "-m"))
318 magnify = atoi(argv[++i]);
319 if (!strcmp(argv[i], "-z"))
320 zoom = atof(argv[++i]);
321 if (!strcmp(argv[i], "-j"))
322 jump = atoi(argv[++i]);
323 if (!strcmp(argv[i], "-f"))
324 fullscreen = 1;
325 if (!strcmp(argv[i], "-a"))
326 video = 0;
327 if (!strcmp(argv[i], "-v"))
328 audio = 0;
329 if (!strcmp(argv[i], "-t"))
330 frame_jmp = 1024 / 32;
331 if (!strcmp(argv[i], "-h"))
332 printf(usage);
333 if (!strcmp(argv[i], "-R"))
334 just = 1;
335 i++;
339 int main(int argc, char *argv[])
341 pthread_t a_thread;
342 char *path = argv[argc - 1];
343 if (argc < 2) {
344 printf("usage: %s [options] filename\n", argv[0]);
345 return 1;
347 read_args(argc, argv);
348 ffs_globinit();
349 if (video && !(vffs = ffs_alloc(path, 1)))
350 video = 0;
351 if (audio && !(affs = ffs_alloc(path, 0)))
352 audio = 0;
353 if (!video && !audio)
354 return 1;
355 if (audio)
356 pthread_create(&a_thread, NULL, process_audio, NULL);
357 if (video) {
358 int w, h;
359 if (fb_init())
360 return 1;
361 ffs_vinfo(vffs, &w, &h);
362 if (magnify != 1 && sizeof(fbval_t) != FBM_BPP(fb_mode()))
363 fprintf(stderr, "fbff: magnify != 1 and fbval_t doesn't match\n");
364 if (fullscreen)
365 zoom = (float) fb_cols() / w / magnify;
366 ffs_vsetup(vffs, zoom, fb_mode());
368 term_setup();
369 signal(SIGCONT, sigcont);
370 mainloop();
371 term_cleanup();
373 if (video) {
374 fb_free();
375 ffs_free(vffs);
377 if (audio) {
378 pthread_join(a_thread, NULL);
379 ffs_free(affs);
381 return 0;