环境搭建
将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
如果报错: