Skip to content

Instantly share code, notes, and snippets.

@arkadijs
Last active July 24, 2024 07:43
Show Gist options
  • Save arkadijs/7027bf8aad399f3420406600c73df9e4 to your computer and use it in GitHub Desktop.
Save arkadijs/7027bf8aad399f3420406600c73df9e4 to your computer and use it in GitHub Desktop.
PortAudio capture WASAPI synthetic [Loopback] device
#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;
}
@dmitrykos
Copy link

dmitrykos commented Jul 24, 2024

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.

#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)
#define CHANNELS (1)
#define DEVICES_MAX (32)

const int sampleRates[] = {8000, 16000, 32000, 44100, 48000};

volatile static int frame_counter = 0;
int16_t buffer[10000000][CHANNELS];

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)*CHANNELS);
    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[DEVICES_MAX] = {0};

    for (int i = 0; i < nDev && (i < DEVICES_MAX); 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 = CHANNELS;
        loopbackParameters.device = i;
        loopbackParameters.sampleFormat = paInt16;
        loopbackParameters.suggestedLatency = device->defaultHighInputLatency;
        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][CHANNELS];

                    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)*CHANNELS, 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