Это не является частью спецификации JMS; у некоторых поставщиков есть механизм доставки в очередь недоставленных сообщений после некоторого количества (настраиваемых) попыток.
Последние брокеры предоставляют счетчик доставки в заголовке JMSXDeliveryCount
, чтобы вы могли отказаться от него, когда счет достигнет некоторого числа.
Если вы используете CLIENT_ACKNOWLEDGE и не делаете этого, он вообще не будет доставлен (если вы не закроете потребителя / соединение).
Я решил использовать RemoteIO AudioUnit и фоновый поток, который заполняет свинг-буферы (один буфер для чтения, другой для записи, а затем подкачки) с помощью API AudioFileServices. Затем буферы обрабатываются и смешиваются в потоке AudioUnit. Поток AudioUnit сигнализирует потоку bgnd, когда он должен начать загрузку следующего буфера обмена. Вся обработка была в C и использовала API posix thread. Весь пользовательский интерфейс был в ObjC.
IMO, подход AudioUnit / AudioFileServices обеспечивает наибольшую степень гибкости и контроля.
Приветствия,
Бен
У вас было несколько хороших ответов, но я подумал, что предложу код для решения, которое сработало для меня. Когда я начал исследовать это, я фактически искал, как работают циклы выполнения в играх, и нашел хорошее решение, которое было очень эффективным для меня, используя 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];
Весь код, который вы видите здесь, является упрощением проекта, который я с открытым исходным кодом, вы можете посмотреть и запустить его сами здесь , если это поможет. Приветствия.
Еще одна вещь, которая может улучшить отзывчивость в реальном времени, - это установить kAudioSessionProperty_PreferredHardwareIOBufferDuration аудиосеанса в несколько миллисекунд (например, 0,005 секунды), прежде чем активировать аудиосеанс. Это заставит RemoteIO чаще запрашивать более короткие буферы обратного вызова (в потоке в реальном времени). Не тратьте много времени на эти обратные вызовы аудио в реальном времени, иначе вы убьете аудиопоток и все аудио для своего приложения.
Просто считать более короткие буферы обратного вызова RemoteIO примерно в 10 раз точнее и с меньшей задержкой, чем при использовании NSTimer. А подсчет сэмплов в буфере звукового обратного вызова для позиционирования начала вашего звукового микса даст вам относительную синхронизацию менее миллисекунды.
Я подумал, что лучшим подходом к управлению временем было бы установить настройку ударов в минуту (например, 120), и вместо этого отключить ее. Измерения минут и секунд практически бесполезны при написании / создании музыки / музыкальных приложений.
Если вы посмотрите на какое-либо приложение для секвенирования, все они будут биться, а не время. С другой стороны, если вы посмотрите на редактор сигналов, он использует минуты и секунды.
Я не уверен в наилучшем способе реализации этого кода любым способом, но я думаю, что этот подход избавит вас от многих головных болей в будущем.
NSTimer не дает абсолютно никаких гарантий относительно того, когда он срабатывает. Он планирует время зажигания в цикле выполнения, и когда цикл выполнения переходит к таймерам, он видит, просрочены ли какие-либо из таймеров. Если да, то запускает их селекторы. Отлично подходит для самых разных задач; бесполезно для этого.
Шаг первый заключается в том, что вам нужно переместить обработку звука в отдельный поток и выйти из потока пользовательского интерфейса. Что касается времени, вы можете создать свой собственный механизм синхронизации, используя обычные подходы C, но я бы начал с рассмотрения CAAnimation и особенно CAMediaTiming.
Имейте в виду, что в Какао есть много вещей, которые предназначены только для работы на основной нить. Не выполняйте, например, работу пользовательского интерфейса в фоновом потоке. В общем, внимательно прочтите документацию, чтобы узнать, что они говорят о безопасности потоков. Но, как правило, если нет Так как много связи между потоками (чего не должно быть в большинстве случаев IMO), потоки в Какао довольно просты. Посмотрите на NSThread.
Я делаю нечто подобное, используя вывод remoteIO. я не полагаюсь на NSTimer. Я использую временную метку, предоставленную в обратном вызове рендеринга, для расчета всего моего времени. Я не знаю, насколько точна частота Гц у iphone, но я уверен, что она довольно близка к 44100 Гц, поэтому я просто вычисляю, когда мне следует загружать следующий бит, основываясь на текущем номере выборки.
пример проекта, который использует удаленный io можно найти здесь , посмотрите на аргумент inTimeStamp обратного вызова рендеринга.
РЕДАКТИРОВАТЬ: Пример работы этого подхода (и в магазине приложений можно найти здесь )