commit
44ded2c0cd
@ -0,0 +1 @@
|
|||||||
|
*.exe
|
@ -0,0 +1,9 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018 Nikola Forró
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@ -0,0 +1,9 @@
|
|||||||
|
CC = x86_64-w64-mingw32-gcc
|
||||||
|
CFLAGS = -W -Wall -pedantic
|
||||||
|
LIBS = -lole32 -lavrt
|
||||||
|
|
||||||
|
|
||||||
|
all: mirror.exe
|
||||||
|
|
||||||
|
mirror.exe: mirror.c
|
||||||
|
$(CC) $(CFLAGS) $< -o $@ $(LIBS)
|
@ -0,0 +1,468 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "packet_size = %d, buffer_size = %d, padding = %d\n", packet_size, buffer_size, padding);
|
||||||
|
|
||||||
|
BYTE *mon_data = NULL;
|
||||||
|
BYTE *out_data = NULL;
|
||||||
|
UINT32 frames = 0;
|
||||||
|
DWORD flags = 0;
|
||||||
|
|
||||||
|
if (packet_size > buffer_size - padding)
|
||||||
|
{
|
||||||
|
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 = capture->lpVtbl->ReleaseBuffer(capture, 0)) != S_OK)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Failed to release capture buffer of monitor client! [0x%08lx]\n", res);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (packet_size > 0)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
Loading…
Reference in new issue