You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WASAPI环回录制音频质量劣化问题排查求助

WASAPI环回录制音质下降问题解决方案

问题描述

使用WASAPI进行环回录制时,捕获的WAV音频清晰度和保真度远低于原始音源,音质差异明显。

核心问题分析

  1. WAV头部处理逻辑错误:初始化时重复创建WAVWriter实例,导致头部信息不一致;固定subchunk1Size为16,无法兼容扩展音频格式。
  2. 同步方式不合理:使用Sleep主动等待缓冲区数据,容易出现丢帧或重复读取,破坏音频连续性。
  3. 格式兼容性缺失:未校验捕获的音频格式是否为PCM,直接写入非PCM格式会导致播放器解析异常。

修复方案及代码修改

1. 修复WAV头部处理逻辑(Record.h)

#include <windows.h>
#include <mmdeviceapi.h>
#include <audioclient.h>
#include <iostream>
#include <fstream>
#define REFTIMES_PER_SEC  10000000
#define REFTIMES_PER_MILLISEC  10000

#define EXIT_ON_ERROR(hres)  \
              if (FAILED(hres)) { goto Exit; }
#define SAFE_RELEASE(punk)  \
              if ((punk) != NULL)  \
                { (punk)->Release(); (punk) = NULL; }

struct WAVHeader {
    char chunkID[4];
    uint32_t chunkSize;
    char format[4];
    char subchunk1ID[4];
    uint32_t subchunk1Size;
    uint16_t audioFormat;
    uint16_t numChannels;
    uint32_t sampleRate;
    uint32_t byteRate;
    uint16_t blockAlign;
    uint16_t bitsPerSample;
    char subchunk2ID[4];
    uint32_t subchunk2Size;
};

struct WAVData {
    WAVHeader header;
    uint8_t* sample;
};

class WAVWriter {
public:
    WAVWriter(const char* filename)
        : file(filename, std::ios::binary), totalDataSize(0) {
        if (!file.is_open()) {
            std::cerr << "Failed to open file for writing\n";
            return;
        }
        memcpy(wavData.header.chunkID, "RIFF", 4);
        memcpy(wavData.header.format, "WAVE", 4);
        memcpy(wavData.header.subchunk1ID, "fmt ", 4);
        memcpy(wavData.header.subchunk2ID, "data", 4);
        file.write(reinterpret_cast<const char*>(&wavData.header), sizeof(wavData.header));
    }

    void updateHeader(WAVEFORMATEX* pwfx) {
        if (pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
            auto pEx = reinterpret_cast<WAVEFORMATEXTENSIBLE*>(pwfx);
            wavData.header.subchunk1Size = sizeof(WAVEFORMATEXTENSIBLE);
            wavData.header.audioFormat = (pEx->SubFormat.Data1 == WAVE_FORMAT_PCM) ? 1 : 0;
            wavData.header.bitsPerSample = pEx->Samples.wValidBitsPerSample;
        } else {
            wavData.header.subchunk1Size = sizeof(WAVEFORMATEX);
            wavData.header.audioFormat = pwfx->wFormatTag;
            wavData.header.bitsPerSample = pwfx->wBitsPerSample;
        }
        wavData.header.numChannels = pwfx->nChannels;
        wavData.header.sampleRate = pwfx->nSamplesPerSec;
        wavData.header.byteRate = pwfx->nSamplesPerSec * pwfx->nChannels * wavData.header.bitsPerSample / 8;
        wavData.header.blockAlign = pwfx->nChannels * wavData.header.bitsPerSample / 8;
    }

    void WriteData(BYTE* pData, UINT32 dataSize) {
        if (!file.is_open()) return;
        file.write(reinterpret_cast<const char*>(pData), dataSize);
        totalDataSize += dataSize;
    }

    void Finalize() {
        if (!file.is_open()) return;
        wavData.header.chunkSize = 36 + totalDataSize;
        wavData.header.subchunk2Size = totalDataSize;
        file.seekp(0, std::ios::beg);
        file.write(reinterpret_cast<const char*>(&wavData.header), sizeof(wavData.header));
        file.close();
        delete[] wavData.sample;
    }

private:
    std::ofstream file;
    WAVData wavData;
    uint32_t totalDataSize;
};

class MyAudioSink {
public:
    MyAudioSink(const char* outputFile) : wavWriter(outputFile) {}

    void SetFormat(WAVEFORMATEX* pwfx) {
        std::cout << "Audio format set: " << pwfx->nChannels << " channels, "
                  << pwfx->nSamplesPerSec << " Hz\n";
        wavWriter.updateHeader(pwfx);
    }

    HRESULT CopyData(BYTE* pData, UINT32 numFramesAvailable, WAVEFORMATEX* pwfx, BOOL* bDone) {
        if (numFramesAvailable > 0) {
            UINT32 dataSize = numFramesAvailable * pwfx->nBlockAlign;
            if (pData != NULL) {
                wavWriter.WriteData(pData, dataSize);
            } else {
                BYTE* silenceData = new BYTE[dataSize];
                memset(silenceData, 0, dataSize);
                wavWriter.WriteData(silenceData, dataSize);
                delete[] silenceData;
            }
        }
        return S_OK;
    }

    void Finalize() {
        wavWriter.Finalize();
    }

private:
    WAVWriter wavWriter;
};

2. 替换同步方式为事件驱动(主文件)

#include <windows.h>
#include <mmdeviceapi.h>
#include <audioclient.h>
#include <iostream>
#include <fstream>
#include "Record.h"

const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
const IID IID_IAudioClient = __uuidof(IAudioClient);
const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient);

HRESULT RecordAudioStream(MyAudioSink* pMySink) {
    HRESULT hr;
    REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC;
    UINT32 bufferFrameCount;
    UINT32 numFramesAvailable;
    IMMDeviceEnumerator* pEnumerator = NULL;
    IMMDevice* pDevice = NULL;
    IAudioClient* pAudioClient = NULL;
    IAudioCaptureClient* pCaptureClient = NULL;
    WAVEFORMATEX* pwfx = NULL;
    UINT32 packetLength = 0;
    BOOL bDone = FALSE;
    BYTE* pData;
    DWORD flags;
    HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

    hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&pEnumerator);
    EXIT_ON_ERROR(hr)

    hr = pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &pDevice);
    EXIT_ON_ERROR(hr)

    hr = pDevice->Activate(IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&pAudioClient);
    EXIT_ON_ERROR(hr)

    hr = pAudioClient->GetMixFormat(&pwfx);
    EXIT_ON_ERROR(hr)

    // 仅支持PCM格式
    if (pwfx->wFormatTag != WAVE_FORMAT_PCM && !(pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE && 
        reinterpret_cast<WAVEFORMATEXTENSIBLE*>(pwfx)->SubFormat.Data1 == WAVE_FORMAT_PCM)) {
        std::cerr << "Unsupported format: only PCM is allowed\n";
        hr = E_INVALIDARG;
        goto Exit;
    }

    hr = pAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_EVENTCALLBACK, 
        hnsRequestedDuration, 0, pwfx, NULL);
    EXIT_ON_ERROR(hr)

    hr = pAudioClient->SetEventHandle(hEvent);
    EXIT_ON_ERROR(hr)

    hr = pAudioClient->GetBufferSize(&bufferFrameCount);
    EXIT_ON_ERROR(hr)

    hr = pAudioClient->GetService(IID_IAudioCaptureClient, (void**)&pCaptureClient);
    EXIT_ON_ERROR(hr)

    pMySink->SetFormat(pwfx);

    hr = pAudioClient->Start();
    EXIT_ON_ERROR(hr)

    while (!bDone) {
        DWORD waitResult = WaitForSingleObject(hEvent, INFINITE);
        if (waitResult != WAIT_OBJECT_0) break;

        hr = pCaptureClient->GetNextPacketSize(&packetLength);
        EXIT_ON_ERROR(hr)

        while (packetLength != 0) {
            hr = pCaptureClient->GetBuffer(&pData, &numFramesAvailable, &flags, NULL, NULL);
            EXIT_ON_ERROR(hr)

            if (flags & AUDCLNT_BUFFERFLAGS_SILENT) pData = NULL;

            hr = pMySink->CopyData(pData, numFramesAvailable, pwfx, &bDone);
            EXIT_ON_ERROR(hr)

            hr = pCaptureClient->ReleaseBuffer(numFramesAvailable);
            EXIT_ON_ERROR(hr)

            hr = pCaptureClient->GetNextPacketSize(&packetLength);
            EXIT_ON_ERROR(hr)
        }

        if (GetAsyncKeyState(VK_RETURN)) bDone = TRUE;
    }

    hr = pAudioClient->Stop();
    EXIT_ON_ERROR(hr)

Exit:
    CloseHandle(hEvent);
    CoTaskMemFree(pwfx);
    SAFE_RELEASE(pEnumerator)
    SAFE_RELEASE(pDevice)
    SAFE_RELEASE(pAudioClient)
    SAFE_RELEASE(pCaptureClient)

    return hr;
}

int main() {
    CoInitialize(NULL);
    MyAudioSink audioSink("output.wav");
    HRESULT hr = RecordAudioStream(&audioSink);
    audioSink.Finalize();
    CoUninitialize();
    return hr == S_OK ? 0 : 1;
}

修复效果说明

  • 动态适配音频格式,确保WAV头部信息与捕获数据完全匹配,避免解析错误。
  • 事件驱动方式保证音频捕获的同步性,彻底解决丢帧问题。
  • 严格校验PCM格式,避免非PCM数据直接写入导致的音质损坏。

内容的提问来源于stack exchange,提问作者zz hh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 06:00:53