通过上方(电话)扬声器播放audio

我试图在我的应用程序中获取audio,通过iPhone上方的扬声器进行播放,这是您在通话期间按到耳边的那个扬声器。 我知道这是可能的,因为我已经在App Store上玩过一个模拟电话的游戏(“The Heist”)。

我在网上做了大量的研究,但是我发现一个甚至讨论过这个可能性的人都很难过。 绝大多数post似乎是关于免提扬声器vs插入式耳机,(比如这个 , 这个和这个 ),而不是上面的“电话”扬声器与免提扬声器。 (这个问题的一部分可能没有一个好名字:“电话扬声器”往往意味着设备底部的免提扬声器等,所以很难做一个真正有针对性的search)。 我已经看过苹果的Audio Session Category Route Overrides ,但似乎(纠正我,如果我错了)只处理底部的免提扬声器,而不是在电话顶部的扬声器。

我发现一个似乎是关于这个链接的post。 它甚至提供了一堆代码,所以我认为我是免费的,但现在我似乎无法让代码工作。 为了简单起见,我只复制了DisableSpeakerPhone方法(如果我正确地理解它应该是将audio重新路由到上面的扬声器的方法)到我的viewDidLoad ,看它是否会工作,但第一个“断言”行失败,audio继续发挥出底部。 (我还导入了AudioToolbox框架,正如评论中所build议的,所以这不是问题。)

这里是我正在使用的主要代码块(这是我复制到我的viewDidLoadtesting),虽然在我链接到的文章中有几个方法:

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

所以我的问题是这样的:任何人都可以A)帮我找出为什么代码不工作,或B)提供一个更好的build议,能够按下一个button,并将audio路由到上面的扬声器?

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

更新:

从“他是”的build议(下)我已经删除了上面引用的代码,并用下面的代码replace:

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

viewDidLoad开头。 但它仍然没有工作,(我的意思是audio仍然从电话底部的扬声器,而不是顶部的接收器)。 显然,默认行为应该是AVAudioSessionCategoryPlayAndRecord自己发送audio,所以有些东西仍然是错误的。

更具体地说,我在做这个代码的过程是通过iPod音乐播放器播放audio(在viewDidLoad的AVAudioSession行之后进行初始化,值得):

 _musicPlayer = [MPMusicPlayerController iPodMusicPlayer]; 

并且通过MPMediaPickerControllerselectiPod音乐播放器的媒体:

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

这一切对我来说似乎相当直接,我没有任何错误或警告,我知道媒体select器和音乐播放器工作正常,因为正确的歌曲开始播放,只是错误的发言者。 可以有一个“播放媒体使用这个AudioSession”方法或东西? 还是有办法检查什么样的audio会话目前是活跃的,以确认没有任何东西可以切换回来或什么? 有没有办法强调告诉代码使用接收器,而不是依靠默认的做法呢? 我觉得我在一码线上,我只是需要穿越最后一点…

编辑:我只是想到了一个理论,其中它是关于iPod音乐播放器,不想播放接收器的东西。 我的推理:可以设置一首歌曲,通过官方的iPod应用程序开始播放,然后通过我正在开发的应用程序进行无缝调整(暂停,跳过等)。 从一个应用程序到下一个应用程序的连续播放让我觉得iPod音乐播放器可能有自己的audiopath设置,或者它不停下来检查新应用程序中的设置? 有谁知道他们在谈论什么呢?可以这样想吗?

你必须先初始化audio会话。

使用C API

  AudioSessionInitialize (NULL, NULL, NULL, NULL); 

在iOS6中,您可以使用AVAudioSession方法(您将需要导入AVFoundation框架以使用AVAudioSession ):

使用AVAudioSession进行初始化

  self.audioSession = [AVAudioSession sharedInstance]; 

使用AVAudioSession设置audioSession类别

  [self.audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:nil]; 

为了进一步的研究,如果你想要更好的search条件,这里是发言者的常量的全名:

 const CFStringRef kAudioSessionOutputRoute_BuiltInReceiver; const CFStringRef kAudioSessionOutputRoute_BuiltInSpeaker; 

在这里看苹果的文档

但真正的秘密是为什么你有任何麻烦路由到接收器。 这是playAndRecord类别的默认行为。 苹果的kAudioSessionOverrideAudioRoute_None的文档:

“对于kAudioSessionCategory_PlayAndRecord类别,指定输出audio应该到达接收器, 这是该类别的默认输出audio路由。

更新

在您更新的问题中,您会发现您正在使用MPMusicPlayerController类。 这个类调用全局音乐播放器(音乐应用程序中使用的同一个播放器)。 此音乐播放器与您的应用程序是分开的,因此不会与您的应用程序的audioSession共享相同的audio会话。 您在应用程序的audioSession中设置的任何属性都将被MPMusicPlayerController忽略。

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

另外请记住,audio会话为您提供了一种方式来描述您的应用程序audio的预期行为与整个iOS环境的关系,但不会给您完全的控制权。 苹果公司注意确保用户对其设备audio行为的期望在应用程序之间保持一致,以及当一个应用程序需要中断另一个audiostream时。

更新2

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

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

在你的评论中你想知道:

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

这是一个新的问题(大)的主题。 这里是一个很好的开始阅读(从Chris Adamson的博客) 从iPod库到PCM样本比以前less得多的步骤是必要的 – 这是续集从iphone媒体库到pcm样本在几十个混杂和潜在的有损步骤 – 应该给你对你将面临的复杂性有一种意识。 这可能会更容易,因为iOS6,但我不会那么肯定!


1otherAudioPlaying还有一个otherAudioPlaying只读BOOL属性,不过就是这样

也一直在挣扎着。 也许这会稍后帮助别人。你也可以使用更新的方法来覆盖端口。 示例代码中的许多方法实际上已被弃用。

所以如果你通过获得你的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]; } 

这应该是将输出切换到扬声器电话和接收器的快捷方式。

Swift 3.0代码

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