实现一个简单的录制软件:支持录制桌面与窗口_win32 指定窗口录屏-CSDN博客
在这上面接着开发, 上面的工程中我们可以录制捕获源,但是无法在Ui层像obs一样预览:
QT 自定义渲染与OBS中的实现-CSDN博客
obs中源的预览使用的是OBSQTDisplay类,该类会创建一个native的窗口,然后将材质渲染在该窗口中,因此性能很高,详细可参见上面的文章。
obs的UI层逻辑很复杂,我们可以使用obs的c库,自定义UI层,将捕捉源渲染在UI上。
使用到的obs中的文件有qt-display.hpp qt-display.cpp display-helpers.hpp 在编译过程中有些报错,因此删除了不必要的功能。
#pragma once
#include
#include
#define GREY_COLOR_BACKGROUND 0xFF4C4C4C
class OBSQTDisplay : public QWidget {
Q_OBJECT
Q_PROPERTY(QColor displayBackgroundColor MEMBER backgroundColor READ
GetDisplayBackgroundColor WRITE
SetDisplayBackgroundColor)
OBSDisplay display;
void resizeEvent(QResizeEvent* event) override;
void paintEvent(QPaintEvent* event) override;
signals:
void DisplayCreated(OBSQTDisplay* window);
void DisplayResized();
public:
OBSQTDisplay(QWidget* parent = nullptr,
Qt::WindowFlags flags = Qt::WindowFlags());
~OBSQTDisplay() { display = nullptr; }
virtual QPaintEngine* paintEngine() const override;
inline obs_display_t* GetDisplay() const { return display; }
uint32_t backgroundColor = GREY_COLOR_BACKGROUND;
QColor GetDisplayBackgroundColor() const;
void SetDisplayBackgroundColor(const QColor& color);
void UpdateDisplayBackgroundColor();
void CreateDisplay(bool force = false);
};
#include "qt-display.hpp"
#include "display-helpers.hpp"
#include
#include
#include
#include
#include
namespace {
bool QTToGSWindow(QWindow* window, gs_window& gswindow) {
bool success = true;
#ifdef _WIN32
gswindow.hwnd = (HWND)window->winId();
#elif __APPLE__
gswindow.view = (id)window->winId();
#else
switch (obs_get_nix_platform()) {
case OBS_NIX_PLATFORM_X11_GLX:
case OBS_NIX_PLATFORM_X11_EGL:
gswindow.id = window->winId();
gswindow.display = obs_get_nix_platform_display();
break;
#ifdef ENABLE_WAYLAND
case OBS_NIX_PLATFORM_WAYLAND:
QPlatformNativeInterface* native =
QGuiApplication::platformNativeInterface();
gswindow.display =
native->nativeResourceForWindow("surface", window);
success = gswindow.display != nullptr;
break;
#endif
}
#endif
return success;
}
}
#ifdef ENABLE_WAYLAND
#include
class SurfaceEventFilter : public QObject {
OBSQTDisplay* display;
int mTimerId;
public:
SurfaceEventFilter(OBSQTDisplay* src) : display(src), mTimerId(0) {}
protected:
bool eventFilter(QObject* obj, QEvent* event) override {
bool result = QObject::eventFilter(obj, event);
QPlatformSurfaceEvent* surfaceEvent;
switch (event->type()) {
case QEvent::PlatformSurface:
surfaceEvent =
static_cast(event);
if (surfaceEvent->surfaceEventType() !=
QPlatformSurfaceEvent::SurfaceCreated)
return result;
if (display->windowHandle()->isExposed())
createOBSDisplay();
else
mTimerId = startTimer(67); // Arbitrary
break;
case QEvent::Expose:
createOBSDisplay();
break;
default:
break;
}
return result;
}
void timerEvent(QTimerEvent*) { createOBSDisplay(true); }
private:
void createOBSDisplay(bool force = false) {
display->CreateDisplay(force);
if (mTimerId > 0) {
killTimer(mTimerId);
mTimerId = 0;
}
}
};
#endif
static inline long long color_to_int(const QColor& color) {
auto shift = [&](unsigned val, int shift) {
return ((val & 0xff) << shift);
};
return shift(color.red(), 0) | shift(color.green(), 8) |
shift(color.blue(), 16) | shift(color.alpha(), 24);
}
static inline QColor rgba_to_color(uint32_t rgba) {
return QColor::fromRgb(rgba & 0xFF, (rgba >> 8) & 0xFF,
(rgba >> 16) & 0xFF, (rgba >> 24) & 0xFF);
}
OBSQTDisplay::OBSQTDisplay(QWidget* parent, Qt::WindowFlags flags)
: QWidget(parent, flags) {
setAttribute(Qt::WA_PaintOnScreen);
setAttribute(Qt::WA_StaticContents);
setAttribute(Qt::WA_NoSystemBackground);
setAttribute(Qt::WA_OpaquePaintEvent);
setAttribute(Qt::WA_DontCreateNativeAncestors);
setAttribute(Qt::WA_NativeWindow);
auto windowVisible = [this](bool visible) {
if (!visible) {
#ifdef ENABLE_WAYLAND
if (obs_get_nix_platform() == OBS_NIX_PLATFORM_WAYLAND)
display = nullptr;
#endif
return;
}
if (!display) {
CreateDisplay();
} else {
QSize size = GetPixelSize(this);
obs_display_resize(display, size.width(),
size.height());
}
};
auto screenChanged = [this](QScreen*) {
CreateDisplay();
QSize size = GetPixelSize(this);
obs_display_resize(display, size.width(), size.height());
};
connect(windowHandle(), &QWindow::visibleChanged, windowVisible);
connect(windowHandle(), &QWindow::screenChanged, screenChanged);
#ifdef ENABLE_WAYLAND
if (obs_get_nix_platform() == OBS_NIX_PLATFORM_WAYLAND)
windowHandle()->installEventFilter(
new SurfaceEventFilter(this));
#endif
}
QColor OBSQTDisplay::GetDisplayBackgroundColor() const {
return rgba_to_color(backgroundColor);
}
void OBSQTDisplay::SetDisplayBackgroundColor(const QColor& color) {
uint32_t newBackgroundColor = (uint32_t)color_to_int(color);
if (newBackgroundColor != backgroundColor) {
backgroundColor = newBackgroundColor;
UpdateDisplayBackgroundColor();
}
}
void OBSQTDisplay::UpdateDisplayBackgroundColor() {
obs_display_set_background_color(display, backgroundColor);
}
void OBSQTDisplay::CreateDisplay(bool force) {
if (display)
return;
if (!windowHandle()->isExposed() && !force)
return;
QSize size = GetPixelSize(this);
gs_init_data info = {};
info.cx = size.width();
info.cy = size.height();
info.format = GS_BGRA;
info.zsformat = GS_ZS_NONE;
if (!QTToGSWindow(windowHandle(), info.window))
return;
display = obs_display_create(&info, backgroundColor);
emit DisplayCreated(this);
}
void OBSQTDisplay::resizeEvent(QResizeEvent* event) {
QWidget::resizeEvent(event);
CreateDisplay();
if (isVisible() && display) {
QSize size = GetPixelSize(this);
obs_display_resize(display, size.width(), size.height());
}
emit DisplayResized();
}
void OBSQTDisplay::paintEvent(QPaintEvent* event) {
CreateDisplay();
QWidget::paintEvent(event);
}
QPaintEngine* OBSQTDisplay::paintEngine() const {
return nullptr;
}
display-helpers.hpp
/******************************************************************************
Copyright (C) 2014 by Hugh Bailey
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
******************************************************************************/
#pragma once
#include
#include
static inline void GetScaleAndCenterPos(int baseCX, int baseCY, int windowCX,
int windowCY, int &x, int &y,
float &scale)
{
double windowAspect, baseAspect;
int newCX, newCY;
windowAspect = double(windowCX) / double(windowCY);
baseAspect = double(baseCX) / double(baseCY);
if (windowAspect > baseAspect) {
scale = float(windowCY) / float(baseCY);
newCX = int(double(windowCY) * baseAspect);
newCY = windowCY;
} else {
scale = float(windowCX) / float(baseCX);
newCX = windowCX;
newCY = int(float(windowCX) / baseAspect);
}
x = windowCX / 2 - newCX / 2;
y = windowCY / 2 - newCY / 2;
}
static inline void GetCenterPosFromFixedScale(int baseCX, int baseCY,
int windowCX, int windowCY,
int &x, int &y, float scale)
{
x = (float(windowCX) - float(baseCX) * scale) / 2.0f;
y = (float(windowCY) - float(baseCY) * scale) / 2.0f;
}
static inline QSize GetPixelSize(QWidget *widget)
{
return widget->size() * widget->devicePixelRatioF();
}
#define OUTLINE_COLOR 0xFFD0D0D0
#define LINE_LENGTH 0.1f
// Rec. ITU-R BT.1848-1 / EBU R 95
#define ACTION_SAFE_PERCENT 0.035f // 3.5%
#define GRAPHICS_SAFE_PERCENT 0.05f // 5.0%
#define FOURBYTHREE_SAFE_PERCENT 0.1625f // 16.25%
static inline void InitSafeAreas(gs_vertbuffer_t **actionSafeMargin,
gs_vertbuffer_t **graphicsSafeMargin,
gs_vertbuffer_t **fourByThreeSafeMargin,
gs_vertbuffer_t **leftLine,
gs_vertbuffer_t **topLine,
gs_vertbuffer_t **rightLine)
{
obs_enter_graphics();
// All essential action should be placed inside this area
gs_render_start(true);
gs_vertex2f(ACTION_SAFE_PERCENT, ACTION_SAFE_PERCENT);
gs_vertex2f(ACTION_SAFE_PERCENT, 1 - ACTION_SAFE_PERCENT);
gs_vertex2f(1 - ACTION_SAFE_PERCENT, 1 - ACTION_SAFE_PERCENT);
gs_vertex2f(1 - ACTION_SAFE_PERCENT, ACTION_SAFE_PERCENT);
gs_vertex2f(ACTION_SAFE_PERCENT, ACTION_SAFE_PERCENT);
*actionSafeMargin = gs_render_save();
// All graphics should be placed inside this area
gs_render_start(true);
gs_vertex2f(GRAPHICS_SAFE_PERCENT, GRAPHICS_SAFE_PERCENT);
gs_vertex2f(GRAPHICS_SAFE_PERCENT, 1 - GRAPHICS_SAFE_PERCENT);
gs_vertex2f(1 - GRAPHICS_SAFE_PERCENT, 1 - GRAPHICS_SAFE_PERCENT);
gs_vertex2f(1 - GRAPHICS_SAFE_PERCENT, GRAPHICS_SAFE_PERCENT);
gs_vertex2f(GRAPHICS_SAFE_PERCENT, GRAPHICS_SAFE_PERCENT);
*graphicsSafeMargin = gs_render_save();
// 4:3 safe area for widescreen
gs_render_start(true);
gs_vertex2f(FOURBYTHREE_SAFE_PERCENT, GRAPHICS_SAFE_PERCENT);
gs_vertex2f(1 - FOURBYTHREE_SAFE_PERCENT, GRAPHICS_SAFE_PERCENT);
gs_vertex2f(1 - FOURBYTHREE_SAFE_PERCENT, 1 - GRAPHICS_SAFE_PERCENT);
gs_vertex2f(FOURBYTHREE_SAFE_PERCENT, 1 - GRAPHICS_SAFE_PERCENT);
gs_vertex2f(FOURBYTHREE_SAFE_PERCENT, GRAPHICS_SAFE_PERCENT);
*fourByThreeSafeMargin = gs_render_save();
gs_render_start(true);
gs_vertex2f(0.0f, 0.5f);
gs_vertex2f(LINE_LENGTH, 0.5f);
*leftLine = gs_render_save();
gs_render_start(true);
gs_vertex2f(0.5f, 0.0f);
gs_vertex2f(0.5f, LINE_LENGTH);
*topLine = gs_render_save();
gs_render_start(true);
gs_vertex2f(1.0f, 0.5f);
gs_vertex2f(1 - LINE_LENGTH, 0.5f);
*rightLine = gs_render_save();
obs_leave_graphics();
}
static inline void RenderSafeAreas(gs_vertbuffer_t *vb, int cx, int cy)
{
if (!vb)
return;
matrix4 transform;
matrix4_identity(&transform);
transform.x.x = cx;
transform.y.y = cy;
gs_load_vertexbuffer(vb);
gs_matrix_push();
gs_matrix_mul(&transform);
gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID);
gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color");
gs_effect_set_color(color, OUTLINE_COLOR);
while (gs_effect_loop(solid, "Solid"))
gs_draw(GS_LINESTRIP, 0, 0);
gs_matrix_pop();
}
然后新建了一个preview.h preview.cc ,Preview 类继承OBSQTDisplay,做具体的渲染功能。
如下:
#pragma once
#include "qt-display.hpp"
class Preview : public OBSQTDisplay {
Q_OBJECT
public:
Preview(QWidget* parent, Qt::WindowFlags flags = 0);
~Preview();
void DrawSource(obs_source_t* source, uint32_t cx, uint32_t cy);
private:
vec4* back_ground_color_;
};
#include "preview.h"
#include "display-helpers.hpp"
Preview::Preview(QWidget* parent, Qt::WindowFlags flags)
: OBSQTDisplay(parent, flags) {
setMouseTracking(true);
back_ground_color_ = reinterpret_cast(bmalloc(sizeof(vec4)));
vec4_set(back_ground_color_, 0x29 / 255.f, 0x29 / 255.f, 0x36 / 255.f, 1.0f);
}
Preview::~Preview() {
}
void Preview::DrawSource(obs_source_t* source, uint32_t cx, uint32_t cy) {
if (!source)
return;
gs_clear(GS_CLEAR_COLOR, back_ground_color_, 0, 0);
uint32_t sourceCX = obs_source_get_width(source);
if (sourceCX < 1u)
sourceCX = 1u;
uint32_t sourceCY = obs_source_get_height(source);
if (sourceCY < 1u)
sourceCY = 1u;
int x, y;
int newCX, newCY;
float scale;
GetScaleAndCenterPos(sourceCX, sourceCY, cx, cy, x, y, scale);
newCX = int(scale * float(sourceCX));
newCY = int(scale * float(sourceCY));
gs_viewport_push();
gs_projection_push();
gs_ortho(0.0f, float(sourceCX), 0.0f, float(sourceCY),
-100.0f, 100.0f);
gs_set_viewport(x, y, newCX, newCY);
obs_source_video_render(source);
gs_projection_pop();
gs_viewport_pop();
}
然后再UI层:
该块布局控件用于预览捕捉源;
同时添加了两个小功能 仅仅录制捕获源 与 添加图片源
obs_set_output_source 这个方法把源对象设置到某个输出通道,如果将当前scene设置到output 则会录制整个场景中的画面,如果仅仅设置某个源则会录制某个源
void obs_set_output_source(uint32_t channel, obs_source_t *source)
Sets the primary output source for a channel.
void DesktopRec::OnChangeOutputSource() {
///如果设置capture_source_ 则仅仅录制该源
obs_set_output_source(0, obs->GetCurCaptureSource());
}
//添加一个图片源
std::string file_path = qApp->applicationDirPath().toStdString() + "\\..\\DesktopRec\\image.png";
obs_data_t* bk_setting = obs_data_create();
obs_data_set_string(bk_setting, "file", file_path.c_str());
obs_source_t* image_source = obs_source_create("image_source", file_path.c_str(), bk_setting, nullptr);
auto content_sceneitem = obs_scene_add(obs->GetCurScene(), image_source);
obs_transform_info trans_info{};
vec2_set(&trans_info.pos, this->width() - 300, 0);
trans_info.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP;
vec2_set(&trans_info.bounds, 200, 200);
trans_info.bounds_type = OBS_BOUNDS_STRETCH;
vec2_set(&trans_info.scale, 1.f, 1.f);
trans_info.bounds_alignment = OBS_ALIGN_CENTER;
obs_sceneitem_set_info(content_sceneitem, &trans_info);
obs_sceneitem_set_visible(content_sceneitem, true);
效果图: