Вам понадобится:
- Хорошее понимание формата аудио линейного PCM (см. Страница Linear PCM в Википедии ).
- Хорошее понимание образца аудио-rate и некоторые базовые математические вычисления для преобразования ваших таймингов в смещения выборки.
- Осознание того, как двоичные числа, дополняющие два * (знаковые / беззнаковые, 16-битные, 32-битные и т. д..) хранятся на компьютерах, и как порядковый номер процессора влияет на это.
- Терпение, интерес к обучению и сильное желание заставить это работать.
Вот что нужно сделать:
Включить общий доступ к файлам в вашем приложении (UIFileSharingEnabled=YES
в info.plist и записывать файлы в каталог /Documents
).
Рендеринг используемых звуков в буферы памяти, содержащие линейные аудиоданные PCM (если они еще не сделаны, т.е. если они сжаты).Вы можете сделать это с помощью функции офлайн-рендеринга аудио-очередей (см. Документы Apple аудио-очереди ).Это сделает вещи на много проще, если вы отрендерите их все в один и тот же формат PCM и частоту дискретизации (например, 16-битные сэмплы со знаком при 44,100 Гц, я буду использовать этот формат для всех примеров), ииспользуйте тот же формат для вашего вывода.Я рекомендую начать с формата Mono, а затем добавить стерео, как только оно заработает.
Выберите выходной формат без сжатия и микшируйте свои звуки в один поток:
3.1,Выделите достаточно большой буфер или откройте поток файлов для записи.
3.2.Запишите любые заголовки (например, если вы используете вывод формата WAV вместо необработанного PCM) и запишите нули (или среднюю точку диапазона семплов, если не используется формат семплов со знаком) для любого начального молчания до того, как начнется ваш первый звук.Например, если вы хотите молчать 0,1 секунды перед вашим первым звуком, запишите 4410 (0,1 * 44100) нулевых сэмплов, т.е. запишите 4410 шорт (16-битных), все с нулями.
3.3.Теперь отслеживайте все «воспроизводимые в данный момент» звуки и смешивайте их вместе.Начните с пустого списка «воспроизводимых в данный момент звуков» и следите за «текущим временем» сэмплирования, который вы микшируете, для каждого записываемого сэмпла увеличивайте «текущее время» на 1.0/sample_rate
.Когда у него будет время для запуска другого звука, добавьте его в список «воспроизводимых в данный момент» с смещением сэмпла 0. Теперь, чтобы выполнить микширование, вы перебираете все «воспроизводимые в данный момент» звуки и складываете их текущий сэмпл,затем увеличьте смещение выборки для каждого из них.Запишите суммированное значение в выходной буфер.Например, если soundA начинается с 0,1 секунды (после паузы), а soundB начинается с 0,2 секунды, вы будете делать эквивалент output[8820] = soundA[4410] + soundB[0];
для сэмпла 8820 и затем output[8821] = soundA[4411] + soundB[1];
для сэмпла 8821 и т. Д. По окончании звука (выдоберитесь до конца сэмплов) просто удалите его из списка «воспроизводящихся в данный момент» и продолжайте до конца ваших аудиоданных.
3.4.Простое смешивание (сумма образцов), описанное выше, имеет некоторые проблемы.Например, если две выборки имеют значения, которые в сумме составляют число больше 32767, это не может быть сохранено в 16-разрядном знаке со знаком, это называется отсечением.А пока просто зафиксируйте значение на 32767 и включите его ... позже вернитесь и установите простой ограничитель (см. Описание в конце).
Теперь, когда у вассмешанной версии вашего трека в несжатом линейном формате PCM, этого может быть достаточно, поэтому запишите ее в /Documents
.Если вы хотите записать его в сжатом формате, вам необходимо получить источник для аудиокодера и запустить через него линейный выход PCM.
Простой ограничитель:
Давайте решим ограничить верхние 10% диапазона выборки, поэтому, если абсолютное значение больше 29490 (int limitBegin = (int)(32767 * 0.9f);
), мы уменьшим значение.Максимально возможный пик был бы int maxSampleValue = 32767 * numPlayingSounds;
, и мы хотим масштабировать значения выше limitBegin
до пика 32767. Поэтому сделайте суммирование в sampleValue
в соответствии с очень простым микшером, описанным выше, затем:
if(sampleValue > limitBegin)
{
float overLimit = (sampleValue - limitBegin) / (float)(maxSampleValue - limitBegin);
sampleValue = limitBegin + (int)(overLimit * (32767 - limitBegin));
}
Если вы обратите внимание, вы заметите, что при изменении numPlayingSounds
(например, когда начинается новый звук), ограничитель становится более (или менее) резким, и это может привести к резким изменениям громкости (в пределах ограниченный диапазон) для размещения дополнительного звука. Вместо этого вы можете использовать максимальное количество воспроизводимых звуков или придумать какой-нибудь умный способ увеличить лимитер за несколько миллисекунд.
Помните, что это работает с абсолютным значением sampleValue
(которое может быть отрицательным в форматах со знаком), поэтому код здесь просто для демонстрации идеи. Вам нужно будет написать это правильно, чтобы справиться с ограничением на обоих концах (пик и впадина) вашего диапазона выборки. Кроме того, есть некоторые приемы, которые вы можете сделать, чтобы оптимизировать все вышеперечисленное во время микширования - вы, вероятно, заметите их во время написания микшера, будьте осторожны и сначала запустите его, а затем вернитесь и выполните рефакторинг / оптимизацию, если необходимо.
Также не забывайте учитывать порядковый номер используемой вами платформы и формат файла, в который вы записываете, так как вам может потребоваться выполнить некоторую замену байтов.