前言:
首先声明,对于WASAPI实现播放与录制仅处于新手阶段,code中有不足的地方希望指出....谢谢.
边播边录是可以使用音频线将Microphone(麦克风)和Line-Out(喇叭)接在一起,然后执行该程式就能够将你所需要的播放的音频文件(我是用的是.wav音频文件)录制下来;同样Line-In(音频输入)和Line-Out(喇叭)也可以这样子操作;
程式是能够运行,但是实际测试录制下来的音频文件声音大于原始音频文件且声音清晰度降低,由于我刚接触,所以对此部分也不太明白.
如果想单独执行录制/播放的功能,在Main.cpp中将其中一个功能去掉即可,Code中已经做了录音和播放的分离。
参考的文章:
WAV音频文件格式分析:
带你分析wav音频文件结构(实例+代码)_wav 声音 数据结构-CSDN博客https://blog.csdn.net/ljrsunshine/article/details/89320026/
WAVE PCM soundfile formathttp://soundfile.sapp.org/doc/WaveFormat/
WASAPI:
音频-基于Core Audio技术采集音频(版本2)_xta audiocore-CSDN博客https://blog.csdn.net/wenluderen/article/details/123014648#:~:text=%E9%87%87%E7%94%A8windows用户模式音频组件 - Win32 apps | Microsoft Learn
https://learn.microsoft.com/zh-cn/windows/win32/coreaudio/user-mode-audio-components 音频终结点设备 - Win32 apps | Microsoft Learn
https://learn.microsoft.com/zh-cn/windows/win32/coreaudio/audio-endpoint-devices 设备拓扑 - Win32 apps | Microsoft Learn
https://learn.microsoft.com/zh-cn/windows/win32/coreaudio/device-topologies
工具:
文字转语音|语音合成 - 在线文字转换语音软件 - 迅捷PDF转换器在线免费版 (xunjiepdf.com)https://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;
}