OBS 使用OBSQTDisplay渲染捕捉源

实现一个简单的录制软件:支持录制桌面与窗口_win32 指定窗口录屏-CSDN博客

在这上面接着开发, 上面的工程中我们可以录制捕获源,但是无法在Ui层像obs一样预览:

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 使用OBSQTDisplay渲染捕捉源_第1张图片

该块布局控件用于预览捕捉源;

        同时添加了两个小功能 仅仅录制捕获源 与 添加图片源  

obs_set_output_source

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

效果图:

OBS 使用OBSQTDisplay渲染捕捉源_第2张图片

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