iOS 使用AVFoundation实现语音的录制和播放

iOS的AVFoundation框架中的AVAudioRecorder和AVAudioPlayer可以实现语音的录制和播放功能demo下载

AVAudioRecorder

AVAudioRecorder 的初始化方法是

- (nullable instancetype)initWithURL:(NSURL *)url settings:(NSDictionary *)settings error:(NSError **)outError;
  • url: 录制的语音文件保存的路径,文件的类型是由这个参数值的file extension推测的。
  • settings: 对audio recored的设置。在iOS7中,默认的设置是
{ AVFormatIDKey = 1819304813;  
AVLinearPCMBitDepthKey = 16;
AVLinearPCMIsBigEndianKey = 0;
AVLinearPCMIsFloatKey = 0; 
AVLinearPCMIsNonInterleaved = 0;
AVNumberOfChannelsKey = 2; 
AVSampleRateKey = 44100;}

下面的三个key适用于所有的语音格式:
AVFormatIDKey:格式的标识,表示音频文件的格式,它对应的是个枚举值。

Use these identifiers to test for the presence of audio codecs on a system. If a given codec is present, you can use its identifier to specify that codec for data encoding or decoding, according to the capabilities of the codec. For more information, see Core Audio Overview.

CF_ENUM(AudioFormatID)
{
    kAudioFormatLinearPCM               = 'lpcm',
    kAudioFormatAC3                     = 'ac-3',
    kAudioFormat60958AC3                = 'cac3',
    kAudioFormatAppleIMA4               = 'ima4',
    kAudioFormatMPEG4AAC                = 'aac ',
    kAudioFormatMPEG4CELP               = 'celp',
    kAudioFormatMPEG4HVXC               = 'hvxc',
    kAudioFormatMPEG4TwinVQ             = 'twvq',
    kAudioFormatMACE3                   = 'MAC3',
    kAudioFormatMACE6                   = 'MAC6',
    kAudioFormatULaw                    = 'ulaw',
    kAudioFormatALaw                    = 'alaw',
    kAudioFormatQDesign                 = 'QDMC',
    kAudioFormatQDesign2                = 'QDM2',
    kAudioFormatQUALCOMM                = 'Qclp',
    kAudioFormatMPEGLayer1              = '.mp1',
    kAudioFormatMPEGLayer2              = '.mp2',
    kAudioFormatMPEGLayer3              = '.mp3',
    kAudioFormatTimeCode                = 'time',
    kAudioFormatMIDIStream              = 'midi',
    kAudioFormatParameterValueStream    = 'apvs',
    kAudioFormatAppleLossless           = 'alac',
    kAudioFormatMPEG4AAC_HE             = 'aach',
    kAudioFormatMPEG4AAC_LD             = 'aacl',
    kAudioFormatMPEG4AAC_ELD            = 'aace',
    kAudioFormatMPEG4AAC_ELD_SBR        = 'aacf',
    kAudioFormatMPEG4AAC_ELD_V2         = 'aacg',    
    kAudioFormatMPEG4AAC_HE_V2          = 'aacp',
    kAudioFormatMPEG4AAC_Spatial        = 'aacs',
    kAudioFormatAMR                     = 'samr',
    kAudioFormatAMR_WB                  = 'sawb',
    kAudioFormatAudible                 = 'AUDB',
    kAudioFormatiLBC                    = 'ilbc',
    kAudioFormatDVIIntelIMA             = 0x6D730011,
    kAudioFormatMicrosoftGSM            = 0x6D730031,
    kAudioFormatAES3                    = 'aes3',
    kAudioFormatEnhancedAC3             = 'ec-3'
};

AVSampleRateKey: 抽样率,单位时间内的抽样数。44.1kHZ和标准的CD Audio是相同的,除非你需要一个高保真的录音,你不需要这样高的采样率,大部分的音频软件只能特定的速率像32KHZ,24KHZ,16KHZ,12KHZ.8KHZ是电话采样率,对一般的录音已经足够了。
AVNumberOfChannelsKey:通道数。设成2的话是双声道。iPhone只有一个麦克风,一个单声道的通道足够了,它把你的数据需求削减了一半。
AVLinearPCMBitDepthKey:位宽。抽样后的数值用二进制表示,这个值表示二进制的位数。值可以是 8, 16, 24, or 32。
参考

开始录音的代码:


    NSString * url = NSTemporaryDirectory();
    url = [url stringByAppendingString:[NSString stringWithFormat:@"%f.wav", [[NSDate date] timeIntervalSince1970]]];
    NSMutableDictionary * settings = @{}.mutableCopy;
    [settings setObject:[NSNumber numberWithFloat:8000.0] forKey:AVSampleRateKey];
    [settings setObject:[NSNumber numberWithInt: kAudioFormatLinearPCM] forKey:AVFormatIDKey];  
    [settings setObject:@1 forKey:AVNumberOfChannelsKey];//设置成一个通道,iPnone只有一个麦克风,一个通道已经足够了
    [settings setObject:@16 forKey:AVLinearPCMBitDepthKey];//采样的位数
    self.audioRecorder = [[AVAudioRecorder  alloc] initWithURL:[NSURL fileURLWithPath:url] settings:settings error:&error];
    self.audioRecorder.delegate = self;
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryRecord error:nil];
    self.audioRecorder.meteringEnabled = YES;
    BOOL success = [self.audioRecorder record];
    if (success) {
        NSLog(@"录音开始成功");
    }else{
        NSLog(@"录音开始失败");
    }

遇到的问题

[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryRecord error:nil];

这个是设置AVAudioSession的category的,如果不设置的话,在模拟器上success是YES,但是在真机上是NO。同样,在用AVAudioPlayer播放语音时要设置category为AVAudioSessionCategoryPlayback

如果把setting里的AVFormatIDKey值改为kAudioFormatMPEGLayer3,则url的后缀也要改成.mp3,否则初始化AVAudioRecorder实例会失败。

指定的音频格式一定要和文件写入的URL文件类型保持一致。如果录制.wav文件格式,AVFormatIDKey指定的值不是kAudioFormatLinearPCM则会发生错误。NSError 会返回如下错误
The operation couldn’t be completed. (OSState error 1718449215.)

iOS 4.3以后不支持amr格式的录制和播放了,所以使用kAudioFormatAMR因为无法录制amr格式的语音。

停止录音

    [self.audioRecorder stop];

调用这个方法后,会走下面的代理方法

-(void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag{
    NSURL * url = recorder.url;
}

获取录音过程中的分贝

要在录音过程中获取分贝数,要在录音前先把AVAudioRecorder的属性meteringEnabled设置成YES.
在录音开始后,可以设置一个定时器获取分贝数

_metesTimer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(setVoiceImage) userInfo:nil repeats:YES];
-(void)setVoiceImage{
    if (self.audioRecorder.isRecording) {
        [self.audioRecorder updateMeters];
        float peakPower = [self.audioRecorder peakPowerForChannel:0];
        NSLog(@"%f", peakPower);
    }
}

AVAudioRecorderaveragePowerForChannelpeakPowerForChannel方法返回的是分贝数据,数值在-160 – 0之间

播放语音

可以将录音后获取的url传入下面的方法中:

- (void)playAudioWithURL:(NSURL *)url{
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
    NSError * error;
    self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
    self.audioPlayer.delegate = self;
    BOOL success = [self.audioPlayer play];
    if (success) {
        NSLog(@"播放成功");
    }else{
        NSLog(@"播放失败");
    }
}

我在demo里封装了一个ZMAudioManager的类,方便进行语音的播放和录制:
开始录音

[[ZMAudioManager shareInstance] startRecordingWithFileName:[NSString stringWithFormat:@"%f.wav", [[NSDate date] timeIntervalSince1970]] completion:^(NSError *error) {
    if (error) {

    else{

    }
}];

停止录音,recordPath是录音文件存放的路径,aDuration是录音时长

 [[ZMAudioManager  shareInstance] stopRecordingWithType:ZMAudioRecordeAMRType completion:^(NSString *recordPath, NSInteger aDuration, NSError *error) {
        if (error) {
            UIAlertView *a = [[UIAlertView alloc] initWithTitle:@"error" message:error.domain delegate:nil cancelButtonTitle:@"确定" otherButtonTitles: nil];
            [a show];
        }else{
           
 }];

播放录音,audioPath是录音文件存放的路径,录音播放完后会走completion回调

 [[ZMAudioManager shareInstance] playAudioWithPath:audioPath completion:^(NSError *error) {
 
  }];

在实际开发中,考虑到要和android之间通信,android支持amr,不支持wav;iOS支持wav,不支持amr。通常iOS客户端会进行amr和wav的互相转换。
在demo里EMVoiceConverter文件包含了wav和amr互相转换的方法:

+ (int)amrToWav:(NSString*)_amrPath wavSavePath:(NSString*)_savePath;

+ (int)wavToAmr:(NSString*)_wavPath amrSavePath:(NSString*)_savePath;

基础知识

声音是物体震动发出的声波。声音的频率是值单位时间内(每秒钟)物体震动的次数,或者说频率是每秒经过一给定点的声波数量。
人耳所能听到的声音,最低的频率是从20Hz起一直到最高频率20KHZ,因此音频文件格式的最大带宽是20KHZ。根据奈奎斯特的理论,只有采样频率高于声音信号最高频率的两倍时,才能把数字信号表示的声音还原成为原来的声音,所以音频文件的采样率一般在40~50KHZ,比如最常见的CD音质采样率44.1KHZ。

音频的采集过程主要通过设备将环境中的模拟信号采集成 PCM (Pulse Code Modulation)(脉冲编码调制)编码的原始数据,然后编码压缩成 MP3 等格式的数据分发出去。常见的音频压缩格式有:MP3,AAC,OGG,WMA,Opus,FLAC,APE,m4a 和 AMR 等

PCM编码

PCM编码主要过程是将话音、图像等模拟信号每隔一定时间进行取样,使其离散化,同时将抽样值按分层单位四舍五入取整量化,同时将抽样值按一组二进制码来表示抽样脉冲的幅值。
也就是说,PCM对模拟信号进行了抽样、量化和编码三个过程。
PCM数据是最原始的音频数据完全无损,所以PCM数据虽然音质优秀但体积庞大,为了解决这个问题先后诞生了一系列的音频格式,这些音频格式运用不同的方法对音频数据进行压缩,其中有无损压缩(ALAC、APE、FLAC)和有损压缩(MP3、AAC、OGG、WMA)两种。
目前最为常用的音频格式是MP3,MP3是一种有损压缩的音频格式,设计这种格式的目的就是为了大幅度的减小音频的数据量,它舍弃PCM音频数据中人类听觉不敏感的部分,

  • 抽样频率(Sampling Rate):单位时间内采集的样本数。采样频率必须至少是信号中最大频率分量频率的两倍,否则就不能从信号采样中恢复原始信号,这其实就是著名的香农采样定理。CD音质采样率为 44.1 kHz,其他常用采样率:22.05KHz,11.025KHz,一般网络和移动通信的音频采样率:8KHz。声音的频率是每秒经过一给定点的声波数量。

  • 位宽(bit depth):
    每一个采样点都需要用一个数值来表示大小,这个数值的数据类型大小可以是:4bit、8bit、16bit、32bit 等等,位数越多,表示得就越精细,声音质量自然就越好,而数据量也会成倍增大。我们在音频采样过程中常用的位宽是 8bit 或者 16bit;

  • 声道数(channels):
    由于音频的采集和播放是可以叠加的,因此,可以同时从多个音频源采集声音,并分别输出到不同的扬声器,故声道数一般表示声音录制时的音源数量或回放时相应的扬声器数量。声道数为 1 和 2 分别称为单声道和双声道,是比较常见的声道参数;

  • 音频帧(frame):
    音频跟视频很不一样,视频每一帧就是一张图像,而从上面的正玄波可以看出,音频数据是流式的,本身没有明确的一帧帧的概念,在实际的应用中,为了音频算法处理/传输的方便,一般约定俗成取 2.5ms~60ms 为单位的数据量为一帧音频。这个时间被称之为“采样时间”,其长度没有特别的标准,它是根据编解码器和具体应用的需求来决定的
    根据以上定义,我们可以计算一下一帧音频帧的大小。
    假设某音频信号是采样率为 8kHz、双通道、位宽为 16bit,20ms 一帧,则一帧音频数据的大小为:
    1size = 8000 x 2 x 16bit x 0.02s = 5120 bit = 640 byte

  • 比特率(bit rate):表示经过编码(压缩)后的音频数据每秒钟需要用多少个比特来表示,单位常为kbps。

常见的音频编码格式

AVAudioSession

iOS 使用AVFoundation实现语音的录制和播放_第1张图片
Audio Session category behavior
Responding to Audio Session Interruptions

如果你在音乐应用中播放音乐并收到电话或FaceTime请求时,应用的音频播放会暂停。如果拒绝来电或请求,则控制返回到应用程序,音频再次开始播放。
你可以直接观察被AVAudioSession发出的终端通知:

- (void)setupNotifications{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruption:) name:AVAudioSessionInterruptionNotification object:nil];
}
- (void)handleInterruption:(NSNotification *)notification{
    if (notification.userInfo[AVAudioSessionInterruptionTypeKey]) {
        AVAudioSessionInterruptionType type =  [notification.userInfo[AVAudioSessionInterruptionTypeKey] intValue];
        if (type == AVAudioSessionInterruptionTypeBegan) {
            //中断开始
        }else if (type == AVAudioSessionInterruptionTypeEnded){
            //中断结束
            if (notification.userInfo[AVAudioSessionInterruptionOptionKey]) {
                AVAudioSessionInterruptionOptions option = [notification.userInfo[AVAudioSessionInterruptionOptionKey] intValue];
                if (option == AVAudioSessionInterruptionOptionShouldResume) {
                    //中断结束,播放将恢复
                }else{
                    //中断结束,播放不会结束
                }
            }
        }
    }    
}
Observe for Route Change Notifications

AVAudioSession的一项重要职责是管理audio route changes。将音频输入或输出添加到iOS设备或从iOS设备中移除时, route change 会发生。route change的行为包括插入一副耳机,连接蓝牙耳机或拔下USB音频接口。当放生这些route change时,AVAudioSession将相应地重新传送音频信号,并向任何注册的观察者发送包含更改细节的通知。

- (void)setupNotifications{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruption:) name:AVAudioSessionRouteChangeNotification object:nil];
}

- (void)handleInterruption:(NSNotification *)notification{
    BOOL headphonesConnected;
    if (notification.userInfo[AVAudioSessionRouteChangeReasonKey]) {
        AVAudioSessionRouteChangeReason reason =  [notification.userInfo[AVAudioSessionRouteChangeReasonKey] intValue];
        switch (reason) {
            //插入耳机
            case AVAudioSessionRouteChangeReasonNewDeviceAvailable:{
                AVAudioSession *session = [AVAudioSession sharedInstance];
                for (AVAudioSessionPortDescription * output in session.currentRoute.outputs) {
                    if (output.portType == AVAudioSessionPortHeadphones) {
                        headphonesConnected = YES;
                        break;
                    }
                }
            }
            break;
           //拔出耳机
            case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:{
                AVAudioSessionRouteDescription * previousRoute = notification.userInfo[AVAudioSessionRouteChangePreviousRouteKey];
                for (AVAudioSessionPortDescription * output in previousRoute.outputs) {
                    if (output.portType == AVAudioSessionPortHeadphones) {
                        headphonesConnected = false;
                        break;
                    }
                }
            }
            break;
            default:
                break;
        }
    }
}

active

setActive: withOptions:error:nil
当你的app deactive自己的AudioSession时系统会通知上一个被中断播放app中断已经结束。如果你的app在deactive时传入了AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation参数,那么其他app在接到打断结束回调时会多得到一个参数kAudioSessionInterruptionType_ShouldResume否则就是AVAudioSessionInterruptionOptionShouldResume,根据参数的值可以决定是否继续播放。
大概流程是这样的:

  • 一个音乐软件A正在播放;
  • 用户打开你的软件播放对话语音,AudioSession active;
  • 音乐软件A音乐被打断并收到InterruptBegin事件;
    对话语音播放结束,AudioSession deactive并且传入NotifyOthersOnDeactivation参数;
  • 音乐软件A收到InterruptEnd事件,查看Resume参数,如果是ShouldResume控制音频继续播放,如果是ShouldNotResume就维持打断状态;

你可能感兴趣的:(iOS 使用AVFoundation实现语音的录制和播放)