1

WaveOut API 是否对当前播放的缓冲区大小有一些内部限制?我的意思是,如果我提供一个非常小的缓冲区,它是否会以某种方式影响播放到扬声器的声音。当我用小缓冲区生成和播放正弦波时,我遇到了非常奇怪的噪音。像高峰或“BUMP”之类的东西。

完整的故事:

我做了一个可以实时生成窦性声音信号的程序。可变参数是频率和音量。项目要求最大延迟为 50 毫秒。因此该程序必须能够实时产生具有手动可调音频信号频率的正弦信号。

我使用 Windows WaveOut API、C# 和 P/invoke 来访问 API。

当声音缓冲区为 1000 毫秒时,一切正常。如果我根据延迟要求将缓冲区最小化到 50 毫秒,那么对于我在每个缓冲区结束时遇到的某些频率,会出现噪音或“BUMP”。我不明白生成的声音是否格式错误(我检查过但不是)或者音频芯片发生了什么问题,或者初始化和播放有一些延迟。

当我将制作的音频保存到 .wav 文件时,一切都很完美。

这意味着我的代码中一定有一些错误,或者音频子系统对发送给它的缓冲区块有限制。

对于那些不知道 WaveOut 必须首先初始化的人,然后必须为每个缓冲区准备好包含需要播放的字节数的音频头以及指向包含需要播放的音频的内存的指针成为玩家。

更新

以下组合会产生噪音 44100 SamplingRate、16 位、2 通道、50 毫秒缓冲并生成 201Hz、202Hz、203Hz、204Hz、205Hz ... 219Hz、220Hz、240Hz 的 Sinus 音频信号,没问题

为什么相差20,我不知道。

4

2 回答 2

3

当您需要流畅地输出音频时,需要牢记以下几点:

  • waveOutXxxxAPI 是较低级别 API 之上的遗留/兼容性层,因此它具有更大的开销,当您要达到最小延迟时不建议这样做。请注意,这不太可能是您的主要问题,但这是有助于理解的一般知识
  • 因为 Windows 不是实时操作系统,它的音频子系统也不是实时的,或者您无法控制在将音频数据排队以供输出和数据真正播放之间所涉及的随机延迟,关键是要保持一定程度的缓冲区充满度它可以保护您免受播放下溢并提供流畅的播放
  • waveOutXxxx您不仅限于拥有单个缓冲区,您可以分配多个可重复使用的缓冲区并回收它们

总而言之,waveOutXxxx、DirectSound、DirectShow API 在延迟 50 毫秒及以上的情况下运行良好。使用 WASAPI 独占模式流,您可以获得 5 毫秒甚至更低的延迟

编辑:我似乎太早说了大约 20 毫秒的延迟。为了弥补这一点,这里有一个简单的工具LowLatencyWaveOutPlay ( Win32 , x64 ) 来估计您可以实现的延迟。有足够的缓冲播放是流畅的,否则你会听到卡顿。

我的理解是,缓冲区可能会延迟返回,并且在最小延迟方面的最佳设计在于拥有更多更小的缓冲区,以便尽早将它们归还。例如,10 个缓冲区 3 ms/缓冲区而不是 3 个缓冲区 10 ms/缓冲区。

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
于 2013-01-12T13:42:01.960 回答
0

所以我来这里是因为我也想找到 waveoutwrite() 的基本延迟。在获得平滑的正弦音之前,我有大约 25-26 毫秒的延迟。

这是为了:

AMD Phenom(tm) 9850 四核处理器 2.51 GHz 4.00 GB ram 64 位操作系统,基于 x64 的处理器 Windows 10 Enterprise N

代码如下。它是 Petzold 的正弦波程序的修改版本,重构为在命令行上运行。我还将缓冲区的轮询更改为在缓冲区完成上使用回调,其想法是这将使程序更有效率,但这并没有什么不同。

它还具有经过时间的设置,我用它来探测缓冲区操作的各种时间。使用我得到的那些:

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) ;

}
于 2021-01-28T06:09:38.257 回答