大刘的 iOS 自学笔记

音频pcm转wav

2022-06-13  本文已影响0人  大刘

Created by 大刘 liuxing8807@126.com

什么是WAV和PCM?

WAV:wav是一种无损的音频文件格式,WAV符合 PIFF(Resource Interchange File Format)规范。所有的WAV都有一个文件头,这个文件头音频流的编码参数。WAV对音频流的编码没有硬性规定,除了PCM之外,还有几乎所有支持ACM规范的编码都可以为WAV的音频流进行编码。

PCM:PCM(Pulse Code Modulation----脉码调制录音)。所谓PCM录音就是将声音等模拟信号变成符号化的脉冲列,再予以记录。PCM信号是由[1]、[0]等符号构成的数字信号,而未经过任何编码和压缩处理。

简单来说:wav是一种无损的音频文件格式,pcm是没有压缩的编码方式。

有时候需要将录音文件保存为wav格式,这需要手动填充wav的文件头信息

audio_1.png audio_2.png

ZZWavHeader

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

/// WAV Header class
@interface ZZWavHeader : NSObject

/// 数据长度
@property (nonatomic, assign) UInt32 dataLength;

/// 音频数据的编码方式, 1表示PCM编码
@property (nonatomic, assign) UInt16 formatTag;

/// 通道数
@property (nonatomic, assign) UInt16 channels;

/// 采样率
@property (nonatomic, assign) UInt32 samplesPerSec;

/// 精度,即位深
@property (nonatomic, assign) UInt16 bitsPerSample;

/// 获取WAV Header数据
- (NSData *)getData;

@end

NS_ASSUME_NONNULL_END
//  http://www.skyfox.org/ios-audio-wav-write-header.html
//  https://stackoverflow.com/questions/10914888/recording-wav-format-file-with-avaudiorecorder

#import "ZZWavHeader.h"

@implementation ZZWavHeader

- (NSData *)getData {
    UInt32 totalDataLen = self.dataLength + (44 - 8);
    UInt16 blockAlign   = self.channels * self.bitsPerSample / 8;
    UInt32 byteRate     = blockAlign * self.samplesPerSec;
    
    Byte header[44];
    
    // RIFF:4字节
    header[0] = 'R'; // 0x52
    header[1] = 'I'; // 0x49
    header[2] = 'F'; // 0x46
    header[3] = 'F'; // 0x46
    
    // 文件长度: 4字节
    // 这个长度不包括"RIFF"标志(4节节)和文件长度本身所占字节(4字节)
    header[4] = (Byte) (totalDataLen & 0xff);
    header[5] = (Byte) ((totalDataLen >> 8) & 0xff);
    header[6] = (Byte) ((totalDataLen >> 16) & 0xff);
    header[7] = (Byte) ((totalDataLen >> 24) & 0xff);
    
    // WAVE:4字节
    header[8]  = 'W'; // 0x57
    header[9]  = 'A'; // 0x41
    header[10] = 'V'; // 0x56
    header[11] = 'E'; // 0x45
    
    // fmt : 4字节,注:fmt后跟一英文空格字符
    header[12] = 'f'; // 0x66
    header[13] = 'm'; // 0x6d
    header[14] = 't'; // 0x74
    header[15] = ' '; // 0x20
    
    // 4 bytes: size of 'fmt ' chunk, Length of format data.  Always 16
    // 文件内部格式信息数据的大小: 4字节
    header[16] = 16;
    header[17] = 0;
    header[18] = 0;
    header[19] = 0;
    
    // 音频数据的编码方式: 2字节
    // 1:表示是PCM 编码
    // format = 1, Wave type PCM
    header[20] = self.formatTag;
    header[21] = 0;
    
    // 声道数channels: 2字节
    header[22] = (Byte)self.channels;
    header[23] = 0;
    
    // 采样率: 4字节
    header[24] = (Byte) (self.samplesPerSec & 0xff);
    header[25] = (Byte) ((self.samplesPerSec >> 8) & 0xff);
    header[26] = (Byte) ((self.samplesPerSec >> 16) & 0xff);
    header[27] = (Byte) ((self.samplesPerSec >> 24) & 0xff);
    
    // 音频数据传送速率: 4字节
    // 播放软件利用此值可以估计缓冲区的大小
    // 其值为采样率×每次采样大小
    // 传送速率单位是字节,因此采样大小需要位深除以 8,然后乘以通道数
    // bytePerSecond = sampleRate(比如16000) * (bitsPerSample(16位深) / 8) * channels
    header[28] = (Byte) (byteRate & 0xff);
    header[29] = (Byte) ((byteRate >> 8) & 0xff);
    header[30] = (Byte) ((byteRate >> 16) & 0xff);
    header[31] = (Byte) ((byteRate >> 24) & 0xff);
    
    // 每次采样大小: 2字节
    // blockAlign = 通道数(比如1) * bitPerSample(比如位深16) / 8
    header[32] = (Byte) (self.channels * self.bitsPerSample / 8);
    header[33] = 0;
    
    // 采样精度,即位深
    header[34] = self.bitsPerSample & 0xff;
    header[35] = 0;
    
    // data: 4字节
    header[36] = 'd';
    header[37] = 'a';
    header[38] = 't';
    header[39] = 'a';
    
    // 数据长度: 4字节
    // 此数据长度即未添加header之前的原始数据长度
    // iOS: [attributes fileSize]
    header[40] = (Byte)(self.dataLength & 0xff);
    header[41] = (Byte)((self.dataLength >> 8) & 0xff);
    header[42] = (Byte)((self.dataLength >> 16) & 0xff);
    header[43] = (Byte)((self.dataLength >> 24) & 0xff);
    
    return [[NSData alloc] initWithBytes:header length:44];
}

@end

调用代码

- (void)encode:(NSError * __autoreleasing _Nullable *)error {
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if (![fileManager fileExistsAtPath:self.srcFilePath]) {
        NSString *message = [NSString stringWithFormat:@"encode wav data fail, file not exists: %@", self.srcFilePath];
        NSLog(@"%@", message);
        return;
    }
    NSError *err = nil;
    NSDictionary *attributes = [fileManager attributesOfItemAtPath:self.srcFilePath error:&err];
    if (!attributes || err) {
        NSString *message = [NSString stringWithFormat:@"get file attribute error"];
        NSLog(@"%@", message);
        return;
    }
    int totalSize           = (int)[attributes fileSize];
    ZZWavHeader *header     = [[ZZWavHeader alloc] init];
    header.channels         = _CHANNEL_COUNT;
    header.formatTag        = 0x0001;
    header.samplesPerSec    = _SAMPLE_RATE;
    header.bitsPerSample    = _BIT_DEPTH;
    header.dataLength       = totalSize;
    NSData *headerData = [header getData];
    if (headerData.length != 44) {
        NSString *message = [NSString stringWithFormat:@"convert to wav fail, header not 44 byte length"];
        NSLog(@"%@", message);
        return;
    }
    
    NSMutableData *data = [NSMutableData dataWithData:headerData];
    [data appendData:[NSData dataWithContentsOfFile:self.srcFilePath]];
    NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:self.srcFilePath];
    [fileHandle writeData:data];
    [fileHandle closeFile];
    NSLog(@"%@", [NSString stringWithFormat:@"转换成功:%@", self.srcFilePath]);
}
上一篇下一篇

猜你喜欢

热点阅读