文章目录

  • 前言
  • 底层原理
  • ffmpeg-output 创建
  • ffmpeg_output_create 创建
  • ffmpeg-output 启动
  • ffmpeg_output_start 启动
  • 音视频编码处理
  • receive_audio 音频编码
  • receive_video 视频编码
  • ffmpeg-output 推流 | 录制
  • write_thread 线程
  • ffmpeg-output 停止
  • ffmpeg_output_stop 停止
  • 总结



前言

obs系列文章入口:obs-studio项目简介和架构

ffmpeg-output 输出是 obs 中支持协议最丰富的一个输出源。即支持本地视频录制,又支持丰富的网络协议推流。还支持设置音频编码器,视频编码器,以及对应的编码参数。是一个自定义选项非常丰富的输出源。

obs python 视频上传 obs输出视频_rtmp

底层原理

这个输出源插件是基于 ffmpeg libavcodec libavformat 提供的 视频编码、音频编码、视频录制、网络推流 能力。

理论上只要是 obs 所依赖的 ffmpeg 相关库支持的音视频编码和媒体协议,都可以用来录制或者推流。

ffmpeg-output 创建

ffmpeg_output_create 函数打断点,可以看到 ffmpeg-output 的创建是在程序启动时。

下面贴一下创建 ffmpeg-output 的调用堆栈

>	obs-ffmpeg.dll!ffmpeg_output_create(obs_data * settings, obs_output * output) 行 648	C
 	obs.dll!obs_output_create(const char* id, const char* name, obs_data* settings, obs_data* hotkey_data) 行 155	C
 	obs64.exe!AdvancedOutput::AdvancedOutput(OBSBasic * main_) 行 1225	C++	  // 创建 ffmpeg-output 高级输出
 	obs64.exe!CreateAdvancedOutputHandler(OBSBasic * main) 行 2069	C++
 	obs64.exe!OBSBasic::ResetOutputs() 行 1696	C++
 	obs64.exe!OBSBasic::OBSInit() 行 1841	C++
 	obs64.exe!OBSApp::OBSInit() 行 1474	C++
 	obs64.exe!run_program(std::basic_fstream<char,std::char_traits<char>> & logFile, int argc, char * * argv) 行 2138	C++
 	obs64.exe!main(int argc, char * * argv) 行 2839	C++
 	obs64.exe!WinMain(HINSTANCE__ * __formal, HINSTANCE__ * __formal, char * __formal, int __formal) 行 97	C++

ffmpeg_output_create 创建

static void *ffmpeg_output_create(obs_data_t *settings, obs_output_t *output)
{
	struct ffmpeg_output *data = bzalloc(sizeof(struct ffmpeg_output));
	pthread_mutex_init_value(&data->write_mutex);
	data->output = output;
	// 初始化相关的 mutex  event 信号量
	if (pthread_mutex_init(&data->write_mutex, NULL) != 0)
		goto fail;
	if (os_event_init(&data->stop_event, OS_EVENT_TYPE_AUTO) != 0)
		goto fail;
	if (os_sem_init(&data->write_sem, 0) != 0)
		goto fail;
	// 设置 ffmepg 的日志输出回调函数
	av_log_set_callback(ffmpeg_log_callback);

	UNUSED_PARAMETER(settings);
	return data;

// 创建失败后的处理 ,几乎不可能发生
fail:
	pthread_mutex_destroy(&data->write_mutex);
	os_event_destroy(data->stop_event);
	bfree(data);
	return NULL;
}

ffmpeg-output 启动

obs 的 ffmpeg 高级输出是通过点击 UI 的开始录制按钮启动的。

虽然是开始录制按钮,如果设置的 url 是网络地址,则是根据设置的推流地址进行网络推流。

启动 ffmepg-output 的调用堆栈

>	obs-ffmpeg.dll!ffmpeg_output_start(void * data) 行 1180	C
 	obs.dll!obs_output_actual_start(obs_output * output) 行 256	C
 	obs.dll!obs_output_start(obs_output * output) 行 293	C
 	obs64.exe!AdvancedOutput::StartRecording() 行 1904	C++
 	obs64.exe!OBSBasic::StartRecording() 行 7070	C++
 	obs64.exe!OBSBasic::on_recordButton_clicked() 行 7589	C++
 	obs64.exe!OBSBasic::qt_static_metacall(QObject* _o, QMetaObject::Call _c, int _id, void** _a) 行 1306	C++
 	obs64.exe!OBSBasic::qt_metacall(QMetaObject::Call _c, int _id, void * * _a) 行 1500	C++	[外部代码]	
 	obs64.exe!run_program(std::basic_fstream<char,std::char_traits<char>> & logFile, int argc, char * * argv) 行 2143	C++
 	obs64.exe!main(int argc, char * * argv) 行 2839	C++
 	obs64.exe!WinMain(HINSTANCE__ * __formal, HINSTANCE__ * __formal, char * __formal, int __formal) 行 97	C++

贴一下 ffmpeg-output 的关键函数调用图

obs python 视频上传 obs输出视频_ffmpeg_02

ffmpeg_output_start 启动

通过上面的截图可以看到 ffmpeg_output 的启动是通过创建 start_thread 线程来做的启动工作。
start_thread 线程主要做了以下工作

  • 初始化音视频流
  • 调用ffmepg api 打开 url
  • 写入文件头
  • 创建了 write_thread 线程等待处理音视频包的输出

音视频编码处理

receive_audio 音频编码

参考截图可以看到在 receive_audio 函数里面调用 ffmpeg 编码相关api 完成了音频的编码,obs 的源码是兼容了 3.x 和 4.x 新老版本的 ffmpeg 编码api 写法。有需要兼容老版本 ffmepg 的编码 api的需求可以参考 obs的做法。

完成音频编码后,将编码后的音频包插入 ffmpeg_output -> DARRAY(AVPacket) packets 包队列,并发送信号量通知 write_thread 线程发送音视频包。

音频编码处理堆栈
obs 音频编码线程参考这篇文章:audio_thread 音频编码线程理解

>	obs-ffmpeg.dll!receive_audio(void * param, unsigned __int64 mix_idx, audio_data * frame) 行 900	C
 	obs.dll!default_raw_audio_callback(void * param, unsigned __int64 mix_idx, audio_data * in) 行 1859	C
 	obs.dll!do_audio_output(audio_output * audio, unsigned __int64 mix_idx, unsigned __int64 timestamp, unsigned int frames) 行 126	C
 	obs.dll!input_and_output(audio_output * audio, unsigned __int64 audio_time, unsigned __int64 prev_time) 行 201	C
 	obs.dll!audio_thread(void * param) 行 241	C
 	w32-pthreads.dll!ptw32_threadStart(void * vthreadParms) 行 225	C

receive_video 视频编码

同音频编码处理类似,在 receive_video 函数完成了视频帧的编码,将编码后的视频帧插入音视频包队列。并发送信号量通知 write_thread 线程发送音视频包。

视频编码处理堆栈
obs 视频编码线程参考这篇文章:video_thread 视频编码线程理解

>	obs-ffmpeg.dll!receive_video(void * param, video_data * frame) 行 744	C
 	obs.dll!default_raw_video_callback(void * param, video_data * frame) 行 1768	C
 	obs.dll!video_output_cur_frame(video_output * video) 行 143	C
 	obs.dll!video_thread(void * param) 行 188	C
 	w32-pthreads.dll!ptw32_threadStart(void * vthreadParms) 行 225	C

ffmpeg-output 推流 | 录制

write_thread 线程

write_thread 线程收到 receive_audioreceive_video 发送的信号量后调用 av_interleaved_write_frame 将音视频包写入本地文件或者推流到网络。

这个线程工作比较简单,对照上面的截图阅读源码很容易理解。

ffmpeg-output 停止

ffmpeg_output_stop 停止

  • 释放 ffmepg 申请的资源
  • 写入文件尾

总结

这个 ffmpeg 高级输出对于我们开发人员非常有用,可以非常方便的测试很多种流媒体协议推流。比如udp rtp srt rtsp 推流,只要是依赖的 ffmpeg 相关库支持的协议,可以很方便的推出你想要的流媒体协议流。

整个插件的代码也不复杂,都是对于 ffmpeg api 的调用,而且兼容了新老版本的 ffmpeg 音视频编码。