obs 源码解析笔记

由于obs rtp音频传输有问题,所以可能需要修改obs源码,学习了两天,发现官方文档有些混乱,国内有关说明又少,特此记录,也方便以后自己查阅。这里主要涉及工作有关源码其他基本略过,除非重要。
原文链接:https://blog.csdn.net/weixin_44259356/article/details/102582493

obs源码编译

见我的另一篇文章:VS2017-OBS24.03源码编译
https://blog.csdn.net/weixin_44259356/article/details/102548121

obs源码简介

obs有两套源码,新版源码采用微内核思想,核心功能很少,大部分功能通过插件的方式实现,方便后续维护开发,这里我用的是最新版源码24.03。

插件模块头介绍

libobs / obs-module.h –用于创建插件模块的主要标头。该文件自动包括以下文件:
libobs / obs.h –主libobs标头。该文件自动包括以下文件:
libobs / obs-source.h –用于在插件模块中实现源代码
libobs / obs-output.h –用于在插件模块中实现输出
libobs / obs-encoder.h –用于在插件模块中实现编码器
libobs / obs-service.h –用于在插件模块中实现服务
libobs / obs-data.h –用于管理libobs对象的设置
libobs / obs-properties.h –用于为libobs对象生成属性
libobs / graphics / graphics.h –用于图形渲染

目录结构

结构如下:

其中比较重要的是:

ALL_BUILD

整个项目通过它来启动,直接启动obs会有部分文件找不到。

obs

主程序

obs-ffmpeg

obs通过调用ffmpeg api来实现推流录像等功能,以插件的方式实现

obs-x264

也是插件方式实现x264编码,这里后续如果要为obs增加编码方式比如h265等可以参考此插件源码

w32-pthreads

obs发送数据流线程,用的是另外一个开源项目,功能比较强大,详细信息可以打开源码注释查看,这里特别注意线程里的互斥锁,我在调试中很多obs的问题都是使用没有及时释放的资源引起的。

libobs

obs视频采集线程

推流部分关键函数源码简介

点击开始录制,obs将会调用ui部分代码读取录像设置参数:
位于\UI\window-basic-main-outputs.cpp

bool AdvancedOutput::StartRecording()
{const char *path;const char *recFormat;const char *filenameFormat;bool noSpace = false;bool overwriteIfExists = false;if (!useStreamEncoder) {if (!ffmpegOutput) {UpdateRecordingSettings();}} else if (!obs_output_active(streamOutput)) {UpdateStreamSettings();}UpdateAudioSettings();if (!Active())SetupOutputs();if (!ffmpegOutput || ffmpegRecording) {path = config_get_string(main->Config(), "AdvOut",ffmpegRecording ? "FFFilePath": "RecFilePath");recFormat = config_get_string(main->Config(), "AdvOut",ffmpegRecording ? "FFExtension": "RecFormat");filenameFormat = config_get_string(main->Config(), "Output","FilenameFormatting");overwriteIfExists = config_get_bool(main->Config(), "Output","OverwriteIfExists");noSpace = config_get_bool(main->Config(), "AdvOut",ffmpegRecording? "FFFileNameWithoutSpace": "RecFileNameWithoutSpace");os_dir_t *dir = path && path[0] ? os_opendir(path) : nullptr;if (!dir) {if (main->isVisible())OBSMessageBox::warning(main, QTStr("Output.BadPath.Title"),QTStr("Output.BadPath.Text"));elsemain->SysTrayNotify(QTStr("Output.BadPath.Text"),QSystemTrayIcon::Warning);return false;}os_closedir(dir);string strPath;strPath += path;char lastChar = strPath.back();if (lastChar != '/' && lastChar != '\\')strPath += "/";strPath += GenerateSpecifiedFilename(recFormat, noSpace,filenameFormat);ensure_directory_exists(strPath);if (!overwriteIfExists)FindBestFilename(strPath, noSpace);obs_data_t *settings = obs_data_create();obs_data_set_string(settings, ffmpegRecording ? "url" : "path",strPath.c_str());obs_output_update(fileOutput, settings);obs_data_release(settings);}if (!obs_output_start(fileOutput)) {QString error_reason;const char *error = obs_output_get_last_error(fileOutput);if (error)error_reason = QT_UTF8(error);elseerror_reason = QTStr("Output.StartFailedGeneric");QMessageBox::critical(main,QTStr("Output.StartRecordingFailed"),error_reason);return false;}return true;
}

读取完毕后,将参数应用于obs同时修改视频编码和音频编码设置,同时更新obs程序运行参数

inline void AdvancedOutput::SetupFFmpeg()
{const char *url = config_get_string(main->Config(), "AdvOut", "FFURL");int vBitrate = config_get_int(main->Config(), "AdvOut", "FFVBitrate");int gopSize = config_get_int(main->Config(), "AdvOut", "FFVGOPSize");bool rescale = config_get_bool(main->Config(), "AdvOut", "FFRescale");const char *rescaleRes =config_get_string(main->Config(), "AdvOut", "FFRescaleRes");const char *formatName =config_get_string(main->Config(), "AdvOut", "FFFormat");const char *mimeType =config_get_string(main->Config(), "AdvOut", "FFFormatMimeType");const char *muxCustom =config_get_string(main->Config(), "AdvOut", "FFMCustom");const char *vEncoder =config_get_string(main->Config(), "AdvOut", "FFVEncoder");int vEncoderId =config_get_int(main->Config(), "AdvOut", "FFVEncoderId");const char *vEncCustom =config_get_string(main->Config(), "AdvOut", "FFVCustom");int aBitrate = config_get_int(main->Config(), "AdvOut", "FFABitrate");int aMixes = config_get_int(main->Config(), "AdvOut", "FFAudioMixes");const char *aEncoder =config_get_string(main->Config(), "AdvOut", "FFAEncoder");int aEncoderId =config_get_int(main->Config(), "AdvOut", "FFAEncoderId");const char *aEncCustom =config_get_string(main->Config(), "AdvOut", "FFACustom");obs_data_t *settings = obs_data_create();obs_data_set_string(settings, "url", url);obs_data_set_string(settings, "format_name", formatName);obs_data_set_string(settings, "format_mime_type", mimeType);obs_data_set_string(settings, "muxer_settings", muxCustom);obs_data_set_int(settings, "gop_size", gopSize);obs_data_set_int(settings, "video_bitrate", vBitrate);obs_data_set_string(settings, "video_encoder", vEncoder);obs_data_set_int(settings, "video_encoder_id", vEncoderId);obs_data_set_string(settings, "video_settings", vEncCustom);obs_data_set_int(settings, "audio_bitrate", aBitrate);obs_data_set_string(settings, "audio_encoder", aEncoder);obs_data_set_int(settings, "audio_encoder_id", aEncoderId);obs_data_set_string(settings, "audio_settings", aEncCustom);if (rescale && rescaleRes && *rescaleRes) {int width;int height;int val = sscanf(rescaleRes, "%dx%d", &width, &height);if (val == 2 && width && height) {obs_data_set_int(settings, "scale_width", width);obs_data_set_int(settings, "scale_height", height);}}obs_output_set_mixers(fileOutput, aMixes);obs_output_set_media(fileOutput, obs_get_video(), obs_get_audio());obs_output_update(fileOutput, settings);obs_data_release(settings);
}

参数设置成功后开始准备输出,位于:\libobs\obs-output.c

bool obs_output_start(obs_output_t *output)
{bool encoded;bool has_service;if (!obs_output_valid(output, "obs_output_start"))return false;if (!output->context.data)return false;has_service = (output->info.flags & OBS_OUTPUT_SERVICE) != 0;if (has_service && !obs_service_initialize(output->service, output))return false;encoded = (output->info.flags & OBS_OUTPUT_ENCODED) != 0;if (encoded && output->delay_sec) {return obs_output_delay_start(output);} else {if (obs_output_actual_start(output)) {do_output_signal(output, "starting");return true;}return false;}
}

输出流准备好了之后,创建输出函数线程,开始调用ffmpeg有关函数准备输出。位于\obs-ffmpeg\obs-ffmpeg-output.c

static bool ffmpeg_output_start(void *data)
{struct ffmpeg_output *output = data;int ret;if (output->connecting)return false;os_atomic_set_bool(&output->stopping, false);output->audio_start_ts = 0;output->video_start_ts = 0;output->total_bytes = 0;ret = pthread_create(&output->start_thread, NULL, start_thread, output);return (output->connecting = (ret == 0));
}

然后创建ffmpeg输出,设置输出参数,

**avformat_alloc_output_context2(&data->output, output_format, NULL,NULL);**

上面是调用ffmpeg关键代码设置输出方式,如rtp,rtmp等。下面是完整函数。

static bool ffmpeg_data_init(struct ffmpeg_data *data,struct ffmpeg_cfg *config)
{bool is_rtmp = false;memset(data, 0, sizeof(struct ffmpeg_data));data->config = *config;data->num_audio_streams = config->audio_mix_count;data->audio_tracks = config->audio_tracks;if (!config->url || !*config->url)return false;#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100)av_register_all();
#endifavformat_network_init();is_rtmp = (astrcmpi_n(config->url, "rtmp://", 7) == 0);AVOutputFormat *output_format = av_guess_format(is_rtmp ? "flv" : data->config.format_name, data->config.url,is_rtmp ? NULL : data->config.format_mime_type);if (output_format == NULL) {ffmpeg_log_error(LOG_WARNING, data,"Couldn't find matching output format with ""parameters: name=%s, url=%s, mime=%s",safe_str(is_rtmp ? "flv" : data->config.format_name),safe_str(data->config.url),safe_str(is_rtmp ? NULL: data->config.format_mime_type));goto fail;}avformat_alloc_output_context2(&data->output, output_format, NULL,NULL);if (!data->output) {ffmpeg_log_error(LOG_WARNING, data,"Couldn't create avformat context");goto fail;}if (is_rtmp) {data->output->oformat->video_codec = AV_CODEC_ID_H264;data->output->oformat->audio_codec = AV_CODEC_ID_AAC;} else {if (data->config.format_name)set_encoder_ids(data);}if (!init_streams(data))goto fail;if (!open_output_file(data))goto fail;av_dump_format(data->output, 0, NULL, 1);data->initialized = true;return true;fail:blog(LOG_WARNING, "ffmpeg_data_init failed");return false;
}

写入文件头,调用ffmpeg函数代码为:

ret = avformat_write_header(data->output, &dict);

以下为完整函数代码:

static inline bool open_output_file(struct ffmpeg_data *data)
{AVOutputFormat *format = data->output->oformat;int ret;AVDictionary *dict = NULL;if ((ret = av_dict_parse_string(&dict, data->config.muxer_settings, "="," ", 0))) {ffmpeg_log_error(LOG_WARNING, data,"Failed to parse muxer settings: %s\n%s",av_err2str(ret), data->config.muxer_settings);av_dict_free(&dict);return false;}if (av_dict_count(dict) > 0) {struct dstr str = {0};AVDictionaryEntry *entry = NULL;while ((entry = av_dict_get(dict, "", entry,AV_DICT_IGNORE_SUFFIX)))dstr_catf(&str, "\n\t%s=%s", entry->key, entry->value);blog(LOG_INFO, "Using muxer settings: %s", str.array);dstr_free(&str);}if ((format->flags & AVFMT_NOFILE) == 0) {ret = avio_open2(&data->output->pb, data->config.url,AVIO_FLAG_WRITE, NULL, &dict);if (ret < 0) {ffmpeg_log_error(LOG_WARNING, data,"Couldn't open '%s', %s",data->config.url, av_err2str(ret));av_dict_free(&dict);return false;}}strncpy(data->output->filename, data->config.url,sizeof(data->output->filename));data->output->filename[sizeof(data->output->filename) - 1] = 0;ret = avformat_write_header(data->output, &dict);if (ret < 0) {ffmpeg_log_error(LOG_WARNING, data, "Error opening '%s': %s",data->config.url, av_err2str(ret));return false;}if (av_dict_count(dict) > 0) {struct dstr str = {0};AVDictionaryEntry *entry = NULL;while ((entry = av_dict_get(dict, "", entry,AV_DICT_IGNORE_SUFFIX)))dstr_catf(&str, "\n\t%s=%s", entry->key, entry->value);blog(LOG_INFO, "Invalid muxer settings: %s", str.array);dstr_free(&str);}av_dict_free(&dict);return true;
}

创建数据线程,设置视频流,音频流编码,然后捕获数据流开始输出

static bool try_connect(struct ffmpeg_output *output)
{video_t *video = obs_output_video(output->output);const struct video_output_info *voi = video_output_get_info(video);struct ffmpeg_cfg config;obs_data_t *settings;bool success;int ret;settings = obs_output_get_settings(output->output);obs_data_set_default_int(settings, "gop_size", 120);config.url = obs_data_get_string(settings, "url");config.format_name = get_string_or_null(settings, "format_name");config.format_mime_type =get_string_or_null(settings, "format_mime_type");config.muxer_settings = obs_data_get_string(settings, "muxer_settings");config.video_bitrate = (int)obs_data_get_int(settings, "video_bitrate");config.audio_bitrate = (int)obs_data_get_int(settings, "audio_bitrate");config.gop_size = (int)obs_data_get_int(settings, "gop_size");config.video_encoder = get_string_or_null(settings, "video_encoder");config.video_encoder_id =(int)obs_data_get_int(settings, "video_encoder_id");config.audio_encoder = get_string_or_null(settings, "audio_encoder");config.audio_encoder_id =(int)obs_data_get_int(settings, "audio_encoder_id");config.video_settings = obs_data_get_string(settings, "video_settings");config.audio_settings = obs_data_get_string(settings, "audio_settings");config.scale_width = (int)obs_data_get_int(settings, "scale_width");config.scale_height = (int)obs_data_get_int(settings, "scale_height");config.width = (int)obs_output_get_width(output->output);config.height = (int)obs_output_get_height(output->output);config.format =obs_to_ffmpeg_video_format(video_output_get_format(video));config.audio_tracks = (int)obs_output_get_mixers(output->output);config.audio_mix_count = get_audio_mix_count(config.audio_tracks);if (format_is_yuv(voi->format)) {config.color_range = voi->range == VIDEO_RANGE_FULL? AVCOL_RANGE_JPEG: AVCOL_RANGE_MPEG;config.color_space = voi->colorspace == VIDEO_CS_709? AVCOL_SPC_BT709: AVCOL_SPC_BT470BG;} else {config.color_range = AVCOL_RANGE_UNSPECIFIED;config.color_space = AVCOL_SPC_RGB;}if (config.format == AV_PIX_FMT_NONE) {blog(LOG_DEBUG, "invalid pixel format used for FFmpeg output");return false;}if (!config.scale_width)config.scale_width = config.width;if (!config.scale_height)config.scale_height = config.height;success = ffmpeg_data_init(&output->ff_data, &config);obs_data_release(settings);if (!success) {if (output->ff_data.last_error) {obs_output_set_last_error(output->output,output->ff_data.last_error);}ffmpeg_data_free(&output->ff_data);return false;}struct audio_convert_info aci = {.format =output->ff_data.audio_format};output->active = true;if (!obs_output_can_begin_data_capture(output->output, 0))return false;ret = pthread_create(&output->write_thread, NULL, write_thread, output);if (ret != 0) {ffmpeg_log_error(LOG_WARNING, &output->ff_data,"ffmpeg_output_start: failed to create write ""thread.");ffmpeg_output_full_stop(output);return false;}obs_output_set_video_conversion(output->output, NULL);obs_output_set_audio_conversion(output->output, &aci);obs_output_begin_data_capture(output->output, 0);output->write_thread_active = true;return true;
}

线程设置完毕后,开始通过线程写入数据,输出

static void *write_thread(void *data)
{struct ffmpeg_output *output = data;while (os_sem_wait(output->write_sem) == 0) {/* check to see if shutting down */if (os_event_try(output->stop_event) == 0)break;int ret = process_packet(output);if (ret != 0) {int code = OBS_OUTPUT_ERROR;pthread_detach(output->write_thread);output->write_thread_active = false;if (ret == -ENOSPC)code = OBS_OUTPUT_NO_SPACE;obs_output_signal_stop(output->output, code);ffmpeg_deactivate(output);break;}}output->active = false;return NULL;
}

线程将反复调用下面函数处理数据包,注意这里线程有互斥锁判断,修改不当容易造成程序崩溃。

static int process_packet(struct ffmpeg_output *output)
{AVPacket packet;bool new_packet = false;int ret;pthread_mutex_lock(&output->write_mutex);if (output->packets.num) {packet = output->packets.array[0];da_erase(output->packets, 0);new_packet = true;}pthread_mutex_unlock(&output->write_mutex);if (!new_packet)return 0;/*blog(LOG_DEBUG, "size = %d, flags = %lX, stream = %d, ""packets queued: %lu",packet.size, packet.flags,packet.stream_index, output->packets.num);*/if (stopping(output)) {uint64_t sys_ts = get_packet_sys_dts(output, &packet);if (sys_ts >= output->stop_ts) {ffmpeg_output_full_stop(output);return 0;}}output->total_bytes += packet.size;ret = av_interleaved_write_frame(output->ff_data.output, &packet);if (ret < 0) {av_free_packet(&packet);ffmpeg_log_error(LOG_WARNING, &output->ff_data,"receive_audio: Error writing packet: %s",av_err2str(ret));return ret;}return 0;
}

点击停止录制后,线程将会一步步释放资源。工作涉及整个流程大体如上,后续可能会补充。
附录国人写的一个ffmpeg官方函数调用实现rtmp或者rtp数据传输开源项目:
https://github.com/leixiaohua1020/simplest_ffmpeg_streamer/blob/master/simplest_ffmpeg_streamer/simplest_ffmpeg_streamer.cpp

obs 源码解析笔记相关推荐

  1. 源码解析:Spring源码解析笔记(五)接口设计总览

    本文由colodoo(纸伞)整理 QQ 425343603 Java学习交流群(717726984) Spring解析笔记 启动过程部分已经完成,对启动过程源码有兴趣的朋友可以作为参考文章. 源码解析 ...

  2. React深入学习与源码解析笔记

    ***当前阶段的笔记 *** 「面向实习生阶段」https://www.aliyundrive.com/s/VTME123M4T9 提取码: 8s6v 点击链接保存,或者复制本段内容,打开「阿里云盘」 ...

  3. 震撼来袭,阿里高工的源码解析笔记手抄本,看完去怼面试官

    很多程序员一开始在学习上找不到方向,但我想在渡过了一段时间的新手期之后这类问题大多都会变得不再那么明显,工作的方向也会逐渐变得清晰起来. 但是没过多久,能了解到的资料就开始超过每天学习的能力,像是买了 ...

  4. Spring IOC源码解析笔记

    小伙伴们,你们好,我是老寇 Spring最重要的概念就算IOC和AOP,本篇主要记录Spring IOC容器的相关知识,本文适合有spring相关基础并且想了解spring ioc的相关原理的人看 本 ...

  5. mybatis源码深度解析_30天消化MyBatis源码解析笔记,吊打面试官,offer接到手软

    MyBatis 是一个优秀的 Java 持久化框架,SSM 框架组合(Spring + SpringMVC + Mybatis),依赖 MyBatis 搭建的项目更是数不胜数,在互联网公司的使用中,占 ...

  6. Vue源码解析(笔记)

    github vue源码分析 认识flow flow类型检查 安装flow sudo npm install -g flow-bin 初始化flow flow init 运行flow命令 命令: fl ...

  7. iperf3.1源码解析笔记(1)-网络压力测试工具iperf的使用

    文章目录 一 iperf背景与下载 二 安装及使用方式 三 注意事项 一 iperf背景与下载 iperf用于测试网络性能,支持TCP.UDP和SCTP.可得到网络带宽.延迟抖动和报文丢包的实际测试数 ...

  8. jQuery 源码解析笔记(一)

    从毕业入前端行业,我最先接触的不是原生的js反而是jQuery,因为在大学几年,都是忙着玩,学习的一些编程语言,都是考试了才去看看书,记点基础,更没去学原生js的念想,毕业时候,应聘上前端这个职位,就 ...

  9. 2015.07.20MapReducer源码解析(笔记)

    MapReducer源码解析(笔记) 第一步,读取数据源,将每一行内容解析成一个个键值对,每个键值对供map函数定义一次,数据源由FileInputFormat:指定的,程序就能从地址读取记录,读取的 ...

最新文章

  1. 【TUP第11期】腾讯黄朝兴:浅谈客户端架构
  2. 所长不会用计算机,梅州市公安局: 两派出所所长不会电脑被停职
  3. 成功解决sklearn\ensemble\weight_boosting.py:29: DeprecationWarning: numpy.core.umath_tests is an interna
  4. 设计模式--桥(Bridge)模式
  5. cocos2dx中关于Action动作的相关API的详细介绍
  6. win8计算机安全模式,WIN8如何设置按F8进入安全模式
  7. LeetCode 940. 不同的子序列 II(动态规划)
  8. 如何使用消息队列,Spring Boot和Kubernetes扩展微服务
  9. .Net 数据类型转化
  10. Hello 2019(有待更新)
  11. 安装音量控制程序 WINDOWS
  12. MySQL 排序规则
  13. Navicat导入xlsx文件提示无法打开文件
  14. (linux-x86-ARM)麒麟V10安装DBeaver21.3通用的数据库管理工具和 SQL 客户端
  15. 腾讯云服务器配置jre、jdk、tomcat
  16. fastadmin-addons使用
  17. React从零开始搭建项目
  18. python读json文件数组_如何在python中从json文件读取json对象数组
  19. codeforces 794E Choosing Carrot
  20. JXTA的安装和使用

热门文章

  1. 关于碰到树莓派Zero 2W的HDMI不显示问题
  2. ASCII码与16进制转换对照表
  3. 计算机简介和Xcode认识
  4. uglify的几个配置参数
  5. th和td标签的区别
  6. 关于0宽度unicode字符 “zero-width-space“ “M-bM-^@M-^K“ <U+200B>
  7. 揭秘“飞天”的24小时:马云真正的“核武器”
  8. Flutter 运行项目边框出现绿色的框框包围
  9. 定位+精灵图时,精灵图缩放用法
  10. 4、ABPZero系列教程之拼多多卖家工具 集成短信发送模块