use average frame size and duration for seeking
[minmad.git] / minmad.c
blob0e674428e07c624f742b72cf46aa75d3baf91803
1 /*
2 * minmad - a minimal mp3 player using libmad and oss
4 * Copyright (C) 2009-2011 Ali Gholami Rudi
6 * This program is released under GNU GPL version 2.
7 */
8 #include <ctype.h>
9 #include <fcntl.h>
10 #include <pty.h>
11 #include <signal.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <sys/stat.h>
16 #include <sys/mman.h>
17 #include <sys/poll.h>
18 #include <termios.h>
19 #include <unistd.h>
20 #include <sys/soundcard.h>
21 #include <mad.h>
23 #define CTRLKEY(x) ((x) - 96)
24 #define MIN(a, b) ((a) < (b) ? (a) : (b))
25 #define MAX(a, b) ((a) > (b) ? (a) : (b))
26 #define JMP3 600
27 #define JMP2 60
28 #define JMP1 10
30 static int afd;
31 static unsigned char *mem;
32 static unsigned long len;
33 static struct mad_decoder decoder;
34 static unsigned long pos;
35 static int frame_sz; /* frame size */
36 static int frame_ms; /* frame duration in milliseconds */
37 static int count;
38 static mad_timer_t played;
39 static unsigned int rate;
41 static struct termios termios;
42 static int exited;
43 static int paused;
45 static int readkey(void)
47 char b;
48 if (read(STDIN_FILENO, &b, 1) <= 0)
49 return -1;
50 return b;
53 static void updatepos(void)
55 int sz, ms;
56 if (decoder.sync) {
57 pos = decoder.sync->stream.this_frame - mem;
58 sz = decoder.sync->stream.next_frame -
59 decoder.sync->stream.this_frame;
60 ms = mad_timer_count(decoder.sync->frame.header.duration,
61 MAD_UNITS_MILLISECONDS);
62 frame_ms = frame_ms ? ((frame_ms << 3) - frame_ms + ms) >> 3 : ms;
63 frame_sz = frame_sz ? ((frame_sz << 3) - frame_sz + sz) >> 3 : sz;
67 static void printinfo(void)
69 int per = pos * 1000.0 / len;
70 int dur = mad_timer_count(played, MAD_UNITS_DECISECONDS);
71 int loc = pos * frame_ms / frame_sz / 1000;
72 printf("minmad: %3d:%02d %3d.%d%% %8d.%ds\r",
73 loc / 60, loc % 60,
74 per / 10, per % 10, dur / 10, dur % 10);
75 fflush(stdout);
78 static int getcount(int def)
80 int result = count ? count : def;
81 count = 0;
82 return result;
85 static void seek(int n)
87 int diff = n * frame_sz * 1000 / (frame_ms ? frame_ms : 40);
88 pos = MAX(0, MIN(len, pos + diff));
91 static void seek_thousands(int n)
93 pos = len * (float) n / 1000;
94 pos -= pos % frame_sz;
97 static int execkey(void)
99 int c;
100 updatepos();
101 while ((c = readkey()) != -1) {
102 switch (c) {
103 case 'J':
104 seek(JMP3 * getcount(1));
105 return 1;
106 case 'K':
107 seek(-JMP3 * getcount(1));
108 return 1;
109 case 'j':
110 seek(JMP2 * getcount(1));
111 return 1;
112 case 'k':
113 seek(-JMP2 * getcount(1));
114 return 1;
115 case 'l':
116 seek(JMP1 * getcount(1));
117 return 1;
118 case 'h':
119 seek(-JMP1 * getcount(1));
120 return 1;
121 case '%':
122 seek_thousands(getcount(0) * 10);
123 return 1;
124 case 'i':
125 printinfo();
126 break;
127 case 'p':
128 case ' ':
129 paused = !paused;
130 return 1;
131 case 'q':
132 exited = 1;
133 return 1;
134 case 27:
135 count = 0;
136 break;
137 default:
138 if (isdigit(c))
139 count = count * 10 + c - '0';
142 return 0;
145 static enum mad_flow input(void *data, struct mad_stream *stream)
147 static unsigned long cpos;
148 if (pos && pos == cpos) {
149 exited = 1;
150 return MAD_FLOW_STOP;
152 cpos = pos;
153 mad_stream_buffer(stream, mem + pos, len - pos);
154 return MAD_FLOW_CONTINUE;
157 static signed int scale(mad_fixed_t sample)
159 /* round */
160 sample += (1L << (MAD_F_FRACBITS - 16));
161 /* clip */
162 if (sample >= MAD_F_ONE)
163 sample = MAD_F_ONE - 1;
164 else if (sample < -MAD_F_ONE)
165 sample = -MAD_F_ONE;
166 /* quantize */
167 return sample >> (MAD_F_FRACBITS + 1 - 16);
170 static void push_sample(char *buf, mad_fixed_t sample)
172 *buf++ = (sample >> 0) & 0xff;
173 *buf++ = (sample >> 8) & 0xff;
176 static void oss_conf(void)
178 int ch = 2;
179 int bits = 16;
180 ioctl(afd, SOUND_PCM_WRITE_RATE, &rate);
181 ioctl(afd, SOUND_PCM_WRITE_CHANNELS, &ch);
182 ioctl(afd, SOUND_PCM_WRITE_BITS, &bits);
185 static char mixed[1 << 20];
186 static enum mad_flow output(void *data,
187 struct mad_header const *header,
188 struct mad_pcm *pcm)
190 int i;
191 int right = pcm->channels > 1 ? 1 : 0;
192 mad_timer_add(&played, decoder.sync->frame.header.duration);
193 for (i = 0; i < pcm->length; i++) {
194 push_sample(mixed + i * 4, scale(pcm->samples[0][i]));
195 push_sample(mixed + i * 4 + 2, scale(pcm->samples[right][i]));
197 if (header->samplerate != rate) {
198 rate = header->samplerate;
199 oss_conf();
201 write(afd, mixed, pcm->length * 4);
202 return execkey() ? MAD_FLOW_STOP : MAD_FLOW_CONTINUE;
205 static enum mad_flow error(void *data,
206 struct mad_stream *stream,
207 struct mad_frame *frame)
209 return MAD_FLOW_CONTINUE;
212 static void waitkey(void)
214 struct pollfd ufds[1];
215 ufds[0].fd = STDIN_FILENO;
216 ufds[0].events = POLLIN;
217 poll(ufds, 1, -1);
220 static void decode(void)
222 mad_decoder_init(&decoder, NULL, input, 0, 0, output, error, 0);
223 while (!exited) {
224 if (paused) {
225 waitkey();
226 execkey();
227 } else {
228 mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC);
231 mad_decoder_finish(&decoder);
234 static void oss_init(void)
236 afd = open("/dev/dsp", O_RDWR);
237 if (afd < 0) {
238 fprintf(stderr, "cannot open /dev/dsp\n");
239 exit(1);
243 static void oss_close(void)
245 close(afd);
248 static void term_setup(void)
250 struct termios newtermios;
251 tcgetattr(STDIN_FILENO, &termios);
252 newtermios = termios;
253 newtermios.c_lflag &= ~ICANON;
254 newtermios.c_lflag &= ~ECHO;
255 tcsetattr(STDIN_FILENO, TCSAFLUSH, &newtermios);
256 fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | O_NONBLOCK);
259 static void term_cleanup(void)
261 tcsetattr(STDIN_FILENO, 0, &termios);
264 static void sigcont(int sig)
266 term_setup();
269 int main(int argc, char *argv[])
271 struct stat stat;
272 int fd;
273 if (argc < 2)
274 return 1;
275 fd = open(argv[1], O_RDONLY);
276 if (fstat(fd, &stat) == -1 || stat.st_size == 0)
277 return 1;
278 mem = mmap(0, stat.st_size, PROT_READ, MAP_SHARED, fd, 0);
279 len = stat.st_size;
280 if (mem == MAP_FAILED)
281 return 1;
282 term_setup();
283 signal(SIGCONT, sigcont);
284 oss_init();
285 decode();
286 oss_close();
287 term_cleanup();
288 munmap(mem, stat.st_size);
289 close(fd);
290 return 0;