obs录制功能源码分析

录制按钮

界面文件:

 

 主界面:OBSBasic.ui  中开始录制按钮的objectName 是   recordButton

槽函数:


void OBSBasic::on_recordButton_clicked()
{//1 输出模式是否被激活if (outputHandler->RecordingActive()) {bool confirm = config_get_bool(GetGlobalConfig(), "BasicWindow","WarnBeforeStoppingRecord");if (confirm && isVisible()) {QMessageBox::StandardButton button =OBSMessageBox::question(this, QTStr("ConfirmStopRecord.Title"),QTStr("ConfirmStopRecord.Text"),QMessageBox::Yes | QMessageBox::No,QMessageBox::No);if (button == QMessageBox::No) {ui->recordButton->setChecked(true);return;}}StopRecording();} else {if (!UIValidation::NoSourcesConfirmation(this)) {ui->recordButton->setChecked(false);return;}// 执行StartRecording();}
}

 调用堆栈:

 开始录制

void OBSBasic::StartRecording()
{if (outputHandler->RecordingActive())return;if (disableOutputsRef)return;// 1 检查磁盘是否有效if (!OutputPathValid()) {OutputPathInvalidMessage();ui->recordButton->setChecked(false);return;}//2 检查磁盘空间if (LowDiskSpace()) {DiskSpaceMessage();ui->recordButton->setChecked(false);return;}if (api)api->on_event(OBS_FRONTEND_EVENT_RECORDING_STARTING);SaveProject();//3 开始录制 if (!outputHandler->StartRecording())ui->recordButton->setChecked(false);
}// 简单输出
bool SimpleOutput::StartRecording()
{UpdateRecording();if (!ConfigureRecording(false))return false;if (!obs_output_start(fileOutput)) {QString error_reason;const char *error = obs_output_get_last_error(fileOutput);if (error)error_reason = QT_UTF8(error);elseerror_reason = QTStr("Output.StartFailedGeneric");QMessageBox::critical(main,QTStr("Output.StartRecordingFailed"),error_reason);return false;}return true;
}

调用libobs 

E:\opensrc\obs_src_19041\obs-studio-19141\obs-studio\libobs\obs-output.c

bool obs_output_start(obs_output_t *output)
{bool encoded;bool has_service;if (!obs_output_valid(output, "obs_output_start"))return false;if (!output->context.data)return false;has_service = (output->info.flags & OBS_OUTPUT_SERVICE) != 0;if (has_service && !obs_service_initialize(output->service, output))return false;encoded = (output->info.flags & OBS_OUTPUT_ENCODED) != 0;if (encoded && output->delay_sec) {return obs_output_delay_start(output);} else {if (obs_output_actual_start(output)) {do_output_signal(output, "starting");return true;}return false;}
}

停止录制:

void SimpleOutput::StopRecording(bool force)
{if (force)obs_output_force_stop(fileOutput);elseobs_output_stop(fileOutput);
}

调用的都是libobs.dll的导出 导出方法   obs.h中声明

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

obs输出模式

高級输出

 

bool AdvancedOutput::StartRecording()
{const char *path;const char *recFormat;const char *filenameFormat;bool noSpace = false;bool overwriteIfExists = false;if (!useStreamEncoder) {if (!ffmpegOutput) {UpdateRecordingSettings();}} else if (!obs_output_active(streamOutput)) {UpdateStreamSettings();}UpdateAudioSettings();// 1 设置输出,ffmpeg 输出 or 录制输出if (!Active())SetupOutputs();//2 配置if (!ffmpegOutput || ffmpegRecording) {path = config_get_string(main->Config(), "AdvOut",ffmpegRecording ? "FFFilePath": "RecFilePath");recFormat = config_get_string(main->Config(), "AdvOut",ffmpegRecording ? "FFExtension": "RecFormat");filenameFormat = config_get_string(main->Config(), "Output","FilenameFormatting");overwriteIfExists = config_get_bool(main->Config(), "Output","OverwriteIfExists");noSpace = config_get_bool(main->Config(), "AdvOut",ffmpegRecording? "FFFileNameWithoutSpace": "RecFileNameWithoutSpace");string strPath = GetRecordingFilename(path, recFormat, noSpace,overwriteIfExists,filenameFormat,ffmpegRecording);obs_data_t *settings = obs_data_create();obs_data_set_string(settings, ffmpegRecording ? "url" : "path",strPath.c_str());obs_output_update(fileOutput, settings);obs_data_release(settings);}//3 startif (!obs_output_start(fileOutput)) {QString error_reason;const char *error = obs_output_get_last_error(fileOutput);if (error)error_reason = QT_UTF8(error);elseerror_reason = QTStr("Output.StartFailedGeneric");QMessageBox::critical(main,QTStr("Output.StartRecordingFailed"),error_reason);return false;}return true;
}

录制

 

 为什么不使用ffmepeg命令行录制

  •  1 录制窗口鼠标闪烁
  • 2 无法处理屏幕分辨率非100%时的区域的录制不全
  • 3 热插拔时,无法更改采集源
  • 4 无法录制DirectUi窗口(无窗口句柄)

FFmpeg

Builds - CODEX FFMPEG @ gyan.dev

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

OBS采集模块

  • 显示器采集:core中的 libobs-d3d11和 libos-winrt  以及    duplicator-monitor-capture.c
  • 窗口采集:win-capture      window-capture.c
  •  游戏采集:game-capture.c
  • 采集卡采集:decklink
  • 摄像头采集:win-dshow

窗口采集方式

 

BitBlt的采集效率比较低,且只能采集一些有窗口句柄的窗口,无窗口句柄得用WGC。例如谷歌浏览器,VSCode 等都是无句柄窗口,这些窗口用BitBlt采集,显示都是黑色的

 

 E:\opensrc\obs_src_19041\obs-studio-19141\obs-studio\plugins\win-capture\window-capture.c

enum window_capture_method {METHOD_AUTO,METHOD_BITBLT,METHOD_WGC,
};

具体使用哪种方式根据窗口特点,在自动模式下:当是以下窗口是使用wgc 否则使用bltblt,

static const char *wgc_partial_match_classes[] = {"Chrome","Mozilla",NULL,
};static const char *wgc_whole_match_classes[] = {"ApplicationFrameWindow","Windows.UI.Core.CoreWindow","XLMAIN",        /* excel*/"PPTFrameClass", /* powerpoint */"OpusApp",       /* word */NULL,
};
choose_method(enum window_capture_method method, bool wgc_supported,const char *current_class)
{if (!wgc_supported)return METHOD_BITBLT;if (method != METHOD_AUTO)return method;if (!current_class)return METHOD_BITBLT;const char **class = wgc_partial_match_classes;while (*class) {if (astrstri(current_class, *class) != NULL) {return METHOD_WGC;}class ++;}class = wgc_whole_match_classes;while (*class) {if (astrcmpi(current_class, *class) == 0) {return METHOD_WGC;}class ++;}return METHOD_BITBLT;
}

采集方式如上;

 WGC本质是使用到d3d11采集,GPU

 

 Windows.Graphics.Capture Namespace - Windows UWP applications | Microsoft Learn

E:\opensrc\obs_src_19041\obs-studio-19141\obs-studio\plugins\win-capture\window-capture.c

 

 桌面采集

 Advances to the display Infrastructure - Windows drivers | Microsoft Learn

Desktop duplication - Windows drivers | Microsoft Learn 

 

两种采集方式:

enum display_capture_method {METHOD_AUTO,METHOD_DXGI,METHOD_WGC,
};

采集方式的选择:

bool obs_module_load(void)
{struct win_version_info ver;bool win8_or_above = false;char *config_dir;//1 确定windows 版本struct win_version_info win1903 = {.major = 10, .minor = 0, .build = 18362, .revis = 0};config_dir = obs_module_config_path(NULL);if (config_dir) {os_mkdirs(config_dir);bfree(config_dir);}get_win_ver(&ver);// 2 win8win8_or_above = ver.major > 6 || (ver.major == 6 && ver.minor >= 2);// 3 使用d3dobs_enter_graphics();graphics_uses_d3d11 = gs_get_device_type() == GS_DEVICE_DIRECT3D_11;obs_leave_graphics();// 4 支持wgcif (graphics_uses_d3d11)wgc_supported = win_version_compare(&ver, &win1903) >= 0;// 5 选择使用duplicator_capture_info (wgc)或monitor_capture_info(gdi)  if (win8_or_above && graphics_uses_d3d11)obs_register_source(&duplicator_capture_info);elseobs_register_source(&monitor_capture_info);obs_register_source(&window_capture_info);char *config_path = obs_module_config_path(NULL);init_hook_files();init_hooks_thread =CreateThread(NULL, 0, init_hooks, config_path, 0, NULL);obs_register_source(&game_capture_info);return true;
}

 E:\opensrc\obs_src_19041\obs-studio-19141\obs-studio\plugins\win-capture\duplicator-monitor-capture.c

static void duplicator_capture_tick(void *data, float seconds)
{struct duplicator_capture *capture = data;/* completely shut down monitor capture if not in use, otherwise it can* sometimes generate system lag when a game is in fullscreen mode */if (!obs_source_showing(capture->source)) {if (capture->showing) {obs_enter_graphics();free_capture_data(capture);obs_leave_graphics();capture->showing = false;}return;}/* always try to load the capture immediately when the source is first* shown */if (!capture->showing) {capture->reset_timeout = RESET_INTERVAL_SEC;}obs_enter_graphics();// 采集模式选择      if (capture->method == METHOD_WGC) {//wgcif (capture->reset_wgc && capture->capture_winrt) {capture->exports.winrt_capture_free(capture->capture_winrt);capture->capture_winrt = NULL;capture->reset_wgc = false;capture->reset_timeout = RESET_INTERVAL_SEC;}if (!capture->capture_winrt) {capture->reset_timeout += seconds;if (capture->reset_timeout >= RESET_INTERVAL_SEC) {capture->capture_winrt =capture->exports.winrt_capture_init_monitor(capture->capture_cursor,capture->handle);capture->reset_timeout = 0.0f;}}} else {if (capture->capture_winrt) {capture->exports.winrt_capture_free(capture->capture_winrt);capture->capture_winrt = NULL;}if (!capture->duplicator) {capture->reset_timeout += seconds;//桌面复制if (capture->reset_timeout >= RESET_INTERVAL_SEC) {capture->duplicator = gs_duplicator_create(capture->dxgi_index);capture->reset_timeout = 0.0f;}}if (capture->duplicator) {if (capture->capture_cursor)cursor_capture(&capture->cursor_data);if (!gs_duplicator_update_frame(capture->duplicator)) {free_capture_data(capture);} else if (capture->width == 0) {reset_capture_data(capture);}}}obs_leave_graphics();if (!capture->showing)capture->showing = true;UNUSED_PARAMETER(seconds);
}

默认会走到桌面复制

 

 当选择 windows 10 則使用wgc

 BitBlt x264 采集

四个线程:

  • video_thread:视频编码和输出线程,后面简称“video 线程,负责视频编码和输出
  • obs_graphics_thread:视频渲染线程,后面简称“graphics 线程,也就是源合成音视频、生成原始视频帧、显示到窗口(预览)等功能
  • audio_thread: 音频编码和输出线程,后面简称“audio 线程
  • CaptureThread:音频采集

  当点击开始录制按钮时,在ffmpeg_mux_start 中,raw_active被设置为ture,obs_graphics_thread post信号量,video_thread接收到信号量后,编码输出

点击开始录制后:

video_thread线程的创建过程:

ResetVideo

   AttemptToResetVideo

        --obs_reset_video

             -- obs_init_video 

                       --video_output_open(&video->video, &vi);

                                    --pthread_create(&out->thread, NULL, video_thread   创建video线程

                        --pthread_create(&video->video_thread, NULL, obs_graphics_thread, obs);创建视频渲染线程

在obs_graphics_thread中,通过while (obs_graphics_thread_loop(&context)) 处理任务:

当帧准备好后,通过 output_video_data 调用video_output_unlock_frame  发送信号量

通知video线程开始工作 :

video线程拿到信号量进入while循环

video_output_cur_frame 处理当前视频帧

 

scale_video_output 做scale 处理,采集到rgb转yuv,等

input->callback是static void receive_video(void *param, struct video_data *frame)

做视频编码:

 

进入具体的编码方式:obs_x264_encode

static bool obs_x264_encode(void *data, struct encoder_frame *frame,struct encoder_packet *packet,bool *received_packet)
{struct obs_x264 *obsx264 = data;x264_nal_t *nals;int nal_count;int ret;x264_picture_t pic, pic_out;if (!frame || !packet || !received_packet)return false;if (frame)init_pic_data(obsx264, &pic, frame);ret = x264_encoder_encode(obsx264->context, &nals, &nal_count,(frame ? &pic : NULL), &pic_out);if (ret < 0) {warn("encode failed");return false;}*received_packet = (nal_count != 0);parse_packet(obsx264, packet, nals, nal_count, &pic_out);return true;
}

 其中:x264_encoder_encode 是依赖中的接口

E:\opensrc\obs_src_19041\dependencies2019\win32\include\x264.h

 

结束录制

主线程调用堆栈:

     obs-ffmpeg.dll!ffmpeg_mux_stop(void * data, unsigned __int64 ts) 

     obs.dll!obs_output_actual_stop(obs_output * output, bool force, unsigned __int64 ts) 

     obs.dll!obs_output_stop(obs_output * output) 

     obs32.exe!SimpleOutput::StopRecording(bool force) 

     obs32.exe!OBSBasic::StopRecording()

ucrtbased.dll线程 obs_x264_destroy:x64销毁

 音频设备初始化以及音频采集线程创建

E:\opensrc\obs_src_19041\obs-studio-19141\obs-studio\plugins\win-wasapi\win-wasapi.cpp


static void *CreateWASAPISource(obs_data_t *settings, obs_source_t *source,bool input)
{try {return new WASAPISource(settings, source, input);} catch (const char *error) {blog(LOG_ERROR, "[CreateWASAPISource] %s", error);}return nullptr;
}

 com初始化

WASAPISource::WASAPISource(obs_data_t *settings, obs_source_t *source_,bool input): source(source_), isInputDevice(input)
{UpdateSettings(settings);stopSignal = CreateEvent(nullptr, true, false, nullptr);if (!stopSignal.Valid())throw "Could not create stop signal";receiveSignal = CreateEvent(nullptr, false, false, nullptr);if (!receiveSignal.Valid())throw "Could not create receive signal";Start();
}inline void WASAPISource::Start()
{if (!TryInitialize()) {blog(LOG_INFO,"[WASAPISource::WASAPISource] ""Device '%s' not found.  Waiting for device",device_id.c_str());Reconnect();}
}......void WASAPISource::Initialize()
{HRESULT res;//初始化comres = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr,CLSCTX_ALL, __uuidof(IMMDeviceEnumerator),(void **)enumerator.Assign());if (FAILED(res))throw HRError("Failed to create enumerator", res);if (!InitDevice())return;device_name = GetDeviceName(device);if (!notify) {notify = new WASAPINotify(this);enumerator->RegisterEndpointNotificationCallback(notify);}HRESULT resSample;IPropertyStore *store = nullptr;PWAVEFORMATEX deviceFormatProperties;PROPVARIANT prop;resSample = device->OpenPropertyStore(STGM_READ, &store);if (!FAILED(resSample)) {resSample =store->GetValue(PKEY_AudioEngine_DeviceFormat, &prop);if (!FAILED(resSample)) {if (prop.vt != VT_EMPTY && prop.blob.pBlobData) {deviceFormatProperties =(PWAVEFORMATEX)prop.blob.pBlobData;device_sample = std::to_string(deviceFormatProperties->nSamplesPerSec);}}store->Release();}InitClient();if (!isInputDevice)InitRender();InitCapture();
}

创建音频采集线程CaptureThread

 

bool WASAPISource::ProcessCaptureData()
{HRESULT res;LPBYTE buffer;UINT32 frames;DWORD flags;UINT64 pos, ts;UINT captureSize = 0;while (true) {// 获取采集的数据包大小res = capture->GetNextPacketSize(&captureSize);if (FAILED(res)) {if (res != AUDCLNT_E_DEVICE_INVALIDATED)blog(LOG_WARNING,"[WASAPISource::GetCaptureData]"" capture->GetNextPacketSize"" failed: %lX",res);return false;}if (!captureSize)break;//获取音频数据res = capture->GetBuffer(&buffer, &frames, &flags, &pos, &ts);if (FAILED(res)) {if (res != AUDCLNT_E_DEVICE_INVALIDATED)blog(LOG_WARNING,"[WASAPISource::GetCaptureData]"" capture->GetBuffer"" failed: %lX",res);return false;}obs_source_audio data = {};data.data[0] = (const uint8_t *)buffer;data.frames = (uint32_t)frames;data.speakers = speakers;data.samples_per_sec = sampleRate;data.format = format;data.timestamp = useDeviceTiming ? ts * 100 : os_gettime_ns();if (!useDeviceTiming)data.timestamp -= util_mul_div64(frames, 1000000000ULL,sampleRate);//音频数据处理obs_source_output_audio(source, &data);capture->ReleaseBuffer(frames);}return true;
}

 audio_thread

 

 

 

 

static void input_and_output(struct audio_output *audio, uint64_t audio_time,uint64_t prev_time)
{size_t bytes = AUDIO_OUTPUT_FRAMES * audio->block_size;struct audio_output_data data[MAX_AUDIO_MIXES];uint32_t active_mixes = 0;uint64_t new_ts = 0;bool success;memset(data, 0, sizeof(data));#ifdef DEBUG_AUDIOblog(LOG_DEBUG, "audio_time: %llu, prev_time: %llu, bytes: %lu",audio_time, prev_time, bytes);
#endif/* get mixers */pthread_mutex_lock(&audio->input_mutex);for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) {if (audio->mixes[i].inputs.num)active_mixes |= (1 << i);}pthread_mutex_unlock(&audio->input_mutex);/* clear mix buffers */for (size_t mix_idx = 0; mix_idx < MAX_AUDIO_MIXES; mix_idx++) {struct audio_mix *mix = &audio->mixes[mix_idx];memset(mix->buffer, 0, sizeof(mix->buffer));for (size_t i = 0; i < audio->planes; i++)data[mix_idx].data[i] = mix->buffer[i];}/* get new audio data */success = audio->input_cb(audio->input_param, prev_time, audio_time,&new_ts, active_mixes, data);if (!success)return;/* clamps audio data to -1.0..1.0 */clamp_audio_output(audio, bytes);/* output */for (size_t i = 0; i < MAX_AUDIO_MIXES; i++)do_audio_output(audio, i, new_ts, AUDIO_OUTPUT_FRAMES);
}

最后调用到do_audio_output

static const char *receive_audio_name = "receive_audio";
static void receive_audio(void *param, size_t mix_idx, struct audio_data *in)
{profile_start(receive_audio_name);struct obs_encoder *encoder = param;struct audio_data audio = *in;if (!encoder->first_received) {encoder->first_raw_ts = audio.timestamp;encoder->first_received = true;clear_audio(encoder);}if (audio_pause_check(&encoder->pause, &audio, encoder->samplerate))goto end;if (!buffer_audio(encoder, &audio))goto end;while (encoder->audio_input_buffer[0].size >=encoder->framesize_bytes) {if (!send_audio_data(encoder)) {break;}}UNUSED_PARAMETER(mix_idx);end:profile_end(receive_audio_name);
}

 send_audio_data 中进行编码

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LiDAR SLAM 闭环——BoW3D: Bag of Words for Real-time Loop Closing in 3D LiDAR SLAM

先说总结 现算法已经开源&#xff0c;代码链接&#xff1a; GitHub - YungeCui/BoW3D: [RA-L] BoW3D: Bag of Words for Real-Time Loop Closing in 3D LiDAR SLAM. 背景 SLAM&#xff08;同时定位与地图构建&#xff09;是一种让机器人在构建环境地图的同时&#xff0c;定位…

Spring整合Junit单元测试

1.Spring整合Junit单元测试 1.1 原始Junit测试Spring的问题 在测试类中&#xff0c;每个测试方法都有以下两行代码&#xff1a; ApplicationContext ac new ClassPathXmlApplicationContext("application.xml");BookDao bookDao (BookDao)ac.getBean("bookDa…

Angular 调试 —— 一个真实的多重循环导致的Bug

导致性能问题的原因可能很复杂&#xff0c;也可能很简单&#xff0c;今天让我们来看一个现实的例子。一个多重循环导致列表卡死。 startDemo() {this.processing true// 创建复杂数据结构const data [];for (let i 0; i < 5000; i) {const innerArray [];for (let j …

(小程序)uniapp调接口完整流程

(小程序)uniapp调接口完整流程 代码&#xff1a; <script lang"ts" setup>import { ref } from "vue"; const form ref({searchVal: "", });//搜索const searchClick () > {console.log(form.value.searchVal)let data {text: form…

git merge 和git rebase的区别

文章目录 1. 概念2. git merge2.1. 示例 3. git rebase3.1. 示例 4. 总结 1. 概念 在Git版本控制系统中&#xff0c;有两种方式可以将一个分支的更改合并到另一个分支&#xff1a;git merge 和 git rebase。虽然它们都可以完成相同的任务&#xff0c;但它们的实现方式有所不同…

软考:中级软件设计师:计算机存储结构,cache,局部性原理,RAM和ROM,磁盘结构和计算

软考&#xff1a;中级软件设计师:计算机存储结构 提示&#xff1a;系列被面试官问的问题&#xff0c;我自己当时不会&#xff0c;所以下来自己复盘一下&#xff0c;认真学习和总结&#xff0c;以应对未来更多的可能性 关于互联网大厂的笔试面试&#xff0c;都是需要细心准备的…

用git下载gitee上的项目资源

目录 用git下载gitee上的项目资源 用git 的clone 命令 然后到gitee上复制相关的下载地址&#xff1a; 粘贴到clone后面即可&#xff08;注意地址与clone之间有空格&#xff01;&#xff01;&#xff01;&#xff09; 运行结果&#xff1a; 用git下载gitee上的项目资源 用git…

软件设计模式与体系结构-设计模式-行为型软件设计模式-迭代器模式

行为型软件设计模式 概述 行为型设计模式是软件设计模式中的一类&#xff0c;用于处理对象之间的交互和通信。这些模式关注的是对象之间的行为和职责分配。以下是几种常见的行为型设计模式&#xff1a; 观察者模式&#xff08;Observer Pattern&#xff09;&#xff1a;定义了…

【CEEMDAN-VMD-GRU】完备集合经验模态分解-变分模态分解-门控循环单元预测研究(Python代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【随手记】使用Flask做代理为虚拟机提供pip源

最近在重做虚拟机环境&#xff0c;虚拟机不可连外网&#xff0c;最初python包都是通过离线whl进行安装。但是离线文件已经找不到了&#xff0c;不想重新去一个个下载&#xff0c;而且本地环境跟虚拟机环境也不一致&#xff0c;pip download可能会遇到版本问题&#xff0c;遂考虑…

【对象存储】那些事

最近在某个项目中使用了对象存储。以前看过一个新闻&#xff1a;某公司的对象存储被盗刷&#xff0c;一夜之间账户欠费几十万&#xff01;我们这点小买卖可经不起这么折腾&#xff01;所以下功夫研究了下&#xff0c;防患于未然。 说到防盗刷&#xff0c;我们还得了解对象存储是…

前端实战——尚品汇(网页开发)

/* 基础设置 */ .container {width: 1190px;margin: 0 auto; } /* #region顶部导航条start */ .topbar {height: 30px;background-color: #ececec; } .welcome {height: 30px;line-height: 30px;font-size: 0;color: #666; } .welcome span,.welcome a {font-size: 12px; } .we…