gpt4 book ai didi

c - 如果分块读取,libmad播放速度太快

转载 作者:行者123 更新时间:2023-12-02 08:18:26 25 4
gpt4 key购买 nike

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

我使用的示例可以在这里找到: https://github.com/fasterthanlime/libmad/blob/master/minimad.c
我正在使用libao播放生成的PCM,将其添加到示例中时效果很好,因此我想这不是libao的问题。

最佳答案

这是一个古老的问题,但是我遇到了同样的问题,目前充其量很难找到简单的示例代码来做到这一点,甚至很难在邮件列表之外进行详尽的解释。

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

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

问题

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

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

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

|    read() 1    |    read() 2    |    read() 3    |    read() 4    |
|hdr|frame1|??????|frame3|frame4|??????|frame6|??????|frame8|frame9|?

在这种特殊情况下,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|??|

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

解决方案

您需要一个数据缓冲区,该缓冲区至少可以容纳一个最大长度的MP3帧,再加上8个字节的libmad的 MAD_BUFFER_GUARD。那将至少是2881个字节长( source)。但这是假设缓冲区始于帧的开始。如果您不知道第一帧的位置(即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;
    }

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

    希望这可以节省某人一天的工作时间!

    关于c - 如果分块读取,libmad播放速度太快,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39803572/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com