Какой наименьший аудиобуфер необходим для создания тонального звука без искажений с помощью 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) ;
}