LAME-encoder
源码解析
#include "mp3_encoder.h"
#include <stdio.h>
#include <android/log.h>
#include <errno.h>
#include <string.h>
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "xx", __VA_ARGS__)
int Mp3Encoder::Init(const char* pcmFilePath, const char *
mp3FilePath, int sampleRate, int channels, int bitRate) {
int ret = -1;
LOGI("pcmFilePath = %s", pcmFilePath);
pcmFile = fopen(pcmFilePath, "rb");
if (pcmFile) {
mp3File = fopen(mp3FilePath, "wb");
if (mp3File) {
lameClient = lame_init();
// 设置输入文件的采样率
lame_set_in_samplerate(lameClient, sampleRate);
// 设置输出文件的采样率
lame_set_out_samplerate(lameClient, sampleRate);
// 设置通道数
lame_set_num_channels(lameClient, channels);
// 设置比特率,1411200,44100 * 2 * 16
lame_set_brate(lameClient, bitRate / 1000);
lame_init_params(lameClient);
ret = 0;
} else {
LOGI("mp3File open fail");
LOGI("open fail errno = %d reason = %s \n", errno, strerror(errno));
}
} else {
LOGI("pcmFile open fail");
LOGI("open fail errno = %d reason = %s \n", errno, strerror(errno));
}
return ret;
}
void Mp3Encoder::Encode() {
int bufferSize = 1024 * 256;
short* buffer = new short[bufferSize / 2];
short* leftBuffer = new short[bufferSize / 4];
short* rightBuffer = new short[bufferSize / 4];
unsigned char* mp3_buffer = new unsigned char[bufferSize];
size_t readBufferSize = 0;
// 读PCM文件
while ((readBufferSize = fread(buffer, 2, bufferSize / 2, pcmFile)) > 0) {
for (int i = 0; i < readBufferSize; i++) {
if (i % 2 == 0) {
leftBuffer[i / 2] = buffer[i];
} else {
rightBuffer[i / 2] = buffer[i];
}
}
// 进行MP3编码
size_t wroteSize = lame_encode_buffer(lameClient, (short int *) leftBuffer,
(short int *) rightBuffer, (int)(readBufferSize / 2), mp3_buffer, bufferSize);
// 写到文件中
fwrite(mp3_buffer, 1, wroteSize, mp3File);
}
delete[] buffer;
delete[] leftBuffer;
delete[] rightBuffer;
delete[] mp3_buffer;
}
void Mp3Encoder::Destory() {
if (pcmFile) {
fclose(pcmFile);
}
if (mp3File) {
fclose(mp3File);
lame_close(lameClient);
}
}
Mp3Encoder::Mp3Encoder() {}
问题
1. 如何通过WAV文件,获取MP3文件
1. WAV文件解析
WAV文件由数据头 + PCM数据组成,数据头一般由3个区块组成:RIFF chunk,Format chunk和Data chunk。文件中还可能包含一些可选的区块,如:`Fact chunk`、`Cue points chunk`、`Playlist chunk`、`Associated data list chunk`等。下面介绍RIFF chunk,Format chunk和Data chunk。
1. RIFF chunk
名称 | 偏移地址 | 字节数 | 端序 | 内容 |
---|---|---|---|---|
ID | 0x00 | 4Byte | 大端 | 'RIFF' (0x52494646) |
Size | 0x04 | 4Byte | 小端 | fileSize - 8 |
Type | 0x08 | 4Byte | 大端 | 'WAVE'(0x57415645) |
- 以
'RIFF'
为标识 -
Size
是整个文件的长度减去ID
和Size
的长度 -
Type
是WAVE
表示后面需要两个子块:Format
区块和Data
区块
2. Format chunk
名称 | 偏移地址 | 字节数 | 端序 | 内容 |
---|---|---|---|---|
ID | 0x00 | 4Byte | 大端 | 'fmt ' (0x666D7420) |
Size | 0x04 | 4Byte | 小端 | 16 |
AudioFormat | 0x08 | 2Byte | 小端 | 音频格式 |
NumChannels | 0x0A | 2Byte | 小端 | 声道数 |
SampleRate | 0x0C | 4Byte | 小端 | 采样率 |
ByteRate | 0x10 | 4Byte | 小端 | 每秒数据字节数 |
BlockAlign | 0x14 | 2Byte | 小端 | 数据块对齐 |
BitsPerSample | 0x16 | 2Byte | 小端 | 采样位数 |
以
'fmt '
为标识Size
表示该区块数据的长度(不包含ID
和Size
的长度)AudioFormat
表示Data
区块存储的音频数据的格式,PCM
音频数据的值为1NumChannels
表示音频数据的声道数,1:单声道,2:双声道SampleRate
表示音频数据的采样率ByteRate
每秒数据字节数 = SampleRate * NumChannels * BitsPerSample / 8BlockAlign
每个采样所需的字节数 = NumChannels * BitsPerSample / 8BitsPerSample
每个采样存储的bit数,8:8bit,16:16bit,32:32bit
3. Data chunk
名称 | 偏移地址 | 字节数 | 端序 | 内容 |
---|---|---|---|---|
ID | 0x00 | 4Byte | 大端 | 'data' (0x64617461) |
Size | 0x04 | 4Byte | 小端 | N |
Data | 0x08 | NByte | 小端 | 音频数据 |
- 以
data
为标识 -
Size
表示音频数据的长度,N = ByteRate * seconds -
Data
音频数据
4. data数据的布局
备注:括号对应一个采样点
(1)8bit 单声道
(数据1) -> (数据2)
(2)8bit 双声道
(声道1数据1 -> 声道2数据1)-> (声道1数据2 -> 声道2数据2)
(3)16bit 单声道
(数据1低字节 -> 数据1高字节) -> (数据2低字节 -> 数据2高字节)
(4)16bit 双声道
(声道1数据1低字节 -> 声道1数据1高字节 -> 声道2数据1低字节 -> 声道2数据1高字节)
2. 通过WAV获得PCM文件
用UltraEdit软件打开WAV文件,找到data字段 + 其后面4个字节,然后右键剪切,就可以了
参考
(1)WAV文件格式详解
https://blog.csdn.net/imxiangzi/article/details/80265978
(2)源码位置:https://github.com/mashenlyl/lame-mp3Encoder