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

news2024/11/24 22:47:01

 

环境搭建

CSDN

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

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

插件;

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

vs 2019创建一个qt工程

debug win32 

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

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

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

拷貝

到工程目录下

UI层 

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中的调用

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中的:

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后

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

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

创建ffmpeg_output

 音频编码器的创建

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

高级输出,需要设置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);
}

然后:

因此可以:

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

结束录制

设置场景与源

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

在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中将源设置为空

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

obs最多支持64个源 

2 InitDefaultTransitions

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

3 创建场景 

4 CreateFirstRunSources 

重设音频设备

wasapi_input_capture    音频输入设备

wasapi_output_capture  音频输出设备

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

采集系统音频麦克风音频

参考

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

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

窗口采集 window_capture

桌面采集 monitor_capture

创建源:

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

src

#include "ObsWrapper.h"
#include <string>
#include <QApplication>
#include "libavcodec/avcodec.h"
#include <QDateTime>
#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 <string>
#include <vector>


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<string> 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<string> 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博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/725153.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

uniapp 微信小程序导航功能(从地址列表内点击某一个地址)

效果图&#xff1a; <template><view class"user"><view class"list"><view class"title">地址列表</view><view class"title-label"><view>名称</view><view>距离&#xff…

开启Windows共享文件夹审核,让用户查看谁删除了文件

在动画行业有个常用到的需求&#xff0c; 我的共享文件夹内的文件被谁删除了&#xff0c;查不到&#xff0c;只能查看谁创建&#xff0c;谁修改的&#xff0c;但查不到谁删除的&#xff0c;分享一下&#xff1a; 1 开始->运行->gpedit.msc 开发本地组策略编辑器, 在计算…

el-select 下拉选择框添加字段单位显示 el-select下拉按钮前加单位显示

背景&#xff1a;el-select可以通过自定义模版在下拉选项内加单位但是选择后没法显示单位 实现效果 实现代码 html <el-selectv-model"form.day"class"select-prefix"><el-option label"1" :value"1" /><el-option la…

Spark SQL 6-7

6. Spark SQL实战 6.1 数据说明 数据集是货品交易数据集。 每个订单可能包含多个货品&#xff0c;每个订单可以产生多次交易&#xff0c;不同的货品有不同的单价。 6.2 加载数据 tbStock&#xff1a; scala> case class tbStock(ordernumber:String,locationid:String,…

基于Java+vue前后端分离餐厅点菜管理系统设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

Ext JS 如何设置工具栏按钮和一般按钮保持统一样式

在Ext JS 中, Button的背景色保持和系统的主色调一致, 样式如下: 但是使用工具栏(toolbar) 添加按钮的时候, 按钮的背景色确实灰色,如下图所示: 为什么会有这个差别呢? 如何让它们保持一致呢? 工具栏按钮与Button不一致的原因 看一下Toolbar里面的按钮最终产生…

C++中,C::C::C::C::foo() 为什么编译成功?

有人问&#xff1a; class Entity { public:static void foo() {} };int main() {Entity::Entity::Entity::Entity::Entity::foo(); } 为什么 最后那行&#xff1a; Entity::Entity::Entity::Entity::Entity::foo(); 能编译成功&#xff1f;这是什么规则&#xff1f; 嗯……

如何优雅的跳出 for 循环

文章目录 需求分析1. 普通for循环2. for..in循环3. for..of循环(ES6)4. forEach(callbackFn, ?thisArg)方法(ES5.1) 源码1. 终止 普通 for 循环2. 终止 forEach2.1 forEach 可以跳出本次循环&#xff0c;执行下一次循环2.2 forEach终止循环 需求 如何做到优雅的跳出 for 循环 …

像考研一样学个宇宙之刷题篇:剑指offerⅡ:整数系列——整数除法0706 TODO

001. 整数除法&#xff1a; 给定两个整数 a 和 b &#xff0c;求它们的除法的商 a/b &#xff0c;要求不得使用乘号 ‘*’、除号 ‘/’ 以及求余符号 ‘%’ 。 一些知识点和思路 第一题&#xff0c;easy题&#xff0c;狠狠来了个下马威。 首先是 “被除数/除数”关于溢出的情…

| 交互式建模与学习:重建人类运动功能

在报告《交互式建模与学习&#xff1a;重建人类运动功能》中&#xff0c;清华大学副教授眭亚楠介绍了AI在重建人类运动功能时&#xff0c;从无模型学习&#xff08;model-free learning&#xff09;到基于模型学习&#xff08;model-based learning&#xff09;的转变&#xff…

剑指 Offer II . 删除链表的倒数第 n 个结点

给定一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5] 示例 2&#xff1a; 输入&#xff1a;head [1], n 1 输出&#xff1a;[] 示例 3&#x…

【MySQL入门实战5】-Linux PRM 包安装MySQL

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&#x1f61…

【QT】QCustomPlot开发笔记

QCustomPlot开发 01、QCustomPlot简介1.1 帮助文档1.2 下载&使用 02、QCustomPlot项目使用笔记2.1 创建QCustomPlot 03、源代码 01、QCustomPlot简介 QCustomPlot 是一个用于科学绘图的 QT 第三方库&#xff0c;可以用于常见的二维图像绘制&#xff0c;比如函数曲线、参数…

管理类联考——择校——学费

截止2023年06月&#xff0c;关于广东的管理类联考的xuefei。 借鉴&#xff1a;https://zhuanlan.zhihu.com/p/421296334。罗列985相关院校 广州以本土MBA为主&#xff0c;共有9家院校&#xff1b; 深圳以外地MBA为主&#xff0c;有超过20家院校。 一梯队&#xff1a;北大光华…

日志---spdlog

spdlog中各对象都分为多线程与单线程版本&#xff1a; *_st&#xff1a;单线程版本&#xff0c;不用加锁&#xff0c;效率更高。*_mt&#xff1a;多线程版本&#xff0c;用于多线程程序是线程安全的。 spdlog是基于C11实现的一款纯头文件的日志管理库 git地址&#xff1a;htt…

WSI-finetuning

一、贡献 (1)通过引入一个IB模块来提出WSI-MIL的简单代理任务&#xff0c;该模块将包中超过10k个冗余实例提炼成不到1k个最受支持的实例。因此&#xff0c;在千兆像素图像上进行基于梯度的训练的并行计算成本减轻了十倍以上。通过对简化袋的学习和分类&#xff0c;发现由于病理…

【自学笔记】在SQL Server中创建用户角色及授权(使用SQL语句)更新2023.07.06

--<在SQL Server中创建用户角色及授权(使用SQL语句)>更新2023.07.06 --1. 首先在 SQL Server 服务器级别&#xff0c;创建登陆帐户&#xff08;create login&#xff09; --2. 创建数据库用户&#xff08;create user&#xff09;&#xff1a; --3. 通过加入数据库角色&a…

不忘初心,筑梦未来 | 【2023 ACDU 中国行·深圳站】数据库主题交流活动成功举办!

6月30日下午&#xff0c;【ACDU 中国行深圳站】在深圳回酒店圆满落下帷幕。本次活动由中国数据库联盟&#xff08;ACDU&#xff09;联合墨天轮社区主办&#xff0c;围绕「数据库前沿技术揭秘及应用」这一主题&#xff0c;七位数据库行业的领军人物从数据库新特性解读、创新与应…

Docker集群部署-redis集群

学习要求 利用Docker实现redis 集群的部署&#xff0c;实现3主3从集群配置&#xff0c;并在此基础上实现主从扩容、缩容。 学习准备 要求实验主机能够连接外网&#xff0c;已经正确安装Docker&#xff0c;并关闭防火墙和selinux。 学习步骤 创建6个docker容器实例&#xf…

OpenFeign 源码分析

&#xff08;学习别人的思想&#xff0c;可以找 bug&#xff0c;优化你的代码&#xff0c;提高代码的健壮性&#xff09;看源码之前要先大致猜想一下 他是怎么实现的&#xff1f;&#xff08;先使用在分析&#xff09; 5.1 OpenFeign 的原理是什么&#xff1f; 根据前文的案例…