【obs-studio开源项目从入门到放弃】win-wasapi 音频的采集

文章目录

    • 前言
      • win-audio-capture 应用音频输出捕获
    • 创建windows音频捕获插件
    • 音频捕获插件的初始化
    • 捕获的音频输出
    • 总结
    • 技术参考


前言

obs系列文章入口:https://blog.csdn.net/qq_33844311/article/details/121479224

windows平台下obs官方自带的音频插件只有一个 win-wasapi ,负责采集扬声器和麦克风的声音。

win-audio-capture 应用音频输出捕获

这里推荐一个非官方的音频捕获插件 win-audio-capture,可以采集指定进程的音频输入到obs。
obs论坛:https://obsproject.com/forum/threads/win-capture-audio.147240/
项目地址:https://github.com/bozbez/win-capture-audio
这个插件的使用对 win10 版本有最低要求 Windows 10 2004 (released 2020-05-27) or later.
视频介绍:https://www.bilibili.com/video/av676469967

创建windows音频捕获插件

obs音频采集插件在程序启动的时候就创建好了,包括扬声器声音捕获 wasapi_output_capture 和麦克风声音捕获 wasapi_input_capture 。两者管理都是通过 class WASAPISource 对象来管理。
以下是创建音频插件的调用堆栈。

>	win-wasapi.dll!CreateWASAPISource(obs_data * settings, obs_source * source, bool input)1044	C++
 	win-wasapi.dll!CreateWASAPIOutput(obs_data * settings, obs_source * source)1062	C++
 	obs.dll!obs_source_create_internal(const char * id, const char * name, obs_data * settings, obs_data * hotkey_data, bool private, unsigned int last_obs_ver)387	C
 	obs.dll!obs_source_create_set_last_ver(const char * id, const char * name, obs_data * settings, obs_data * hotkey_data, unsigned int last_obs_ver)432	C
 	obs.dll!obs_load_source_type(obs_data * source_data)1781	C
 	obs.dll!obs_load_source(obs_data * source_data)1890	C
 	obs64.exe!LoadAudioDevice(const char * name, int channel, obs_data * parent)747	C++
 	obs64.exe!OBSBasic::LoadData(obs_data * data, const char * file)1026	C++
 	obs64.exe!OBSBasic::Load(const char * file)970	C++
 	obs64.exe!OBSBasic::OBSInit()1893	C++
 	obs64.exe!OBSApp::OBSInit()1474	C++
 	obs64.exe!run_program(std::basic_fstream<char,std::char_traits<char>> & logFile, int argc, char * * argv)2138	C++
 	obs64.exe!main(int argc, char * * argv)2839	C++
 	obs64.exe!WinMain(HINSTANCE__ * __formal, HINSTANCE__ * __formal, char * __formal, int __formal)97	C++

音频捕获插件的初始化

音频的捕获工作在 WASAPISource 对象的构造函数里的完成所有的初始化工作。还注册了默认音频设备切换的回调通知,win8以上系统使用了微软的实时调度工作队列相关api,微软声称,如果使用他们的API,它可以更好地安排音频。这个是我在提交日志里面看到。
对于不支持的系统创建了 WASAPISource::CaptureThread 音频采集队列来捕获windows的声音。

提交: 24d82062aecca2f702dbf1cd7a85e84036b24ccf [24d8206]
作者: jpark37 [email protected] 日期: 2021年9月26日 4:21:22

下面贴一下管理音频捕获对象的构造函数,通过注释的方式,分析一下创建过程。

WASAPISource::WASAPISource(obs_data_t *settings, obs_source_t *source_, bool input)
	: source(source_),
	  isInputDevice(input),		// true: 表示创建麦克风音频捕获  false:表示创建扬声器音频捕获
	  startCapture(this),	
	  sampleReady(this),
	  restart(this)
{
	// 获取音频设备id 是否使用设备时间  是否使用默认音频设备
	UpdateSettings(settings);
	// 各种event的创建
	idleSignal = CreateEvent(nullptr, true, false, nullptr);
	if (!idleSignal.Valid())
		throw "Could not create idle signal";

	stopSignal = CreateEvent(nullptr, true, false, nullptr);
	if (!stopSignal.Valid())
		throw "Could not create stop signal";

	receiveSignal = CreateEvent(nullptr, false, false, nullptr);
	if (!receiveSignal.Valid())
		throw "Could not create receive signal";

	restartSignal = CreateEvent(nullptr, true, false, nullptr);
	if (!restartSignal.Valid())
		throw "Could not create restart signal";

	exitSignal = CreateEvent(nullptr, true, false, nullptr);
	if (!exitSignal.Valid())
		throw "Could not create exit signal";

	initSignal = CreateEvent(nullptr, false, false, nullptr);
	if (!initSignal.Valid())
		throw "Could not create init signal";

	reconnectSignal = CreateEvent(nullptr, false, false, nullptr);
	if (!reconnectSignal.Valid())
		throw "Could not create reconnect signal";

	// 创建重新初始化音频采集的线程
	reconnectThread = CreateThread(
		nullptr, 0, WASAPISource::ReconnectThread, this, 0, nullptr);
	if (!reconnectThread.Valid())
		throw "Failed to create reconnect thread";
		
	// 创建默认音频设备切换的通知对象 IMMNotificationClient
	notify = new WASAPINotify(this);
	if (!notify)
		throw "Could not create WASAPINotify";
	
	HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr,
				      CLSCTX_ALL,
				      IID_PPV_ARGS(enumerator.Assign()));
	if (FAILED(hr))
		throw HRError("Failed to create enumerator", hr);
	// 注册通知
	hr = enumerator->RegisterEndpointNotificationCallback(notify);
	if (FAILED(hr))
		throw HRError("Failed to register endpoint callback", hr);

	/* OBS will already load DLL on startup if it exists */
	// 检测系统是否支持 实时工作队列新的api (win8及以上系统才支持)
	// true:支持创建工作队列调度音频的捕获
	// false:不支持则创建 CaptureThread 线程负责音频的捕获
	const HMODULE rtwq_module = GetModuleHandle(L"RTWorkQ.dll");
	rtwq_supported = rtwq_module != NULL;
	if (rtwq_supported) {
		rtwq_unlock_work_queue =
			(PFN_RtwqUnlockWorkQueue)GetProcAddress(
				rtwq_module, "RtwqUnlockWorkQueue");
		rtwq_lock_shared_work_queue =
			(PFN_RtwqLockSharedWorkQueue)GetProcAddress(
				rtwq_module, "RtwqLockSharedWorkQueue");
		rtwq_create_async_result =
			(PFN_RtwqCreateAsyncResult)GetProcAddress(
				rtwq_module, "RtwqCreateAsyncResult");
		rtwq_put_work_item = (PFN_RtwqPutWorkItem)GetProcAddress(
			rtwq_module, "RtwqPutWorkItem");
		rtwq_put_waiting_work_item =
			(PFN_RtwqPutWaitingWorkItem)GetProcAddress(
				rtwq_module, "RtwqPutWaitingWorkItem");

		hr = rtwq_create_async_result(nullptr, &startCapture, nullptr,
					      &startCaptureAsyncResult);
		if (FAILED(hr)) {
			enumerator->UnregisterEndpointNotificationCallback(
				notify);
			throw HRError(
				"Could not create startCaptureAsyncResult", hr);
		}

		hr = rtwq_create_async_result(nullptr, &sampleReady, nullptr,
					      &sampleReadyAsyncResult);
		if (FAILED(hr)) {
			enumerator->UnregisterEndpointNotificationCallback(
				notify);
			throw HRError("Could not create sampleReadyAsyncResult",
				      hr);
		}

		hr = rtwq_create_async_result(nullptr, &restart, nullptr,
					      &restartAsyncResult);
		if (FAILED(hr)) {
			enumerator->UnregisterEndpointNotificationCallback(
				notify);
			throw HRError("Could not create restartAsyncResult",
				      hr);
		}

		DWORD taskId = 0;
		DWORD id = 0;
		hr = rtwq_lock_shared_work_queue(L"Capture", 0, &taskId, &id);
		if (FAILED(hr)) {
			enumerator->UnregisterEndpointNotificationCallback(
				notify);
			throw HRError("RtwqLockSharedWorkQueue failed", hr);
		}

		startCapture.SetQueueId(id);
		sampleReady.SetQueueId(id);
		restart.SetQueueId(id);
	} else {
		// =======创建 CaptureThread 线程负责音频的捕获 win7 操作系统走这个分支 ============
		captureThread = CreateThread(nullptr, 0,
					     WASAPISource::CaptureThread, this,
					     0, nullptr);
		if (!captureThread.Valid()) {
			enumerator->UnregisterEndpointNotificationCallback(
				notify);
			throw "Failed to create capture thread";
		}
	}
	// 开始音频捕获工作
	Start();
}

捕获的音频输出

下面的堆栈是使用微软的实时调度工作队列相关api的输出堆栈

	obs_source_output_audio(source, &data);		// obs的音频数据都是异步的输出
	win-wasapi.dll!WASAPISource::ProcessCaptureData()772	C++
 	win-wasapi.dll!WASAPISource::OnSampleReady()965	C++
 	win-wasapi.dll!WASAPISource::CallbackSampleReady::Invoke(IRtwqAsyncResult * __formal)141	C++

下面的堆栈是使用 CaptureThread 输出采集的音频数据

	obs_source_output_audio(source, &data);		// obs的音频数据都是异步的输出
 	win-wasapi.dll!WASAPISource::ProcessCaptureData()775	C++
 	win-wasapi.dll!WASAPISource::CaptureThread(void * param)863	C++

通过 obs_source_output_audio 输出到obs的音频队列里面,具体的音频的处理线程参考: audio_thread 音频编码线程

总结

windows下的音频采集是基于 windows audio session 相关api实现的音频采集工作。具体的api使用和介绍,我就不班门弄斧了,参考微软的官方文档即可。

以上都是个人工作当中对obs-studio开源项目的理解,难免有错误的地方,如果有欢迎指出。

若有帮助幸甚。


技术参考

  1. 视频技术参考: https://ke.qq.com/course/3202131?flowToken=1041285
  2. windows audio session : https://docs.microsoft.com/zh-cn/samples/microsoft/windows-universal-samples/windowsaudiosession/
  3. rtworkq:https://docs.microsoft.com/en-us/windows/win32/api/rtworkq/

你可能感兴趣的:(obs-studio入门到放弃,音视频,obs,was,windows,10,ffmpeg)