730 lines
29 KiB
C
730 lines
29 KiB
C
// Native render-stream syscall smoke test.
|
|
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <ntlite/syscall.h>
|
|
|
|
#define TEST_WAIT_MILLISECONDS 3000ull
|
|
#define TEST_EXTRA_FRAMES 512u
|
|
#define TEST_REFILL_FRAMES 1024u
|
|
#define TEST_TONE_FRAMES 48000u
|
|
#define TEST_AUDIBLE_FRAMES SYS_AUDIO_STREAM_CAPACITY_FRAMES
|
|
#define TEST_AUDIBLE_RAMP_FRAMES 240u
|
|
#define TEST_CLOSE_RUNDOWN_ITERATIONS 72u
|
|
#define TEST_CLOSE_RUNDOWN_SLEEP_MILLISECONDS 5ull
|
|
#define TEST_IDLE_QUIESCE_MILLISECONDS 300ull
|
|
#define TEST_TONE_PHASE_MODULUS 1200u
|
|
#define TEST_TONE_PHASE_STEP 11u
|
|
#define TEST_TONE_QUARTER_PHASE 300u
|
|
#define TEST_TONE_PEAK 8192
|
|
|
|
static int16_t gFrames[TEST_TONE_FRAMES * 2u];
|
|
static int16_t gAudibleFrames[TEST_AUDIBLE_FRAMES * 2u];
|
|
static uint32_t gOpenStream;
|
|
|
|
typedef char TEST_AUDIO_FORMAT_SIZE[(sizeof(SYS_AUDIO_FORMAT) == 8) ? 1 : -1];
|
|
typedef char TEST_AUDIO_ENDPOINT_INFO_SIZE[(sizeof(SYS_AUDIO_ENDPOINT_INFO) == 64) ? 1 : -1];
|
|
typedef char TEST_AUDIO_ENUMERATE_REQUEST_SIZE[(sizeof(SYS_AUDIO_ENUMERATE_ENDPOINTS_REQUEST) == 32) ? 1 : -1];
|
|
typedef char TEST_AUDIO_OPEN_REQUEST_SIZE[(sizeof(SYS_AUDIO_OPEN_STREAM_REQUEST) == 40) ? 1 : -1];
|
|
typedef char TEST_AUDIO_WRITE_REQUEST_SIZE[(sizeof(SYS_AUDIO_WRITE_REQUEST) == 32) ? 1 : -1];
|
|
typedef char TEST_AUDIO_ARM_WAIT_REQUEST_SIZE[(sizeof(SYS_AUDIO_ARM_WAIT_REQUEST) == 56) ? 1 : -1];
|
|
typedef char TEST_AUDIO_STREAM_INFO_SIZE[(sizeof(SYS_AUDIO_STREAM_INFO) == 128) ? 1 : -1];
|
|
typedef char TEST_AUDIO_VOLUME_REQUEST_SIZE[(sizeof(SYS_AUDIO_SET_VOLUME_REQUEST) == 24) ? 1 : -1];
|
|
typedef char TEST_AUDIO_MASTER_VOLUME_REQUEST_SIZE[(sizeof(SYS_AUDIO_SET_MASTER_VOLUME_REQUEST) == 24) ? 1 : -1];
|
|
|
|
// Writes one NUL-terminated string to the console.
|
|
static void print(const char* text) {
|
|
SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromCstr(text) };
|
|
(void)SysWriteConsole(&request);
|
|
}
|
|
|
|
// Writes one unsigned integer to the console.
|
|
static void print_u64(uint64_t value) {
|
|
char digits[32];
|
|
uint32_t count = 0;
|
|
if (!value) {
|
|
print("0");
|
|
return;
|
|
}
|
|
while (value && count < sizeof(digits)) {
|
|
digits[count++] = (char)('0' + value % 10u);
|
|
value /= 10u;
|
|
}
|
|
while (count) {
|
|
char character = digits[--count];
|
|
SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromParts(&character, 1) };
|
|
(void)SysWriteConsole(&request);
|
|
}
|
|
}
|
|
|
|
// Stops and closes the canonical stream after success, failure, or process interruption.
|
|
static void cleanup_stream(void) {
|
|
if (!gOpenStream)
|
|
return;
|
|
(void)SysAudioStop(gOpenStream);
|
|
(void)SysCloseHandle(gOpenStream);
|
|
gOpenStream = 0;
|
|
}
|
|
|
|
// Terminates the test process.
|
|
static void exit_now(int32_t code) {
|
|
cleanup_stream();
|
|
while (true) (void)SysExitProcess(code);
|
|
}
|
|
|
|
// Fails the test with one status value.
|
|
static void fail_status(const char* operation, KSTATUS status) {
|
|
print("audio_stream_smoke: ");
|
|
print(operation);
|
|
print(" status=");
|
|
print_u64(status);
|
|
print("\n");
|
|
exit_now(1);
|
|
}
|
|
|
|
// Fails the test with one plain diagnostic.
|
|
static void fail(const char* message) {
|
|
print("audio_stream_smoke: ");
|
|
print(message);
|
|
print("\n");
|
|
exit_now(1);
|
|
}
|
|
|
|
// Returns one exact 440 Hz integer triangle-wave sample for the supplied phase.
|
|
static int16_t tone_sample(uint32_t phase) {
|
|
int32_t sample;
|
|
if (phase < TEST_TONE_QUARTER_PHASE) {
|
|
sample = (int32_t)(phase * TEST_TONE_PEAK / TEST_TONE_QUARTER_PHASE);
|
|
} else if (phase < TEST_TONE_QUARTER_PHASE * 2u) {
|
|
sample = (int32_t)((TEST_TONE_QUARTER_PHASE * 2u - phase) *
|
|
TEST_TONE_PEAK / TEST_TONE_QUARTER_PHASE);
|
|
} else if (phase < TEST_TONE_QUARTER_PHASE * 3u) {
|
|
sample = -(int32_t)((phase - TEST_TONE_QUARTER_PHASE * 2u) *
|
|
TEST_TONE_PEAK / TEST_TONE_QUARTER_PHASE);
|
|
} else {
|
|
sample = -(int32_t)((TEST_TONE_PHASE_MODULUS - phase) *
|
|
TEST_TONE_PEAK / TEST_TONE_QUARTER_PHASE);
|
|
}
|
|
return (int16_t)sample;
|
|
}
|
|
|
|
// Fills contract and audible buffers without requiring floating-point support.
|
|
static void fill_test_frames(void) {
|
|
uint32_t phase = 0;
|
|
for (uint32_t frame = 0; frame < TEST_TONE_FRAMES; frame++) {
|
|
int16_t sample = tone_sample(phase);
|
|
gFrames[frame * 2u] = sample;
|
|
gFrames[frame * 2u + 1u] = sample;
|
|
phase += TEST_TONE_PHASE_STEP;
|
|
if (phase >= TEST_TONE_PHASE_MODULUS)
|
|
phase -= TEST_TONE_PHASE_MODULUS;
|
|
}
|
|
|
|
phase = 0;
|
|
for (uint32_t frame = 0; frame < TEST_AUDIBLE_FRAMES; frame++) {
|
|
int32_t sample = tone_sample(phase);
|
|
uint32_t gain = TEST_AUDIBLE_RAMP_FRAMES;
|
|
if (frame < gain)
|
|
gain = frame;
|
|
uint32_t remaining = TEST_AUDIBLE_FRAMES - 1u - frame;
|
|
if (remaining < gain)
|
|
gain = remaining;
|
|
sample = sample * (int32_t)gain / (int32_t)TEST_AUDIBLE_RAMP_FRAMES;
|
|
gAudibleFrames[frame * 2u] = (int16_t)sample;
|
|
gAudibleFrames[frame * 2u + 1u] = (int16_t)sample;
|
|
phase += TEST_TONE_PHASE_STEP;
|
|
if (phase >= TEST_TONE_PHASE_MODULUS)
|
|
phase -= TEST_TONE_PHASE_MODULUS;
|
|
}
|
|
}
|
|
|
|
// Queries one stream with the required public structure size.
|
|
static SYS_AUDIO_STREAM_INFO query_stream(uint32_t stream) {
|
|
SYS_AUDIO_STREAM_INFO info;
|
|
memset(&info, 0, sizeof(info));
|
|
info.Size = sizeof(info);
|
|
KSTATUS status = SysAudioQueryStream(stream, &info);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
fail_status("query stream", status);
|
|
if (info.Version != SYS_AUDIO_ABI_VERSION)
|
|
fail("unexpected audio ABI version");
|
|
return info;
|
|
}
|
|
|
|
// Applies one valid stream volume, pan, and mute setting.
|
|
static void set_stream_mix(uint32_t stream,
|
|
uint32_t volumeQ16,
|
|
int32_t panQ16,
|
|
uint32_t muted) {
|
|
SYS_AUDIO_SET_VOLUME_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.StreamHandle = stream;
|
|
request.Muted = muted;
|
|
request.VolumeQ16 = volumeQ16;
|
|
request.PanQ16 = panQ16;
|
|
KSTATUS status = SysAudioSetVolume(&request);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
fail_status("set stream mix", status);
|
|
}
|
|
|
|
// Selects the available default endpoint, or the first available endpoint.
|
|
static const SYS_AUDIO_ENDPOINT_INFO* select_endpoint(const SYS_AUDIO_ENDPOINT_INFO* endpoints,
|
|
uint32_t count) {
|
|
const SYS_AUDIO_ENDPOINT_INFO* fallback = 0;
|
|
for (uint32_t index = 0; index < count; index++) {
|
|
const SYS_AUDIO_ENDPOINT_INFO* endpoint = &endpoints[index];
|
|
if (endpoint->Size != sizeof(*endpoint) ||
|
|
endpoint->Version != SYS_AUDIO_ABI_VERSION ||
|
|
endpoint->State != SysAudioEndpointReady ||
|
|
(endpoint->Flags & SYS_AUDIO_ENDPOINT_FLAG_AVAILABLE) == 0)
|
|
continue;
|
|
if (!fallback) fallback = endpoint;
|
|
if (endpoint->Flags & SYS_AUDIO_ENDPOINT_FLAG_DEFAULT)
|
|
return endpoint;
|
|
}
|
|
return fallback;
|
|
}
|
|
|
|
// Queries one endpoint by its stable identifier.
|
|
static SYS_AUDIO_ENDPOINT_INFO query_endpoint(uint64_t endpointId) {
|
|
SYS_AUDIO_ENDPOINT_INFO endpoints[SYS_AUDIO_MAX_ENDPOINTS];
|
|
uint32_t count = 0;
|
|
SYS_AUDIO_ENUMERATE_ENDPOINTS_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.Endpoints = endpoints;
|
|
request.Capacity = SYS_AUDIO_MAX_ENDPOINTS;
|
|
request.CountOut = &count;
|
|
KSTATUS status = SysAudioEnumerateEndpoints(&request);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
fail_status("query endpoint", status);
|
|
|
|
for (uint32_t index = 0; index < count; index++) {
|
|
if (endpoints[index].EndpointId == endpointId)
|
|
return endpoints[index];
|
|
}
|
|
fail("endpoint disappeared during control test");
|
|
return endpoints[0];
|
|
}
|
|
|
|
// Verifies endpoint master volume, mute, validation, and restoration.
|
|
static void test_master_volume(const SYS_AUDIO_ENDPOINT_INFO* endpoint) {
|
|
SYS_AUDIO_SET_MASTER_VOLUME_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.EndpointId = endpoint->EndpointId;
|
|
request.Muted = 1u;
|
|
request.VolumeQ16 = SYS_AUDIO_GAIN_Q16_UNITY / 2u;
|
|
|
|
KSTATUS status = SysAudioSetMasterVolume(&request);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
fail_status("set master volume", status);
|
|
SYS_AUDIO_ENDPOINT_INFO updated = query_endpoint(endpoint->EndpointId);
|
|
if (updated.MasterVolumeQ16 != SYS_AUDIO_GAIN_Q16_UNITY / 2u ||
|
|
updated.MasterMuted != 1u)
|
|
fail("master volume snapshot mismatch");
|
|
|
|
request.VolumeQ16 = SYS_AUDIO_GAIN_Q16_UNITY + 1u;
|
|
status = SysAudioSetMasterVolume(&request);
|
|
if (status != NTLSTATUS_INVALID_PARAMETER)
|
|
fail_status("reject master volume above unity", status);
|
|
|
|
request.Muted = endpoint->MasterMuted;
|
|
request.VolumeQ16 = endpoint->MasterVolumeQ16;
|
|
status = SysAudioSetMasterVolume(&request);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
fail_status("restore master volume", status);
|
|
}
|
|
|
|
// Opens one stream format and checks the expected status.
|
|
static uint32_t open_stream_format(uint64_t endpointId,
|
|
uint32_t sampleRate,
|
|
uint16_t channelCount,
|
|
uint16_t sampleFormat,
|
|
KSTATUS expectedStatus) {
|
|
uint32_t stream = 0;
|
|
SYS_AUDIO_OPEN_STREAM_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.EndpointId = endpointId;
|
|
request.Format.SampleRate = sampleRate;
|
|
request.Format.ChannelCount = channelCount;
|
|
request.Format.SampleFormat = sampleFormat;
|
|
request.StreamHandleOut = &stream;
|
|
KSTATUS status = SysAudioOpenStream(&request);
|
|
if (status != expectedStatus)
|
|
fail_status("open stream format", status);
|
|
if (status == NTLSTATUS_SUCCESS && !stream)
|
|
fail("open returned a null handle");
|
|
if (status != NTLSTATUS_SUCCESS && stream)
|
|
fail("rejected format returned a handle");
|
|
return stream;
|
|
}
|
|
|
|
// Proves the complete accepted and rejected source-format surface.
|
|
static void test_format_contract(uint64_t endpointId) {
|
|
static const uint32_t rates[] = { 11025u, 22050u, 44100u, 48000u };
|
|
for (uint32_t rateIndex = 0; rateIndex < sizeof(rates) / sizeof(rates[0]); rateIndex++) {
|
|
for (uint16_t channels = 1; channels <= 2; channels++) {
|
|
uint32_t stream = open_stream_format(endpointId,
|
|
rates[rateIndex],
|
|
channels,
|
|
SysAudioSampleS16,
|
|
NTLSTATUS_SUCCESS);
|
|
KSTATUS status = SysCloseHandle(stream);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
fail_status("close format stream", status);
|
|
}
|
|
}
|
|
|
|
(void)open_stream_format(endpointId, 48000u, 2, 2, NTLSTATUS_NOT_SUPPORTED);
|
|
(void)open_stream_format(endpointId, 32000u, 2, SysAudioSampleS16, NTLSTATUS_NOT_SUPPORTED);
|
|
(void)open_stream_format(endpointId, 48000u, 3, SysAudioSampleS16, NTLSTATUS_NOT_SUPPORTED);
|
|
}
|
|
|
|
// Opens the canonical test stream.
|
|
static uint32_t open_stream(uint64_t endpointId) {
|
|
return open_stream_format(endpointId,
|
|
48000u,
|
|
2,
|
|
SysAudioSampleS16,
|
|
NTLSTATUS_SUCCESS);
|
|
}
|
|
|
|
// Writes one sample run and returns the accepted frame count.
|
|
static uint32_t write_samples(uint32_t stream,
|
|
const int16_t* frames,
|
|
uint32_t frameCount,
|
|
KSTATUS expectedStatus) {
|
|
uint32_t written = 0xFFFFFFFFu;
|
|
SYS_AUDIO_WRITE_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.StreamHandle = stream;
|
|
request.FrameCount = frameCount;
|
|
request.Frames = frameCount ? frames : 0;
|
|
request.WrittenFramesOut = &written;
|
|
KSTATUS status = SysAudioWrite(&request);
|
|
if (status != expectedStatus) fail_status("write stream", status);
|
|
if (status != NTLSTATUS_SUCCESS && written != 0)
|
|
fail("failed write reported accepted frames");
|
|
return written;
|
|
}
|
|
|
|
// Writes one contract-tone range and returns the accepted frame count.
|
|
static uint32_t write_frames(uint32_t stream,
|
|
uint32_t frameOffset,
|
|
uint32_t frameCount,
|
|
KSTATUS expectedStatus) {
|
|
if (frameOffset > TEST_TONE_FRAMES ||
|
|
frameCount > TEST_TONE_FRAMES - frameOffset)
|
|
fail("test write range exceeded the tone buffer");
|
|
return write_samples(stream,
|
|
frameCount ? gFrames + (size_t)frameOffset * 2u : 0,
|
|
frameCount,
|
|
expectedStatus);
|
|
}
|
|
|
|
// Arms one exact set of stream progress targets.
|
|
static void arm_wait(uint32_t stream,
|
|
uint32_t mask,
|
|
uint32_t writableFrames,
|
|
uint64_t consumedFrames,
|
|
uint64_t presentedFrames,
|
|
uint64_t drainedGeneration,
|
|
uint64_t discontinuityGeneration) {
|
|
SYS_AUDIO_ARM_WAIT_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.StreamHandle = stream;
|
|
request.Mask = mask;
|
|
request.WritableFrames = writableFrames;
|
|
request.ConsumedFrames = consumedFrames;
|
|
request.PresentedFrames = presentedFrames;
|
|
request.DrainedGeneration = drainedGeneration;
|
|
request.DiscontinuityGeneration = discontinuityGeneration;
|
|
KSTATUS status = SysAudioArmWait(&request);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
fail_status("arm wait", status);
|
|
}
|
|
|
|
// Waits once and converts timeout or alert completion into a focused failure.
|
|
static void wait_stream(uint32_t stream, const char* operation) {
|
|
KSTATUS status = SysWaitSingle(stream, SYS_WAIT_FLAG_NONE, TEST_WAIT_MILLISECONDS);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
fail_status(operation, status);
|
|
}
|
|
|
|
// Proves that closing the final handle retires an active stream with queued periods.
|
|
static void test_active_close_rundown(uint64_t endpointId) {
|
|
for (uint32_t iteration = 0;
|
|
iteration < TEST_CLOSE_RUNDOWN_ITERATIONS;
|
|
iteration++) {
|
|
uint32_t stream = open_stream(endpointId);
|
|
gOpenStream = stream;
|
|
set_stream_mix(stream,
|
|
SYS_AUDIO_GAIN_Q16_UNITY,
|
|
SYS_AUDIO_PAN_Q16_CENTER,
|
|
1u);
|
|
uint32_t written = write_frames(stream,
|
|
0,
|
|
SYS_AUDIO_STREAM_CAPACITY_FRAMES,
|
|
NTLSTATUS_SUCCESS);
|
|
if (written != SYS_AUDIO_STREAM_CAPACITY_FRAMES)
|
|
fail("active-close prefill was partial");
|
|
|
|
SYS_AUDIO_STREAM_INFO before = query_stream(stream);
|
|
arm_wait(stream,
|
|
SYS_AUDIO_WAIT_CONSUMED |
|
|
SYS_AUDIO_WAIT_FAILED |
|
|
SYS_AUDIO_WAIT_DISCONTINUITY,
|
|
0,
|
|
before.FramesConsumed + TEST_EXTRA_FRAMES,
|
|
before.FramesPresented,
|
|
before.DrainedGeneration,
|
|
before.DiscontinuityGeneration);
|
|
KSTATUS status = SysAudioStart(stream);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
fail_status("active-close start", status);
|
|
wait_stream(stream, "active-close consumed wait");
|
|
|
|
SYS_AUDIO_STREAM_INFO active = query_stream(stream);
|
|
if (active.State == SysAudioStreamFailed)
|
|
fail_status("active-close stream failure", active.FailureStatus);
|
|
if (active.DiscontinuityGeneration != before.DiscontinuityGeneration ||
|
|
active.FramesConsumed < before.FramesConsumed + TEST_EXTRA_FRAMES)
|
|
fail("active-close progress mismatch");
|
|
|
|
status = SysCloseHandle(stream);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
fail_status("active-close final handle", status);
|
|
gOpenStream = 0;
|
|
status = SysSleep(TEST_CLOSE_RUNDOWN_SLEEP_MILLISECONDS);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
fail_status("active-close rundown sleep", status);
|
|
}
|
|
}
|
|
|
|
// Plays one bounded audible tone without depending on real-time user refills.
|
|
static void play_continuous_tone(uint32_t stream) {
|
|
KSTATUS status = SysAudioReset(stream);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
fail_status("audible reset", status);
|
|
set_stream_mix(stream,
|
|
SYS_AUDIO_GAIN_Q16_UNITY,
|
|
SYS_AUDIO_PAN_Q16_CENTER,
|
|
0u);
|
|
|
|
SYS_AUDIO_STREAM_INFO baseline = query_stream(stream);
|
|
uint32_t submitted = write_samples(stream,
|
|
gAudibleFrames,
|
|
TEST_AUDIBLE_FRAMES,
|
|
NTLSTATUS_SUCCESS);
|
|
if (submitted != TEST_AUDIBLE_FRAMES)
|
|
fail("audible prefill was partial");
|
|
|
|
status = SysAudioStart(stream);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
fail_status("audible start", status);
|
|
status = SysAudioDrain(stream);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
fail_status("audible drain", status);
|
|
print("audio_stream_smoke: audible 440 Hz tone begin\n");
|
|
|
|
arm_wait(stream,
|
|
SYS_AUDIO_WAIT_DRAINED |
|
|
SYS_AUDIO_WAIT_FAILED |
|
|
SYS_AUDIO_WAIT_DISCONTINUITY,
|
|
0,
|
|
baseline.FramesConsumed,
|
|
baseline.FramesPresented,
|
|
baseline.DrainedGeneration,
|
|
baseline.DiscontinuityGeneration);
|
|
wait_stream(stream, "audible drained wait");
|
|
|
|
SYS_AUDIO_STREAM_INFO drained = query_stream(stream);
|
|
if (drained.State == SysAudioStreamFailed)
|
|
fail_status("audible stream failure", drained.FailureStatus);
|
|
if (drained.State != SysAudioStreamStopped ||
|
|
drained.DrainedGeneration != baseline.DrainedGeneration + 1u ||
|
|
drained.DiscontinuityGeneration != baseline.DiscontinuityGeneration ||
|
|
drained.FramesSubmitted != TEST_AUDIBLE_FRAMES ||
|
|
drained.FramesConsumed != TEST_AUDIBLE_FRAMES ||
|
|
drained.FramesPresented != TEST_AUDIBLE_FRAMES ||
|
|
drained.BufferedFrames != 0 ||
|
|
drained.Underruns != 0 ||
|
|
drained.SilenceFramesInserted != 0)
|
|
fail("audible tone completion mismatch");
|
|
print("audio_stream_smoke: audible 440 Hz tone complete\n");
|
|
}
|
|
|
|
// Runs the complete native stream contract against one endpoint.
|
|
static void test_stream(const SYS_AUDIO_ENDPOINT_INFO* endpoint) {
|
|
test_format_contract(endpoint->EndpointId);
|
|
test_active_close_rundown(endpoint->EndpointId);
|
|
|
|
uint32_t stream = open_stream(endpoint->EndpointId);
|
|
gOpenStream = stream;
|
|
KSTATUS status;
|
|
SYS_AUDIO_STREAM_INFO info = query_stream(stream);
|
|
if (info.State != SysAudioStreamCreated ||
|
|
info.CapacityFrames != SYS_AUDIO_STREAM_CAPACITY_FRAMES ||
|
|
info.Format.SampleFormat != SysAudioSampleS16)
|
|
fail("initial stream snapshot mismatch");
|
|
|
|
SYS_AUDIO_SET_VOLUME_REQUEST volume;
|
|
memset(&volume, 0, sizeof(volume));
|
|
volume.Size = sizeof(volume);
|
|
volume.StreamHandle = stream;
|
|
volume.VolumeQ16 = SYS_AUDIO_GAIN_Q16_UNITY / 2u;
|
|
volume.PanQ16 = SYS_AUDIO_PAN_Q16_CENTER;
|
|
status = SysAudioSetVolume(&volume);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
fail_status("set volume", status);
|
|
info = query_stream(stream);
|
|
if (info.VolumeQ16 != SYS_AUDIO_GAIN_Q16_UNITY / 2u ||
|
|
info.PanQ16 != SYS_AUDIO_PAN_Q16_CENTER || info.Muted)
|
|
fail("volume snapshot mismatch");
|
|
|
|
volume.VolumeQ16 = SYS_AUDIO_GAIN_Q16_UNITY + 1u;
|
|
status = SysAudioSetVolume(&volume);
|
|
if (status != NTLSTATUS_INVALID_PARAMETER)
|
|
fail_status("reject volume above unity", status);
|
|
volume.VolumeQ16 = SYS_AUDIO_GAIN_Q16_UNITY / 2u;
|
|
volume.PanQ16 = SYS_AUDIO_PAN_Q16_RIGHT + 1;
|
|
status = SysAudioSetVolume(&volume);
|
|
if (status != NTLSTATUS_INVALID_PARAMETER)
|
|
fail_status("reject pan outside range", status);
|
|
|
|
volume.VolumeQ16 = SYS_AUDIO_GAIN_Q16_UNITY;
|
|
volume.PanQ16 = SYS_AUDIO_PAN_Q16_CENTER;
|
|
volume.Muted = 1u;
|
|
status = SysAudioSetVolume(&volume);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
fail_status("mute control stream", status);
|
|
|
|
uint32_t written = write_frames(stream, 0, 0, NTLSTATUS_SUCCESS);
|
|
if (written != 0) fail("zero-length write accepted frames");
|
|
written = write_frames(stream,
|
|
0,
|
|
SYS_AUDIO_STREAM_CAPACITY_FRAMES + TEST_EXTRA_FRAMES,
|
|
NTLSTATUS_SUCCESS);
|
|
if (written != SYS_AUDIO_STREAM_CAPACITY_FRAMES)
|
|
fail("partial write did not fill exactly one stream ring");
|
|
written = write_frames(stream, 0, 1, NTLSTATUS_WOULD_BLOCK);
|
|
if (written != 0) fail("full-ring write accepted a frame");
|
|
|
|
info = query_stream(stream);
|
|
arm_wait(stream,
|
|
SYS_AUDIO_WAIT_WRITABLE |
|
|
SYS_AUDIO_WAIT_CONSUMED |
|
|
SYS_AUDIO_WAIT_FAILED |
|
|
SYS_AUDIO_WAIT_DISCONTINUITY,
|
|
TEST_EXTRA_FRAMES,
|
|
info.FramesConsumed + TEST_EXTRA_FRAMES,
|
|
0,
|
|
info.DrainedGeneration,
|
|
info.DiscontinuityGeneration);
|
|
status = SysAudioStart(stream);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
fail_status("start", status);
|
|
wait_stream(stream, "writable wait");
|
|
|
|
SYS_AUDIO_STREAM_INFO progressed = query_stream(stream);
|
|
if (progressed.State == SysAudioStreamFailed)
|
|
fail_status("stream failure", progressed.FailureStatus);
|
|
if (progressed.DiscontinuityGeneration != info.DiscontinuityGeneration)
|
|
fail("unexpected discontinuity before writable target");
|
|
if (progressed.WritableFrames < TEST_EXTRA_FRAMES ||
|
|
progressed.FramesConsumed < info.FramesConsumed + TEST_EXTRA_FRAMES)
|
|
fail("writable or consumed wait did not observe its target");
|
|
|
|
status = SysAudioPause(stream);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
fail_status("pause", status);
|
|
if (query_stream(stream).State != SysAudioStreamPaused)
|
|
fail("pause state mismatch");
|
|
status = SysAudioResume(stream);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
fail_status("resume", status);
|
|
|
|
uint32_t submitted = SYS_AUDIO_STREAM_CAPACITY_FRAMES;
|
|
written = write_frames(stream, submitted, TEST_EXTRA_FRAMES, NTLSTATUS_SUCCESS);
|
|
if (written != TEST_EXTRA_FRAMES)
|
|
fail("resume refill was partial");
|
|
submitted += written;
|
|
|
|
while (submitted < TEST_TONE_FRAMES) {
|
|
SYS_AUDIO_STREAM_INFO refill = query_stream(stream);
|
|
if (refill.State == SysAudioStreamFailed)
|
|
fail_status("stream failure", refill.FailureStatus);
|
|
if (refill.DiscontinuityGeneration != info.DiscontinuityGeneration)
|
|
fail("unexpected discontinuity during muted control refill");
|
|
|
|
uint32_t remaining = TEST_TONE_FRAMES - submitted;
|
|
uint32_t refillFrames = remaining < TEST_REFILL_FRAMES
|
|
? remaining
|
|
: TEST_REFILL_FRAMES;
|
|
if (refill.WritableFrames < refillFrames) {
|
|
arm_wait(stream,
|
|
SYS_AUDIO_WAIT_WRITABLE |
|
|
SYS_AUDIO_WAIT_FAILED |
|
|
SYS_AUDIO_WAIT_DISCONTINUITY,
|
|
refillFrames,
|
|
refill.FramesConsumed,
|
|
refill.FramesPresented,
|
|
refill.DrainedGeneration,
|
|
info.DiscontinuityGeneration);
|
|
wait_stream(stream, "muted control refill wait");
|
|
continue;
|
|
}
|
|
|
|
written = write_frames(stream, submitted, refillFrames, NTLSTATUS_SUCCESS);
|
|
if (written != refillFrames)
|
|
fail("muted control refill was partial despite reserved capacity");
|
|
submitted += written;
|
|
}
|
|
|
|
SYS_AUDIO_STREAM_INFO beforeDrain = query_stream(stream);
|
|
if (beforeDrain.FramesSubmitted != TEST_TONE_FRAMES)
|
|
fail("muted control frame submission mismatch");
|
|
uint64_t presentedTarget = beforeDrain.FramesSubmitted;
|
|
status = SysAudioDrain(stream);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
fail_status("drain", status);
|
|
arm_wait(stream,
|
|
SYS_AUDIO_WAIT_PRESENTED |
|
|
SYS_AUDIO_WAIT_FAILED |
|
|
SYS_AUDIO_WAIT_DISCONTINUITY,
|
|
0,
|
|
beforeDrain.FramesConsumed,
|
|
presentedTarget,
|
|
beforeDrain.DrainedGeneration,
|
|
beforeDrain.DiscontinuityGeneration);
|
|
wait_stream(stream, "presentation wait");
|
|
|
|
SYS_AUDIO_STREAM_INFO presented = query_stream(stream);
|
|
if (presented.State == SysAudioStreamFailed)
|
|
fail_status("stream failure", presented.FailureStatus);
|
|
if (presented.DiscontinuityGeneration != beforeDrain.DiscontinuityGeneration)
|
|
fail("unexpected discontinuity before presentation target");
|
|
if (presented.FramesPresented < presentedTarget)
|
|
fail("presented wait did not observe its target");
|
|
|
|
if (presented.State != SysAudioStreamStopped) {
|
|
arm_wait(stream,
|
|
SYS_AUDIO_WAIT_DRAINED |
|
|
SYS_AUDIO_WAIT_FAILED |
|
|
SYS_AUDIO_WAIT_DISCONTINUITY,
|
|
0,
|
|
presented.FramesConsumed,
|
|
presented.FramesPresented,
|
|
beforeDrain.DrainedGeneration,
|
|
beforeDrain.DiscontinuityGeneration);
|
|
wait_stream(stream, "drained wait");
|
|
}
|
|
|
|
SYS_AUDIO_STREAM_INFO drained = query_stream(stream);
|
|
if (drained.State == SysAudioStreamFailed)
|
|
fail_status("stream failure", drained.FailureStatus);
|
|
if (drained.DiscontinuityGeneration != beforeDrain.DiscontinuityGeneration)
|
|
fail("unexpected discontinuity during drain");
|
|
if (drained.State != SysAudioStreamStopped ||
|
|
drained.DrainedGeneration != beforeDrain.DrainedGeneration + 1u ||
|
|
drained.BufferedFrames != 0 ||
|
|
drained.FramesPresented < presentedTarget ||
|
|
drained.FramesPresented + drained.FramesDiscarded < drained.FramesSubmitted)
|
|
fail("drained stream snapshot mismatch");
|
|
|
|
status = SysAudioReset(stream);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
fail_status("reset", status);
|
|
SYS_AUDIO_STREAM_INFO reset = query_stream(stream);
|
|
if (reset.State != SysAudioStreamStopped || reset.FramesSubmitted != 0 ||
|
|
reset.FramesConsumed != 0 || reset.FramesPresented != 0 ||
|
|
reset.DrainedGeneration != drained.DrainedGeneration)
|
|
fail("reset stream snapshot mismatch");
|
|
|
|
written = write_frames(stream, 0, 128, NTLSTATUS_SUCCESS);
|
|
if (written != 128) fail("stop test write was partial");
|
|
status = SysAudioStart(stream);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
fail_status("restart", status);
|
|
SYS_AUDIO_STREAM_INFO beforeStop = query_stream(stream);
|
|
arm_wait(stream,
|
|
SYS_AUDIO_WAIT_STOPPED |
|
|
SYS_AUDIO_WAIT_FAILED |
|
|
SYS_AUDIO_WAIT_DISCONTINUITY,
|
|
0,
|
|
beforeStop.FramesConsumed,
|
|
beforeStop.FramesPresented,
|
|
beforeStop.DrainedGeneration,
|
|
beforeStop.DiscontinuityGeneration);
|
|
status = SysAudioStop(stream);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
fail_status("stop", status);
|
|
wait_stream(stream, "stopped wait");
|
|
if (query_stream(stream).State != SysAudioStreamStopped)
|
|
fail("stop state mismatch");
|
|
|
|
play_continuous_tone(stream);
|
|
|
|
status = SysCloseHandle(stream);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
fail_status("close stream", status);
|
|
gOpenStream = 0;
|
|
status = SysSleep(TEST_IDLE_QUIESCE_MILLISECONDS);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
fail_status("endpoint quiesce sleep", status);
|
|
}
|
|
|
|
// Executes the native audio syscall smoke test.
|
|
int main(void) {
|
|
fill_test_frames();
|
|
print("audio_stream_smoke: begin native render contract test\n");
|
|
|
|
uint32_t count = 0;
|
|
SYS_AUDIO_ENUMERATE_ENDPOINTS_REQUEST countRequest;
|
|
memset(&countRequest, 0, sizeof(countRequest));
|
|
countRequest.Size = sizeof(countRequest);
|
|
countRequest.CountOut = &count;
|
|
KSTATUS status = SysAudioEnumerateEndpoints(&countRequest);
|
|
if (status != NTLSTATUS_SUCCESS && status != NTLSTATUS_BUFFER_TOO_SMALL)
|
|
fail_status("count endpoints", status);
|
|
if (!count) {
|
|
print("audio_stream_smoke: SKIP no render endpoint\n");
|
|
exit_now(0);
|
|
}
|
|
if (count > SYS_AUDIO_MAX_ENDPOINTS)
|
|
fail("endpoint count exceeded ABI maximum");
|
|
|
|
SYS_AUDIO_ENDPOINT_INFO endpoints[SYS_AUDIO_MAX_ENDPOINTS];
|
|
memset(endpoints, 0, sizeof(endpoints));
|
|
SYS_AUDIO_ENUMERATE_ENDPOINTS_REQUEST enumerate;
|
|
memset(&enumerate, 0, sizeof(enumerate));
|
|
enumerate.Size = sizeof(enumerate);
|
|
enumerate.Endpoints = endpoints;
|
|
enumerate.Capacity = SYS_AUDIO_MAX_ENDPOINTS;
|
|
enumerate.CountOut = &count;
|
|
status = SysAudioEnumerateEndpoints(&enumerate);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
fail_status("enumerate endpoints", status);
|
|
|
|
const SYS_AUDIO_ENDPOINT_INFO* endpoint = select_endpoint(endpoints, count);
|
|
if (!endpoint) {
|
|
print("audio_stream_smoke: SKIP no available render endpoint\n");
|
|
exit_now(0);
|
|
}
|
|
|
|
print("audio_stream_smoke: endpoint=");
|
|
print_u64(endpoint->EndpointId);
|
|
print("\n");
|
|
test_master_volume(endpoint);
|
|
test_stream(endpoint);
|
|
print("audio_stream_smoke: PASS endpoint=");
|
|
print_u64(endpoint->EndpointId);
|
|
print("\n");
|
|
exit_now(0);
|
|
}
|