【obs-studio开源项目从入门到放弃】obs rtmp推流插件 rtmp_output源码解析

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

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

obs最常用的一个功能就是直播推流,推流协议里面目前最常用的还是rtmp协议,当然obs也支持srt推流协议,srt采用可靠udp和快速重传技术,有抗丢包,低延时的优点,将来或许可能代替rtmp,成为新一代的直播推流协议,目前很多cdn厂商已经支持srt上行推流,流媒体服务器进行srt转rtmp,再分发给观众端,回头有时间再详细唠唠obs srt推流的技术细节。

接下来通过阅读 obs的源码,详细分析一下 obs推 rtmp流的过程当中的工作细节。

1.rtmp_output 推流源的创建

rtmp_stream_create打上断点,F5启动调试,点击UI上开始推流按钮,可以看到以下堆栈信息,OBSBasic::StartStreaming()是创建rtmp_output的入口,也是开始rtmp推流的入口,推流相关操作都封装在 std::unique_ptr outputHandler 对象。

>	obs-outputs.dll!rtmp_stream_create(obs_data * settings, obs_output * output)152	C
 	obs.dll!obs_output_create(const char * id, const char * name, obs_data * settings, obs_data * hotkey_data)155	C
 	obs64.exe!AdvancedOutput::SetupStreaming(obs_service * service)1727	C++
 	obs64.exe!OBSBasic::StartStreaming()6429	C++
 	obs64.exe!OBSBasic::on_streamButton_clicked()7559	C++
 	obs64.exe!OBSBasic::qt_static_metacall(QObject * _o, QMetaObject::Call _c, int _id, void * * _a)1305	C++
 	obs64.exe!OBSBasic::qt_metacall(QMetaObject::Call _c, int _id, void * * _a)1500	C++
 	[外部代码]	
 	obs64.exe!run_program(std::basic_fstream<char,std::char_traits<char>> & logFile, int argc, char * * argv)2143	C++
 	obs64.exe!main(int argc, char * * argv)2839	C++
 	obs64.exe!WinMain(HINSTANCE__ * __formal, HINSTANCE__ * __formal, char * __formal, int __formal)97	C++

创建启动rtmp_output的入口 OBSBasic::StartStreaming()

通过注释源码来分一下创建的逻辑,下面贴出的代码删除了一些UI相关的代码,否则都贴上去看的太长。只抓住关键逻辑就好。

void OBSBasic::StartStreaming()
{	
	//判断当前是否有正在推流或者录制,存在推流就直接返回
	if (outputHandler->StreamingActive())
		return;
	if (disableOutputsRef)
		return;
	// 创建rtmp_output输出源,并绑定视频编码器,音频编码器到rtmp_output
	if (!outputHandler->SetupStreaming(service)) {
		DisplayStreamStartError();
		return;
	}

	if (api)
		api->on_event(OBS_FRONTEND_EVENT_STREAMING_STARTING);
	//保存配置文件
	SaveProject();
	...
	// 开启rtmp推流
	if (!outputHandler->StartStreaming(service)) {
		DisplayStreamStartError();
		return;
	}
}

2.rtmp推流线程的启动

rtmp_stream_start启动函数中并没有直接创建rtmp的发送线程,而是创建了一个连接线程connect_thread,之所以这样设计,我认为是rtmp的创建连接是一个耗时工作,不应该放到主线程去做,所以创建了一个connect_thread线程,在这个线程里面做rtmp的连接工作,以及创建rtmp发送线程send_thread,负责打包发送音视频包到流媒体服务器。

rtmp_stream_start 开始推流

主线程开始rtmp推流的调用堆栈

//主线程开始rtmp推流调用堆栈
OBSBasic::StartStreaming() -> outputHandler->StartStreaming(service) ->obs_output_start(streamOutput) ->  
obs_output_actual_start(output)-> output->info.start-> rtmp_stream_start 

// 最终调用到rtmp-stream.c的rtmp_stream_start
static bool rtmp_stream_start(void *data)
{
	struct rtmp_stream *stream = data;
	//判断是否可以开始音视频数据的捕获
	if (!obs_output_can_begin_data_capture(stream->output, 0))
		return false;
	//初始化视频编码器、音频编码器
	if (!obs_output_initialize_encoders(stream->output, 0))
		return false;
	
	// 设置rtmp流的状态为正在连接中,防止重入
	os_atomic_set_bool(&stream->connecting, true);
	// 创建rtmp连接线程 防止UI卡死
	return pthread_create(&stream->connect_thread, NULL, connect_thread,
			      stream) == 0;
}

connect_thread 连接线程

下面贴一张我准备的思维导图截图,一图抵千言。

对照着下图很容易理清楚connect_thread 的整个工作流程,大家参考着源码就很容易理解。想要思维导图的可以评论区留下你的邮箱,发你邮箱一份。

rtmp的初始化和连接到流媒体服务器的代码好理解,重点要理解obs_output_begin_data_capture函数,给音视频编码器绑定,编码完成回调函数【receive_audio receive_video】,通过绑定的回调函数才可以让编码后的音视频包送到rtmp发送线程去打包发送到网络。

在这里插入图片描述

connect_thread的调用堆栈

	//绑定画面完成回调receive_video
>	obs.dll!video_output_connect(video_output * video, const video_scale_info * conversion, void(*)(void *, video_data *) callback, void * param)352	C
 	obs.dll!start_raw_video(video_output * v, const video_scale_info * conversion, void(*)(void *, video_data *) callback, void * param)2364	C
 	obs.dll!add_connection(obs_encoder * encoder)213	C	// 在这里绑定 receive_audio receive_video
 	obs.dll!obs_encoder_start_internal(obs_encoder * encoder, void(*)(void *, encoder_packet *) new_packet, void * param)581	C
 	obs.dll!obs_encoder_start(obs_encoder * encoder, void(*)(void *, encoder_packet *) new_packet, void * param)595	C
 	obs.dll!hook_data_capture(obs_output * output, bool encoded, bool has_video, bool has_audio)1948	C
 	obs.dll!obs_output_begin_data_capture(obs_output * output, unsigned int flags)2201	C
 	obs-outputs.dll!init_send(rtmp_stream * stream)903	C
 	obs-outputs.dll!try_connect(rtmp_stream * stream)1030	C
 	obs-outputs.dll!connect_thread(void * data)1138	C
 	w32-pthreads.dll!ptw32_threadStart(void * vthreadParms)225	C

3.rtmp发送线程工作内容

send_thread负责音视频包发送

rtmp的发送线程负责从交织队列里面取出音视频包打包成flv通过rtmp协议发送到网络,从视频编码线程音频编码线程可以知道最后都是调用到rtmp_stream_data函数,将音频包和视频包添加到struct circlebuf packets数据包队列,然后发送信号量通知send_thread线程工作。

static void *send_thread(void *data)
{
	struct rtmp_stream *stream = data;

	//=================收到信号量发送音视频包==============================
	while (os_sem_wait(stream->send_sem) == 0) {
		struct encoder_packet packet;
		struct dbr_frame dbr_frame;

		if (stopping(stream) && stream->stop_ts == 0) {
			break;
		}
		
		// 获取下一个音视频包
		if (!get_next_packet(stream, &packet))
			continue;

		if (stopping(stream)) {
			if (can_shutdown_stream(stream, &packet)) {
				obs_encoder_packet_release(&packet);
				break;
			}
		}
		// 没有发送flv音视频头数据首先发送头数据
		if (!stream->sent_headers) {
			if (!send_headers(stream)) {
				os_atomic_set_bool(&stream->disconnected, true);
				break;
			}
		}
		//动态码率相关统计数据,obs支持设置动态码率,如果发送带宽不够会降低视频编码码率,保证推流的流畅性
		if (stream->dbr_enabled) {
			dbr_frame.send_beg = os_gettime_ns();
			dbr_frame.size = packet.size;
		}
		// 通过librtmp发送音视频包数据
		if (send_packet(stream, &packet, false, packet.track_idx) < 0) {
			os_atomic_set_bool(&stream->disconnected, true);
			break;
		}

		if (stream->dbr_enabled) {
			dbr_frame.send_end = os_gettime_ns();
			//统计实际发送码率是否和设置的编码码率一致,
			//如果一致证明网络状况良好,如果小于设置的视频编码码率,说明带宽不够,降低编码码率 
			// 在这两个函数里面修改视频编码码率 dbr_inc_bitrate dbr_set_bitrate
			pthread_mutex_lock(&stream->dbr_mutex);
			dbr_add_frame(stream, &dbr_frame);
			pthread_mutex_unlock(&stream->dbr_mutex);
		}
	}
	//=======================while循环结束=================================
	// 获取是否是编码出错
	bool encode_error = os_atomic_load_bool(&stream->encode_error);
	
	// 打印断开rtmp连接日志
	if (disconnected(stream)) {
		info("Disconnected from %s", stream->path.array);
	} else if (encode_error) {
		info("Encoder error, disconnecting");
	} else {
		info("User stopped the stream");
	}

	if (stream->new_socket_loop) {
		os_event_signal(stream->send_thread_signaled_exit);
		os_event_signal(stream->buffer_has_data_event);
		pthread_join(stream->socket_thread, NULL);
		stream->socket_thread_active = false;
		stream->rtmp.m_bCustomSend = false;
	}

	set_output_error(stream);
	// 关闭rtmp连接
	RTMP_Close(&stream->rtmp);

	if (!stopping(stream)) {
		pthread_detach(stream->send_thread);
		obs_output_signal_stop(stream->output, OBS_OUTPUT_DISCONNECTED);
	} else if (encode_error) {
		obs_output_signal_stop(stream->output, OBS_OUTPUT_ENCODE_ERROR);
	} else {
		obs_output_end_data_capture(stream->output);
	}
	//释放音视频包缓存队列
	free_packets(stream);
	os_event_reset(stream->stop_event);
	os_atomic_set_bool(&stream->active, false);
	stream->sent_headers = false;

	/* reset bitrate on stop */
	if (stream->dbr_enabled) {
		if (stream->dbr_cur_bitrate != stream->dbr_orig_bitrate) {
			stream->dbr_cur_bitrate = stream->dbr_orig_bitrate;
			dbr_set_bitrate(stream);
		}
	}
	return NULL;
}

发送音视频包调用堆栈

		ret = RTMP_Write(&stream->rtmp, (char *)data, (int)size, 0);// librtmp 发送接口
			flv_video(&s, dts_offset, packet, is_header);			//打包flv视频包
			flv_audio(&s, dts_offset, packet, is_header);			//打包flv音频包
		flv_packet_mux()	//	音视频裸数据包打包flv容器音视频包
>	obs-outputs.dll!send_packet(rtmp_stream*stream,encoder_packet*packet,bool is_header,unsigned __int64 idx)439C
 	obs-outputs.dll!send_audio_header(rtmp_stream * stream, unsigned __int64 idx, bool * next)713	C
 	obs-outputs.dll!send_headers(rtmp_stream * stream)737	C
 	obs-outputs.dll!send_thread(void * data)597	C
 	w32-pthreads.dll!ptw32_threadStart(void * vthreadParms)225	C

总结

obs整个rtmp的推流发送流程还是比较复杂的,需要配合视频编码线程音频编码线程一起理解。

rtmp_output推流源的创建是在主线程中,连接流媒体服务器是在 connect_thread 线程中,发送音视频包数据是在 send_packet 线程。各个线程之间分工明确,通过信号量互相通信。理解了这几个线程的工作细节,就算是入门obs的编程框架了。

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

若有帮助幸甚。


技术参考

  1. 视频技术参考: https://ke.qq.com/course/3202131?flowToken=1040950
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值