obs源码分析【四】:obs录制的窗口截图与视频编码

  在obs中,录制时,提供了两种种窗口截屏方法,

enum window_capture_method {
	METHOD_AUTO,
	METHOD_BITBLT,
	METHOD_WGC,
};

  但是在窗口像素抓取时默认用的BitBlt,下面的代码选择抓屏的方法:

static enum window_capture_method
choose_method(enum window_capture_method method, bool wgc_supported,
	      const char *current_class)
{
	if (!wgc_supported)
		return METHOD_BITBLT;

	if (method != METHOD_AUTO)
		return method;

	if (!current_class)
		return METHOD_BITBLT;

	const char **class = wgc_partial_match_classes;
	while (*class) {
		if (astrstri(current_class, *class) != NULL) {
			return METHOD_WGC;
		}
		class ++;
	}

	class = wgc_whole_match_classes;
	while (*class) {
		if (astrcmpi(current_class, *class) == 0) {
			return METHOD_WGC;
		}
		class ++;
	}

	return METHOD_BITBLT;
}

  真正调用BitBlt方法是在wc_tick函数中,

static void wc_tick(void *data, float seconds)
{
	....
	dc_capture_capture(&wc->capture, wc->window);
	....
}

  dc_capture_capture根据窗口句柄wc->window调用BitBit进行抓屏,这个函数在初始化时会赋值给结构体obs_source_info,如下:

struct obs_source_info window_capture_info = {
	.id = "window_capture",
	.type = OBS_SOURCE_TYPE_INPUT,
	.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW |
			OBS_SOURCE_SRGB,
	.get_name = wc_getname,
	.create = wc_create,
	.destroy = wc_destroy,
	.update = wc_update,
	.video_render = wc_render,
	.hide = wc_hide,
	//抓屏方法赋值
	.video_tick = wc_tick,
	.get_width = wc_width,
	.get_height = wc_height,
	.get_defaults = wc_defaults,
	.get_properties = wc_properties,
	.icon_type = OBS_ICON_TYPE_WINDOW_CAPTURE,
};

  该结构体在obs初始化时加载

bool obs_module_load(void)
{
	。。。
	
	//注册窗口采集的信息
	obs_register_source(&window_capture_info);

	。。。
	
	return true;
}

  window_capture_info不是在当前文件定义的,所以用extern声明
在这里插入图片描述
  当前的obs_module_load是在插件dll中,需要在libobs中导入,方法如下:

static int load_module_exports(struct obs_module *mod, const char *path)
{
	mod->load = os_dlsym(mod->module, "obs_module_load");
	if (!mod->load)
		return req_func_not_found("obs_module_load", path);
	。。。
	。。。
}

  os_dlsym其实就是调用win32 API GetProcAddress根据dll 方法名获取函数地址,做windows C++开发的应该很熟悉GetProcAddress,os_dlsym代码如下:

void *os_dlsym(void *module, const char *func)
{
	void *handle;

	handle = (void *)GetProcAddress(module, func);

	return handle;
}

  有了GetProcAddress必然得有LoadLibraryW,在os_dlsym的上方其实os_dlopen就是根据路径加载dll. 就是逐一加载下面这个路径的dll.
在这里插入图片描述

  经过跟踪代码,可以发现是在Qt界面初始化时加载所有的dll, 先是OBSBasic类的OBSInit方法,代码如下:

void OBSBasic::OBSInit()
{
	ProfileScope("OBSBasic::OBSInit");
	AddExtraModulePaths();
	blog(LOG_INFO, "---------------------------------");
	obs_load_all_modules(); //加载所有dll
	blog(LOG_INFO, "---------------------------------");
	obs_log_loaded_modules();
	blog(LOG_INFO, "---------------------------------");
	obs_post_load_modules();
}

  然后是obs_load_all_modules()调用load_all_callback , 代码如下:

void obs_load_all_modules(void)
{
	profile_start(obs_load_all_modules_name);

	//传入load_all_callback,加载dll
	obs_find_modules(load_all_callback, NULL);
#ifdef _WIN32
	profile_start(reset_win32_symbol_paths_name);
	reset_win32_symbol_paths();
	profile_end(reset_win32_symbol_paths_name);
#endif
	profile_end(obs_load_all_modules_name);
}

  再就是obs_open_module,调用os_dlopen

int obs_open_module(obs_module_t **module, const char *path,
		    const char *data_path)
{
	。。。
	mod.module = os_dlopen(path);
	。。。
}

  至此dll的加载流程就清晰了,但是上图中其实有很多dll, 那得调用多次LoadLibrary,从一个dll的加载,然后alt 7查看堆栈,可以知道是在哪里调用的多次:
在这里插入图片描述
  obs_find_moudules里面执行for循环调用find_modules_in_path

void obs_find_modules(obs_find_module_callback_t callback, void *param)
{
	if (!obs)
		return;

	for (size_t i = 0; i < obs->module_paths.num; i++) {
		struct obs_module_path *omp = obs->module_paths.array + i;
		find_modules_in_path(omp, callback, param);
	}
}

  find_modules_in_path的代码

static void find_modules_in_path(struct obs_module_path *omp,
				 obs_find_module_callback_t callback,
				 void *param)
{
	。。。
	if (os_glob(search_path.array, 0, &gi) == 0) {
			for (size_t i = 0; i < gi->gl_pathc; i++) {
				if (search_directories == gi->gl_pathv[i].directory)
					process_found_module(omp, gi->gl_pathv[i].path,
							     search_directories,
							     callback, param);
			}
	
			os_globfree(gi);
		}
	。。。
}

  一共是21个dll.
  process_found_module根据dll的名字,调用callback load dll.至此所有的dll加载就弄清楚了。窗口录制win-capture.dll以及其它dll就是在obs界面初始化全部调用进来的,在dll中load dll, dll有点多,挺绕的。
  上面这么多题外话,搞清楚了窗口采集dll的加载过程,下面来说说窗口截图后是如何编码的?
  要解决这个问题,必须从问题的源头来解决,obs采用的x264编码,那么就得从x264开始,包括ffmpeg也是集成了x264, 如果不用h264,那是不可能的,你自己写个编码标准,世界音视频协会不认,其他播放器打不开,不管怎样,最终还是要转为x264, 265等标准,视频帧的编码最终调用的是x264_encoder_encode,也就是在github上需要下载的那个依赖。
在这里插入图片描述

  代码如下:

/* x264_encoder_encode:
 *      encode one picture.
 *      *pi_nal is the number of NAL units outputted in pp_nal.
 *      returns the number of bytes in the returned NALs.
 *      returns negative on error and zero if no NAL units returned.
 *      the payloads of all output NALs are guaranteed to be sequential in memory. */
X264_API int x264_encoder_encode( x264_t *, x264_nal_t **pp_nal, int *pi_nal, x264_picture_t *pic_in, x264_picture_t *pic_out );

  关于x264的源码解析,雷神已经讲的很清楚了,可以去认真看看,底层开发真的是太无聊了,不像前端,后端,直接调接口,就可以很快做出各种应用,但是底层开发不行,你得了解各种深层次的原理机制,才能做出一点点事情,不过现在音视频也被很多大厂承包了,比如声网Agora, 靠提供音视频服务,弯道超车,已经上市了;还有其它厂,比如腾讯,阿里,网易也有自己的音视频服务,小公司要做音视频,不是不可以,得提供足够的资源,不然是不可能的。

  经过调试,其实也可以调试出来,当你在界面是点击【开始录制】按钮后就会触发线程相关的代码,比较多,我暂时没有仔细看。然后线程就会启动

status = sp->exitStatus = (*start) (arg);

然后通过 video_thread调用video_output_cur_frame

static void *video_thread(void *param)
{
	。。。
	while (!video->stop && !video_output_cur_frame(video)) {
			os_atomic_inc_long(&video->total_frames);
		}
	。。。
}

  video_output_cur_framed调用 receive_video

static inline bool video_output_cur_frame(struct video_output *video)
{
	。。。
	input->callback(input->param, &frame);
	。。。

  触发callback,实际是调用receive_video,receive_video调用do_encode

static void receive_video(void *param, struct video_data *frame)
{
	。。。
	if (do_encode(encoder, &enc_frame))
		encoder->cur_pts += encoder->timebase_num;
	。。。

  do_encode再进行回调的调用

bool do_encode(struct obs_encoder *encoder, struct encoder_frame *frame)
{
	。。。
	success = encoder->info.encode(encoder->context.data, frame, &pkt,
				       &received);
	。。。
}

  info的encode就是调用x264的x264_encoder_encode.
  编码的实现大概流程就是这样,其中涉及到线程操作的部分没有仔细看,代码太多了,obs也迭代了十多年,搞清楚整个流程得花不少时间。

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

令狐掌门

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值