Использование кругового буфера позволяет асинхронно обрабатывать входные и выходные данные из своего источника. Процесс визуализации аудио происходит в потоке с высоким приоритетом. Он запрашивает образцы аудио из вашего приложения (воспроизведение) и предлагает аудио (запись/обработка) по таймеру в виде обратных вызовов.
Типичным сценарием было бы, чтобы звуковой обратный вызов срабатывал каждые 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 - хорошая статья, объясняющая аккуратный трюк памяти для реализации циклического буфера.
Круговой буфер позволяет избежать накладных расходов, связанных с распределением и освобождением пространства буферов - это особенно важно в приложении, таком как приложение VoIP, где данные будут оставаться в буфере в течение очень короткого времени, и их будет много. Постоянное распределение и освобождение памяти также может привести к фрагментации кучи, которая также может вызвать проблемы с производительностью. – Paulw11