1c32b365e976497333458064f0883d0c0d80909f
[fbff.git] / fbff.c
blob1c32b365e976497333458064f0883d0c0d80909f
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_cnt = 32; /* synchronization steps */
50 static int sync_cur; /* synchronization steps left */
52 static void stroll(void)
54 usleep(10000);
57 static void draw_frame(void *img, int linelen)
59 int w, h;
60 fbval_t buf[1 << 14];
61 int nr, nc, cb, rb;
62 int i, r, c;
63 ffs_vinfo(vffs, &w, &h);
64 nr = MIN(h * zoom, fb_rows() / magnify);
65 nc = MIN(w * zoom, fb_cols() / magnify);
66 cb = rjust ? fb_cols() - nc * magnify : 0;
67 rb = bjust ? fb_rows() - nr * magnify : 0;
68 for (r = 0; r < nr; r++) {
69 fbval_t *row = img + r * linelen;
70 if (magnify == 1) {
71 fb_set(rb + r, cb, row, nc);
72 continue;
74 for (c = 0; c < nc; c++)
75 for (i = 0; i < magnify; i++)
76 buf[c * magnify + i] = row[c];
77 for (i = 0; i < magnify; i++)
78 fb_set((rb + r) * magnify + i, cb, buf, nc * magnify);
82 #define ABUFSZ (1 << 18)
84 static int a_cons;
85 static int a_prod;
86 static char a_buf[AUDIOBUFS][ABUFSZ];
87 static int a_len[AUDIOBUFS];
88 static int a_reset;
90 static int a_conswait(void)
92 return a_cons == a_prod;
95 static int a_prodwait(void)
97 return ((a_prod + 1) & (AUDIOBUFS - 1)) == a_cons;
100 static void a_doreset(int pause)
102 a_reset = 1 + pause;
103 while (audio && a_reset)
104 stroll();
107 static int readkey(void)
109 char b;
110 if (read(STDIN_FILENO, &b, 1) <= 0)
111 return -1;
112 return b;
115 static void waitkey(void)
117 struct pollfd ufds[1];
118 ufds[0].fd = STDIN_FILENO;
119 ufds[0].events = POLLIN;
120 poll(ufds, 1, -1);
123 static int ffarg(int def)
125 int n = arg;
126 arg = 0;
127 return n ? n : def;
130 static void ffjmp(int n, int rel)
132 struct ffs *ffs = video ? vffs : affs;
133 long pos = ffs_pos(ffs, n);
134 a_doreset(0);
135 sync_cur = sync_cnt;
136 if (audio)
137 ffs_seek(affs, pos, frame_jmp);
138 if (video)
139 ffs_seek(vffs, pos, frame_jmp);
142 static void printinfo(void)
144 struct ffs *ffs = video ? vffs : affs;
145 printf("fbff: %8lx \t (AV: %d)\r",
146 ffs_pos(ffs, 0), video && audio ? ffs_avdiff(vffs, affs) : 0);
147 fflush(stdout);
150 #define JMP1 (1 << 5)
151 #define JMP2 (JMP1 << 3)
152 #define JMP3 (JMP2 << 5)
154 static void execkey(void)
156 int c;
157 while ((c = readkey()) != -1) {
158 switch (c) {
159 case 'q':
160 exited = 1;
161 break;
162 case 'l':
163 ffjmp(ffarg(1) * JMP1, 1);
164 break;
165 case 'h':
166 ffjmp(-ffarg(1) * JMP1, 1);
167 break;
168 case 'j':
169 ffjmp(ffarg(1) * JMP2, 1);
170 break;
171 case 'k':
172 ffjmp(-ffarg(1) * JMP2, 1);
173 break;
174 case 'J':
175 ffjmp(ffarg(1) * JMP3, 1);
176 break;
177 case 'K':
178 ffjmp(-ffarg(1) * JMP3, 1);
179 break;
180 case '%':
181 if (arg)
182 ffjmp(100, 0);
183 break;
184 case 'i':
185 printinfo();
186 break;
187 case ' ':
188 case 'p':
189 paused = !paused;
190 sync_cur = sync_cnt;
191 break;
192 case '-':
193 sync_diff = -ffarg(0);
194 break;
195 case '+':
196 sync_diff = ffarg(0);
197 break;
198 case 'a':
199 sync_diff = ffs_avdiff(vffs, affs);
200 break;
201 case 'c':
202 sync_cnt = ffarg(0);
203 break;
204 case 's':
205 sync_cur = ffarg(sync_cnt);
206 break;
207 case 27:
208 arg = 0;
209 break;
210 default:
211 if (isdigit(c))
212 arg = arg * 10 + c - '0';
217 /* return nonzero if one more video frame can be decoded */
218 static int vsync(void)
220 if (sync_cur > 0) {
221 sync_cur--;
222 return ffs_avdiff(vffs, affs) >= sync_diff;
224 ffs_wait(vffs);
225 return 1;
228 static void mainloop(void)
230 int eof = 0;
231 while (eof < audio + video) {
232 execkey();
233 if (exited)
234 break;
235 if (paused) {
236 a_doreset(1);
237 waitkey();
238 continue;
240 while (audio && !eof && !a_prodwait()) {
241 int ret = ffs_adec(affs, a_buf[a_prod], ABUFSZ);
242 if (ret < 0)
243 eof++;
244 if (ret > 0) {
245 a_len[a_prod] = ret;
246 a_prod = (a_prod + 1) & (AUDIOBUFS - 1);
249 if (video && (!audio || eof || vsync())) {
250 int ignore = jump && (vnum++ % (jump + 1));
251 void *buf;
252 int ret = ffs_vdec(vffs, ignore ? NULL : &buf);
253 if (ret < 0)
254 eof++;
255 if (ret > 0)
256 draw_frame((void *) buf, ret);
257 } else {
258 stroll();
261 exited = 1;
264 static void oss_init(void)
266 int rate, ch, bps;
267 afd = open("/dev/dsp", O_RDWR);
268 if (afd < 0) {
269 fprintf(stderr, "cannot open /dev/dsp\n");
270 exit(1);
272 ffs_ainfo(affs, &rate, &bps, &ch);
273 ioctl(afd, SOUND_PCM_WRITE_RATE, &rate);
274 ioctl(afd, SOUND_PCM_WRITE_CHANNELS, &ch);
275 ioctl(afd, SOUND_PCM_WRITE_BITS, &bps);
278 static void oss_close(void)
280 close(afd);
283 static void *process_audio(void *dat)
285 oss_init();
286 while (1) {
287 while (!a_reset && (a_conswait() || paused) && !exited)
288 stroll();
289 if (exited)
290 goto ret;
291 if (a_reset) {
292 if (a_reset == 1)
293 a_cons = a_prod;
294 a_reset = 0;
295 continue;
297 write(afd, a_buf[a_cons], a_len[a_cons]);
298 a_cons = (a_cons + 1) & (AUDIOBUFS - 1);
300 ret:
301 oss_close();
302 return NULL;
305 static void term_setup(void)
307 struct termios newtermios;
308 tcgetattr(STDIN_FILENO, &termios);
309 newtermios = termios;
310 newtermios.c_lflag &= ~ICANON;
311 newtermios.c_lflag &= ~ECHO;
312 tcsetattr(STDIN_FILENO, TCSAFLUSH, &newtermios);
313 fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | O_NONBLOCK);
316 static void term_cleanup(void)
318 tcsetattr(STDIN_FILENO, 0, &termios);
321 static void sigcont(int sig)
323 term_setup();
326 static char *usage = "usage: fbff [options] file\n"
327 "\noptions:\n"
328 " -z x zoom the screen using ffmpeg\n"
329 " -m x magnify the screen by repeating pixels\n"
330 " -j x jump every x video frames; for slow machines\n"
331 " -f start full screen\n"
332 " -v x select video stream; '-' disables video\n"
333 " -a x select audio stream; '-' disables audio\n"
334 " -s always synchronize; useful for files with bad video framerate\n"
335 " -t use time based seeking; only if the default does't work\n"
336 " -r adjust the video to the right of the screen\n"
337 " -b adjust the video to the bottom of the screen\n\n";
339 static void read_args(int argc, char *argv[])
341 int i = 1;
342 while (i < argc) {
343 if (!strcmp(argv[i], "-m"))
344 magnify = atoi(argv[++i]);
345 if (!strcmp(argv[i], "-z"))
346 zoom = atof(argv[++i]);
347 if (!strcmp(argv[i], "-j"))
348 jump = atoi(argv[++i]);
349 if (!strcmp(argv[i], "-f"))
350 fullscreen = 1;
351 if (!strcmp(argv[i], "-s"))
352 sync_cnt = sync_cur = (1 << 30);
353 if (!strcmp(argv[i], "-v"))
354 video = argv[++i][0] == '-' ? 0 : atoi(argv[i]) + 2;
355 if (!strcmp(argv[i], "-a"))
356 audio = argv[++i][0] == '-' ? 0 : atoi(argv[i]) + 2;
357 if (!strcmp(argv[i], "-t"))
358 frame_jmp = 1024;
359 if (!strcmp(argv[i], "-h"))
360 printf(usage);
361 if (!strcmp(argv[i], "-r"))
362 rjust = 1;
363 if (!strcmp(argv[i], "-b"))
364 bjust = 1;
365 i++;
369 int main(int argc, char *argv[])
371 pthread_t a_thread;
372 char *path = argv[argc - 1];
373 if (argc < 2) {
374 printf("usage: %s [options] filename\n", argv[0]);
375 return 1;
377 read_args(argc, argv);
378 ffs_globinit();
379 if (video && !(vffs = ffs_alloc(path, FFS_VIDEO | (video - 1))))
380 video = 0;
381 if (audio && !(affs = ffs_alloc(path, FFS_AUDIO | (audio - 1))))
382 audio = 0;
383 if (!video && !audio)
384 return 1;
385 if (audio)
386 pthread_create(&a_thread, NULL, process_audio, NULL);
387 if (video) {
388 int w, h;
389 if (fb_init())
390 return 1;
391 ffs_vinfo(vffs, &w, &h);
392 if (magnify != 1 && sizeof(fbval_t) != FBM_BPP(fb_mode()))
393 fprintf(stderr, "fbff: magnify != 1 and fbval_t doesn't match\n");
394 if (fullscreen)
395 zoom = (float) fb_cols() / w / magnify;
396 ffs_vsetup(vffs, zoom, fb_mode());
398 term_setup();
399 signal(SIGCONT, sigcont);
400 mainloop();
401 term_cleanup();
403 if (video) {
404 fb_free();
405 ffs_free(vffs);
407 if (audio) {
408 pthread_join(a_thread, NULL);
409 ffs_free(affs);
411 return 0;