音视频(四):PCM转WAV

1,315 阅读2分钟

我们都知道PCM是无法直接播放的,我们要想播放PCM,最简单的就是把PCM转换成WAV格式进行播放,只需要在PCM的数据前面加入44个字节的WAV头,就可以转换成一个WAV音频文件

一:首先来看一下WAV的定义(来自维基百科)

Waveform Audio File FormatWAVE,又或者是因为扩展名而被大众所知的WAV),是微软IBM公司所开发在个人电脑存储音频流的编码格式,在Windows平台的应用软件受到广泛的支持,地位上类似于麦金塔电脑里的AIFF[2] 此格式属于资源交换文件格式(RIFF)的应用之一,通常会将采用脉冲编码调制的音频资存储在区块中。也是其音乐发烧友中常用的指定规格之一。由于此音频格式未经过压缩,所以在音质方面不会出现失真的情况,但文件的体积因而在众多音频格式中较为大

二:WAV文件格式解析

497279-20210319021131588-1269411109.png

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];

}