【obs-studio开源项目从入门到放弃】ffmpeg_muxer 视频的录制(flv-mp4-ts-srt-udp-tcp)

本文深入剖析OBS视频录制的内部机制,重点介绍了ffmpeg_muxer的创建过程、启动录制的具体步骤,以及音视频数据如何通过管道传递给obs-ffmpeg-mux.exe进程进行文件写入。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


前言

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

obs支持非常丰富的视频格式的录制,底层是基于ffmpeg的api实现的视频文件的录制。不仅支持本地视频文件录制(flv,mp4,mkv,ts,mp3),也支持流媒体网络协议的推流(tcp,udp,srt,rtmp,http,rist)。理论上只要obs所依赖的ffmpeg支持的协议都可以推流或者录制。
针对视频的录制obs有两套实现方案,一个是支持的录制参数相对简单的 ffmpeg_muxer,一个是支持非常丰富的录制参数设置的 ffmpeg_output
这篇文章主要介绍简单输出 ffmpeg_muxer

ffmpeg_muxer的创建

通过下面的调用堆栈分享,在程序启动的时候就创建了 ffmpeg_muxer 视频录制output。而rtmp_output的创建是在用户点击开始推流按钮后才创建,详细请参考这篇文章:rtmp_output源码解析

>	obs-ffmpeg.dll!ffmpeg_mux_create(obs_data * settings, obs_output * output)87	C
 	obs.dll!obs_output_create(const char* id, const char* name,obs_data* settings, obs_data* hotkey_data)155	C
 	obs64.exe!SimpleOutput::SimpleOutput(OBSBasic * main_)458	C++
 	obs64.exe!CreateSimpleOutputHandler(OBSBasic * main)2037	C++
 	obs64.exe!OBSBasic::ResetOutputs()1658	C++
 	obs64.exe!OBSBasic::OBSInit()1802	C++
 	obs64.exe!OBSApp::OBSInit()1470	C++
 	obs64.exe!run_program(std::basic_fstream<char,std::char_traits<char>> & logFile, int argc, char* * argv)2134	C++
 	obs64.exe!main(int argc, char * * argv)2835	C++
 	obs64.exe!WinMain(HINSTANCE__ * __formal, HINSTANCE__ * __formal, char * __formal, int __formal)97	C++

启动视频录制

当用户点击UI上的开始录制按钮,得到下面的调用堆栈。

>	obs-ffmpeg.dll!ffmpeg_mux_start(void * data)320	C
 	obs.dll!obs_output_actual_start(obs_output * output)256	C
 	obs.dll!obs_output_start(obs_output * output)293	C
 	obs64.exe!SimpleOutput::StartRecording()1030	C++
 	obs64.exe!OBSBasic::StartRecording()7012	C++
 	obs64.exe!OBSBasic::on_recordButton_clicked()7532	C++
 	obs64.exe!OBSBasic::qt_static_metacall(QObject * _o, QMetaObject::Call _c, int _id, void * * _a)1310	C++
 	obs64.exe!OBSBasic::qt_metacall(QMetaObject::Call _c, int _id, void * * _a)1504	C++
 	[外部代码]	
 	obs64.exe!run_program(std::basic_fstream<char,std::char_traits<char>> & logFile, int argc, char * * argv)2139	C++
 	obs64.exe!main(int argc, char * * argv)2835	C++
 	obs64.exe!WinMain(HINSTANCE__ * __formal, HINSTANCE__ * __formal, char * __formal, int __formal)97	C++

ffmpeg_mux_start 真正启动视频录制的函数

视频写文件操作在另一个进程obs-ffmpeg-mux.exe里面 ,它和主进程obs64.exe通过管道进行通信。obs对管道的操作都封装在 obs-studio\libobs\util\pipe.h 包含了管道的创建、销毁、读取、写入等操作,关于管道的编程相关操作就不做赘述,具体实现阅读源码。

接下来通过源码注释方式,分以下 ffmpeg_mux_start 都做了什么工作。

static bool ffmpeg_mux_start(void *data)
{
	struct ffmpeg_muxer *stream = data;
	obs_data_t *settings;
	const char *path;
	// 判断是否可以开启视频录制
	if (!obs_output_can_begin_data_capture(stream->output, 0))
		return false;

	// 创建视频编码器和音频编码器 
	// obs中 x264编码器的使用:https://blog.youkuaiyun.com/qq_33844311/article/details/122072255
	if (!obs_output_initialize_encoders(stream->output, 0))
		return false;

	// 获取输出的配文件settings
	settings = obs_output_get_settings(stream->output);
	if (stream->is_network) {
		obs_service_t *service;
		service = obs_output_get_service(stream->output);
		if (!service)
			return false;
		// 获取推流地址url 
		path = obs_service_get_url(service);
	} else {
		//获取录制的视频文件路径
		path = obs_data_get_string(settings, "path");
	}
	
	// 如果不是网络流,检查写入的文件路径是否合法
	if (!stream->is_network) {
		/* ensure output path is writable to avoid generic error
		 * message.
		 *
		 * TODO: remove once ffmpeg-mux is refactored to pass
		 * errors back */
		FILE *test_file = os_fopen(path, "wb");
		if (!test_file) {
			set_file_not_readable_error(stream, settings, path);
			return false;
		}

		fclose(test_file);
		os_unlink(path);
	}
	// 创建管道并启动 obs-ffmpeg-mux.exe进程
	start_pipe(stream, path);
	obs_data_release(settings);
	
	// 管道创建失败则报错
	if (!stream->pipe) {
		obs_output_set_last_error(
			stream->output, obs_module_text("HelperProcessFailed"));
		warn("Failed to create process pipe");
		return false;
	}

	/* write headers and start capture  设置开始录制标志为true*/
	os_atomic_set_bool(&stream->active, true);
	os_atomic_set_bool(&stream->capturing, true);
	stream->total_bytes = 0;	//统计录制的视频文件大小
	
	// 开始视频的输出
	obs_output_begin_data_capture(stream->output, 0);

	info("Writing file '%s'...", stream->path.array);
	return true;
}

ffmpeg_mux_data 音视频包输出到管道函数

obs的音频编码线程和视频编码线程,完成音视频帧的编码后最终都会调用到 output->info.encoded_packet 绑定的回调函数。
视频输出绑定的是ffmpeg_mux_data ,这里完成了视频的写入操作。如果有的话首先写入的视频编码器的头信息,音频编码器的头信息,接下来就是通过管道的写操作api,写入视频包和音频包。obs-ffmpeg-mux.exe进程收到音视频包的数据后写入磁盘完成视频的录制。

static void ffmpeg_mux_data(void *data, struct encoder_packet *packet)
{
	struct ffmpeg_muxer *stream = data;

	if (!active(stream))
		return;

	/* encoder failure */
	if (!packet) {
		deactivate(stream, OBS_OUTPUT_ENCODE_ERROR);
		return;
	}
	// 如果没有发送音视频编码器的头信息,就发送音视频编码器的头信息
	if (!stream->sent_headers) {
		if (!send_headers(stream))
			return;
		// 头信息发送成功后 sent_headers置为true
		stream->sent_headers = true;
	}
	// 检查是否停止视频录制或者推流
	if (stopping(stream)) {
		if (packet->sys_dts_usec >= stream->stop_ts) {
			deactivate(stream, 0);
			return;
		}
	}
	// 写入音视频包到管道
	write_packet(stream, packet);
}

write_packet 写管道操作

先写入packet的媒体信息pts dts type keyframe等,在写入媒体数据。

bool write_packet(struct ffmpeg_muxer *stream, struct encoder_packet *packet)
{
	bool is_video = packet->type == OBS_ENCODER_VIDEO;
	size_t ret;

	struct ffm_packet_info info = {.pts = packet->pts,
				       .dts = packet->dts,
				       .size = (uint32_t)packet->size,
				       .index = (int)packet->track_idx,
				       .type = is_video ? FFM_PACKET_VIDEO
							: FFM_PACKET_AUDIO,
				       .keyframe = packet->keyframe};
	// 写入音视频包的媒体信息
	ret = os_process_pipe_write(stream->pipe, (const uint8_t *)&info,
				    sizeof(info));
	if (ret != sizeof(info)) {
		warn("os_process_pipe_write for info structure failed");
		signal_failure(stream);
		return false;
	}
	// 写入媒体包的流媒体数据
	ret = os_process_pipe_write(stream->pipe, packet->data, packet->size);
	if (ret != packet->size) {
		warn("os_process_pipe_write for packet data failed");
		signal_failure(stream);
		return false;
	}

	stream->total_bytes += packet->size;
	return true;
}

obs-ffmpeg-mux.exe 进程写文件

obs-ffmpeg-mux这个进程在obs-studio工程里面是单独一个项目,一共只有两个文件【ffmpeg-mux.h ffmpeg-mux.c】。

该进程负责的内容也比较简单,作为管道接收端进程,负责接收音视频数据包写文件或者推流到网络的操作。

整个代码不复杂,可以说是教科书般的对ffmpeg api写文件流程的演示。下面贴上我绘制的函数调用逻辑图。配合阅读源码,不难理解里面的操作。
obs-ffmpeg-mux.exe进程写文件

总结

了解obs的视频编码线程音频编码线程后,再理解obs的视频录制、网络推流就非常容易。obs的视频录制模块也是一个学习ffmpeg api使用流程的非常好的例子。

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

若有帮助幸甚。


技术参考

  1. 视频技术参考: https://ke.qq.com/course/3202131?flowToken=1041285
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值