Last active
July 24, 2024 07:43
-
-
Save arkadijs/7027bf8aad399f3420406600c73df9e4 to your computer and use it in GitHub Desktop.
PortAudio capture WASAPI synthetic [Loopback] device
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <fcntl.h> | |
#include <io.h> | |
#include <stdint.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <windows.h> | |
#include "portaudio.h" | |
#define FRAMES_PER_BUFFER (512) | |
const int sampleRates[] = {8000, 16000, 32000, 44100, 48000}; | |
volatile static int frame_counter = 0; | |
int16_t buffer[10000000]; | |
int callback(const void *input, void *output, unsigned long frameCount, | |
const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData) { | |
// printf("%s: pushed %lu frames\n", (char*)userData, frameCount); | |
memcpy(buffer + frame_counter, input, frameCount*sizeof(int16_t)); | |
frame_counter += frameCount; | |
return paContinue; | |
} | |
int main(void) { | |
SetConsoleOutputCP(65001); | |
PaError err = paNoError; | |
err = Pa_Initialize(); | |
if (err != paNoError) | |
return err; | |
FILE *capture_file = fopen("capture.pcm", "wb"); | |
const int nDev = Pa_GetDeviceCount(); | |
const PaDeviceInfo *devices[nDev] = {}; | |
for (int i = 0; i < nDev; i++) { | |
// devices[i] = NULL; | |
const PaDeviceInfo *device; | |
if ((device = Pa_GetDeviceInfo(i)) != NULL) { | |
const char *host_api_name = Pa_GetHostApiInfo(device->hostApi)->name; | |
char *selected = ""; | |
if (strstr(device->name, "[Loopback]") != NULL) { | |
devices[i] = device; | |
selected = " <======="; | |
} | |
printf("Device: %s > %s rate:%.0f%s\n", host_api_name, | |
device->name, device->defaultSampleRate, selected); | |
} | |
} | |
for (int i = 0; i < nDev; i++) { | |
const PaDeviceInfo *device = devices[i]; | |
if (device == NULL) { | |
continue; | |
} | |
PaStreamParameters loopbackParameters = {0}; | |
int loopbackSampleRate = 0; | |
loopbackParameters.channelCount = 1; | |
loopbackParameters.device = i; | |
loopbackParameters.sampleFormat = paInt16; | |
loopbackParameters.suggestedLatency = device->defaultLowInputLatency; | |
loopbackParameters.hostApiSpecificStreamInfo = NULL; | |
for (int i = 0; i < sizeof(sampleRates)/sizeof(sampleRates[0]); i++) { | |
// Check if the format is supported | |
err = Pa_IsFormatSupported(&loopbackParameters, NULL, sampleRates[i]); | |
if (err == paFormatIsSupported) { | |
loopbackSampleRate = sampleRates[i]; | |
} | |
} | |
if (!loopbackSampleRate) { | |
printf("%s: not compatible\n", device->name); | |
continue; | |
} | |
PaStream *loopbackStream; | |
const char *result = "success"; | |
printf("%s: trying callback API...\n", device->name); | |
err = Pa_OpenStream(&loopbackStream, &loopbackParameters, NULL, | |
loopbackSampleRate, paFramesPerBufferUnspecified, paClipOff, callback, (void*)device->name); | |
if (err != paNoError) { | |
result = Pa_GetErrorText(err); | |
} else { | |
int frame_counter_started_at = frame_counter; | |
err = Pa_StartStream(loopbackStream); | |
if (err != paNoError) { | |
result = Pa_GetErrorText(err); | |
} else { | |
Sleep(3000); | |
PaError active = Pa_IsStreamActive(loopbackStream); | |
if (active < 0) { | |
printf("%s: %s\n", device->name, Pa_GetErrorText(active)); | |
} else { | |
printf("%s: stream active: %d\n", device->name, active); | |
} | |
err = Pa_StopStream(loopbackStream); | |
if (err != paNoError) { | |
result = Pa_GetErrorText(err); | |
} else if (frame_counter_started_at == frame_counter) { | |
result = "no frames pushed"; | |
} | |
} | |
Pa_CloseStream(loopbackStream); | |
} | |
printf("%s: %s\n", device->name, result); | |
if (!strcmp(result, "success")) { | |
printf("%s: trying blocking API...\n", device->name); | |
err = Pa_OpenStream(&loopbackStream, &loopbackParameters, NULL, | |
loopbackSampleRate, paFramesPerBufferUnspecified, paClipOff, NULL, NULL); | |
if (err != paNoError) { | |
result = Pa_GetErrorText(err); | |
} else { | |
err = Pa_StartStream(loopbackStream); | |
if (err != paNoError) { | |
result = Pa_GetErrorText(err); | |
} else { | |
int16_t buf[FRAMES_PER_BUFFER]; | |
for (int i = 0; i < 300; i++) { | |
err = Pa_ReadStream(loopbackStream, buf, FRAMES_PER_BUFFER); | |
if (err != paNoError) { | |
result = Pa_GetErrorText(err); | |
break; | |
} else { | |
memcpy(buffer + frame_counter, buf, sizeof(buf)); | |
frame_counter += FRAMES_PER_BUFFER; | |
// printf("%s: read %d frames\n", device->name, FRAMES_PER_BUFFER); | |
} | |
} | |
} | |
Pa_CloseStream(loopbackStream); | |
} | |
printf("%s: %s\n", device->name, result); | |
} | |
} | |
fwrite(buffer, sizeof(int16_t), frame_counter, capture_file); | |
fclose(capture_file); | |
Pa_Terminate(); | |
return err; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I modified it a little bit to make channels configurable and to compare Stereo and Mono. Potentially, as many as needed channels can be tested like that.
Note for other users: To test output you can use Audacity -> File -> Import -> Raw Data.