Ios 通过上部(电话)扬声器播放音频

Ios 通过上部(电话)扬声器播放音频,ios,objective-c,avfoundation,media-player,audiotoolbox,Ios,Objective C,Avfoundation,Media Player,Audiotoolbox,我正在尝试让我的应用程序中的音频通过iPhone上的高音扬声器播放,即在通话中你按在耳朵上的扬声器。我知道这是可能的,因为我在App Store玩过一个游戏(“tap-tap-tap”下的“Heist”),它模拟手机通话,并能做到这一点 我在网上做了很多研究,但我很难找到一个讨论过这种可能性的人。绝大多数帖子似乎都是关于免提扬声器与插入式耳机(如和)的对比,而不是上面的“电话通话”扬声器与免提扬声器的对比。(问题的一部分可能是它没有一个好名字:“电话扬声器”通常指的是设备底部的免提扬声器,等等,

我正在尝试让我的应用程序中的音频通过iPhone上的高音扬声器播放,即在通话中你按在耳朵上的扬声器。我知道这是可能的,因为我在App Store玩过一个游戏(“tap-tap-tap”下的“Heist”),它模拟手机通话,并能做到这一点

我在网上做了很多研究,但我很难找到一个讨论过这种可能性的人。绝大多数帖子似乎都是关于免提扬声器与插入式耳机(如和)的对比,而不是上面的“电话通话”扬声器与免提扬声器的对比。(问题的一部分可能是它没有一个好名字:“电话扬声器”通常指的是设备底部的免提扬声器,等等,所以很难进行真正有针对性的搜索)。我已经研究过苹果公司的
音频会话类别路由覆盖
,但这些似乎再次(如果我错了,请纠正我)只涉及手机底部的免提扬声器,而不是手机顶部的扬声器

我发现一个帖子似乎是关于这个的:。它甚至提供了一堆代码,所以我以为我在家是免费的,但现在我似乎无法让代码正常工作。为了简单起见,我只是将
DisableSpeakerPhone
方法(如果我理解正确,应该是将音频重新路由到高音扬声器的方法)复制到我的
viewDidLoad
中,看看它是否能工作,但第一行“assert”失败,音频继续播放到底部。(我还导入了AudioToolbox框架,正如评论中所建议的,所以这不是问题所在。)

下面是我正在使用的主要代码块(这是我复制到我的
viewDidLoad
中进行测试的代码块),尽管我链接到的文章中还有一些方法:

void DisableSpeakerPhone () {
    UInt32 dataSize = sizeof(CFStringRef);
    CFStringRef currentRoute = NULL;
    OSStatus result = noErr;

    AudioSessionGetProperty(kAudioSessionProperty_AudioRoute, &dataSize, &currentRoute);

    // Set the category to use the speakers and microphone.
    UInt32 sessionCategory = kAudioSessionCategory_PlayAndRecord;
    result = AudioSessionSetProperty (
                                      kAudioSessionProperty_AudioCategory,
                                      sizeof (sessionCategory),
                                      &sessionCategory
                                      );
    assert(result == kAudioSessionNoError);

    Float64 sampleRate = 44100.0;
    dataSize = sizeof(sampleRate);
    result = AudioSessionSetProperty (
                                      kAudioSessionProperty_PreferredHardwareSampleRate,
                                      dataSize,
                                      &sampleRate
                                      );
    assert(result == kAudioSessionNoError);

    // Default to speakerphone if a headset isn't plugged in.
    // Overriding the output audio route

    UInt32 audioRouteOverride = kAudioSessionOverrideAudioRoute_None; 
    dataSize = sizeof(audioRouteOverride);
    AudioSessionSetProperty(
                            kAudioSessionProperty_OverrideAudioRoute,
                            dataSize,
                            &audioRouteOverride);

    assert(result == kAudioSessionNoError);

    AudioSessionSetActive(YES);
} 
所以我的问题是:有人能帮我找出代码不起作用的原因吗,或者有人能给我一个更好的建议,让我按下按钮,将音频上传到高音扬声器吗

PS我越来越熟悉iOS编程,但这是我第一次涉足音频会话等领域,因此非常感谢详细信息和代码示例!谢谢你的帮助

更新:

根据“He Was”(以下)的建议,我删除了上面引用的代码,并将其替换为:

[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayAndRecord error:nil];
[[AVAudioSession sharedInstance] setActive: YES error:nil];
viewDidLoad
的开头。但它仍然无法工作(我的意思是音频仍然从手机底部的扬声器中传出,而不是从顶部的接收器中传出)。显然,
AVAudioSessionCategoryPlayAndRecord
的默认行为应该是自己从接收器发送音频,所以还是有问题

更具体地说,我用这段代码做的是通过iPod音乐播放器播放音频(在
viewDidLoad
中的AVAudioSession行之后立即初始化,这是值得的):

iPod音乐播放器的媒体通过MPMediaPickerController选择:

- (void) mediaPicker: (MPMediaPickerController *) mediaPicker didPickMediaItems: (MPMediaItemCollection *) mediaItemCollection {
    if (mediaItemCollection) {
        [_musicPlayer setQueueWithItemCollection: mediaItemCollection];
        [_musicPlayer play];
    }

    [self dismissViewControllerAnimated:YES completion:nil];
}
这一切对我来说似乎相当简单,我没有任何错误或警告,我知道媒体选择器和音乐播放器工作正常,因为正确的歌曲开始播放,只是扬声器出了问题。是否有“使用此音频会话播放媒体”的方法或其他方法?或者是否有办法检查当前活动的音频会话类别,以确认没有任何内容可以将其切换回或其他内容?有没有一种方法可以强调地告诉代码使用接收者,而不是依赖默认值?我觉得我在一码线上,我只需要穿过最后一点


编辑:我刚刚想到了一个理论,就是iPod音乐播放器不想在接收器外播放。我的理由是:可以通过官方的iPod应用程序设置一首歌曲开始播放,然后通过我正在开发的应用程序进行无缝调整(暂停、跳过等)。从一个应用程序到下一个应用程序的连续播放让我想到,也许iPod音乐播放器有自己的音频路径设置,或者它没有停下来检查新应用程序中的设置?知道他们在说什么的人会认为可能是这样吗?

你必须先初始化音频会话

使用C API

  AudioSessionInitialize (NULL, NULL, NULL, NULL);
在iOS6中,您可以改用AVAudioSession方法(您需要导入AVFoundation框架才能使用
AVAudioSession
):

使用AVAudioSession初始化

 self.audioSession = [AVAudioSession sharedInstance];
使用AVAudioSession设置audioSession类别

 [self.audioSession setCategory:AVAudioSessionCategoryPlayAndRecord
                                       error:nil];
为了进一步研究,如果您想要更好的搜索词,以下是演讲者常数的全名:

const CFStringRef kAudioSessionOutputRoute_BuiltInReceiver;
const CFStringRef kAudioSessionOutputRoute_BuiltInSpeaker;
见苹果的文档

但真正的谜团是,为什么你在传送到接收器时遇到任何问题。这是playAndRecord类别的默认行为。苹果的
kAudioSessionOverrideAudioRoute\u None

“为kAudioSessionCategory\u Play和Record类别指定输出音频应发送到接收器。这是该类别的默认输出音频路由。

更新

func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {       
let routePort: AVAudioSessionPortDescription? = obsession. current Route. outputs. first
let portType: String? = routePort?.portType
if (portType == "Receiver") {
    try? audioSession.overrideOutputAudioPort(.speaker)
   }
   else {
        try? audioSession.overrideOutputAudioPort(.none)
   }
在更新的问题中,您显示您正在使用
MPMusicLayerController
类。此类调用全局音乐播放器(与音乐应用程序中使用的播放器相同)。此音乐播放器与你的应用程序是分开的,因此不会与你的应用程序的audioSession共享相同的音频会话。MPMusicLayerController将忽略您在应用程序的audioSession上设置的任何属性

如果你想控制应用程序的音频行为,你需要使用应用程序内部的音频框架。这将是
AVAudioRecorder
/
AVAudioPlayer
或核心音频(音频队列、音频单元或OpenAL)。无论您使用哪种方法,音频会话
NSError *error = nil;
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];
[session setActive: YES error:nil];
AVAudioSessionPortDescription *routePort = session.currentRoute.outputs.firstObject;
NSString *portType = routePort.portType;
if ([portType isEqualToString:@"Receiver"]) {
       [session  overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:&error];
} else {
       [session  overrideOutputAudioPort:AVAudioSessionPortOverrideNone error:&error];
}
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {       
let routePort: AVAudioSessionPortDescription? = obsession. current Route. outputs. first
let portType: String? = routePort?.portType
if (portType == "Receiver") {
    try? audioSession.overrideOutputAudioPort(.speaker)
   }
   else {
        try? audioSession.overrideOutputAudioPort(.none)
   }
func activateProximitySensor(isOn: Bool) {
    let device = UIDevice.current
    device.isProximityMonitoringEnabled = isOn
    if isOn {
        NotificationCenter.default.addObserver(self, selector: #selector(proximityStateDidChange), name: UIDevice.proximityStateDidChangeNotification, object: device)
        let session = AVAudioSession.sharedInstance()
        do{
            try session.setCategory(.playAndRecord)
            try session.setActive(true)
            try session.overrideOutputAudioPort(AVAudioSession.PortOverride.speaker)
        } catch {
            print ("\(#file) - \(#function) error: \(error.localizedDescription)")
        }
    } else {
        NotificationCenter.default.removeObserver(self, name: UIDevice.proximityStateDidChangeNotification, object: device)
    }
}

@objc func proximityStateDidChange(notification: NSNotification) {
    if let device = notification.object as? UIDevice {
        print(device)
        let session = AVAudioSession.sharedInstance()
        do{
            let routePort: AVAudioSessionPortDescription? = session.currentRoute.outputs.first
            let portType = routePort?.portType
            if let type = portType, type.rawValue == "Receiver" {
                try session.overrideOutputAudioPort(AVAudioSession.PortOverride.speaker)
            } else {
                try session.overrideOutputAudioPort(AVAudioSession.PortOverride.none)
            }
        } catch {
            print ("\(#file) - \(#function) error: \(error.localizedDescription)")
        }
    }
}