minmad: OSSDSP environment variable for OSS device
[minmad.git] / minmad.c
blobbc80da89cbd96aa01fc388e8aa18c089dab00bf3
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 char *ossdsp; /* oss device */
29 static int mfd; /* input file descriptor */
30 static long msize; /* file size */
31 static unsigned char mbuf[1 << 16];
32 static long mpos; /* the position of mbuf[] */
33 static long mlen; /* data in mbuf[] */
34 static long moff; /* offset into mbuf[] */
35 static long mark[256]; /* mark positions */
36 static int frame_sz; /* frame size */
37 static int frame_ms; /* frame duration in milliseconds */
38 static int played; /* playing time in milliseconds */
39 static int rate; /* current oss sample rate */
40 static int topause; /* planned pause (compared with played) */
42 static int exited;
43 static int paused;
44 static int domark;
45 static int dojump;
46 static int doseek;
47 static int count;
49 static int oss_open(void)
51 afd = open(ossdsp ? ossdsp : "/dev/dsp", O_WRONLY);
52 return afd < 0;
55 static void oss_close(void)
57 if (afd > 0) /* zero fd is used for input */
58 close(afd);
59 afd = 0;
60 rate = 0;
63 static void oss_conf(int rate, int ch, int bits)
65 int frag = 0x0003000b; /* 0xmmmmssss: 2^m fragments of size 2^s each */
66 ioctl(afd, SOUND_PCM_WRITE_CHANNELS, &ch);
67 ioctl(afd, SOUND_PCM_WRITE_BITS, &bits);
68 ioctl(afd, SOUND_PCM_WRITE_RATE, &rate);
69 ioctl(afd, SOUND_PCM_SETFRAGMENT, &frag);
72 static int cmdread(void)
74 char b;
75 if (read(0, &b, 1) <= 0)
76 return -1;
77 return (unsigned char) b;
80 static void cmdwait(void)
82 struct pollfd ufds[1];
83 ufds[0].fd = 0;
84 ufds[0].events = POLLIN;
85 poll(ufds, 1, -1);
88 static long muldiv64(long num, long mul, long div)
90 return (long long) num * mul / div;
93 static void cmdinfo(void)
95 int per = muldiv64(mpos + moff, 1000, msize);
96 int loc = muldiv64(mpos + moff, frame_ms, frame_sz * 1000);
97 printf("%c %02d.%d%% (%d:%02d:%02d - %04d.%ds) [%s]\r",
98 paused ? (afd < 0 ? '*' : ' ') : '>',
99 per / 10, per % 10,
100 loc / 3600, (loc % 3600) / 60, loc % 60,
101 played / 1000, (played / 100) % 10,
102 filename);
103 fflush(stdout);
106 static int cmdcount(int def)
108 int result = count ? count : def;
109 count = 0;
110 return result;
113 static void seek(long pos)
115 mark['\''] = mpos + moff;
116 mpos = MAX(0, MIN(msize, pos));
117 doseek = 1;
120 static void cmdseekrel(int n)
122 int diff = muldiv64(n, frame_sz * 1000, frame_ms ? frame_ms : 40);
123 seek(mpos + moff + diff);
126 static void cmdseek100(int n)
128 if (n <= 100)
129 seek(muldiv64(msize, n, 100));
132 static void cmdseek(int n)
134 long pos = muldiv64(n * 60, frame_sz * 1000, frame_ms ? frame_ms : 40);
135 seek(pos);
138 static int cmdpause(int pause)
140 if (!pause && paused) {
141 if (oss_open())
142 return 1;
143 paused = 0;
145 if (pause && !paused) {
146 oss_close();
147 paused = 1;
149 return 0;
152 static int cmdexec(void)
154 int c;
155 if (topause > 0 && topause <= played) {
156 topause = 0;
157 return !cmdpause(1);
159 while ((c = cmdread()) >= 0) {
160 if (domark) {
161 domark = 0;
162 mark[c] = mpos + moff;
163 return 0;
165 if (dojump) {
166 dojump = 0;
167 if (mark[c] > 0)
168 seek(mark[c]);
169 return mark[c] > 0;
171 switch (c) {
172 case 'J':
173 cmdseekrel(+600 * cmdcount(1));
174 return 1;
175 case 'K':
176 cmdseekrel(-600 * cmdcount(1));
177 return 1;
178 case 'j':
179 cmdseekrel(+60 * cmdcount(1));
180 return 1;
181 case 'k':
182 cmdseekrel(-60 * cmdcount(1));
183 return 1;
184 case 'l':
185 cmdseekrel(+10 * cmdcount(1));
186 return 1;
187 case 'h':
188 cmdseekrel(-10 * cmdcount(1));
189 return 1;
190 case '%':
191 cmdseek100(cmdcount(0));
192 return 1;
193 case 'G':
194 cmdseek(cmdcount(0));
195 return 1;
196 case 'i':
197 cmdinfo();
198 break;
199 case 'm':
200 domark = 1;
201 break;
202 case '\'':
203 dojump = 1;
204 break;
205 case 'p':
206 case ' ':
207 if (cmdpause(!paused))
208 break;
209 return 1;
210 case 'P':
211 topause = count ? played + cmdcount(0) * 60000 : 0;
212 break;
213 case 'q':
214 exited = 1;
215 return 1;
216 case 27:
217 count = 0;
218 break;
219 default:
220 if (isdigit(c))
221 count = count * 10 + c - '0';
224 return 0;
227 static enum mad_flow madinput(void *data, struct mad_stream *stream)
229 int nread = stream->next_frame ? stream->next_frame - mbuf : moff;
230 int nleft = mlen - nread;
231 int nr = 0;
232 if (doseek) {
233 doseek = 0;
234 nleft = 0;
235 nread = 0;
236 lseek(mfd, mpos, 0);
238 memmove(mbuf, mbuf + nread, nleft);
239 if (nleft < sizeof(mbuf)) {
240 if ((nr = read(mfd, mbuf + nleft, sizeof(mbuf) - nleft)) <= 0) {
241 exited = 1;
242 return MAD_FLOW_STOP;
245 mlen = nleft + nr;
246 mad_stream_buffer(stream, mbuf, mlen);
247 mpos += nread;
248 moff = 0;
249 return MAD_FLOW_CONTINUE;
252 static signed int madscale(mad_fixed_t sample)
254 sample += (1l << (MAD_F_FRACBITS - 16)); /* round */
255 if (sample >= MAD_F_ONE) /* clip */
256 sample = MAD_F_ONE - 1;
257 if (sample < -MAD_F_ONE)
258 sample = -MAD_F_ONE;
259 return sample >> (MAD_F_FRACBITS + 1 - 16); /* quantize */
262 static void madupdate(void)
264 int sz, ms;
265 if (maddec.sync) {
266 moff = maddec.sync->stream.this_frame - mbuf;
267 sz = maddec.sync->stream.next_frame -
268 maddec.sync->stream.this_frame;
269 ms = mad_timer_count(maddec.sync->frame.header.duration,
270 MAD_UNITS_MILLISECONDS);
271 frame_ms = frame_ms ? ((frame_ms << 5) - frame_ms + ms) >> 5 : ms;
272 frame_sz = frame_sz ? ((frame_sz << 5) - frame_sz + sz) >> 5 : sz;
276 static char mixed[1 << 18];
277 static enum mad_flow madoutput(void *data,
278 struct mad_header const *header,
279 struct mad_pcm *pcm)
281 int c1 = 0;
282 int c2 = pcm->channels > 1 ? 1 : 0;
283 int i;
284 played += mad_timer_count(maddec.sync->frame.header.duration,
285 MAD_UNITS_MILLISECONDS);
286 for (i = 0; i < pcm->length; i++) {
287 mixed[i * 4 + 0] = madscale(pcm->samples[c1][i]) & 0xff;
288 mixed[i * 4 + 1] = (madscale(pcm->samples[c1][i]) >> 8) & 0xff;
289 mixed[i * 4 + 2] = madscale(pcm->samples[c2][i]) & 0xff;
290 mixed[i * 4 + 3] = (madscale(pcm->samples[c2][i]) >> 8) & 0xff;
292 if (header->samplerate != rate) {
293 rate = header->samplerate;
294 oss_conf(rate, 2, 16);
296 write(afd, mixed, pcm->length * 4);
297 madupdate();
298 return cmdexec() ? MAD_FLOW_STOP : MAD_FLOW_CONTINUE;
301 static enum mad_flow maderror(void *data,
302 struct mad_stream *stream,
303 struct mad_frame *frame)
305 return MAD_FLOW_CONTINUE;
308 static void maddecode(void)
310 mad_decoder_init(&maddec, NULL, madinput, 0, 0, madoutput, maderror, 0);
311 while (!exited) {
312 if (paused) {
313 cmdwait();
314 cmdexec();
315 } else {
316 mad_decoder_run(&maddec, MAD_DECODER_MODE_SYNC);
319 mad_decoder_finish(&maddec);
322 static void term_init(struct termios *termios)
324 struct termios newtermios;
325 tcgetattr(0, termios);
326 newtermios = *termios;
327 newtermios.c_lflag &= ~ICANON;
328 newtermios.c_lflag &= ~ECHO;
329 tcsetattr(0, TCSAFLUSH, &newtermios);
330 fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);
333 static void term_done(struct termios *termios)
335 tcsetattr(0, 0, termios);
338 int main(int argc, char *argv[])
340 struct stat stat;
341 struct termios termios;
342 char *path = argc >= 2 ? argv[1] : NULL;
343 if (!path)
344 return 1;
345 if (strchr(path, '/'))
346 path = strrchr(path, '/') + 1;
347 snprintf(filename, 30, "%s", path);
348 mfd = open(argv[1], O_RDONLY);
349 if (fstat(mfd, &stat) == -1 || stat.st_size == 0)
350 return 1;
351 msize = stat.st_size;
352 ossdsp = getenv("OSSDSP");
353 if (oss_open()) {
354 fprintf(stderr, "minmad: /dev/dsp busy?\n");
355 return 1;
357 term_init(&termios);
358 maddecode();
359 oss_close();
360 term_done(&termios);
361 close(mfd);
362 printf("\n");
363 return 0;