456 lines
18 KiB
C
456 lines
18 KiB
C
// WinMM waveform-output smoke test over the native render-stream personality path.
|
|
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
#include <intrin.h>
|
|
#include <ntlite/syscall.h>
|
|
|
|
typedef void* HANDLE;
|
|
typedef void* HWAVEOUT;
|
|
typedef unsigned int MMRESULT;
|
|
typedef unsigned int UINT;
|
|
typedef uint32_t DWORD;
|
|
typedef uintptr_t DWORD_PTR;
|
|
typedef unsigned short WORD;
|
|
typedef int BOOL;
|
|
|
|
#define FALSE 0
|
|
#define TRUE 1
|
|
#define INFINITE 0xFFFFFFFFu
|
|
#define WAIT_OBJECT_0 0u
|
|
|
|
#define MMSYSERR_NOERROR 0u
|
|
#define MMSYSERR_NOTSUPPORTED 8u
|
|
#define WAVERR_BADFORMAT 32u
|
|
#define WAVERR_STILLPLAYING 33u
|
|
|
|
#define WAVE_FORMAT_PCM 1u
|
|
#define WAVE_FORMAT_QUERY 0x00000001u
|
|
#define WAVE_ALLOWSYNC 0x00000002u
|
|
#define WAVE_FORMAT_DIRECT 0x00000008u
|
|
#define CALLBACK_WINDOW 0x00010000u
|
|
#define CALLBACK_THREAD 0x00020000u
|
|
#define CALLBACK_FUNCTION 0x00030000u
|
|
#define CALLBACK_EVENT 0x00050000u
|
|
#define WAVE_MAPPER 0xFFFFFFFFu
|
|
|
|
#define WHDR_DONE 0x00000001u
|
|
#define WHDR_PREPARED 0x00000002u
|
|
#define WHDR_BEGINLOOP 0x00000004u
|
|
#define WHDR_ENDLOOP 0x00000008u
|
|
#define WHDR_INQUEUE 0x00000010u
|
|
|
|
#define WOM_OPEN 0x03BBu
|
|
#define WOM_CLOSE 0x03BCu
|
|
#define WOM_DONE 0x03BDu
|
|
|
|
#define TIME_SAMPLES 0x0002u
|
|
|
|
#define TEST_FRAMES 4096u
|
|
#define TEST_CONTROL_FRAMES 16384u
|
|
#define TEST_PHASE_MODULUS 1200u
|
|
#define TEST_PHASE_STEP 11u
|
|
#define TEST_QUARTER_PHASE 300u
|
|
#define TEST_PEAK 8192
|
|
|
|
typedef struct WAVEFORMATEX {
|
|
WORD wFormatTag;
|
|
WORD nChannels;
|
|
DWORD nSamplesPerSec;
|
|
DWORD nAvgBytesPerSec;
|
|
WORD nBlockAlign;
|
|
WORD wBitsPerSample;
|
|
WORD cbSize;
|
|
} WAVEFORMATEX;
|
|
|
|
typedef struct WAVEHDR {
|
|
char* lpData;
|
|
DWORD dwBufferLength;
|
|
DWORD dwBytesRecorded;
|
|
DWORD_PTR dwUser;
|
|
DWORD dwFlags;
|
|
DWORD dwLoops;
|
|
struct WAVEHDR* lpNext;
|
|
DWORD_PTR reserved;
|
|
} WAVEHDR;
|
|
|
|
typedef struct MMTIME {
|
|
UINT wType;
|
|
union {
|
|
DWORD ms;
|
|
DWORD sample;
|
|
DWORD cb;
|
|
DWORD ticks;
|
|
} u;
|
|
} MMTIME;
|
|
|
|
typedef struct WAVEOUTCAPSA {
|
|
WORD wMid;
|
|
WORD wPid;
|
|
DWORD vDriverVersion;
|
|
char szPname[32];
|
|
DWORD dwFormats;
|
|
WORD wChannels;
|
|
WORD wReserved1;
|
|
DWORD dwSupport;
|
|
} WAVEOUTCAPSA;
|
|
|
|
typedef char TEST_WAVEFORMATEX_SIZE[(sizeof(WAVEFORMATEX) == 20u) ? 1 : -1];
|
|
typedef char TEST_WAVEHDR_SIZE[(sizeof(WAVEHDR) == 48u) ? 1 : -1];
|
|
typedef char TEST_MMTIME_SIZE[(sizeof(MMTIME) == 8u) ? 1 : -1];
|
|
typedef char TEST_WAVEOUTCAPSA_SIZE[(sizeof(WAVEOUTCAPSA) == 52u) ? 1 : -1];
|
|
|
|
typedef void (__stdcall *WAVEOUTPROC)(HWAVEOUT, UINT, DWORD_PTR, DWORD_PTR, DWORD_PTR);
|
|
|
|
__declspec(dllimport) UINT waveOutGetNumDevs(void);
|
|
__declspec(dllimport) MMRESULT waveOutOpen(HWAVEOUT*, UINT, const WAVEFORMATEX*, DWORD_PTR, DWORD_PTR, DWORD);
|
|
__declspec(dllimport) MMRESULT waveOutClose(HWAVEOUT);
|
|
__declspec(dllimport) MMRESULT waveOutPrepareHeader(HWAVEOUT, WAVEHDR*, UINT);
|
|
__declspec(dllimport) MMRESULT waveOutUnprepareHeader(HWAVEOUT, WAVEHDR*, UINT);
|
|
__declspec(dllimport) MMRESULT waveOutWrite(HWAVEOUT, WAVEHDR*, UINT);
|
|
__declspec(dllimport) MMRESULT waveOutPause(HWAVEOUT);
|
|
__declspec(dllimport) MMRESULT waveOutRestart(HWAVEOUT);
|
|
__declspec(dllimport) MMRESULT waveOutReset(HWAVEOUT);
|
|
__declspec(dllimport) MMRESULT waveOutSetVolume(HWAVEOUT, DWORD);
|
|
__declspec(dllimport) MMRESULT waveOutGetVolume(HWAVEOUT, DWORD*);
|
|
__declspec(dllimport) MMRESULT waveOutGetPosition(HWAVEOUT, MMTIME*, UINT);
|
|
__declspec(dllimport) MMRESULT waveOutGetID(HWAVEOUT, UINT*);
|
|
__declspec(dllimport) MMRESULT waveOutGetDevCapsA(UINT, WAVEOUTCAPSA*, UINT);
|
|
__declspec(dllimport) MMRESULT waveOutGetErrorTextA(MMRESULT, char*, UINT);
|
|
__declspec(dllimport) HANDLE CreateEventA(void*, BOOL, BOOL, const char*);
|
|
__declspec(dllimport) BOOL SetEvent(HANDLE);
|
|
__declspec(dllimport) DWORD WaitForSingleObject(HANDLE, DWORD);
|
|
__declspec(dllimport) BOOL CloseHandle(HANDLE);
|
|
|
|
static int16_t gS16Frames[TEST_FRAMES * 2u];
|
|
static uint8_t gS24Frames[TEST_FRAMES * 2u * 3u];
|
|
static int16_t gControlFrames[TEST_CONTROL_FRAMES * 2u];
|
|
static HANDLE gFunctionEvent;
|
|
static volatile long gOpenCallbacks;
|
|
static volatile long gDoneCallbacks;
|
|
static volatile long gCloseCallbacks;
|
|
|
|
// Writes one NUL-terminated string to the kernel console.
|
|
static void print(const char* text) {
|
|
SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromCstr(text) };
|
|
(void)SysWriteConsole(&request);
|
|
}
|
|
|
|
// Terminates the test process after one failure.
|
|
static void fail(const char* message) {
|
|
print("winmm_waveout_smoke: ");
|
|
print(message);
|
|
print("\n");
|
|
while (true) (void)SysExitProcess(1);
|
|
}
|
|
|
|
// Verifies one WinMM result exactly.
|
|
static void expect_result(const char* operation, MMRESULT actual, MMRESULT expected) {
|
|
if (actual != expected) {
|
|
print("winmm_waveout_smoke: unexpected result from ");
|
|
print(operation);
|
|
print("\n");
|
|
while (true) (void)SysExitProcess(1);
|
|
}
|
|
}
|
|
|
|
// Returns one exact integer 440 Hz triangle-wave sample at 48 kHz.
|
|
static int16_t tone_sample(uint32_t phase) {
|
|
int32_t sample;
|
|
if (phase < TEST_QUARTER_PHASE) {
|
|
sample = (int32_t)(phase * TEST_PEAK / TEST_QUARTER_PHASE);
|
|
} else if (phase < TEST_QUARTER_PHASE * 2u) {
|
|
sample = (int32_t)((TEST_QUARTER_PHASE * 2u - phase) *
|
|
TEST_PEAK / TEST_QUARTER_PHASE);
|
|
} else if (phase < TEST_QUARTER_PHASE * 3u) {
|
|
sample = -(int32_t)((phase - TEST_QUARTER_PHASE * 2u) *
|
|
TEST_PEAK / TEST_QUARTER_PHASE);
|
|
} else {
|
|
sample = -(int32_t)((TEST_PHASE_MODULUS - phase) *
|
|
TEST_PEAK / TEST_QUARTER_PHASE);
|
|
}
|
|
return (int16_t)sample;
|
|
}
|
|
|
|
// Fills S16, packed-S24, and muted control buffers without floating point.
|
|
static void fill_buffers(void) {
|
|
uint32_t phase = 0;
|
|
for (uint32_t frame = 0; frame < TEST_FRAMES; frame++) {
|
|
int16_t sample = tone_sample(phase);
|
|
gS16Frames[frame * 2u] = sample;
|
|
gS16Frames[frame * 2u + 1u] = sample;
|
|
int32_t sample24 = (int32_t)sample * 256;
|
|
for (uint32_t channel = 0; channel < 2u; channel++) {
|
|
size_t offset = ((size_t)frame * 2u + channel) * 3u;
|
|
gS24Frames[offset] = (uint8_t)sample24;
|
|
gS24Frames[offset + 1u] = (uint8_t)(sample24 >> 8u);
|
|
gS24Frames[offset + 2u] = (uint8_t)(sample24 >> 16u);
|
|
}
|
|
phase += TEST_PHASE_STEP;
|
|
if (phase >= TEST_PHASE_MODULUS)
|
|
phase -= TEST_PHASE_MODULUS;
|
|
}
|
|
memset(gControlFrames, 0, sizeof(gControlFrames));
|
|
}
|
|
|
|
// Receives function callbacks on the per-HWAVEOUT worker thread.
|
|
static void __stdcall wave_callback(HWAVEOUT waveOut,
|
|
UINT message,
|
|
DWORD_PTR instance,
|
|
DWORD_PTR parameter1,
|
|
DWORD_PTR parameter2) {
|
|
(void)waveOut;
|
|
(void)instance;
|
|
(void)parameter1;
|
|
(void)parameter2;
|
|
if (message == WOM_OPEN)
|
|
_InterlockedIncrement(&gOpenCallbacks);
|
|
else if (message == WOM_DONE) {
|
|
_InterlockedIncrement(&gDoneCallbacks);
|
|
(void)SetEvent(gFunctionEvent);
|
|
} else if (message == WOM_CLOSE)
|
|
_InterlockedIncrement(&gCloseCallbacks);
|
|
}
|
|
|
|
// Waits for one callback event with a finite deadline.
|
|
static void wait_event(HANDLE event, const char* operation) {
|
|
if (WaitForSingleObject(event, 5000u) != WAIT_OBJECT_0)
|
|
fail(operation);
|
|
}
|
|
|
|
// Returns one canonical PCM format descriptor.
|
|
static WAVEFORMATEX make_format_at(uint32_t sampleRate,
|
|
uint16_t channels,
|
|
uint16_t bitsPerSample) {
|
|
WAVEFORMATEX format;
|
|
memset(&format, 0, sizeof(format));
|
|
format.wFormatTag = WAVE_FORMAT_PCM;
|
|
format.nChannels = channels;
|
|
format.nSamplesPerSec = sampleRate;
|
|
format.wBitsPerSample = bitsPerSample;
|
|
format.nBlockAlign = (uint16_t)(channels * (bitsPerSample / 8u));
|
|
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
|
|
return format;
|
|
}
|
|
|
|
// Returns the default stereo 48 kHz PCM descriptor used for playback tests.
|
|
static WAVEFORMATEX make_format(uint16_t bitsPerSample) {
|
|
return make_format_at(48000u, 2u, bitsPerSample);
|
|
}
|
|
|
|
|
|
// Verifies device enumeration, capabilities, and fixed error descriptions.
|
|
static void test_device_surface(void) {
|
|
WAVEOUTCAPSA caps;
|
|
memset(&caps, 0, sizeof(caps));
|
|
expect_result("device capabilities",
|
|
waveOutGetDevCapsA(WAVE_MAPPER, &caps, sizeof(caps)),
|
|
MMSYSERR_NOERROR);
|
|
if (caps.wChannels != 2u || !caps.szPname[0])
|
|
fail("device capabilities mismatch");
|
|
|
|
char errorText[64];
|
|
memset(errorText, 0, sizeof(errorText));
|
|
expect_result("error text",
|
|
waveOutGetErrorTextA(WAVERR_BADFORMAT, errorText, sizeof(errorText)),
|
|
MMSYSERR_NOERROR);
|
|
if (!errorText[0])
|
|
fail("error text was empty");
|
|
}
|
|
|
|
// Verifies function callbacks, queued completion, immediate copy, and position.
|
|
static void test_function_callback(void) {
|
|
WAVEFORMATEX format = make_format(16u);
|
|
gFunctionEvent = CreateEventA(NULL, FALSE, FALSE, NULL);
|
|
if (!gFunctionEvent)
|
|
fail("CreateEventA for function callback failed");
|
|
|
|
HWAVEOUT waveOut = NULL;
|
|
expect_result("function waveOutOpen",
|
|
waveOutOpen(&waveOut,
|
|
WAVE_MAPPER,
|
|
&format,
|
|
(DWORD_PTR)(WAVEOUTPROC)wave_callback,
|
|
0,
|
|
CALLBACK_FUNCTION | WAVE_ALLOWSYNC | WAVE_FORMAT_DIRECT),
|
|
MMSYSERR_NOERROR);
|
|
if (!waveOut || gOpenCallbacks != 1)
|
|
fail("function WOM_OPEN callback missing");
|
|
UINT deviceId = UINT32_MAX;
|
|
expect_result("get device ID", waveOutGetID(waveOut, &deviceId), MMSYSERR_NOERROR);
|
|
if (deviceId >= waveOutGetNumDevs())
|
|
fail("resolved device ID is out of range");
|
|
|
|
WAVEHDR first;
|
|
WAVEHDR second;
|
|
memset(&first, 0, sizeof(first));
|
|
memset(&second, 0, sizeof(second));
|
|
first.lpData = (char*)gS16Frames;
|
|
first.dwBufferLength = sizeof(gS16Frames) / 2u;
|
|
second.lpData = (char*)gS16Frames + first.dwBufferLength;
|
|
second.dwBufferLength = sizeof(gS16Frames) - first.dwBufferLength;
|
|
expect_result("prepare first", waveOutPrepareHeader(waveOut, &first, sizeof(first)), MMSYSERR_NOERROR);
|
|
expect_result("prepare second", waveOutPrepareHeader(waveOut, &second, sizeof(second)), MMSYSERR_NOERROR);
|
|
expect_result("write first", waveOutWrite(waveOut, &first, sizeof(first)), MMSYSERR_NOERROR);
|
|
expect_result("write second", waveOutWrite(waveOut, &second, sizeof(second)), MMSYSERR_NOERROR);
|
|
|
|
memset(gS16Frames, 0, sizeof(gS16Frames));
|
|
while (gDoneCallbacks < 2)
|
|
wait_event(gFunctionEvent, "function WOM_DONE timed out");
|
|
if (gDoneCallbacks != 2 ||
|
|
(first.dwFlags & (WHDR_DONE | WHDR_PREPARED)) != (WHDR_DONE | WHDR_PREPARED) ||
|
|
(second.dwFlags & (WHDR_DONE | WHDR_PREPARED)) != (WHDR_DONE | WHDR_PREPARED) ||
|
|
(first.dwFlags & WHDR_INQUEUE) || (second.dwFlags & WHDR_INQUEUE))
|
|
fail("function header completion mismatch");
|
|
|
|
MMTIME position;
|
|
memset(&position, 0, sizeof(position));
|
|
position.wType = TIME_SAMPLES;
|
|
expect_result("get position", waveOutGetPosition(waveOut, &position, sizeof(position)), MMSYSERR_NOERROR);
|
|
if (position.u.sample < TEST_FRAMES)
|
|
fail("position did not reach submitted frames");
|
|
|
|
expect_result("set volume", waveOutSetVolume(waveOut, 0xC0008000u), MMSYSERR_NOERROR);
|
|
DWORD volume = 0;
|
|
expect_result("get volume", waveOutGetVolume(waveOut, &volume), MMSYSERR_NOERROR);
|
|
if (volume != 0xC0008000u)
|
|
fail("volume did not round trip");
|
|
|
|
expect_result("unprepare first", waveOutUnprepareHeader(waveOut, &first, sizeof(first)), MMSYSERR_NOERROR);
|
|
expect_result("unprepare second", waveOutUnprepareHeader(waveOut, &second, sizeof(second)), MMSYSERR_NOERROR);
|
|
expect_result("function close", waveOutClose(waveOut), MMSYSERR_NOERROR);
|
|
if (gCloseCallbacks != 1)
|
|
fail("function WOM_CLOSE callback missing");
|
|
if (!CloseHandle(gFunctionEvent))
|
|
fail("function callback event close failed");
|
|
gFunctionEvent = NULL;
|
|
}
|
|
|
|
// Verifies CALLBACK_EVENT and packed-S24 conversion through the native S16 stream.
|
|
static void test_event_s24(void) {
|
|
WAVEFORMATEX format = make_format(24u);
|
|
HANDLE event = CreateEventA(NULL, FALSE, FALSE, NULL);
|
|
if (!event)
|
|
fail("CreateEventA for event callback failed");
|
|
|
|
HWAVEOUT waveOut = NULL;
|
|
expect_result("event waveOutOpen",
|
|
waveOutOpen(&waveOut,
|
|
WAVE_MAPPER,
|
|
&format,
|
|
(DWORD_PTR)event,
|
|
0,
|
|
CALLBACK_EVENT),
|
|
MMSYSERR_NOERROR);
|
|
wait_event(event, "event WOM_OPEN timed out");
|
|
|
|
WAVEHDR header;
|
|
memset(&header, 0, sizeof(header));
|
|
header.lpData = (char*)gS24Frames;
|
|
header.dwBufferLength = sizeof(gS24Frames);
|
|
expect_result("event prepare", waveOutPrepareHeader(waveOut, &header, sizeof(header)), MMSYSERR_NOERROR);
|
|
expect_result("event write", waveOutWrite(waveOut, &header, sizeof(header)), MMSYSERR_NOERROR);
|
|
memset(gS24Frames, 0, sizeof(gS24Frames));
|
|
wait_event(event, "event WOM_DONE timed out");
|
|
if ((header.dwFlags & WHDR_DONE) == 0 || (header.dwFlags & WHDR_INQUEUE))
|
|
fail("event header completion mismatch");
|
|
expect_result("event unprepare", waveOutUnprepareHeader(waveOut, &header, sizeof(header)), MMSYSERR_NOERROR);
|
|
expect_result("event close", waveOutClose(waveOut), MMSYSERR_NOERROR);
|
|
wait_event(event, "event WOM_CLOSE timed out");
|
|
if (!CloseHandle(event))
|
|
fail("event callback handle close failed");
|
|
}
|
|
|
|
// Verifies queued-close refusal and synchronous pause, restart, and reset.
|
|
static void test_control_operations(void) {
|
|
WAVEFORMATEX format = make_format(16u);
|
|
HWAVEOUT waveOut = NULL;
|
|
expect_result("control waveOutOpen",
|
|
waveOutOpen(&waveOut, WAVE_MAPPER, &format, 0, 0, 0),
|
|
MMSYSERR_NOERROR);
|
|
expect_result("control mute", waveOutSetVolume(waveOut, 0), MMSYSERR_NOERROR);
|
|
|
|
WAVEHDR header;
|
|
memset(&header, 0, sizeof(header));
|
|
header.lpData = (char*)gControlFrames;
|
|
header.dwBufferLength = sizeof(gControlFrames);
|
|
expect_result("control prepare", waveOutPrepareHeader(waveOut, &header, sizeof(header)), MMSYSERR_NOERROR);
|
|
header.dwFlags |= WHDR_BEGINLOOP | WHDR_ENDLOOP;
|
|
header.dwLoops = 2u;
|
|
expect_result("loop rejection", waveOutWrite(waveOut, &header, sizeof(header)), MMSYSERR_NOTSUPPORTED);
|
|
header.dwFlags &= ~(WHDR_BEGINLOOP | WHDR_ENDLOOP);
|
|
header.dwLoops = 0;
|
|
expect_result("control write", waveOutWrite(waveOut, &header, sizeof(header)), MMSYSERR_NOERROR);
|
|
expect_result("close while queued", waveOutClose(waveOut), WAVERR_STILLPLAYING);
|
|
expect_result("unprepare while queued",
|
|
waveOutUnprepareHeader(waveOut, &header, sizeof(header)),
|
|
WAVERR_STILLPLAYING);
|
|
expect_result("pause", waveOutPause(waveOut), MMSYSERR_NOERROR);
|
|
expect_result("second pause", waveOutPause(waveOut), MMSYSERR_NOERROR);
|
|
expect_result("restart", waveOutRestart(waveOut), MMSYSERR_NOERROR);
|
|
expect_result("second restart", waveOutRestart(waveOut), MMSYSERR_NOERROR);
|
|
expect_result("reset", waveOutReset(waveOut), MMSYSERR_NOERROR);
|
|
if ((header.dwFlags & WHDR_DONE) == 0 || (header.dwFlags & WHDR_INQUEUE))
|
|
fail("reset did not complete queued header");
|
|
expect_result("control unprepare", waveOutUnprepareHeader(waveOut, &header, sizeof(header)), MMSYSERR_NOERROR);
|
|
expect_result("control close", waveOutClose(waveOut), MMSYSERR_NOERROR);
|
|
}
|
|
|
|
// Verifies the complete accepted PCM matrix and explicit rejection paths.
|
|
static void test_formats_and_rejections(void) {
|
|
static const uint32_t rates[] = { 11025u, 22050u, 44100u, 48000u };
|
|
static const uint16_t bits[] = { 16u, 24u };
|
|
for (uint32_t rateIndex = 0; rateIndex < sizeof(rates) / sizeof(rates[0]); rateIndex++) {
|
|
for (uint16_t channels = 1u; channels <= 2u; channels++) {
|
|
for (uint32_t bitsIndex = 0; bitsIndex < sizeof(bits) / sizeof(bits[0]); bitsIndex++) {
|
|
WAVEFORMATEX format = make_format_at(rates[rateIndex], channels, bits[bitsIndex]);
|
|
expect_result("accepted PCM query",
|
|
waveOutOpen(NULL, WAVE_MAPPER, &format, 0, 0, WAVE_FORMAT_QUERY),
|
|
MMSYSERR_NOERROR);
|
|
}
|
|
}
|
|
}
|
|
|
|
WAVEFORMATEX s16 = make_format(16u);
|
|
HWAVEOUT waveOut = NULL;
|
|
expect_result("window callback rejection",
|
|
waveOutOpen(&waveOut, WAVE_MAPPER, &s16, 1, 0, CALLBACK_WINDOW),
|
|
MMSYSERR_NOTSUPPORTED);
|
|
expect_result("thread callback rejection",
|
|
waveOutOpen(&waveOut, WAVE_MAPPER, &s16, 1, 0, CALLBACK_THREAD),
|
|
MMSYSERR_NOTSUPPORTED);
|
|
|
|
WAVEFORMATEX floating = make_format_at(48000u, 2u, 32u);
|
|
floating.wFormatTag = 3u;
|
|
expect_result("Float32 rejection",
|
|
waveOutOpen(NULL, WAVE_MAPPER, &floating, 0, 0, WAVE_FORMAT_QUERY),
|
|
WAVERR_BADFORMAT);
|
|
|
|
WAVEFORMATEX badRate = make_format_at(32000u, 2u, 16u);
|
|
expect_result("32 kHz rejection",
|
|
waveOutOpen(NULL, WAVE_MAPPER, &badRate, 0, 0, WAVE_FORMAT_QUERY),
|
|
WAVERR_BADFORMAT);
|
|
|
|
WAVEFORMATEX badChannels = make_format_at(48000u, 3u, 16u);
|
|
expect_result("three-channel rejection",
|
|
waveOutOpen(NULL, WAVE_MAPPER, &badChannels, 0, 0, WAVE_FORMAT_QUERY),
|
|
WAVERR_BADFORMAT);
|
|
}
|
|
|
|
// Runs the focused WinMM waveform-output contract test.
|
|
int main(void) {
|
|
print("winmm_waveout_smoke: begin\n");
|
|
if (!waveOutGetNumDevs())
|
|
fail("no waveform output devices");
|
|
fill_buffers();
|
|
test_device_surface();
|
|
test_formats_and_rejections();
|
|
test_control_operations();
|
|
test_function_callback();
|
|
test_event_s24();
|
|
print("winmm_waveout_smoke: PASS\n");
|
|
while (true) (void)SysExitProcess(0);
|
|
return 0;
|
|
}
|