You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

463 lines
12 KiB

#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <windows.h>
#include <initguid.h>
#include <mmsystem.h>
#include <mmdeviceapi.h>
#include <audioclient.h>
#include <avrt.h>
#include <functiondiscoverykeys_devpkey.h>
struct nd_pair
{
wchar_t name[BUFSIZ];
IMMDevice *device;
};
int list_devices(struct nd_pair **pairs)
{
int result = -1;
HRESULT res = 0;
IMMDeviceEnumerator *enumerator = NULL;
IMMDeviceCollection *collection = NULL;
UINT count = 0;
if (pairs == NULL)
{
goto out;
}
if ((res = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
&IID_IMMDeviceEnumerator, (void **)&enumerator)) != S_OK)
{
fprintf(stderr, "Failed to create IMMDeviceEnumerator instance! [0x%08lx]\n", res);
goto out;
}
if ((res = enumerator->lpVtbl->EnumAudioEndpoints(enumerator, eRender,
DEVICE_STATE_ACTIVE, &collection)) != S_OK)
{
fprintf(stderr, "Failed to enumerate audio endpoints! [0x%08lx]\n", res);
goto out;
}
if ((res = collection->lpVtbl->GetCount(collection, &count)) != S_OK)
{
fprintf(stderr, "Failed to get number of audio endpoints! [0x%08lx]\n", res);
goto out;
}
*pairs = malloc(count * sizeof(struct nd_pair));
ZeroMemory(*pairs, count * sizeof(struct nd_pair));
if (*pairs == NULL)
{
fprintf(stderr, "Failed to allocate memory for device list!\n");
goto out;
}
for (UINT i = 0; i < count; i++)
{
IMMDevice *device = NULL;
if ((res = collection->lpVtbl->Item(collection, i, &device)) != S_OK)
{
fprintf(stderr, "Failed to get audio device! [0x%08lx]\n", res);
goto out;
}
(*pairs)[i].device = device;
IPropertyStore *props = NULL;
if ((res = device->lpVtbl->OpenPropertyStore(device, STGM_READ, &props)) != S_OK)
{
fprintf(stderr, "Failed to open property store! [0x%08lx]\n", res);
goto out;
}
PROPVARIANT pv;
PropVariantInit(&pv);
if ((res = props->lpVtbl->GetValue(props, &PKEY_Device_FriendlyName, &pv)) != S_OK)
{
fprintf(stderr, "Failed to get device friendly name! [0x%08lx]\n", res);
PropVariantClear(&pv);
props->lpVtbl->Release(props);
goto out;
}
if (pv.vt != VT_LPWSTR)
{
fprintf(stderr, "Unexpected property type!\n");
PropVariantClear(&pv);
props->lpVtbl->Release(props);
goto out;
}
memset((*pairs)[i].name, 0, BUFSIZ * sizeof(wchar_t));
wcsncpy((*pairs)[i].name, pv.pwszVal, wcsnlen(pv.pwszVal, BUFSIZ));
PropVariantClear(&pv);
props->lpVtbl->Release(props);
}
result = count;
out:
if (result < 0 && *pairs != NULL)
{
for (UINT i = 0; i < count; i++)
{
if ((*pairs)[i].device != NULL)
{
(*pairs)[i].device->lpVtbl->Release((*pairs)[i].device);
}
}
free(*pairs);
}
if (collection != NULL)
{
collection->lpVtbl->Release(collection);
}
if (enumerator != NULL)
{
enumerator->lpVtbl->Release(enumerator);
}
return result;
}
void clear_devices(struct nd_pair **pairs, int count)
{
if (pairs == NULL)
{
return;
}
for (int i = 0; i < count; i++)
{
(*pairs)[i].device->lpVtbl->Release((*pairs)[i].device);
}
free(*pairs);
*pairs = NULL;
}
bool mirror(IMMDevice *monitor, IMMDevice *output)
{
bool result = false;
HRESULT res = 0;
HANDLE event = NULL;
DWORD task_index = 0;
HANDLE task = NULL;
IAudioClient *mon_client = NULL;
IAudioClient *out_client = NULL;
IAudioCaptureClient *capture = NULL;
IAudioRenderClient *render = NULL;
WAVEFORMATEX *format = NULL;
REFERENCE_TIME period = 0;
UINT32 buffer_size = 0;
if ((event = CreateEvent(NULL, FALSE, FALSE, NULL)) == NULL)
{
fprintf(stderr, "Failed to create event!\n");
goto out;
}
if ((task = AvSetMmThreadCharacteristics(TEXT("Audio"), &task_index)) == NULL)
{
fprintf(stderr, "Failed to set thread characteristics!\n");
goto out;
}
if ((res = monitor->lpVtbl->Activate(monitor, &IID_IAudioClient, CLSCTX_ALL,
NULL, (void **)&mon_client)) != S_OK)
{
fprintf(stderr, "Failed to activate monitor device! [0x%08lx]\n", res);
goto out;
}
if ((res = mon_client->lpVtbl->GetDevicePeriod(mon_client, &period, NULL)) != S_OK)
{
fprintf(stderr, "Failed to get monitor client period! [0x%08lx]\n", res);
goto out;
}
if ((res = mon_client->lpVtbl->GetMixFormat(mon_client, &format)) != S_OK)
{
fprintf(stderr, "Failed to get monitor client mix format! [0x%08lx]\n", res);
goto out;
}
if ((res = mon_client->lpVtbl->Initialize(mon_client, AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_LOOPBACK, 0, 0, format, 0)) != S_OK)
{
fprintf(stderr, "Failed to initialize monitor client! [0x%08lx]\n", res);
goto out;
}
if ((res = mon_client->lpVtbl->GetService(mon_client, &IID_IAudioCaptureClient,
(void **)&capture)) != S_OK)
{
fprintf(stderr, "Failed to get capture service from monitor client! [0x%08lx]\n", res);
goto out;
}
if ((res = output->lpVtbl->Activate(output, &IID_IAudioClient, CLSCTX_ALL,
NULL, (void **)&out_client)) != S_OK)
{
fprintf(stderr, "Failed to activate output device! [0x%08lx]\n", res);
goto out;
}
if ((res =out_client->lpVtbl->Initialize(out_client, AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK, period, 0, format, 0)) != S_OK)
{
fprintf(stderr, "Failed to initialize output client! [0x%08lx]\n", res);
goto out;
}
if ((res = out_client->lpVtbl->GetBufferSize(out_client, &buffer_size)) != S_OK)
{
fprintf(stderr, "Failed to get buffer size of output client! [0x%08lx]\n", res);
goto out;
}
if ((res = out_client->lpVtbl->GetService(out_client, &IID_IAudioRenderClient,
(void **)&render)) != S_OK)
{
fprintf(stderr, "Failed to get render service from output client! [0x%08lx]\n", res);
goto out;
}
if ((res = out_client->lpVtbl->SetEventHandle(out_client, event)) != S_OK)
{
fprintf(stderr, "Failed to associate event with output client! [0x%08lx]\n", res);
goto out;
}
if ((res = mon_client->lpVtbl->Start(mon_client)) != S_OK)
{
fprintf(stderr, "Failed to start monitor client! [0x%08lx]\n", res);
goto out;
}
if ((res = out_client->lpVtbl->Start(out_client)) != S_OK)
{
fprintf(stderr, "Failed to start output client! [0x%08lx]\n", res);
goto out;
}
for (;;)
{
if (WaitForSingleObject(event, INFINITE) != WAIT_OBJECT_0)
{
break;
}
for (;;)
{
UINT32 padding = 0;
if ((res = out_client->lpVtbl->GetCurrentPadding(out_client, &padding)) != S_OK)
{
fprintf(stderr, "Failed to get current padding of output client! [0x%08lx]\n", res);
goto out;
}
UINT32 packet_size = 0;
if ((res = capture->lpVtbl->GetNextPacketSize(capture, &packet_size)) != S_OK)
{
fprintf(stderr, "Failed to get next packet size of monitor client! [0x%08lx]\n", res);
goto out;
}
BYTE *mon_data = NULL;
BYTE *out_data = NULL;
UINT32 frames = 0;
DWORD flags = 0;
if (packet_size == 0)
{
// no input data, play silence
if ((res = render->lpVtbl->GetBuffer(render, buffer_size - padding, &out_data)) != S_OK)
{
fprintf(stderr, "Failed to get render buffer of output client! [0x%08lx]\n", res);
goto out;
}
if ((res = render->lpVtbl->ReleaseBuffer(render, buffer_size - padding,
AUDCLNT_BUFFERFLAGS_SILENT)) != S_OK)
{
fprintf(stderr, "Failed to release render buffer of output client! [0x%08lx]\n", res);
goto out;
}
break;
}
else if (packet_size > buffer_size - padding)
{
// output buffer is full, we have to wait
break;
}
else
{
if ((res = capture->lpVtbl->GetBuffer(capture, &mon_data, &frames, &flags,
NULL, NULL)) != S_OK)
{
fprintf(stderr, "Failed to get capture buffer of monitor client! [0x%08lx]\n", res);
goto out;
}
if ((res = render->lpVtbl->GetBuffer(render, frames, &out_data)) != S_OK)
{
fprintf(stderr, "Failed to get render buffer of output client! [0x%08lx]\n", res);
goto out;
}
CopyMemory(out_data, mon_data, frames * format->nBlockAlign);
if ((res = capture->lpVtbl->ReleaseBuffer(capture, frames)) != S_OK)
{
fprintf(stderr, "Failed to release capture buffer of monitor client! [0x%08lx]\n", res);
goto out;
}
if ((res = render->lpVtbl->ReleaseBuffer(render, frames, 0)) != S_OK)
{
fprintf(stderr, "Failed to release render buffer of output client! [0x%08lx]\n", res);
goto out;
}
}
}
}
result = true;
out:
if (render != NULL)
{
render->lpVtbl->Release(render);
}
if (capture != NULL)
{
capture->lpVtbl->Release(capture);
}
if (format != NULL)
{
CoTaskMemFree(format);
}
if (out_client != NULL)
{
out_client->lpVtbl->Stop(out_client);
out_client->lpVtbl->Release(out_client);
}
if (mon_client != NULL)
{
mon_client->lpVtbl->Stop(mon_client);
mon_client->lpVtbl->Release(mon_client);
}
if (task != NULL)
{
AvRevertMmThreadCharacteristics(task);
CloseHandle(task);
}
if (event != NULL)
{
CloseHandle(event);
}
return result;
}
bool run()
{
bool result = false;
struct nd_pair *pairs = NULL;
int count = list_devices(&pairs);
if (count <= 0)
{
fprintf(stderr, "No playback audio devices detected!\n");
goto out;
}
printf("Detected playback audio devices:\n\n");
for (int i = 0; i < count; i++)
{
printf("[%d] %ls\n", i, pairs[i].name);
}
printf("\n");
char buf[5] = "";
printf("Select device to monitor: ");
char *res = fgets(buf, 5, stdin);
int mon = strtol(buf, NULL, 10);
while (res != buf || mon < 0 || mon >= count)
{
printf("Select device to monitor: ");
res = fgets(buf, 5, stdin);
mon = strtol(buf, NULL, 10);
}
printf("Select output device: ");
res = fgets(buf, 5, stdin);
int out = strtol(buf, NULL, 10);
while (res != buf || out < 0 || out >= count || out == mon)
{
printf("Select output device: ");
res = fgets(buf, 5, stdin);
out = strtol(buf, NULL, 10);
}
printf("\nMirroring:\n");
printf("%ls\n", pairs[mon].name);
printf(" -> %ls\n\n", pairs[out].name);
result = mirror(pairs[mon].device, pairs[out].device);
out:
clear_devices(&pairs, count);
return result;
}
int main(void)
{
if (CoInitialize(NULL) != S_OK)
{
return EXIT_FAILURE;
}
if (! run())
{
CoUninitialize();
return EXIT_FAILURE;
}
CoUninitialize();
return EXIT_SUCCESS;
}