代码之家  ›  专栏  ›  技术社区  ›  hidefromkgb

双缓冲waveOutWrite()口吃得很厉害

  •  5
  • hidefromkgb  · 技术社区  · 8 年前

    [谜团已经解开;对于那些寻求解释的人来说,它在这篇文章的底部]

    下面是我正在尝试使用Windows编写的Windows音调生成器 waveOut*() 功能。

    尽管按照MSDN做了所有事情(例如,回调事件 reset manually ),我无法从该死的东西中获得平滑的方波播放–实际上,任何平滑的播放,但为了简单起见,我演示了方波。Buffer borders总是用点击来欢迎我!看起来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;
    }
    

    [更新:]

    我被告知复制了标题,但没有用:

    #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在我切换缓冲区的那一刻就用完了数据,因此播放一直很不顺畅。为了避免这种情况, 在反馈循环开始之前,必须为系统提供两个缓冲区 ,这样,当其中一个缓冲区完成播放时,下一个缓冲区已经准备好并发送,而您重新填充刚刚失效的缓冲区。

    愿那些迷失的灵魂(像我两天前一样)最终在这里找到答案=)

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

    3 回复  |  直到 8 年前
        1
  •  5
  •   Roman Ryltsov    8 年前

    虽然代码基本上没有问题(就功能而言,但就可读性和清晰度而言),但线程功能并不好。

    您应该在未做好准备的情况下进行填充,然后进行播放。

    现在开始(线程的优先级也不需要高于正常值):

    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;
    }
    
        2
  •  3
  •   catnip    8 年前

    我没有足够的rep来评论,所以这必须是一个答案,但我只想补充一点,您不必每次都取消准备并重新准备WAVEHDR,您可以重新使用它。

    此外,如果您需要将其他数据与WAVEHDR关联,您可以分配一个更大的结构并将其固定在末端-waveoutWrite不会在意。如果缓冲区在被重用之前通过某种处理链,这将非常方便(主要是对于输入缓冲区)。我在将DSD转换为PCM时使用了这个技巧。

    Wave APIs摇滚乐!

        3
  •  1
  •   jaket    8 年前

    我认为您没有按预期进行双缓冲。首先,我只能看到一个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;
    }