如果分块读取,libmad播放速度太快 [英] libmad playback too fast if read in chunks

查看:106
本文介绍了如果分块读取,libmad播放速度太快的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我以libmad示例C文件为例,并播放了一个mp3,效果很好.但是,当我尝试分块读取文件时,而不是一次读取文件的示例,我听到中断"的声音,并且播放速度太快了.

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;
}

我使用的示例可以在这里找到:https://github.com/fasterthanlime/libmad/blob/master/minimad.c

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

我正在使用libao播放生成的PCM,将其添加到示例中后效果很好,因此我想这不是libao的问题.

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.

推荐答案

这是一个老问题,但是我遇到了同样的问题,目前最多很难找到简单的示例代码来做到这一点,甚至是一半.邮件列表之外的说明还不错.

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.

首先,关于您的特定代码,与仅使用mmap()处理整个文件相比,我不知道每次都对文件的一小部分调用mmap()是没有好处的. mmap()不会像您想象的那样将文件读入内存.它甚至不为整个文件分配物理内存.它仅分配虚拟内存.每当您的程序从尚未加载的虚拟内存(页面错误处理程序)中读取程序时,操作系统就会负责将文件读取到物理内存中,并且每当再次从物理内存中删除文件的某些部分时,操作系统就会进行处理.物理内存需要在其他地方使用.

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.

话虽如此,如果您使用的是没有内存管理单元的嵌入式系统,则不会有具有这些特征的mmap(),并且您可能没有足够的物理内存将整个MP3文件加载到内存中任何一个.因此,在本说明的其余部分中,我假设您正在使用一些类似于read()的泛型函数来获取数据,并具有一个目标系统,其内存大小以千字节为单位指定.

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.

问题在于mad_stream_buffer()不会执行您认为或希望执行的操作.它使您认为它将向内部流添加您提供的任何缓冲区,并在该流运行不足时调用input().但是没有内部流. libmad只能使用您提供的缓冲区,调用mad_stream_buffer()只会替换缓冲区指针.

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.

要真正理解为什么这是一个问题,您还需要了解MP3的工作原理. MP3文件的音频部分被分解为称为帧"的数据块.帧是按字节对齐的,并从全部设置为1的比特串开始,称为同步字.开始播放或搜索后,用于查找第一帧的开始. libmad将在调用input()回调之后始终在其当前输入缓冲区中查找第一个同步字,并跳过找到的第一个同步字之前的任何数据.然后,libmad将开始解码MP3帧,直到没有数据剩余或遇到不完整的帧为止.最后的不完整帧也将被忽略,并再次调用input().

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似乎会跳过第2、5和10帧.这就是播放速度太快的原因.此外,如果您的MP3文件使用了位存储库(此功能允许帧为额外的帧缓冲额外的数据,以便以后的帧中可能有更多的数据要编码),则由于以下原因,确实解码的帧会因吱吱作响的声音而失真.数据丢失.

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|??|

,依此类推.如果libmad获得的缓冲区不包含帧或不以帧边界结尾,则它将在传递给MAD_ERROR_BUFLEN的输入的struct mad_stream参数中设置error条目.如果它在帧中间结束,则next_frame条目将被设置为先前给定数据缓冲区内的指针,该指针标记了不完整帧的开始.如果缓冲区中根本没有帧,则此指针的值将为null.在这种情况下,如果有回调,您还将在错误回调中收到同步丢失"错误.

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.

您需要一个数据缓冲区,该缓冲区至少可以容纳一个最大长度的MP3帧,外加8个字节(用于libmad的MAD_BUFFER_GUARD).那将至少是2881个字节长() .但这是假设缓冲区从一帧的开始处开始.如果您不知道第一帧的位置(即MP3文件的开头),则需要在这种情况下逐字节移动数据缓冲区,以在最坏的情况下找到它.因此,您最好将电源关闭两次.

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.

在input()中,您大约需要执行以下操作:

In input(), you need to do approximately the following:

  • 如果error不是MAD_ERROR_BUFLEN,则使用全新的数据块加载数据缓冲区并返回.
  • 如果设置了next_frame,请将(上一个)缓冲区的未使用部分移至缓冲区的开头,然后用新数据填充缓冲区的其余部分.
  • 如果next_frame为null,则尚未找到有效的帧.为了确保不跳过第一帧(可能已部分缓存在其中),我们需要将数据缓存最多移位(buflen-max_frame_size),然后用新数据填充缓存的其余部分.
  • 如果文件中剩余的数据不足以填充缓冲区,请追加最多MAD_BUFFER_GUARD个零字节.
  • 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;
}

请注意,我没有进行广泛的测试,例如实际上确保它正确解码了第一帧和最后一帧,并且我没有参与该项目,因此这里可能存在一些小错误.不过,在键入这些单词时,我正在收听此代码解码的MP3.

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!

这篇关于如果分块读取,libmad播放速度太快的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆