使用 DirectSound 录制麦克风音频
使用 DirectSound 录制麦克风音频
本文所有代码均可在以下仓库找到
https://gitcode.net/PeaZomboss/learnaudios
目录是demo/dscapture
之前那篇文章简单介绍了DirectSound,并用其实现了对WAV格式文件的播放操作,本文将继续聚焦于DirectSound,但目标变成了用其实现对麦克风音频的录制,并将其保存为WAV格式的文件。
DirectSound录制的方法和播放的方法其实差不多,都是使用循环+多缓冲的方法,在实际写代码的过程中没有什么很大的不同,也无需过多解释,所以本文主要就是展示一些关键代码。
关于IDirectSoundCapture等接口的具体定义还是建议直接看文档就行了。
https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ee416960(v=vs.85)
接口定义
接口和播放器的非常相似,仅有部分小改动
// 参数1:缓冲区指针
// 参数2:缓冲区大小(单位字节)
// 参数3:用户自定义指针
typedef void (*CopyBufCallback)(void *, int, void *);
DWord WINAPI copy_thread(void *parameter);
class Recorder
{
private:
IDirectSoundCapture *dsc;
IDirectSoundCaptureBuffer *dscb;
CopyBufCallback copybuf_func;
void *copybuf_ctx;
WAVEFORMATEX fmtex;
DWord block_size;
DWord buf_size;
HANDLE events[3];
HANDLE h_copy_thread; // copy_thread线程句柄
bool recording; // 录制中(包括暂停)
bool suspended; // 是否挂起,用于暂停
void copy_buf(void *ptr, int len); // 方便调用
public:
friend DWord WINAPI copy_thread(void *parameter);
Recorder();
Recorder(GUID *device);
~Recorder();
void set_block_size(DWord bs);
void set_fmt(const WAVEFORMATEX &fmtex);
void set_copy_buf_callback(CopyBufCallback copy, void *ctx);
void start();
void stop();
void pause();
void resume();
};
具体实现
对于录制功能的实现,使用简单的双缓冲即可,目前暂未发现一些问题。
线程实现如下:
DWord WINAPI copy_thread(void *parameter)
{
Recorder *recorder = (Recorder *)parameter;
void *ptr;
DWord len;
while (true) {
DWORD res = WaitForMultipleObjects(3, &recorder->events[0], FALSE, INFINITE);
if (res == 0) { // 录制第一段缓冲区时保存第二段
recorder->dscb->Lock(recorder->buf_size, recorder->buf_size, &ptr, &len, NULL, NULL, 0);
recorder->copy_buf(ptr, len);
recorder->dscb->Unlock(ptr, len, NULL, 0);
}
else if (res == 1) { // 同理保存第一段
recorder->dscb->Lock(0, recorder->buf_size, &ptr, &len, NULL, NULL, 0);
recorder->copy_buf(ptr, len);
recorder->dscb->Unlock(ptr, len, NULL, 0);
}
else { // 保存剩下部分
DWord pos;
recorder->dscb->Stop();
recorder->dscb->GetCurrentPosition(&pos, NULL);
// 根据当前录制位置确定是在第一段还是第二段缓冲区
if (pos > recorder->buf_size)
recorder->dscb->Lock(recorder->buf_size, pos - recorder->buf_size, &ptr, &len, NULL, NULL, 0);
else
recorder->dscb->Lock(0, pos, &ptr, &len, NULL, NULL, 0);
recorder->copy_buf(ptr, len);
recorder->dscb->Unlock(ptr, len, NULL, 0);
break;
}
}
printf("Recording thread end.\n");
return 0;
}
然后是start()的实现:
void Recorder::start()
{
if (dscb || recording) return;
buf_size = block_size * fmtex.nBlockAlign;
DSCBUFFERDESC dscbd;
memset(&dscbd, 0, sizeof(DSCBUFFERDESC));
dscbd.dwSize = sizeof(DSCBUFFERDESC);
dscbd.dwBufferBytes = buf_size * 2; // 使用双缓冲
dscbd.lpwfxFormat = &fmtex;
HRESULT hr = dsc->CreateCaptureBuffer(&dscbd, &dscb, NULL);
if (FAILED(hr)) {
printf("Can not create capture buffer\n");
return;
}
void *ptr;
DWord len;
dscb->Lock(0, 0, &ptr, &len, NULL, NULL, DSCBLOCK_ENTIREBUFFER);
memset(ptr, 0, len); // 清空整个缓冲区
dscb->Unlock(ptr, len, NULL, 0);
IDirectSoundNotify *dsn;
hr = dscb->QueryInterface(_iid_IDirectSoundNotify, (void **)&dsn);
if (FAILED(hr)) {
printf("Can not query direct sound notify\n");
return;
}
DSBPOSITIONNOTIFY dsbpn[2];
dsbpn[0].dwOffset = 0;
dsbpn[0].hEventNotify = events[0];
dsbpn[1].dwOffset = buf_size;
dsbpn[1].hEventNotify = events[1];
dsn->SetNotificationPositions(2, &dsbpn[0]); // 设置通知位置
dsn->Release();
recording = true;
dscb->Start(DSCBSTART_LOOPING);
printf("Recording ...\n");
DWord copy_thread_id = 0;
h_copy_thread = CreateThread(NULL, 0, copy_thread, this, 0, ©_thread_id);
printf("Record thread handle: %d, thread id: %d\n", h_copy_thread, copy_thread_id);
suspended = false;
}
stop(),pause(),resume()的实现:
void Recorder::stop()
{
if (recording) {
printf("Stopping...\n");
SetEvent(events[2]);
WaitForSingleObject(h_copy_thread, 1000); // 等待线程结束
dscb->Release();
dscb = NULL;
recording = false;
printf("Done...\n");
}
}
void Recorder::pause()
{
if (recording && !suspended) {
dscb->Stop();
suspended = true;
printf("pause\n");
}
}
void Recorder::resume()
{
if (recording && suspended) {
dscb->Start(DSCBSTART_LOOPING);
suspended = false;
printf("resume\n");
}
}
主程序
为了方便将数据写入文件,使用全局变量如下:
FILE *fp;
DWord total_len = 0; // 保存实际写入的数据长度(单位字节)
然后是保存缓冲区数据的方法:
void copy_buf(void *buf, int len, void *ctx)
{
int wt_size = fwrite(buf, 1, len, fp);
total_len += wt_size; // 累加写入的长度
}
然后就是主函数:
int main()
{
char fn[256];
SYSTEMTIME st;
GetLocalTime(&st); // 获取当前时间
// 根据时间生成文件名
sprintf(fn, "%d-%d-%d_%02d%02d%02d.wav", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
fp = fopen(fn, "wb");
RIFFHeader rh;
strncpy(rh.id.chr, "RIFF", 4);
rh.size = 0; // 暂时填零
strncpy(rh.type.chr, "WAVE", 4);
fwrite(&rh, sizeof(RIFFHeader), 1, fp); // 写入RIFF头
RIFFChunkHeader rch;
strncpy(rch.id.chr, "fmt ", 4);
rch.size = sizeof(WAVEFORMATEX) - sizeof(WORD); // 不要多余的cbSize字段
fwrite(&rch, sizeof(RIFFChunkHeader), 1, fp); // 写入fmt头
WAVEFORMATEX fmtex;
fmtex.wFormatTag = 1;
fmtex.nChannels = 2;
fmtex.nSamplesPerSec = 16000;
fmtex.wBitsPerSample = 16;
fmtex.nBlockAlign = 4;
fmtex.nAvgBytesPerSec = 16000 * 4;
fmtex.cbSize = 0;
fwrite(&fmtex, sizeof(WAVEFORMATEX) - sizeof(WORD), 1, fp); // 写入fmt内容
strncpy(rch.id.chr, "data", 4);
rch.size = 0; // 同理
fwrite(&rch, sizeof(RIFFChunkHeader), 1, fp); // 写入data头
Recorder recorder;
recorder.set_fmt(fmtex);
recorder.set_copy_buf_callback(copy_buf, NULL);
recorder.start();
int command = 0;
printf("Input 'q' to quit, 'p' to pause, 'r' to resume\n");
do {
command = getchar();
if (command == 'p')
recorder.pause();
else if (command == 'r')
recorder.resume();
} while (command != 'q');
recorder.stop();
printf("Total got %d (bytes) data\n", total_len);
// 回到文件头写入实际的大小信息
DWord riff_size = total_len + 44 - 8; // 头部共占用44字节内容
fseek(fp, 4, SEEK_SET); // 此处使用硬编码,实际使用不推荐
fwrite(&riff_size, 4, 1, fp); // 重新写入实际的大小
fseek(fp, 40, SEEK_SET); // 同上
fwrite(&total_len, 4, 1, fp);
fclose(fp);
printf("Saved to file \"%s\", size: %d\n", fn, riff_size + 8);
}
使用 DirectSound 录制麦克风音频的更多相关文章
- javaCV开发详解之5:录制音频(录制麦克风)到本地文件/流媒体服务器(基于javax.sound、javaCV-FFMPEG)
javaCV系列文章: javacv开发详解之1:调用本机摄像头视频 javaCV开发详解之2:推流器实现,推本地摄像头视频到流媒体服务器以及摄像头录制视频功能实现(基于javaCV-FFMPEG.j ...
- 如何用FFmpeg API采集摄像头视频和麦克风音频,并实现录制文件的功能
之前一直用Directshow技术采集摄像头数据,但是觉得涉及的细节比较多,要开发者比较了解Directshow的框架知识,学习起来有一点点难度.最近发现很多人问怎么用FFmpeg采集摄像头图像,事实 ...
- Windows Phone 8初学者开发—第20部分:录制Wav音频文件
原文 Windows Phone 8初学者开发—第20部分:录制Wav音频文件 原文地址:http://channel9.msdn.com/Series/Windows-Phone-8-Develop ...
- 公布一个软件,轻新视频录播程序,H264/AAC录制视音频,保存FLV,支持RTMP直播
已经上传到CSDN,下载地址:http://download.csdn.net/detail/avsuper/7421647,不要钱滴,嘿嘿... 本程序能够把摄像头视频和麦克风音频,录制为FLV文件 ...
- html5 录制mp3音频,支持采样率和比特率设置
13年的时候做过html5录音,一个问题是保存的wav格式文件很大,当初用了一个迂回的方式,上传到服务器后调用 lame 编码器转换,但由于文件大,上传较慢.不得不说,前端技术发展真是日新月异,有人实 ...
- Android OpenSL ES 开发:Android OpenSL 录制 PCM 音频数据
一.实现说明 OpenSL ES的录音要比播放简单一些,在创建好引擎后,再创建好录音接口基本就可以录音了.在这里我们做的是流式录音,所以需要用至少2个buffer来缓存录制好的PCM数据,这里我们可以 ...
- Kubuntu麦克风音频无声音
前段时间买了新本,装了双系统,win8和kubuntu 14.04,使用的过程感觉都不错,因为平时玩游戏看视频是用win8,但最近打算在kubuntu上听音乐时,发现音频没有声音,麦克风也没有声音,这 ...
- wp8 自定义相机+nokia滤镜+录制amr音频
demo截图: 代码量有点多,就不贴出来了. 备注: 1.自定义相机主要横竖屏时,对相机进行旋转. 2.播放amr格式可以在页面中直接添加MediaElement控件进行播放,或者使用Bac ...
- 使用FlashWavRecorder实现浏览器录制wav音频和上传音频文件,兼容IE8以上浏览器
前言:本项目基于github开源插件实现,该插件使用flash实现,兼容IE8以上浏览器 感谢michalstocki的分享该项目,github项目地址:https://github.com/mich ...
- Android MediaRecorder录制播放音频
1.请求录制音频权限 <user-permission android:name="android.permission.RECORD_AUDIO"/> RECORD_ ...
随机推荐
- 【iOS逆向与安全】frida-trace入门
前言 frida-trace是一个用于动态跟踪函数调用的工具.支持android和ios.安装教程请参考官网.工欲善其事必先利其器.本文将以某App为示范,演示frida-trace的各种方法在iOS ...
- 关于linux更改root用户下面的鼠标样式
前言 这几天一直研究关于 lightmd 显示管理器(也就是刚进入系统的用户登录界面)的主题配置问题,我发现鼠标样式和登录个人用户的鼠标样式不一样(之前我也发现了,懒得捣鼓)今天抽出时间研究了一下,这 ...
- SpringCloud Alibaba(三) - GateWay网关
1.基本环境搭建 1.1 依赖 <!-- Gatway 网关会和springMvc冲突,不能添加web依赖 --> <dependency> <groupId>or ...
- TKE 超级节点,Serverless 落地的最佳形态
陈冰心,腾讯云产品经理,负责超级节点迭代与客户拓展,专注于 TKE Serverless 产品演进. 背景 让人又爱又恨的 Serverless Serverless 炙手可热,被称为云原生未来发展的 ...
- python-面向过程与函数式
面向过程与函数式 面向过程 "面向过程"核心是"过程"二字,"过程"指的是解决问题的步骤,即先干什么再干什么......,基于面向过程开发程 ...
- Forest + IDEA = 双倍快乐!ForestX 隆重登场
Forest + IDEA = 双倍快乐!ForestX 隆重登场 Forest 是一款声明式的 Java 开源 HTTP 框架,相比它的前辈 Httpclient 和 OkHttp 更简明易懂.也更 ...
- 使用PyLint分析评估代码质量
什么是PyLint PyLint是一款用于评估Python代码质量的分析工具,它诞生于2003年,其最初十年的主要作者和维护者是Sylvain Thénault.PyLint可以用来检查代码是否错误. ...
- IdentityServer4的最佳使用
简介 本人做微服务项目也有一段时间了,在微服务中让我感触颇深的就是这个IdentityServer4了,ID4(IdentityServer4的简称)中涉及的概念颇多,本文不谈概念(我怕读者没耐心 ...
- VUE 使用md5对用户登录密码进行加密传输
VUE 使用md5对用户登录密码进行加密传输到数据库 前言 第一步 npm下载js-md5依赖包 第二步 引入js-md5 直接在需要使用md5加密的页面引入 全局挂载,将js-md5添加到vue原型 ...
- error: expected ‘)’ before ‘PRIx64’
打印uint64时编译报错 printf("prefix:0x%"PRIx64"\n",ipv6Prefix); 解决办法:添加头文件 #include < ...