2015-06-07 1 views
4

Мой вопрос почти сам пояснительный. Извините, если он кажется слишком тупым.В чем причина использования кругового буфера в iOS Audio Calling APP?

Я пишу VoIP-дозвон iOS и проверил некоторый открытый код (приложение для вызова звука iOS). И почти все из них используют Circular Buffer для хранения записанных и полученных аудио данных PCM. Так что мне интересно, зачем в этом случае использовать круговой буфер. Какова конкретная причина использования такого звукового буфера.

Заранее спасибо.

+0

Круговой буфер позволяет избежать накладных расходов, связанных с распределением и освобождением пространства буферов - это особенно важно в приложении, таком как приложение VoIP, где данные будут оставаться в буфере в течение очень короткого времени, и их будет много. Постоянное распределение и освобождение памяти также может привести к фрагментации кучи, которая также может вызвать проблемы с производительностью. – Paulw11

ответ

2

Хороший вопрос. Есть еще одна веская причина для использования кругового буфера.

В iOS, если вы используете обратные вызовы (аудиокомпьютер) для записи и воспроизведения звука (на самом деле вам нужно использовать его, если вы хотите создать приложение для передачи аудио в реальном времени), тогда вы получите кусок данных за определенный промежуток времени (скажем, 20 миллисекунд) от обратного вызова рекордера. И в iOS вы никогда не будете получать фиксированную длину данных всегда (если вы установите интервал обратного вызова как 20 мс, тогда вы получите 370 или 372 байта данных. И вы никогда не узнаете, когда вы получите 370 байт или 372 байта. если я ошибаюсь). Затем для передачи аудио через UDP-пакеты вам необходимо использовать кодек для кодирования и декодирования данных (G729 обычно используется для приложений VoIP). Но g729 берет данные умножителем 8. Предположим, вы кодируете 368 (8 * 46) байт за 20 мс. Итак, что вы собираетесь делать с остальными данными? Вам необходимо сохранить его по порядку для следующего процесса обработки.

И в этом причина. Есть еще кое-какие подробности, но я сделал это простым для вашего лучшего понимания. Просто напишите ниже, если у вас есть какие-либо вопросы.

12

Использование кругового буфера позволяет асинхронно обрабатывать входные и выходные данные из своего источника. Процесс визуализации аудио происходит в потоке с высоким приоритетом. Он запрашивает образцы аудио из вашего приложения (воспроизведение) и предлагает аудио (запись/обработка) по таймеру в виде обратных вызовов.

Типичным сценарием было бы, чтобы звуковой обратный вызов срабатывал каждые 0,023 секунды, чтобы запросить (и/или предложить) 1024 выборки аудио. Этот поток синхронизируется с системным оборудованием, поэтому необходимо, чтобы ваш обратный вызов возвращался до 0.023 секунд. Если вы этого не сделаете, аппаратное обеспечение не будет ждать вас, оно просто пропустит этот цикл, и у вас будет слышимая поп-музыка или тишина, или пропустите звук, который вы пытаетесь записать.

Место кругового буфера - это передача данных между потоками. В звуковом приложении, которое будет перемещать образцы в и из аудиопотока асинхронно. Один поток производит выборки на «головку» буфера, а другой поток потребляет их из «хвоста».

Вот пример, извлечение образцов звука с микрофона и их запись на диск. Ваше приложение подписалось на обратный вызов, который срабатывает каждые 0,023 секунды, предлагая 1024 отсчета для записи. Наивный подход состоял в том, чтобы просто записать аудио на диск из этого обратного вызова.

void myCallback(float *samples,int sampleCount, SampleSaver *saver){ 
    SampleSaverSaveSamples(saver,samples,sampleCount); 
} 

Это будет работать! В большинстве случаев ...

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

Правильный способ сделать это - использовать круговой буфер. Я лично использую TPCircularBuffer, потому что это потрясающе.Способ, которым он работает (извне), заключается в том, что вы запрашиваете буфер для указателя на запись данных (голова) в один поток, а затем в другой поток вы запрашиваете буфер для чтения указателя (хвоста). Вот как это будет сделано с помощью TPCircularBuffer (пропустить настройку и использовать упрощенный обратный вызов).

//this is on the high priority thread that can't wait for anything like a slow write to disk 
void myCallback(float *samples,int sampleCount, TPCircularBuffer *buffer){ 
    int32_t availableBytes = 0; 
    float *head = TPCircularBufferHead(buffer, &availableBytes); 
    memcpy(head,samples,sampleCount * sizeof(float));//copies samples to head 
    TPCircularBufferProduce(buffer,sampleCount * sizeof(float)); //moves buffer head "forward in the circle" 

} 

Эта операция очень быстрая и не оказывает дополнительного давления на эту чувствительную звуковую нить. Затем вы создаете собственный таймер для отдельного потока, чтобы записать образцы на диск.

//this is on some background thread that can take it's sweet time 
void myLeisurelySavingCallback(TPCircularBuffer *buffer, SampleSaver *saver){ 
    int32_t available; 
    float *tail = TPCircularBufferTail(buffer, &available); 
    int samplesInBuffer = available/sizeof(float); //mono 
    SampleSaverSaveSamples(saver, tail, samplesInBuffer); 
    TPCircularBufferConsume(buffer, samplesInBuffer * sizeof(float)); // moves tail forward 
} 

И там у вас есть, вы не только избежать звуковых сбоев, но если вы инициализировать достаточно большой буфер, вы можете настроить запись на диск обратного вызова только стрелять каждый второй или два (после кольцевого буфера создал хороший бит аудио), который намного проще в вашей системе, чем запись на диск каждые 0,023 секунды!

Основная причина использования буфера заключается в том, что образцы могут обрабатываться асинхронно. Они отлично подходят для передачи сообщений между потоками без блокировок. Here - хорошая статья, объясняющая аккуратный трюк памяти для реализации циклического буфера.

+0

Очень хорошее объяснение! У меня есть дополнительный вопрос: как вызвать обратный вызов, который читает хвост буфера? Мой первый подход - это цикл while (true), который, конечно, приводит к очень высокому использованию процессора и большому количеству «активных ожиданий» для новых буферов. –

+2

Я просто использую простой NSTimer. В этом примере я могу настроить таймер на каждые 0,023 секунды (хотя я лично делаю это реже), а затем вызовите функцию myLeisurelySavingCallback в этом обратном вызове. Обратите внимание, как я вычисляю sampleInBuffer в функции? Если он пуст, он просто ждет следующего вызова, если у него есть один или несколько буферов сэмплов, хранящихся в нем, он обработает их все. – dave234