您当前的位置: 首页 > 

令狐掌门

暂无认证

  • 1浏览

    0关注

    513博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

obs源码分析【五】:音频采集线程

令狐掌门 发布时间:2021-07-05 21:02:54 ,浏览量:1

  在第三篇介绍了视频的线程,音频的线程代码也是在那一块儿:

if (!ResetAudio())
		throw "Failed to initialize audio";

  音频线程的创建是在audio_output_open函数中

int audio_output_open(audio_t **audio, struct audio_output_info *info)
{
	。。。
	if (pthread_create(&out->thread, NULL, audio_thread, out) != 0)
		goto fail;
	。。。
}

  obs在windows上采集音频用的是wasapi, 主要是在win-wasapi这个工程里面,在界面初始化时需要加载各种dll, 这里面就有个wasapi.dll,音频采集线程创建的代码如下

void WASAPISource::InitCapture()
{
	HRESULT res = client->GetService(__uuidof(IAudioCaptureClient),
					 (void **)capture.Assign());
	if (FAILED(res))
		throw HRError("Failed to create capture context", res);

	res = client->SetEventHandle(receiveSignal);
	if (FAILED(res))
		throw HRError("Failed to set event handle", res);

	//创建音频采集线程
	captureThread = CreateThread(nullptr, 0, WASAPISource::CaptureThread,
				     this, 0, nullptr);
	if (!captureThread.Valid())
		throw "Failed to create capture thread";

	client->Start();
	active = true;

	blog(LOG_INFO, "WASAPI: Device '%s' [%s Hz] initialized",
	     device_name.c_str(), device_sample.c_str());
}

  可以去看看线程函数 CaptureThread的代码

while (WaitForCaptureSignal(2, sigs, dur)) {
		if (!source->ProcessCaptureData()) {
			reconnect = true;
			break;
		}
	}

  在点击【开始录制】按钮后,会触发相应的windows事件,然后进入这个while循环,调用ProcessCaptureData去采集处理音频数据,主要就是调用下面这句代码:

res = capture->GetBuffer(&buffer, &frames, &flags, &pos, &ts);

  然后执行到下面这句代码

obs_source_output_audio(source, &data);

  再就是

void obs_source_output_audio(obs_source_t *source,
			     const struct obs_source_audio *audio)
{
	。。。
	source_output_audio_data(source, &data);
	。。。

  source_output_audio_data这个函数就很重要了,他需要处理音视频同步

static void source_output_audio_data(obs_source_t *source,
				     const struct audio_data *data)
{
	size_t sample_rate = audio_output_get_sample_rate(obs->audio.audio);
	struct audio_data in = *data;
	uint64_t diff;
	uint64_t os_time = os_gettime_ns();
	int64_t sync_offset;
	bool using_direct_ts = false;
	bool push_back = false;

	/* detects 'directly' set timestamps as long as they're within
	 * a certain threshold */
	if (uint64_diff(in.timestamp, os_time) timing_adjust = 0;
		source->timing_set = true;
		using_direct_ts = true;
	}

	if (!source->timing_set) {
		reset_audio_timing(source, in.timestamp, os_time);

	} else if (source->next_audio_ts_min != 0) {
		diff = uint64_diff(source->next_audio_ts_min, in.timestamp);

		/* smooth audio if within threshold */
		if (diff > MAX_TS_VAR && !using_direct_ts)
			handle_ts_jump(source, source->next_audio_ts_min,
				       in.timestamp, diff, os_time);
		else if (diff async_unbuffered && source->async_decoupled)
				source->timing_adjust = os_time - in.timestamp;
			in.timestamp = source->next_audio_ts_min;
		}
	}

	source->last_audio_ts = in.timestamp;
	source->next_audio_ts_min =
		in.timestamp + conv_frames_to_time(sample_rate, in.frames);

	in.timestamp += source->timing_adjust;

	pthread_mutex_lock(&source->audio_buf_mutex);

	if (source->next_audio_sys_ts_min == in.timestamp) {
		push_back = true;

	} else if (source->next_audio_sys_ts_min) {
		diff = uint64_diff(source->next_audio_sys_ts_min, in.timestamp);

		if (diff  MAX_TS_VAR) {
			reset_audio_timing(source, data->timestamp, os_time);
			in.timestamp = data->timestamp + source->timing_adjust;
		}
	}

	sync_offset = source->sync_offset;
	in.timestamp += sync_offset;
	in.timestamp -= source->resample_offset;

	source->next_audio_sys_ts_min =
		source->next_audio_ts_min + source->timing_adjust;

	if (source->last_sync_offset != sync_offset) {
		if (source->last_sync_offset)
			push_back = false;
		source->last_sync_offset = sync_offset;
	}

	if (source->monitoring_type != OBS_MONITORING_TYPE_MONITOR_ONLY) {
		if (push_back && source->audio_ts)
			source_output_audio_push_back(source, &in);
		else
			source_output_audio_place(source, &in);
	}

	pthread_mutex_unlock(&source->audio_buf_mutex);

	source_signal_audio_data(source, data, source_muted(source, os_time));
}

  关于音视频同步的内容,可以去看看ffplay的源码解析,有个阈值TS_SMOOTHING_THRESHOLD,需要把音频、视频的每一帧时间戳的差值TS_SMOOTHING_THRESHOLD做比较,然后做延时还是快进。

  wasapi是com接口,在使用之前需要做初始化,如下所示:

void WASAPISource::Initialize()
{
	HRESULT res;

	res = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr,
			       CLSCTX_ALL, __uuidof(IMMDeviceEnumerator),
			       (void **)enumerator.Assign());
	if (FAILED(res))
		throw HRError("Failed to create enumerator", res);

	if (!InitDevice())
		return;

	device_name = GetDeviceName(device);

	if (!notify) {
		notify = new WASAPINotify(this);
		enumerator->RegisterEndpointNotificationCallback(notify);
	}

	HRESULT resSample;
	IPropertyStore *store = nullptr;
	PWAVEFORMATEX deviceFormatProperties;
	PROPVARIANT prop;
	resSample = device->OpenPropertyStore(STGM_READ, &store);
	if (!FAILED(resSample)) {
		resSample =
			store->GetValue(PKEY_AudioEngine_DeviceFormat, &prop);
		if (!FAILED(resSample)) {
			if (prop.vt != VT_EMPTY && prop.blob.pBlobData) {
				deviceFormatProperties =
					(PWAVEFORMATEX)prop.blob.pBlobData;
				device_sample = std::to_string(
					deviceFormatProperties->nSamplesPerSec);
			}
		}

		store->Release();
	}

	InitClient();
	if (!isInputDevice)
		InitRender();
	InitCapture();
}

  win-wasapi.cpp这个文件,大部分都是对系统音频设备的操作,初始化wasapi, 获取设备,获取音频buffer等。

关注
打赏
1652240117
查看更多评论
立即登录/注册

微信扫码登录

0.0595s