简体   繁体   中英

Forcing iPhone Microphone as Audio Input

I am developing an iOS application (targetted specifically for iPhone, at the moment) that requires the application to record audio only from iPhone internal microphone (even when headphone/headset is plugged in), and playback at headphone (let's assumed headphone is plugged in for now).

I am wondering if this is currently possible with the available APIs? If so, can anyone please shed some light on how do I go about doing this?

Thanks!

I believe the answer to this question to be 'no'. I used an iPhone 4 and the new-to-iOS 4 AVFoundation to experiment, focussing on the AVCaptureDevice class.

I added the following to an application:

NSLog(@"%@", [AVCaptureDevice devices]);

So that asks that all devices that can be used for capturing audio and/or video be listed. Without the headphones plugged in, I get:

(
    "Back Camera",
    "Front Camera",
    "iPhone Microphone" 
)

With the headphones plugged in I get:

(
    "Back Camera",
    "Front Camera",
    Headphones
)

So the iPhone microphone drops off the list of available AVCaptureDevices as soon as the headphones become available. To probe this further, I added a quick bit of code to grab the AVCaptureDevice instance for the available audio device and to print its unique ID. For both the device identifying itself as "iPhone Microphone" and the device identifying itself as "Headphones", I got:

com.apple.avfoundation.avcapturedevice.built-in_audio:0

It would seem to me to be obvious that two devices can't have the same unique ID, so clearly it's the same device changing its state. Although AVCaptureDevices have a lot of stuff for setting state, it's limited to visual things like focus, exposure, flash and white balance.

Looks like its really not possible.

My goal is to send output to bluetooth headsets and record input from it too. As far I can see, my best options are: "PlayAndRecord + AllowBluetoothInput" property (iphone 4, nokia BH-214 headset)

IMPORTANT thing is that according to the apple documentation, you always have to RE-override your audio category when audio route changes!

THIS IS MY ROUTE CHANGE LISTENER method, that prints: 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, &currentRouteDescriptionDictionary);
    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(@"]--------------------------[  ]-----------------------------[");        

}

}

Since apple changed the audio system again starting from 7.0 I'm going to post the update-ed code over here:

#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(@"]--------------------------[  ]-----------------------------[");

}

Remember to add observer since the audio session's delegate is deprecated too:

[[NSNotificationCenter defaultCenter] addObserver: self
                                                 selector: @selector(audioInterruption:)
                                                     name: AVAudioSessionInterruptionNotification
                                                   object: nil];

PS: you dont need to reset the category here (as in 6.0)

It is not possible, I try to figure out with route changed listener (with AudioSession). My result is : you can't configure separately input or output because of categories provided by Apple. I try *PlayAndRecord, when I pair a bluetooth device, route change like this :

old route : HeadsetBT
new route : SpeakerAndMicrophone

In fact, my bluetooth is not an headset, just speakers... So for me there is no solution.

I am pretty confident this is possible via your application's Audio Session:

An audio session is an intermediary between your application and iOS. Each iPhone application has exactly one audio session. You configure it to express your application's audio intentions. To start, you answer some questions about how you want your application to behave:

  • How do you want your application to respond to interruptions, such as a phone call?
  • Do you intend to mix your application's sounds with those from other running applications, or do you intend to silence them?
  • How should your application respond to an audio route change, for example, when a user plugs in or unplugs a headset?

With answers in hand, you employ the audio session interface (declared in AudioToolbox/AudioServices.h) to configure your audio session and your application.

Dig on these docs:

And let me know how it goes!

我发现当使用 AirPod pro 耳机并尝试录制语音邮件问候语时,即使耳机已完全连接且正在使用中,手机仍使用内置麦克风。

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM