实现一个简单的录制软件:支持录制桌面与窗口

实现一个简单的录制软件:支持录制桌面与窗口_第1张图片

 

环境搭建

CSDN

实现一个简单的录制软件:支持录制桌面与窗口_第2张图片

将data文件与obs-plugins文件夹复制到bin/win32文件下

 VS2019安装Qt插件(附安装失败解决方案)_振华OPPO的博客-CSDN博客

插件;

链接:https://pan.baidu.com/s/1fdNDJwrwrJ1SA0Q9AiM7qA?pwd=iz4f 
提取码:iz4f

vs 2019创建一个qt工程

实现一个简单的录制软件:支持录制桌面与窗口_第3张图片

debug win32 

拷贝 install生成的include文件夹当前工程源文件目录

bin下的32bit 目录到lib文件夹

实现一个简单的录制软件:支持录制桌面与窗口_第4张图片

工程  ->属性  添加 头文件目录   ,附加库目录 附加依赖项

拷貝实现一个简单的录制软件:支持录制桌面与窗口_第5张图片

到工程目录下

UI层 

实现一个简单的录制软件:支持录制桌面与窗口_第6张图片实现一个简单的录制软件:支持录制桌面与窗口_第7张图片

obs初始化

bool ObsWrapper::init_obs()
{
	string cfg_path = "D:/desktop_rec_cfg";

	if (!obs_initialized())
	{
		//1 初始化obs
		if (!obs_startup("zh-CN", cfg_path.c_str(), NULL))
		{
			return false;
		}

		//2 加载插件 位置 需要加载 data目录与 obs-plugins
		QString path = qApp->applicationDirPath();
		string path_str = path.toStdString();

		string plugin_path = path_str + "/obs-plugins/32bit";
		string data_path = path_str + "/data/obs-plugins/%module%";

		obs_add_module_path(plugin_path.c_str(), data_path.c_str());

		obs_load_all_modules();
	}

	//3 音频设置
	if (!ResetAudio())
		return false;

	//4 视频设置
	if (ResetVideo() != OBS_VIDEO_SUCCESS)
		return false;

    //5 设置输出模式
	if (!create_output_mode())
		return false;

	return true;
}

1 obs_startup

参照:E:\opensrc\obs_src_19041\obs-studio-19141\obs-studio\UI\window-basic-main.cpp

首先调用  obs_startup()

obs中的调用

实现一个简单的录制软件:支持录制桌面与窗口_第8张图片

e:\opensrc\obs_src_19041\obs-studio-19141\obs-studio\libobs\obs.c

参数1:locale :当前所用的语言:zh-CN

参数2: module_config_path:设置配置文件缓存路径:如C:\Users\Administrator\AppData\Roaming\obs-studio/plugin_config

参数3:可以为null

/**
 * 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);

2 obs_add_module_path

/**
  * 添加与 obs_find_modules 一起使用的模块搜索路径。 如果搜索
  * 路径字符串包含%module%,该文本将被替换为模块
  * 使用时的名称。
  *
  * @param bin 指定模块的二进制目录搜索路径。
  * @param data 指定模块的数据目录搜索路径。
  */
/**
 * Adds a module search path to be used with obs_find_modules.  If the search
 * path strings contain %module%, that text will be replaced with the module
 * name when used.
 *
 * @param  bin   Specifies the module's binary directory search path.
 * @param  data  Specifies the module's data directory search path.
 */
EXPORT void obs_add_module_path(const char *bin, const char *data);


/** 自动从模块路径加载所有模块(方便功能) */
/** Automatically loads all modules from module paths (convenience function) */
EXPORT void obs_load_all_modules(void);

3 ResetAudio

过程参照obs中的:

实现一个简单的录制软件:支持录制桌面与窗口_第9张图片

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);
}

obs中的ResetAudio 主要读取配置文件basicConfig中的数据,然后调用obs_set_audio

/**
  * 设置基本音频输出格式/通道/样本/等
  *
  * @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 ObsWrapper::ResetAudio()
{
	struct obs_audio_info ai;
	ai.samples_per_sec = 48000;
	ai.speakers = SPEAKERS_STEREO;

	return obs_reset_audio(&ai);
}

4 ResetVideo

OBS中的操作

首先读取basicConfig 设计基本参数,然后调用AttemptToResetVideo,

static inline int AttemptToResetVideo(struct obs_video_info *ovi)
{
    return obs_reset_video(ovi);
}

仅仅调用了 obs_reset_video ,然后判断返回值:

如果DirectX失败,则使用OpenGL


int OBSBasic::ResetVideo()
{
	if (outputHandler && outputHandler->Active())
		return OBS_VIDEO_CURRENTLY_ACTIVE;

	ProfileScope("OBSBasic::ResetVideo");

	struct obs_video_info ovi;
	int ret;

	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);
	}

	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;
}

struct obs_video_info {
#ifndef SWIG
	/**
	 * Graphics module to use (usually "libobs-opengl" or "libobs-d3d11")
	 */
	const char *graphics_module;
#endif

	uint32_t fps_num; /**< Output FPS numerator */
	uint32_t fps_den; /**< Output FPS denominator */

	uint32_t base_width;  /**< Base compositing width */
	uint32_t base_height; /**< Base compositing height */

	uint32_t output_width;           /**< Output width */
	uint32_t output_height;          /**< Output height */
	enum video_format output_format; /**< Output format */

	/** Video adapter index to use (NOTE: avoid for optimus laptops) */
	uint32_t adapter;

	/** Use shaders to convert to different color formats */
	bool gpu_conversion;

	enum video_colorspace colorspace; /**< YUV type (if YUV) */
	enum video_range_type range;      /**< YUV range (if YUV) */

	enum obs_scale_type scale_type; /**< How to scale if scaling */
};
/**
  * 设置基本视频输出基本分辨率/fps/格式。
  *
  * @note 如果输出当前处于活动状态,则无法更改此数据。
  * @note 在不完全破坏图形模块的情况下无法更改图形模块
  * OBS 上下文。
  *
  * @param ovi 指向包含以下内容的 obs_video_info 结构的指针
  * 图形子系统的规范,
  * 如果成功则返回OBS_VIDEO_SUCCESS
  * OBS_VIDEO_NOT_SUPPORTED 如果适配器缺乏功能
  * OBS_VIDEO_INVALID_PARAM 如果参数无效
  * OBS_VIDEO_CURRENTLY_ACTIVE 如果视频当前处于活动状态
  * OBS_VIDEO_MODULE_NOT_FOUND 如果未找到图形模块
  * OBS_VIDEO_FAIL 表示一般失败
  */

/**
 * Sets base video output base resolution/fps/format.
 *
 * @note This data cannot be changed if an output is currently active.
 * @note The graphics module cannot be changed without fully destroying the
 *       OBS context.
 *
 * @param   ovi  Pointer to an obs_video_info structure containing the
 *               specification of the graphics subsystem,
 * @return       OBS_VIDEO_SUCCESS if successful
 *               OBS_VIDEO_NOT_SUPPORTED if the adapter lacks capabilities
 *               OBS_VIDEO_INVALID_PARAM if a parameter is invalid
 *               OBS_VIDEO_CURRENTLY_ACTIVE if video is currently active
 *               OBS_VIDEO_MODULE_NOT_FOUND if the graphics module is not found
 *               OBS_VIDEO_FAIL for generic failure
 */
EXPORT int obs_reset_video(struct obs_video_info *ovi);

简单配置:

int ObsWrapper::ResetVideo()
{
	struct obs_video_info ovi;
	ovi.fps_num = VIDEO_FPS;
	ovi.fps_den = 1;

	ovi.graphics_module = "libobs-d3d11.dll";
	ovi.base_width = 1920;
	ovi.base_height = 1080;
	ovi.output_width = 1920;
	ovi.output_height = 1080;
	ovi.output_format = VIDEO_FORMAT_I420;
	ovi.colorspace = VIDEO_CS_709;
	ovi.range = VIDEO_RANGE_FULL;
	ovi.adapter = 0;
	ovi.gpu_conversion = true;
	ovi.scale_type = OBS_SCALE_BICUBIC;

	return obs_reset_video(&ovi);
}

5 创建输出模式

 参考 ResetOutputs 此操作在obs resetaudio resetvideo后

实现一个简单的录制软件:支持录制桌面与窗口_第10张图片

 可以创建简单模式或高级模式

AdvancedOutput::AdvancedOutput(OBSBasic *main_)  高级模式的构造中

创建ffmpeg_output

实现一个简单的录制软件:支持录制桌面与窗口_第11张图片

 音频编码器的创建

实现一个简单的录制软件:支持录制桌面与窗口_第12张图片

obs中分了简单输出与高级输出,这里设置高级输出

bool ObsWrapper::create_output_mode()
{
	if (!fileOutput)
	{
		//高级输出 ffmpeg
		fileOutput = obs_output_create("ffmpeg_output", "adv_ffmpeg_output", nullptr, nullptr);

		if (!fileOutput)
			return false;
	}
	
	for (int i = 0; i < MAX_AUDIO_MIXES; i++) {
		char name[9];
		sprintf(name, "adv_aac%d", i);

		if (!CreateAACEncoder(aacTrack[i], aacEncoderID[i], name, i))
		{
			return false;
		}

		obs_encoder_set_audio(aacTrack[i], obs_get_audio());
	}

	return true;
}

开始录制

直接调用这两个接口完成开始录制 结束录制

/** Starts the output. */
EXPORT bool obs_output_start(obs_output_t *output);

/** Stops the output. */
EXPORT void obs_output_stop(obs_output_t *output);

实现一个简单的录制软件:支持录制桌面与窗口_第13张图片

高级输出,需要设置ffmpeg参数

bool AdvancedOutput::StartRecording() 中的设置:

首先调用  AdvancedOutput::SetupFFmpeg()


inline void AdvancedOutput::SetupFFmpeg()
{
	const char *url = config_get_string(main->Config(), "AdvOut", "FFURL");
	int vBitrate = config_get_int(main->Config(), "AdvOut", "FFVBitrate");
	int gopSize = config_get_int(main->Config(), "AdvOut", "FFVGOPSize");
	bool rescale = config_get_bool(main->Config(), "AdvOut", "FFRescale");
	const char *rescaleRes =
		config_get_string(main->Config(), "AdvOut", "FFRescaleRes");
	const char *formatName =
		config_get_string(main->Config(), "AdvOut", "FFFormat");
	const char *mimeType =
		config_get_string(main->Config(), "AdvOut", "FFFormatMimeType");
	const char *muxCustom =
		config_get_string(main->Config(), "AdvOut", "FFMCustom");
	const char *vEncoder =
		config_get_string(main->Config(), "AdvOut", "FFVEncoder");
	int vEncoderId =
		config_get_int(main->Config(), "AdvOut", "FFVEncoderId");
	const char *vEncCustom =
		config_get_string(main->Config(), "AdvOut", "FFVCustom");
	int aBitrate = config_get_int(main->Config(), "AdvOut", "FFABitrate");
	int aMixes = config_get_int(main->Config(), "AdvOut", "FFAudioMixes");
	const char *aEncoder =
		config_get_string(main->Config(), "AdvOut", "FFAEncoder");
	int aEncoderId =
		config_get_int(main->Config(), "AdvOut", "FFAEncoderId");
	const char *aEncCustom =
		config_get_string(main->Config(), "AdvOut", "FFACustom");
	obs_data_t *settings = obs_data_create();

	obs_data_set_string(settings, "url", url);
	obs_data_set_string(settings, "format_name", formatName);
	obs_data_set_string(settings, "format_mime_type", mimeType);
	obs_data_set_string(settings, "muxer_settings", muxCustom);
	obs_data_set_int(settings, "gop_size", gopSize);
	obs_data_set_int(settings, "video_bitrate", vBitrate);
	obs_data_set_string(settings, "video_encoder", vEncoder);
	obs_data_set_int(settings, "video_encoder_id", vEncoderId);
	obs_data_set_string(settings, "video_settings", vEncCustom);
	obs_data_set_int(settings, "audio_bitrate", aBitrate);
	obs_data_set_string(settings, "audio_encoder", aEncoder);
	obs_data_set_int(settings, "audio_encoder_id", aEncoderId);
	obs_data_set_string(settings, "audio_settings", aEncCustom);

	if (rescale && rescaleRes && *rescaleRes) {
		int width;
		int height;
		int val = sscanf(rescaleRes, "%dx%d", &width, &height);

		if (val == 2 && width && height) {
			obs_data_set_int(settings, "scale_width", width);
			obs_data_set_int(settings, "scale_height", height);
		}
	}

	obs_output_set_mixers(fileOutput, aMixes);
	obs_output_set_media(fileOutput, obs_get_video(), obs_get_audio());
	obs_output_update(fileOutput, settings);

	obs_data_release(settings);
}

然后:实现一个简单的录制软件:支持录制桌面与窗口_第14张图片

因此可以:

int ObsWrapper::start_rec()
{
	SetupFFmpeg();

	if(!obs_output_start(fileOutput))
		return -1;

	return 0;
}


void ObsWrapper::SetupFFmpeg()
{
	obs_data_t* settings = obs_data_create();

	QDateTime dt = QDateTime::currentDateTime();
	QString time_str = dt.toString("yyyy_MM_dd_hh_mm_ss");

	string timestr = time_str.toStdString();

	string path = CConfigParse::instance().getOutputPath();
	string out_file_name = path + timestr + ".mp4";

	obs_data_set_string(settings, "url", out_file_name.c_str());
	obs_data_set_string(settings, "format_name", RECORD_OUTPUT_FORMAT);
	obs_data_set_string(settings, "format_mime_type", RECORD_OUTPUT_FORMAT_MIME);
	obs_data_set_string(settings, "muxer_settings", "movflags=faststart"); 
	obs_data_set_int(settings, "gop_size", VIDEO_FPS * 10);
	obs_data_set_string(settings, "video_encoder", VIDEO_ENCODER_NAME);
	obs_data_set_int(settings, "video_encoder_id", VIDEO_ENCODER_ID);

	if (VIDEO_ENCODER_ID == AV_CODEC_ID_H264)
		obs_data_set_string(settings, "video_settings", "profile=main x264-params=crf=22");
	else if (VIDEO_ENCODER_ID == AV_CODEC_ID_FLV1)
		obs_data_set_int(settings, "video_bitrate", VIDEO_BITRATE);

	obs_data_set_int(settings, "audio_bitrate", AUDIO_BITRATE);
	obs_data_set_string(settings, "audio_encoder", "aac");
	obs_data_set_int(settings, "audio_encoder_id", AV_CODEC_ID_AAC);
	obs_data_set_string(settings, "audio_settings", NULL);

	obs_data_set_int(settings, "scale_width", OUT_WIDTH);
	obs_data_set_int(settings, "scale_height", OUT_HEIGHT);

	obs_output_set_mixer(fileOutput, 1);  //混流器,如果不设置,可能只有视频没有音频
	obs_output_set_media(fileOutput, obs_get_video(), obs_get_audio());
	obs_output_update(fileOutput, settings);

	obs_data_release(settings);
}

结束录制

实现一个简单的录制软件:支持录制桌面与窗口_第15张图片

设置场景与源

在开始录制之前,需要先设置 场景

在obs中调用的是:

CreateDefaultScene(true); 

void OBSBasic::CreateDefaultScene(bool firstStart)
{
	disableSaving++;
   
    //1 清理
	ClearSceneData();

    // 2 初始化默认转场
	InitDefaultTransitions();

    
	CreateDefaultQuickTransitions();
	ui->transitionDuration->setValue(300);
	SetTransition(fadeTransition);
   
    //3 创建场景
	obs_scene_t *scene = obs_scene_create(Str("Basic.Scene"));

    //4 创建第一个运行的源:重设音频设备
	if (firstStart)
		CreateFirstRunSources();

    //5 设置当前场景
	SetCurrentScene(scene, true);
	
    //6 释放scene 与create 对应
    obs_scene_release(scene);

	disableSaving--;
}
1 ClearSceneData中将源设置为空

实现一个简单的录制软件:支持录制桌面与窗口_第16张图片

/** Maximum number of source channels for output and per display */
#define MAX_CHANNELS 64

obs最多支持64个源 

2 InitDefaultTransitions

自动添加没有配置的转场,红框中的需要设置

实现一个简单的录制软件:支持录制桌面与窗口_第17张图片

3 创建场景 

实现一个简单的录制软件:支持录制桌面与窗口_第18张图片

4 CreateFirstRunSources 

重设音频设备

wasapi_input_capture    音频输入设备

wasapi_output_capture  音频输出设备

实现一个简单的录制软件:支持录制桌面与窗口_第19张图片

#elif _WIN32
#define INPUT_AUDIO_SOURCE "wasapi_input_capture"
#define OUTPUT_AUDIO_SOURCE "wasapi_output_capture"
#else

const char *OBSApp::InputAudioSource() const
{
	return INPUT_AUDIO_SOURCE;
}

const char *OBSApp::OutputAudioSource() const
{
	return OUTPUT_AUDIO_SOURCE;
}
static inline bool HasAudioDevices(const char *source_id)
{
	const char *output_id = source_id;
	obs_properties_t *props = obs_get_source_properties(output_id);
	size_t count = 0;

	if (!props)
		return false;

	obs_property_t *devices = obs_properties_get(props, "device_id");
	if (devices)
		count = obs_property_list_item_count(devices);

	obs_properties_destroy(props);

	return count != 0;
}

void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId,
				const char *deviceDesc, int channel)
{
	bool disable = deviceId && strcmp(deviceId, "disabled") == 0;
	obs_source_t *source;
	obs_data_t *settings;

	source = obs_get_output_source(channel);
	if (source) {
		if (disable) {
			obs_set_output_source(channel, nullptr);
		} else {
			settings = obs_source_get_settings(source);
			const char *oldId =
				obs_data_get_string(settings, "device_id");
			if (strcmp(oldId, deviceId) != 0) {
				obs_data_set_string(settings, "device_id",
						    deviceId);
				obs_source_update(source, settings);
			}
			obs_data_release(settings);
		}

		obs_source_release(source);

	} else if (!disable) {
		settings = obs_data_create();
		obs_data_set_string(settings, "device_id", deviceId);
		source = obs_source_create(sourceId, deviceDesc, settings,
					   nullptr);
		obs_data_release(settings);

		obs_set_output_source(channel, source);
		obs_source_release(source);
	}
}

ObsWrapper 添加源过程

int ObsWrapper::add_scene_source(REC_TYPE type)
{
	//1  参考 ClearSceneData 清空所有源

	/*
	for(int i=0;i<6;++i){
		obs_set_output_source(i, nullptr);
	}
	
	*/
	obs_set_output_source(SOURCE_CHANNEL_TRANSITION, nullptr);
	obs_set_output_source(SOURCE_CHANNEL_AUDIO_OUTPUT, nullptr);
	obs_set_output_source(SOURCE_CHANNEL_AUDIO_INPUT, nullptr);


	//2 参考 InitDefaultTransitions 设置默认转场
	size_t idx = 0;
	const char* id;

	/* automatically add transitions that have no configuration (things
	 * such as cut/fade/etc) */
	while (obs_enum_transition_types(idx++, &id)) {
		const char* name = obs_source_get_display_name(id);

		if (!obs_is_source_configurable(id)) {
			obs_source_t* tr = obs_source_create_private(id, name, NULL);

			if (strcmp(id, "fade_transition") == 0)
				fadeTransition = tr;
		}
	}

	if (!fadeTransition)
	{
		return -1;
	}

	obs_set_output_source(SOURCE_CHANNEL_TRANSITION, fadeTransition);
	obs_source_release(fadeTransition);

	//3 创建场景
	scene = obs_scene_create("MyScene");
	if (!scene)
	{
		return -2;
	}

    //4 设置场景 参考SetCurrentScene
	obs_source_t* s = obs_get_output_source(SOURCE_CHANNEL_TRANSITION);
	obs_transition_set(s, obs_scene_get_source(scene));
	obs_source_release(s);

	//创建源:显示器采集
	if(type == REC_DESKTOP)
		captureSource = obs_source_create("monitor_capture", "Computer_Monitor_Capture", NULL, nullptr);
	else
	    captureSource = obs_source_create("window_capture", "MyWindow_Capture", NULL, nullptr);

	if (captureSource)
	{
		obs_scene_atomic_update(scene, AddSource, captureSource);
	}
	else
	{
		return -3;
	}

	// 设置窗口捕获原的窗口或显示器
	setting_source = obs_data_create();
	obs_data_t* curSetting = obs_source_get_settings(captureSource);
	obs_data_apply(setting_source, curSetting);
	obs_data_release(curSetting);

	properties = obs_source_properties(captureSource);
	property = obs_properties_first(properties);
}

采集系统音频麦克风音频

参考

实现一个简单的录制软件:支持录制桌面与窗口_第20张图片

系统音频即输出   ,麦克风音频即输入音频 

void ObsWrapper::rec_system_audio()
{
	bool hasDesktopAudio = HasAudioDevices(OUTPUT_AUDIO_SOURCE);

	if (hasDesktopAudio)
		ResetAudioDevice(OUTPUT_AUDIO_SOURCE, "default",
			"Default Desktop Audio", SOURCE_CHANNEL_AUDIO_OUTPUT);
}

void ObsWrapper::rec_out_audio()
{
	bool hasInputAudio = HasAudioDevices(INPUT_AUDIO_SOURCE);
	if (hasInputAudio)
		ResetAudioDevice(INPUT_AUDIO_SOURCE, "default",
			"Default Mic/Aux", SOURCE_CHANNEL_AUDIO_INPUT);
}

创建源

obs中使用id 的方式区分不同的源:

如 摄像头  dshow_input

实现一个简单的录制软件:支持录制桌面与窗口_第21张图片

窗口采集 window_capture

实现一个简单的录制软件:支持录制桌面与窗口_第22张图片

桌面采集 monitor_capture

实现一个简单的录制软件:支持录制桌面与窗口_第23张图片

创建源:

/**
 * Creates a source of the specified type with the specified settings.
 *
 *   The "source" context is used for anything related to presenting
 * or modifying video/audio.  Use obs_source_release to release it.
 */
EXPORT obs_source_t *obs_source_create(const char *id, const char *name,
				       obs_data_t *settings,
				       obs_data_t *hotkey_data);

实现一个简单的录制软件:支持录制桌面与窗口_第24张图片

src

#include "ObsWrapper.h"
#include 
#include 
#include "libavcodec/avcodec.h"
#include 
#include "CConfigParse.h"

using namespace std;


#define DL_D3D11 = "libobs-d3d11.dll"
#define DL_OPENGL = "libobs-opengl.dll"

#define INPUT_AUDIO_SOURCE "wasapi_input_capture"
#define OUTPUT_AUDIO_SOURCE "wasapi_output_capture"

#define VIDEO_ENCODER_ID           AV_CODEC_ID_H264
#define VIDEO_ENCODER_NAME         "libx264"
#define RECORD_OUTPUT_FORMAT       "mp4"
#define RECORD_OUTPUT_FORMAT_MIME  "video/mp4"
#define VIDEO_FPS            30
#define VIDEO_ENCODER_ID           AV_CODEC_ID_H264
#define AUDIO_BITRATE 128 
#define VIDEO_BITRATE 150 
#define OUT_WIDTH  1920
#define OUT_HEIGHT 1080

enum SOURCE_CHANNELS {
	SOURCE_CHANNEL_TRANSITION,
	SOURCE_CHANNEL_AUDIO_OUTPUT,
	SOURCE_CHANNEL_AUDIO_OUTPUT_2,
	SOURCE_CHANNEL_AUDIO_INPUT,
	SOURCE_CHANNEL_AUDIO_INPUT_2,
	SOURCE_CHANNEL_AUDIO_INPUT_3,
};

ObsWrapper::ObsWrapper()
{
}

ObsWrapper::~ObsWrapper()
{
}

bool ObsWrapper::init_obs()
{
	string cfg_path = "D:/desktop_rec_cfg";

	if (!obs_initialized())
	{
		//初始化obs
		if (!obs_startup("zh-CN", cfg_path.c_str(), NULL))
		{
			return false;
		}

		//加载插件
		QString path = qApp->applicationDirPath();
		string path_str = path.toStdString();

		string plugin_path = path_str + "/obs-plugins/32bit";
		string data_path = path_str + "/data/obs-plugins/%module%";

		obs_add_module_path(plugin_path.c_str(), data_path.c_str());

		obs_load_all_modules();
	}

	//音频设置
	if (!ResetAudio())
		return false;

	//视频设置
	if (ResetVideo() != OBS_VIDEO_SUCCESS)
		return false;

	if (!create_output_mode())
		return false;

	return true;
}

int ObsWrapper::start_rec()
{
	SetupFFmpeg();

	if(!obs_output_start(fileOutput))
		return -1;

	return 0;
}

int ObsWrapper::stop_rec()
{
	bool force = true;

	if (force)
		obs_output_force_stop(fileOutput);
	else
		obs_output_stop(fileOutput);

	return 0;
}

static void AddSource(void* _data, obs_scene_t* scene)
{
	obs_source_t* source = (obs_source_t*)_data;
	obs_scene_add(scene, source);
	obs_source_release(source);
}

int ObsWrapper::add_scene_source(REC_TYPE type)
{
	//1  参考 ClearSceneData 清空所有源

	/*
	for(int i=0;i<6;++i){
		obs_set_output_source(i, nullptr);
	}
	
	*/
	obs_set_output_source(SOURCE_CHANNEL_TRANSITION, nullptr);
	obs_set_output_source(SOURCE_CHANNEL_AUDIO_OUTPUT, nullptr);
	obs_set_output_source(SOURCE_CHANNEL_AUDIO_INPUT, nullptr);


	//2 参考 InitDefaultTransitions 设置默认转场
	size_t idx = 0;
	const char* id;

	/* automatically add transitions that have no configuration (things
	 * such as cut/fade/etc) */
	while (obs_enum_transition_types(idx++, &id)) {
		const char* name = obs_source_get_display_name(id);

		if (!obs_is_source_configurable(id)) {
			obs_source_t* tr = obs_source_create_private(id, name, NULL);

			if (strcmp(id, "fade_transition") == 0)
				fadeTransition = tr;
		}
	}

	if (!fadeTransition)
	{
		return -1;
	}

	obs_set_output_source(SOURCE_CHANNEL_TRANSITION, fadeTransition);
	obs_source_release(fadeTransition);

	//3 创建场景
	scene = obs_scene_create("MyScene");
	if (!scene)
	{
		return -2;
	}

    //4 设置场景 参考SetCurrentScene
	obs_source_t* s = obs_get_output_source(SOURCE_CHANNEL_TRANSITION);
	obs_transition_set(s, obs_scene_get_source(scene));
	obs_source_release(s);

	//创建源:显示器采集
	if(type == REC_DESKTOP)
		captureSource = obs_source_create("monitor_capture", "Computer_Monitor_Capture", NULL, nullptr);
	else
	    captureSource = obs_source_create("window_capture", "MyWindow_Capture", NULL, nullptr);

	if (captureSource)
	{
		obs_scene_atomic_update(scene, AddSource, captureSource);
	}
	else
	{
		return -3;
	}

	// 设置窗口捕获原的窗口或显示器
	setting_source = obs_data_create();
	obs_data_t* curSetting = obs_source_get_settings(captureSource);
	obs_data_apply(setting_source, curSetting);
	obs_data_release(curSetting);

	properties = obs_source_properties(captureSource);
	property = obs_properties_first(properties);
}

void ResetAudioDevice(const char* sourceId, const char* deviceId,
	const char* deviceDesc, int channel)
{
	bool disable = deviceId && strcmp(deviceId, "disabled") == 0;
	obs_source_t* source;
	obs_data_t* settings;

	source = obs_get_output_source(channel);
	if (source) {
		if (disable) {
			obs_set_output_source(channel, nullptr);
		}
		else {
			settings = obs_source_get_settings(source);
			const char* oldId =
				obs_data_get_string(settings, "device_id");
			if (strcmp(oldId, deviceId) != 0) {
				obs_data_set_string(settings, "device_id",
					deviceId);
				obs_source_update(source, settings);
			}
			obs_data_release(settings);
		}

		obs_source_release(source);

	}
	else if (!disable) {
		settings = obs_data_create();
		obs_data_set_string(settings, "device_id", deviceId);
		source = obs_source_create(sourceId, deviceDesc, settings,
			nullptr);
		obs_data_release(settings);

		obs_set_output_source(channel, source);
		obs_source_release(source);
	}
}

static inline bool HasAudioDevices(const char* source_id)
{
	const char* output_id = source_id;
	obs_properties_t* props = obs_get_source_properties(output_id);
	size_t count = 0;

	if (!props)
		return false;

	obs_property_t* devices = obs_properties_get(props, "device_id");
	if (devices)
		count = obs_property_list_item_count(devices);

	obs_properties_destroy(props);

	return count != 0;
}

static bool CreateAACEncoder(OBSEncoder& res, string& id,
	const char* name, size_t idx)
{
	const char* id_ = "ffmpeg_aac";

	res = obs_audio_encoder_create(id_, name, nullptr, idx, nullptr);

	if (res) {
		obs_encoder_release(res);
		return true;
	}

	return false;
}

void ObsWrapper::rec_system_audio()
{
	bool hasDesktopAudio = HasAudioDevices(OUTPUT_AUDIO_SOURCE);

	if (hasDesktopAudio)
		ResetAudioDevice(OUTPUT_AUDIO_SOURCE, "default",
			"Default Desktop Audio", SOURCE_CHANNEL_AUDIO_OUTPUT);
}

void ObsWrapper::rec_out_audio()
{
	bool hasInputAudio = HasAudioDevices(INPUT_AUDIO_SOURCE);
	if (hasInputAudio)
		ResetAudioDevice(INPUT_AUDIO_SOURCE, "default",
			"Default Mic/Aux", SOURCE_CHANNEL_AUDIO_INPUT);
}

void ObsWrapper::SearchRecTargets(REC_TYPE type)
{
	m_vecRecTargets.clear();

	const char* rec_type_name = nullptr;
	if (type == REC_WINDOWS)
	{
		rec_type_name = "window";
	}
	else
	{
		rec_type_name = "monitor";
	}

	while (property)
	{
		const char* name = obs_property_name(property);

		//找到该属性
		if (strcmp(name, rec_type_name) == 0)
		{
			//获取该属性的的item列表
			size_t count = obs_property_list_item_count(property);
			const char* string = nullptr;

			for (size_t i = 0; i < count; i++)
			{
				if (type == REC_WINDOWS)
				{
					//枚举到每个item 的名称
					string = obs_property_list_item_string(property, i);
				}
				else
				{
					const char* item_name = obs_property_list_item_name(property, i);
					string = item_name;
				}


				m_vecRecTargets.push_back(string);
			}
		}

		//获取下一个
		obs_property_next(&property);
	}
}

void ObsWrapper::UpdateRecItem(const char* target, REC_TYPE type)
{
	for (auto ele : m_vecRecTargets)
	{
		if (ele == string(target))
		{
			if(type == REC_DESKTOP)
				obs_data_set_string(setting_source, "monitor", target);
			else
				obs_data_set_string(setting_source, "window", target);
			/** Updates settings for this source */
			obs_source_update(captureSource, setting_source);

			break;
		}
	}
	
	obs_data_release(setting_source);
}

bool ObsWrapper::ResetAudio()
{
	struct obs_audio_info ai;
	ai.samples_per_sec = 48000;
	ai.speakers = SPEAKERS_STEREO;

	return obs_reset_audio(&ai);
}

int ObsWrapper::ResetVideo()
{
	struct obs_video_info ovi;
	ovi.fps_num = VIDEO_FPS;
	ovi.fps_den = 1;

	ovi.graphics_module = "libobs-d3d11.dll";
	ovi.base_width = 1920;
	ovi.base_height = 1080;
	ovi.output_width = 1920;
	ovi.output_height = 1080;
	ovi.output_format = VIDEO_FORMAT_I420;
	ovi.colorspace = VIDEO_CS_709;
	ovi.range = VIDEO_RANGE_FULL;
	ovi.adapter = 0;
	ovi.gpu_conversion = true;
	ovi.scale_type = OBS_SCALE_BICUBIC;

	return obs_reset_video(&ovi);
}

bool ObsWrapper::create_output_mode()
{
	if (!fileOutput)
	{
		//高级输出 ffmpeg
		fileOutput = obs_output_create("ffmpeg_output", "adv_ffmpeg_output", nullptr, nullptr);

		if (!fileOutput)
			return false;
	}
	
	for (int i = 0; i < MAX_AUDIO_MIXES; i++) {
		char name[9];
		sprintf(name, "adv_aac%d", i);

		if (!CreateAACEncoder(aacTrack[i], aacEncoderID[i], name, i))
		{
			return false;
		}

		obs_encoder_set_audio(aacTrack[i], obs_get_audio());
	}

	return true;
}

///**
// * @brief 长整数转string, 主要是针对时间戳
// */
//string i64_to_string(__int64 number)
//{
//	char str[20];  //足够了
//	_i64toa(number, str, 10);
//	string s(str);
//	return s;
//}
//
///**
// * @brief 产生时间秒数
// */
//time_t getTimeSeconds()
//{
//	time_t myt = time(NULL);
//	return myt;
//}
//
///**
// * @brief 获取时间秒数并转为字符串
// */
//string getTimeSecondsString()
//{
//	std::string str = i64_to_string(getTimeSeconds());
//	return str;
//}

void ObsWrapper::SetupFFmpeg()
{
	obs_data_t* settings = obs_data_create();

	QDateTime dt = QDateTime::currentDateTime();
	QString time_str = dt.toString("yyyy_MM_dd_hh_mm_ss");

	string timestr = time_str.toStdString();

	string path = CConfigParse::instance().getOutputPath();
	string out_file_name = path + timestr + ".mp4";

	obs_data_set_string(settings, "url", out_file_name.c_str());
	obs_data_set_string(settings, "format_name", RECORD_OUTPUT_FORMAT);
	obs_data_set_string(settings, "format_mime_type", RECORD_OUTPUT_FORMAT_MIME);
	obs_data_set_string(settings, "muxer_settings", "movflags=faststart"); 
	obs_data_set_int(settings, "gop_size", VIDEO_FPS * 10);
	obs_data_set_string(settings, "video_encoder", VIDEO_ENCODER_NAME);
	obs_data_set_int(settings, "video_encoder_id", VIDEO_ENCODER_ID);

	if (VIDEO_ENCODER_ID == AV_CODEC_ID_H264)
		obs_data_set_string(settings, "video_settings", "profile=main x264-params=crf=22");
	else if (VIDEO_ENCODER_ID == AV_CODEC_ID_FLV1)
		obs_data_set_int(settings, "video_bitrate", VIDEO_BITRATE);

	obs_data_set_int(settings, "audio_bitrate", AUDIO_BITRATE);
	obs_data_set_string(settings, "audio_encoder", "aac");
	obs_data_set_int(settings, "audio_encoder_id", AV_CODEC_ID_AAC);
	obs_data_set_string(settings, "audio_settings", NULL);

	obs_data_set_int(settings, "scale_width", OUT_WIDTH);
	obs_data_set_int(settings, "scale_height", OUT_HEIGHT);

	obs_output_set_mixer(fileOutput, 1);  //混流器,如果不设置,可能只有视频没有音频
	obs_output_set_media(fileOutput, obs_get_video(), obs_get_audio());
	obs_output_update(fileOutput, settings);

	obs_data_release(settings);
}

/*

obs封装类

*/

#pragma once

#include "obs.h"
#include "obs.hpp"
#include 
#include 


using namespace std;

enum REC_TYPE
{
	REC_WINDOWS,
	REC_DESKTOP
};

class ObsWrapper
{
public:
	ObsWrapper();
	~ObsWrapper();

	/*
		初始化
	*/
	bool init_obs();

	/*
		开始录制
	*/
	int  start_rec();

	/*
		停止录制
	*/
	int  stop_rec();

	/*
		添加源:具体的窗口
	*/
	int  add_scene_source(REC_TYPE type);

	/*
		采集系统音频
	*/
	void rec_system_audio();


	/*
		采集麦克风音频
	*/
	void rec_out_audio();

	/*
		枚举当前类型:屏幕 或 窗口
	*/
	void SearchRecTargets(REC_TYPE type);

	/*
		更新源的item :设置具体的某个源内容
	*/
	void UpdateRecItem(const char* target, REC_TYPE type);

	/*
		获取当前类型屏幕 或 窗口的列表
	*/
	vector getRecTargets() const
	{
		return m_vecRecTargets;
	}

private:
	bool ResetAudio();
	int ResetVideo();
	bool create_output_mode();
	void SetupFFmpeg();

private:
	OBSOutput fileOutput;
	obs_source_t* fadeTransition = nullptr;
	obs_scene_t* scene = nullptr;
	obs_source_t* captureSource;
	obs_properties_t* properties;

	OBSEncoder aacTrack[MAX_AUDIO_MIXES];
	std::string aacEncoderID[MAX_AUDIO_MIXES];

	vector m_vecRecTargets;  //存储查找出来的的窗口或显示器

	obs_property_t* property = nullptr;
	obs_data_t* setting_source = nullptr;
};

工程链接:

链接:https://pan.baidu.com/s/1OL_-XwA3tySlZtlws65e1g?pwd=j94s 
提取码:j94s

如果报错:

error:there‘s no Qt version assigned to project please assign a Qt installation in qt project settin_there's no qt version assigned to project_妙为的博客-CSDN博客

你可能感兴趣的:(obs,c++)