Implementing MP3 player in C
Surprised by the lack of English-written articles about MP3 decoding in C,I thought that I should put down a couple of words about that.
First of all, there are many was to read a file. Most modern operatingsystems have mmap() syscall,which basically maps the file contents to virtual memory space,which means that you can access the file justby reading an address in memory. The kernel transparently fetchesa chunk of file from the harddisk and caches it in the physical memory.This makes dozen tasks easier - no need to keep track of buffers andreduces excessive copies in memory thus getting one step closer tozero-copy architecture.
Secondly,
there are many libraries for decoding particular audio format.
Even for MP3 there are several:
LGPL v2.1 licensed libmpg123,
GPL v2 licensed libmad
and
LGPL licensed liblame.
Note that libmad is using fixed-point arithmetic which performsseveral times better on FPU-less ARM processors which suck at floating pointarithmetic. You might find an ARM processor in a mobile phone,NAS-box, Cubieboard and in a Raspberry Pi.
Thirdly and finally there are many libraries for audio output.
Note thatdecoder library produces simply PCM-audio samples.
In case of Linux basedmachines one could directly usealsa-libfeed the samples to kernel, which in turn transfers them to the audio chip.Modern distributions intercept ALSA output and hook those applications to thePulseAudio audioserver.PulseAudio allows on-the-fly switching of audio output between headphones,HDMI output or Bluetooth headset and other fancy stuff that you would expectfrom modern operating system. In that case one might use PulseAudio libraries tofeed the PCM samples directly to PulseAudio server.There are of course libraries like libaowhich build yet another abstraction layer on top of ALSA, PulseAudio and others,but at the moment of writing it seems to be not so well maintained in Ubuntu package management.
So, here goes a sample using libmad and libpulse:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> #include <sys/mman.h> #include <mad.h> #include <pulse/simple.h> #include <pulse/error.h> pa_simple *device = NULL; int ret = 1; int error; struct mad_stream mad_stream; struct mad_frame mad_frame; struct mad_synth mad_synth; void output(struct mad_header const *header, struct mad_pcm *pcm); int main(int argc, char **argv) { // Parse command-line arguments if (argc != 2) { fprintf(stderr, "Usage: %s [filename.mp3]", argv[0]); return 255; } // Set up PulseAudio 16-bit 44.1kHz stereo output static const pa_sample_spec ss = { .format = PA_SAMPLE_S16LE, .rate = 44100, .channels = 2 }; if (!(device = pa_simple_new(NULL, "MP3 player", PA_STREAM_PLAYBACK, NULL, "playback", &ss, NULL, NULL, &error))) { printf("pa_simple_new() failed\n"); return 255; } // Initialize MAD library mad_stream_init(&mad_stream); mad_synth_init(&mad_synth); mad_frame_init(&mad_frame); // Filename pointer char *filename = argv[1]; // File pointer FILE *fp = fopen(filename, "r"); int fd = fileno(fp); // Fetch file size, etc struct stat metadata; if (fstat(fd, &metadata) >= 0) { printf("File size %d bytes\n", (int)metadata.st_size); } else { printf("Failed to stat %s\n", filename); fclose(fp); return 254; } // Let kernel do all the dirty job of buffering etc, map file contents to memory char *input_stream = mmap(0, metadata.st_size, PROT_READ, MAP_SHARED, fd, 0); // Copy pointer and length to mad_stream struct mad_stream_buffer(&mad_stream, input_stream, metadata.st_size); // Decode frame and synthesize loop while (1) { // Decode frame from the stream if (mad_frame_decode(&mad_frame, &mad_stream)) { if (MAD_RECOVERABLE(mad_stream.error)) { continue; } else if (mad_stream.error == MAD_ERROR_BUFLEN) { continue; } else { break; } } // Synthesize PCM data of frame mad_synth_frame(&mad_synth, &mad_frame); output(&mad_frame.header, &mad_synth.pcm); } // Close fclose(fp); // Free MAD structs mad_synth_finish(&mad_synth); mad_frame_finish(&mad_frame); mad_stream_finish(&mad_stream); // Close PulseAudio output if (device) pa_simple_free(device); return EXIT_SUCCESS; } // Some helper functions, to be cleaned up in the future int scale(mad_fixed_t sample) { /* round */ sample += (1L << (MAD_F_FRACBITS - 16)); /* clip */ if (sample >= MAD_F_ONE) sample = MAD_F_ONE - 1; else if (sample < -MAD_F_ONE) sample = -MAD_F_ONE; /* quantize */ return sample >> (MAD_F_FRACBITS + 1 - 16); } void output(struct mad_header const *header, struct mad_pcm *pcm) { register int nsamples = pcm->length; mad_fixed_t const *left_ch = pcm->samples[0], *right_ch = pcm->samples[1]; static char stream[1152*4]; if (pcm->channels == 2) { while (nsamples--) { signed int sample; sample = scale(*left_ch++); stream[(pcm->length-nsamples)*4 ] = ((sample >> 0) & 0xff); stream[(pcm->length-nsamples)*4 +1] = ((sample >> 8) & 0xff); sample = scale(*right_ch++); stream[(pcm->length-nsamples)*4+2 ] = ((sample >> 0) & 0xff); stream[(pcm->length-nsamples)*4 +3] = ((sample >> 8) & 0xff); } if (pa_simple_write(device, stream, (size_t)1152*4, &error) < 0) { fprintf(stderr, "pa_simple_write() failed: %s\n", pa_strerror(error)); return; } } else { printf("Mono not supported!"); } }
You may compile this snippet by linking against the appropriate libraries:
gcc -o player player.c -lpulse -lpulse-simple -lmad -g
On Ubuntu you of course have to first install the dependencies:
sudo apt-get install libpulse-dev libmad0-dev libpulse0 libmad
References: