我们都知道PCM是无法直接播放的,我们要想播放PCM,最简单的就是把PCM转换成WAV格式进行播放,只需要在PCM的数据前面加入44个字节的WAV头,就可以转换成一个WAV音频文件
一:首先来看一下WAV的定义(来自维基百科)
Waveform Audio File Format(WAVE,又或者是因为扩展名而被大众所知的WAV),是微软与IBM公司所开发在个人电脑存储音频流的编码格式,在Windows平台的应用软件受到广泛的支持,地位上类似于麦金塔电脑里的AIFF。[2] 此格式属于资源交换文件格式(RIFF)的应用之一,通常会将采用脉冲编码调制的音频资存储在区块中。也是其音乐发烧友中常用的指定规格之一。由于此音频格式未经过压缩,所以在音质方面不会出现失真的情况,但文件的体积因而在众多音频格式中较为大
二:WAV文件格式解析
RIFF chunk id:固定字符数组RIFF,占4字节
RIFF chunk data size:data部分长度,占4字节
format:固定字符数组WAVE,占4字节
fmt chunk id:固定字符数组f,m,t,空格,占4字节
fmt chunk data size:fmt chunk data的大小,固定为16,占4字节
fmt chunk data:共16字节,包含音频编码2字节(1表示PCM 3表示 Floating Point)+ 声道数2字节+采样率4字节+字节率4字节+样本帧大小2字节+位深度2字节
data chunk id:固定字符数组data,占4字节
data chunk data size:PCM数据大小,占4字节
data:PCM数据
三:代码实现
首先定义一个WAV的header
typedef struct WAVHeader {
//RIFF chunkID
char riffChunkID[4];
//RIFF chunk的data大小
uint32_t riffChunkDataSize;
//WAVE
uint8_t format[4];
//fmt的chunk ID
uint8_t fmtChunkId[4];
//fmt chunk的data大小:存储PCM数据时,是16
uint32_t fmtChunkDataSize;
//音频编码 1表示PCM 3表示 Floating Point
uint16_t audioFormat;
//声道数
uint16_t numChannels;
//采样率
uint32_t sampleRate;
//字节率 = sampleRate * blockAlign
uint32_t byteRate;
//一个样本的字节数 = bitsPerSample * numChannels / 8
uint16_t blockAlign;
//位深度
uint16_t bitsPerSample;
//data chunk id
char dataChunkId[4];
//音频文件的总长度
uint32_t dataChunkDataSize;
} WAVHeader;
方法实现
+ (void)pcmTowav:(WAVHeader)header pcmFilepath:(NSString *)pcmFilepath wavFilename:(NSString *)wavFilename
{
header.riffChunkID[0] = 'R';
header.riffChunkID[1] = 'I';
header.riffChunkID[2] = 'F';
header.riffChunkID[3] = 'F';
header.format[0] = 'W';
header.format[1] = 'A';
header.format[2] = 'V';
header.format[3] = 'E';
header.fmtChunkId[0] = 'f';
header.fmtChunkId[1] = 'm';
header.fmtChunkId[2] = 't';
header.fmtChunkId[3] = ' ';
header.fmtChunkDataSize = 16;
header.audioFormat = 1;
header.numChannels = 1;
header.sampleRate = 44100;
header.bitsPerSample = 16;
header.blockAlign = header.bitsPerSample * header.numChannels >> 3;
header.byteRate = header.sampleRate * header.blockAlign;
header.dataChunkId[0] = 'd';
header.dataChunkId[1] = 'a';
header.dataChunkId[2] = 't';
header.dataChunkId[3] = 'a';
//读取PCM数据
NSFileHandle *readHandle = [NSFileHandle fileHandleForReadingAtPath:pcmFilepath];
NSData *pcmData = [readHandle readDataToEndOfFile];
header.dataChunkDataSize = (uint32_t)pcmData.length;
header.riffChunkDataSize = header.dataChunkDataSize + sizeof (WAVHeader) - 8;
//创建wav文件
NSString *wavFilePath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, **YES**).lastObject stringByAppendingPathComponent:wavFilename];
NSFileManager *manager = [NSFileManager defaultManager];
if ([manager fileExistsAtPath:wavFilePath]) {
[manager removeItemAtPath:wavFilePath error:nil];
}
[manager createFileAtPath:wavFilePath contents:nil attributes:nil];
//写入头部数据
NSFileHandle *writeHandle = [NSFileHandle fileHandleForWritingAtPath:wavFilePath];
[writeHandle writeData:[NSData dataWithBytes:&header length:sizeof(WAVHeader)]];
//写入PCM数据
[writeHandle writeData:pcmData];
}