基于portaudio的音频设备名称对齐,整合MME与WASAPI的优点在一起(非性能上的整合,捕获、播放音频流仍然使用MME)

/// 基于portaudio的音频设备名称对应,MME模式时名称存在被切断的情况,
/// 此时用WASAPI模式下可以得到完整设备名称,再将序号对齐于MME。
/// 其次WASAPI模式下可以天然过滤"windows下的混响"设备。
///

#include 

#ifdef _WIN32
#include 
#include 
#include 
#include 
#endif

#include 
#include 

/// 
/// 输入输出设备信息
/// 
struct DeviceInfo {
    int id = 0;
    std::string deviceId;
    int structVersion = 0;  /* this is struct version 2 */
    std::string name;
    PaHostApiIndex hostApi = 0; /**< note this is a host API index, not a type id*/

    int maxInputChannels = 0;
    int maxOutputChannels = 0;

    /** Default latency values for interactive performance. */
    PaTime defaultLowInputLatency = 0.0;
    PaTime defaultLowOutputLatency = 0.0;
    /** Default latency values for robust non-interactive applications (eg. playing sound files). */
    PaTime defaultHighInputLatency = 0.0;
    PaTime defaultHighOutputLatency = 0.0;

    double defaultSampleRate = 0.0;

    DeviceInfo() {}
    DeviceInfo(int id, const std::string& deviceId, const PaDeviceInfo& devinfo)
    {
        this->id = id;
        this->deviceId = deviceId;
        operator=(devinfo);
    }
    DeviceInfo(int id, const DeviceInfo& devinfo)
    {
        this->id = id;
        this->deviceId = devinfo.deviceId;
        operator=(devinfo);
    }
    DeviceInfo& operator=(const PaDeviceInfo& devinfo)
    {
        return set(devinfo);
    }
    DeviceInfo& operator=(const DeviceInfo& devinfo)
    {
        return set(devinfo);
    }
    template 
    DeviceInfo& set(const Info& devinfo)
    {
        structVersion = devinfo.structVersion;
        name = typeid(Info) == typeid(DeviceInfo) ? devinfo.name : mp::utils::Utf82Ascii(devinfo.name);
        hostApi = devinfo.hostApi;
        maxInputChannels = devinfo.maxInputChannels;
        maxOutputChannels = devinfo.maxOutputChannels;
        defaultLowInputLatency = devinfo.defaultLowInputLatency;
        defaultLowOutputLatency = devinfo.defaultLowOutputLatency;
        defaultHighInputLatency = devinfo.defaultHighInputLatency;
        defaultHighOutputLatency = devinfo.defaultHighOutputLatency;
        defaultSampleRate = devinfo.defaultSampleRate;

        return *this;
    }
    std::string to_string()
    {
        return (std::string)*this;
    }
    operator std::string()
    {
        std::stringstream ss;
        ss << "{\"devinfo\":{\"id\":" << id
            << ",\"deviceId\":\"" << deviceId << "\""
            << ",\"structVersion\":" << structVersion
            << ",\"name\":\"" << name << "\""
            << ",\"hostApi\":" << hostApi
            << ",\"maxInputChannels\":" << maxInputChannels
            << ",\"maxOutputChannels\":" << maxOutputChannels
            << ",\"defaultLowInputLatency\":" << defaultLowInputLatency
            << ",\"defaultLowOutputLatency\":" << defaultLowOutputLatency
            << ",\"defaultHighInputLatency\":" << defaultHighInputLatency
            << ",\"defaultHighOutputLatency\":" << defaultHighOutputLatency
            << ",\"defaultSampleRate\":" << defaultSampleRate << "}}";
        return ss.str();
    }
};

#ifdef _WIN32
/// 
/// 通过COM组件获取输入输出设备编号信息
/// 
/// 
/// 
static std::vector> getDeviceFromComList(bool isInputDevice = true)
{
    std::vector> deviceIdNames;
    IMMDeviceEnumerator* pMMDeviceEnumerator = nullptr;
    IMMDeviceCollection* pMMDeviceCollection = nullptr;
    HRESULT hr = CoCreateInstance(
        __uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL,
        __uuidof(IMMDeviceEnumerator),
        reinterpret_cast(&pMMDeviceEnumerator));
    if (FAILED(hr))
    {
        printf("create instance failed: error code: %d,%d\n", GetLastError(), hr);
        return deviceIdNames;
    }
    // get all the active render endpoints
    hr = pMMDeviceEnumerator->EnumAudioEndpoints(isInputDevice ? eCapture : eRender, DEVICE_STATE_ACTIVE, &pMMDeviceCollection);
    if (FAILED(hr))
    {
        printf("enum audio endpoints failed: error code: %d,%x\n", GetLastError(), hr);
        return deviceIdNames;
    }

    uint32_t devicecount = 0;
    pMMDeviceCollection->GetCount(&devicecount);

    deviceIdNames.resize(devicecount);
    deviceIdNames.clear();

    for (int i = 0; i < devicecount; i++)
    {
        IMMDevice* pMMDevice = nullptr;
        IPropertyStore* pPropertyStore = nullptr;
        PROPVARIANT pv;
        PropVariantInit(&pv);

        hr = pMMDeviceCollection->Item(i, &pMMDevice);
        if (FAILED(hr))
        {
            printf("enum item fail: error code: %d, hresult %x\n", GetLastError(), hr);
            continue;
        }
        LPWSTR pwszDeviceId = nullptr;
        hr = pMMDevice->GetId(&pwszDeviceId);
        if (FAILED(hr))
        {
            printf("get device id fail: error code: %d\n", GetLastError());
            pMMDevice->Release();
            continue;
        }
        std::wstring id(pwszDeviceId);
        free(pwszDeviceId);

        // open the property store on that device
        hr = pMMDevice->OpenPropertyStore(STGM_READ, &pPropertyStore);
        if (FAILED(hr))
        {
            printf("open property store fail: error code: %d", GetLastError());
            continue;
        }

        // get the long name property
        hr = pPropertyStore->GetValue(PKEY_Device_FriendlyName, &pv);
        if (FAILED(hr))
        {
            printf("get value fail: error code: %d", GetLastError());
            continue;
        }
        std::wstring name(pv.pwszVal);
        PropVariantClear(&pv);
        pPropertyStore->Release();

        deviceIdNames.push_back(std::make_pair(mp::utils::WideByte2Ascii(id), mp::utils::WideByte2Ascii(name)));

        pMMDevice->Release();
    }
    pMMDeviceCollection->Release();
    pMMDeviceEnumerator->Release();

    return deviceIdNames;
}
#endif

/// 
/// 获取(输入/输出)设备列表
/// 
/// true为输入设备,否则为输出设备
/// 设为列表
std::vector getDeviceList(bool isInputDevice = true)
{
    std::vector devices;
    auto devcount = Pa_GetDeviceCount();
#ifdef _WIN32
    auto hostapiWasapi = Pa_HostApiTypeIdToHostApiIndex(paWASAPI);
    auto hostapiMme = Pa_HostApiTypeIdToHostApiIndex(paMME);

    auto deviceIdNames = getDeviceFromComList(isInputDevice);
    std::vector wases(devcount);
    std::vector mmes(devcount);
    wases.clear();
    mmes.clear();

    // 检索portaudio里的MME和WASAPI
    for (int i = 0; i < devcount; i++)
    {
        auto devinfo = Pa_GetDeviceInfo(i);
        if ((!isInputDevice && devinfo->maxOutputChannels > 0)
            || (isInputDevice && devinfo->maxInputChannels > 0))
        {
            if (devinfo->hostApi == hostapiWasapi) {
                auto name = mp::utils::Utf82Ascii(devinfo->name);
                for (auto& devidname : deviceIdNames) {
                    if (devidname.second == name) {
                        wases.push_back(DeviceInfo(i, devidname.first, *devinfo));
                        break;
                    }
                }
            }
            else if (devinfo->hostApi == hostapiMme) {
                mmes.push_back(DeviceInfo(i, "", *devinfo));
            }
        }
    }

    // 找到 WASAPI 对应的 MME 编号
    devices.resize(wases.size());
    devices.clear();
    for (auto& devinfo : wases)
    {
        for (int j = 0; j < mmes.size(); j++)
        {
            auto& devinfotemp = mmes[j];
            if (devinfo.name.find(devinfotemp.name) == 0)
            {
                devices.push_back(DeviceInfo(devinfotemp.id, devinfo));
                break;
            }
        }
    }
#else

#endif
    return devices;
}

int main()
{
	Pa_Initialize();
	bool isInputDevice = false;
	auto deviceList = getDeviceList(isInputDevice);
	for (auto& device : deviceList) {
		printf("%s\n", device.to_string().c_str());
	}

	return 0;
}

你可能感兴趣的:(MME名称被截断,WASAPI过滤混响设备)