API, на который вам нужно обратить внимание: "Audio Queue Services" .
Правильно, здесь приведен общий обзор того, что вам нужно сделать.
При воспроизведении аудио вы устанавливаете очередь или службу. Эта очередь будет запрашивать некоторые аудиоданные. Затем, когда он проиграл все это, он попросит еще немного. Это продолжается до тех пор, пока вы не остановите очередь или не закончите воспроизведение данных.
Двумя основными API нижнего уровня в iOS являются Audio Unit и Audio Queue. Под более низким уровнем я подразумеваю API, который немного более грубый, чем сказать «просто воспроизвести этот mp3» или что-то в этом роде.
По моему опыту, Audio Unit имеет меньшую задержку, но Audio Queue больше подходит для потоковой передачи аудио. Поэтому я думаю, что для вас последний вариант лучше.
Ключевой частью того, что вам нужно сделать, является буферизация. Это означает, что данные загружаются достаточно, чтобы в вашем воспроизведении не было пробелов. Возможно, вы захотите справиться с этим, изначально загружая больший объем данных. Тогда вы играете впереди. Вы будете иметь достаточно большой буфер в памяти, одновременно получая больше данных в фоновом потоке.
Пример проекта, который я бы рекомендовал внимательно изучить, - SpeakHere . В частности посмотрите на классы SpeakHereController.mm
и AQPlayer.mm
.
Контроллер обрабатывает такие вещи, как запуск и остановка AQPlayer
. AQPlayer
представляет AudioQueue
. Посмотрите внимательно на AQPlayer::AQBufferCallback
. Это метод обратного вызова, который вызывается, когда очереди требуется больше данных.
Вам необходимо убедиться, что настройки данных очереди и формат полученных данных точно совпадают. Проверяйте такие вещи, как количество каналов (моно или стерео?), Количество кадров, целые числа или числа с плавающей запятой и частота дискретизации. Если что-то не совпадает, вы либо получите EXC_BAD_ACCESS
ошибок при прохождении через соответствующие буферы, либо получите белый шум, либо - в случае неправильной частоты дискретизации - звук, который звучит замедленно или ускорился.
Обратите внимание, что SpeakHere запускает две аудио-очереди; один для записи, а другой для воспроизведения. Все аудио вещи работают с использованием буферов данных. Таким образом, вы всегда передаете указатели на буферы. Так, например, во время воспроизведения вы скажете буфер памяти, который имеет 20 секунд аудио. Возможно, каждую секунду ваш обратный вызов будет вызываться из очереди, по сути говоря, «дайте мне еще одну секунду данных, пожалуйста». Вы можете думать об этом как о воспроизводящей головке, которая перемещается по вашим данным, запрашивая дополнительную информацию.
Давайте посмотрим на это более подробно. В отличие от SpeakHere, вы будете работать с буферами памяти, а не записывать аудио во временный файл.
Обратите внимание, что если вы работаете с большими объемами данных на устройстве iOS, у вас не будет другого выбора, кроме как хранить большую часть данных на диске. Особенно, если пользователь может воспроизвести звук, перемотать его и т. Д., Вам нужно где-то все это хранить!
В любом случае, предполагая, что AQPlayer
будет считывать из памяти, нам нужно изменить его следующим образом.
Во-первых, где-то для хранения данных, в AQPlayer.h
:
void SetAudioBuffer(float *inAudioBuffer) { mMyAudioBuffer = inAudioBuffer; }
У вас уже есть эти данные в NSData
объекте, так что вы можете просто передать указатель, возвращенный из вызова на [myData bytes]
.
Что предоставляет эти данные в аудио-очередь? Это метод обратного вызова, настроенный в AQPlayer
:
void AQPlayer::AQBufferCallback(void * inUserData,
AudioQueueRef inAQ,
AudioQueueBufferRef inCompleteAQBuffer)
Метод, который мы будем использовать для добавления части наших данных в аудио-очередь: AudioQueueEnqueueBuffer
:
AudioQueueEnqueueBuffer(inAQ, inCompleteAQBuffer, 0, NULL);
inAQ
- ссылка на очередь, полученную нашим обратным вызовом.
inCompleteAQBuffer
- указатель на буфер аудио-очереди.
Итак, как вы получаете ваши данные - то есть указатель, возвращаемый путем вызова метода bytes
вашего объекта NSData - в буфер аудио-очереди inCompleteAQBuffer
?
Использование memcpy
:
memcpy(inCompleteAQBuffer->mAudioData, THIS->mMyAudioBuffer + (THIS->mMyPlayBufferPosition / sizeof(float)), numBytesToCopy);
Вам также необходимо установить размер буфера:
inCompleteAQBuffer->mAudioDataByteSize = numBytesToCopy;
numBytesToCopy
всегда будет одинаковым, , если только у вас просто не хватит данных.Например, если ваш буфер содержит 2 секунды аудиоданных и у вас есть 9 секунд для воспроизведения, то для первых четырех обратных вызовов вы пропустите значение 2 секунды.Для окончательного обратного вызова у вас останется только 1 секунда данных.numBytesToCopy
должно отражать это.
// Calculate how many bytes are remaining? It could be less than a normal buffer
// size. For example, if the buffer size is 0.5 seconds and recording stopped
// halfway through that. In which case, we copy across only the recorded bytes
// and we don't enqueue any more buffers.
SInt64 numRemainingBytes = THIS->mPlayBufferEndPosition - THIS->mPlayBufferPosition;
SInt64 numBytesToCopy = numRemainingBytes < THIS->mBufferByteSize ? numRemainingBytes : THIS->mBufferByteSize;
Наконец, мы продвигаем головку воспроизведения.В нашем обратном вызове мы дали очереди некоторые данные для воспроизведения.Что произойдет в следующий раз, когда мы получим обратный звонок?Мы не хотим снова предоставлять те же данные.Нет, если вы не делаете какой-то забавный диджейский цикл!
Таким образом, мы продвигаем голову, которая в основном является просто указателем на наш аудио-буфер.Указатель перемещается через буфер, как стрелка на записи:
SELF->mPlayBufferPosition += numBytesToCopy;
Вот и все!Есть и другая логика, но вы можете получить ее, изучив полный метод обратного вызова в SpeakHere.
Пара моментов, которые я должен подчеркнуть.Во-первых, не просто скопируйте и вставьте мой код выше.Абсолютно убедитесь, что вы понимаете, что вы делаете.Несомненно, вы столкнетесь с проблемами, и вам нужно будет понять, что происходит.
Во-вторых, убедитесь, что аудиоформаты одинаковы, и еще лучше, чтобы вы понимали аудиоформат.Это описано в Руководстве по программированию Audio Queue Services в разделе Запись аудио .Посмотрите на Перечисление 2-8. Указание формата аудиоданных аудио-очереди .
Важно понимать, что у вас есть самая примитивная единица данных, целое число или число с плавающей точкой.Моно или стерео у вас есть один или два канала в кадре.Это определяет, сколько целых чисел или чисел с плавающей точкой в этом кадре.Тогда у вас есть кадры на пакет (вероятно, 1).Ваша частота дискретизации определяет, сколько из этих пакетов у вас в секунду.
Все это описано в документации.Просто убедитесь, что все совпадает, иначе у вас будут довольно странные звуки!
Удачи!