obs推流核心流程分析
前置步骤和录屏是一样的,见我的上一篇文章
https://www.cnblogs.com/billin/p/17219558.html
bool obs_output_actual_start(obs_output_t *output)在上文的这个函数中,如果是启用推流直播,函数指针会转到这里
//碧麟精简批注版
//rtmp推流开始
static bool rtmp_stream_start(void *data)
{
struct rtmp_stream *stream = data;
os_atomic_set_bool(&stream->connecting, true);
//开启推流线程
return pthread_create(&stream->connect_thread, NULL, connect_thread,
stream) == 0;
}
推流使用的是obs-outputs插件
上面函数就是新开一个线程,来执行
这个函数static void *connect_thread(void *data),线程传入参数是stream
先看一下rtmp_stream结构
//rtmp stream结构
struct rtmp_stream {
//obs_output
obs_output_t *output;
//packet信息
struct circlebuf packets;
bool sent_headers;
bool got_first_video;
int64_t start_dts_offset;
volatile bool connecting;
//连接线程地址
pthread_t connect_thread;
//发送线程地址
pthread_t send_thread;
os_sem_t *send_sem;
//推流地址,key
//比如b站,推流地址是rtmp://live-push.bilivideo.com/live-bvc/
struct dstr path, key;
//用户名,密码
struct dstr username, password;
//编码器名字:
//我用的是FMLE/3.0 (compatible; FMSc/1.0)
struct dstr encoder_name;
struct dstr bind_ip;
int64_t last_dts_usec;
uint64_t total_bytes_sent;
int dropped_frames;
pthread_mutex_t dbr_mutex;
struct circlebuf dbr_frames;
size_t dbr_data_size;
long audio_bitrate;
long dbr_est_bitrate;
long dbr_orig_bitrate;
long dbr_prev_bitrate;
long dbr_cur_bitrate;
long dbr_inc_bitrate;
bool dbr_enabled;
// RTMP结构对象
RTMP rtmp;
pthread_t socket_thread;
uint8_t *write_buf;
size_t write_buf_len;
size_t write_buf_size;
pthread_mutex_t write_buf_mutex;
};
rtmp_stream结构里保存了推流的所有关键信息,包括推流地址,key,流的编码器等参数
还保存了几个关键的指针,用于多线程中调度。有连接线程connect_thread,也有发送线程pthread_t send_thread;
//rtmp连接线程
static void *connect_thread(void *data)
{
struct rtmp_stream *stream = data;
int ret;
//设置线程名
os_set_thread_name("rtmp-stream: connect_thread");
//初始化
if (!silently_reconnecting(stream)) {
if (!init_connect(stream)) {
obs_output_signal_stop(stream->output,
OBS_OUTPUT_BAD_PATH);
os_atomic_set_bool(&stream->silent_reconnect, false);
return NULL;
}
} else {
struct encoder_packet packet;
peek_next_packet(stream, &packet);
stream->start_dts_offset = get_ms_time(&packet, packet.dts);
}
//连接
ret = try_connect(stream);
if (ret != OBS_OUTPUT_SUCCESS) {
obs_output_signal_stop(stream->output, ret);
info("Connection to %s failed: %d", stream->path.array, ret);
}
if (!stopping(stream))
pthread_detach(stream->connect_thread);
os_atomic_set_bool(&stream->silent_reconnect, false);
os_atomic_set_bool(&stream->connecting, false);
return NULL;
}
上面的方法是在单独的线程中执行的,主要是做了下面几件事:
1 设置线程名为“rtmp-stream :connect_thread”
2 初始化 init_connect
3 通过调用try_connect(stream)执行实际连接逻辑
//rtmp connect
static int try_connect(struct rtmp_stream *stream)
{
info("Connecting to RTMP URL %s...", stream->path.array);
//rtmp初始化
RTMP_Init(&stream->rtmp);
//设置URL
if (!RTMP_SetupURL(&stream->rtmp, stream->path.array))
return OBS_OUTPUT_BAD_PATH;
RTMP_EnableWrite(&stream->rtmp);
//设置流编码格式
dstr_copy(&stream->encoder_name, "FMLE/3.0 (compatible; FMSc/1.0)");
//设置用户名密码
set_rtmp_dstr(&stream->rtmp.Link.pubUser, &stream->username);
set_rtmp_dstr(&stream->rtmp.Link.pubPasswd, &stream->password);
set_rtmp_dstr(&stream->rtmp.Link.flashVer, &stream->encoder_name);
stream->rtmp.Link.swfUrl = stream->rtmp.Link.tcUrl;
stream->rtmp.Link.customConnectEncode = add_connect_data;
if (dstr_is_empty(&stream->bind_ip) ||
dstr_cmp(&stream->bind_ip, "default") == 0) {
memset(&stream->rtmp.m_bindIP, 0,
sizeof(stream->rtmp.m_bindIP));
} else {
bool success = netif_str_to_addr(&stream->rtmp.m_bindIP.addr,
&stream->rtmp.m_bindIP.addrLen,
stream->bind_ip.array);
if (success) {
int len = stream->rtmp.m_bindIP.addrLen;
bool ipv6 = len == sizeof(struct sockaddr_in6);
info("Binding to IPv%d", ipv6 ? 6 : 4);
}
}
RTMP_AddStream(&stream->rtmp, stream->key.array);
stream->rtmp.m_outChunkSize = 4096;
stream->rtmp.m_bSendChunkSizeInfo = true;
stream->rtmp.m_bUseNagle = true;
//连接
if (!RTMP_Connect(&stream->rtmp, NULL)) {
set_output_error(stream);
return OBS_OUTPUT_CONNECT_FAILED;
}
if (!RTMP_ConnectStream(&stream->rtmp, 0))
return OBS_OUTPUT_INVALID_STREAM;
info("Connection to %s successful", stream->path.array);
//到这里说明连接成功,开始初始化send逻辑,准备推流
return init_send(stream);
}
当连接成功,开始调用init_send函数,初始化send,准备推流
需要注意,send也是在单独的线
程处理的,因为传视频比较大,如果不单开线程,肯定会造成阻塞。
//初始化rtmp send逻辑
static int init_send(struct rtmp_stream *stream)
{
int ret;
obs_output_t *context = stream->output;
//创建send线程,执行send_thread方法,参数是stream
ret = pthread_create(&stream->send_thread, NULL, send_thread, stream);
if (stream->new_socket_loop) {
int one = 1;
#ifdef _WIN32
if (ioctlsocket(stream->rtmp.m_sb.sb_socket, FIONBIO, &one)) {
stream->rtmp.last_error_code = WSAGetLastError();
#else
if (ioctl(stream->rtmp.m_sb.sb_socket, FIONBIO, &one)) {
stream->rtmp.last_error_code = errno;
#endif
warn("Failed to set non-blocking socket");
return OBS_OUTPUT_ERROR;
}
os_event_reset(stream->send_thread_signaled_exit);
info("New socket loop enabled by user");
if (stream->low_latency_mode)
info("Low latency mode enabled by user");
if (stream->write_buf)
bfree(stream->write_buf);
int total_bitrate = 0;
obs_encoder_t *vencoder = obs_output_get_video_encoder(context);
if (vencoder) {
obs_data_t *params = obs_encoder_get_settings(vencoder);
if (params) {
int bitrate =
obs_data_get_int(params, "bitrate");
if (!bitrate) {
warn("Video encoder didn't return a "
"valid bitrate, new network "
"code may function poorly. "
"Low latency mode disabled.");
stream->low_latency_mode = false;
bitrate = 10000;
}
total_bitrate += bitrate;
obs_data_release(params);
}
}
obs_encoder_t *aencoder =
obs_output_get_audio_encoder(context, 0);
if (aencoder) {
obs_data_t *params = obs_encoder_get_settings(aencoder);
if (params) {
int bitrate =
obs_data_get_int(params, "bitrate");
if (!bitrate)
bitrate = 160;
total_bitrate += bitrate;
obs_data_release(params);
}
}
// to bytes/sec
int ideal_buffer_size = total_bitrate * 128;
if (ideal_buffer_size < 131072)
ideal_buffer_size = 131072;
stream->write_buf_size = ideal_buffer_size;
stream->write_buf = bmalloc(ideal_buffer_size);
#ifdef _WIN32
ret = pthread_create(&stream->socket_thread, NULL,
socket_thread_windows, stream);
#else
warn("New socket loop not supported on this platform");
return OBS_OUTPUT_ERROR;
#endif
if (ret != 0) {
RTMP_Close(&stream->rtmp);
warn("Failed to create socket thread");
return OBS_OUTPUT_ERROR;
}
stream->socket_thread_active = true;
stream->rtmp.m_bCustomSend = true;
stream->rtmp.m_customSendFunc = socket_queue_data;
stream->rtmp.m_customSendParam = stream;
}
os_atomic_set_bool(&stream->active, true);
if (!send_meta_data(stream)) {
warn("Disconnected while attempting to send metadata");
set_output_error(stream);
return OBS_OUTPUT_DISCONNECTED;
}
obs_encoder_t *aencoder = obs_output_get_audio_encoder(context, 1);
if (aencoder && !send_additional_meta_data(stream)) {
warn("Disconnected while attempting to send additional "
"metadata");
return OBS_OUTPUT_DISCONNECTED;
}
if (obs_output_get_audio_encoder(context, 2) != NULL) {
warn("Additional audio streams not supported");
return OBS_OUTPUT_DISCONNECTED;
}
if (!silently_reconnecting(stream))
obs_output_begin_data_capture(stream->output, 0);
return OBS_OUTPUT_SUCCESS;
}
核心推流线程,在单独的线程里完成推流逻辑
//推流核心线程
static void *send_thread(void *data)
{
struct rtmp_stream *stream = data;
//设置线程名
os_set_thread_name("rtmp-stream: send_thread");
//设定buffersize
#if defined(_WIN32)
// Despite MSDN claiming otherwise, send buffer auto tuning on
// Windows 7 doesn't seem to work very well.
if (get_win_ver_int() == 0x601) {
DWORD cur_sendbuf_size;
DWORD desired_sendbuf_size = 524288;
socklen_t int_size = sizeof(int);
if (!getsockopt(stream->rtmp.m_sb.sb_socket, SOL_SOCKET,
SO_SNDBUF, (char *)&cur_sendbuf_size,
&int_size) &&
cur_sendbuf_size < desired_sendbuf_size) {
setsockopt(stream->rtmp.m_sb.sb_socket, SOL_SOCKET,
SO_SNDBUF, (char *)&desired_sendbuf_size,
sizeof(desired_sendbuf_size));
}
}
log_sndbuf_size(stream);
#endif
//推流主循环
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;
}
}
if (!stream->sent_headers) {
if (!send_headers(stream)) {
os_atomic_set_bool(&stream->disconnected, true);
break;
}
}
/* silent reconnect signal received from server, reconnect on
* next keyframe */
if (silently_reconnecting(stream) &&
packet.type == OBS_ENCODER_VIDEO && packet.keyframe) {
reinsert_packet_at_front(stream, &packet);
break;
}
if (stream->dbr_enabled) {
dbr_frame.send_beg = os_gettime_ns();
dbr_frame.size = packet.size;
}
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();
pthread_mutex_lock(&stream->dbr_mutex);
dbr_add_frame(stream, &dbr_frame);
pthread_mutex_unlock(&stream->dbr_mutex);
}
}
bool encode_error = os_atomic_load_bool(&stream->encode_error);
if (disconnected(stream)) {
info("Disconnected from %s", stream->path.array);
} else if (encode_error) {
info("Encoder error, disconnecting");
} else if (silently_reconnecting(stream)) {
info("Silent reconnect signal received from server");
} else {
info("User stopped the stream");
}
#if defined(_WIN32)
log_sndbuf_size(stream);
#endif
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);
if (silently_reconnecting(stream)) {
/* manually close the socket to prevent librtmp from sending
* unpublish / deletestream messages when we call RTMP_Close,
* since we want to re-use this stream when we reconnect */
RTMPSockBuf_Close(&stream->rtmp.m_sb);
stream->rtmp.m_sb.sb_socket = -1;
}
RTMP_Close(&stream->rtmp);
/* 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);
}
}
if (!stopping(stream)) {
pthread_detach(stream->send_thread);
if (!silently_reconnecting(stream))
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);
}
if (!silently_reconnecting(stream)) {
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);
}
}
if (silently_reconnecting(stream)) {
rtmp_stream_start(stream);
}
return NULL;
}
obs推流核心流程分析的更多相关文章
- Mybatis源码学习第六天(核心流程分析)之Executor分析(补充)
补充上一章没有讲解的三个Executor执行器; 还是贴一下之前的代码吧;我发现其实有些分析注释还是写在代码里面比较好,方便大家理解,之前是我的疏忽,不好意思 @Override public < ...
- Mybatis源码学习第六天(核心流程分析)之Executor分析
今Executor这个类,Mybatis虽然表面是SqlSession做的增删改查,其实底层统一调用的是Executor这个接口 在这里贴一下Mybatis查询体系结构图 Executor组件分析 E ...
- 【转】Hostapd工作流程分析
[转]Hostapd工作流程分析 转自:http://blog.chinaunix.net/uid-30081165-id-5290531.html Hostapd是一个运行在用户态的守护进程,可以通 ...
- Android7.0 Phone应用源码分析(二) phone来电流程分析
接上篇博文:Android7.0 Phone应用源码分析(一) phone拨号流程分析 今天我们再来分析下Android7.0 的phone的来电流程 1.1TelephonyFramework 当有 ...
- u-boot启动流程分析(2)_板级(board)部分
转自:http://www.wowotech.net/u-boot/boot_flow_2.html 目录: 1. 前言 2. Generic Board 3. _main 4. global dat ...
- Android 4.4KitKat AudioRecord 流程分析
Android是架构分为三层: 底层 Linux Kernel 中间层 主要由C++实现 (Android 60%源码都是C++实现) 应用层 主要由JAVA开发的应用程序 应用程序执行 ...
- HDFS2.x之RPC流程分析
HDFS2.x之RPC流程分析 1 概述 Hadoop提供了一个统一的RPC机制来处理client-namenode, namenode-dataname,client-dataname之间的通信.R ...
- 【转】linux文件系统之mount流程分析
本质上,Ext3 mount的过程实际上是inode被替代的过程. 例如,/dev/sdb块设备被mount到/mnt/alan目录.命令:mount -t ext3 /dev/sdb /mnt/al ...
- 第2章 rsync算法原理和工作流程分析
本文通过示例详细分析rsync算法原理和rsync的工作流程,是对rsync官方技术报告和官方推荐文章的解释. 以下是本文的姊妹篇: 1.rsync(一):基本命令和用法 2.rsync(二):ino ...
- SpringMVC的流程分析(一)—— 整体流程概括
SpringMVC的整体概括 之前也写过springmvc的流程分析,只是当时理解的还不透彻所以那篇文章就放弃了,现在比之前好了些,想着写下来分享下,也能增强记忆,也希望可以帮助到人,如果文章中有什么 ...
随机推荐
- 请求接口类型blob,下载文件
axiosGet () { var This = this var url = '/group1/M00/00/21/MejEvGOX_zOAL2kiAAAAUhB5Iqg138.txt?token= ...
- ant design vue的tooltip的宽度修改不生效
// 注意,vue 项目,style 标签不能加 scoped 选项,否则样式不生效 <a-tooltip placement="top" :overlayClassName ...
- 基于rabbitmq之MQTT协议的智能家居
智能家居项目 智能可燃气体报警器 产品是一款可燃气体报警器,如果家中燃气泄露浓度到达一定阈值,报警器检测到并上传气体浓度值给后台,后台以电话.短信.微信等方式,提醒用户家中可能有气体泄漏. 用户还可能 ...
- 【七侠传】冲刺阶段--Day6
[七侠传]冲刺阶段--Day6 团队成员 20181221曾宇涛 20181202李祎铭 20181209沙桐 20181215薛胜瀚 20181216杨越麒 20181223何家豪 20181232 ...
- Luogu P3368 【模板】树状数组 2 [区间修改-单点查询]
P3368 [模板]树状数组 2 题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某区间每一个数数加上x 2.求出某一个数的和 输入输出格式 输入格式: 第一行包含两个整数N.M,分别表 ...
- ethcat开发记录 一
一.方案 1.移植开源方案SOEM 2.专用芯片 二.SOEM移植 (一)硬件 stm32f407,168M PHY:LAN8720A (ii) Points to note 1, the PHY a ...
- ES6的Map和Set的了解和练习
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 分布式-Etcd介绍
Etcd客户端基本操作 提供了如下操作接口: put - 添加一个新的 key-value 到存储中 get - 获取一个 key 的 value delete - 删除节点 range - 获取一个 ...
- daimayuan第二课(1.二叉树的遍历,2.二叉树的最近公共祖先,3.二叉搜索树)
二叉树的最近公共祖先: 1:概念:就是两个节点在这棵树上深度最大的公共的祖先节点. 换句话说,就是两个点在这棵树上距离最近的公共祖先节点. 2:因为是基础课,所以tarjan和倍增lca就留到中级课再 ...
- suse 关闭 图形界面
图形界面 /etc/systemd/system/default.target -> /usr/lib/systemd/system/graphical.target ln -s /usr/li ...