您当前的位置: 首页 > 

令狐掌门

暂无认证

  • 3浏览

    0关注

    513博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

obs源码分析【三】:视频模块线程创建与参数初始化

令狐掌门 发布时间:2021-06-20 23:46:54 ,浏览量:3

  obs源码分析专栏地址: https://blog.csdn.net/yao_hou/category_11128777.html

  obs源码分析【二】介绍了录制的大概思路与obs的录制实现机制,本篇主要介绍obs的视频线程是如何创建和运行的。   在录制之前,音视频的一些参数得先知道吧,比如视频的帧率,输出位置,编码方式等,这些是在【设置】里面配置的,在OBS主界面启动的时候,会获取这些这些。

mainWindow = new OBSBasic();

mainWindow->setAttribute(Qt::WA_DeleteOnClose, true);
connect(mainWindow, SIGNAL(destroyed()), this, SLOT(quit()));

mainWindow->OBSInit();

  OBSInit这里面的信息也挺多的,我主要说说音频,视频的信息获取与设置

void OBSBasic::OBSInit()
{
	ProfileScope("OBSBasic::OBSInit");

	。。。
	。。。
	if (!InitBasicConfig())
		throw "Failed to load basic.ini";
	if (!ResetAudio())
		throw "Failed to initialize audio";

	ret = ResetVideo();

  经过前人的经验,可以知道ResetVideo的代码是很关键的,ResetVideo函数的前面主要是配置文件信息的获取与配置,配置文件的类是ConfigFile,在启动obs时,会调用它的Open方法,断点调试我们可以看到ini的路径是 C:\Users\你的用户名\AppData\Roaming\obs-studio\global.ini 根据当前路径,重设OBS的一些信息。   配置信息搞完后,出现了一个函数AttemptToResetVideo,根据字面意思,常尝试去重新设置视频啥的,没有一点新颖的地方。

ret = AttemptToResetVideo(&ovi);

  如果真的是这样那就算了,但恰恰就是这一句代码,非常的关键,它创建video线程,调用很多录制或推流编码之类的功能。

static inline int AttemptToResetVideo(struct obs_video_info *ovi)
{
	return obs_reset_video(ovi);
}

  我们进去看看,AttemptToResetVideo是如何创建线程的。跟踪代码后,我们会走到obs.c的这儿,前面的代码就不看了,主要看看最后一句。

int obs_reset_video(struct obs_video_info *ovi)
{
	。。。
	。。。
	return obs_init_video(ovi);
}

  obs_init_video做了线程创建的工作。

static int obs_init_video(struct obs_video_info *ovi)
{
	。。。
	。。。
#ifdef __APPLE__
	errorcode = pthread_create(&video->video_thread, NULL,
				   obs_graphics_thread_autorelease, obs);
#else
	errorcode = pthread_create(&video->video_thread, NULL,
				   obs_graphics_thread, obs);
#endif
	if (errorcode != 0)
		return OBS_VIDEO_FAIL;

	video->thread_initialized = true;
	video->ovi = *ovi;
	return OBS_VIDEO_SUCCESS;
}

  前面的代码就省略不看了,主要看看pthread_create,如果是windows怎调用的是

errorcode = pthread_create(&video->video_thread, NULL,
				   obs_graphics_thread, obs);

  可以看到,线程函数是 obs_graphics_thread,这个我们先记着,很重要。其实,如果是打断点到BitBlt进行调试看堆栈信息,也是可以定位到obs_init_graphics,看代码就像破案,如果你知道案件的谜底,倒着推是很容易的,如果不知道谜底,那就只能顺着来,一步步看代码,分析,打断点调试了。 最终到了线程的创建,create.c在解决方案里没有加载,只是导入进来了。

在这里插入图片描述   为了跨平台,obs使用了pthreads-win32线程库,那时C++11还没有出来,这部分代码现在可以用C++ 11 thread来写,就不需要那么多的宏来区分操作系统了,关于pthreads-win32的资料,可以看这个链接:https://www.sourceware.org/pthreads-win32/,已经很多年没更新了。   线程创建,最终还是调用了_beginthreadex, Qt的QThread的底层其实也是调用这个,params参数保存了在obs_init_video里传过来的obs_graphics_thread,ptw32_threadStart是线程体

#if ! (defined(__MINGW64__) || defined(__MINGW32__)) || (defined (__MSVCRT__) && ! defined (__DMC__))
unsigned
  __stdcall
#else
void
#endif
ptw32_threadStart (void *vthreadParms)
{
	。。。
	if (0 == setjmp_rc)
    {

      /*
       * Run the caller's routine;
       */
      status = sp->exitStatus = (*start) (arg);
      sp->state = PThreadStateExiting;
    }
    。。。

  这里的start就是obs_graphics_thread,如果你打断点调试,你会发现他们的地址相同。   经过上面的分析,现在对于obs的视频部分线程创建已经很清楚了,线程的起点是不起眼的(*start)(arg), 经过抽丝剥茧,发现是 (1)mainWindow->OBSInit(); (2)ret = ResetVideo(); (3)obs_init_video(ovi); (4)errorcode = pthread_create(&video->video_thread, NULL, obs_graphics_thread, obs); (5)tp->threadH = (HANDLE) _beginthreadex ((void ) NULL, / No security info / stackSize, / default stack size */ ptw32_threadStart, parms, (unsigned) CREATE_SUSPENDED, (unsigned *) &(tp->thread)); (6)ptw32_threadStart最终调用obs_graphics_thread,也就是(*start) (arg);这句代码   其实如果你在解决方案搜索ptw32_threadStart,只会有一条记录,是不是有点懵逼,怎么可能只有一条记录,谁调用的ptw32_threadStart???,但是如果你在vscode里搜索源码,你就会找到它的源头,原来pthread_create所在的源码没有添加到vs的解决方案,所以搜不到。 在这里插入图片描述   线程的调用理清楚了,但是还是没有看到是如何调用dc_capture_capture去截图录屏。其实如果知道答案,很好找,直接打断点到BitBlt的位置就很清晰了,其实就是obs_graphics_thread里调用的,如果不知道答案,那没办法,看代码吧,既然视频线程最终调用的是obs_graphics_thread,那就去看它的代码吧,跟踪代码,其实可以发现如下的调用流程:

void *obs_graphics_thread(void *param)
{
#ifdef __APPLE__
	while (obs_graphics_thread_loop_autorelease(&context))
#else
	while (obs_graphics_thread_loop(&context))
#endif
}

obs_graphics_thread_loop

bool obs_graphics_thread_loop(struct obs_graphics_context *context)
{
	profile_start(tick_sources_name);
	context->last_time =
		tick_sources(obs->video.video_time, context->last_time);
	profile_end(tick_sources_name);

tick_sources

static uint64_t tick_sources(uint64_t cur_time, uint64_t last_time)
{
	。。。
	if (cur_source) {
			obs_source_video_tick(cur_source, seconds);
			obs_source_release(cur_source);
		}
	。。。
}

obs_source_video_tick 在这里插入图片描述

很不幸,有遇到了函数指针,F11进去看看是执行了如下代码

void dc_capture_capture(struct dc_capture *capture, HWND window)
{
	HDC hdc_target;
	HDC hdc;

	if (capture->capture_cursor) {
		memset(&capture->ci, 0, sizeof(CURSORINFO));
		capture->ci.cbSize = sizeof(CURSORINFO);
		capture->cursor_captured = GetCursorInfo(&capture->ci);
	}

	hdc = dc_capture_get_dc(capture);
	if (!hdc) {
		blog(LOG_WARNING, "[capture_screen] Failed to get "
				  "texture DC");
		return;
	}

	hdc_target = GetDC(window);

	BitBlt(hdc, 0, 0, capture->width, capture->height, hdc_target,
	       capture->x, capture->y, SRCCOPY);

	ReleaseDC(NULL, hdc_target);

	if (capture->cursor_captured && !capture->cursor_hidden)
		draw_cursor(capture, hdc, window);

	dc_capture_release_dc(capture);

	capture->texture_written = true;
}

  ok,窗口录制的方法找到了,是用的BitBlt, 鼠标是重绘的draw_cursor(capture, hdc, window);如果你把鼠标绘制的代码注释掉,你会发现,最终生成的mp4文件没有鼠标,也就是说在录屏时,鼠标操作的动作没有保存到buffer, 没有编码到h264.   经过以上的分析,也解决了obs源码分析【二】最后的问题,视频线程是如何执行的,窗口录制的代码是怎么调用的。视频线程的主要逻辑搞清楚了,但是obs提供的窗口录制方案其实有两种,下篇博客继续揭秘。

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

微信扫码登录

0.0613s