Какой наименьший аудиобуфер необходим для создания тонального звука без искажений с помощью WaveOUT API

Есть ли в WaveOut API какое-то внутреннее ограничение на размер текущего воспроизводимого фрагмента буфера? Я имею в виду, что если я предоставлю очень маленький буфер, это как-то повлияет на звук, воспроизводимый динамиками. Я испытываю очень странный шум, когда генерирую и играю синусоидальную волну с небольшим буфером. Что-то вроде пика или "БАМП".

Полная история:

Я сделал программу, которая может генерировать звуковой сигнал Sinus в режиме реального времени. Переменные параметры: частота и громкость. Требование проекта должно было иметь максимальную задержку 50 мс. Таким образом, программа должна быть способна выдавать синусоидальные сигналы с настраиваемой вручную частотой звукового сигнала в реальном времени.

Я использовал Windows WaveOut API, C# и P / invoke для доступа к API.

Все отлично работает, когда размер звукового буфера составляет 1000 мс. Если я минимизирую буфер до 50 мс согласно требованию к задержке, то для определенных частот, которые я испытываю в конце каждого буфера, возникает шум или "BUMP". Я не понимаю, является ли сгенерированный звук искаженным (я проверял и нет) или что-то происходит с аудио-чипом, или некоторая задержка при инициализации и воспроизведении.

Когда я сохраняю произведенный звук в файл.wav, все идеально.

Это означает, что в моем коде должна быть какая-то ошибка, или аудиоподсистема имеет ограничение на отправленные ему фрагменты буфера.

Для тех, кто не знает, WaveOut должен быть инициализирован в первый раз, а затем должен быть подготовлен с заголовками аудио для каждого буфера, которые содержат количество байтов, которые должны быть воспроизведены, и указатель на память, которая содержит аудио, которое необходимо быть игроком

ОБНОВИТЬ

Шум возникает в следующих комбинациях 44100 SamplingRate, 16 бит, 2 канала, 50 мс буфера и сгенерированный синусоидальный аудиосигнал 201 Гц, 202 Гц, 203 Гц, 204 Гц, 205 Гц... 219 Гц, 220 Гц, 240 Гц, все в порядке

Почему эта разница 20, я не знаю.

2 ответа

Решение

Есть несколько вещей, о которых нужно помнить, когда вам нужно выводить звук плавно:

  • waveOutXxxx API - это унаследованный / совместимый уровень поверх низкоуровневого API, поэтому он имеет большие накладные расходы и не рекомендуется, когда вы хотите достичь минимальной задержки. Обратите внимание, что это вряд ли будет вашей основной проблемой, но это часть общих знаний, полезных для понимания
  • поскольку Windows не является операционной системой реального времени, а ее аудиоподсистема не является реальной в реальном времени, либо вы не контролируете случайную задержку между входящими в очередь аудиоданными для вывода и данными, которые действительно воспроизводятся, ключом является сохранение определенного уровня заполнения буфера. который защищает вас от недоразвития воспроизведения и обеспечивает плавное воспроизведение
  • с waveOutXxxx Вы не ограничены одним буфером, вы можете выделить несколько буферов многократного использования и перерабатывать их

В целом API-интерфейсы waveOutXxxx, DirectSound, DirectShow хорошо работают с задержками 50 мс и более. С потоками эксклюзивного режима WASAPI вы можете получить задержки 5 мс и даже меньше.

РЕДАКТИРОВАТЬ: Я, кажется, сказал слишком рано около 20 мс задержки. Чтобы компенсировать это, вот простой инструмент LowLatencyWaveOutPlay ( Win32, x64) для оценки задержки, которую вы можете достичь. При достаточной буферизации воспроизведение происходит плавно, иначе вы услышите заикание.

Насколько я понимаю, буферы могут быть возвращены с опозданием, и оптимальная схема с точки зрения наименьшей задержки лежит в основе наличия более мелких буферов, чтобы вы возвращали их как можно раньше. Например, 10 буферов 3 мс / буфер, а не 3 буфера 10 мс / буфер.

D:\>LowLatencyWaveOutPlay.exe 48000 10 3
Format: 48000 Hz, 1 channels, 16 bits per sample
Buffer Count: 10
Buffer Length: 3 ms (288 bytes)
Signal Frequency: 1000 Hz
^C

Итак, я пришел сюда, потому что хотел также найти базовую задержку waveoutwrite(). Я получил около 25-26 мс задержки, прежде чем я добрался до гладкого синусоидального тона.

Это для:

Четырехъядерный процессор AMD Phenom(tm) 9850 2,51 ГГц 4,00 ГБ ОЗУ 64-разрядная операционная система, процессор на базе x64Windows 10 Enterprise N

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

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

      Sine wave output program
Channels:         2
Sample rate:      44100
Bytes per second: 176400
Block align:      4
Bits per sample:  16
Time per buffer:  0.025850
Total time prepare header:   87.5000000000 usec
Total time to fill:          327.9000000000 usec
Total time for waveOutWrite: 90.8000000000 usec

Программа:

      /*******************************************************************************

WaveOut example program

Based on C. Petzold's sine wave example, outputs a sine wave via the waveOut
API in Win32.

*******************************************************************************/

#include <stdio.h>
#include <windows.h>
#include <math.h>
#include <limits.h>
#include <unistd.h>

#define SAMPLE_RATE      44100
#define FREQ_INIT        440
#define OUT_BUFFER_SIZE  570*4
#define PI               3.14159
#define CHANNELS         2
#define BITS             16
#define MAXTIM           1000000000

double        fAngle;
LARGE_INTEGER perffreq;
PWAVEHDR      pWaveHdr1, pWaveHdr2;
int           iFreq = FREQ_INIT;

VOID FillBuffer (short* pBuffer, int iFreq)

{

     int i;
     int c;

     for (i = 0 ; i < OUT_BUFFER_SIZE ; i += CHANNELS) {

          for (c = 0; c < CHANNELS; c++)
            pBuffer[i+c] = (short)(SHRT_MAX*sin (fAngle));
          fAngle += 2*PI*iFreq/SAMPLE_RATE;
          if (fAngle > 2 * PI) fAngle -= 2*PI;

     }

}

double elapsed(LARGE_INTEGER t)

{

    LARGE_INTEGER rt;
    long tt;

    QueryPerformanceCounter(&rt);
    tt = rt.QuadPart-t.QuadPart;

    return (tt*(1.0/(double)perffreq.QuadPart));

}

void CALLBACK waveOutProc(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)

{

    if (uMsg == WOM_DONE) {

        if (pWaveHdr1->dwFlags & WHDR_DONE) {

            FillBuffer((short*)pWaveHdr1->lpData, iFreq);
            waveOutWrite(hwo, pWaveHdr1, sizeof(WAVEHDR));

        }
        if (pWaveHdr2->dwFlags & WHDR_DONE) {

            FillBuffer((short*)pWaveHdr2->lpData, iFreq);
            waveOutWrite(hwo, pWaveHdr2, sizeof(WAVEHDR));

        }

    }

}

int main()

{

    HWAVEOUT     hWaveOut ;

    short*       pBuffer1;
    short*       pBuffer2;
    short*       pBuffer3;

    WAVEFORMATEX waveformat;
    UINT         wReturn;
    int          bytes;
    long         t;
    LARGE_INTEGER rt;
    double       timprep;
    double       filtim;
    double       waveouttim;

    printf("Sine wave output program\n");

    fAngle = 0; /* start sine angle */

    QueryPerformanceFrequency(&perffreq);

    pWaveHdr1 = malloc (sizeof (WAVEHDR));
    pWaveHdr2 = malloc (sizeof (WAVEHDR));
    pBuffer1  = malloc (OUT_BUFFER_SIZE*sizeof(short));
    pBuffer2  = malloc (OUT_BUFFER_SIZE*sizeof(short));
    pBuffer3  = malloc (OUT_BUFFER_SIZE*sizeof(short));

    if (!pWaveHdr1 || !pWaveHdr2 || !pBuffer1 || !pBuffer2) {

        if (!pWaveHdr1) free (pWaveHdr1) ;
        if (!pWaveHdr2) free (pWaveHdr2) ;
        if (!pBuffer1)  free (pBuffer1) ;
        if (!pBuffer2)  free (pBuffer2) ;

        fprintf(stderr, "*** Error: No memory\n");
        exit(1);

    }

    // Load prime parameters to format
    waveformat.wFormatTag      = WAVE_FORMAT_PCM;
    waveformat.nChannels       = CHANNELS;
    waveformat.nSamplesPerSec  = SAMPLE_RATE;
    waveformat.wBitsPerSample  = BITS;
    waveformat.cbSize          = 0;

    // Calculate other parameters
    bytes = waveformat.wBitsPerSample/8; /* find bytes per sample */
    if (waveformat.wBitsPerSample&8) bytes++; /* round  up */
    bytes *= waveformat.nChannels; /* find total channels size */
    waveformat.nBlockAlign = bytes; /* set block align */
    /* find average bytes/sec */
    waveformat.nAvgBytesPerSec = bytes*waveformat.nSamplesPerSec;

    printf("Channels:         %d\n", waveformat.nChannels);
    printf("Sample rate:      %d\n", waveformat.nSamplesPerSec);
    printf("Bytes per second: %d\n", waveformat.nAvgBytesPerSec);
    printf("Block align:      %d\n", waveformat.nBlockAlign);
    printf("Bits per sample:  %d\n", waveformat.wBitsPerSample);
    printf("Time per buffer:  %f\n",
        OUT_BUFFER_SIZE*sizeof(short)/(double)waveformat.nAvgBytesPerSec);

    if (waveOutOpen (&hWaveOut, WAVE_MAPPER, &waveformat, (DWORD_PTR)waveOutProc, 0, CALLBACK_FUNCTION)
        != MMSYSERR_NOERROR) {

        free (pWaveHdr1) ;
        free (pWaveHdr2) ;
        free (pBuffer1) ;
        free (pBuffer2) ;

        hWaveOut = NULL ;
        fprintf(stderr, "*** Error: No memory\n");
        exit(1);

    }

    // Set up headers and prepare them

    pWaveHdr1->lpData          = (LPSTR)pBuffer1;
    pWaveHdr1->dwBufferLength  = OUT_BUFFER_SIZE*sizeof(short);
    pWaveHdr1->dwBytesRecorded = 0;
    pWaveHdr1->dwUser          = 0;
    pWaveHdr1->dwFlags         = WHDR_DONE;
    pWaveHdr1->dwLoops         = 1;
    pWaveHdr1->lpNext          = NULL;
    pWaveHdr1->reserved        = 0;

QueryPerformanceCounter(&rt);
    waveOutPrepareHeader(hWaveOut, pWaveHdr1, sizeof (WAVEHDR));
timprep = elapsed(rt);

    pWaveHdr2->lpData          = (LPSTR)pBuffer2;
    pWaveHdr2->dwBufferLength  = OUT_BUFFER_SIZE*sizeof(short);
    pWaveHdr2->dwBytesRecorded = 0;
    pWaveHdr2->dwUser          = 0;
    pWaveHdr2->dwFlags         = WHDR_DONE;
    pWaveHdr2->dwLoops         = 1;
    pWaveHdr2->lpNext          = NULL;
    pWaveHdr2->reserved        = 0;

    waveOutPrepareHeader(hWaveOut, pWaveHdr2, sizeof (WAVEHDR));

    // Send two buffers to waveform output device

QueryPerformanceCounter(&rt);
    FillBuffer (pBuffer1, iFreq);
filtim = elapsed(rt);

QueryPerformanceCounter(&rt);
    waveOutWrite (hWaveOut, pWaveHdr1, sizeof (WAVEHDR));
waveouttim = elapsed(rt);

    FillBuffer (pBuffer2, iFreq);
    waveOutWrite (hWaveOut, pWaveHdr2, sizeof (WAVEHDR));

    // Run waveform loop
    sleep(10);

printf("Total time prepare header:   %.10f usec\n", timprep*1000000);
printf("Total time to fill:          %.10f usec\n", filtim*1000000);
printf("Total time for waveOutWrite: %.10f usec\n", waveouttim*1000000);

    waveOutUnprepareHeader(hWaveOut, pWaveHdr1, sizeof (WAVEHDR));
    waveOutUnprepareHeader(hWaveOut, pWaveHdr2, sizeof (WAVEHDR));
    // Close waveform file
    free (pWaveHdr1) ;
    free (pWaveHdr2) ;
    free (pBuffer1) ;
    free (pBuffer2) ;

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