При воспроизведении аудиопотока с помощью аудиоустройства слышен потрескивающий звук

В приложении iOS

Воспроизведение аудиопотока из BLE с помощью TPCircularBuffer и Audio Unit.

Звук воспроизводится хорошо, но когда буфер пуст и нет байтов для воспроизведения, это, например, вызывает потрескивание.

Вот моя конфигурация аудиопотока,

  AudioStreamBasicDescription audioFormat;
        audioFormat.mSampleRate         = 8000.00;
        audioFormat.mFormatID           = kAudioFormatLinearPCM;
        audioFormat.mFormatFlags        = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsPacked;
        // || kAudioFormatFlagsNativeFloatPacked || kAudioFormatFlagIsSignedInteger
        //kAudioFormatFlagIsSignedInteger
        audioFormat.mFramesPerPacket    = 1;
        audioFormat.mChannelsPerFrame   = 1;
        audioFormat.mBitsPerChannel     = 32;// Update when required
        audioFormat.mBytesPerPacket     = 4; // Update when required
        audioFormat.mBytesPerFrame      = 4; // Update when required
    

Ниже представлена ​​функция воспроизведения аудиоустройства.

static OSStatus playbackCallback(void *inRefCon, 
                                 AudioUnitRenderActionFlags *ioActionFlags, 
                             const AudioTimeStamp *inTimeStamp, 
                             UInt32 inBusNumber, 
                             UInt32 inNumberFrames, 
                             AudioBufferList *ioData) {

//1
for (int i=0; i < ioData->mNumberBuffers; i++) { 

    IosAudioController *THIS = (__bridge IosAudioController *)inRefCon;

    int bytesAskingByPlayback = ioData->mBuffers[i].mDataByteSize;

    SInt16 *targetBuffer = (SInt16*)ioData->mBuffers[i].mData;

    // Pull audio from playthrough buffer
    int32_t availableBytesFromBuffer;

    SInt16 *sBuffer = TPCircularBufferTail(iosAudio.addressOfTPBuffer, &availableBytesFromBuffer);
    
    int willRemainBytes = availableBytesFromBuffer - bytesAskingByPlayback;

    if (willRemainBytes > 0) {
        memcpy(targetBuffer, sBuffer, bytesAskingByPlayback);
        TPCircularBufferConsume(iosAudio.addressOfTPBuffer,bytesAskingByPlayback);
        } else {


        //Note: Mostly need to update code here

                memcpy(targetBuffer, sBuffer, availableBytesFromBuffer);

            TPCircularBufferConsume(iosAudio.addressOfTPBuffer, availableBytesFromBuffer);

    }
}
    return noErr;
}

Размер буфера 16384.

В каком-то решении говорилось, что я заполню целевой буфер нулями, чтобы заставить замолчать, но это не работает.

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

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
0
54
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

но когда буфер пуст и нет байтов для воспроизведения.

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

Каждый раз, когда вы вводите постоянное значение, будь то ноль или последнее полученное значение, вы будете вводить высокочастотный шум. Это будет звучать как небольшой хлопок. Если делать это часто, то он будет хрустеть. Существуют методы сглаживания этой проблемы, но они довольно сложны, и вам не следует обращаться к ним, прежде чем устранять основную проблему недозапуска.

Если ваш восходящий поток имеет переменную задержку, вам нужно будет немного буферизовать (10 мс, 50 ​​мс, 2000 мс, это зависит от того, насколько он переменный), прежде чем начать нисходящий поток. Если ваши буферы исчерпаны, вам придется приостановить работу нисходящего потока, пока вы не сможете снова создать буфер.

Иногда стоит время от времени «всплывать», чтобы не приостанавливать нисходящий поток, и именно тогда приходят советы типа «заполнить нулями» или «заполнить последним значением». Но если вы получаете всплывающие сообщения много раз в секунду (и это то, что обычно это «треск»), это, скорее всего, означает, что вы недостаточно буферизуете перед началом нисходящего потока.

В зависимости от характера вашего аудио вам также необходимо учитывать случаи, когда в восходящем потоке происходит длительная пауза, а затем вы внезапно получаете много прошлых данных. На этом этапе вы должны решить, оставить ли его и увеличить задержку или отказаться от него, чтобы приблизиться к «реальному времени». Стратегии для этого полностью зависят от вашего варианта использования и являются важной частью проектирования любой системы реального времени.

Обратите внимание, что TPCircularBuffer и Audio Unit являются инструментами довольно низкого уровня и возлагают на вас большую часть работы. Лично я предпочитаю создавать подобные системы с помощью инструмента более высокого уровня, такого как AVSampleBuffer. Это по-прежнему сложно, и вам необходимо разбираться в системах реального времени, но AVFoundation сделает за вас гораздо больше работы. (Лично мне часто приходится беспокоиться о таких вещах, как пауза, перемотка назад, пропуск и тому подобное, поэтому моя проблема может существенно отличаться от вашей, поэтому этот совет неприменим.)

Другие вопросы по теме