handle eof for audio-only playback
[fbff.git] / fbff.c
blob943c04d7fe7bb085533ce96f772b1cb446c0a009
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 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 audio = 1;
38 static int video = 1;
39 static int just = 0;
40 static int frame_jmp = 1; /* the changes to pos_cur for each frame */
42 static struct ffs *affs; /* audio ffmpeg stream */
43 static struct ffs *vffs; /* video ffmpeg stream */
44 static int afd; /* oss fd */
45 static int vnum; /* decoded video frame count */
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 static void mainloop(void)
195 int eof = 0;
196 while (eof < audio + video) {
197 execkey();
198 if (exited)
199 break;
200 if (paused) {
201 a_doreset(1);
202 waitkey();
203 continue;
205 while (audio && !eof && !a_prodwait()) {
206 int ret = ffs_adec(affs, a_buf[a_prod], ABUFSZ);
207 if (ret < 0)
208 eof++;
209 if (ret > 0) {
210 a_len[a_prod] = ret;
211 a_prod = (a_prod + 1) & (AUDIOBUFS - 1);
214 if (video && (!audio || eof || ffs_vsync(vffs, affs, AUDIOBUFS))) {
215 int ignore = jump && (vnum++ % (jump + 1));
216 void *buf;
217 int ret = ffs_vdec(vffs, ignore ? NULL : &buf);
218 if (ret < 0)
219 eof++;
220 if (ret > 0)
221 draw_frame((void *) buf, ret);
222 ffs_wait(vffs);
223 } else {
224 stroll();
227 exited = 1;
230 static void oss_init(void)
232 int rate, ch, bps;
233 afd = open("/dev/dsp", O_RDWR);
234 if (afd < 0) {
235 fprintf(stderr, "cannot open /dev/dsp\n");
236 exit(1);
238 ffs_ainfo(affs, &rate, &bps, &ch);
239 ioctl(afd, SOUND_PCM_WRITE_RATE, &rate);
240 ioctl(afd, SOUND_PCM_WRITE_CHANNELS, &ch);
241 ioctl(afd, SOUND_PCM_WRITE_BITS, &bps);
244 static void oss_close(void)
246 close(afd);
249 static void *process_audio(void *dat)
251 oss_init();
252 while (1) {
253 while (!a_reset && (a_conswait() || paused) && !exited)
254 stroll();
255 if (exited)
256 goto ret;
257 if (a_reset) {
258 if (a_reset == 1)
259 a_cons = a_prod;
260 a_reset = 0;
261 continue;
263 write(afd, a_buf[a_cons], a_len[a_cons]);
264 a_cons = (a_cons + 1) & (AUDIOBUFS - 1);
266 ret:
267 oss_close();
268 return NULL;
271 static void term_setup(void)
273 struct termios newtermios;
274 tcgetattr(STDIN_FILENO, &termios);
275 newtermios = termios;
276 newtermios.c_lflag &= ~ICANON;
277 newtermios.c_lflag &= ~ECHO;
278 tcsetattr(STDIN_FILENO, TCSAFLUSH, &newtermios);
279 fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | O_NONBLOCK);
282 static void term_cleanup(void)
284 tcsetattr(STDIN_FILENO, 0, &termios);
287 static void sigcont(int sig)
289 term_setup();
292 static char *usage = "usage: fbff [options] file\n"
293 "\noptions:\n"
294 " -m x magnify the screen by repeating pixels\n"
295 " -z x zoom the screen using ffmpeg\n"
296 " -j x jump every x video frames; for slow machines\n"
297 " -f start full screen\n"
298 " -v video only playback\n"
299 " -a audio only playback\n"
300 " -t use time based seeking; only if the default does't work\n"
301 " -R adjust the video to the right of the screen\n\n";
303 static void read_args(int argc, char *argv[])
305 int i = 1;
306 while (i < argc) {
307 if (!strcmp(argv[i], "-m"))
308 magnify = atoi(argv[++i]);
309 if (!strcmp(argv[i], "-z"))
310 zoom = atof(argv[++i]);
311 if (!strcmp(argv[i], "-j"))
312 jump = atoi(argv[++i]);
313 if (!strcmp(argv[i], "-f"))
314 fullscreen = 1;
315 if (!strcmp(argv[i], "-a"))
316 video = 0;
317 if (!strcmp(argv[i], "-v"))
318 audio = 0;
319 if (!strcmp(argv[i], "-t"))
320 frame_jmp = 1024 / 32;
321 if (!strcmp(argv[i], "-h"))
322 printf(usage);
323 if (!strcmp(argv[i], "-R"))
324 just = 1;
325 i++;
329 int main(int argc, char *argv[])
331 pthread_t a_thread;
332 char *path = argv[argc - 1];
333 if (argc < 2) {
334 printf("usage: %s [options] filename\n", argv[0]);
335 return 1;
337 read_args(argc, argv);
338 ffs_globinit();
339 if (video && !(vffs = ffs_alloc(path, 1)))
340 video = 0;
341 if (audio && !(affs = ffs_alloc(path, 0)))
342 audio = 0;
343 if (!video && !audio)
344 return 1;
345 if (audio)
346 pthread_create(&a_thread, NULL, process_audio, NULL);
347 if (video) {
348 int w, h;
349 if (fb_init())
350 return 1;
351 ffs_vinfo(vffs, &w, &h);
352 if (magnify != 1 && sizeof(fbval_t) != FBM_BPP(fb_mode()))
353 fprintf(stderr, "fbff: magnify != 1 and fbval_t doesn't match\n");
354 if (fullscreen)
355 zoom = (float) fb_cols() / w / magnify;
356 ffs_vsetup(vffs, zoom, fb_mode());
358 term_setup();
359 signal(SIGCONT, sigcont);
360 mainloop();
361 term_cleanup();
363 if (video) {
364 fb_free();
365 ffs_free(vffs);
367 if (audio) {
368 pthread_join(a_thread, NULL);
369 ffs_free(affs);
371 return 0;