Задержка (около 200 мс) при воспроизведении потокового аудио

У меня есть приложение, которое воспроизводит потоковые аудиоданные (например, клиент чата). Рабочий процесс состоит из трех простых шагов:

  1. Информация заголовка файла (частота дискретизации, количество битов на выборку и количество каналов) отправляется первой.
  2. Устройство аудиосигнала инициализируется на основе вышеуказанных параметров.
  3. Аудиоданные (pcm) отправляются и воспроизводятся на указанном выше устройстве.

Код приема данных — нативный (C-код). и он читает данные из сокета. Затем он вызывает управляемый код C#, который использует библиотеку Naudio для инициализации устройства и воспроизведения звука.

Теперь проблема в том, что я вижу некоторую задержку в воспроизведении звука. Я уже оснастил остальную часть своего кода (в частности: передачу данных в сокет и передачу их обратно в управляемый код), и это, кажется, нормально. Весь процесс передачи занимает около 600 микросекунд, но после того, как я назначаю буфер Naudio, кажется, что он начинает воспроизводить его через некоторое время (около 200-250 миллисекунд).

Вот мой класс С#, который обрабатывает часть воспроизведения звука:

class foo
{
    static  IWavePlayer     s_WaveOut;
    static  WaveFormat      s_WaveOutFormat;
    static  BufferedWaveProvider    s_WaveProvider;
    static  byte[]          s_Samples       = new byte[10000];

    // called from native code to init deivce with specified sample rate and num of channels
    private static void DeviceInit(int rate, int bits, int channels)
    {
        s_WaveOut   = new WaveOut(WaveCallbackInfo.FunctionCallback());
        s_WaveOutFormat = new WaveFormat(rate, bits, channels);
        s_WaveProvider  = new BufferedWaveProvider(s_WaveOutFormat);

        s_WaveProvider.DiscardOnBufferOverflow      = true;
        s_WaveProvider.BufferLength         = 5 * 1024 * 1024;

        s_WaveOut.Init(s_WaveProvider);
        s_WaveOut.Play();
    }

    // called from native 'C' code upon receiving audio packates
    private unsafe static void PlayDataCallback(
        IntPtr buff,
        Int32 size) 
    {
        Marshal.Copy(buff, s_Samples, 0, size);
        s_WaveProvider.AddSamples(s_Samples, 0, size);
    }
}

Кто-нибудь знает, что может быть причиной задержки, или я использую его (Naudio) каким-то неправильным образом.

Я попробовал ту же библиотеку Naudio для воспроизведения wav-файла, и, похоже, она работает идеально, проблема возникает только тогда, когда я добавляю сэмплы после инициализации устройства самостоятельно.

[обновление] Если я изменю s_WaveOut = new WaveOut(WaveCallbackInfo.FunctionCallback()); на s_WaveOut = new DirectSound();, производительность станет намного выше. Если после этого я изменю источник Naudio, чтобы установить приоритет потока воспроизведения на Highest (по умолчанию — «Нормальный»), производительность улучшится еще больше, но, как и ожидалось, процесс начнет потреблять много ресурсов.

Спасибо,

Викрам


person Vikram.exe    schedule 28.12.2011    source источник
comment
Я думаю, что буферизация в звуковой подсистеме добавляет задержку. Это может быть чисто из соображений непрерывности звука (устройство должно иметь достаточно предварительно буферизованных данных, иначе оно будет воспроизводить тишину, заметную как промежутки и хлопки).   -  person Alexey Frunze    schedule 28.12.2011
comment
@ALex, я думал о том же, но при воспроизведении видеофайла задержки нет, звук и видео синхронизированы. Также, если я играю в игру, звук для различных действий приходит мгновенно. Может быть, я упускаю какие-то настройки?   -  person Vikram.exe    schedule 28.12.2011


Ответы (2)


Я также разрабатываю приложение для потоковой передачи звука с использованием NAudio. У нас также есть проблема с задержкой. Доходит до 300 мс.

Захват происходит 10 раз в секунду (один раз в 100 мс).

Использование совета Vikram.exe по использованию DirectSoundOut вместо WaveOut немного помогло. Задержка уменьшилась на 50 или 100 мс, но только если я установил желаемую задержку на 50 мс.

new DirectSoundOut(guid, 50);

Еще один трюк позволил снизить задержку на 100 или 200 мс. Проверяем, воспроизводится ли звук, и пропускаем новые кадры, если он есть.

if (s_WaveProvider.BufferedDuration <= 100)
    s_WaveProvider.AddSamples(s_Samples, 0, size);

Еще предстоит проделать некоторую работу в отношении плавности звука, но в целом у нас сейчас нет задержки.

person Vasyl Boroviak    schedule 21.09.2012

При воспроизведении звука всегда возникает задержка. Для WaveOut можно указать количество буферов и общую желаемую задержку. Вы также можете указать размеры буфера для других моделей драйверов. Для большинства сценариев воспроизведения идеально подходят два буфера по 100 мс каждый, поскольку они достаточно отзывчивы и не будут заикаться, кроме как при экстремальной нагрузке. Тем не менее, вы можете пойти ниже, если вам нужно, но рискуете не успеть заполнить следующий буфер вовремя. Не ожидайте таких низких задержек, как вы могли бы получить в DAW (например, 5 мс) с NAudio, он не так сильно оптимизирован, а среда .NET в любом случае не особенно хорошо подходит для такого рода приложений из-за сборщика мусора.

Вот пример установки выходной задержки для WaveOut:

var waveOut = new WaveOut();
waveout.DesiredLatency = 50; // 50ms latency
person Mark Heath    schedule 30.12.2011
comment
Немного грубо сказать, что NET Framework плохо подходит для такой работы. Вполне возможно написать приложение реального времени на .NET, которое вообще не генерирует никакого мусора (после инициализации). Проблема в том, что .NET поощряет удобный, но неэффективный стиль кодирования, который противоречит приложениям, критически важным для производительности в реальном времени, и большинству программистов .NET никогда не нужно думать о низкоуровневой производительности (в отличие от кодировщиков C++, которые буквально вынуждены принять это). менталитет, но .NETters могут оставаться в блаженном неведении :). - person MattDavey; 30.12.2011
comment
@MattDavey, я пытался написать NAudio таким образом, чтобы для GC выполнялась минимальная работа, например, создание буферов заранее и их повторное использование, даже то же самое в местах с EventArgs, но если ваше приложение имеет графический интерфейс вы также можете принять тот факт, что GC сработает в какой-то момент, и когда он будет воспроизводить звук, вероятно, произойдет сбой даже при довольно скромных задержках. - person Mark Heath; 31.12.2011
comment
ааа, да, это верное замечание, особенно Winforms создает много мусора. Но не стоит винить в этом GC :) - person MattDavey; 03.01.2012
comment
waveout.DesiredLatency = 50; код работает некорректно на большинстве систем. Звук хрипит. Иногда даже 100 мс недостаточно. - person Vasyl Boroviak; 21.09.2012