首页 文章

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

提问于
浏览
9

我正试图在我的应用程序中通过iPhone上的上扬声器播放音频,这是您在通话期间按下的那个 . 我知道这是可能的,因为我已经从App Store玩了一个游戏(通过“点击水龙头”的“The Heist”)来模拟电话,并且正是如此 .

我很难找到一个甚至讨论过这种可能性的人 . 绝大多数帖子似乎都是关于免提扬声器和插入式耳机(如thisthisthis),而不是上部扬声器与免提扬声器 . (问题的一部分可能没有一个好名字:"phone speaker"通常意味着设备底部的免提扬声器等等,所以它看起来像是Apple的 Audio Session Category Route Overrides ,但那些似乎再次出现(如果我的话,请纠正我)我错了)只处理底部的免提扬声器,而不是手机顶部的扬声器 .

我发现了一个似乎与此相关的帖子:link . 它甚至提供了一堆代码,所以我认为我在家是免费的,但现在我似乎无法让代码工作 . 为简单起见,我只是将 DisableSpeakerPhone 方法(如果我理解它正确应该是将音频重新路由到上方扬声器的那个)复制到我的 viewDidLoad 中,看看它是否可行,但是第一个"assert"线路失败了,音频继续发挥出底线 . (我也按照评论中的建议导入了AudioToolbox框架,因此这不是问题 . )

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

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);
}

So my question is this: 任何人都可以A)帮我弄清楚为什么代码不起作用,或者B)提供一个更好的建议,能够按下按钮并将音频路由到上方扬声器?

PS我越来越熟悉iOS编程了,但这是我第一次涉足AudioSessions等世界,因此非常感谢细节和代码示例!谢谢您的帮助!

UPDATE:

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

[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayAndRecord error:nil];
[[AVAudioSession sharedInstance] setActive: YES error:nil];

viewDidLoad 的开头 . 但它仍然无法工作(我的意思是音频仍然是从手机底部的扬声器而不是顶部的接收器) . 显然,默认行为应该是 AVAudioSessionCategoryPlayAndRecord 自己将音频发送出接收器,所以有些事情仍然是错误的 .

更具体地说,我正在使用此代码播放音频是通过iPod音乐播放器播放音频(在 viewDidLoad 上面的AVAudioSession行之后初始化,以获得它的 Value ):

_musicPlayer = [MPMusicPlayerController iPodMusicPlayer];

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

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

    [self dismissViewControllerAnimated:YES completion:nil];
}

这对我来说似乎相当简单,我没有任何错误或警告,我知道媒体选择器和音乐播放器工作正常,因为正确的歌曲开始播放,它只是错误的扬声器 . 可能有“播放媒体使用此AudioSession”方法或其他什么?或者有没有办法检查当前活动的音频会话类别,确认没有任何东西可以切换回来或什么?有没有办法强调告诉代码使用接收器,而不是依赖于默认值?我觉得我在一码线上,我只需要越过最后一点......

EDIT: 我只是想到了一个理论,它想要从接收器中发挥出来 . 我的推理:可以设置一首歌来开始播放官方iPod应用程序,然后通过应用程序无缝调整它(暂停,跳过等)我停止检查新应用程序中的设置?有谁知道他们在说什么,认为它可能是这样的吗?

3 回答

  • 4

    您必须先初始化音频会话 .

    使用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;
    

    看苹果的文档here

    但真正的谜团是你在接收器路由时遇到麻烦的原因 . 它's the default behaviour for the playAndRecord category. Apple'的文档 kAudioSessionOverrideAudioRoute_None

    “对于kAudioSessionCategory_PlayAndRecord类别,指定输出音频应该发送到接收器 . This is the default output audio route for this category.

    update

    在您更新的问题中,您显示您正在使用 MPMusicPlayerController 类 . 此类调用全局音乐播放器(音乐应用程序中使用的同一播放器) . 这个音乐播放器与你的应用程序是分开的,所以没有_2959217的audioSession . 您在应用程序的audioSession上设置的任何属性都将被MPMusicPlayerController忽略 .

    如果您想控制应用的音频行为,则需要使用应用内部的音频框架 . 这将是 AVAudioRecorder / AVAudioPlayer 或核心音频(音频队列,音频单元或OpenAL) . 无论使用哪种方法,都可以通过 AVAudioSession 属性或通过Core Audio API控制音频会话 . Core Audio为您提供更细粒度的控制,但随着iOS的每个新版本的更多内容被移植到AVFoundation,所以从这开始 .

    还要记住,音频会话为您提供了一种方式来描述应用程序音频相对于整个iOS环境的预期行为,但它不会让您完全控制 . Apple注重确保用户对其设备音频行为的期望在应用程序之间保持一致,并且当一个应用程序需要中断另一个应用程序的音频流时 .

    update 2

    在您的编辑中,您可以提到音频会话检查其他应用程序的音频会话设置的可能性 . 那不会发生1 . 这个想法是每个应用程序使用它的自包含音频会话设置它自己的音频行为 . 当多个应用程序竞争不可共享的资源(例如内部麦克风或其中一个扬声器)时,操作系统会在冲突的音频要求之间进行仲裁,并且通常会决定支持最有可能满足用户期望的行为 . 设备整体 .

    MPMusicPlayerController类稍微不同寻常,因为它使您能够让一个应用程序对另一个应用程序有一定程度的控制 . 在这种情况下,您的应用程序不播放音频,它正在向音乐播放器发送请求以代表您播放音频 . 您的控件受MPMusicPlayerController API范围的限制 . 要获得更多控制权,您的应用必须提供自己的音频播放实现 .

    在你的评论中,你想知道:

    有没有办法从MPMusicPlayerController中提取MPMediaItem然后通过特定于应用程序的音频会话播放它们,或类似的东西?

    那个's a (big) subject for a new question. Here is a good starting read (from Chris Adamson'博客)From iPod Library to PCM Samples in Far Fewer Steps Than Were Previously Necessary - 这是From iphone media library to pcm samples in dozens of confounding and potentially lossy steps的续集 - 这应该让你对你将面临的复杂性有所了解 . 这可能比iOS6更容易,但我不太确定!


    1 ios6中有一个 otherAudioPlaying 只读BOOL属性,但就是这样

  • 1

    一段时间也在努力解决这个问题 . 也许这会对以后的人有所帮助 . 你也可以使用更新的覆盖端口的方法 . 实际代码中的许多方法实际上都已弃用 .

    因此,如果您通过获取AudioSession sharedInstance,

    NSError *error = nil;
    AVAudioSession *session = [AVAudioSession sharedInstance];
    [session setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];
    [session setActive: YES error:nil];
    

    会话类别必须是AVAudioSessionCategoryPlayAndRecord您可以通过检查此值来获取当前输出 .

    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];
    }
    

    这应该是将输出切换到扬声器电话和接收器的快速方法 .

  • 27

    Swift 3.0 Code

    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)
       }
    

相关问题