obs源码分析与调试:obs初始化过程

文章详细介绍了开源软件OBSStudio的后端libobs库,它使用C语言实现,提供音视频处理的核心功能。前端基于Qt/C++,实现用户界面逻辑。OBS启动过程涉及设置崩溃处理、日志记录、系统设置升级、Curl初始化等。在音频和视频初始化中,分别调用libobs库的函数进行音频和视频输出格式的设置,创建音频和视频线程。此外,还提到了输出模式的设定,如简单模式与复杂模式的区别。
摘要由CSDN通过智能技术生成

英语能力比较好的读者可以先看看官方文档:
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

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值