双缓冲waveOutWrite()像地狱一样口吃

时间:2018-04-02 05:02:24

标签: c++ windows audio waveout waveoutwrite

[神秘已经解决了;对于那些寻求解释的人来说,这是在这篇文章的底部]

下面是我尝试使用Windows waveOut*()函数编写的Windows音频生成器。

尽管根据MSDN做了字面上的一切(例如应该是reset manually的回调事件),我无法从该死的东西得到平滑的方波播放 - 实际上任何流畅的播放,但为了简单起见我展示正方形。缓冲区边框总是通过点击来迎接我!看起来Windows只是忽略了我使用双缓冲的事实。

生成器本身与缓冲区大小无关,如果我使用更大的缓冲区,无缝播放会持续更长的时间 - 但是当缓冲区最终结束时会有一次点击。

帮助。

#define _WIN32_IE 0x0500
#define _WIN32_WINNT 0x0501
#define WINVER _WIN32_WINNT

#include <windows.h>
#include <mmsystem.h>
#include <commctrl.h>

#include <stdint.h>
#include <stdio.h>
#include <math.h>



short freq, ampl;



typedef struct {
    long chnl, smpl, bits, size, swiz;
    void *sink, *data[2];
} WAVE;



LRESULT APIENTRY WndProc(HWND hWnd, UINT uMsg, WPARAM wPrm, LPARAM lPrm) {
    switch (uMsg) {
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        case WM_NOTIFY:
            switch (((NMHDR*)lPrm)->idFrom) {
                case 2:
                    freq = ((NMUPDOWN*)lPrm)->iPos;
                    break;

                case 4:
                    ampl = ((NMUPDOWN*)lPrm)->iPos;
                    break;
            }
            return 0;

        default:
            break;
    }
    return DefWindowProc(hWnd, uMsg, wPrm, lPrm);
}



void FillBuf(WAVE *wave, short freq, short ampl, long *phaz) {
    int16_t *data = wave->data[wave->swiz ^= 1];
    float tone = 1.0 * freq / wave->smpl;
    long iter;

    for(iter = 0; iter < wave->size; iter++)
        data[iter] = ((long)(tone * (iter + *phaz)) & 1)? ampl : -ampl;

    *phaz = *phaz + iter;//2.0 * frac(0.5 * tone * (iter + *phaz)) / tone;
}



DWORD APIENTRY WaveFunc(LPVOID data) {
    WAVEHDR *whdr;
    WAVE *wave;
    intptr_t *sink;
    long size, phaz = 0;

    wave = (WAVE*)data;
    whdr = (WAVEHDR*)(sink = wave->sink)[1];
    size = wave->chnl * wave->size * (wave->bits >> 3);
    wave->data[0] = calloc(1, size);
    wave->data[1] = calloc(1, size);
    do {
        waveOutUnprepareHeader((HWAVEOUT)sink[0], whdr, sizeof(WAVEHDR));
        whdr->dwBufferLength = size;
        whdr->dwFlags = 0;
        whdr->dwLoops = 0;
        whdr->lpData = (LPSTR)wave->data[wave->swiz];
        waveOutPrepareHeader((HWAVEOUT)sink[0], whdr, sizeof(WAVEHDR));
        ResetEvent((HANDLE)sink[2]);
        waveOutWrite((HWAVEOUT)sink[0], whdr, sizeof(WAVEHDR));
        FillBuf(wave, freq, ampl, &phaz);
    } while (!WaitForSingleObject((HANDLE)sink[2], INFINITE));
    return 0;
}



int APIENTRY WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR args, int show) {
    WNDCLASSEX wndc = {sizeof(wndc), CS_HREDRAW | CS_VREDRAW, WndProc, 0, 0,
                       inst, LoadIcon(0, IDI_HAND), LoadCursor(0, IDC_ARROW),
                       (HBRUSH)(COLOR_BTNFACE + 1), 0, "-", 0};
    INITCOMMONCONTROLSEX icct = {sizeof(icct), ICC_STANDARD_CLASSES};
    MSG pmsg;

    HWND mwnd, cwnd, spin;
    DWORD thrd;
    WAVEFORMATEX wfmt;
    intptr_t data[3];
    WAVE wave = {1, 44100, 16, 4096, 0, data};

//    AllocConsole();
//    freopen("CONOUT$", "wb", stdout);

    InitCommonControlsEx(&icct);
    RegisterClassEx(&wndc);
    mwnd = CreateWindowEx(0, wndc.lpszClassName, " ",
                          WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                          CW_USEDEFAULT, CW_USEDEFAULT, 320, 240,
                          HWND_DESKTOP, 0, wndc.hInstance, 0);


    cwnd = CreateWindowEx(WS_EX_CLIENTEDGE, WC_EDIT, 0, ES_AUTOHSCROLL
                        | ES_WANTRETURN | ES_MULTILINE | ES_NUMBER | WS_CHILD
                        | WS_VISIBLE, 10, 10, 100, 24, mwnd, (HMENU)1, 0, 0);
    SendMessage(cwnd, EM_LIMITTEXT, 9, 0);
    spin = CreateWindowEx(0, UPDOWN_CLASS, 0, UDS_HOTTRACK | UDS_NOTHOUSANDS
                        | UDS_ALIGNRIGHT | UDS_SETBUDDYINT | UDS_ARROWKEYS
                        | WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, mwnd, (HMENU)2, 0, 0);
    SendMessage(spin, UDM_SETBUDDY, (WPARAM)cwnd, 0);
    SendMessage(spin, UDM_SETRANGE32, (WPARAM)20, (LPARAM)22050);
    SendMessage(spin, UDM_SETPOS32, 0, (LPARAM)(freq = 400));


    cwnd = CreateWindowEx(WS_EX_CLIENTEDGE, WC_EDIT, 0, ES_AUTOHSCROLL
                        | ES_WANTRETURN | ES_MULTILINE | ES_NUMBER | WS_CHILD
                        | WS_VISIBLE, 10, 44, 100, 24, mwnd, (HMENU)3, 0, 0);
    SendMessage(cwnd, EM_LIMITTEXT, 9, 0);
    spin = CreateWindowEx(0, UPDOWN_CLASS, 0, UDS_HOTTRACK | UDS_NOTHOUSANDS
                        | UDS_ALIGNRIGHT | UDS_SETBUDDYINT | UDS_ARROWKEYS
                        | WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, mwnd, (HMENU)4, 0, 0);
    SendMessage(spin, UDM_SETBUDDY, (WPARAM)cwnd, 0);
    SendMessage(spin, UDM_SETRANGE32, (WPARAM)0, (LPARAM)32767);
    SendMessage(spin, UDM_SETPOS32, 0, (LPARAM)(ampl = 32767));


    wfmt = (WAVEFORMATEX){WAVE_FORMAT_PCM, wave.chnl, wave.smpl,
                         ((wave.chnl * wave.bits) >> 3) * wave.smpl,
                          (wave.chnl * wave.bits) >> 3, wave.bits};
    data[1] = (intptr_t)calloc(1, sizeof(WAVEHDR));
    waveOutOpen((LPHWAVEOUT)&data[0], WAVE_MAPPER, &wfmt,
                 data[2] = (intptr_t)CreateEvent(0, 1, 0, 0), 0,
                 CALLBACK_EVENT);
    SetThreadPriority(CreateThread(0, 0, WaveFunc, &wave, 0, &thrd),
                      THREAD_PRIORITY_TIME_CRITICAL);

    while (pmsg.message != WM_QUIT) {
        if (PeekMessage(&pmsg, 0, 0, 0, PM_REMOVE)) {
            TranslateMessage(&pmsg);
            DispatchMessage(&pmsg);
            continue;
        }
        Sleep(1);
    }
    waveOutClose((HWAVEOUT)data[0]);
    fclose(stdout);
    FreeConsole();

    exit(pmsg.wParam);
    return 0;
}

[UPDATE:]

我被告知重复标题,但无济于事:

#define _WIN32_IE 0x0500
#define _WIN32_WINNT 0x0501
#define WINVER _WIN32_WINNT

#include <windows.h>
#include <mmsystem.h>
#include <commctrl.h>

#include <stdint.h>
#include <stdio.h>
#include <math.h>



short freq, ampl;



typedef struct {
    long chnl, smpl, bits, size, swiz;
    void *sink, *data[2];
} WAVE;



LRESULT APIENTRY WndProc(HWND hWnd, UINT uMsg, WPARAM wPrm, LPARAM lPrm) {
    switch (uMsg) {
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        case WM_NOTIFY:
            switch (((NMHDR*)lPrm)->idFrom) {
                case 2:
                    freq = ((NMUPDOWN*)lPrm)->iPos;
                    break;

                case 4:
                    ampl = ((NMUPDOWN*)lPrm)->iPos;
                    break;
            }
            return 0;

        default:
            break;
    }
    return DefWindowProc(hWnd, uMsg, wPrm, lPrm);
}



void FillBuf(WAVE *wave, short freq, short ampl, long *phaz) {
    int16_t *data = wave->data[wave->swiz ^= 1];
    float tone = 1.0 * freq / wave->smpl;
    long iter;

    for(iter = 0; iter < wave->size; iter++)
        data[iter] = ((long)(tone * (iter + *phaz)) & 1)? ampl : -ampl;

    *phaz = *phaz + iter;//2.0 * frac(0.5 * tone * (iter + *phaz)) / tone;
}



DWORD APIENTRY WaveFunc(LPVOID data) {
    WAVEHDR *whdr;
    WAVE *wave;
    intptr_t *sink;
    long size, phaz = 0;

    wave = (WAVE*)data;
    whdr = (WAVEHDR*)(sink = wave->sink)[1];
    size = wave->chnl * wave->size * (wave->bits >> 3);

    whdr[0].dwBufferLength = whdr[1].dwBufferLength = size;
    whdr[0].dwFlags        = whdr[1].dwFlags        = 0;
    whdr[0].dwLoops        = whdr[1].dwLoops        = 0;
    whdr[0].lpData = (LPSTR)(wave->data[0] = calloc(1, size));
    whdr[1].lpData = (LPSTR)(wave->data[1] = calloc(1, size));

    do {
        waveOutUnprepareHeader((HWAVEOUT)sink[0], &whdr[wave->swiz], sizeof(WAVEHDR));
        waveOutPrepareHeader((HWAVEOUT)sink[0], &whdr[wave->swiz], sizeof(WAVEHDR));
        ResetEvent((HANDLE)sink[2]);
        waveOutWrite((HWAVEOUT)sink[0], &whdr[wave->swiz], sizeof(WAVEHDR));
        FillBuf(wave, freq, ampl, &phaz);
    } while (!WaitForSingleObject((HANDLE)sink[2], INFINITE));
    return 0;
}



int APIENTRY WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR args, int show) {
    WNDCLASSEX wndc = {sizeof(wndc), CS_HREDRAW | CS_VREDRAW, WndProc, 0, 0,
                       inst, LoadIcon(0, IDI_HAND), LoadCursor(0, IDC_ARROW),
                       (HBRUSH)(COLOR_BTNFACE + 1), 0, "-", 0};
    INITCOMMONCONTROLSEX icct = {sizeof(icct), ICC_STANDARD_CLASSES};
    MSG pmsg;

    HWND mwnd, cwnd, spin;
    DWORD thrd;
    WAVEFORMATEX wfmt;
    intptr_t sink[3];
    WAVE wave = {1, 44100, 16, 4096, 0, sink};

//    AllocConsole();
//    freopen("CONOUT$", "wb", stdout);

    InitCommonControlsEx(&icct);
    RegisterClassEx(&wndc);
    mwnd = CreateWindowEx(0, wndc.lpszClassName, " ",
                          WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                          CW_USEDEFAULT, CW_USEDEFAULT, 320, 240,
                          HWND_DESKTOP, 0, wndc.hInstance, 0);


    cwnd = CreateWindowEx(WS_EX_CLIENTEDGE, WC_EDIT, 0, ES_AUTOHSCROLL
                        | ES_WANTRETURN | ES_MULTILINE | ES_NUMBER | WS_CHILD
                        | WS_VISIBLE, 10, 10, 100, 24, mwnd, (HMENU)1, 0, 0);
    SendMessage(cwnd, EM_LIMITTEXT, 9, 0);
    spin = CreateWindowEx(0, UPDOWN_CLASS, 0, UDS_HOTTRACK | UDS_NOTHOUSANDS
                        | UDS_ALIGNRIGHT | UDS_SETBUDDYINT | UDS_ARROWKEYS
                        | WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, mwnd, (HMENU)2, 0, 0);
    SendMessage(spin, UDM_SETBUDDY, (WPARAM)cwnd, 0);
    SendMessage(spin, UDM_SETRANGE32, (WPARAM)20, (LPARAM)22050);
    SendMessage(spin, UDM_SETPOS32, 0, (LPARAM)(freq = 400));


    cwnd = CreateWindowEx(WS_EX_CLIENTEDGE, WC_EDIT, 0, ES_AUTOHSCROLL
                        | ES_WANTRETURN | ES_MULTILINE | ES_NUMBER | WS_CHILD
                        | WS_VISIBLE, 10, 44, 100, 24, mwnd, (HMENU)3, 0, 0);
    SendMessage(cwnd, EM_LIMITTEXT, 9, 0);
    spin = CreateWindowEx(0, UPDOWN_CLASS, 0, UDS_HOTTRACK | UDS_NOTHOUSANDS
                        | UDS_ALIGNRIGHT | UDS_SETBUDDYINT | UDS_ARROWKEYS
                        | WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, mwnd, (HMENU)4, 0, 0);
    SendMessage(spin, UDM_SETBUDDY, (WPARAM)cwnd, 0);
    SendMessage(spin, UDM_SETRANGE32, (WPARAM)0, (LPARAM)32767);
    SendMessage(spin, UDM_SETPOS32, 0, (LPARAM)(ampl = 32767));


    wfmt = (WAVEFORMATEX){WAVE_FORMAT_PCM, wave.chnl, wave.smpl,
                         ((wave.chnl * wave.bits) >> 3) * wave.smpl,
                          (wave.chnl * wave.bits) >> 3, wave.bits};
    sink[1] = (intptr_t)calloc(2, sizeof(WAVEHDR));
    waveOutOpen((LPHWAVEOUT)&sink[0], WAVE_MAPPER, &wfmt,
                 sink[2] = (intptr_t)CreateEvent(0, 1, 0, 0), 0,
                 CALLBACK_EVENT);
    SetThreadPriority(CreateThread(0, 0, WaveFunc, &wave, 0, &thrd),
                      THREAD_PRIORITY_TIME_CRITICAL);

    while (pmsg.message != WM_QUIT) {
        if (PeekMessage(&pmsg, 0, 0, 0, PM_REMOVE)) {
            TranslateMessage(&pmsg);
            DispatchMessage(&pmsg);
            continue;
        }
        Sleep(1);
    }
    waveOutClose((HWAVEOUT)sink[0]);
    fclose(stdout);
    FreeConsole();

    exit(pmsg.wParam);
    return 0;
}

[实际发生的事情:]

由于Windows切换缓冲区时Windows耗尽了这一事实,因此播放一直不稳定。为了避免这种情况,你必须在反馈循环开始之前向系统提供两个缓冲区,这样当其中一个缓冲区完成播放时,下一个缓冲区已经准备好并发送,当你重新填充时一个刚刚退休的人。

也许失落的灵魂(比如我前两天)终于在这里找到了清晰度=)

说真的,暂时这是互联网上唯一一个提出实际工作解决方案的页面,它不使用计时器或任何kludge而不是正确的方法。

3 个答案:

答案 0 :(得分:3)

虽然代码大多没问题(在功能方面但不具备可读性和清晰度),但线程功能并不好。

你应该在没有准备的情况下填写并随后播放。

这里你去了(线程也不需要优先级高于正常值):

DWORD APIENTRY WaveFunc(LPVOID data) 
{
    WAVEHDR *whdr;
    WAVE *wave;
    intptr_t *sink;
    long size, phaz = 0;

    wave = (WAVE*)data;
    whdr = (WAVEHDR*)(sink = (intptr_t*) wave->sink)[1];
    size = wave->chnl * wave->size * (wave->bits >> 3);

    HWAVEOUT hWaveOut = (HWAVEOUT) sink[0];
    HANDLE hEvent = (HANDLE)sink[2];

    whdr[0].dwBufferLength = whdr[1].dwBufferLength = size;
    whdr[0].dwFlags        = whdr[1].dwFlags        = 0;
    whdr[0].dwLoops        = whdr[1].dwLoops        = 0;
    whdr[0].lpData = (LPSTR)(wave->data[0] = calloc(1, size));
    whdr[1].lpData = (LPSTR)(wave->data[1] = calloc(1, size));

    ResetEvent(hEvent);

    assert(wave->swiz == 0);
    FillBuf(wave, freq, ampl, &phaz);
    waveOutPrepareHeader(hWaveOut, &whdr[1], sizeof (WAVEHDR));
    waveOutWrite(hWaveOut, &whdr[1], sizeof (WAVEHDR));

    assert(wave->swiz == 1);
    FillBuf(wave, freq, ampl, &phaz);
    waveOutPrepareHeader(hWaveOut, &whdr[0], sizeof (WAVEHDR));
    waveOutWrite(hWaveOut, &whdr[0], sizeof (WAVEHDR));

    for(; ; )
    {
        WaitForSingleObject(hEvent, INFINITE);
        ResetEvent(hEvent);
        for(long index = 0; index < 2; index++)
            if(whdr[index].dwFlags & WHDR_DONE)
            {
                wave->swiz = index ^ 1;
                // NOTE: See comment from Paul Sanders: the headers have to be
                //       prepared before writing, however there is no need to
                //       re-prepare to upload new data
                //waveOutUnprepareHeader(hWaveOut, &whdr[wave->swiz], sizeof (WAVEHDR));
                FillBuf(wave, freq, ampl, &phaz);
                //waveOutPrepareHeader(hWaveOut, &whdr[wave->swiz], sizeof (WAVEHDR));
                waveOutWrite(hWaveOut, &whdr[wave->swiz], sizeof (WAVEHDR));
            }
    }
    return 0;
}

答案 1 :(得分:3)

我没有足够的评论来评论,所以这必须是一个答案,但我只是想补充一点,你不必每次都不准备和代表WAVEHDR,你可以重新使用它。 / p>

此外,如果您需要将其他数据与WAVEHDR相关联,您可以分配更大的结构并在最后添加 - waveoutWrite也不关心。如果缓冲区在重用之前通过某种处理链,这可能很方便(主要用于输入缓冲区)。我在将DSD转换为PCM时使用此技巧。

Wave API摇滚!

答案 2 :(得分:1)

我不认为你按照预期进行双缓冲。首先,我只能看到一个WAVEHDR被实例化。

在您的设置中,创建2个WAVEHDR。

在你的主题中执行以下操作(伪代码)

waveOutPrepareHeader(hdr[0]);
waveOutPrepareHeader(hdr[1]);
FillBuffer(hdr[0]->lpData);
FillBuffer(hdr[1]->lpData);
waveOutWrite(hdr[0]);
waveOutWrite(hdr[1]);
int nextBuf = 0;
while (!WaitForSingleObject(....)))
{
    waveOutUnprepareHeader(hdr[nextBuf]);
    waveOutPrepareHeader(hdr[nextBuf]);
    FillBuffer(hdr[nextBuf]);
    waveOutWrite(hdr[nextBuf]);
    nextBuf = (nextBuf+1) % 2;
}