如果分块读取,libmad 播放速度太快
libmad playback too fast if read in chunks
我拿了libmad example C 文件,播放了一个mp3,播放得很好。但是,当我尝试分块读取文件时,与一次性读取文件的示例相反,我听到 "breaks" 并且播放速度太快。
这是我的输入回调和输出回调
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 文件的音频部分被分解成称为 "frames" 的数据块。帧是字节对齐的,并以一串全部设置为 1 的位开始,称为同步字。用于在开始播放或搜索后查找第一帧的开始。在调用 input() 回调后,libmad 将始终在其当前输入缓冲区中查找第一个同步字,跳过它找到的第一个同步字之前的任何数据。然后 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 得到一个不包含帧或不在帧边界结束的缓冲区,它会将传递给输入的 struct mad_stream
参数中的 error
条目设置为 MAD_ERROR_BUFLEN
。如果它在帧的中间结束,next_frame
条目将被设置为先前给定的数据缓冲区中的指针,该缓冲区标记不完整帧的开始。如果缓冲区中根本没有帧,则此指针的值将为空。在这种情况下,您还会在错误回调中收到 "synchronization lost" 错误(如果有的话)。
解决方法
您需要一个可以容纳至少一个最大长度 MP3 帧的数据缓冲区,外加 8 个字节用于 libmad 的 MAD_BUFFER_GUARD
。那将至少有 2881 个字节长 (source)。但这是假设缓冲区从帧的开头开始。如果您还不知道第一帧在哪里(即 MP3 文件的开头),那么在这种情况下,您需要逐字节移动数据缓冲区,以便在最坏的情况下找到它。所以你不妨四舍五入到断电二。
在input()中,你需要做的大致如下:
- 如果
error
不是MAD_ERROR_BUFLEN
,用全新的数据块和return加载数据缓冲区。
- 如果设置了
next_frame
,将(前一个)缓冲区未使用的部分移动到缓冲区的开头,然后用新数据填充缓冲区的剩余部分。
- 如果
next_frame
为空,则还没有找到有效的帧。为了确保第一帧不被跳过,它可能已经部分在缓冲区中,我们需要将数据缓冲区最多移动 (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。
希望这可以为某人节省一天的工作时间!
我拿了libmad example C 文件,播放了一个mp3,播放得很好。但是,当我尝试分块读取文件时,与一次性读取文件的示例相反,我听到 "breaks" 并且播放速度太快。
这是我的输入回调和输出回调
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 文件的音频部分被分解成称为 "frames" 的数据块。帧是字节对齐的,并以一串全部设置为 1 的位开始,称为同步字。用于在开始播放或搜索后查找第一帧的开始。在调用 input() 回调后,libmad 将始终在其当前输入缓冲区中查找第一个同步字,跳过它找到的第一个同步字之前的任何数据。然后 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 得到一个不包含帧或不在帧边界结束的缓冲区,它会将传递给输入的 struct mad_stream
参数中的 error
条目设置为 MAD_ERROR_BUFLEN
。如果它在帧的中间结束,next_frame
条目将被设置为先前给定的数据缓冲区中的指针,该缓冲区标记不完整帧的开始。如果缓冲区中根本没有帧,则此指针的值将为空。在这种情况下,您还会在错误回调中收到 "synchronization lost" 错误(如果有的话)。
解决方法
您需要一个可以容纳至少一个最大长度 MP3 帧的数据缓冲区,外加 8 个字节用于 libmad 的 MAD_BUFFER_GUARD
。那将至少有 2881 个字节长 (source)。但这是假设缓冲区从帧的开头开始。如果您还不知道第一帧在哪里(即 MP3 文件的开头),那么在这种情况下,您需要逐字节移动数据缓冲区,以便在最坏的情况下找到它。所以你不妨四舍五入到断电二。
在input()中,你需要做的大致如下:
- 如果
error
不是MAD_ERROR_BUFLEN
,用全新的数据块和return加载数据缓冲区。 - 如果设置了
next_frame
,将(前一个)缓冲区未使用的部分移动到缓冲区的开头,然后用新数据填充缓冲区的剩余部分。 - 如果
next_frame
为空,则还没有找到有效的帧。为了确保第一帧不被跳过,它可能已经部分在缓冲区中,我们需要将数据缓冲区最多移动 (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。
希望这可以为某人节省一天的工作时间!