Files
nt-lite/code/user/tests/winmm_waveout_smoke/winmm_waveout_smoke.c
T

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