文章目录
前言
obs系列文章入口:https://blog.youkuaiyun.com/qq_33844311/article/details/121479224
obs支持非常丰富的视频格式的录制,底层是基于ffmpeg的api实现的视频文件的录制。不仅支持本地视频文件录制(flv,mp4,mkv,ts,mp3),也支持流媒体网络协议的推流(tcp,udp,srt,rtmp,http,rist)。理论上只要obs所依赖的ffmpeg支持的协议都可以推流或者录制。
针对视频的录制obs有两套实现方案,一个是支持的录制参数相对简单的 ffmpeg_muxer,一个是支持非常丰富的录制参数设置的 ffmpeg_output 。
这篇文章主要介绍简单输出 ffmpeg_muxer
ffmpeg_muxer的创建
通过下面的调用堆栈分享,在程序启动的时候就创建了 ffmpeg_muxer 视频录制output。而rtmp_output的创建是在用户点击开始推流按钮后才创建,详细请参考这篇文章:rtmp_output源码解析
> obs-ffmpeg.dll!ffmpeg_mux_create(obs_data * settings, obs_output * output) 行 87 C
obs.dll!obs_output_create(const char* id, const char* name,obs_data* settings, obs_data* hotkey_data) 行 155 C
obs64.exe!SimpleOutput::SimpleOutput(OBSBasic * main_) 行 458 C++
obs64.exe!CreateSimpleOutputHandler(OBSBasic * main) 行 2037 C++
obs64.exe!OBSBasic::ResetOutputs() 行 1658 C++
obs64.exe!OBSBasic::OBSInit() 行 1802 C++
obs64.exe!OBSApp::OBSInit() 行 1470 C++
obs64.exe!run_program(std::basic_fstream<char,std::char_traits<char>> & logFile, int argc, char* * argv) 行 2134 C++
obs64.exe!main(int argc, char * * argv) 行 2835 C++
obs64.exe!WinMain(HINSTANCE__ * __formal, HINSTANCE__ * __formal, char * __formal, int __formal) 行 97 C++
启动视频录制
当用户点击UI上的开始录制按钮,得到下面的调用堆栈。
> obs-ffmpeg.dll!ffmpeg_mux_start(void * data) 行 320 C
obs.dll!obs_output_actual_start(obs_output * output) 行 256 C
obs.dll!obs_output_start(obs_output * output) 行 293 C
obs64.exe!SimpleOutput::StartRecording() 行 1030 C++
obs64.exe!OBSBasic::StartRecording() 行 7012 C++
obs64.exe!OBSBasic::on_recordButton_clicked() 行 7532 C++
obs64.exe!OBSBasic::qt_static_metacall(QObject * _o, QMetaObject::Call _c, int _id, void * * _a) 行 1310 C++
obs64.exe!OBSBasic::qt_metacall(QMetaObject::Call _c, int _id, void * * _a) 行 1504 C++
[外部代码]
obs64.exe!run_program(std::basic_fstream<char,std::char_traits<char>> & logFile, int argc, char * * argv) 行 2139 C++
obs64.exe!main(int argc, char * * argv) 行 2835 C++
obs64.exe!WinMain(HINSTANCE__ * __formal, HINSTANCE__ * __formal, char * __formal, int __formal) 行 97 C++
ffmpeg_mux_start 真正启动视频录制的函数
视频写文件操作在另一个进程obs-ffmpeg-mux.exe里面 ,它和主进程obs64.exe通过管道进行通信。obs对管道的操作都封装在 obs-studio\libobs\util\pipe.h 包含了管道的创建、销毁、读取、写入等操作,关于管道的编程相关操作就不做赘述,具体实现阅读源码。
接下来通过源码注释方式,分以下 ffmpeg_mux_start 都做了什么工作。
static bool ffmpeg_mux_start(void *data)
{
struct ffmpeg_muxer *stream = data;
obs_data_t *settings;
const char *path;
// 判断是否可以开启视频录制
if (!obs_output_can_begin_data_capture(stream->output, 0))
return false;
// 创建视频编码器和音频编码器
// obs中 x264编码器的使用:https://blog.youkuaiyun.com/qq_33844311/article/details/122072255
if (!obs_output_initialize_encoders(stream->output, 0))
return false;
// 获取输出的配文件settings
settings = obs_output_get_settings(stream->output);
if (stream->is_network) {
obs_service_t *service;
service = obs_output_get_service(stream->output);
if (!service)
return false;
// 获取推流地址url
path = obs_service_get_url(service);
} else {
//获取录制的视频文件路径
path = obs_data_get_string(settings, "path");
}
// 如果不是网络流,检查写入的文件路径是否合法
if (!stream->is_network) {
/* ensure output path is writable to avoid generic error
* message.
*
* TODO: remove once ffmpeg-mux is refactored to pass
* errors back */
FILE *test_file = os_fopen(path, "wb");
if (!test_file) {
set_file_not_readable_error(stream, settings, path);
return false;
}
fclose(test_file);
os_unlink(path);
}
// 创建管道并启动 obs-ffmpeg-mux.exe进程
start_pipe(stream, path);
obs_data_release(settings);
// 管道创建失败则报错
if (!stream->pipe) {
obs_output_set_last_error(
stream->output, obs_module_text("HelperProcessFailed"));
warn("Failed to create process pipe");
return false;
}
/* write headers and start capture 设置开始录制标志为true*/
os_atomic_set_bool(&stream->active, true);
os_atomic_set_bool(&stream->capturing, true);
stream->total_bytes = 0; //统计录制的视频文件大小
// 开始视频的输出
obs_output_begin_data_capture(stream->output, 0);
info("Writing file '%s'...", stream->path.array);
return true;
}
ffmpeg_mux_data 音视频包输出到管道函数
obs的音频编码线程和视频编码线程,完成音视频帧的编码后最终都会调用到 output->info.encoded_packet 绑定的回调函数。
视频输出绑定的是ffmpeg_mux_data ,这里完成了视频的写入操作。如果有的话首先写入的视频编码器的头信息,音频编码器的头信息,接下来就是通过管道的写操作api,写入视频包和音频包。obs-ffmpeg-mux.exe进程收到音视频包的数据后写入磁盘完成视频的录制。
static void ffmpeg_mux_data(void *data, struct encoder_packet *packet)
{
struct ffmpeg_muxer *stream = data;
if (!active(stream))
return;
/* encoder failure */
if (!packet) {
deactivate(stream, OBS_OUTPUT_ENCODE_ERROR);
return;
}
// 如果没有发送音视频编码器的头信息,就发送音视频编码器的头信息
if (!stream->sent_headers) {
if (!send_headers(stream))
return;
// 头信息发送成功后 sent_headers置为true
stream->sent_headers = true;
}
// 检查是否停止视频录制或者推流
if (stopping(stream)) {
if (packet->sys_dts_usec >= stream->stop_ts) {
deactivate(stream, 0);
return;
}
}
// 写入音视频包到管道
write_packet(stream, packet);
}
write_packet 写管道操作
先写入packet的媒体信息pts dts type keyframe等,在写入媒体数据。
bool write_packet(struct ffmpeg_muxer *stream, struct encoder_packet *packet)
{
bool is_video = packet->type == OBS_ENCODER_VIDEO;
size_t ret;
struct ffm_packet_info info = {.pts = packet->pts,
.dts = packet->dts,
.size = (uint32_t)packet->size,
.index = (int)packet->track_idx,
.type = is_video ? FFM_PACKET_VIDEO
: FFM_PACKET_AUDIO,
.keyframe = packet->keyframe};
// 写入音视频包的媒体信息
ret = os_process_pipe_write(stream->pipe, (const uint8_t *)&info,
sizeof(info));
if (ret != sizeof(info)) {
warn("os_process_pipe_write for info structure failed");
signal_failure(stream);
return false;
}
// 写入媒体包的流媒体数据
ret = os_process_pipe_write(stream->pipe, packet->data, packet->size);
if (ret != packet->size) {
warn("os_process_pipe_write for packet data failed");
signal_failure(stream);
return false;
}
stream->total_bytes += packet->size;
return true;
}
obs-ffmpeg-mux.exe 进程写文件
obs-ffmpeg-mux这个进程在obs-studio工程里面是单独一个项目,一共只有两个文件【ffmpeg-mux.h ffmpeg-mux.c】。
该进程负责的内容也比较简单,作为管道接收端进程,负责接收音视频数据包写文件或者推流到网络的操作。
整个代码不复杂,可以说是教科书般的对ffmpeg api写文件流程的演示。下面贴上我绘制的函数调用逻辑图。配合阅读源码,不难理解里面的操作。
总结
了解obs的视频编码线程和音频编码线程后,再理解obs的视频录制、网络推流就非常容易。obs的视频录制模块也是一个学习ffmpeg api使用流程的非常好的例子。
以上都是个人工作当中对obs-studio开源项目的理解,难免有错误的地方,如果有欢迎指出。
若有帮助幸甚。