OBS-RTMP推流
1、OBSBasic::StartStreaming()
//调用函数
outputHandler->SetupStreaming(service)
outputHandler->StartStreaming(service)
outputHandler是指SimpleOutput或者AdvancedOutput,这两者都继承自BasicOutputHandler。
具体使用哪一个,由用户在 设置->输出->输出模式中配置。
AdvancedOutput提供了更多的编码配置选项,包括音频和视频。
2、 SetupStreaming(obs_service_t *service)
//调用函数
if (!Active())
SetupOutputs();
streamOutput =obs_output_create();
//设置streamOutput->video_encoder为h264Streaming
//也就是为视频的编码器
//h264Streaming目前可以使用的有x264 、nvenc等
obs_output_set_video_encoder(streamOutput, h264Streaming);
//设置streamOutput->audio_encoders[0]为streamAudioEnc
obs_output_set_audio_encoder(streamOutput, streamAudioEnc, 0);
1、配置流输出参数
2、创建输出
2.1 SetupOutputs()
//为视频编码器h264Streaming设置视频源
obs_encoder_set_video(h264Streaming, obs_get_video());
if (h264Recording)
obs_encoder_set_video(h264Recording, obs_get_video());
for (size_t i = 0; i < MAX_AUDIO_MIXES; i++)
obs_encoder_set_audio(aacTrack[i], obs_get_audio());
//为音频编码器h264Streaming设置音频源
obs_encoder_set_audio(streamAudioEnc, obs_get_audio());
obs_encoder_set_audio(streamArchiveEnc, obs_get_audio());
//设置编码器的参数例如视频 分辨率、比特率等
SetupStreaming();
2.2 obs_output_create()
//调用函数
info = find_output(id); //从输出插件组中找到与id相同的输出插件
//对于rtmp来讲就是rtmp输出插件。
//创建一个obs_output结构
//初始化obs_output中的参数,主要是互斥锁、信号处理函数
//初始化重连参数
//设置视频源
output->video = obs_get_video();
//设置音频源
output->audio = obs_get_audio();
//调用输出插件的create函数。
//对于rtmp来讲就是rtmp_stream_create(),返回值为struct rtmp_stream *;
//将返回值赋值给output->context.data
output->context.data = info->create(output->context.settings,
output);
2.2.1 info->create()
对于rtmp输出来讲它调用了rtmp_stream_create()
1、创建rtmp_stream,它是OBS通过rtmp协议进行推流的主要模块;
2、RTMP_Init();
3、对rtmp_stream中的成员变量初始化,主要是互斥锁和条件变量;
4、返回struct rtmp_stream *;
3、StartStreaming()
通过SetupStreaming进行一系列输出配置过后,调用StartStreaming启动推流。
//设置重连参数
//延迟参数
//绑定ip
//调用函数
obs_output_start(streamOutput);
3.1 obs_output_start()
//output->info 指向一个结构体,结构体内存放的是输出插件所对应的一组函数和
//插件所对应的flags,对于rtmp来讲就是rtmp输出插件
//
bool has_service = (output->info.flags & OBS_OUTPUT_SERVICE) != 0;
// rtmp输出插件设置了OBS_OUTPUT_SERVICE,
// service必须通过 obs_output_set_service() 函数分配,它是推流的必要配置。这个service 通常是指twitch、YouTube等推流服务器
// 如果设置了initialize函数,则调用对应的初始化函数。
if (has_service && !obs_service_initialize(output->service, output))
{
return false;
}
// encoded是指输出是已编码数据
// rtmp输出插件设置了OBS_OUTPUT_ENCODED
bool encoded = (output->info.flags & OBS_OUTPUT_ENCODED) != 0;
// delay_sec 串流延迟时间。由用户在配置里设置串流延迟。
if (encoded && output->delay_sec) {
obs_output_delay_start(output);
} else {
obs_output_actual_start(output)
}
3.1.2 obs_output_delay_start()
//这里主要是设置一个延时处理的事件
struct delay_data dd = {
.msg = DELAY_MSG_START,
.ts = os_gettime_ns(),
};
//做一些检查
//将dd 放入到output->delay_data 随后会用到
circlebuf_push_back(&output->delay_data, &dd, sizeof(dd));
3.1.1 obs_output_actual_start()
//等待停止操作完成
os_event_wait(output->stopping_event);
// output->context.data:在obs_output_create,通过调用info->create创建,
// 对于rtmp来讲output->context.data是指向struct rtmp_stream 的指针。
//output->info.start 对于rtmp来讲是rtmp_stream_start。
if (output->context.data)
success = output->info.start(output->context.data);
3.1.1.1 rtmp_stream_start()
//开始前进行一系列的判断,是否能够开始
//创建连接线程。
pthread_create(&stream->connect_thread, NULL, connect_thread,stream) == 0;
3.1.1.1.1 connect_thread()
开启新的线程处理耗时的链接过程。
//该线程中主要调用以下连个函数
//初始化连接
if (!init_connect(stream)) {
obs_output_signal_stop(stream->output, OBS_OUTPUT_BAD_PATH);
return NULL;
}
//尝试连接
ret = try_connect(stream);
3.1.1.1.1.1 init_connect()
//如果之前的推流线程还没有退出,则先等待推流线程退出。
if (stopping(stream)) {
pthread_join(stream->send_thread, NULL);
}
//获取配置的推流服务器
//初始化stream的参数,主要是发送数据的统计
//获取推流路径、推流秘钥、用户名、密码
//B帧、P帧丢帧策略
//视频和音频编码器
obs_encoder_t *venc = obs_output_get_video_encoder(stream->output);
obs_encoder_t *aenc = obs_output_get_audio_encoder(stream->output, 0);
//获取编码器的配置参数
obs_data_t *vsettings = obs_encoder_get_settings(venc);
obs_data_t *asettings = obs_encoder_get_settings(aenc);
//根据编码器的配置获取音频比特率、视频比特率
//绑定的IP
3.1.1.1.1.2 try_connect()
//在重新连接时,需要重新设置librtmp内部的参数,
//否则数据发送和接收在另一端不会被正确解析
RTMP_Reset(&stream->rtmp);
//因为我们没有在上面调用RTMP_Init,所以没有其他好地方可以重置它,
//因为在 RTMP_Close 中这样做会破坏丑陋的 RTMP 身份验证系统
memset(&stream->rtmp.Link, 0, sizeof(stream->rtmp.Link));
stream->rtmp.last_error_code = 0;
// 设置推拉流的URL。内部调用了RTMP_ParseURL主要用于解析url
if (!RTMP_SetupURL(&stream->rtmp, stream->path.array))
return OBS_OUTPUT_BAD_PATH;
// 设置RTMP_EnableWrite将发送publish命令将命名流发布到服务端。
RTMP_EnableWrite(&stream->rtmp);
//初始化一个stream id 将流媒体资源(或推流码)绑定到一个stream id上
//path 流媒体服务器地址(例如:rtmp://sendtc3.douyu.com/live)
//key 流媒体资源(推流码)
RTMP_AddStream(&stream->rtmp, stream->key.array);
//建立socket 连接,进行rtmp的握手过程,建立rtmp的NetConnection连接。
if (!RTMP_Connect(&stream->rtmp, NULL)) {
set_output_error(stream);
return OBS_OUTPUT_CONNECT_FAILED;
}
//创建流/循环读取服务端发送过来的各种消息,比如window ack, set peer bandwidth, set chunk size, _result等
//直到接收到了play或者publish可以开始的信息。
if (!RTMP_ConnectStream(&stream->rtmp, 0))
return OBS_OUTPUT_INVALID_STREAM;
return init_send(stream);
3.1.1.1.1.2.1 init_send()
创建数据发送线程 send_thread()
ret = pthread_create(&stream->send_thread, NULL, send_thread, stream);
// 发送视频元数据,主要是flv头部信息,包含了音视频的编码。
if (!send_meta_data(stream)) {
warn("Disconnected while attempting to send metadata");
set_output_error(stream);
return OBS_OUTPUT_DISCONNECTED;
}
obs_output_begin_data_capture(stream->output, 0);
3.1.1.1.1.2.1.1 send_thread()
//这里主要是循环等待要发送的数据包,然后发送这个数据包。
while (os_sem_wait(stream->send_sem) == 0) {
//判断需不需要停止
if (stopping(stream) && stream->stop_ts == 0) {
break;
}
//获取数据包
if (!get_next_packet(stream, &packet))
continue;
if (!stream->sent_headers) {
if (!send_headers(stream)) {
os_atomic_set_bool(&stream->disconnected, true);
break;
}
}
//
//发送数据包
if (send_packet(stream, &packet, false, packet.track_idx) < 0) {
info("send packet failed, idx: %u", packet.track_idx);
os_atomic_set_bool(&stream->disconnected, true);
break;
}
}
3.1.1.1.1.2.1.1.1 send_packet()
//对要发送的数据包进行flv封装。
if (idx > 0) {
flv_additional_packet_mux(
packet, is_header ? 0 : stream->start_dts_offset, &data,
&size, is_header, idx);
} else {
flv_packet_mux(packet, is_header ? 0 : stream->start_dts_offset,
&data, &size, is_header);
}
//通过rtmp发送数据
ret = RTMP_Write(&stream->rtmp, (char *)data, (int)size, 0);
3.1.1.1.1.2.1.1 obs_output_begin_data_capture(obs_output_t *output, uint32_t flags)
//这个函数主要是调用hook_data_capture,
//对于视频来讲,会设置渲染后原始视频帧输出给编码器,编码器对视频编码后输出给音视频同步函数(如果只有音频或视频则输出给默认的回调),
//音视频同步后输出给obs_output_t 类型的插件,由插件进行推流或者录制。
hook_data_capture(output, encoded, has_video, has_audio);
//函数:hook_data_capture(output, encoded, has_video, has_audio)
// 数据需要编码
if (encoded) {
// 编码回调设置
//如果有音频和视频回调函数就设置为interleave_packets,这里主要是进行音视频的同步。
//音频和视频只有一个则使用默认的default_encoded_callback函数
encoded_callback = (has_video && has_audio)
? interleave_packets
: default_encoded_callback;
//是否设置了延迟,如果设置了延迟则将encoded_callback 设置为process_delay
//设置output->delay_callback 为encoded_callback
//这一步对应obs_output_delay_start
if (output->delay_sec) {
output->delay_cur_flags = output->delay_flags;
output->delay_callback = encoded_callback;
encoded_callback = process_delay;
os_atomic_set_bool(&output->delay_active, true);
}
//有音频则启动音频编码
if (has_audio)
start_audio_encoders(output, encoded_callback);
//有视频则启动视频编码
if (has_video)
obs_encoder_start(output->video_encoder,
encoded_callback, output);
} else {
// 数据不需要编码,直接发送
if (has_video)
start_raw_video(output->video,
get_video_conversion(output),
default_raw_video_callback, output);
if (has_audio)
start_raw_audio(output);
}
//函数 void obs_encoder_start(obs_encoder_t *encoder,void (*new_packet)(void *param,struct encoder_packet *packet),void *param)
//参数 param 对应 obs_output_t
//调用函数
obs_encoder_start_internal(encoder, new_packet, param);
//函数 void obs_encoder_start_internal(obs_encoder_t *encoder,void (*new_packet)(void *param, struct encoder_packet *packet),void *param))
//设置编码器的 callback的参数,主要是为数据被编码后所需要进行的后续操作,
// new_packet 对应 interleave_packets或 default_encoded_callback;
struct encoder_callback cb = {false, new_packet, param};
bool first = (encoder->callbacks.num == 0);
size_t idx = get_callback_idx(encoder, new_packet, param);
// 将回调函数塞入encoder->callbacks数组中
if (idx == DARRAY_INVALID)
da_push_back(encoder->callbacks, &cb);
if (first) {
.
.
.
add_connection(encoder);
}
//函数 void add_connection(struct obs_encoder *encoder)
if (encoder->info.type == OBS_ENCODER_AUDIO) {
.
.
.
//设置音频的回调
audio_output_connect(encoder->media, encoder->mixer_idx,
&audio_info, receive_audio, encoder);
} else {
.
.
.
//设置视频的回调
if (gpu_encode_available(encoder)) {
start_gpu_encode(encoder);
} else {
start_raw_video(encoder->media, &info, receive_video,
encoder);
}
}
//函数 void start_raw_video(video_t *v, const struct video_scale_info *conversion, void (*callback)(void *param, struct video_data *frame),void *param)
//调用
video_output_connect(v, conversion, callback, param);
//函数 void video_output_connect(video_t *video, const struct video_scale_info *conversion,void (*callback)(void *param, struct video_data *frame), void *param)
if (video_get_input_idx(video, callback, param) == DARRAY_INVALID) {
struct video_input input;
memset(&input, 0, sizeof(input));
//设置回调函数为obs-encoder.c:receive_video
input.callback = callback; // callback: obs-encoder.c:receive_video
input.param = param;
//设置参数
//初始化video_input
success = video_input_init(&input, video);
if (success) {
if (video->inputs.num == 0) {
if (!os_atomic_load_long(&video->gpu_refs)) {
reset_frames(video);
}
os_atomic_set_bool(&video->raw_active, true);
}
//将input 塞入video->inputs
da_push_back(video->inputs, &input);
}
}
// 视频编码线程。编码视频帧,编码后将数据输出到文件(录制)或者送给推流
// 待编码的视频源是由渲染线程提供
static void *video_thread(void *param)
{
// 等待渲染线程提供数据 video->update_semaphore == obs->video.video->update_semaphore
while (os_sem_wait(video->update_semaphore) == 0) {
// video_output_cur_frame
while (!video->stop && !video_output_cur_frame(video)) {
os_atomic_inc_long(&video->total_frames);
}
}
}
//函数video_output_cur_frame
static inline bool video_output_cur_frame(struct video_output *video)
{
frame_info = &video->cache[video->first_added]; // 取待编码数据
for (size_t i = 0; i < video->inputs.num; i++) {
struct video_input *input = video->inputs.array + i;
struct video_data frame = frame_info->frame;
// start_raw_video处设置的回调。receive_video or default_raw_video_callback.
if (scale_video_output(input, &frame))
input->callback(input->param, &frame);
}
}
//函数video_output_cur_frame
bool video_output_cur_frame(struct video_output *video)
{
frame_info = &video->cache[video->first_added]; // 取待编码数据
for (size_t i = 0; i < video->inputs.num; i++) {
struct video_input *input = video->inputs.array + i;
struct video_data frame = frame_info->frame;
// start_raw_video处设置的回调。receive_video or default_raw_video_callback.
if (scale_video_output(input, &frame))
input->callback(input->param, &frame);
}
}
//函数 obs-encoder.c : receive_video(void *param, struct video_data *frame)
static void receive_video(void *param, struct video_data *frame)
{
//设置好待编码的数据和参数
// 执行编码enc_frame数据
if (do_encode(encoder, &enc_frame))
encoder->cur_pts += encoder->timebase_num;
}
//函数 obs-encoder.c : bool do_encode(struct obs_encoder *encoder, struct encoder_frame *frame)
bool do_encode(struct obs_encoder *encoder, struct encoder_frame *frame)
{
// 编码后编码后的NAL数据存储在encoder->context.data(obs_x264 *)和pkt中
// x264 对应为 obs_x264_encode
success = encoder->info.encode(encoder->context.data, frame, &pkt,
&received);
send_off_encoder_packet(encoder, success, received, &pkt);
}
//函数 obs-encoder.c : void send_off_encoder_packet(obs_encoder_t *encoder, bool success,bool received, struct encoder_packet *pkt)
bool send_off_encoder_packet(obs_encoder_t *encoder, bool success,bool received, struct encoder_packet *pkt)
{
if (received) {
//cb 对应为 interleave_packets 或者 default_encoded_callback
struct encoder_callback *cb;
cb = encoder->callbacks.array + (i - 1);
send_packet(encoder, cb, pkt);
}
}
//函数 obs-encoder.c : void send_packet(struct obs_encoder *encoder, struct encoder_callback *cb,struct encoder_packet *packet)
void send_packet(struct obs_encoder *encoder, struct encoder_callback *cb,struct encoder_packet *packet)
{
/* include SEI in first video packet */
if (encoder->info.type == OBS_ENCODER_VIDEO && !cb->sent_first_packet)
send_first_video_packet(encoder, cb, packet); // 将视频的SEI信息添加到视频数据中
else
//param 对应为struct obs_output *
cb->new_packet(cb->param, packet); // 调用interleave_packets 或者 default_encoded_callback 进行视频数据的发送
}
//函数 obs-output.c : void interleave_packets(void *data, struct encoder_packet *packet)
void interleave_packets(void *data, struct encoder_packet *packet)
{
//音频视频的同步
//发送数据
send_interleaved(output); // 发送数据
}
// send_interleaved内部主要是调用output->info.encoded_packet(output->context.data,&out);
// output->info.encoded_packet 对于rtmp 来讲就是rtmp_stream_data