minmad: prevent multiplication overflow for large files
[minmad.git] / minmad.c
blob74a9d1ad879c9a5234dde8e0ec9a3385e885906d
1 /*
2 * minmad - a minimal mp3 player using libmad and oss
4 * Copyright (C) 2009-2015 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 CTRLKEY(x) ((x) - 96)
22 #define MIN(a, b) ((a) < (b) ? (a) : (b))
23 #define MAX(a, b) ((a) > (b) ? (a) : (b))
25 static struct mad_decoder maddec;
26 static int afd; /* oss fd */
28 static char filename[128];
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;
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 ioctl(afd, SOUND_PCM_WRITE_CHANNELS, &ch);
65 ioctl(afd, SOUND_PCM_WRITE_BITS, &bits);
66 ioctl(afd, SOUND_PCM_WRITE_RATE, &rate);
69 static int cmdread(void)
71 char b;
72 if (read(0, &b, 1) <= 0)
73 return -1;
74 return (unsigned char) b;
77 static void cmdwait(void)
79 struct pollfd ufds[1];
80 ufds[0].fd = 0;
81 ufds[0].events = POLLIN;
82 poll(ufds, 1, -1);
85 static long muldiv64(long num, long mul, long div)
87 return (long long) num * mul / div;
90 static void cmdinfo(void)
92 int per = muldiv64(mpos + moff, 1000, msize);
93 int loc = muldiv64(mpos + moff, frame_ms, frame_sz * 1000);
94 printf("%c %02d.%d%% (%d:%02d:%02d - %04d.%ds) [%s]\r",
95 paused ? (afd < 0 ? '*' : ' ') : '>',
96 per / 10, per % 10,
97 loc / 3600, (loc % 3600) / 60, loc % 60,
98 played / 1000, (played / 100) % 10,
99 filename);
100 fflush(stdout);
103 static int cmdcount(int def)
105 int result = count ? count : def;
106 count = 0;
107 return result;
110 static void seek(long pos)
112 mark['\''] = mpos + moff;
113 mpos = MAX(0, MIN(msize, pos));
114 doseek = 1;
117 static void cmdseekrel(int n)
119 int diff = muldiv64(n, frame_sz * 1000, frame_ms ? frame_ms : 40);
120 seek(mpos + moff + diff);
123 static void cmdseek100(int n)
125 if (n <= 100)
126 seek(muldiv64(msize, n, 100));
129 static int cmdexec(void)
131 int c;
132 while ((c = cmdread()) >= 0) {
133 if (domark) {
134 domark = 0;
135 mark[c] = mpos + moff;
136 return 0;
138 if (dojump) {
139 dojump = 0;
140 if (mark[c] > 0)
141 seek(mark[c]);
142 return mark[c] > 0;
144 switch (c) {
145 case 'J':
146 cmdseekrel(+600 * cmdcount(1));
147 return 1;
148 case 'K':
149 cmdseekrel(-600 * cmdcount(1));
150 return 1;
151 case 'j':
152 cmdseekrel(+60 * cmdcount(1));
153 return 1;
154 case 'k':
155 cmdseekrel(-60 * cmdcount(1));
156 return 1;
157 case 'l':
158 cmdseekrel(+10 * cmdcount(1));
159 return 1;
160 case 'h':
161 cmdseekrel(-10 * cmdcount(1));
162 return 1;
163 case '%':
164 cmdseek100(cmdcount(0));
165 return 1;
166 case 'i':
167 cmdinfo();
168 break;
169 case 'm':
170 domark = 1;
171 break;
172 case '\'':
173 dojump = 1;
174 break;
175 case 'p':
176 case ' ':
177 if (paused)
178 if (oss_open())
179 break;
180 if (!paused)
181 oss_close();
182 paused = !paused;
183 return 1;
184 case 'q':
185 exited = 1;
186 return 1;
187 case 27:
188 count = 0;
189 break;
190 default:
191 if (isdigit(c))
192 count = count * 10 + c - '0';
195 return 0;
198 static enum mad_flow madinput(void *data, struct mad_stream *stream)
200 int nread = stream->next_frame ? stream->next_frame - mbuf : moff;
201 int nleft = mlen - nread;
202 int nr = 0;
203 if (doseek) {
204 doseek = 0;
205 nleft = 0;
206 nread = 0;
207 lseek(mfd, mpos, 0);
209 memmove(mbuf, mbuf + nread, nleft);
210 if (nleft < sizeof(mbuf)) {
211 if ((nr = read(mfd, mbuf + nleft, sizeof(mbuf) - nleft)) <= 0) {
212 exited = 1;
213 return MAD_FLOW_STOP;
216 mlen = nleft + nr;
217 mad_stream_buffer(stream, mbuf, mlen);
218 mpos += nread;
219 moff = 0;
220 return MAD_FLOW_CONTINUE;
223 static signed int madscale(mad_fixed_t sample)
225 sample += (1l << (MAD_F_FRACBITS - 16)); /* round */
226 if (sample >= MAD_F_ONE) /* clip */
227 sample = MAD_F_ONE - 1;
228 if (sample < -MAD_F_ONE)
229 sample = -MAD_F_ONE;
230 return sample >> (MAD_F_FRACBITS + 1 - 16); /* quantize */
233 static void madupdate(void)
235 int sz, ms;
236 if (maddec.sync) {
237 moff = maddec.sync->stream.this_frame - mbuf;
238 sz = maddec.sync->stream.next_frame -
239 maddec.sync->stream.this_frame;
240 ms = mad_timer_count(maddec.sync->frame.header.duration,
241 MAD_UNITS_MILLISECONDS);
242 frame_ms = frame_ms ? ((frame_ms << 5) - frame_ms + ms) >> 5 : ms;
243 frame_sz = frame_sz ? ((frame_sz << 5) - frame_sz + sz) >> 5 : sz;
247 static char mixed[1 << 18];
248 static enum mad_flow madoutput(void *data,
249 struct mad_header const *header,
250 struct mad_pcm *pcm)
252 int c1 = 0;
253 int c2 = pcm->channels > 1 ? 1 : 0;
254 int i;
255 played += mad_timer_count(maddec.sync->frame.header.duration,
256 MAD_UNITS_MILLISECONDS);
257 for (i = 0; i < pcm->length; i++) {
258 mixed[i * 4 + 0] = madscale(pcm->samples[c1][i]) & 0xff;
259 mixed[i * 4 + 1] = (madscale(pcm->samples[c1][i]) >> 8) & 0xff;
260 mixed[i * 4 + 2] = madscale(pcm->samples[c2][i]) & 0xff;
261 mixed[i * 4 + 3] = (madscale(pcm->samples[c2][i]) >> 8) & 0xff;
263 if (header->samplerate != rate) {
264 rate = header->samplerate;
265 oss_conf(rate, 2, 16);
267 write(afd, mixed, pcm->length * 4);
268 madupdate();
269 return cmdexec() ? MAD_FLOW_STOP : MAD_FLOW_CONTINUE;
272 static enum mad_flow maderror(void *data,
273 struct mad_stream *stream,
274 struct mad_frame *frame)
276 return MAD_FLOW_CONTINUE;
279 static void maddecode(void)
281 mad_decoder_init(&maddec, NULL, madinput, 0, 0, madoutput, maderror, 0);
282 while (!exited) {
283 if (paused) {
284 cmdwait();
285 cmdexec();
286 } else {
287 mad_decoder_run(&maddec, MAD_DECODER_MODE_SYNC);
290 mad_decoder_finish(&maddec);
293 static void term_init(struct termios *termios)
295 struct termios newtermios;
296 tcgetattr(0, termios);
297 newtermios = *termios;
298 newtermios.c_lflag &= ~ICANON;
299 newtermios.c_lflag &= ~ECHO;
300 tcsetattr(0, TCSAFLUSH, &newtermios);
301 fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);
304 static void term_done(struct termios *termios)
306 tcsetattr(0, 0, termios);
309 int main(int argc, char *argv[])
311 struct stat stat;
312 struct termios termios;
313 if (argc < 2)
314 return 1;
315 snprintf(filename, 30, "%s", argv[1]);
316 mfd = open(argv[1], O_RDONLY);
317 if (fstat(mfd, &stat) == -1 || stat.st_size == 0)
318 return 1;
319 msize = stat.st_size;
320 if (oss_open()) {
321 fprintf(stderr, "minmad: /dev/dsp busy?\n");
322 return 1;
324 term_init(&termios);
325 maddecode();
326 oss_close();
327 term_done(&termios);
328 close(mfd);
329 printf("\n");
330 return 0;