2

I took libmad example C file, and played an mp3, which played just fine. However, when I try to read the file in chunks, as opposed to the example, which reads the file in one go, I hear "breaks" and the playback is way too fast.

Here's my input callback, and my output callback

static enum mad_flow input(void *data, struct mad_stream *stream) {
  struct buffer *buffer = data;
//   char* raw_data[buffer->size];
//  if(fgets(*raw_data, buffer->size, buffer->file) == NULL) {
      // file is finished!
      // in our case we would want to move to next file here!
      // when we get there, we will get data from node->file of LL, instead of file.
      // with node->file, we can simply move to next song when playing the music.
//      return MAD_FLOW_STOP;
//  }
//printf("%s\n",*raw_data);

    void *fdm;
    fdm = mmap(0, BUFFER_SIZE, PROT_READ, MAP_SHARED, buffer->fd, buffer->offset);
    if (fdm == MAP_FAILED) {
        printf("%s\n","failed");
        return MAD_FLOW_STOP;
    }

    if(buffer->offset >= buffer->size) {
        if (munmap(fdm, BUFFER_SIZE) == -1)
            return MAD_FLOW_STOP;
        return MAD_FLOW_STOP;
    }

    mad_stream_buffer(stream, fdm, BUFFER_SIZE);

    printf("size is %lu and offset is %lu\n",buffer->size, buffer->offset);

    buffer->offset += BUFFER_SIZE;

printf("%s\n","read");

  return MAD_FLOW_CONTINUE;
}

static enum mad_flow output(void *data, 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 unsigned char stream[1152*4]; /* 1152 because that's what mad has as a max; *4 because
  there are 4 distinct bytes per sample (in 2 channel case) */
  static unsigned int rate = 0;
  static int channels = 0;
  //static struct audio_dither dither;

  register char * ptr = stream;
  register signed int sample;
  register mad_fixed_t tempsample;

  printf("%s\n", "playing");

  /* We need to know information about the file before we can open the playdevice
  in some cases. So, we do it here. */

  if (pcm->channels == 2) {
    while (nsamples--) {
      signed int sample;
      sample = scale(*left_ch++);
      // sample = (signed int) audio_linear_dither(16, tempsample, &dither);
      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);
    }
    ao_play(device, stream, pcm->length * 4);
  } else {
    while (nsamples--) {
      signed int sample;
      sample = scale(*left_ch++);
      stream[(pcm->length-nsamples)*2 ] = ((sample >> 0) & 0xff);
      stream[(pcm->length-nsamples)*2 +1] = ((sample >> 8) & 0xff);
    }
    ao_play(device, stream, pcm->length * 2);
  }
  return MAD_FLOW_CONTINUE;
}

The example I used can be found here: https://github.com/fasterthanlime/libmad/blob/master/minimad.c

I am using libao to play the generated PCM, which is working fine when added to the example, and hence I guess it's not a problem of libao.

Amit
  • 3,952
  • 7
  • 46
  • 80
  • excuse me, I know this question is old, but I am trying to learn libmad and from the few examples I have seen, I just can't figure out how to tell libmad which file to decode, you said you succeded on decoding a few MP3s, so I suppose you know – platinoob_ Mar 24 '21 at 12:07

1 Answers1

7

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!

Jeroen
  • 86
  • 1
  • 4
  • Wow! Thanks a lot! – Amit Apr 07 '17 at 08:14
  • How to I "tell" libmad which file to decode? I have made a question about it (https://stackoverflow.com/q/65915925/12690890), but I got no answer – platinoob_ Mar 29 '21 at 11:56
  • As far as I know, libmad doesn't have an API at such a high abstraction level. You have to feed it MP3-encoded bytes, and it will give you PCM-encoded bytes in return; anything beyond that you'd have to write yourself. I don't do anything with Windows anymore, so I can't help you there. But from googling around it looks to me like [Windows has facilities to play MP3s built into it](https://stackoverflow.com/questions/2049825/simplest-way-to-play-mp3-from-visual-c). – Jeroen Mar 30 '21 at 13:58