obs推流核心流程分析

博客 动态
0 175
羽尘
羽尘 2023-03-16 19:26:28
悬赏:0 积分 收藏

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;
}

 

posted @ 2023-03-16 18:53  billin  阅读(8)  评论(0编辑  收藏  举报
回帖
    羽尘

    羽尘 (王者 段位)

    2335 积分 (2)粉丝 (11)源码

     

    温馨提示

    亦奇源码

    最新会员