提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
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流的过程当中的工作细节。
在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++
通过注释源码来分一下创建的逻辑,下面贴出的代码删除了一些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;
}
}
在rtmp_stream_start启动函数中并没有直接创建rtmp的发送线程,而是创建了一个连接线程connect_thread,之所以这样设计,我认为是rtmp的创建连接是一个耗时工作,不应该放到主线程去做,所以创建了一个connect_thread线程,在这个线程里面做rtmp的连接工作,以及创建rtmp发送线程send_thread,负责打包发送音视频包到流媒体服务器。
主线程开始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 的整个工作流程,大家参考着源码就很容易理解。想要思维导图的可以评论区留下你的邮箱,发你邮箱一份。
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
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开源项目的理解,难免有错误的地方,如果有欢迎指出。
若有帮助幸甚。