Files
quisk/sound_wasapi.c
T

1380 lines
47 KiB
C

/*
* This module provides sound access for QUISK using the Windows WASAPI library.
* This software is Copyright (C) 2020 by James C. Ahlstrom, and is
* licensed for use under the GNU General Public License (GPL).
* See http://www.opensource.org.
* Note that there is NO WARRANTY AT ALL. USE AT YOUR OWN RISK!!
*/
#ifdef QUISK_HAVE_WASAPI
#define UNICODE
#include <Python.h>
#include <complex.h>
#include <math.h>
#include "quisk.h"
#include <stdint.h>
#include <stdatomic.h>
#define INITGUID
#define CINTERFACE
#define COBJMACROS
#include <audioclient.h>
#include <endpointvolume.h>
#include <mmdeviceapi.h>
#include <mmreg.h>
#include <functiondiscoverykeys_devpkey.h>
#include <avrt.h>
#include <ksmedia.h>
#include <processthreadsapi.h>
#define EXIT_ON_ERROR(hres) \
if (FAILED(hres)) { goto Exit; }
// REFERENCE_TIME time units per second and per millisecond
#define REFTIMES_PER_SEC 10000000
#define REFTIMES_PER_MILLISEC 10000
#define REFTIMES_PER_MICROSEC 10
#define PKEY_DEVICE_FRIENDLYNAME (&PKEY_Device_FriendlyName)
#define CLSID_MMDEVICEENUMERATOR (&CLSID_MMDeviceEnumerator)
#define IID_IAUDIOCLIENT (&IID_IAudioClient)
#define IID_IAUDIORENDERCLIENT (&IID_IAudioRenderClient)
#define IID_IAUDIOCAPTURECLIENT (&IID_IAudioCaptureClient)
#define IID_IMMDEVICEENUMERATOR (&IID_IMMDeviceEnumerator)
const GUID KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = {
0x00000003,0x0000,0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
} ;
const GUID KSDATAFORMAT_SUBTYPE_PCM = {
0x00000001,0x0000,0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
} ;
static IMMDeviceEnumerator *pEnumerator = NULL;
struct dev_data_t {
IMMDevice *pDevice;
IAudioClient *pAudioClient;
IAudioCaptureClient *pCaptureClient;
IAudioRenderClient *pRenderClient;
UINT32 bufferSizeFrames; // size of the Wasapi buffer in frames
AUDCLNT_SHAREMODE sharemode;
HANDLE hEvent;
int playbuf_underrun_reset; // playbuf is used for the play device threadproc
int playbuf_underrun_msg;
int playbuf_overflow_reset;
int playbuf_nFrames;
_Atomic int64_t playbuf_nRead; // Total number of frames read from buffer
_Atomic int64_t playbuf_nWrite; // Total number of frames written to buffer
unsigned char * playbuf_buf; // frames are in native format
} ;
static LPWSTR to_pwsz(const char *str) { // Convert UTF-8 string to Wide Character string.
static wchar_t wchar_buffer[256];
if (MultiByteToWideChar(CP_UTF8, 0, str, -1, wchar_buffer, 256) == 0)
return NULL;
return wchar_buffer;
}
static void quisk_reset_audio_device(struct sound_dev * dev)
{ // Contributed by Ben Cahill, AC2YD, February 2021
struct dev_data_t * DD = dev->device_data;
HRESULT hr;
hr = IAudioClient_Stop(DD->pAudioClient);
if (hr != S_OK) {
QuiskPrintf("%s: IAudioClient_Stop() returned hr = 0x%lx!!\n", dev->stream_description, hr);
}
hr = IAudioClient_Reset(DD->pAudioClient);
if (hr != S_OK) {
QuiskPrintf("%s: IAudioClient_Reset() returned hr = 0x%lx\n!!", dev->stream_description, hr);
}
hr = IAudioClient_Start(DD->pAudioClient);
if (hr != S_OK) {
QuiskPrintf("%s: IAudioClient_Start() returned hr = 0x%lx!!", dev->stream_description, hr);
}
}
static void close_device(struct sound_dev *, const char *);
static void open_wasapi_playback(struct sound_dev *, int);
static DWORD WINAPI playdevice_threadproc(LPVOID lpParameter) // Called by a special thread.
{ // Read samples from the playdevice threadproc buffer and write them to the sound card.
struct sound_dev * dev = (struct sound_dev *)lpParameter;
struct dev_data_t * DD = dev->device_data;
DWORD retval;
BYTE * pData;
BYTE * ptSamples;
int64_t nRead, nWrite;
int i, two, ch_I, ch_Q, frames_in_buffer, bytes_per_sample, bytes_per_frame, index, frames_to_end;
DWORD taskIndex = 0;
HANDLE hTask;
UINT32 write_frames;
UINT32 NumPaddingFrames;
if (CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) != S_OK)
QuiskPrintf("%s: CoInitializeEx failed\n", dev->stream_description);
hTask = AvSetMmThreadCharacteristics(TEXT("Pro Audio"), &taskIndex);
if (hTask == NULL)
QuiskPrintf("%s: Could not set thread to Pro Audio\n", dev->stream_description);
if (FAILED(IAudioClient_GetService(DD->pAudioClient, IID_IAUDIORENDERCLIENT, (void**)&DD->pRenderClient))) {
close_device(dev, "Could not create playback client");
return 1;
}
if (DD->sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE) {
// Load the buffer with silence before starting the stream.
if (SUCCEEDED(IAudioRenderClient_GetBuffer(DD->pRenderClient, DD->bufferSizeFrames, &pData)))
IAudioRenderClient_ReleaseBuffer(DD->pRenderClient, DD->bufferSizeFrames, AUDCLNT_BUFFERFLAGS_SILENT);
}
dev->handle = DD->pRenderClient;
if (FAILED(IAudioClient_Start(DD->pAudioClient))) {
close_device(dev, "Could not start");
return 1;
}
while (1) {
// Wait for next buffer event to be signaled.
retval = WaitForSingleObject(DD->hEvent, 1000);
if (retval != WAIT_OBJECT_0)
break;
if (DD == NULL || dev->handle == NULL)
break;
if (DD->sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE) {
write_frames = DD->bufferSizeFrames;
}
else {
if (IAudioClient_GetCurrentPadding(DD->pAudioClient, &NumPaddingFrames) != S_OK) {
QuiskPrintf("%s: playdevice: GetCurrentPadding failed\n", dev->stream_description);
dev->dev_error++;
continue;
}
write_frames = DD->bufferSizeFrames - NumPaddingFrames;
if (write_frames == 0)
continue;
}
if (IAudioRenderClient_GetBuffer(DD->pRenderClient, write_frames, &pData) != S_OK) {
QuiskPrintf("%s: playdevice: GetBuffer failed\n", dev->stream_description);
dev->dev_error++;
continue;
}
if (quisk_play_state < RECEIVE) { // Shutdown or Starting
DD->playbuf_underrun_reset = 0;
IAudioRenderClient_ReleaseBuffer(DD->pRenderClient, write_frames, AUDCLNT_BUFFERFLAGS_SILENT);
continue;
}
nRead = atomic_load(&DD->playbuf_nRead);
nWrite = atomic_load(&DD->playbuf_nWrite);
frames_in_buffer = nWrite - nRead;
if (DD->playbuf_underrun_reset) {
if (frames_in_buffer >= DD->playbuf_nFrames / 2) {
DD->playbuf_underrun_reset = 0;
}
else {
IAudioRenderClient_ReleaseBuffer(DD->pRenderClient, write_frames, AUDCLNT_BUFFERFLAGS_SILENT);
continue;
}
}
else if (frames_in_buffer < write_frames) {
DD->playbuf_underrun_reset = 1;
DD->playbuf_underrun_msg = 1;
dev->dev_underrun++;
IAudioRenderClient_ReleaseBuffer(DD->pRenderClient, write_frames, AUDCLNT_BUFFERFLAGS_SILENT);
continue;
}
two = dev->num_channels >= 2;
ch_I = dev->channel_I;
ch_Q = dev->channel_Q;
bytes_per_sample = dev->sample_bytes;
bytes_per_frame = bytes_per_sample * dev->num_channels;
if ((rxMode == CWU || rxMode == CWL) && quiskSpotLevel < 0 && dev->dev_index == t_MicPlayback) { // SoftRock Tx CW from wasapi
for (i = 0; i < write_frames; i++) {
ptSamples = quisk_make_txIQ(dev, 0);
memcpy(pData + ch_I * bytes_per_sample, ptSamples, bytes_per_sample);
if (two) {
ptSamples += bytes_per_sample;
memcpy(pData + ch_Q * bytes_per_sample, ptSamples, bytes_per_sample);
}
pData += bytes_per_frame;
}
}
else if (quisk_play_state > RECEIVE && quisk_active_sidetone == 1 && dev->dev_index == t_Playback) { // Sidetone from wasapi
for (i = 0; i < write_frames; i++) {
ptSamples = quisk_make_sidetone(dev, 0);
memcpy(pData + ch_I * bytes_per_sample, ptSamples, bytes_per_sample);
if (two)
memcpy(pData + ch_Q * bytes_per_sample, ptSamples, bytes_per_sample);
pData += bytes_per_frame;
}
}
else { // copy sound samples from the buffer to the sound device.
index = nRead % DD->playbuf_nFrames;
frames_to_end = DD->playbuf_nFrames - index;
ptSamples = DD->playbuf_buf + index * bytes_per_frame;
if (write_frames <= frames_to_end) {
memcpy(pData, ptSamples, write_frames * bytes_per_frame);
}
else {
memcpy(pData, ptSamples, frames_to_end * bytes_per_frame);
memcpy(pData + frames_to_end * bytes_per_frame,
DD->playbuf_buf, (write_frames - frames_to_end) * bytes_per_frame);
}
}
IAudioRenderClient_ReleaseBuffer(DD->pRenderClient, write_frames, 0);
nRead = nRead + write_frames; // update number of frames read
atomic_store(&DD->playbuf_nRead, nRead);
}
AvRevertMmThreadCharacteristics(hTask);
if (quisk_sound_state.verbose_sound)
QuiskPrintf("%s: Exit playback thread\n", dev->stream_description);
CoUninitialize();
return 0;
}
void quisk_write_wasapi(struct sound_dev * dev, int nSamples, complex double * cSamples, double volume)
{ // Called from Quisk by the sound thread. Write samples to the playdevice threadproc buffer.
// The ring buffer always stores samples as native frames.
struct dev_data_t * DD = dev->device_data;
int64_t nRead, nWrite;
int i, frames_in_buffer, index, bytes_per_frame, num_channels, ch_I, ch_Q;
int8_t * ptInt8;
int16_t * ptInt16;
int32_t * ptInt32;
float * ptFloat;
int32_t samp32;
unsigned char * buffer_end;
//QuiskPrintTime("write play buffer", 0);
//QuiskMeasureRate("write_wasapi", nSamples, 1, 1);
if (quisk_play_state < RECEIVE)
return;
if (DD == NULL || dev->handle == NULL)
return;
nRead = atomic_load(&DD->playbuf_nRead);
nWrite = atomic_load(&DD->playbuf_nWrite);
frames_in_buffer = nWrite - nRead;
dev->cr_average_fill += (double)(frames_in_buffer + nSamples / 2) / DD->playbuf_nFrames;
dev->cr_average_count++;
dev->dev_latency = frames_in_buffer + nSamples; // frames in buffer available to play
if (quisk_sound_state.verbose_sound) {
if (DD->playbuf_underrun_msg) {
DD->playbuf_underrun_msg = 0;
QuiskPrintf("%s: playbuf underflow\n", dev->stream_description);
}
}
if (DD->playbuf_overflow_reset) {
if (frames_in_buffer <= DD->playbuf_nFrames / 2) {
DD->playbuf_overflow_reset = 0;
if (quisk_sound_state.verbose_sound)
QuiskPrintf("%s: play_buffer overflow recovery frames_in_buffer %d, nSamples %d\n", dev->stream_description,
frames_in_buffer, nSamples);
}
else {
//QuiskPrintf("frames_in_buffer %d, nSamples %d\n", frames_in_buffer, nSamples);
return;
}
}
if (frames_in_buffer + nSamples >= DD->playbuf_nFrames) {
dev->dev_error++;
nSamples = DD->playbuf_nFrames * 9 / 10 - frames_in_buffer; // almost fill buffer
if (nSamples < 0) { // buffer is nearly full
DD->playbuf_overflow_reset = 1;
quisk_reset_audio_device(dev); // Ben Cahill: Correct fault in the play callback thread
if (quisk_sound_state.verbose_sound)
QuiskPrintf("%s: play_buffer overflow and reset, frames_in_buffer %d, nSamples %d\n", dev->stream_description, frames_in_buffer, nSamples);
}
else if (quisk_sound_state.verbose_sound) {
QuiskPrintf("%s: play_buffer overflow, frames_in_buffer %d, nSamples %d\n", dev->stream_description, frames_in_buffer, nSamples);
}
}
if (nSamples <= 0)
return;
ch_I = dev->channel_I;
ch_Q = dev->channel_Q;
bytes_per_frame = dev->sample_bytes * dev->num_channels;
num_channels = dev->num_channels;
index = nWrite % DD->playbuf_nFrames;
buffer_end = DD->playbuf_buf + DD->playbuf_nFrames * bytes_per_frame;
switch (dev->sound_format) {
case Int16:
ptInt16 = (int16_t *)(DD->playbuf_buf + index * bytes_per_frame);
for (i = 0; i < nSamples; i++) { // for each frame
ptInt16[ch_I] = volume * creal(cSamples[i]) / 65536;
ptInt16[ch_Q] = volume * cimag(cSamples[i]) / 65536;
ptInt16 += num_channels;
if ((unsigned char *)ptInt16 >= buffer_end) {
ptInt16 = (int16_t *)DD->playbuf_buf;
}
}
break;
case Int24: // only works for little-endian
ptInt8 = (int8_t *)(DD->playbuf_buf + index * bytes_per_frame);
for (i = 0; i < nSamples; i++) {
samp32 = volume * creal(cSamples[i]);
memcpy(ptInt8 + ch_I * 3, (int8_t *)&samp32 + 1, 3);
samp32 = volume * cimag(cSamples[i]);
memcpy(ptInt8 + ch_Q * 3, (int8_t *)&samp32 + 1, 3);
ptInt8 += bytes_per_frame;
if ((unsigned char *)ptInt8 >= buffer_end) {
ptInt8 = (int8_t *)DD->playbuf_buf;
}
}
break;
case Int32:
ptInt32 = (int32_t *)(DD->playbuf_buf + index * bytes_per_frame);
for (i = 0; i < nSamples; i++) {
ptInt32[ch_I] = volume * creal(cSamples[i]);
ptInt32[ch_Q] = volume * cimag(cSamples[i]);
ptInt32 += num_channels;
if ((unsigned char *)ptInt32 >= buffer_end) {
ptInt32 = (int32_t *)DD->playbuf_buf;
}
}
break;
case Float32:
ptFloat = (float *)(DD->playbuf_buf + index * bytes_per_frame);
for (i = 0; i < nSamples; i++) {
ptFloat[ch_I] = volume * creal(cSamples[i]) / CLIP32;
ptFloat[ch_Q] = volume * cimag(cSamples[i]) / CLIP32;
ptFloat += num_channels;
if ((unsigned char *)ptFloat >= buffer_end) {
ptFloat = (float *)DD->playbuf_buf;
}
}
break;
}
nWrite = nWrite + nSamples;
atomic_store(&DD->playbuf_nWrite, nWrite);
}
int quisk_read_wasapi(struct sound_dev * dev, complex double * cSamples)
{ // Called from Quisk by the sound thread. Read samples from the sound card capture buffer.
// cSamples can be NULL to discard samples.
struct dev_data_t * DD = dev->device_data;
int i, bytes_per_frame, num_channels, ch_I, ch_Q;
BYTE * pData;
UINT32 numFramesAvailable;
DWORD flags;
double samp_r, samp_i;
int8_t * ptInt8;
int16_t * ptInt16;
int32_t * ptInt32;
float * ptFloat32;
int32_t iReal, iImag;
int nSamples;
HRESULT hr;
if (DD == NULL || dev->handle == NULL)
return 0;
nSamples = 0;
ch_I = dev->channel_I;
ch_Q = dev->channel_Q;
bytes_per_frame = dev->sample_bytes * dev->num_channels;
num_channels = dev->num_channels;
while (1) { // Get the available data
hr = IAudioCaptureClient_GetBuffer(DD->pCaptureClient, &pData, &numFramesAvailable, &flags, NULL, NULL);
if (hr == AUDCLNT_S_BUFFER_EMPTY) {
break; // always use non-blocking read
}
if (hr != S_OK) { // Error
dev->dev_error++;
QuiskPrintf("%s: Error processing buffer\n", dev->stream_description);
break;
}
// There is data to read
if (cSamples) {
if (flags & AUDCLNT_BUFFERFLAGS_SILENT) {
for (i = 0; i < numFramesAvailable; i++)
cSamples[nSamples++] = 0;
}
else switch (dev->sound_format) {
case Int16:
ptInt16 = (int16_t *)pData;
for (i = 0; i < numFramesAvailable; i++) {
samp_r = ptInt16[ch_I];
samp_i = ptInt16[ch_Q];
cSamples[nSamples++] = (samp_r + I * samp_i) * CLIP16;
ptInt16 += num_channels;
}
break;
case Int24: // only works for little-endian
ptInt8 = (int8_t *)pData;
for (i = 0; i < numFramesAvailable; i++) {
iReal = 0;
memcpy((int8_t *)&iReal + 1, ptInt8 + ch_I * 3, 3);
iImag = 0;
memcpy((int8_t *)&iImag + 1, ptInt8 + ch_Q * 3, 3);
cSamples[nSamples++] = (iReal + I * iImag);
ptInt8 += bytes_per_frame;
}
break;
case Int32:
ptInt32 = (int32_t *)pData;
for (i = 0; i < numFramesAvailable; i++) {
samp_r = ptInt32[ch_I];
samp_i = ptInt32[ch_Q];
cSamples[nSamples++] = (samp_r + I * samp_i);
ptInt32 += num_channels;
}
break;
case Float32:
ptFloat32 = (float *)pData;
for (i = 0; i < numFramesAvailable; i++) {
samp_r = ptFloat32[ch_I];
samp_i = ptFloat32[ch_Q];
cSamples[nSamples++] = (samp_r + I * samp_i) * CLIP32;
ptFloat32 += num_channels;
}
break;
}
}
if (FAILED(IAudioCaptureClient_ReleaseBuffer(DD->pCaptureClient, numFramesAvailable))) {
dev->dev_error++;
QuiskPrintf("%s: Failure to release buffer\n", dev->stream_description);
break;
}
}
dev->dev_latency = nSamples;
return nSamples;
}
#if 0
static void MeasureTimerPrecision(void)
{
DWORD t1, t2;
int state = 1;
t1 = timeGetTime();
while (state) {
switch (state) {
case 1:
t1 = timeGetTime();
state = 2;
break;
case 2:
t2 = timeGetTime();
if (t1 != t2) {
t1 = t2;
state = 3;
}
break;
case 3:
t2 = timeGetTime();
if (t1 != t2) {
QuiskPrintf("MeasureTimerPrecision %d millisec\n", t2 - t1);
t1 = t2;
state = 4;
}
break;
case 4:
t2 = timeGetTime();
if (t1 != t2) {
QuiskPrintf("MeasureTimerPrecision %d millisec\n", t2 - t1);
t1 = t2;
state = 0;
}
break;
}
}
}
#endif
void quisk_play_wasapi(struct sound_dev * dev, int nSamples, complex double * cSamples, double volume)
{ // Called from Quisk by the sound thread. Write samples to the sound card buffer.
UINT32 numFramesPadding;
BYTE *pData;
int8_t * ptb;
int16_t * pts;
int32_t * ptl;
float * ptf;
int32_t samp32;
struct dev_data_t * DD = dev->device_data;
double buffer_fill;
int n, frames, bytes_per_frame;
if (DD == NULL || dev->handle == NULL)
return;
if (FAILED(IAudioClient_GetCurrentPadding(DD->pAudioClient, &numFramesPadding))) {
QuiskPrintf("%s: quisk_play_wasapi failed to get padding\n", dev->stream_description);
return;
}
buffer_fill = (double)(numFramesPadding + nSamples / 2) / DD->bufferSizeFrames;
dev->cr_average_fill += buffer_fill;
dev->cr_average_count++;
dev->dev_latency = numFramesPadding + nSamples; // frames in buffer available to play
switch(dev->started) {
case 0: // Starting state; wait for samples to become regular
if (quisk_play_state < RECEIVE) {
frames = DD->bufferSizeFrames / 2 - (int)numFramesPadding;
if (frames > 0 && SUCCEEDED(IAudioRenderClient_GetBuffer(DD->pRenderClient, frames, &pData))) {
IAudioRenderClient_ReleaseBuffer(DD->pRenderClient, frames, AUDCLNT_BUFFERFLAGS_SILENT);
}
return;
}
dev->started = 1;
if (quisk_sound_state.verbose_sound)
QuiskPrintf("%s: Starting\n", dev->stream_description);
// FALL THRU
case 1: // Normal run state
// Check for underrun
if (numFramesPadding <= dev->sample_rate / 200) { // mimumum play time in buffer
quisk_sound_state.underrun_error++;
dev->dev_underrun++;
if (quisk_sound_state.verbose_sound)
QuiskPrintf ("%s: Underrun error\n", dev->stream_description);
for (nSamples = 0; nSamples < DD->bufferSizeFrames / 2; nSamples++)
cSamples[nSamples] = 0; // fill with silence
buffer_fill = 0.5;
}
// Check if play buffer is too full.
if (numFramesPadding + nSamples >= DD->bufferSizeFrames) {
quisk_sound_state.write_error++;
dev->dev_error++;
if (quisk_sound_state.verbose_sound)
QuiskPrintf ("%s: Buffer too full\n", dev->stream_description);
nSamples = 0;
dev->started = 2;
}
break;
case 2: // Buffer is too full; wait for it to drain
if (buffer_fill <= 0.5) {
dev->started = 1;
if (quisk_sound_state.verbose_sound)
QuiskPrintf("%s: Resume adding samples\n", dev->stream_description);
}
else {
nSamples = 0;
}
break;
}
if (nSamples <= 0)
return;
if (FAILED(IAudioRenderClient_GetBuffer(DD->pRenderClient, nSamples, &pData))) {
QuiskPrintf("%s: quisk_play_wasapi failed get the buffer\n", dev->stream_description);
return;
}
switch (dev->sound_format) {
case Int16:
pts = (int16_t *)pData;
for (n = 0; n < nSamples; n++) {
pts[dev->channel_I] = (int16_t)(volume * creal(cSamples[n]) / 65536);
pts[dev->channel_Q] = (int16_t)(volume * cimag(cSamples[n]) / 65536);
pts += dev->num_channels;
}
break;
case Int32:
ptl = (int32_t *)pData;
for (n = 0; n < nSamples; n++) {
ptl[dev->channel_I] = (int32_t)(volume * creal(cSamples[n]));
ptl[dev->channel_Q] = (int32_t)(volume * cimag(cSamples[n]));
ptl += dev->num_channels;
}
break;
case Float32:
ptf = (float *)pData;
for (n = 0; n < nSamples; n++) {
ptf[dev->channel_I] = (volume * creal(cSamples[n]) / CLIP32);
ptf[dev->channel_Q] = (volume * cimag(cSamples[n]) / CLIP32);
ptf += dev->num_channels;
}
break;
case Int24: // only works for little-endian
ptb = (int8_t *)pData;
bytes_per_frame = dev->sample_bytes * dev->num_channels;
for (n = 0; n < nSamples; n++) {
samp32 = volume * creal(cSamples[n]);
memcpy(ptb + dev->channel_I * 3, (int8_t *)&samp32 + 1, 3);
samp32 = volume * cimag(cSamples[n]);
memcpy(ptb + dev->channel_Q * 3, (int8_t *)&samp32 + 1, 3);
ptb += bytes_per_frame;
}
break;
}
IAudioRenderClient_ReleaseBuffer(DD->pRenderClient, nSamples, 0);
}
static void MakeWFext(int extensible, sound_format_t sound_format, WORD nChannels, struct sound_dev * dev, WAVEFORMATEXTENSIBLE * pwfex)
{ // fill in a WAVEFORMATEXTENSIBLE structure
dev->sound_format = sound_format;
switch (sound_format) {
case Int16:
dev->use_float = 0;
dev->sample_bytes = 2;
break;
case Int24:
dev->use_float = 0;
dev->sample_bytes = 3;
break;
case Int32:
dev->use_float = 0;
dev->sample_bytes = 4;
break;
case Float32:
dev->use_float = 1;
dev->sample_bytes = 4;
break;
}
if (extensible) {
pwfex->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
pwfex->Format.cbSize = 22;
pwfex->Samples.wValidBitsPerSample = dev->sample_bytes * 8;
switch (nChannels) {
case 1:
pwfex->dwChannelMask = 0x01;
break;
case 2:
pwfex->dwChannelMask = 0x03;
break;
case 3:
pwfex->dwChannelMask = 0x07;
break;
case 4:
pwfex->dwChannelMask = 0x0F;
break;
case 5:
pwfex->dwChannelMask = 0x1F;
break;
case 6:
pwfex->dwChannelMask = 0x3F;
break;
case 7:
default:
pwfex->dwChannelMask = 0x7F;
break;
}
if (sound_format == Float32)
pwfex->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
else
pwfex->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
}
else {
pwfex->Format.cbSize = 0;
if (sound_format == Float32)
pwfex->Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
else
pwfex->Format.wFormatTag = WAVE_FORMAT_PCM;
}
pwfex->Format.nChannels = nChannels;
pwfex->Format.nSamplesPerSec = dev->sample_rate;
pwfex->Format.nAvgBytesPerSec = nChannels * dev->sample_rate * dev->sample_bytes;
pwfex->Format.nBlockAlign = nChannels * dev->sample_bytes;
pwfex->Format.wBitsPerSample = dev->sample_bytes * 8;
}
static int make_format(struct sound_dev * dev, WAVEFORMATEXTENSIBLE * pWaveFormat, AUDCLNT_SHAREMODE sharemode)
{
struct dev_data_t * DD = dev->device_data;
WAVEFORMATEX * pEx = (WAVEFORMATEX *)pWaveFormat;
WAVEFORMATEX * pClosestMatch;
WORD nChannels;
sound_format_t sound_format;
HRESULT result;
nChannels = dev->num_channels;
for (sound_format = 0; sound_format <= Int24; sound_format++) {
MakeWFext(1, sound_format, nChannels, dev, pWaveFormat);
if (sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE)
result = IAudioClient_IsFormatSupported(DD->pAudioClient, sharemode, pEx, NULL);
else {
pClosestMatch = NULL;
result = IAudioClient_IsFormatSupported(DD->pAudioClient, sharemode, pEx, &pClosestMatch);
CoTaskMemFree(pClosestMatch);
}
if (result == S_OK)
return 1;
}
if (dev->dev_index == t_Playback || dev->dev_index == t_MicCapture) { // mono device is OK for radio sound or mic
for (sound_format = 0; sound_format <= Int24; sound_format++) {
MakeWFext(1, sound_format, 1, dev, pWaveFormat);
if (sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE)
result = IAudioClient_IsFormatSupported(DD->pAudioClient, sharemode, pEx, NULL);
else {
pClosestMatch = NULL;
result = IAudioClient_IsFormatSupported(DD->pAudioClient, sharemode, pEx, &pClosestMatch);
CoTaskMemFree(pClosestMatch);
}
if (result == S_OK) {
dev->num_channels = 1;
dev->channel_I = 0;
dev->channel_Q = 0;
return 1;
}
}
}
// Try to open with more channels
for (nChannels = dev->num_channels + 1; nChannels <= 7; nChannels++) {
for (sound_format = 0; sound_format <= Int24; sound_format++) {
MakeWFext(1, sound_format, nChannels, dev, pWaveFormat);
if (sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE)
result = IAudioClient_IsFormatSupported(DD->pAudioClient, sharemode, pEx, NULL);
else {
pClosestMatch = NULL;
result = IAudioClient_IsFormatSupported(DD->pAudioClient, sharemode, pEx, &pClosestMatch);
CoTaskMemFree(pClosestMatch);
}
if (result == S_OK) {
dev->num_channels = nChannels;
return 1;
}
}
}
return 0;
}
static void close_device(struct sound_dev * dev, const char * msg)
{
struct dev_data_t * device_data = dev->device_data;
if (msg) {
snprintf(dev->dev_errmsg, QUISK_SC_SIZE, "%s: %.80s", msg, dev->name);
if (quisk_sound_state.verbose_sound)
QuiskPrintf("%s\n", dev->dev_errmsg);
}
if (device_data->pAudioClient)
IAudioClient_Stop(device_data->pAudioClient);
if (device_data->pCaptureClient)
IAudioCaptureClient_Release(device_data->pCaptureClient);
if (device_data->pRenderClient)
IAudioRenderClient_Release(device_data->pRenderClient);
if (device_data->pAudioClient)
IAudioClient_Release(device_data->pAudioClient);
if (device_data->pDevice)
IMMDevice_Release(device_data->pDevice);
if (device_data->playbuf_buf)
free(device_data->playbuf_buf);
if (device_data->hEvent)
CloseHandle(device_data->hEvent);
free(device_data);
dev->device_data = NULL;
}
static void open_wasapi_capture(struct sound_dev * dev)
{
REFERENCE_TIME hnsRequestedDuration;
REFERENCE_TIME def_period, min_period;
WAVEFORMATEXTENSIBLE wave_format;
LPWSTR pwszID;
struct dev_data_t * DD = dev->device_data;
HRESULT hr;
DWORD err_code;
dev->dev_errmsg[0] = 0;
if (quisk_sound_state.verbose_sound)
QuiskPrintf("Opening Wasapi capture device %s\n Name %s\n Device name %s\n", dev->stream_description, dev->name, dev->device_name);
if (pEnumerator == NULL)
return;
pwszID = to_pwsz(dev->device_name);
if (pwszID == NULL) {
err_code = GetLastError();
snprintf(dev->dev_errmsg, QUISK_SC_SIZE, "Failure 0x%lX to convert device name: %.80s", err_code, dev->device_name);
if (quisk_sound_state.verbose_sound)
QuiskPrintf("%s\n", dev->dev_errmsg);
close_device(dev, NULL);
return;
}
if (FAILED(IMMDeviceEnumerator_GetDevice(pEnumerator, pwszID, &DD->pDevice))) {
close_device(dev, "Sound device not found");
return;
}
if (FAILED(IMMDevice_Activate(DD->pDevice, IID_IAUDIOCLIENT, CLSCTX_ALL, NULL, (void**)&DD->pAudioClient))) {
close_device(dev, "No audio client");
return;
}
if (SUCCEEDED(IAudioClient_GetDevicePeriod(DD->pAudioClient, &def_period, &min_period))) {
if (quisk_sound_state.verbose_sound)
QuiskPrintf(" Device default period %d, min period %d microsec\n",
(int)def_period / REFTIMES_PER_MICROSEC, (int)min_period / REFTIMES_PER_MICROSEC);
}
if (make_format(dev, &wave_format, AUDCLNT_SHAREMODE_EXCLUSIVE) == 0) { // failure
if (make_format(dev, &wave_format, AUDCLNT_SHAREMODE_SHARED) == 0) { // failure
close_device(dev, "Device can not support the sample rate or number of channels.");
return;
}
DD->sharemode = AUDCLNT_SHAREMODE_SHARED;
if (quisk_sound_state.verbose_sound)
QuiskPrintf(" Exclusive mode refused. Open device in Shared mode\n");
}
else {
DD->sharemode = AUDCLNT_SHAREMODE_EXCLUSIVE;
if (quisk_sound_state.verbose_sound)
QuiskPrintf(" Open device in Exclusive mode\n");
}
if (quisk_sound_state.verbose_sound) {
QuiskPrintf(" Sample rate %d\n Channel_I %d\n Channel_Q %d\n",
dev->sample_rate, dev->channel_I, dev->channel_Q);
QuiskPrintf(" Sound device format %s\n", sound_format_names[dev->sound_format]);
QuiskPrintf(" Number of channels %d, bytes per sample %d\n", dev->num_channels, dev->sample_bytes);
}
hnsRequestedDuration = REFTIMES_PER_MILLISEC * quisk_sound_state.latency_millisecs;
hr = IAudioClient_Initialize(DD->pAudioClient, DD->sharemode, 0, hnsRequestedDuration, 0, (WAVEFORMATEX *)&wave_format, NULL);
if (FAILED(hr)) {
for ( ;hnsRequestedDuration > REFTIMES_PER_MILLISEC * 20; hnsRequestedDuration -= (REFTIMES_PER_MILLISEC * 20)) {
hr = IAudioClient_Initialize(DD->pAudioClient, DD->sharemode, 0, hnsRequestedDuration, 0, (WAVEFORMATEX *)&wave_format, NULL);
if (hr == S_OK)
break;
}
}
if (FAILED(hr)) {
close_device(dev, "Could not initialize");
return;
}
// Get the size of the allocated buffer.
if (FAILED(IAudioClient_GetBufferSize(DD->pAudioClient, &DD->bufferSizeFrames))) {
close_device(dev, "Could not get buffer size");
return;
}
dev->play_buf_size = DD->bufferSizeFrames * dev->sample_bytes * dev->num_channels;
if (FAILED(IAudioClient_GetService(DD->pAudioClient, IID_IAUDIOCAPTURECLIENT, (void**)&DD->pCaptureClient))) {
close_device(dev, "Could not create capture client");
return;
}
dev->handle = DD->pCaptureClient;
if (FAILED(IAudioClient_Start(DD->pAudioClient))) {
close_device(dev, "Could not start");
return;
}
if (quisk_sound_state.verbose_sound) {
QuiskPrintf(" Capture Wasapi buffer size %d frames\n Started.\n", DD->bufferSizeFrames);
}
}
static void open_wasapi_playback(struct sound_dev * dev, int use_callback)
{
REFERENCE_TIME hnsRequestedDuration;
REFERENCE_TIME def_period, min_period;
WAVEFORMATEXTENSIBLE wave_format;
int i;
BYTE *pData;
LPWSTR pwszID;
struct dev_data_t * DD = dev->device_data;
HRESULT hr = S_OK;
HANDLE hThread = NULL;
DWORD ThreadID;
DWORD err_code;
dev->dev_errmsg[0] = 0;
if (quisk_sound_state.verbose_sound) {
QuiskPrintf("Opening Wasapi playback device %s\n Name %s\n Device name %s\n", dev->stream_description, dev->name, dev->device_name);
}
if (pEnumerator == NULL)
return;
pwszID = to_pwsz(dev->device_name);
if (pwszID == NULL) {
err_code = GetLastError();
snprintf(dev->dev_errmsg, QUISK_SC_SIZE, "Failure 0x%lX to convert device name: %.80s", err_code, dev->device_name);
if (quisk_sound_state.verbose_sound)
QuiskPrintf("%s\n", dev->dev_errmsg);
close_device(dev, NULL);
return;
}
if (FAILED(IMMDeviceEnumerator_GetDevice(pEnumerator, pwszID, &DD->pDevice))) {
close_device(dev, "Sound device not found");
return;
}
if (FAILED(IMMDevice_Activate(DD->pDevice, IID_IAUDIOCLIENT, CLSCTX_ALL, NULL, (void**)&DD->pAudioClient))) {
close_device(dev, "No audio client");
return;
}
if (SUCCEEDED(IAudioClient_GetDevicePeriod(DD->pAudioClient, &def_period, &min_period))) {
if (quisk_sound_state.verbose_sound)
QuiskPrintf(" Device default period %d, min period %d microsec\n",
(int)def_period / REFTIMES_PER_MICROSEC, (int)min_period / REFTIMES_PER_MICROSEC);
}
if (make_format(dev, &wave_format, AUDCLNT_SHAREMODE_EXCLUSIVE) == 0) { // failure
if (make_format(dev, &wave_format, AUDCLNT_SHAREMODE_SHARED) == 0) { // failure
close_device(dev, "Device can not support the sample rate or number of channels.");
return;
}
DD->sharemode = AUDCLNT_SHAREMODE_SHARED;
if (quisk_sound_state.verbose_sound)
QuiskPrintf(" Exclusive mode refused. Open device in Shared mode\n");
}
else {
DD->sharemode = AUDCLNT_SHAREMODE_EXCLUSIVE;
if (quisk_sound_state.verbose_sound)
QuiskPrintf(" Open device in Exclusive mode\n");
}
if (quisk_sound_state.verbose_sound) {
QuiskPrintf(" Sample rate %d\n Channel_I %d\n Channel_Q %d\n",
dev->sample_rate, dev->channel_I, dev->channel_Q);
QuiskPrintf(" Sound device format %s\n", sound_format_names[dev->sound_format]);
QuiskPrintf(" Number of channels %d, bytes per sample %d\n", dev->num_channels, dev->sample_bytes);
//MeasureTimerPrecision();
}
if (use_callback) {
if (DD->sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE) {
hnsRequestedDuration = REFTIMES_PER_MICROSEC * quisk_sound_state.data_poll_usec;
if (hnsRequestedDuration < min_period)
hnsRequestedDuration = min_period;
}
else {
hnsRequestedDuration = 0;
}
hr = IAudioClient_Initialize(DD->pAudioClient, DD->sharemode, AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
hnsRequestedDuration, hnsRequestedDuration, (WAVEFORMATEX *)&wave_format, NULL);
if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) {
if (quisk_sound_state.verbose_sound)
QuiskPrintf(" Fix for buffer size not aligned\n");
// Get the size of the aligned buffer.
if (FAILED(IAudioClient_GetBufferSize(DD->pAudioClient, &DD->bufferSizeFrames))) {
close_device(dev, "Could not get buffer size");
return;
}
hnsRequestedDuration = (REFERENCE_TIME)((10000.0 * 1000 / wave_format.Format.nSamplesPerSec * DD->bufferSizeFrames) + 0.5);
IAudioClient_Release(DD->pAudioClient);
if (FAILED(IMMDevice_Activate(DD->pDevice, IID_IAUDIOCLIENT, CLSCTX_ALL, NULL, (void**)&DD->pAudioClient))) {
close_device(dev, "No audio client");
return;
}
hr = IAudioClient_Initialize(DD->pAudioClient, DD->sharemode, AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
hnsRequestedDuration, hnsRequestedDuration, (WAVEFORMATEX *)&wave_format, NULL);
}
if (hr != S_OK) {
close_device(dev, "Could not initialize");
return;
}
// Get the size of the allocated buffer.
if (FAILED(IAudioClient_GetBufferSize(DD->pAudioClient, &DD->bufferSizeFrames))) {
close_device(dev, "Could not get buffer size");
return;
}
// Create an event handle and register it for buffer-event notifications.
DD->hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (DD->hEvent == NULL) {
close_device(dev, "CreateEvent failed");
return;
}
if (FAILED(IAudioClient_SetEventHandle(DD->pAudioClient, DD->hEvent))) {
close_device(dev, "SetEventHandle failed");
return;
}
i = dev->latency_frames * 2 / DD->bufferSizeFrames; // playbuf_nFrames is a multiple of bufferSizeFrames
i = ((i + 1) / 2) * 2; // multiple is an even number
if ( i < 4)
i = 4;
DD->playbuf_nFrames = i * DD->bufferSizeFrames;
DD->playbuf_buf = calloc(DD->playbuf_nFrames * dev->sample_bytes * dev->num_channels, 1);
atomic_store(&DD->playbuf_nWrite, i / 2 * DD->bufferSizeFrames); // buffer starts half full
atomic_store(&DD->playbuf_nRead, 0);
DD->playbuf_underrun_reset = 0;
DD->playbuf_underrun_msg = 0;
DD->playbuf_overflow_reset = 0;
hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) playdevice_threadproc, dev, CREATE_SUSPENDED, &ThreadID);
if (hThread == NULL ) {
close_device(dev, "Create thread failed");
return;
}
dev->play_buf_size = DD->playbuf_nFrames;
if (quisk_sound_state.verbose_sound) {
QuiskPrintf(" Wasapi buffer size %d frames\n", DD->bufferSizeFrames);
QuiskPrintf(" Callback ring buffer size %d frames\n", DD->playbuf_nFrames);
QuiskPrintf(" Starting the callback thread for %s\n", dev->stream_description);
}
ResumeThread(hThread);
}
else {
hnsRequestedDuration = quisk_sound_state.latency_millisecs * REFTIMES_PER_MILLISEC * 2; // reduce this if too large
for ( ;hnsRequestedDuration > REFTIMES_PER_MILLISEC * 20; hnsRequestedDuration -= (REFTIMES_PER_MILLISEC * 20)) {
hr = IAudioClient_Initialize(DD->pAudioClient, DD->sharemode, 0,
hnsRequestedDuration, 0, (WAVEFORMATEX *)&wave_format, NULL);
if (hr == S_OK)
break;
}
if (FAILED(hr)) {
close_device(dev, "Could not initialize");
return;
}
// Get the size of the allocated buffer.
if (FAILED(IAudioClient_GetBufferSize(DD->pAudioClient, &DD->bufferSizeFrames))) {
close_device(dev, "Could not get buffer size");
return;
}
dev->play_buf_size = DD->bufferSizeFrames;
if (FAILED(IAudioClient_GetService(DD->pAudioClient, IID_IAUDIORENDERCLIENT, (void**)&DD->pRenderClient))) {
close_device(dev, "Could not create playback client");
return;
}
// Load the buffer with silence before starting the stream.
if (SUCCEEDED(IAudioRenderClient_GetBuffer(DD->pRenderClient, DD->bufferSizeFrames / 2, &pData)))
IAudioRenderClient_ReleaseBuffer(DD->pRenderClient, DD->bufferSizeFrames / 2, AUDCLNT_BUFFERFLAGS_SILENT);
dev->handle = DD->pRenderClient;
if (FAILED(IAudioClient_Start(DD->pAudioClient))) {
close_device(dev, "Could not start");
return;
}
if (quisk_sound_state.verbose_sound) {
QuiskPrintf(" Playback Wasapi buffer size %d frames\n", DD->bufferSizeFrames);
QuiskPrintf(" Starting playback for %s\n", dev->stream_description);
}
}
}
void quisk_start_sound_wasapi (struct sound_dev ** pCapture, struct sound_dev ** pPlayback)
{ // Open the sound devices and start them. Called from the sound thread.
struct sound_dev * pDev;
if (CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) != S_OK)
QuiskPrintf("CoInitializeEx failed\n");
if (FAILED(CoCreateInstance(CLSID_MMDEVICEENUMERATOR, NULL, CLSCTX_ALL, IID_IMMDEVICEENUMERATOR, (void**)&pEnumerator))) {
QuiskPrintf("CoCreateInstance failed in start_sound_wasapi\n");
pEnumerator = NULL;
return;
}
while (*pCapture) {
pDev = *pCapture++;
if (pDev->driver == DEV_DRIVER_WASAPI) {
pDev->device_data = calloc(sizeof(struct dev_data_t), 1);
open_wasapi_capture(pDev);
}
}
while (*pPlayback) {
pDev = *pPlayback++;
if (pDev->driver == DEV_DRIVER_WASAPI) {
pDev->device_data = calloc(sizeof(struct dev_data_t), 1);
open_wasapi_playback(pDev, 0);
}
else if (pDev->driver == DEV_DRIVER_WASAPI2) {
pDev->device_data = calloc(sizeof(struct dev_data_t), 1);
open_wasapi_playback(pDev, 1);
}
}
}
void quisk_close_sound_wasapi(struct sound_dev ** pCapture, struct sound_dev ** pPlayback)
{
struct sound_dev * pDev;
while (*pCapture) {
pDev = *pCapture++;
if (pDev->driver == DEV_DRIVER_WASAPI && pDev->device_data) {
pDev->handle = NULL;
Sleep(200);
close_device(pDev, NULL);
if (quisk_sound_state.verbose_sound)
QuiskPrintf("Close %s\n", pDev->stream_description);
}
}
while (*pPlayback) {
pDev = *pPlayback++;
if ((pDev->driver == DEV_DRIVER_WASAPI || pDev->driver == DEV_DRIVER_WASAPI2) && pDev->device_data) {
pDev->handle = NULL;
Sleep(200);
close_device(pDev, NULL);
if (quisk_sound_state.verbose_sound)
QuiskPrintf("Close %s\n", pDev->stream_description);
}
}
if (pEnumerator)
IMMDeviceEnumerator_Release(pEnumerator);
pEnumerator = NULL;
CoUninitialize();
}
PyObject * quisk_wasapi_sound_devices(PyObject * self, PyObject * args) // Called from the GUI thread
{ // Return a list of sound device data [pycapt, pyplay]
PyObject * pylist, * pycapt, * pyplay, * pytup;
HRESULT hr = S_OK;
IMMDeviceEnumerator *pEnum = NULL;
IMMDeviceCollection *pCollection = NULL;
IMMDevice *pEndpoint = NULL;
IPropertyStore *pProps = NULL;
PROPVARIANT varName;
LPWSTR pwszID = NULL;
UINT i, count;
if (!PyArg_ParseTuple (args, ""))
return NULL;
pylist = PyList_New(0); // list [pycapt, pyplay]
pycapt = PyList_New(0); // list of capture devices (name, id, is_raw)
pyplay = PyList_New(0); // list of play devices
PyList_Append(pylist, pycapt);
PyList_Append(pylist, pyplay);
hr = CoCreateInstance(CLSID_MMDEVICEENUMERATOR, NULL, CLSCTX_ALL, IID_IMMDEVICEENUMERATOR, (void**)&pEnum);
if (FAILED(hr)) {
QuiskPrintf("CoCreateInstance failed in wasapi_sound_devices\n");
return pylist;
}
hr = IMMDeviceEnumerator_EnumAudioEndpoints(pEnum, eRender, DEVICE_STATE_ACTIVE, &pCollection);
EXIT_ON_ERROR(hr)
hr = IMMDeviceCollection_GetCount(pCollection, &count);
EXIT_ON_ERROR(hr)
for (i = 0; i < count; i++) { // Get pointer to eRender endpoint number i.
hr = IMMDeviceCollection_Item(pCollection, i, &pEndpoint);
EXIT_ON_ERROR(hr)
hr = IMMDevice_GetId(pEndpoint, &pwszID);
EXIT_ON_ERROR(hr)
hr = IMMDevice_OpenPropertyStore(pEndpoint, STGM_READ, &pProps);
EXIT_ON_ERROR(hr)
PropVariantInit(&varName);
hr = IPropertyStore_GetValue(pProps, &PKEY_Device_FriendlyName, &varName);
EXIT_ON_ERROR(hr)
// data items are (name, id, is_raw)
pytup = PyTuple_New(3);
PyList_Append(pyplay, pytup);
PyTuple_SET_ITEM(pytup, 0, PyUnicode_FromWideChar(varName.pwszVal, -1));
PyTuple_SET_ITEM(pytup, 1, PyUnicode_FromWideChar(pwszID, -1));
PyTuple_SET_ITEM(pytup, 2, PyInt_FromLong(1));
CoTaskMemFree(pwszID);
pwszID = NULL;
PropVariantClear(&varName);
IPropertyStore_Release(pProps);
pProps = NULL;
IMMDevice_Release(pEndpoint);
pEndpoint = NULL;
}
IMMDeviceCollection_Release(pCollection);
pCollection = NULL;
hr = IMMDeviceEnumerator_EnumAudioEndpoints(pEnum, eCapture, DEVICE_STATE_ACTIVE, &pCollection);
EXIT_ON_ERROR(hr)
hr = IMMDeviceCollection_GetCount(pCollection, &count);
EXIT_ON_ERROR(hr)
for (i = 0; i < count; i++) { // Get pointer to eCapture endpoint number i.
hr = IMMDeviceCollection_Item(pCollection, i, &pEndpoint);
EXIT_ON_ERROR(hr)
hr = IMMDevice_GetId(pEndpoint, &pwszID);
EXIT_ON_ERROR(hr)
hr = IMMDevice_OpenPropertyStore(pEndpoint, STGM_READ, &pProps);
EXIT_ON_ERROR(hr)
PropVariantInit(&varName);
hr = IPropertyStore_GetValue(pProps, &PKEY_Device_FriendlyName, &varName);
EXIT_ON_ERROR(hr)
// data items are (name, id, is_raw)
pytup = PyTuple_New(3);
PyList_Append(pycapt, pytup);
PyTuple_SET_ITEM(pytup, 0, PyUnicode_FromWideChar(varName.pwszVal, -1));
PyTuple_SET_ITEM(pytup, 1, PyUnicode_FromWideChar(pwszID, -1));
PyTuple_SET_ITEM(pytup, 2, PyInt_FromLong(1));
CoTaskMemFree(pwszID);
pwszID = NULL;
PropVariantClear(&varName);
IPropertyStore_Release(pProps);
pProps = NULL;
IMMDevice_Release(pEndpoint);
pEndpoint = NULL;
}
IMMDeviceCollection_Release(pCollection);
IMMDeviceEnumerator_Release(pEnum);
return pylist;
Exit:
if (pwszID)
CoTaskMemFree(pwszID);
if (pCollection)
IMMDeviceCollection_Release(pCollection);
if (pEndpoint)
IMMDevice_Release(pEndpoint);
if (pProps)
IPropertyStore_Release(pProps);
if (pEnum)
IMMDeviceEnumerator_Release(pEnum);
return pylist;
}
/****** MIDI ******/
static void midi_in_devices(PyObject * pylist)
{ // Return a list of MIDI In devices.
UINT nMidiDeviceNum;
MIDIINCAPS caps;
UINT i;
nMidiDeviceNum = midiInGetNumDevs();
for (i = 0; i < nMidiDeviceNum; ++i) {
if (midiInGetDevCaps(i, &caps, sizeof(MIDIINCAPS)) == MMSYSERR_NOERROR)
PyList_Append(pylist, PyUnicode_FromWideChar(caps.szPname, -1));
}
}
#define MIDI_MAX 6000
static char midi_chars[MIDI_MAX];
static int midi_length;
static HANDLE MIDI_mutex;
static void CALLBACK MidiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
{
int status, note, velocity;
if (wMsg == MIM_DATA) {
status = dwParam1 & 0xFF;
note = (dwParam1 >> 8) & 0x7F;
velocity = (dwParam1 >> 16) & 0x7F;
while (WaitForSingleObject(MIDI_mutex, 0) == WAIT_TIMEOUT) ;
if (midi_length <= MIDI_MAX - 3) {
midi_chars[midi_length++] = status;
midi_chars[midi_length++] = note;
midi_chars[midi_length++] = velocity;
}
ReleaseMutex(MIDI_mutex);
if (note == dwInstance) { // dwInstance is the CW note
// ignore channel number
if ((status & 0xF0) == 0x90) { // Note ON
if (velocity)
quisk_midi_cwkey = 1;
else
quisk_midi_cwkey = 0;
}
else if ((status & 0xF0) == 0x80) { // Note OFF
quisk_midi_cwkey = 0;
}
}
}
}
PyObject * quisk_wasapi_control_midi(PyObject * self, PyObject * args, PyObject * keywds)
{ /* Call with keyword arguments ONLY */
// midiOutShortMsg
static char * kwlist[] = {"client", "device", "close_port", "get_event", "midi_cwkey_note",
"get_in_names", "get_in_devices", NULL} ;
int client, close_port, get_event, get_in_names, get_in_devices;
static int midi_cwkey_note = -1;
char * device;
PyObject * pylist, * pybytes;
static HMIDIIN hMidiDevice = NULL;;
client = close_port = get_event = get_in_names = get_in_devices = -1;
device = NULL;
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|isiiiii", kwlist,
&client, &device, &close_port, &get_event, &midi_cwkey_note, &get_in_names, &get_in_devices))
return NULL;
if (close_port == 1) { // shutdown
if (hMidiDevice) {
midiInStop(hMidiDevice);
midiInClose(hMidiDevice);
}
hMidiDevice = NULL;
quisk_midi_cwkey = 0;
}
if (get_in_devices == 1) { // return a list of MIDI devices; just a list of names
pylist = PyList_New(0);
midi_in_devices(pylist);
return pylist;
}
if (get_in_names == 1) { // return a list of MIDI devices; just a list of names
pylist = PyList_New(0);
midi_in_devices(pylist);
return pylist;
}
if (client >= 0) { // open client port
if ( ! MIDI_mutex)
MIDI_mutex = CreateMutex(NULL, FALSE, NULL);
quisk_midi_cwkey = 0;
if (hMidiDevice == NULL) {
if (midiInOpen(&hMidiDevice, client, (DWORD_PTR)(void*)MidiInProc, midi_cwkey_note, CALLBACK_FUNCTION) != MMSYSERR_NOERROR) {
QuiskPrintf("Could not open MIDI device\n");
hMidiDevice = NULL;
}
else {
midiInStart(hMidiDevice);
if (quisk_sound_state.verbose_sound)
QuiskPrintf("Open MIDI device %d\n", client);
}
}
}
if (get_event == 1) { // poll to get event
if (midi_length != 0) {
while (WaitForSingleObject(MIDI_mutex, 0) == WAIT_TIMEOUT) ;
pybytes = PyByteArray_FromStringAndSize(midi_chars, midi_length);
midi_length = 0;
ReleaseMutex(MIDI_mutex);
return pybytes;
}
}
Py_INCREF (Py_None);
return Py_None;
}
#else // No Wasapi available
#include <Python.h>
#include <complex.h>
#include "quisk.h"
PyObject * quisk_wasapi_sound_devices(PyObject * self, PyObject * args) // Called from the GUI thread
{ // Return a list of sound device data [pycapt, pyplay]
return quisk_dummy_sound_devices(self, args);
}
void quisk_start_sound_wasapi (struct sound_dev ** pCapture, struct sound_dev ** pPlayback)
{
struct sound_dev * pDev;
const char * msg = "No driver support for this device";
while (*pCapture) {
pDev = *pCapture++;
if (pDev->driver == DEV_DRIVER_WASAPI) {
strMcpy(pDev->dev_errmsg, msg, QUISK_SC_SIZE);
if (quisk_sound_state.verbose_sound)
QuiskPrintf("%s\n", msg);
}
}
while (*pPlayback) {
pDev = *pPlayback++;
if (pDev->driver == DEV_DRIVER_WASAPI) {
strMcpy(pDev->dev_errmsg, msg, QUISK_SC_SIZE);
if (quisk_sound_state.verbose_sound)
QuiskPrintf("%s\n", msg);
}
else if (pDev->driver == DEV_DRIVER_WASAPI2) {
strMcpy(pDev->dev_errmsg, msg, QUISK_SC_SIZE);
if (quisk_sound_state.verbose_sound)
QuiskPrintf("%s\n", msg);
}
}
}
void quisk_write_wasapi(struct sound_dev * dev, int nSamples, complex double * cSamples, double volume)
{}
int quisk_read_wasapi(struct sound_dev * dev, complex double * cSamples)
{
return 0;
}
void quisk_play_wasapi(struct sound_dev * dev, int nSamples, complex double * cSamples, double volume)
{}
void quisk_close_sound_wasapi(struct sound_dev ** pCapture, struct sound_dev ** pPlayback)
{}
// Note: Return values must be kept up to date!
PyObject * quisk_wasapi_control_midi(PyObject * self, PyObject * args, PyObject * keywds)
{
static char * kwlist[] = {"client", "device", "close_port", "get_event", "midi_cwkey_note",
"get_in_names", "get_in_devices", NULL} ;
int client, close_port, get_event, get_in_names, get_in_devices;
static int midi_cwkey_note = -1;
char * device;
PyObject * pylist;
client = close_port = get_event = get_in_names = get_in_devices = -1;
device = NULL;
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|isiiiii", kwlist,
&client, &device, &close_port, &get_event, &midi_cwkey_note, &get_in_names, &get_in_devices))
return NULL;
if (get_in_devices == 1) { // return a list of MIDI devices; just a list of names
pylist = PyList_New(0);
return pylist;
}
if (get_in_names == 1) { // return a list of MIDI devices; just a list of names
pylist = PyList_New(0);
return pylist;
}
Py_INCREF (Py_None);
return Py_None;
}
#endif