Как программировать точный аудио секвенсер в реальном времени на iPhone?

Это не является частью спецификации JMS; у некоторых поставщиков есть механизм доставки в очередь недоставленных сообщений после некоторого количества (настраиваемых) попыток.

Последние брокеры предоставляют счетчик доставки в заголовке JMSXDeliveryCount, чтобы вы могли отказаться от него, когда счет достигнет некоторого числа.

Если вы используете CLIENT_ACKNOWLEDGE и не делаете этого, он вообще не будет доставлен (если вы не закроете потребителя / соединение).

23
задан Kev 26 September 2011 в 21:54
поделиться

6 ответов

Я решил использовать RemoteIO AudioUnit и фоновый поток, который заполняет свинг-буферы (один буфер для чтения, другой для записи, а затем подкачки) с помощью API AudioFileServices. Затем буферы обрабатываются и смешиваются в потоке AudioUnit. Поток AudioUnit сигнализирует потоку bgnd, когда он должен начать загрузку следующего буфера обмена. Вся обработка была в C и использовала API posix thread. Весь пользовательский интерфейс был в ObjC.

IMO, подход AudioUnit / AudioFileServices обеспечивает наибольшую степень гибкости и контроля.

Приветствия,

Бен

4
ответ дан 29 November 2019 в 02:44
поделиться

У вас было несколько хороших ответов, но я подумал, что предложу код для решения, которое сработало для меня. Когда я начал исследовать это, я фактически искал, как работают циклы выполнения в играх, и нашел хорошее решение, которое было очень эффективным для меня, используя mach_absolute_time.

Вы можете прочитать немного о том, что он делает здесь , но суть в том, что он возвращает время с точностью до наносекунды. Однако возвращаемое число не совсем время, оно зависит от того, какой у вас процессор, поэтому сначала нужно создать структуру mach_timebase_info_data_t, а затем использовать ее для нормализации времени.

// Gives a numerator and denominator that you can apply to mach_absolute_time to
// get the actual nanoseconds
mach_timebase_info_data_t info;
mach_timebase_info(&info);

uint64_t currentTime = mach_absolute_time();

currentTime *= info.numer;
currentTime /= info.denom;

И если бы мы хотели, чтобы он отмечал каждую 16-ю ноту, вы могли бы сделать что-то вроде этого:

uint64_t interval = (1000 * 1000 * 1000) / 16;
uint64_t nextTime = currentTime + interval;

На этом этапе currentTime будет содержать некоторое количество наносекунд, и вы ' Я хочу, чтобы он отмечался каждый раз, когда прошло interval наносекунд, которые мы храним в nextTime. Затем вы можете настроить цикл while, что-то вроде этого:

while (_running) {
    if (currentTime >= nextTime) {
        // Do some work, play the sound files or whatever you like
        nextTime += interval;
    }

    currentTime = mach_absolute_time();
    currentTime *= info.numer;
    currentTime /= info.denom;
}

Материал mach_timebase_info немного сбивает с толку, но как только вы туда его включите, он работает очень хорошо. Это было чрезвычайно эффективно для моих приложений. Также стоит отметить, что вы не захотите запускать это в главном потоке, поэтому целесообразно передать его в свой собственный поток. Вы можете поместить весь приведенный выше код в его собственный метод, называемый run, и запустить его с чем-то вроде:

[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];

Весь код, который вы видите здесь, является упрощением проекта, который я с открытым исходным кодом, вы можете посмотреть и запустить его сами здесь , если это поможет. Приветствия.

4
ответ дан 29 November 2019 в 02:44
поделиться

Еще одна вещь, которая может улучшить отзывчивость в реальном времени, - это установить kAudioSessionProperty_PreferredHardwareIOBufferDuration аудиосеанса в несколько миллисекунд (например, 0,005 секунды), прежде чем активировать аудиосеанс. Это заставит RemoteIO чаще запрашивать более короткие буферы обратного вызова (в потоке в реальном времени). Не тратьте много времени на эти обратные вызовы аудио в реальном времени, иначе вы убьете аудиопоток и все аудио для своего приложения.

Просто считать более короткие буферы обратного вызова RemoteIO примерно в 10 раз точнее и с меньшей задержкой, чем при использовании NSTimer. А подсчет сэмплов в буфере звукового обратного вызова для позиционирования начала вашего звукового микса даст вам относительную синхронизацию менее миллисекунды.

1
ответ дан 29 November 2019 в 02:44
поделиться

Я подумал, что лучшим подходом к управлению временем было бы установить настройку ударов в минуту (например, 120), и вместо этого отключить ее. Измерения минут и секунд практически бесполезны при написании / создании музыки / музыкальных приложений.

Если вы посмотрите на какое-либо приложение для секвенирования, все они будут биться, а не время. С другой стороны, если вы посмотрите на редактор сигналов, он использует минуты и секунды.

Я не уверен в наилучшем способе реализации этого кода любым способом, но я думаю, что этот подход избавит вас от многих головных болей в будущем.

0
ответ дан 29 November 2019 в 02:44
поделиться

NSTimer не дает абсолютно никаких гарантий относительно того, когда он срабатывает. Он планирует время зажигания в цикле выполнения, и когда цикл выполнения переходит к таймерам, он видит, просрочены ли какие-либо из таймеров. Если да, то запускает их селекторы. Отлично подходит для самых разных задач; бесполезно для этого.

Шаг первый заключается в том, что вам нужно переместить обработку звука в отдельный поток и выйти из потока пользовательского интерфейса. Что касается времени, вы можете создать свой собственный механизм синхронизации, используя обычные подходы C, но я бы начал с рассмотрения CAAnimation и особенно CAMediaTiming.

Имейте в виду, что в Какао есть много вещей, которые предназначены только для работы на основной нить. Не выполняйте, например, работу пользовательского интерфейса в фоновом потоке. В общем, внимательно прочтите документацию, чтобы узнать, что они говорят о безопасности потоков. Но, как правило, если нет Так как много связи между потоками (чего не должно быть в большинстве случаев IMO), потоки в Какао довольно просты. Посмотрите на NSThread.

9
ответ дан 29 November 2019 в 02:44
поделиться

Я делаю нечто подобное, используя вывод remoteIO. я не полагаюсь на NSTimer. Я использую временную метку, предоставленную в обратном вызове рендеринга, для расчета всего моего времени. Я не знаю, насколько точна частота Гц у iphone, но я уверен, что она довольно близка к 44100 Гц, поэтому я просто вычисляю, когда мне следует загружать следующий бит, основываясь на текущем номере выборки.

пример проекта, который использует удаленный io можно найти здесь , посмотрите на аргумент inTimeStamp обратного вызова рендеринга.

РЕДАКТИРОВАТЬ: Пример работы этого подхода (и в магазине приложений можно найти здесь )

6
ответ дан 29 November 2019 в 02:44
поделиться
Другие вопросы по тегам:

Похожие вопросы: