minmad: set oss fragment size
[minmad.git] / minmad.c
blobaa4a281b3f533022beb96e841ed2cb914e4640c1
1 /*
2 * minmad - a minimal mp3 player using libmad and oss
4 * Copyright (C) 2009-2016 Ali Gholami Rudi
6 * This program is released under the Modified BSD license.
7 */
8 #include <ctype.h>
9 #include <fcntl.h>
10 #include <pty.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <sys/stat.h>
15 #include <sys/poll.h>
16 #include <termios.h>
17 #include <unistd.h>
18 #include <sys/soundcard.h>
19 #include <mad.h>
21 #define MIN(a, b) ((a) < (b) ? (a) : (b))
22 #define MAX(a, b) ((a) > (b) ? (a) : (b))
24 static struct mad_decoder maddec;
25 static int afd; /* oss fd */
27 static char filename[128];
28 static int mfd; /* input file descriptor */
29 static long msize; /* file size */
30 static unsigned char mbuf[1 << 16];
31 static long mpos; /* the position of mbuf[] */
32 static long mlen; /* data in mbuf[] */
33 static long moff; /* offset into mbuf[] */
34 static long mark[256]; /* mark positions */
35 static int frame_sz; /* frame size */
36 static int frame_ms; /* frame duration in milliseconds */
37 static int played; /* playing time in milliseconds */
38 static int rate; /* current oss sample rate */
39 static int topause; /* planned pause (compared with played) */
41 static int exited;
42 static int paused;
43 static int domark;
44 static int dojump;
45 static int doseek;
46 static int count;
48 static int oss_open(void)
50 afd = open("/dev/dsp", O_WRONLY);
51 return afd < 0;
54 static void oss_close(void)
56 if (afd > 0) /* zero fd is used for input */
57 close(afd);
58 afd = 0;
59 rate = 0;
62 static void oss_conf(int rate, int ch, int bits)
64 int frag = 0x0003000b; /* 0xmmmmssss: 2^m fragments of size 2^s each */
65 ioctl(afd, SOUND_PCM_WRITE_CHANNELS, &ch);
66 ioctl(afd, SOUND_PCM_WRITE_BITS, &bits);
67 ioctl(afd, SOUND_PCM_WRITE_RATE, &rate);
68 ioctl(afd, SOUND_PCM_SETFRAGMENT, &frag);
71 static int cmdread(void)
73 char b;
74 if (read(0, &b, 1) <= 0)
75 return -1;
76 return (unsigned char) b;
79 static void cmdwait(void)
81 struct pollfd ufds[1];
82 ufds[0].fd = 0;
83 ufds[0].events = POLLIN;
84 poll(ufds, 1, -1);
87 static long muldiv64(long num, long mul, long div)
89 return (long long) num * mul / div;
92 static void cmdinfo(void)
94 int per = muldiv64(mpos + moff, 1000, msize);
95 int loc = muldiv64(mpos + moff, frame_ms, frame_sz * 1000);
96 printf("%c %02d.%d%% (%d:%02d:%02d - %04d.%ds) [%s]\r",
97 paused ? (afd < 0 ? '*' : ' ') : '>',
98 per / 10, per % 10,
99 loc / 3600, (loc % 3600) / 60, loc % 60,
100 played / 1000, (played / 100) % 10,
101 filename);
102 fflush(stdout);
105 static int cmdcount(int def)
107 int result = count ? count : def;
108 count = 0;
109 return result;
112 static void seek(long pos)
114 mark['\''] = mpos + moff;
115 mpos = MAX(0, MIN(msize, pos));
116 doseek = 1;
119 static void cmdseekrel(int n)
121 int diff = muldiv64(n, frame_sz * 1000, frame_ms ? frame_ms : 40);
122 seek(mpos + moff + diff);
125 static void cmdseek100(int n)
127 if (n <= 100)
128 seek(muldiv64(msize, n, 100));
131 static void cmdseek(int n)
133 long pos = muldiv64(n * 60, frame_sz * 1000, frame_ms ? frame_ms : 40);
134 seek(pos);
137 static int cmdpause(int pause)
139 if (!pause && paused) {
140 if (oss_open())
141 return 1;
142 paused = 0;
144 if (pause && !paused) {
145 oss_close();
146 paused = 1;
148 return 0;
151 static int cmdexec(void)
153 int c;
154 if (topause > 0 && topause <= played) {
155 topause = 0;
156 return !cmdpause(1);
158 while ((c = cmdread()) >= 0) {
159 if (domark) {
160 domark = 0;
161 mark[c] = mpos + moff;
162 return 0;
164 if (dojump) {
165 dojump = 0;
166 if (mark[c] > 0)
167 seek(mark[c]);
168 return mark[c] > 0;
170 switch (c) {
171 case 'J':
172 cmdseekrel(+600 * cmdcount(1));
173 return 1;
174 case 'K':
175 cmdseekrel(-600 * cmdcount(1));
176 return 1;
177 case 'j':
178 cmdseekrel(+60 * cmdcount(1));
179 return 1;
180 case 'k':
181 cmdseekrel(-60 * cmdcount(1));
182 return 1;
183 case 'l':
184 cmdseekrel(+10 * cmdcount(1));
185 return 1;
186 case 'h':
187 cmdseekrel(-10 * cmdcount(1));
188 return 1;
189 case '%':
190 cmdseek100(cmdcount(0));
191 return 1;
192 case 'G':
193 cmdseek(cmdcount(0));
194 return 1;
195 case 'i':
196 cmdinfo();
197 break;
198 case 'm':
199 domark = 1;
200 break;
201 case '\'':
202 dojump = 1;
203 break;
204 case 'p':
205 case ' ':
206 if (cmdpause(!paused))
207 break;
208 return 1;
209 case 'P':
210 topause = count ? played + cmdcount(0) * 60000 : 0;
211 break;
212 case 'q':
213 exited = 1;
214 return 1;
215 case 27:
216 count = 0;
217 break;
218 default:
219 if (isdigit(c))
220 count = count * 10 + c - '0';
223 return 0;
226 static enum mad_flow madinput(void *data, struct mad_stream *stream)
228 int nread = stream->next_frame ? stream->next_frame - mbuf : moff;
229 int nleft = mlen - nread;
230 int nr = 0;
231 if (doseek) {
232 doseek = 0;
233 nleft = 0;
234 nread = 0;
235 lseek(mfd, mpos, 0);
237 memmove(mbuf, mbuf + nread, nleft);
238 if (nleft < sizeof(mbuf)) {
239 if ((nr = read(mfd, mbuf + nleft, sizeof(mbuf) - nleft)) <= 0) {
240 exited = 1;
241 return MAD_FLOW_STOP;
244 mlen = nleft + nr;
245 mad_stream_buffer(stream, mbuf, mlen);
246 mpos += nread;
247 moff = 0;
248 return MAD_FLOW_CONTINUE;
251 static signed int madscale(mad_fixed_t sample)
253 sample += (1l << (MAD_F_FRACBITS - 16)); /* round */
254 if (sample >= MAD_F_ONE) /* clip */
255 sample = MAD_F_ONE - 1;
256 if (sample < -MAD_F_ONE)
257 sample = -MAD_F_ONE;
258 return sample >> (MAD_F_FRACBITS + 1 - 16); /* quantize */
261 static void madupdate(void)
263 int sz, ms;
264 if (maddec.sync) {
265 moff = maddec.sync->stream.this_frame - mbuf;
266 sz = maddec.sync->stream.next_frame -
267 maddec.sync->stream.this_frame;
268 ms = mad_timer_count(maddec.sync->frame.header.duration,
269 MAD_UNITS_MILLISECONDS);
270 frame_ms = frame_ms ? ((frame_ms << 5) - frame_ms + ms) >> 5 : ms;
271 frame_sz = frame_sz ? ((frame_sz << 5) - frame_sz + sz) >> 5 : sz;
275 static char mixed[1 << 18];
276 static enum mad_flow madoutput(void *data,
277 struct mad_header const *header,
278 struct mad_pcm *pcm)
280 int c1 = 0;
281 int c2 = pcm->channels > 1 ? 1 : 0;
282 int i;
283 played += mad_timer_count(maddec.sync->frame.header.duration,
284 MAD_UNITS_MILLISECONDS);
285 for (i = 0; i < pcm->length; i++) {
286 mixed[i * 4 + 0] = madscale(pcm->samples[c1][i]) & 0xff;
287 mixed[i * 4 + 1] = (madscale(pcm->samples[c1][i]) >> 8) & 0xff;
288 mixed[i * 4 + 2] = madscale(pcm->samples[c2][i]) & 0xff;
289 mixed[i * 4 + 3] = (madscale(pcm->samples[c2][i]) >> 8) & 0xff;
291 if (header->samplerate != rate) {
292 rate = header->samplerate;
293 oss_conf(rate, 2, 16);
295 write(afd, mixed, pcm->length * 4);
296 madupdate();
297 return cmdexec() ? MAD_FLOW_STOP : MAD_FLOW_CONTINUE;
300 static enum mad_flow maderror(void *data,
301 struct mad_stream *stream,
302 struct mad_frame *frame)
304 return MAD_FLOW_CONTINUE;
307 static void maddecode(void)
309 mad_decoder_init(&maddec, NULL, madinput, 0, 0, madoutput, maderror, 0);
310 while (!exited) {
311 if (paused) {
312 cmdwait();
313 cmdexec();
314 } else {
315 mad_decoder_run(&maddec, MAD_DECODER_MODE_SYNC);
318 mad_decoder_finish(&maddec);
321 static void term_init(struct termios *termios)
323 struct termios newtermios;
324 tcgetattr(0, termios);
325 newtermios = *termios;
326 newtermios.c_lflag &= ~ICANON;
327 newtermios.c_lflag &= ~ECHO;
328 tcsetattr(0, TCSAFLUSH, &newtermios);
329 fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);
332 static void term_done(struct termios *termios)
334 tcsetattr(0, 0, termios);
337 int main(int argc, char *argv[])
339 struct stat stat;
340 struct termios termios;
341 char *path = argc >= 2 ? argv[1] : NULL;
342 if (!path)
343 return 1;
344 if (strchr(path, '/'))
345 path = strrchr(path, '/') + 1;
346 snprintf(filename, 30, "%s", path);
347 mfd = open(argv[1], O_RDONLY);
348 if (fstat(mfd, &stat) == -1 || stat.st_size == 0)
349 return 1;
350 msize = stat.st_size;
351 if (oss_open()) {
352 fprintf(stderr, "minmad: /dev/dsp busy?\n");
353 return 1;
355 term_init(&termios);
356 maddecode();
357 oss_close();
358 term_done(&termios);
359 close(mfd);
360 printf("\n");
361 return 0;