This is an old question, but I had the same problem and it is currently difficult at best to find simple example code to do this, or even a half-decent explanation outside of mailing lists.
First, about your particular code, there is no advantage that I know of to calling mmap() for a small part of the file every time compared to just mmap()'ing the complete file. mmap() doesn't read the file into memory as you might think; it doesn't even allocate physical memory for the complete file. It only allocates virtual memory. The operating system will take care of reading the file into physical memory whenever your program reads from a piece of virtual memory that hasn't been loaded yet (page fault handler), and will get rid of parts of the file from physical memory again whenever the physical memory is needed elsewhere.
Having said that, if you're on an embedded system without a memory management unit, you won't have a mmap() with those characteristics, and you may not have enough physical memory to load the entire MP3 file into memory either. So for the remainder of this explanation, I'll assume you're using some generic read()-like function to get the data and have a target system with a memory size specified in kilobytes.
The problem
The problem is that mad_stream_buffer() does not do what you think or want it to do. It makes you think that it will add whatever buffer you give it to an internal stream and call input() whenever that stream runs low. But there is no internal stream. libmad can only use the buffer you give it, calling mad_stream_buffer() simply replaces the buffer pointer.
To really understand why this is a problem you also need to know a bit about how MP3s work. The audio part of an MP3 file is broken up into blocks of data called "frames". Frames are byte aligned, and start with a string of bits that are all set to 1, called the sync word. It's used to find the start of the first frame after starting playback or seeking. libmad will always look for the first sync word in its current input buffer after calling the input() callback, skipping any data before the first sync word that it finds. libmad will then start decoding MP3 frames, until there is no data left or it encounters an incomplete frame. The incomplete frame at the end is also ignored, and input() is called again.
So what ends up happening looks something like this:
| read() 1 | read() 2 | read() 3 | read() 4 |
|hdr|frame1|??????|frame3|frame4|??????|frame6|??????|frame8|frame9|?
libmad will seem to be skipping over frames 2, 5, and 10 in this particular case. That's why playback is too fast. Furthermore, if your MP3 file makes use of the bit reservoir (a feature that allows frames to buffer up extra data for later frames which may have more data to encode), the frames that do get decoded will be distorted with squeaky sounds due to the missing data.
What you need to do to make it work is this:
input():
| read() 1 |
|hdr|frame1|frame|
decoded as:
| buffer 1 |
|???|frame1|?????|
|
input(): |
.----------'
v | read() 2 |
|frame2|frame3|fr|
decoded as:
| buffer 2 |
|frame2|frame3|??|
|
input(): |
.-------------'
v | read() 3 |
|frame4|frame5|fr|
decoded as:
| buffer 3 |
|frame4|frame5|??|
and so on. If libmad got a buffer that does not contain a frame or does not end at a frame boundary, it will set the error
entry in the struct mad_stream
parameter passed to input to MAD_ERROR_BUFLEN
. If it ends in the middle of a frame, the next_frame
entry will be set to the pointer within the previously given data buffer that marks the start of the incomplete frame. If there was no frame in the buffer at all, the value of this pointer will be null. In that case you'll also receive a "synchronization lost" error in the error callback, if you have one.
The solution
You need a data buffer that can hold at least one max-length MP3 frame, plus 8 bytes for libmad's MAD_BUFFER_GUARD
. That would be at least 2881 bytes long (source). But that's assuming that the buffer starts at the start of a frame. If you don't know where the first frame is yet (i.e., the start of an MP3 file), you'll need to shift the data buffer byte by byte in that case to find it in the worst case. So you might as well round up to a power-off-two.
In input(), you need to do approximately the following:
- If
error
is not MAD_ERROR_BUFLEN
, load the data buffer with a completely new block of data and return.
- If
next_frame
is set, move the unconsumed portion of the (previous) buffer to the start of the buffer, and then fill the remainder of the buffer with new data.
- If
next_frame
is null, no valid frame has been found yet. To make sure that the first frame isn't skipped, which may already be in the buffer partly, we need to shift the data buffer by at most (buflen - max_frame_size), and fill the remainder of the buffer with new data.
- If there is not enough data left in the file to fill the buffer, append up to
MAD_BUFFER_GUARD
zero bytes.
Finally, here's the full code that works for me.
#define MP3_BUF_SIZE 4096
#define MP3_FRAME_SIZE 2881
static enum mad_flow input(void *data, struct mad_stream *stream) {
static char mp3_buf[MP3_BUF_SIZE]; /* MP3 data buffer. */
int keep; /* Number of bytes to keep from the previous buffer. */
int retval; /* Return value from read(). */
int len; /* Length of the new buffer. */
int eof; /* Whether this is the last buffer that we can provide. */
/* Figure out how much data we need to move from the end of the previous
buffer into the start of the new buffer. */
if (stream->error != MAD_ERROR_BUFLEN) {
/* All data has been consumed, or this is the first call. */
keep = 0;
} else if (stream->next_frame != NULL) {
/* The previous buffer was consumed partially. Move the unconsumed portion
into the new buffer. */
keep = stream->bufend - stream->next_frame;
} else if ((stream->bufend - stream->buffer) < MP3_BUF_SIZE) {
/* No data has been consumed at all, but our read buffer isn't full yet,
so let's just read more data first. */
keep = stream->bufend - stream->buffer;
} else {
/* No data has been consumed at all, and our read buffer is already full.
Shift the buffer to make room for more data, in such a way that any
possible frame position in the file is completely in the buffer at least
once. */
keep = MP3_BUF_SIZE - MP3_FRAME_SIZE;
}
/* Shift the end of the previous buffer to the start of the new buffer if we
want to keep any bytes. */
if (keep) {
memmove(mp3_buf, stream->bufend - keep, keep);
}
/* Append new data to the buffer. */
retval = read(in_fd, mp3_buf + keep, MP3_BUF_SIZE - keep);
if (retval < 0) {
/* Read error. */
perror("failed to read from input");
return MAD_FLOW_STOP;
} else if (retval == 0) {
/* End of file. Append MAD_BUFFER_GUARD zero bytes to make sure that the
last frame is properly decoded. */
if (keep + MAD_BUFFER_GUARD <= MP3_BUF_SIZE) {
/* Append all guard bytes and stop decoding after this buffer. */
memset(mp3_buf + keep, 0, MAD_BUFFER_GUARD);
len = keep + MAD_BUFFER_GUARD;
eof = 1;
} else {
/* The guard bytes don't all fit in our buffer, so we need to continue
decoding and write all fo teh guard bytes in the next call to input(). */
memset(mp3_buf + keep, 0, MP3_BUF_SIZE - keep);
len = MP3_BUF_SIZE;
eof = 0;
}
} else {
/* New buffer length is amount of bytes that we kept from the previous
buffer plus the bytes that we read just now. */
len = keep + retval;
eof = 0;
}
/* Pass the new buffer information to libmad. */
mad_stream_buffer(stream, mp3_buf, len);
return eof ? MAD_FLOW_STOP : MAD_FLOW_CONTINUE;
}
Note that I did not do extensive testing, like actually making sure it decodes the first and last frame properly, and I'm not involved with the project, so there may be small bugs in here. I'm listening to an MP3 decoded by this code as I'm typing these words, though.
Hope this saves someone somewhere a day of work!