英语能力比较好的读者可以先看看官方文档:
obsproject.com/docs/backen…
OBS 的后端(即 libobs)使用 C 语言实现,提供了最核心的功能,包括:主流程、音视频子系统、通用的插件框架。
- core/libobs/libobs 定义了最核心的数据类型和初始化方法
- core/libobs/media-io 关于“音频编码和输出”、“视频编码和输出”最核心的流程都在这里
- core/libobs/graphics 关于“视频渲染”最核心的方法都在这里
OBS 的前端(即 obs)基于 Qt/C++ 实现,实现了 UI 层的逻辑,可以调用 libobs 的方法与后端交互。
- frontend/obs/obs-app.cpp 应用程序的入口
- frontend/obs/window-basic-main.cpp 主窗口
main 函数
f10 vs将跳转到mian函数第一行:
D:\opensrc\obs\obs_src_19041\obs_src_19041\obs-studio-19141\obs-studio\UI\obs-app.cpp
前期基本工作
设置程序crash 捕获dmp
obs_init_win32_crash_handler
设置日志
base_get_log_handler(&def_log_handler, nullptr);
更新系统设置
upgrade_settings();
路径:Administrator\AppData\Roaming\obs-studio/basic/profiles
初始化curl
curl_global_init(CURL_GLOBAL_ALL);
运行程序
int ret = run_program(logFile, argc, argv
qt高清dpi设置
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
QGuiApplication::setAttribute(opt_disable_high_dpi_scaling
? Qt::AA_DisableHighDpiScaling
: Qt::AA_EnableHighDpiScaling);
#endif
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) && defined(_WIN32)
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(
Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
#endif
QCoreApplication::addLibraryPath(".");
);
QApplication
OBSApp program(argc, argv, profilerNameStore.get());
多语言
OBSTranslator
程序单例运行
RunOnceMutex rom = GetRunOnceMutex(already_running);
启动主窗口
bool OBSApp::OBSInit()
obs初始化
if (!StartupOBS(locale.c_str(), GetProfilerNameStore()))
return false;
调用D:\opensrc\obs\obs_src_19041\obs_src_19041\obs-studio-19141\obs-studio\libobs\obs.c
bool obs_startup(const char *locale, const char *module_config_path,
profiler_name_store_t *store)
该功能调用了libobs 库的函数obs_startup
D:\opensrc\obs\obs_src_19041\obs_src_19041\obs-studio-19141\obs-studio\libobs\obs.h 定义了libobs的导出函数:
......
/**
* Initializes OBS
*
* @param locale The locale to use for modules
* @param module_config_path Path to module config storage directory
* (or NULL if none)
* @param store The profiler name store for OBS to use or NULL
*/
EXPORT bool obs_startup(const char *locale, const char *module_config_path,
profiler_name_store_t *store);
/** Releases all data associated with OBS and terminates the OBS context */
EXPORT void obs_shutdown(void);
/** @return true if the main OBS context has been initialized */
EXPORT bool obs_initialized(void);
......
主窗口
主界面:OBSBasic.ui
ResetAudio() 设置音频
ResetVideo() 设置视频
TimedCheckForUpdates 检查更新
路径:C:\Users\Administrator\AppData\Roaming\obs-studio\updates
系统托盘:SystemTray
消息循环
ret = program.exec();
退出程序
blog(LOG_INFO, "Number of memory leaks: %ld", bnum_allocs());
base_set_log_handler(nullptr, nullptr);
OBS初始化 OBSBasic::OBSInit
(1)InitBasicConfig
(2) 音频初始化:音频线程创建
WASAPI的全称是Windows Audio Session API(Windows音频会话API),是从Windows Vista之后引入的UAA(Universal Audio Architecture)音频架构所属的API。
About WASAPI - Win32 apps | Microsoft Learn
bool OBSBasic::ResetAudio()
{
ProfileScope("OBSBasic::ResetAudio");
struct obs_audio_info ai;
ai.samples_per_sec =
config_get_uint(basicConfig, "Audio", "SampleRate");
const char *channelSetupStr =
config_get_string(basicConfig, "Audio", "ChannelSetup");
if (strcmp(channelSetupStr, "Mono") == 0)
ai.speakers = SPEAKERS_MONO;
else if (strcmp(channelSetupStr, "2.1") == 0)
ai.speakers = SPEAKERS_2POINT1;
else if (strcmp(channelSetupStr, "4.0") == 0)
ai.speakers = SPEAKERS_4POINT0;
else if (strcmp(channelSetupStr, "4.1") == 0)
ai.speakers = SPEAKERS_4POINT1;
else if (strcmp(channelSetupStr, "5.1") == 0)
ai.speakers = SPEAKERS_5POINT1;
else if (strcmp(channelSetupStr, "7.1") == 0)
ai.speakers = SPEAKERS_7POINT1;
else
ai.speakers = SPEAKERS_STEREO;
return obs_reset_audio(&ai);
}
使用的是libobs 库的导出函数:d:\opensrc\obs\obs_src_19041\obs_src_19041\obs-studio-19141\obs-studio\libobs\obs.h
* 设置基本音频输出格式/通道/样本/等
*
* @note 如果输出当前处于活动状态,则无法重置基本音频。
/**
* Sets base audio output format/channels/samples/etc
*
* @note Cannot reset base audio if an output is currently active.
*/
EXPORT bool obs_reset_audio(const struct obs_audio_info *oai);
bool obs_reset_audio(const struct obs_audio_info *oai)
{
struct audio_output_info ai;
/* don't allow changing of audio settings if active. */
if (obs->audio.audio && audio_output_active(obs->audio.audio))
return false;
obs_free_audio();
if (!oai)
return true;
ai.name = "Audio";
ai.samples_per_sec = oai->samples_per_sec;
ai.format = AUDIO_FORMAT_FLOAT_PLANAR;
ai.speakers = oai->speakers;
ai.input_callback = audio_callback;
blog(LOG_INFO, "---------------------------------");
blog(LOG_INFO,
"audio settings reset:\n"
"\tsamples per sec: %d\n"
"\tspeakers: %d",
(int)ai.samples_per_sec, (int)ai.speakers);
return obs_init_audio(&ai);
}
static bool obs_init_audio(struct audio_output_info *ai)
{
struct obs_core_audio *audio = &obs->audio;
int errorcode;
pthread_mutex_init_value(&audio->monitoring_mutex);
if (pthread_mutex_init_recursive(&audio->monitoring_mutex) != 0)
return false;
audio->user_volume = 1.0f;
audio->monitoring_device_name = bstrdup("Default");
audio->monitoring_device_id = bstrdup("default");
errorcode = audio_output_open(&audio->audio, ai);
if (errorcode == AUDIO_OUTPUT_SUCCESS)
return true;
else if (errorcode == AUDIO_OUTPUT_INVALIDPARAM)
blog(LOG_ERROR, "Invalid audio parameters specified");
else
blog(LOG_ERROR, "Could not open audio output");
return false;
}
创建音频线程D:\opensrc\obs\obs_src_19041\obs_src_19041\obs-studio-19141\obs-studio\libobs\media-io\audio-io.c
audio_output_open :pthread_create(&out->thread, NULL, audio_thread, out
int audio_output_open(audio_t **audio, struct audio_output_info *info)
{
struct audio_output *out;
bool planar = is_audio_planar(info->format);
if (!valid_audio_params(info))
return AUDIO_OUTPUT_INVALIDPARAM;
out = bzalloc(sizeof(struct audio_output));
if (!out)
goto fail0;
memcpy(&out->info, info, sizeof(struct audio_output_info));
out->channels = get_audio_channels(info->speakers);
out->planes = planar ? out->channels : 1;
out->input_cb = info->input_callback;
out->input_param = info->input_param;
out->block_size = (planar ? 1 : out->channels) *
get_audio_bytes_per_channel(info->format);
if (pthread_mutex_init_recursive(&out->input_mutex) != 0)
goto fail0;
if (os_event_init(&out->stop_event, OS_EVENT_TYPE_MANUAL) != 0)
goto fail1;
if (pthread_create(&out->thread, NULL, audio_thread, out) != 0)
goto fail2;
out->initialized = true;
*audio = out;
return AUDIO_OUTPUT_SUCCESS;
fail2:
os_event_destroy(out->stop_event);
fail1:
pthread_mutex_destroy(&out->input_mutex);
fail0:
audio_output_close(out);
return AUDIO_OUTPUT_FAIL;
}
(3)视频初始化:视频线程创建
int OBSBasic::ResetVideo()
{
if (outputHandler && outputHandler->Active())
return OBS_VIDEO_CURRENTLY_ACTIVE;
ProfileScope("OBSBasic::ResetVideo");
struct obs_video_info ovi;
int ret;
//1 加载本地配置
GetConfigFPS(ovi.fps_num, ovi.fps_den);
const char *colorFormat =
config_get_string(basicConfig, "Video", "ColorFormat");
const char *colorSpace =
config_get_string(basicConfig, "Video", "ColorSpace");
const char *colorRange =
config_get_string(basicConfig, "Video", "ColorRange");
ovi.graphics_module = App()->GetRenderModule();
ovi.base_width =
(uint32_t)config_get_uint(basicConfig, "Video", "BaseCX");
ovi.base_height =
(uint32_t)config_get_uint(basicConfig, "Video", "BaseCY");
ovi.output_width =
(uint32_t)config_get_uint(basicConfig, "Video", "OutputCX");
ovi.output_height =
(uint32_t)config_get_uint(basicConfig, "Video", "OutputCY");
ovi.output_format = GetVideoFormatFromName(colorFormat);
ovi.colorspace = astrcmpi(colorSpace, "601") == 0
? VIDEO_CS_601
: (astrcmpi(colorSpace, "709") == 0
? VIDEO_CS_709
: VIDEO_CS_SRGB);
ovi.range = astrcmpi(colorRange, "Full") == 0 ? VIDEO_RANGE_FULL
: VIDEO_RANGE_PARTIAL;
ovi.adapter =
config_get_uint(App()->GlobalConfig(), "Video", "AdapterIdx");
ovi.gpu_conversion = true;
ovi.scale_type = GetScaleType(basicConfig);
if (ovi.base_width < 8 || ovi.base_height < 8) {
ovi.base_width = 1920;
ovi.base_height = 1080;
config_set_uint(basicConfig, "Video", "BaseCX", 1920);
config_set_uint(basicConfig, "Video", "BaseCY", 1080);
}
if (ovi.output_width < 8 || ovi.output_height < 8) {
ovi.output_width = ovi.base_width;
ovi.output_height = ovi.base_height;
config_set_uint(basicConfig, "Video", "OutputCX",
ovi.base_width);
config_set_uint(basicConfig, "Video", "OutputCY",
ovi.base_height);
}
//2 调用int obs_reset_video(struct obs_video_info *ovi)
ret = AttemptToResetVideo(&ovi);
if (IS_WIN32 && ret != OBS_VIDEO_SUCCESS) {
if (ret == OBS_VIDEO_CURRENTLY_ACTIVE) {
blog(LOG_WARNING, "Tried to reset when "
"already active");
return ret;
}
/* Try OpenGL if DirectX fails on windows */
if (astrcmpi(ovi.graphics_module, DL_OPENGL) != 0) {
blog(LOG_WARNING,
"Failed to initialize obs video (%d) "
"with graphics_module='%s', retrying "
"with graphics_module='%s'",
ret, ovi.graphics_module, DL_OPENGL);
ovi.graphics_module = DL_OPENGL;
ret = AttemptToResetVideo(&ovi);
}
} else if (ret == OBS_VIDEO_SUCCESS) {
ResizePreview(ovi.base_width, ovi.base_height);
if (program)
ResizeProgram(ovi.base_width, ovi.base_height);
}
if (ret == OBS_VIDEO_SUCCESS) {
OBSBasicStats::InitializeValues();
OBSProjector::UpdateMultiviewProjectors();
}
return ret;
}
int obs_reset_video(struct obs_video_info *ovi)
{
if (!obs)
return OBS_VIDEO_FAIL;
/* don't allow changing of video settings if active. */
if (obs->video.video && obs_video_active())
return OBS_VIDEO_CURRENTLY_ACTIVE;
if (!size_valid(ovi->output_width, ovi->output_height) ||
!size_valid(ovi->base_width, ovi->base_height))
return OBS_VIDEO_INVALID_PARAM;
struct obs_core_video *video = &obs->video;
stop_video();
obs_free_video();
/* align to multiple-of-two and SSE alignment sizes */
ovi->output_width &= 0xFFFFFFFC;
ovi->output_height &= 0xFFFFFFFE;
if (!video->graphics) {
int errorcode = obs_init_graphics(ovi);
if (errorcode != OBS_VIDEO_SUCCESS) {
obs_free_graphics();
return errorcode;
}
}
const char *scale_type_name = "";
switch (ovi->scale_type) {
case OBS_SCALE_DISABLE:
scale_type_name = "Disabled";
break;
case OBS_SCALE_POINT:
scale_type_name = "Point";
break;
case OBS_SCALE_BICUBIC:
scale_type_name = "Bicubic";
break;
case OBS_SCALE_BILINEAR:
scale_type_name = "Bilinear";
break;
case OBS_SCALE_LANCZOS:
scale_type_name = "Lanczos";
break;
case OBS_SCALE_AREA:
scale_type_name = "Area";
break;
}
bool yuv = format_is_yuv(ovi->output_format);
const char *yuv_format = get_video_colorspace_name(ovi->colorspace);
const char *yuv_range =
get_video_range_name(ovi->output_format, ovi->range);
blog(LOG_INFO, "---------------------------------");
blog(LOG_INFO,
"video settings reset:\n"
"\tbase resolution: %dx%d\n"
"\toutput resolution: %dx%d\n"
"\tdownscale filter: %s\n"
"\tfps: %d/%d\n"
"\tformat: %s\n"
"\tYUV mode: %s%s%s",
ovi->base_width, ovi->base_height, ovi->output_width,
ovi->output_height, scale_type_name, ovi->fps_num, ovi->fps_den,
get_video_format_name(ovi->output_format),
yuv ? yuv_format : "None", yuv ? "/" : "", yuv ? yuv_range : "");
return obs_init_video(ovi);
}
obs_init_video
video_output_open 线程 :编解码相关
obs_graphics_thread :图形
static int obs_init_video(struct obs_video_info *ovi)
{
struct obs_core_video *video = &obs->video;
struct video_output_info vi;
int errorcode;
make_video_info(&vi, ovi);
video->base_width = ovi->base_width;
video->base_height = ovi->base_height;
video->output_width = ovi->output_width;
video->output_height = ovi->output_height;
video->gpu_conversion = ovi->gpu_conversion;
video->scale_type = ovi->scale_type;
set_video_matrix(video, ovi);
errorcode = video_output_open(&video->video, &vi);
if (errorcode != VIDEO_OUTPUT_SUCCESS) {
if (errorcode == VIDEO_OUTPUT_INVALIDPARAM) {
blog(LOG_ERROR, "Invalid video parameters specified");
return OBS_VIDEO_INVALID_PARAM;
} else {
blog(LOG_ERROR, "Could not open video output");
}
return OBS_VIDEO_FAIL;
}
gs_enter_context(video->graphics);
if (ovi->gpu_conversion && !obs_init_gpu_conversion(ovi))
return OBS_VIDEO_FAIL;
if (!obs_init_textures(ovi))
return OBS_VIDEO_FAIL;
gs_leave_context();
if (pthread_mutex_init(&video->gpu_encoder_mutex, NULL) < 0)
return OBS_VIDEO_FAIL;
if (pthread_mutex_init(&video->task_mutex, NULL) < 0)
return OBS_VIDEO_FAIL;
#ifdef __APPLE__
errorcode = pthread_create(&video->video_thread, NULL,
obs_graphics_thread_autorelease, obs);
#else
errorcode = pthread_create(&video->video_thread, NULL,
obs_graphics_thread, obs);
#endif
if (errorcode != 0)
return OBS_VIDEO_FAIL;
video->thread_initialized = true;
video->ovi = *ovi;
return OBS_VIDEO_SUCCESS;
}
(4) dll 加载 obs_load_all_modules();
obs.c中定义了obs根对象
struct obs_core *obs = NULL;
(5) ResetOutputs 输出模式
void OBSBasic::ResetOutputs()
{
ProfileScope("OBSBasic::ResetOutputs");
const char *mode = config_get_string(basicConfig, "Output", "Mode");
bool advOut = astrcmpi(mode, "Advanced") == 0;
if (!outputHandler || !outputHandler->Active()) {
outputHandler.reset();
outputHandler.reset(advOut ? CreateAdvancedOutputHandler(this)
: CreateSimpleOutputHandler(this));
delete replayBufferButton;
delete replayLayout;
if (outputHandler->replayBuffer) {
replayBufferButton = new ReplayBufferButton(
QTStr("Basic.Main.StartReplayBuffer"), this);
replayBufferButton->setCheckable(true);
connect(replayBufferButton.data(),
&QPushButton::clicked, this,
&OBSBasic::ReplayBufferClicked);
replayBufferButton->setSizePolicy(QSizePolicy::Ignored,
QSizePolicy::Fixed);
replayLayout = new QHBoxLayout(this);
replayLayout->addWidget(replayBufferButton);
replayBufferButton->setProperty("themeID",
"replayBufferButton");
ui->buttonsVLayout->insertLayout(2, replayLayout);
setTabOrder(ui->recordButton, replayBufferButton);
setTabOrder(replayBufferButton,
ui->buttonsVLayout->itemAt(3)->widget());
}
if (sysTrayReplayBuffer)
sysTrayReplayBuffer->setEnabled(
!!outputHandler->replayBuffer);
} else {
outputHandler->Update();
}
}
输出模式分简单模式与复杂模式:
BasicOutputHandler *CreateSimpleOutputHandler(OBSBasic *main)
{
return new SimpleOutput(main);
}
简单输出的构造:
SimpleOutput::SimpleOutput(OBSBasic *main_) : BasicOutputHandler(main_)
{
const char *encoder = config_get_string(main->Config(), "SimpleOutput",
"StreamEncoder");
if (strcmp(encoder, SIMPLE_ENCODER_QSV) == 0) {
LoadStreamingPreset_h264("obs_qsv11");
} else if (strcmp(encoder, SIMPLE_ENCODER_AMD) == 0) {
LoadStreamingPreset_h264("amd_amf_h264");
} else if (strcmp(encoder, SIMPLE_ENCODER_NVENC) == 0) {
const char *id = EncoderAvailable("jim_nvenc") ? "jim_nvenc"
: "ffmpeg_nvenc";
LoadStreamingPreset_h264(id);
} else {
LoadStreamingPreset_h264("obs_x264");
}
if (!CreateAACEncoder(aacStreaming, aacStreamEncID, GetAudioBitrate(),
"simple_aac", 0))
throw "Failed to create aac streaming encoder (simple output)";
if (!CreateAACEncoder(aacArchive, aacArchiveEncID, GetAudioBitrate(),
SIMPLE_ARCHIVE_NAME, 1))
throw "Failed to create aac arhive encoder (simple output)";
LoadRecordingPreset();
if (!ffmpegOutput) {
bool useReplayBuffer = config_get_bool(main->Config(),
"SimpleOutput", "RecRB");
if (useReplayBuffer) {
obs_data_t *hotkey;
const char *str = config_get_string(
main->Config(), "Hotkeys", "ReplayBuffer");
if (str)
hotkey = obs_data_create_from_json(str);
else
hotkey = nullptr;
replayBuffer = obs_output_create("replay_buffer",
Str("ReplayBuffer"),
nullptr, hotkey);
obs_data_release(hotkey);
if (!replayBuffer)
throw "Failed to create replay buffer output "
"(simple output)";
obs_output_release(replayBuffer);
signal_handler_t *signal =
obs_output_get_signal_handler(replayBuffer);
startReplayBuffer.Connect(signal, "start",
OBSStartReplayBuffer, this);
stopReplayBuffer.Connect(signal, "stop",
OBSStopReplayBuffer, this);
replayBufferStopping.Connect(signal, "stopping",
OBSReplayBufferStopping,
this);
replayBufferSaved.Connect(signal, "saved",
OBSReplayBufferSaved, this);
}
fileOutput = obs_output_create(
"ffmpeg_muxer", "simple_file_output", nullptr, nullptr);
if (!fileOutput)
throw "Failed to create recording output "
"(simple output)";
obs_output_release(fileOutput);
}
startRecording.Connect(obs_output_get_signal_handler(fileOutput),
"start", OBSStartRecording, this);
stopRecording.Connect(obs_output_get_signal_handler(fileOutput), "stop",
OBSStopRecording, this);
recordStopping.Connect(obs_output_get_signal_handler(fileOutput),
"stopping", OBSRecordStopping, this);
}
使用ffmpeg_muxer :
视音频复用器(Muxer)即是将视频压缩数据(例如H.264)和音频压缩数据(例如AAC)合并到一个封装格式数据(例如MKV)中去。如图所示。在这个过程中并不涉及到编码和解码。
obs_output_t *obs_output_create(const char *id, const char *name,
obs_data_t *settings, obs_data_t *hotkey_data)
{
const struct obs_output_info *info = find_output(id);
struct obs_output *output;
int ret;
output = bzalloc(sizeof(struct obs_output));
pthread_mutex_init_value(&output->interleaved_mutex);
pthread_mutex_init_value(&output->delay_mutex);
pthread_mutex_init_value(&output->caption_mutex);
pthread_mutex_init_value(&output->pause.mutex);
if (pthread_mutex_init(&output->interleaved_mutex, NULL) != 0)
goto fail;
if (pthread_mutex_init(&output->delay_mutex, NULL) != 0)
goto fail;
if (pthread_mutex_init(&output->caption_mutex, NULL) != 0)
goto fail;
if (pthread_mutex_init(&output->pause.mutex, NULL) != 0)
goto fail;
if (os_event_init(&output->stopping_event, OS_EVENT_TYPE_MANUAL) != 0)
goto fail;
if (!init_output_handlers(output, name, settings, hotkey_data))
goto fail;
os_event_signal(output->stopping_event);
if (!info) {
blog(LOG_ERROR, "Output ID '%s' not found", id);
output->info.id = bstrdup(id);
output->owns_info_id = true;
} else {
output->info = *info;
}
output->video = obs_get_video();
output->audio = obs_get_audio();
if (output->info.get_defaults)
output->info.get_defaults(output->context.settings);
ret = os_event_init(&output->reconnect_stop_event,
OS_EVENT_TYPE_MANUAL);
if (ret < 0)
goto fail;
output->reconnect_retry_sec = 2;
output->reconnect_retry_max = 20;
output->valid = true;
output->control = bzalloc(sizeof(obs_weak_output_t));
output->control->output = output;
obs_context_data_insert(&output->context, &obs->data.outputs_mutex,
&obs->data.first_output);
if (info)
output->context.data =
info->create(output->context.settings, output);
if (!output->context.data)
blog(LOG_ERROR, "Failed to create output '%s'!", name);
blog(LOG_DEBUG, "output '%s' (%s) created", name, id);
return output;
fail:
obs_output_destroy(output);
return NULL;
}
点击Ui上开始录制,调用堆栈如下;D:\opensrc\obs\obs_src_19041\obs_src_19041\obs-studio-19141\obs-studio\plugins\obs-ffmpeg\obs-ffmpeg-mux.c
obs.dll中使用了obs-ffmpeg.dll
obs-ffmpeg库的加载:D:\opensrc\obs\obs_src_19041\obs_src_19041\obs-studio-19141\obs-studio\plugins\obs-ffmpeg\obs-ffmpeg.c