AVAudioEngine воспроизводит многоканальный звук
Простой вопрос Как воспроизвести многоканальные аудиофайлы (>2 канала) с помощью AVAudioEngine, чтобы я мог слышать все каналы на 2-канальном выходе по умолчанию (наушники / динамик). Следующий код (без проверки на наличие ошибок) воспроизводит файл на первых двух каналах, но я слышу его только при подключенных наушниках.
AVAudioFile *file = [[AVAudioFile alloc] initForReading:[[NSBundle mainBundle] URLForResource:@"nums6ch" withExtension:@"wav"] error:nil];
AVAudioEngine *engine = [[AVAudioEngine alloc] init];
AVAudioPlayerNode *player = [[AVAudioPlayerNode alloc] init];
AVAudioMixerNode *mixer = [[AVAudioMixerNode alloc] init];
[engine attachNode:player];
[engine attachNode:mixer];
AVAudioFormat *processingFormat = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32 sampleRate:file.processingFormat.streamDescription->mSampleRate channels:2 interleaved:false];
[engine connect:player to:mixer format:processingFormat];
[engine connect:mixer to:engine.outputNode format:processingFormat];
[engine startAndReturnError:nil];
[player scheduleFile:file atTime:nil completionHandler:nil];
[player play];
Я перепробовал множество комбинаций с форматами для соединений player->mixer и mixer->output, но они либо приводят к тому же результату, что и приведенный выше код, либо, скорее, к аварийному завершению с одним из них:
ERROR: [0x19c392310] AVAudioNode.mm:495: AUGetFormat: required condition is false: nil != channelLayout && channelLayout.channelCount == asbd.mChannelsPerFrame
*** Terminating app due to uncaught exception 'com.apple.coreaudio.avfaudio', reason: 'required condition is false: nil != channelLayout && channelLayout.channelCount == asbd.mChannelsPerFrame'
или AUSetFormat OSStatus код -10868 (формат не поддерживается, если я не ошибаюсь). Использование file.processingFormat для любого соединения приводит к сбою приложения с первой ошибкой выше. Также сбой происходит на [player play];
Должен быть какой-то способ воспроизвести его так, как я хочу, потому что я могу сделать это без проблем с помощью AUGraph, однако, поскольку AVAudioEngine предоставляет одну функцию, которую я должен включить, я застрял с ней.
Многоканальные аудиофайлы, которые я использую для проверки своего кода, можно найти здесь.
ОБНОВЛЕНИЕ Хорошо, поэтому прослушивание звука только в наушниках, вероятно, произошло из-за того, что я забыл установить аудио сеанс в активном тестовом приложении. Но я все еще слышу только первые два канала...
2 ответа
Итак, вот что мне удалось до сих пор. Это далеко от совершенства, но это несколько работает.
Чтобы получить все каналы, вам нужно использовать AVAudioPCMBuffer и хранить два канала из файла в каждом. Кроме того, для каждой пары каналов вам нужен отдельный AVAudioPlayerNode, затем просто подключите каждый проигрыватель к AVAudioMixerNode, и все готово. Несколько простых кодов для 6-канального звука:
AVAudioFormat *outputFormat = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32 sampleRate:file.processingFormat.sampleRate channels:2 interleaved:false];
AVAudioFile *file = [[AVAudioFile alloc] initForReading:[[NSBundle mainBundle] URLForResource:@"nums6ch" withExtension:@"wav"] error:nil];
AVAudioPCMBuffer *wholeBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:file.processingFormat frameCapacity:(AVAudioFrameCount)file.length];
AVAudioPCMBuffer *buffer1 = [[AVAudioPCMBuffer alloc] initWithPCMFormat:outputFormat frameCapacity:(AVAudioFrameCount)file.length];
AVAudioPCMBuffer *buffer2 = [[AVAudioPCMBuffer alloc] initWithPCMFormat:outputFormat frameCapacity:(AVAudioFrameCount)file.length];
AVAudioPCMBuffer *buffer3 = [[AVAudioPCMBuffer alloc] initWithPCMFormat:outputFormat frameCapacity:(AVAudioFrameCount)file.length];
memcpy(buffer1.audioBufferList->mBuffers[0].mData, wholeBuffer.audioBufferList->mBuffers[0].mData, wholeBuffer.audioBufferList->mBuffers[0].mDataByteSize);
memcpy(buffer1.audioBufferList->mBuffers[1].mData, wholeBuffer.audioBufferList->mBuffers[1].mData, wholeBuffer.audioBufferList->mBuffers[1].mDataByteSize);
buffer1.frameLength = wholeBuffer.audioBufferList->mBuffers[0].mDataByteSize/sizeof(UInt32);
memcpy(buffer2.audioBufferList->mBuffers[0].mData, wholeBuffer.audioBufferList->mBuffers[2].mData, wholeBuffer.audioBufferList->mBuffers[2].mDataByteSize);
memcpy(buffer2.audioBufferList->mBuffers[1].mData, wholeBuffer.audioBufferList->mBuffers[3].mData, wholeBuffer.audioBufferList->mBuffers[3].mDataByteSize);
buffer2.frameLength = wholeBuffer.audioBufferList->mBuffers[0].mDataByteSize/sizeof(UInt32);
memcpy(buffer3.audioBufferList->mBuffers[0].mData, wholeBuffer.audioBufferList->mBuffers[4].mData, wholeBuffer.audioBufferList->mBuffers[4].mDataByteSize);
memcpy(buffer3.audioBufferList->mBuffers[1].mData, wholeBuffer.audioBufferList->mBuffers[5].mData, wholeBuffer.audioBufferList->mBuffers[5].mDataByteSize);
buffer3.frameLength = wholeBuffer.audioBufferList->mBuffers[0].mDataByteSize/sizeof(UInt32);
AVAudioEngine *engine = [[AVAudioEngine alloc] init];
AVAudioPlayerNode *player1 = [[AVAudioPlayerNode alloc] init];
AVAudioPlayerNode *player2 = [[AVAudioPlayerNode alloc] init];
AVAudioPlayerNode *player3 = [[AVAudioPlayerNode alloc] init];
AVAudioMixerNode *mixer = [[AVAudioMixerNode alloc] init];
[engine attachNode:player1];
[engine attachNode:player2];
[engine attachNode:player3];
[engine attachNode:mixer];
[engine connect:player1 to:mixer format:outputFormat];
[engine connect:player2 to:mixer format:outputFormat];
[engine connect:player3 to:mixer format:outputFormat];
[engine connect:mixer to:engine.outputNode format:outputFormat];
[engine startAndReturnError:nil];
[player1 scheduleBuffer:buffer1 completionHandler:nil];
[player2 scheduleBuffer:buffer2 completionHandler:nil];
[player3 scheduleBuffer:buffer3 completionHandler:nil];
[player1 play];
[player2 play];
[player3 play];
Сейчас это решение далеко от совершенства, так как между парами каналов будет задержка из-за вызова play
для каждого игрока в разное время. Я также все еще не могу воспроизвести 8-канальный звук из моих тестовых файлов (см. Ссылку в OP). Формат обработки AVAudioFile имеет 0 для количества каналов, и даже если я создаю свой собственный формат с правильным количеством каналов и разметкой, я получаю ошибку при чтении из буфера. Обратите внимание, что я могу прекрасно воспроизводить этот файл с помощью AUGraph.
Поэтому я буду ждать, прежде чем принять этот ответ, если у вас есть лучшее решение, пожалуйста, поделитесь.
РЕДАКТИРОВАТЬ
Таким образом, похоже, что и моя неспособность синхронизировать узлы, и неспособность воспроизвести этот конкретный 8-канальный звук являются ошибками (подтверждено поддержкой разработчиков Apple).
Так что небольшой совет для людей, вмешивающихся в аудио на iOS. Хотя AVAudioEngine отлично подходит для простых вещей, вам определенно следует выбрать AUGraph с более сложными вещами, даже с теми, которые предполагаются для работы с AVAudioEngine. И если вы не знаете, как копировать определенные вещи из AVAudioEngine в AUGraph (как и я), ну, вам жаль.
У меня была похожая проблема в Swift. Ошибка была 'com.apple.coreaudio.avfaudio', причина: 'обязательное условие ложно:! Nodeimpl->SslEngineImpl()'.
Задача состояла в том, чтобы воспроизвести два аудиофайла, один за другим. Если после воспроизведения первого аудиофайла нажать кнопку "Стоп", а затем воспроизвести второй аудиофайл, произойдет сбой системы.
Я обнаружил, что в функции, которую я создал, у меня было audioEngine.attachNode(audioPlayerNode)
это означает, что audioPlayerNode
был прикреплен к audioEngine
один раз и потом вышел. Поэтому я переместил эту привязку к viewDidLoad()
функционировать так, чтобы он проходил каждый раз.