Double-buffered waveOutWrite() 口吃得要命
Double-buffered waveOutWrite() stuttering like hell
[谜底已解;对于那些寻求解释的人,它位于此 post]
的底部
下面是我尝试使用 Windows waveOut*()
函数编写的 Windows 音调生成器。
尽管根据 MSDN 做了所有事情(例如,回调事件应该是 reset manually),但我无法从该死的东西中获得流畅的 square-wave 播放 - 实际上,任何流畅的播放,但对于为了简单起见,我演示了正方形。缓冲区边框总是用点击来迎接我!看起来 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;
}
[更新:]
有人告诉我重复 header,但无济于事:
#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 运行 在我切换缓冲区的那一刻没有数据。为避免这种情况,你必须在反馈循环开始之前向系统提供两个缓冲区,这样当一个缓冲区播放完毕时,下一个缓冲区已经准备好并发送,而你重新装满刚刚退役的那个。
愿迷失的灵魂(就像两天前的我一样)终于在这里找到清晰的方向 =)
说真的,目前这是 Internet 上唯一提出实际工作解决方案的页面,它不使用计时器或任何错误的方法,而不是正确的方法。
我认为您double-buffering没有按预期进行。其一,我只能看到实例化了一个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;
}
虽然代码大部分没问题(就功能而言,但不是可读性和清晰度),线程功能并不好。
你应该在没有准备的情况下填充,然后路由播放。
给你(线程也不需要高于正常的优先级):
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;
}
我没有足够的代表发表评论所以这必须是一个答案,但我只是想补充一点,你不必每次都取消准备和重新准备 WAVEHDR,你可以 re-use它。
此外,如果您需要将其他数据与 WAVEHDR 相关联,您可以分配一个更大的结构并将其固定在最后 - waveoutWrite 不会关心。如果缓冲区在被重用之前通过某种处理链,这可能很方便(对于输入缓冲区,主要是)。我在将 DSD 转换为 PCM 时使用了这个技巧。
Wave API 摇滚!
[谜底已解;对于那些寻求解释的人,它位于此 post]
的底部下面是我尝试使用 Windows waveOut*()
函数编写的 Windows 音调生成器。
尽管根据 MSDN 做了所有事情(例如,回调事件应该是 reset manually),但我无法从该死的东西中获得流畅的 square-wave 播放 - 实际上,任何流畅的播放,但对于为了简单起见,我演示了正方形。缓冲区边框总是用点击来迎接我!看起来 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;
}
[更新:]
有人告诉我重复 header,但无济于事:
#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 运行 在我切换缓冲区的那一刻没有数据。为避免这种情况,你必须在反馈循环开始之前向系统提供两个缓冲区,这样当一个缓冲区播放完毕时,下一个缓冲区已经准备好并发送,而你重新装满刚刚退役的那个。
愿迷失的灵魂(就像两天前的我一样)终于在这里找到清晰的方向 =)
说真的,目前这是 Internet 上唯一提出实际工作解决方案的页面,它不使用计时器或任何错误的方法,而不是正确的方法。
我认为您double-buffering没有按预期进行。其一,我只能看到实例化了一个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;
}
虽然代码大部分没问题(就功能而言,但不是可读性和清晰度),线程功能并不好。
你应该在没有准备的情况下填充,然后路由播放。
给你(线程也不需要高于正常的优先级):
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;
}
我没有足够的代表发表评论所以这必须是一个答案,但我只是想补充一点,你不必每次都取消准备和重新准备 WAVEHDR,你可以 re-use它。
此外,如果您需要将其他数据与 WAVEHDR 相关联,您可以分配一个更大的结构并将其固定在最后 - waveoutWrite 不会关心。如果缓冲区在被重用之前通过某种处理链,这可能很方便(对于输入缓冲区,主要是)。我在将 DSD 转换为 PCM 时使用了这个技巧。
Wave API 摇滚!