WASAPI (Windows Audio Session API) 实现边播边录

前言:

         首先声明,对于WASAPI实现播放与录制仅处于新手阶段,code中有不足的地方希望指出....谢谢.

        边播边录是可以使用音频线将Microphone(麦克风)和Line-Out(喇叭)接在一起,然后执行该程式就能够将你所需要的播放的音频文件(我是用的是.wav音频文件)录制下来;同样Line-In(音频输入)和Line-Out(喇叭)也可以这样子操作;

        程式是能够运行,但是实际测试录制下来的音频文件声音大于原始音频文件且声音清晰度降低,由于我刚接触,所以对此部分也不太明白.

        如果想单独执行录制/播放的功能,在Main.cpp中将其中一个功能去掉即可,Code中已经做了录音和播放的分离。

参考的文章:

WAV音频文件格式分析:

        带你分析wav音频文件结构(实例+代码)_wav 声音 数据结构-CSDN博客icon-default.png?t=O83Ahttps://blog.csdn.net/ljrsunshine/article/details/89320026/

        WAVE PCM soundfile formaticon-default.png?t=O83Ahttp://soundfile.sapp.org/doc/WaveFormat/

 WASAPI:

        谈谈Windows下的音频采集对于Windows的音频采集来说,方法有很多。可以通过较为上层的多媒体框架去采集,如Dir - 掘金 (juejin.cn)icon-default.png?t=O83Ahttps://juejin.cn/post/6844904118654337031

        音频-基于Core Audio技术采集音频(版本2)_xta audiocore-CSDN博客icon-default.png?t=O83Ahttps://blog.csdn.net/wenluderen/article/details/123014648#:~:text=%E9%87%87%E7%94%A8windows用户模式音频组件 - Win32 apps | Microsoft Learnicon-default.png?t=O83Ahttps://learn.microsoft.com/zh-cn/windows/win32/coreaudio/user-mode-audio-components        音频终结点设备 - Win32 apps | Microsoft Learnicon-default.png?t=O83Ahttps://learn.microsoft.com/zh-cn/windows/win32/coreaudio/audio-endpoint-devices        设备拓扑 - Win32 apps | Microsoft Learnicon-default.png?t=O83Ahttps://learn.microsoft.com/zh-cn/windows/win32/coreaudio/device-topologies

工具:

文字转语音|语音合成 - 在线文字转换语音软件 - 迅捷PDF转换器在线免费版 (xunjiepdf.com)icon-default.png?t=O83Ahttps://app.xunjiepdf.com/text2voice/

相关文件:

需要引用到的文件有Global.h、FileOperator.h、FileOperator.cpp,如下:

Global.h 
#pragma once

#include <windows.h>
#include <iostream>

using namespace std;



#define ERROR_PRINT(Info)       SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_RED);\
						        cout << Info << endl;\
						        SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);

#define SUCCESS_PRINT(Info)     SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_GREEN);\
						        cout << Info << endl;\
						        SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);

#define FAIL_PRINT(Item)        cout << "Test "<< Item << " .................................";\
                                SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_RED);\
					            cout << "FAIL" << endl;\
                                SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_RED| FOREGROUND_GREEN | FOREGROUND_BLUE);

#define PASS_PRINT(Item)        cout << "Test "<< Item << " .................................";\
                                SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_GREEN);\
					            cout << "PASS" << endl;\
						        SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);

#define PRINT_ITEM(Item)        SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY);\
                                cout << "******************************** TEST "<< Item << " ********************************" << endl;\
                                SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
FileOperator.h
#pragma once
#include "Global.h"
#include <fstream>
#include <string>

using namespace std;

#define WAV_HEADER_LENGTH   44
#define DATA_HEADER_LENGTH  8

// WAV 文件头部结构体定义
#pragma pack(push, 1)
typedef struct {
    CHAR    cRiff[4];           // "RIFF" 标识符
    DWORD   dwFileSize;         // 文件大小
    CHAR    cWave[4];           // "WAVE" 标识符
    CHAR    cFmt[4];            // "fmt " 标识符
    DWORD   dwFmtChunkSize;     // fmt 子块大小
    WORD    wFormatTag;         // 音频格式
    WORD    wNumChannels;       // 通道数
    DWORD   dwSamplesPerSec;    // 采样率
    DWORD   dwBytesPerSec;      // 字节率
    WORD    wBlockAlign;        // 块对齐
    WORD    wBitsPerSample;     // 每个样本的位数
    CHAR    cIdentifier[4];     // "data" / "LIST"(格式转换的wav文件)标识符,
    DWORD   dwIdentifier_Size;  // 数据大小
} WavHeader;

typedef struct {
    CHAR  Data[4];
    DWORD dwDataSize;
} Data_Header;

#pragma pack(pop)


class CUSTOM_FILE
{
public:
    CUSTOM_FILE(const string& filename);
    ~CUSTOM_FILE();
    bool ReadWavHeader();
    FILE *file;
    fpos_t fpPosition;  //"data" chunk data position
    WavHeader wav_Header;
    Data_Header data_Header;
private:

};
FileOperator.cpp
#include "FileOperator.h"

CUSTOM_FILE::CUSTOM_FILE(const string& filename)
{
	errno_t err = fopen_s(&file, filename.c_str(), "rb");

	if (err != 0) {
		ERROR_PRINT("Open File :" << filename << "Failed !!!");
		exit(1);
	}
	ZeroMemory(&wav_Header, WAV_HEADER_LENGTH);
	ZeroMemory(&data_Header, DATA_HEADER_LENGTH);
	fpPosition = 0;
}

CUSTOM_FILE::~CUSTOM_FILE()
{
	if (file)
	{
		fclose(file);
	}
}

//读取wav文件格式数据
bool CUSTOM_FILE::ReadWavHeader() {

	size_t Read_Size = 0;
	int    result = 0;

	Read_Size = fread(reinterpret_cast<char*>(&wav_Header), 1, WAV_HEADER_LENGTH, file);

	if ((Read_Size == 0) || (Read_Size != WAV_HEADER_LENGTH))
	{
		ERROR_PRINT("Read WAV file header data Failed !!!");
		return FALSE;
	}

	//cout << "data: " << wav_Header.Identifier << endl;

	if (_strnicmp(wav_Header.cIdentifier, "data", 4) == 0)
	{
		result = fgetpos(file, &fpPosition);
		if (result != 0)
		{
			ERROR_PRINT("Gets the location of the current file pointer failed !!!");
			return FALSE;
		}
		return TRUE;
	}
	else if (_strnicmp(wav_Header.cIdentifier, "LIST", 4) == 0)
	{
		result = fseek(file, wav_Header.dwIdentifier_Size, SEEK_CUR);

		if (result != 0)
		{
			ERROR_PRINT("Move file pointer failed !!!");
			return FALSE;
		}

		Read_Size = fread(reinterpret_cast<char*>(&data_Header), 1, DATA_HEADER_LENGTH, file);
		if ((Read_Size == 0) || (Read_Size != DATA_HEADER_LENGTH))
		{
			ERROR_PRINT("Read data header data Failed !!!");
			return FALSE;
		}

		//cout << "data: " << data_Header.Data << endl;
		if (_strnicmp(data_Header.Data, "data", 4) != 0) {
			ERROR_PRINT("\"data\" chunk identifier error !!!  --> this error value is :" << data_Header.Data);
			return FALSE;
		}
		wav_Header.cIdentifier[0] = 'd';
		wav_Header.cIdentifier[1] = 'a';
		wav_Header.cIdentifier[2] = 't';
		wav_Header.cIdentifier[3] = 'a';
		wav_Header.dwIdentifier_Size = data_Header.dwDataSize;

	}
	else
	{
		ERROR_PRINT("\"data\" \/ \"LIST\" chunk identifier error !!!  --> this error value is :" << wav_Header.cIdentifier);
		return FALSE;
	}
	
	result = fgetpos(file, &fpPosition);
	if (result != 0)
	{
		ERROR_PRINT("Gets the location of the current file pointer failed !!!");
		return FALSE;
	}
	return TRUE;
}



Main.cpp 
//#include "Main.h"
#include "FileOperator.h"
#include <mmdeviceapi.h>
#include <Audioclient.h>

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


int main(int argc, char* agrv[]) {
    HRESULT hr;//用于接收函数返回值

    hr = CoInitializeEx(NULL, 0);//为当前线程初始化COM库并设置并发模式 
    if (FAILED(hr))
    {
        ERROR_PRINT("CoInitializeEx Fail !!!");
        return 1;
    }

    IMMDeviceEnumerator* pEnumerator = nullptr;
    hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&pEnumerator);
    if (FAILED(hr))
    {
        ERROR_PRINT("CoCreateInstance Fail !!!");
        return 1;
    }

    IMMDevice* pCaptureDevice = nullptr;
    hr = pEnumerator->GetDefaultAudioEndpoint(eCapture, eConsole, &pCaptureDevice);// 采集麦克风
    if (FAILED(hr))
    {
        ERROR_PRINT("GetDefaultAudioEndpoint eCapture Fail !!!");
        return 1;
    }

    IMMDevice* pRenderDevice = nullptr;
    hr = pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &pRenderDevice);// 采集扬声器
    if (FAILED(hr))
    {
        ERROR_PRINT("GetDefaultAudioEndpoint eRender Fail !!!");
        return 1;
    }

    IAudioClient* pCaptureAudioClient = nullptr;
    hr = pCaptureDevice->Activate(IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&pCaptureAudioClient);
    if (FAILED(hr))
    {
        ERROR_PRINT("Activate eCapture Fail !!!");
        return 1;
    }

    IAudioClient* pRenderAudioClient = nullptr;
    hr = pRenderDevice->Activate(IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&pRenderAudioClient);
    if (FAILED(hr))
    {
        ERROR_PRINT("Activate eRender Fail !!!");
        return 1;
    }


    WAVEFORMATEX pwfx;
    ZeroMemory(&pwfx, sizeof(WAVEFORMATEX));

    CUSTOM_FILE file("Test.wav");
    file.ReadWavHeader();
    pwfx.wFormatTag = file.wav_Header.wFormatTag;
    pwfx.nChannels = file.wav_Header.wNumChannels;
    pwfx.nSamplesPerSec = file.wav_Header.dwSamplesPerSec;
    pwfx.nAvgBytesPerSec = file.wav_Header.dwBytesPerSec;
    pwfx.nBlockAlign = file.wav_Header.wBlockAlign;
    pwfx.wBitsPerSample = file.wav_Header.wBitsPerSample;

    hr = pCaptureAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED,
        AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY,
        0, 0, &pwfx, NULL);
    if (FAILED(hr))
    {
        ERROR_PRINT("Initialize eCapture Fail !!!");
        return 1;
    }

    hr = pRenderAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED,
        AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY,
        0, 0, &pwfx, NULL);
    if (FAILED(hr))
    {
        ERROR_PRINT("Initialize eRender Fail !!!");
        return 1;
    }


    UINT32  iCapture_BufferSize = 0;
    hr = pCaptureAudioClient->GetBufferSize(&iCapture_BufferSize);
    if (FAILED(hr))
    {
        ERROR_PRINT("GetBufferSize eCapture Fail !!!");
        return 1;
    }

    UINT32  iRender_BufferSize = 0;
    hr = pRenderAudioClient->GetBufferSize(&iRender_BufferSize);
    if (FAILED(hr))
    {
        ERROR_PRINT("GetBufferSize eRender Fail !!!");
        return 1;
    }


    HANDLE hCaptureEvent = CreateEvent(NULL, false, false, NULL);
    hr = pCaptureAudioClient->SetEventHandle(hCaptureEvent);
    if (FAILED(hr))
    {
        ERROR_PRINT("SetEventHandle eCapture Fail !!!");
        return 1;
    }

    HANDLE hRenderEvent = CreateEvent(NULL, false, false, NULL);
    hr = pRenderAudioClient->SetEventHandle(hRenderEvent);
    if (FAILED(hr))
    {
        ERROR_PRINT("SetEventHandle eRender Fail !!!");
        return 1;
    }

    IAudioCaptureClient* pCaptureClient = nullptr;
    hr = pCaptureAudioClient->GetService(IID_IAudioCaptureClient, (void**)&pCaptureClient);
    if (FAILED(hr))
    {
        ERROR_PRINT("GetService eCapture Fail !!!");
        return 1;
    }

    IAudioRenderClient* pRenderClient = nullptr;
    hr = pRenderAudioClient->GetService(IID_IAudioRenderClient, (void**)&pRenderClient);
    if (FAILED(hr))
    {
        ERROR_PRINT("GetService eRender Fail !!!");
        return 1;
    }



    // Start recording.
    hr = pCaptureAudioClient->Start();
    if (FAILED(hr))
    {
        ERROR_PRINT("Start eCapture Fail !!!");
        return 1;
    }

    hr = pRenderAudioClient->Start();
    if (FAILED(hr))
    {
        ERROR_PRINT("Start eRender Fail !!!");
        return 1;
    }


    HANDLE fileHandle;
    DWORD  dwActualBytes = 0;

    fileHandle = CreateFile("example.wav", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (fileHandle == INVALID_HANDLE_VALUE) {
        ERROR_PRINT("Failed to open file.");
        return 1;
    }

    if (!WriteFile(fileHandle, &file.wav_Header, 44, &dwActualBytes, NULL)) {
        ERROR_PRINT("Failed to write data to file.");
        return 1;
    }

    if (dwActualBytes != 44)
    {
        ERROR_PRINT("Written WAV header Fail !!!  dwActualBytes: " << dwActualBytes);
        return 1;
    }

    int  n_lao_cishu_Cnt = 0;//记录采集循环次数

    UINT32 packetLength = 0;
    UINT32 iCaptureFrames = 0;
    UINT32 iRenderFrames = iRender_BufferSize;
    BYTE* pCpatureData = nullptr;
    BYTE* pRenderData = nullptr;
    DWORD  dwCaptureFlag = 0;
    DWORD  dwBytesFrames = 0;
    DWORD  dwTotalWriteSize = 0;
    DWORD  dwTotalReadSize = 0;
    DWORD  dwTotalFileSize = file.wav_Header.dwIdentifier_Size;
    DWORD  dwReadSize = 0;
    UINT32  iPaddingFrames = 0;
    while (true)
    {
        WaitForMultipleObjects(1, &hCaptureEvent, FALSE, INFINITE);

        hr = pCaptureClient->GetNextPacketSize(&packetLength);
        if (FAILED(hr))
        {
            ERROR_PRINT("GetNextPacketSize eCapture Fail !!!");
            return 1;
        }

        hr = pCaptureClient->GetBuffer(&pCpatureData, &iCaptureFrames, &dwCaptureFlag, NULL, NULL);
        if (FAILED(hr))
        {
            ERROR_PRINT("GetBuffer eCapture Fail !!!");
            return 1;
        }

        dwBytesFrames = iCaptureFrames * pwfx.nBlockAlign;

        if (dwCaptureFlag & AUDCLNT_BUFFERFLAGS_SILENT)
        {
            BYTE* buffer = new BYTE[dwBytesFrames];
            ZeroMemory(buffer, dwBytesFrames);
            if (!WriteFile(fileHandle, buffer, dwBytesFrames, &dwActualBytes, NULL)) {
                ERROR_PRINT("Failed to write data to file.");
                return 1;
            }
            if (dwActualBytes != dwBytesFrames)
            {
                ERROR_PRINT("Written WAV data Fail !!!  dwActualBytes: " << dwActualBytes);
                return FALSE;
            }

            dwTotalWriteSize += dwActualBytes;
            delete[] buffer;
        }
        else
        {
            // 写入数据到文件
            if (!WriteFile(fileHandle, pCpatureData, dwBytesFrames, &dwActualBytes, NULL)) {
                ERROR_PRINT("Failed to write data to file.");
                return 1;
            }
            if (dwActualBytes != dwBytesFrames)
            {
                ERROR_PRINT("Written WAV data Fail !!!  dwActualBytes: " << dwActualBytes);
                return 1;
            }
            dwTotalWriteSize += dwActualBytes;
        }

        hr = pCaptureClient->ReleaseBuffer(iCaptureFrames);
        if (FAILED(hr))
        {
            ERROR_PRINT("ReleaseBuffer eCapture Fail !!!");
            return 1;
        }

        //==========================================

        WaitForMultipleObjects(1, &hRenderEvent, FALSE, INFINITE);
        hr = pRenderClient->GetBuffer(iRenderFrames, &pRenderData);
        if (FAILED(hr))
        {
            ERROR_PRINT("GetBuffer eRender Fail !!!");
            return 1;
        }

        dwBytesFrames = iRenderFrames * pwfx.nBlockAlign;


        if (pRenderData)
        {
            ZeroMemory(pRenderData, dwBytesFrames);
            if (dwTotalReadSize >= dwTotalFileSize) {
                hr = pRenderClient->ReleaseBuffer(iRenderFrames, 0);
                if (FAILED(hr))
                {
                    ERROR_PRINT("ReleaseBuffer1 eRender Fail !!!");
                    return 1;
                }
                break;
            }
            else if (dwTotalReadSize + dwBytesFrames < dwTotalFileSize) {
                dwReadSize = fread(pRenderData, 1, dwBytesFrames, file.file);

                if (dwReadSize != dwBytesFrames)
                {
                    ERROR_PRINT("Read WAV data Fail !!!  dwReadSize: " << dwReadSize);
                    return 1;
                }

                dwTotalReadSize += dwReadSize;
            }
            else {
                dwBytesFrames = dwTotalFileSize - dwTotalReadSize;
                dwReadSize = fread(pRenderData, 1, dwBytesFrames, file.file);
                if (dwReadSize != dwBytesFrames)
                {
                    ERROR_PRINT("Read WAV data Fail !!!  dwReadSize: " << dwReadSize);
                    return 1;
                }

                dwTotalReadSize = dwTotalFileSize;
            }
        }


        hr = pRenderClient->ReleaseBuffer(iRenderFrames, 0);
        if (FAILED(hr))
        {
            ERROR_PRINT("ReleaseBuffer eRender Fail !!!");

            return 1;
        }

        hr = pRenderAudioClient->GetCurrentPadding(&iPaddingFrames);
        if (FAILED(hr))
        {
            ERROR_PRINT("GetCurrentPadding eRender Fail !!!");
            return 1;
        }

        iRenderFrames = iRender_BufferSize - iPaddingFrames;

    }
    hr = pCaptureAudioClient->Stop();  // Stop recording.
    if (FAILED(hr))
    {
        ERROR_PRINT("Stop pCapture Fail !!!");
        return 1;
    }

    hr = pRenderAudioClient->Stop();  // Stop recording.
    if (FAILED(hr))
    {
        ERROR_PRINT("Stop eRender Fail !!!");
        return 1;
    }





    // 将文件指针移动到文件开头
    SetFilePointer(fileHandle, 4, NULL, FILE_BEGIN);
    dwTotalWriteSize += 36;

    if (!WriteFile(fileHandle, &dwTotalWriteSize, 4, &dwActualBytes, NULL)) {
        ERROR_PRINT("Failed to write data to file.");
        return 1;
    }
    if (dwActualBytes != 4)
    {
        ERROR_PRINT("Written WAV data Fail !!!  dwActualBytes: " << dwActualBytes);
        return 1;
    }

    SetFilePointer(fileHandle, 32, NULL, FILE_CURRENT);
    dwTotalWriteSize -= 36;

    if (!WriteFile(fileHandle, &dwTotalWriteSize, 4, &dwActualBytes, NULL)) {
        ERROR_PRINT("Failed to write data to file.");
        return 1;
    }
    if (dwActualBytes != 4)
    {
        ERROR_PRINT("Written WAV data Fail !!!  dwActualBytes: " << dwActualBytes);
        return 1;
    }

    CloseHandle(fileHandle);


    if (hCaptureEvent)
    {
        CloseHandle(hCaptureEvent);
        hCaptureEvent = NULL;
    }
    if (hRenderEvent)
    {
        CloseHandle(hRenderEvent);
        hRenderEvent = NULL;
    }
    if (pCaptureClient)
    {
        pCaptureClient->Release();
        pCaptureClient = NULL;
    }
    if (pRenderClient)
    {
        pRenderClient->Release();
        pRenderClient = NULL;
    }
    if (pCaptureAudioClient)
    {
        pCaptureAudioClient->Release();
        pCaptureAudioClient = NULL;
    }
    if (pRenderAudioClient)
    {
        pRenderAudioClient->Release();
        pRenderAudioClient = NULL;
    }
    if (pCaptureDevice)
    {
        pCaptureDevice->Release();
        pCaptureDevice = NULL;
    }
    if (pRenderDevice)
    {
        pRenderDevice->Release();
        pRenderDevice = NULL;
    }
    if (pEnumerator)
    {
        pEnumerator->Release();
        pEnumerator = NULL;
    }


    CoUninitialize();//CoUninitialize关闭当前线程的COM库,卸载线程加载的所有dll,释放任何其他的资源,关闭在线程上维护所有的RPC连接。



    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值