如果以块的形式读取,则libmad播放速度太快

Ami*_*mit 2 c mp3 gcc

我拿了libmad示例C文件,并播放了一个mp3,播放得很好.但是,当我尝试以块的形式读取文件时,与一次性读取文件的示例相反,我听到"中断"并且播放速度太快.

这是我的输入回调,以及我的输出回调

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;
}
Run Code Online (Sandbox Code Playgroud)

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

我正在使用libao来播放生成的PCM,这在添加到示例时工作正常,因此我猜这不是libao的问题.

小智 6

这是一个老问题,但我遇到了同样的问题,目前很难找到简单的示例代码来执行此操作,甚至是在邮件列表之外的半个解释.

首先,关于你的特定代码,我知道每次与mmap()完整文件相比,每次调用mmap()一小部分文件都没有优势.mmap()不会像你想象的那样将文件读入内存; 它甚至没有为整个文件分配物理内存.它只分配虚拟内存.只要程序从尚未加载的虚拟内存中读取(页面错误处理程序),操作系统就会将文件读入物理内存,并且每当从程序内存中删除部分文件时其他地方需要物理记忆.

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

问题

问题是mad_stream_buffer()没有按照你的想法做或想做它.它会让你认为它会将你给它的任何缓冲区添加到内部流中,并在该流低时调用input().但是没有内部流.libmad只能使用你给它的缓冲区,调用mad_stream_buffer()只是替换缓冲区指针.

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

所以最终发生的事情看起来像这样:

|    read() 1    |    read() 2    |    read() 3    |    read() 4    |
|hdr|frame1|??????|frame3|frame4|??????|frame6|??????|frame8|frame9|?
Run Code Online (Sandbox Code Playgroud)

在这种特殊情况下,libmad似乎会跳过第2帧,第5帧和第10帧.这就是播放太快的原因.此外,如果您的MP3文件使用了比特储存器(一种允许帧为后续帧缓冲额外数据的功能,可能有更多数据需要编码),那么被解码的帧将会因为发出吱吱声而失真.缺失数据.

你需要做些什么来使它工作是这样的:

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|??|
Run Code Online (Sandbox Code Playgroud)

等等.如果libmad得到一个不包含框架的缓冲区或者没有在框架边界结束,它将errorstruct mad_stream传递给输入的参数中设置条目MAD_ERROR_BUFLEN.如果它在帧的中间结束,则该next_frame条目将被设置为先前给定的数据缓冲区内的指针,该指针标记不完整帧的开始.如果缓冲区中根本没有帧,则该指针的值将为null.在这种情况下,如果您有错误回调,也会在错误回调中收到"同步丢失"错误.

解决方案

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

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

  • 如果error不是MAD_ERROR_BUFLEN,请使用全新的数据块加载数据缓冲区并返回.
  • 如果next_frame设置,则将(前一个)缓冲区的未使用部分移动到缓冲区的开头,然后用新数据填充缓冲区的剩余部分.
  • 如果next_frame为null,则尚未找到有效帧.为了确保没有跳过第一帧(可能已经部分在缓冲区中),我们需要最多移动数据缓冲区(buflen - max_frame_size),并用新数据填充缓冲区的剩余部分.
  • 如果文件中没有足够的数据来填充缓冲区,则追加MAD_BUFFER_GUARD零字节.

最后,这是适合我的完整代码.

#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;
}
Run Code Online (Sandbox Code Playgroud)

请注意,我没有进行大量测试,比如实际确保它正确地解码了第一帧和最后一帧,并且我没有参与该项目,因此这里可能存在小错误.我正在听这个代码解码的MP3,因为我正在输入这些单词.

希望这可以节省一天工作的某个地方!