Воспроизведение звука через верхний динамик (телефонный звонок)

я пытаюсь получить аудио в моем приложении, чтобы играть через верхний динамик на iPhone, тот, который вы нажимаете на ухо во время телефонного звонка. Я знаю, что это возможно, потому что я играл в игру из App Store ("The Heist" by "tap tap tap"), которая имитирует телефонные звонки и делает именно это.

я провел много исследований в интернете, но мне удивительно трудно найти кого-либо, кто даже обсуждал эту возможность. Подавляющее большинство должностей о громкоговорителе громкой связи vs подключенных наушниках (например,этой и этой и этой), а не верхний динамик "телефонный звонок" против динамика громкой связи. (Часть этой проблемы может не иметь хорошего названия для нее: "телефонный динамик" часто означает громкоговоритель громкой связи в нижней части устройства и т. д., поэтому трудно выполнить действительно целенаправленный поиск). Я заглянул в Apple Audio Session Category Route Overrides, но те снова кажутся (поправьте меня, если я ошибаюсь) дело только с громкоговорителем громкой связи внизу, а не с динамиком в верхней части телефона.

я нашел один пост, который, кажется, об этом:ссылке. Он даже предоставляет кучу кода, поэтому я думал, что я дома бесплатно, но теперь я не могу заставить код работать. Для простоты я просто скопировал 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);
} 

Итак, мой вопрос таков: может ли кто-нибудь A) помочь мне понять, почему этот код не работает, или B) предложить лучшее предложение для возможности нажать кнопку и направить звук до верхнего динамика?

PS Я все больше и больше знакомлюсь с программированием iOS, но это мой первый набег в мир Аудиосессий и тому подобного, поэтому детали и образцы кода очень ценятся! Спасибо за помощь!

обновление:

из предложения "он был" (ниже) я удалил код, указанный выше, и заменил его с:

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

в начале viewDidLoad. Однако он все еще не работает (под этим я подразумеваю, что звук все еще выходит из динамика в нижней части телефона вместо приемника вверху). По-видимому, поведение по умолчанию должно быть для AVAudioSessionCategoryPlayAndRecord для отправки звука из приемника самостоятельно, так что что-то все еще не так.

более конкретно, что я делаю с этим кодом, это воспроизведение звука через музыкальный плеер iPod (инициализируется сразу после AVAudioSession строки выше в viewDidLoad, для чего это стоит):

_musicPlayer = [MPMusicPlayerController iPodMusicPlayer];

и носитель для этого музыкального плеера iPod выбирается через MPMediaPickerController:

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

    [self dismissViewControllerAnimated:YES completion:nil];
}

все это кажется довольно простым для меня, у меня нет ошибок или предупреждений, и я знаю, что СМИ Picker и музыкальный плеер корректно работать, потому что правильные песни начинают играть, это просто из плохого динамика. Может ли быть метод "воспроизведения мультимедиа с помощью этой Аудиосессии" или что-то еще? Или есть ли способ проверить, какая категория аудиосессии в настоящее время активна, чтобы подтвердить, что ничто не могло переключить ее обратно или что-то еще? Есть ли способ решительно сказать коду использовать приемник, а не полагаться на значение по умолчанию для этого? Я чувствую, что нахожусь на одной ярдовой линии, мне просто нужно пересечь этот последний бит...

EDIT: я просто подумал о теории, в которой это что-то о музыкальном проигрывателе iPod, который не хочет играть из приемника. Мои рассуждения: можно установить песню, чтобы начать играть через официальное приложение iPod, а затем легко настроить его (пауза, пропустить и т. д.) Через приложение, которое я разрабатываю. Непрерывное воспроизведение из одного приложения в другое заставило меня подумать, что, возможно, музыкальный плеер iPod имеет свои собственные настройки звукового маршрута, или, может быть, он не останавливается, чтобы проверить настройки в новом приложении? Кто-нибудь, кто знает, о чем они говорят, думает, что это может быть что-то вроде этого?

3 ответов


сначала необходимо инициализировать аудиосессию.

использование C API

  AudioSessionInitialize (NULL, NULL, NULL, NULL);

в iOS6 вы можете использовать методы AVAudioSession вместо этого (вам нужно будет импортировать структуру AVFoundation для использования AVAudioSession):

инициализация с помощью AVAudioSession

 self.audioSession = [AVAudioSession sharedInstance];

настройка категории аудиосессии с помощью AVAudioSession

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

для дальнейшего исследования, если вы хотите лучшие условия поиска, вот полные имена константы для динамиков:

const CFStringRef kAudioSessionOutputRoute_BuiltInReceiver;
const CFStringRef kAudioSessionOutputRoute_BuiltInSpeaker;

см. документы apple здесь

но настоящая тайна заключается в том, почему у вас возникли проблемы с маршрутизацией к получателю. Это поведение по умолчанию для категории playAndRecord. Документация Apple kAudioSessionOverrideAudioRoute_None:

" указывает для категории kAudioSessionCategory_PlayAndRecord, что выходной звук должен идти в приемник. это для вывода звука по умолчанию для этого категория."

обновление

в вашем обновленном вопросе вы показываете, что используете MPMusicPlayerController класса. Этот класс вызывает глобальный музыкальный плеер (тот же плеер, который используется в приложении Music). Этот музыкальный плеер отделен от вашего приложения и поэтому не использует ту же аудиосессию, что и аудиосессия вашего приложения. Любые свойства, заданные в аудиосессии приложения, будут игнорироваться MPMusicPlayerController.

если вы хотите контролировать над звуковым поведением вашего приложения вам нужно использовать звуковую структуру, внутреннюю для вашего приложения. Это было бы AVAudioRecorder / AVAudioPlayer или Core Audio (аудио очереди, аудио блоки или OpenAL). Какой бы метод вы ни использовали, аудиосессия может управляться либо через AVAudioSession свойства или через Core Audio API. Core Audio дает вам более мелкозернистый контроль, но с каждым новым выпуском iOS больше его переносится на AVFoundation, поэтому начните с этого.

также помните, что аудиосессия предоставляет способ описать предполагаемое поведение аудио вашего приложения по отношению к общей среде iOS, но он не передаст вам полный контроль. Apple заботится о том, чтобы ожидания пользователя в отношении звукового поведения своего устройства оставались согласованными между приложениями и когда одному приложению нужно прерывать аудиопоток другого.

обновление 2

в вашем редактировании вы намекаете на возможность проверки аудио сессий другого приложения настройки сеанса. Этого не бывает!--48-->1. Идея заключается в том, что каждое приложение устанавливает свои предпочтения для собственного аудио-поведения, используя его автономную аудиосессию. The операционные системы арбитраж между конфликтующими требованиями к аудио, когда более одного приложения конкурирует за неразделимый ресурс, такой как внутренний микрофон или один из динамиков, и обычно принимает решение в пользу того поведения, которое, скорее всего, соответствует ожиданиям пользователя устройство в целом.

класс MPMusicPlayerController немного необычен тем, что он дает вам возможность для одного приложения иметь некоторую степень контроля над другим. В этом случае ваше приложение не воспроизводит аудио, оно отправляет запрос музыкальному проигрывателю на воспроизведение аудио от вашего имени. Ваш элемент управления ограничен экстентом API MPMusicPlayerController. Для большего контроля ваше приложение должно будет обеспечить собственную реализацию воспроизведения звука.

в вашей комментарий вам интересно:

может ли быть способ вытащить MPMediaItem из MPMusicPlayerController, а затем воспроизвести их через аудио-сеанс приложения или что-нибудь в этом роде?

это (большая) тема для нового вопроса. Вот хорошее начальное чтение (из блога Криса Адамсона) от библиотеки iPod до образцов PCM в гораздо меньшем количестве шагов, чем это было необходимо ранее - это продолжение из медиа-библиотеки iphone к образцам pcm в десятках запутанных и потенциально с потерями шагов - это должно дать вам представление о сложности, с которой вы столкнетесь. Это мая стало легче с iOS6, но я бы не был так уверен!


1 есть otherAudioPlaying только для чтения bool свойство в ios6, но это о нем


тоже некоторое время боролся с этим. Может быть, это поможет кому-то позже.Можно также использовать новые методы переопределения портов. Многие из методов в вашем примере кода фактически устарели.

Итак, если у вас есть Аудиосессия 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)
   }