通話中に耳に押し付けたiPhoneの上部スピーカーから音声を再生できるようにアプリで音声を取得しようとしています。私はAppStoreのゲーム(「TheHeist」と「taptap tap」)をプレイして電話をシミュレートし、まさにそれを実行しているので、それが可能であることを知っています。
私はオンラインで多くの調査を行ってきましたが、その可能性について話し合った人を見つけるのに驚くほど苦労しています。圧倒的多数の投稿は、ハンズフリースピーカーとプラグインイヤホン( this と this と this など)に関するもののようです。上部の「電話」スピーカーとハンズフリースピーカー。 (その問題の一部は、適切な名前が付いていない可能性があります。「電話スピーカー」は、デバイスの下部にあるハンズフリースピーカーなどを意味することが多いため、ターゲットを絞った検索を行うのは困難です)。私はAppleのAudio Session Category Route Overrides
を調べましたが、それらは再び(私が間違っている場合は訂正してください)電話の上部のスピーカーではなく、下部のハンズフリースピーカーのみを扱っているようです。
私はこれについてのように思われる1つの投稿を見つけました: リンク 。たくさんのコードも提供されているので、私は家が空いていると思っていましたが、今ではコードを機能させることができないようです。簡単にするために、DisableSpeakerPhone
メソッド(正しく理解していれば、オーディオを上部スピーカーに再ルーティングするメソッド)をviewDidLoad
にコピーして、機能するかどうかを確認しましたが、最初の「アサート」行は失敗し、オーディオは引き続き下部から再生されます。 (コメントで示唆されているように、AudioToolbox Frameworkもインポートしたので、問題はありません。)
リンクした記事にはさらにいくつかのメソッドがありますが、これが私が使用しているコードのメインブロックです(これはテストのためにviewDidLoad
にコピーしたものです)。
void DisableSpeakerPhone () {
UInt32 dataSize = sizeof(CFStringRef);
CFStringRef currentRoute = NULL;
OSStatus result = noErr;
AudioSessionGetProperty(kAudioSessionProperty_AudioRoute, &dataSize, ¤tRoute);
// 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)より良い提案を提供できますかボタンを押してオーディオを上部スピーカーにルーティングできますか?
PS私はiOSプログラミングにますます慣れてきていますが、これはAudioSessionsなどの世界への私の最初の進出なので、詳細とコードサンプルは大歓迎です!ご協力ありがとうございました!
更新:
「HeWas」(下記)の提案から、上記で引用したコードを削除し、次のように置き換えました。
[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayAndRecord error:nil];
[[AVAudioSession sharedInstance] setActive: YES error:nil];
viewDidLoad
の先頭。ただし、まだ機能していません(つまり、音声は電話の上部にあるレシーバーではなく、下部にあるスピーカーから出ています)。どうやらデフォルトの動作はAVAudioSessionCategoryPlayAndRecord
がそれ自体でレシーバーからオーディオを送信することであるはずなので、何かがまだ間違っています。
より具体的には、このコードで私が行っているのは、iPod Music Playerを介してオーディオを再生することです(上記のviewDidLoad
のAVAudioSession行の直後に初期化されます)。
_musicPlayer = [MPMusicPlayerController iPodMusicPlayer];
そのiPodMusic Playerのメディアは、MPMediaPickerController
から選択されます。
- (void) mediaPicker: (MPMediaPickerController *) mediaPicker didPickMediaItems: (MPMediaItemCollection *) mediaItemCollection {
if (mediaItemCollection) {
[_musicPlayer setQueueWithItemCollection: mediaItemCollection];
[_musicPlayer play];
}
[self dismissViewControllerAnimated:YES completion:nil];
}
これはすべて私にはかなり簡単に思えます。エラーや警告はありません。正しい曲が再生を開始するため、メディアピッカーとミュージックプレーヤーが正しく機能していることがわかります。スピーカーが間違っているだけです。 「このAudioSessionを使用してメディアを再生する」メソッドなどはありますか?または、現在アクティブなオーディオセッションカテゴリをチェックして、何も切り替えられなかったことを確認する方法はありますか?デフォルトに依存するのではなく、レシーバーを使用するようにコードに強調して指示する方法はありますか?私は1ヤードラインにいるような気がします、私はその最後のビットを越える必要があります...
編集:私はちょうど理論を考えました。それはレシーバーから再生したくないiPodミュージックプレーヤーについての何かです。私の推論:公式のiPodアプリで再生を開始するように曲を設定し、開発中のアプリでシームレスに調整(一時停止、スキップなど)することができます。あるアプリから次のアプリへの連続再生は、iPod Music Playerに独自のオーディオルート設定があるのか、それとも新しいアプリの設定を確認するのを止めないのかと思いました。彼らが話していることを知っている人は、それがそのようなものである可能性があると思いますか?
最初にオーディオセッションを初期化する必要があります。
CAPIの使用
AudioSessionInitialize (NULL, NULL, NULL, NULL);
IOS6では、代わりにAVAudioSessionメソッドを使用できます(AVAudioSession
を使用するには、AVFoundationフレームワークをインポートする必要があります)。
AVAudioSessionを使用した初期化
self.audioSession = [AVAudioSession sharedInstance];
AVAudioSessionを使用してaudioSessionカテゴリを設定する
[self.audioSession setCategory:AVAudioSessionCategoryPlayAndRecord
error:nil];
さらに調査するために、より適切な検索用語が必要な場合は、スピーカーの定数のフルネームを次に示します。
const CFStringRef kAudioSessionOutputRoute_BuiltInReceiver;
const CFStringRef kAudioSessionOutputRoute_BuiltInSpeaker;
appleのドキュメントを参照してください ここ
しかし、本当の謎は、受信機へのルーティングに問題がある理由です。これは、playAndRecordカテゴリのデフォルトの動作です。 kAudioSessionOverrideAudioRoute_None
に関するAppleのドキュメント:
「kAudioSessionCategory_PlayAndRecordカテゴリの場合、出力オーディオをレシーバーに送信するように指定します。これは、このカテゴリのデフォルトの出力オーディオルートです。」
更新
更新された質問で、MPMusicPlayerController
クラスを使用していることを明らかにします。このクラスは、グローバルミュージックプレーヤー(ミュージックアプリで使用されているのと同じプレーヤー)を呼び出します。この音楽プレーヤーはアプリとは別のものであるため、アプリのaudioSessionと同じオーディオセッションを共有しません。アプリのaudioSessionに設定したプロパティは、MPMusicPlayerControllerによって無視されます。
アプリのオーディオ動作を制御する場合は、アプリ内部のオーディオフレームワークを使用する必要があります。これは、AVAudioRecorder
/AVAudioPlayer
またはコアオーディオ(オーディオキュー、オーディオユニット、またはOpenAL)になります。どちらの方法を使用する場合でも、オーディオセッションはAVAudioSession
プロパティまたはCoreAudioAPIを介して制御できます。 Core Audioを使用すると、よりきめ細かい制御が可能になりますが、iOSの新しいリリースごとに、より多くの機能がAVFoundationに移植されるため、最初から始めてください。
また、オーディオセッションは、iOS環境全体に関連してアプリのオーディオの意図された動作を説明する方法を提供しますが、完全な制御を提供するわけではないことを忘れないでください。 Appleは、デバイスのオーディオ動作に対するユーザーの期待がアプリ間で一貫していること、およびあるアプリが別のアプリのオーディオストリームを中断する必要がある場合に注意を払います。
更新2
編集では、オーディオセッションが他のアプリのオーディオセッション設定をチェックする可能性をほのめかします。それは起こりません1。アイデアは、各アプリが、自己完結型のオーディオセッションを使用して、独自のオーディオ動作のプリファレンスを設定することです。 オペレーティングシステムは、複数のアプリが内蔵マイクやスピーカーの1つなどの共有できないリソースをめぐって競合する場合、競合するオーディオ要件を調停し、通常はそれを優先して決定しますデバイス全体に対するユーザーの期待に応える可能性が最も高い動作。
MPMusicPlayerControllerクラスは、あるアプリが別のアプリをある程度制御できるという点で少し変わっています。この場合、アプリはオーディオを再生していません。代わりにオーディオを再生するようにミュージックプレーヤーにリクエストを送信しています。コントロールは、MPMusicPlayerControllerAPIの範囲によって制限されます。より詳細に制御するには、アプリが独自のオーディオ再生の実装を提供する必要があります。
あなたのコメントであなたは疑問に思います:
MPMusicPlayerControllerからMPMediaItemをプルして、アプリ固有のオーディオセッションなどで再生する方法はありますか?
それは新しい質問の(大きな)主題です。これは良い最初の読み物です(Chris Adamsonのブログから) iPodライブラリからPCMサンプルへのステップは以前に必要だったよりもはるかに少ない -それは iphoneメディアライブラリからpcmサンプルへ数十の混乱し、潜在的に損失のあるステップ -それはあなたが直面するであろう複雑さの感覚をあなたに与えるはずです。このmayはiOS6以降簡単になりましたが、よくわかりません。
1 ios6には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)
}
Swift 5.
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)")
}
}
}