DirectSound入门指南(1)录制声音

谢阳曜
2023-12-01

在上一篇文章《DirectSound播放声音入门指南(0)》基础上对声音的录制进行研究,形成了本文。同时,本文也主要参考了Doubango开源项目(该项目现在已经难以维护了,因为其功能太强大了,它什么都想做)。它是一个优秀的声音处理开源框架,不仅有完整的声音录制与播放机制,同时还实现了N种音频编解码。但是,它基于sip协议的通信机制,明文而复杂的数据包,已经被时代所抛弃了。


目录:

回顾消费者

在Doubango项目中,DirectSound被封装为两大类:

  • 生产者,Producer;
  • 消费者,Consumer。

其中,消费者就是上一篇文章《DirectSound播放声音入门指南(0)》中所讲到的声音的播放。其声音播放的机制是这样的:

  1. 网络音频数据流(被封装为RTP包);
  2. RTP解包->TCP包;
  3. 获得编码后的音频流(假设采用opus编码)
  4. 调用decode解码器,获得PCM音频脉冲;
  5. 将数据put入,辅助缓冲中,完成播放。

RTP就是在音频包上封装以个RTP协议头。opus是目前一种极为优秀的编码格式,笔者在测试过程中,基本上320字节的音频包被编码为14->18字节左右,而且音质还极棒,可见opus的可怕之处。

Doubango另一个极为优秀的地方,就是它有一个抖动缓冲区。在封RTP包的时候,会根据时间戳计算播放时间(因为已经不是PCM音频流了,无法通过data_size计算),而且RTP头中分装了ssrc,这个用来记录是否切换了用户(就是说话人有没有变化)。一旦变化,就要清空各种缓冲,所以,笔者在实现的时候直接是这样:

static const int ssrc = clock();

声音录制

扯了很长时间的音频播放,其实就是对上一篇文章的补充。

下面将正式开始讲,DirectSound音频录制:

很多原理性的东西,已经在上一篇文章中简单提到了。本文中不打算唠叨这些原理,而是直接给出了C/C++简单实现的DEMO,同上文一样,本文CODE也是来自于开源项目Doubango,笔者自己独立出来的录音部分。

这里,我并不打算给出网络、编解码、RTP包封装、抖动缓冲区实现,而是只给出了PCM脉冲音频的录音,直接存入文件中。当然,其实我也只实现了前三种,抖动缓冲区我并没有深入研究。

DEMO一向很简单粗暴,我只关注具体函数的实现,不去干任何多余的东西。

  • 启动录音
  • 停止录音

就是这些简单的东西。

下面我只是将头文件搬运到博客中来了,我并不打算将.cpp实现也粘贴上来。

因为我已经将完整DEMO放在我的git上了,你可以点击这里《DirectSound录制声音实现》就会进入到我的git。

Wow!所有源码都在上面,注释都有,你看起来并不会感到困难。当然,某些人也会有疑问。可以在这里找到我williamaiden(at)126.com

#ifndef WIN32_AUDIO_CONTROL_DSRECORDER_H
#define WIN32_AUDIO_CONTROL_DSRECORDER_H

#include <dsound.h>
#include <stdint.h>
#include <stdio.h>
#include "Config.h"

#pragma comment (lib,"dsound.lib")
#pragma comment (lib,"dxguid.lib")

#if !defined(RECORDER_NOTIF_POS_COUNT)
#   define RECORDER_NOTIF_POS_COUNT     10
#endif /* RECODER_NOTIF_POS_COUNT */

/*开关,是否将数据写入文件*/
#define OPEN_SAVE_CAPTUREBUFFER_TO_FILE 1

typedef struct RECODER{
    RECODER(){
        device = NULL;
        captureBuffer = NULL;
        started = false;
        bytes_per_notif_ptr = NULL;
#if OPEN_SAVE_CAPTUREBUFFER_TO_FILE
        fp = NULL;
#endif
    }
    LPDIRECTSOUNDCAPTURE device;
    LPDIRECTSOUNDCAPTUREBUFFER captureBuffer;
    HANDLE notifEvents[RECORDER_NOTIF_POS_COUNT];
    bool started;
    size_t bytes_per_notif_size;
    uint8_t* bytes_per_notif_ptr;
    HANDLE tid[2];
#if OPEN_SAVE_CAPTUREBUFFER_TO_FILE
    FILE* fp;
#endif
} Recorder;

/*准备录音*/
int prepare(Recorder* ds);
/*开始录音*/
int startRecorder(Recorder* ds);
/*挂起录音*/
int suspendRecorder(Recorder* ds);
int resumeRecorder(Recorder* ds);
int stopRecorder(Recorder* ds);
/*释放录音资源*/
int unprepare(Recorder* ds);
DWORD WINAPI recorderThreadImpl(LPVOID params);

#if OPEN_SAVE_CAPTUREBUFFER_TO_FILE
int openFile(Recorder* ds);
int closeFile(Recorder* ds);
#endif

#endif
 类似资料: