iOS录音和播放的那些事儿:几个奇葩的需求

最近在做的项目中,需要在iPhone上接上带麦耳机(苹果叫Headset,不带麦耳机叫Headphone),然后实现同步录音和播放。这个功能实现之后,需要改变录音、播放的输入源。

对于实现同步录音和播放功能,肯定就要使用到底层的接口,用AVAudioRecorder/AVAudioPlayer是无法实现的。

我研究了iOS声音处理的知识之后,发现自己实现太过麻烦,需要用到至少包括AudioQueue/AudioBuffer等等,然后还有各种复杂的C Struct、回调函数、回调处理等等,非常麻烦,一不小心就出错,很难找到出错的地方。于是我在网上找了一个比较著名的封闭好的库,Novocaine。它对声音的输入、输出都实现的比较好,也提供了很简单明确的回调函数,我只要修改一下回调函数就可以了,这里不表。

后面项目需要确定能否实现一种输入/输出方式:在插入带麦克风的耳机时,能够让声音从耳机麦克风输入,然后同步的从手机内置扬声器播放。

在手机插入带麦耳机后,输入源共有2个:内置麦克风(Build-In Microphone)、耳机麦克风(Wired Microphone);输出源有3个:打电话时用的,在上面的那个输出(Built-In Receiver)、耳机(Wired Headphones)和放音乐时用的内置扬声器(Built-In Speaker)。

查了苹果的文档后发现,根据苹果的产品策略,用户在插入带麦耳机后,会自动将输入源切换到耳机麦克风,将输出源切换到耳机。而且,在输入源是耳机麦克风的时候,将输出源切换到耳机是强制的。这句话比较绕,我举个例子。假如现在输入源是耳机麦克风,那么输出源只能是耳机。而如果将输入源切换到手机内置麦克风(这是可以实现的)的时候,则可以将输出源切换到手机。

苹果认为,插入耳机代表用户只想从耳机听声音。为了保护用户的隐私,苹果不允许开发者随便换输出源。也就是说,虽然苹果提供了3种方法用于切换输出源(都比较繁琐,后面可能会再写一篇博客),但在用户插入耳机,并且我们需要输入源是耳机麦克风的情况下,是无法将输出源切换到内置扬声器的。这里有更详细的说明:http://stackoverflow.com/questions/5931799/redirecting-audio-output-to-phone-speaker-and-mic-input-to-headphones


第二个问题是,需要在耳机插入的状态下,将输入源调整为手机内置麦克风,而输出源仍然保持为耳机。


在千能(不是万能)的AVAudioSession类里,提供了几个很好的方法:

打印当前正在工作的输入/输入源:

NSArray* input = [[AVAudioSession sharedInstance] currentRoute].inputs;
NSArray* output = [[AVAudioSession sharedInstance] currentRoute].outputs;
NSLog(@"current intput:%@",input);
NSLog(@"current output:%@",output);

打印现在可用的输入源:

NSArray* availableInputs = [[AVAudioSession sharedInstance] availableInputs];
NSLog(@"available inputs:%@",availableInputs);

AVAudioSession没有提供可用的输出源的列表。

将耳机插入之后,打印可用的输入源,结果如下:

2015-01-26 17:45:40.747 PregNotice[605:6d13] available inputs:(

"<AVAudioSessionPortDescription: 0x16dc2de0, type = MicrophoneBuiltIn; name = iPhone \U9ea6\U514b\U98ce; UID = Built-In Microphone; selectedDataSource = \U4e0b>",

"<AVAudioSessionPortDescription: 0x16f40e80, type = MicrophoneWired; name = \U8033\U673a\U9ea6\U514b\U98ce; UID = Wired Microphone; selectedDataSource = (null)>"

)
可以看出,现在可用的输入源包括内置麦克风和耳机麦克风(Wired Microphone)。我们通过以下方法改变输入源:

NSArray* inputArray = [[AVAudioSession sharedInstance] availableInputs];
for (AVAudioSessionPortDescription* desc in inputArray) {
    if ([desc.portType isEqualToString:AVAudioSessionPortBuiltInMic]) {
        NSError* error;
        [[AVAudioSession sharedInstance] setPreferredInput:desc error:&error];
    }
}
同样,在需要切换回来时,检查desc.portType是不是AVAudioSessionPortHeadsetMic,如果是,调整回来。

需要注意的是,使用这个方法会触发AVAudioSessionRouteChangeNotification通知,而插入耳机后,也会调用这个通知。我在测试的时候,为了检查耳机插入的动作,监听了这个通知,然后在通知回调方法里通过上面方法修改了输入源,导致又触发了通知,所以插入1次耳机导致了2次调用。

关于声音,AVAudioSession,还有很多需要了解的。有兴趣的同学,需要搞定iOS声音的同学,一定要好好学学。

你可能感兴趣的:(iOS录音和播放的那些事儿:几个奇葩的需求)