我正在开发一个 iOS 应用程序(目前专门针对 iPhone),它要求应用程序仅从 iPhone 内部麦克风录制音频(即使插入耳机/耳机),并在耳机上播放(假设耳机已插入目前)。
我想知道当前是否可以使用可用的 API?如果是这样,任何人都可以阐明我该怎么做吗?
谢谢!
我正在开发一个 iOS 应用程序(目前专门针对 iPhone),它要求应用程序仅从 iPhone 内部麦克风录制音频(即使插入耳机/耳机),并在耳机上播放(假设耳机已插入目前)。
我想知道当前是否可以使用可用的 API?如果是这样,任何人都可以阐明我该怎么做吗?
谢谢!
我相信这个问题的答案是否定的。我使用 iPhone 4 和新的 iOS 4 AVFoundation 进行实验,重点是AVCaptureDevice类。
我在应用程序中添加了以下内容:
NSLog(@"%@", [AVCaptureDevice devices]);
这要求列出所有可用于捕获音频和/或视频的设备。没有插入耳机,我得到:
(
"Back Camera",
"Front Camera",
"iPhone Microphone"
)
插入耳机后,我得到:
(
"Back Camera",
"Front Camera",
Headphones
)
因此,一旦耳机可用,iPhone 麦克风就会从可用的 AVCaptureDevices 列表中删除。为了进一步探讨这一点,我添加了一些代码来获取可用音频设备的 AVCaptureDevice 实例并打印其唯一 ID。对于将自己标识为“iPhone 麦克风”的设备和将自己标识为“耳机”的设备,我得到:
com.apple.avfoundation.avcapturedevice.built-in_audio:0
在我看来,两台设备不能有相同的唯一 ID 似乎很明显,所以很明显它是同一台设备在改变其状态。虽然 AVCaptureDevices 有很多设置状态的东西,但它仅限于视觉方面的东西,比如焦点、曝光、闪光灯和白平衡。
看来真的不可能了。
我的目标是将输出发送到蓝牙耳机并记录它的输入。据我所知,我最好的选择是:“PlayAndRecord + AllowBluetoothInput”属性(iphone 4,诺基亚 BH-214 耳机)
重要的是,根据苹果文档,当音频路由发生变化时,您总是必须重新覆盖您的音频类别!
这是我的 ROUTE CHANGE LISTENER 方法,打印:RouteChangeReasons, outputRoute, audioRout:
void RouteChangeListener(void *inClientData,
AudioSessionPropertyID inID,
UInt32 inDataSize,
const void *inData) {
if (inID == kAudioSessionProperty_AudioRouteChange) {
NSLog(@"]-----------------[ Audio Route Change ]--------------------[");
// ************************************************************************************************
// Check route change reason **********************************************************************
// ************************************************************************************************
CFDictionaryRef routeDict = (CFDictionaryRef)inData;
NSNumber* reasonValue = (NSNumber*)CFDictionaryGetValue(routeDict, CFSTR(kAudioSession_AudioRouteChangeKey_Reason));
int reason = [reasonValue intValue];
if (reason == kAudioSessionRouteChangeReason_OldDeviceUnavailable) {
NSLog(@"] Logic: audio route change reason: OldDeviceUnavailable");
}else if (reason == kAudioSessionRouteChangeReason_NewDeviceAvailable ) {
NSLog(@"] Logic: audio route change reason: NewDeviceAvailable");
}else if (reason == kAudioSessionRouteChangeReason_Unknown ) {
NSLog(@"] Logic: audio route change reason: Unknown");
}else if (reason == kAudioSessionRouteChangeReason_CategoryChange ) {
NSLog(@"] Logic: audio route change reason: CategoryChange");
}else if (reason == kAudioSessionRouteChangeReason_Override ) {
NSLog(@"] Logic: audio route change reason: Override");
}else if (reason == kAudioSessionRouteChangeReason_WakeFromSleep ) {
NSLog(@"] Logic: audio route change reason: WakeFromSleep");
}else if (reason == kAudioSessionRouteChangeReason_NoSuitableRouteForCategory ) {
NSLog(@"] Logic: audio route chang reasone: NoSuitableRouteForCategory");
}
// ************************************************************************************************
// Check output type ******************************************************************************
// ************************************************************************************************
CFDictionaryRef currentRouteDescriptionDictionary = nil;
UInt32 dataSize = sizeof(currentRouteDescriptionDictionary);
AudioSessionGetProperty(kAudioSessionProperty_AudioRouteDescription, &dataSize, ¤tRouteDescriptionDictionary);
if (currentRouteDescriptionDictionary) {
CFArrayRef outputs = CFDictionaryGetValue(currentRouteDescriptionDictionary, kAudioSession_AudioRouteKey_Outputs);
if(CFArrayGetCount(outputs) > 0) {
CFDictionaryRef currentOutput = CFArrayGetValueAtIndex(outputs, 0);
CFStringRef outputType = CFDictionaryGetValue(currentOutput, kAudioSession_AudioRouteKey_Type);
if ( (CFStringCompare(outputType, kAudioSessionOutputRoute_AirPlay, 0) == kCFCompareEqualTo) ) { // if Airplay
NSLog(@"] Logic: output changed to Airplay");
}
else if ( (CFStringCompare(outputType, kAudioSessionOutputRoute_BluetoothA2DP, 0) == kCFCompareEqualTo) ) { // if Bluetooth A2DP
NSLog(@"] Logic: output changed to A2DP");
// Mix with others category
UInt32 doSetProperty = 1;
AudioSessionSetProperty(kAudioSessionProperty_OverrideCategoryMixWithOthers,sizeof(doSetProperty),&doSetProperty);
// Bluetooth support enable
UInt32 allowBluetoothInput = 1;
AudioSessionSetProperty (kAudioSessionProperty_OverrideCategoryEnableBluetoothInput,sizeof (allowBluetoothInput),&allowBluetoothInput);
}
else if ( (CFStringCompare(outputType, kAudioSessionOutputRoute_BluetoothHFP, 0) == kCFCompareEqualTo) ) { // if Bluetooth HFP
NSLog(@"] Logic: output changed to HFP");
// Mix with others category
UInt32 doSetProperty = 1;
AudioSessionSetProperty(kAudioSessionProperty_OverrideCategoryMixWithOthers,sizeof(doSetProperty),&doSetProperty);
// Bluetooth support enable
UInt32 allowBluetoothInput = 1;
AudioSessionSetProperty (kAudioSessionProperty_OverrideCategoryEnableBluetoothInput,sizeof (allowBluetoothInput),&allowBluetoothInput);
}
else if ( (CFStringCompare(outputType, kAudioSessionOutputRoute_LineOut, 0) == kCFCompareEqualTo) ) { // if Line Out
NSLog(@"] Logic: output changed to Line Out");
}
else if ( (CFStringCompare(outputType, kAudioSessionOutputRoute_Headphones, 0) == kCFCompareEqualTo) ) { // if Headphones
NSLog(@"] Logic: output changed to Headphone");
// Mix with others category
UInt32 doSetProperty = 1;
AudioSessionSetProperty(kAudioSessionProperty_OverrideCategoryMixWithOthers,sizeof(doSetProperty),&doSetProperty);
// Bluetooth support disable
UInt32 allowBluetoothInput = 0;
AudioSessionSetProperty (kAudioSessionProperty_OverrideCategoryEnableBluetoothInput,sizeof (allowBluetoothInput),&allowBluetoothInput);
}
else if ( (CFStringCompare(outputType, kAudioSessionOutputRoute_BuiltInSpeaker, 0) == kCFCompareEqualTo) ) { // if Built In Speaker
NSLog(@"] Logic: output changed to Built In Speaker");
// Mix with others category
UInt32 doSetProperty = 1;
AudioSessionSetProperty(kAudioSessionProperty_OverrideCategoryMixWithOthers,sizeof(doSetProperty),&doSetProperty);
}
else if ( (CFStringCompare(outputType, kAudioSessionOutputRoute_USBAudio, 0) == kCFCompareEqualTo) ) { // if USB audio
NSLog(@"] Logic: output changed to USB Audio");
}
else if ( (CFStringCompare(outputType, kAudioSessionOutputRoute_HDMI, 0) == kCFCompareEqualTo) ) { // if HDMI
NSLog(@"] Logic: output changed to HDMI");
}
else if ( (CFStringCompare(outputType, kAudioSessionOutputRoute_BuiltInReceiver, 0) == kCFCompareEqualTo) ) { // if Built in Reciever
NSLog(@"] Logic: output changed to Built in Reciever");
// Mix with others category
UInt32 doSetProperty = 1;
AudioSessionSetProperty(kAudioSessionProperty_OverrideCategoryMixWithOthers,sizeof(doSetProperty),&doSetProperty);
}
else { // Unknown audio type
NSLog(@"] Logic: WARNING: Unknown audio type: %@",(NSString*)outputType);
}
}
}
// ************************************************************************************************
// Check audio route ******************************************************************************
// ************************************************************************************************
UInt32 routeSize = sizeof(CFStringRef);
CFStringRef route;
AudioSessionGetProperty(kAudioSessionProperty_AudioRoute, &routeSize, &route);
NSLog(@"] Logic: the audio route is: %@",(NSString*)route);
// ************************************************************************************************
NSLog(@"]--------------------------[ ]-----------------------------[");
}
}
由于苹果从 7.0 开始再次更改了音频系统,我将在此处发布更新后的代码:
#pragma mark Route change listener
// *********************************************************************************************************
// *********** Route change listener ***********************************************************************
// *********************************************************************************************************
-(void)routeChanged:(NSNotification*)notification {
NSLog(@"]-----------------[ Audio Route Change ]--------------------[");
AVAudioSession *session = [AVAudioSession sharedInstance];
//AVAudioSessionRouteDescription* prevRoute = [[notification userInfo] objectForKey:AVAudioSessionRouteChangePreviousRouteKey];
// Reason
NSInteger reason = [[[notification userInfo] objectForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
switch (reason) {
case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
NSLog(@"] Audio Route: The route changed because no suitable route is now available for the specified category.");
break;
case AVAudioSessionRouteChangeReasonWakeFromSleep:
NSLog(@"] Audio Route: The route changed when the device woke up from sleep.");
break;
case AVAudioSessionRouteChangeReasonOverride:
NSLog(@"] Audio Route: The output route was overridden by the app.");
break;
case AVAudioSessionRouteChangeReasonCategoryChange:
NSLog(@"] Audio Route: The category of the session object changed.");
break;
case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
NSLog(@"] Audio Route: The previous audio output path is no longer available.");
break;
case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
NSLog(@"] Audio Route: A preferred new audio output path is now available.");
break;
case AVAudioSessionRouteChangeReasonUnknown:
NSLog(@"] Audio Route: The reason for the change is unknown.");
break;
default:
NSLog(@"] Audio Route: The reason for the change is very unknown.");
break;
}
// Output
AVAudioSessionPortDescription *output = [[session.currentRoute.outputs count]?session.currentRoute.outputs:nil objectAtIndex:0];
if ([output.portType isEqualToString:AVAudioSessionPortLineOut]) {
NSLog(@"] Audio Route: Output Port: LineOut");
}
else if ([output.portType isEqualToString:AVAudioSessionPortHeadphones]) {
NSLog(@"] Audio Route: Output Port: Headphones");
}
else if ([output.portType isEqualToString:AVAudioSessionPortBluetoothA2DP]) {
NSLog(@"] Audio Route: Output Port: BluetoothA2DP");
}
else if ([output.portType isEqualToString:AVAudioSessionPortBuiltInReceiver]) {
NSLog(@"] Audio Route: Output Port: BuiltInReceiver");
}
else if ([output.portType isEqualToString:AVAudioSessionPortBuiltInSpeaker]) {
NSLog(@"] Audio Route: Output Port: BuiltInSpeaker");
}
else if ([output.portType isEqualToString:AVAudioSessionPortHDMI]) {
NSLog(@"] Audio Route: Output Port: HDMI");
}
else if ([output.portType isEqualToString:AVAudioSessionPortAirPlay]) {
NSLog(@"] Audio Route: Output Port: AirPlay");
}
else if ([output.portType isEqualToString:AVAudioSessionPortBluetoothLE]) {
NSLog(@"] Audio Route: Output Port: BluetoothLE");
}
else {
NSLog(@"] Audio Route: Output Port: Unknown: %@",output.portType);
}
// Input
AVAudioSessionPortDescription *input = [[session.currentRoute.inputs count] ? session.currentRoute.inputs:nil objectAtIndex:0];
if ([input.portType isEqualToString:AVAudioSessionPortLineIn]) {
NSLog(@"] Audio Route: Input Port: LineIn");
}
else if ([input.portType isEqualToString:AVAudioSessionPortBuiltInMic]) {
NSLog(@"] Audio Route: Input Port: BuiltInMic");
}
else if ([input.portType isEqualToString:AVAudioSessionPortHeadsetMic]) {
NSLog(@"] Audio Route: Input Port: HeadsetMic");
}
else if ([input.portType isEqualToString:AVAudioSessionPortBluetoothHFP]) {
NSLog(@"] Audio Route: Input Port: BluetoothHFP");
}
else if ([input.portType isEqualToString:AVAudioSessionPortUSBAudio]) {
NSLog(@"] Audio Route: Input Port: USBAudio");
}
else if ([input.portType isEqualToString:AVAudioSessionPortCarAudio]) {
NSLog(@"] Audio Route: Input Port: CarAudio");
}
else {
NSLog(@"] Audio Input Port: Unknown: %@",input.portType);
}
NSLog(@"]--------------------------[ ]-----------------------------[");
}
请记住添加观察者,因为音频会话的委托也已弃用:
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector(audioInterruption:)
name: AVAudioSessionInterruptionNotification
object: nil];
PS:这里不需要重置类别(和6.0一样)
这是不可能的,我试着用路由改变的监听器(用 AudioSession)来弄清楚。我的结果是:由于Apple提供的类别,您无法单独配置输入或输出。我尝试 *PlayAndRecord,当我配对蓝牙设备时,路由更改如下:
old route : HeadsetBT
new route : SpeakerAndMicrophone
事实上,我的蓝牙不是耳机,只是扬声器......所以对我来说没有解决方案。
我非常有信心通过您的应用程序的音频会话可以做到这一点:
音频会话是您的应用程序和 iOS 之间的中介。每个 iPhone 应用程序都只有一个音频会话。您对其进行配置以表达您的应用程序的音频意图。首先,您要回答一些有关您希望应用程序如何运行的问题:
- 您希望您的应用程序如何响应中断,例如电话呼叫?
- 您打算将您的应用程序的声音与其他正在运行的应用程序的声音混合,还是打算使它们静音?
- 您的应用程序应如何响应音频路由更改,例如,当用户插入或拔出耳机时?
有了答案,您可以使用音频会话接口(在 AudioToolbox/AudioServices.h 中声明)来配置您的音频会话和应用程序。
挖掘这些文档:
让我知道进展如何!
我发现在使用 AirPod pro 耳机并尝试录制语音邮件问候语时,即使耳机已完全连接并正在使用中,手机也会使用内置麦克风。