声明:本文章内容仅代表个人观点,不能保证完全的正确性,仅供参考!

先上个自己画的图,结合流程图和文字解释,理解起来会更快些

1、视频输出初始化程序运行时,初始化OBS,视频相关的初始化是再mainWindow中进行的OBSApp::OBSInit() -> mainWindow->OBSInit()InitBasicConfig()读取appdata目录下配置文件中Video相关的参数,没有设置的参数使用接口InitBasicConfigDefaults()接口中加载的默认参数OBSBasic::RetsetVideo(),重置视频设置obs_video_info ovi;获取视频设置的参数,包括:帧率,颜色格式,YUV颜色空间,YUV颜色范围,背景及输出分辨率等调用 AttemptToResetVideo() -> obs_reset_video(),将当前参数尝试重置给Video停止当前的video,使用新参数ovi重新初始化video,obs_init_video(ovi)obs_init_video(struct obs_video_info *ovi)通过make_video_info函数,将ovi参数设置给video_out_info vi;调用video_output_open函数启动视频数据输出线程int video_output_open(video_t **video, struct video_output_info *info)创建video_output *out对象,拷贝info中的数据到out->info,设置out->frame_time每一帧的时间差启动线程函数 video_thread,并将out作为参数传入初始化out->cache,调用video_frame_init将cache中每一帧的内容按照视频格式初始化完成后,将out对象赋值给obs->video->video其中线程执行函数video_thread就是视频输出线程,等待信号量video->update_semaphore 被唤醒执行video_output_cur_frame函数,获取视频缓存中第一帧,从video->inputs中获取输出类型调用编码器绑定的回调函数input->callback,receive_video(),进行视频数据编码.而video->update_semaphore 信号量是在所有画面合成完成后被唤醒,后面将介绍是如何唤醒的video->inputs中保存的是输出类型,包括推流和录像,后面将会说到是如何添加的启动画面合成线程函数 obs_graphics_thread(),后面单独介绍画面合成线程的流程至此视频输出的初始化完成,输出线程和画面合成线程已启动2、obs-x264、obs-qsv11、obs-ffmpeg、rtmp模块加载obs-x264是软编,obs-qsv11是intel硬编,obs-ffmpeg中包含ffmpeg_aac、ffmpeg_opus、以及nvenc编码rtmp是推流模块在OBSInit()函数初始化视频后,将执行加载模块的操作,这里将介绍obs-x264模块是怎么加载并且被调用的,其他几个模块的加载是类似的;OBSInit() -> AddExtraModulePaths(),添加加载模块的路径 -> obs_load_all_modules()加载所有模块void obs_load_all_modules(void)obs_find_modules()遍历所有模块目录,load_all_callback是对找到的模块执行的回调函数,find_modules_in_path()在每个目录中查找dll文件,并执行函数process_found_module() -> 执行回调也就是load_all_callback函数load_all_callback() -> obs_open_module() -> os_dlopen()获取打开模块的句柄,接着执行的load_module_exports函数获取模块中的接口地址绑定给obs_module各个函数指针,obs-x264模块只有一个接口地址obs_module_load,被绑定至mod->load;将模块的一些信息填充,包括模块的名称,路径等执行obs_init_module(module),该函数的作用就是为了调用刚才绑定的module->load()接口,也就是obs-x264模块中的obs_module_load函数obs_module_load() -> 宏obs_register_encoder(&obs_x264_encoder),其中obs_x264_encoder是个全局变量,再obs-x264.c文件中完成了obs_x264_encoder的初始化,绑定函数接口,id,编码类型,编码方式;-> obs_register_encoder_s()做一系列的检查 -> 宏REGISTER_OBS_DEF 将obs_x264_encoder添加到obs->encoder_types至此obs-x264模块的加载已完成,后面会介绍如何使用x264编码器
3、输出设置(简单模式)模块加载完成后,将会对输出进行设置,以下是对视频输出设置的说明OBSInit() -> ResetOutputs() 从配置文件中读取当前的视频设置是否是简单模式,重置outputHandler,并创建简单输出模式的指针赋值给outputHandler -> CreateSimpleOutputHandler() -> new SimpleOutput -> 构造函数SimpleOutput::SimpleOutput获取当前编码类型:软编、硬编(QSV)、硬编(nvenc),调用LoadStreamingPreset_h264根据不同的编码类型创建不同的编码器,赋值给成员变量h264Streaming指针,该指针后面将会添加到视频输出的编码器中obs_video_encoder_create,根据编码器id创建编码器 -> 调用create_encoder()函数,根据编码器id和编码器类型创建,在create_encoder函数中构造obs_encoder *encoder,根据id调用find_encoder()函数,从obs->encoder_types找到指定的编码器,赋值给encoder->info,构造完成后,h264Streaming指针就是当前选择的编码器
4、开启推流OBSBasic::StartStreaming() -> outputHandler->StartStreaming()SimpleOutput::StartStreaming(obs_service_t *service)Active()是判断推流、录像、回放缓存是否激活;正常状态下返回false,也就需要执行SetupOututs()函数SetupOutputs中调用了Update()函数,在Update中如果视频格式不是NV12或者I420,将编码器的首要格式设置为NV12;更新编码器h264Streaming中的设置参数,如果编码器注册了update接口(软编和qsv有nvenc没有)将新的参数更新到编码器中,回到SetupOutputs,调用obs_encoer_set_video,设置h264Streaming->media为obs->video.video,设置timebase_num = fps_den;timebase_den = fps_num;注意这里是把帧率的分子分母反着赋值给编码器,完成后回到StartStreaming创建推流的对象调用obs_output_create()函数,根据输出id创建推流对象,与创建编码对象类似,推流对象在加载模块时已添加到obs->output_types中,获取到的推流输出对象赋值给streamOutput指针调用obs_output_set_video_encoder()函数,将推流输出streamOutput->video_encoder设置为编码器创建好的h264Streaming调用obs_output_start() -> obs_output_actual_start() 回调推流对象output->info.start()回调函数开启推流,其中start绑定至rtmp_stream_startstatic bool rtmp_stream_start(void *data)创建线程,执行connect_thread()函数,static void *connect_thread(void *data)init_connect()初始化推流;调用free_packets清空stream->packets,获取推流设置,赋值到streamtry_connect()连接rtmp服务器,RTMP_Init(&stream->rtmp)初始化rtmp客户端,设置推流服务器地址、用户名、密码、流地址、音频编码名称(为何没有添加视频编码名称,母鸡)RTMP_Connect()连接rtmp服务器,RTMP_ConnectStream()连接rtmp流地址init_send()启动发送函数,reset_semaphore()重置发送信号量,创建推流执行线程send_thread,发送视频关键数据send_meta_data(),开启推流数据捕获obs_output_begin_data_capture()其中 send_thread()线程函数:循环等待信号量stream->send_sem 被唤醒,唤醒后 get_next_packet()取出队列中的第一个已编码数据包,执行 send_packet 函数,调用flv_packet_mux进行flv数据封包,再调用RTMP_Write()发送数据包,完成视频数据推流.bool obs_output_begin_data_capture(obs_output_t *output, uint32_t flags)获取output->info也就是rtmp对象设置的flags,是否包含编码,音视频数据,绑定服务器;开启捕获,hook_data_capture(),这里是绑定编码完成后的音视频数据到rtmp推流的回调函数,音视频编码完成的数据回调都是interleave_packets(),start_audio_encoders()添加音频已编码数据捕获,obs_encoder_start()添加视频已编码数据捕获,调用obs_encoder_start_internal(),将回调函数interleave_packets,参数param也就是推流输出对象output构造成结构体encoder_callback cb;static void interleave_packets (void *data, struct encoder_packet *packet)调用obs_encoder_packet_create_instance(&out, packet);拷贝packet中的数据到局部变量out中,其中进行malloc的时候,多申请了一个long类型长度的内存,这个pref是这个数据包的引用计数器;如果音视频数据都收到时,调用apply_interleaved_packet_offset,这个函数是调整时间补偿或者时间修复的吗?否则调用check_received接口将当前的音频或视频已收到标识设置为truewas_started = output->received_audio && output->received_video;......if (was_started)apply_interleaved_packet_offset(output, &out);elsecheck_received(output, packet);根据编码时间戳,将当前数据包插入到输出队列中,并将output->highest_audio_ts设置为当前数据包的编码时间戳insert_interleaved_packet(output, &out);set_higher_ts(output, &out);如果当前是否第一次收到了音频以及视频数据包,调用prune_interleaved_packets(output)对数据包中的内容进行修剪,修剪规则如下:先找出第一帧音频和第一帧视频的数据包,以第一帧视频数据包的index为基准,对比两个数据包的时间戳的差值:如果音频数据包的时间戳减去视频数据包的时间戳的数值大于每帧视频间隔的时间差,那么需要删除这个音频数据包时间戳之前的所有音视频数据包如果没有找到这样的音频数据包,那么就需要找出音视频数据包的时间戳差距最小的那个数据包的index,如果这个index的值比第一帧视频数据包的index小,那么需要删除这个index之前的所有数据包,如果比第一帧视频数据包的index大,那么需要删除第一帧视频数据包之前的所有数据包通过对第一次发送的音视频数据包的裁剪后,当前的待发送数据包中第一帧音视频数据包的时间戳的差距最小,以达到首次发送的音视频数据是同步的,调整修正完成后的待发送数据包相关的时间戳,再次确保发送的第一帧音视频数据包的准确性,并且重新调整待发送数据包的index,调用发送数据包函数 send_interleaved如果是后续收到的音视频数据包,则直接调用发送函数 send_interleavedif (output->received_audio && output->received_video) {if (!was_started) {if (prune_interleaved_packets(output)) {if (initialize_interleaved_packets(output)) {resort_interleaved_packets(output);send_interleaved(output);}}} else {send_interleaved(output);}}static inline void send_interleaved (struct obs_output *output)确认待发送数据包中的第一个数据包时间戳是合法的if (!has_higher_opposing_ts(output, &out))return;把第一个数据包从队列中移除da_erase(output->interleaved_packets, 0);如果是视频数据包的话,在这里统计总的发送帧数if (out.type == OBS_ENCODER_VIDEO) {output->total_frames++;调用output->info.encoded_packet回调函数 rtmp_stream_data,进入rtmp准备发送static void rtmp_stream_data (void *data, struct encoder_packet *packet)将数据包的数据拷贝至局部变量new_packet,数据包的引用技术+1,将new_packet添加到待推流数据块中,视频数据包:add_video_packet,其中视频数据包在添加之前检查是否有需要丢弃的帧,检查完成后调用add_packet,将数据包追加到stream->packets队列中,添加成功后,唤醒信号量stream->send_sem,通知线程send_thread(),执行发送将cb添加到视频编码器encoder->callbacks队列中,如果添加的是第一个已编码数据推流回调,调用add_connection(),启动音频数据输出捕获start_raw_video(),video->raw_active的值增加,说明下raw_active的值,是控制视频数据是否输出的开关,后面在输出视频数据时要用到;调用video_output_connect()函数,关联视频数据到编码的回调,创建video_input input结构体,将数据编码的回调函数 receive_video,编码器对象encoder,视频数据信息,填充到input中,并将input添加到video->inputs队列里,这个队列后面将会用到,其作用是合成后的视频数据调用这个队列中的回调进行视频数据输出(音频数据添加编码回调跟视频类似,在add_connection时调用audio_output_connect,构造audio_input input;将其加入到指定混音器mix->inputs中static void receive_video(void *param, struct video_data *frame)拷贝视频数据到encoder_frame enc_frame,调用do_encode()执行编码,在do_encode函数中,初始化待完成的数据包encoder_packet pkt;调用编码器绑定的编码函数,此处举例x264编码回调obs_x264_encode(),编码完成后调用send_packet,如果当前时视频帧的第一帧,需要单独调用send_first_video_packet函数,将视频的SEI信息添加到视频数据中,调用之前绑定的推流回调函数 interleave_packets(),进行视频数据发送
5、视频画面生成在初始化视频时,启动了一个线程函数obs_graphics_thread(),所有画面源的合成,画面显示以及视频输出都在这个函数里触发,说白了这里就时画面生成和输出的源头void *obs_graphics_thread(void *param)循环处理画面,会根据设置的视频帧数,每隔固定时间处理一次画面tick_sources(),没有深入研究具体是什么内容output_frame():输出当前视频帧static inline void output_frame(bool raw_active)调用render_video(),渲染视频数据,在开启推流和录像功能时,调用render_output_texture(),渲染输出帧,并保存在video->convert_textures和video->output_textures中,再调用stage_output_texture将画面保存到video->copy_surfaces调用download_frme,从video->copy_surfaces中拷贝出当前视频帧数据到video_data *frame,这样就拿到了需要输出的视频画面;将frame传入output_video_data(),在该函数中,调用video_output_lock_frame()函数,拷贝input->cache[last_add]给output_frame,需要注意的是,这个拷贝是将cache[]中的指针地址拷贝过来了,通过格式转换函数例如copy_rgb_frame,将input_frame中的数据内容拷贝到output_frame,实际上也就是将视频内容拷贝到了input->cache[last_add]中,再调用video_output_unlock_frame()函数,唤醒信号量video->update_semaphore,通知线程video_thread视频输出数据已就绪,执行数据输出、编码、rtmp推流调用render_displays()将当前视频画面显示在窗口中,sleep直到下一帧视频数据时间戳

OBS视频数据输出流程(模块加载,编码,推流)详细说明相关推荐

  1. nodejs学习巩固笔记-nodejs基础,Node.js 高级编程(核心模块、模块加载机制)

    目录 Nodejs 基础 大前端开发过程中的必备技能 nodejs 的架构 为什么是 Nodejs Nodejs 异步 IO Nodejs 事件驱动架构 全局对象 全局变量之 process 核心模块 ...

  2. Linux模块加载流程及如何让系统开机自动加载模块

    Linux模块加载 Linux系统加载哪些内核模块,和配置文件有关系. 模块保存在/lib/modules/下. 使用/etc/modules-load.d/来配置系统启动时加载哪些模块. 使用/et ...

  3. python redis模块_大数据入门4 | Redis安装及python中的redis模块加载

    引:前面学习了中文分词.HMM.jieba...这些有很多内容需要总结,但是时间太紧,一下写不完.所以前面的就先放一边,届时复习时再写吧. 今天把刚学的遇到的问题总结一下. 实践部分遇到的问题: 1. ...

  4. 模块加载过程代码分析1

    一.概述 模块是作为ELF对象文件存放在文件系统中的,并通过执行insmod程序链接到内核中.对于每个模块,系统都要分配一个包含以下数据结构的内存区. 一个module对象,表示模块名的一个以null ...

  5. 大前端 - nodejs 基础(核心模块、模块加载机制)

    node基础 一 nodejs 核心模块.模块加载机制 nodejs异步io和事件循环 nodejs单线程 nodejs实现api服务 nodejs核心模块和api使用 提供应用程序可直接调用库,例如 ...

  6. linux ipv6模块,有关Linux ipv6模块加载失败的问题

    有关Linux ipv6模块加载失败的问题 同事一个SUSE11sp3环境配置ipv6地址失败,提示不支持IPv6,请求帮助,第一反应是应该ipv6相关内核模块没有加载. 主要检查内容: ipv6地址 ...

  7. Node.js(一、Node.js基础、模块加载机制、包等)

    Node.js(一.Node.js基础.模块加载机制.包等) 1.Node.js基础 1.1.Node是什么 1.2.Node环境安装失败解决方法 1.2.1.Node环境搭建 1.2.2.错误代码2 ...

  8. ES6 模块加载export 、import、export default 、import() 语法与区别,笔记总结

    ES6模块加载export .import.export default .import() 语法与区别 在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种. ...

  9. nodejs模块加载分析(1).md

    前言 上篇 nodejs 启动流程分析中,遗留了几个问题.这一篇,主要讲讲模块加载流程.大家都应该熟悉 timer 模块的相关功能.我们就以 timer 为引子,一步步看下去吧. C++ init 方 ...

最新文章

  1. R语言ggplot2可视化:ggplot2可视化水平堆叠条形图、并且在每个堆叠条形图的内部居中添加百分比文本标签信息
  2. 【 Vivado 】时钟组(Clock Groups)
  3. 加密和解密算法 Asp.net
  4. GPTEE中的Crypto API的使用
  5. 模态对话框阻塞主线程的话不影响其他线程操作主线程控件(不阻塞)
  6. Jeecg-Boot 1.0 版本发布,基于SpringBoot+Mybatis+AntDesign快速开发平台
  7. 解决Struts2的配置文件struts.xml文件无提示问题
  8. “新基建”对下沉市场意味着什么?
  9. DT浪潮下,大数据在交通管理中的应用实践
  10. 程序员的真实工资是多少?
  11. smith预估 matlab,毕业论文】大滞后系统Smith预估器的控制仿真
  12. 特斯拉电池细节_为什么特斯拉电池日实际上可以辜负炒作
  13. python绝对值编程_python中取绝对值简单方法总结
  14. linux设备授权命令,# Linux命令
  15. BZOJ 4605 崂山白花蛇草水(权值线段树+KD树)
  16. net start mysql启动mysql时报错:发生系统错误 2。找不到指定文件
  17. 软件包管理 、 分区规划及使用 、 NTP时间同步 、 总结和答疑
  18. 10天精读掌握:计算机组成与设计COAD:Patterson and Hennessy 第6天 2018/10.31
  19. 【网站模板】第02期—15款免费企业网站模板,助你提升学习效率与工作效率
  20. HTML5期末大作业:个人主页网站设计——服装明星主页(7页)表格带留言板带音乐

热门文章

  1. 等保评测要求和评测材料需要哪些 你想知道的全攻略都在这里
  2. Rhythmbox中文乱码
  3. CCPC-Wannafly Winter Camp Day1 B-吃豆豆
  4. GoSurf真是奇怪啊。
  5. 九龙证券|豪掷超6000万,10转3派6元,今年第二只高送转股出炉!
  6. java培训是指什么培训
  7. 关于“兴趣爱好”之我见
  8. JsBridge的学习
  9. Nihao Flash3D v1.0稳定版发布
  10. 每周一文(六)Facebook EBR向量召回模型