英语能力比较好的读者可以先看看官方文档:
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);
#endifQCoreApplication::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;elseai.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");elseblog(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);
#elseerrorcode = pthread_create(&video->video_thread, NULL,obs_graphics_thread, obs);
#endifif (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);elsehotkey = 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