OBS Basic窗口分析

 

1 概覽

參照OBSBasic.ui

 

 

OBS窗口爲一個QMainWindow 窗口

由中央widget  菜單欄 狀態欄 還有五個dock窗口組成

 

 

在 ui_OBSBasic.h中,可以看到

 

centralwidget :

        centralwidget = new QWidget(OBSBasic);
        centralwidget->setObjectName(QStringLiteral("centralwidget"));

        OBSBasic->setCentralWidget(centralwidget);

 

以centralWidget爲parent的

        previewDisabledLabel = new QLabel(centralwidget);
        previewDisabledLabel->setObjectName(QStringLiteral("previewDisabledLabel"));

 

        preview = new OBSBasicPreview(centralwidget);
        preview->setObjectName(QStringLiteral("preview"));

 

        verticalLayout = new QVBoxLayout(centralwidget);
        verticalLayout->setObjectName(QStringLiteral("verticalLayout"));

 

其中,垂直佈局中,包含一個水平佈局

        horizontalLayout_2 = new QHBoxLayout();
        horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2"));

         verticalLayout->addLayout(horizontalLayout_2);

在這個水平佈局中,包含着 禁用預覽時展現的label, 啓用預覽時的 預覽視圖,以及preivewLayout (暫沒找到明確用途)

   

         previewDisabledLabel

        previewDisabledLabel = new QLabel(centralwidget);
        previewDisabledLabel->setObjectName(QStringLiteral("previewDisabledLabel"));
        QSizePolicy sizePolicy1(QSizePolicy::Expanding, QSizePolicy::Expanding);
        sizePolicy1.setHorizontalStretch(0);
        sizePolicy1.setVerticalStretch(0);
        sizePolicy1.setHeightForWidth(previewDisabledLabel->sizePolicy().hasHeightForWidth());
        previewDisabledLabel->setSizePolicy(sizePolicy1);
        previewDisabledLabel->setContextMenuPolicy(Qt::CustomContextMenu);
        previewDisabledLabel->setAlignment(Qt::AlignCenter);

        horizontalLayout_2->addWidget(previewDisabledLabel);

 

        preview

        preview = new OBSBasicPreview(centralwidget);
        preview->setObjectName(QStringLiteral("preview"));
        sizePolicy1.setHeightForWidth(preview->sizePolicy().hasHeightForWidth());
        preview->setSizePolicy(sizePolicy1);
        preview->setMinimumSize(QSize(32, 32));
        preview->setFocusPolicy(Qt::ClickFocus);
        preview->setContextMenuPolicy(Qt::CustomContextMenu);

        horizontalLayout_2->addWidget(preview);

       previewLayout

        previewLayout = new QHBoxLayout();
        previewLayout->setSpacing(2);
        previewLayout->setObjectName(QStringLiteral("previewLayout"));
        previewTextLayout = new QVBoxLayout();
        previewTextLayout->setSpacing(4);
        previewTextLayout->setObjectName(QStringLiteral("previewTextLayout"));

        previewLayout->addLayout(previewTextLayout);


        horizontalLayout_2->addLayout(previewLayout);

 

禁止預覽時的,LABEL窗口

 

2預覽窗口

最主要的窗口,預覽窗口

 

我們最關心,最主要的窗口,便是預覽窗口。

預覽窗口對應的是 OBSBasicPreview : public OBSQTDisplay

 

 

 

這個黑色的場景區域是重中之重,各種源的展示都是在這裏,

這部分是怎麼渲染的呢,

在void OBSBasic::OBSInit() 初始化中,有這麼一段語句

	auto addDisplay = [this] (OBSQTDisplay *window)
	{
		obs_display_add_draw_callback(window->GetDisplay(),
				OBSBasic::RenderMain, this);

		struct obs_video_info ovi;
		if (obs_get_video_info(&ovi))
			ResizePreview(ovi.base_width, ovi.base_height);
	};

	connect(ui->preview, &OBSQTDisplay::DisplayCreated, addDisplay);

 

追蹤下DisplayCreated信號

class OBSQTDisplay : public QWidget {
	Q_OBJECT

	OBSDisplay display;

	void CreateDisplay();

	void resizeEvent(QResizeEvent *event) override;
	void paintEvent(QPaintEvent *event) override;

signals:
	void DisplayCreated(OBSQTDisplay *window);
	void DisplayResized();

 

 

void OBSQTDisplay::CreateDisplay()
{
	if (display || !windowHandle()->isExposed())
		return;

	QSize size = GetPixelSize(this);

	gs_init_data info      = {};
	info.cx                = size.width();
	info.cy                = size.height();
	info.format            = GS_RGBA;
	info.zsformat          = GS_ZS_NONE;

	QTToGSWindow(winId(), info.window);

	display = obs_display_create(&info);

	emit DisplayCreated(this);
}

 

由此可知,當預覽窗口創建時,會發出displayCreated信號,該信號會觸發addDisplay ,在此槽函數中,添加了OBS 渲染函數

/**
 * Adds a draw callback for this display context
 *
 * @param  display  The display context.
 * @param  draw     The draw callback which is called each time a frame
 *                  updates.
 * @param  param    The user data to be associated with this draw callback.
 */
EXPORT void obs_display_add_draw_callback(obs_display_t *display,
		void (*draw)(void *param, uint32_t cx, uint32_t cy),
		void *param);

/** Removes a draw callback for this display context */
EXPORT void obs_display_remove_draw_callback(obs_display_t *display,
		void (*draw)(void *param, uint32_t cx, uint32_t cy),
		void *param);

 

		obs_display_add_draw_callback(window->GetDisplay(),
				OBSBasic::RenderMain, this);

 

OBSBasic::RenderMain,是對預覽窗口進行渲染,更細節的說,是對預覽窗口中的場景部分進行渲染,也就是黑色區域。

 

3 預覽窗口渲染

 

       void OBSBasic::OBSInit() 初始化時

        obs_display_add_draw_callback(window->GetDisplay(),
                OBSBasic::RenderMain, this);       

       因此,  this指的是OBSBasic主窗口

 

void OBSBasic::RenderMain(void *data, uint32_t cx, uint32_t cy)
{
	OBSBasic *window = static_cast<OBSBasic*>(data);
	obs_video_info ovi;

	obs_get_video_info(&ovi);

	window->previewCX = int(window->previewScale * float(ovi.base_width));
	window->previewCY = int(window->previewScale * float(ovi.base_height));

	gs_viewport_push();
	gs_projection_push();

	/* --------------------------------------- */

	gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height),
			-100.0f, 100.0f);
	gs_set_viewport(window->previewX, window->previewY,
			window->previewCX, window->previewCY);

	window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height));

	if (window->IsPreviewProgramMode()) {
		OBSScene scene = window->GetCurrentScene();
		obs_source_t *source = obs_scene_get_source(scene);
		if (source)
			obs_source_video_render(source);
	} else {
		obs_render_main_view();
	}
	gs_load_vertexbuffer(nullptr);

	/* --------------------------------------- */

	QSize previewSize = GetPixelSize(window->ui->preview);
	float right  = float(previewSize.width())  - window->previewX;
	float bottom = float(previewSize.height()) - window->previewY;

	gs_ortho(-window->previewX, right,
	         -window->previewY, bottom,
	         -100.0f, 100.0f);
	gs_reset_viewport();

	window->ui->preview->DrawSceneEditing();

	/* --------------------------------------- */

	gs_projection_pop();
	gs_viewport_pop();

	UNUSED_PARAMETER(cx);
	UNUSED_PARAMETER(cy);
}

 

下面,分析下這個渲染函數:

1) 

    obs_video_info ovi;
    obs_get_video_info(&ovi);

    獲得視頻信息,主要是要知道視頻的寬高分辨率信息

    也就是ovi.base_width   

               ovi.base_height

 

 2)獲得預覽窗口中,黑色視頻區域的寬高

           window->previewCX = int(window->previewScale * float(ovi.base_width));
           window->previewCY = int(window->previewScale * float(ovi.base_height));

 

3)gs渲染階段 

    gs_viewport_push();
    gs_projection_push();

    之間部分 爲GS具體渲染


    gs_projection_pop();
    gs_viewport_pop();

 

4)設置攝像頭可見區域, 視口區域(視頻顯示區域)

    gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height),
            -100.0f, 100.0f);
    gs_set_viewport(window->previewX, window->previewY,
            window->previewCX, window->previewCY);

 

5)  

 window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height));

 繪製視頻區域,默認黑色背景

void OBSBasic::DrawBackdrop(float cx, float cy)
{
	if (!box)
		return;

	gs_effect_t    *solid = obs_get_base_effect(OBS_EFFECT_SOLID);
	gs_eparam_t    *color = gs_effect_get_param_by_name(solid, "color");
	gs_technique_t *tech  = gs_effect_get_technique(solid, "Solid");

	vec4 colorVal;
	vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f);
	gs_effect_set_vec4(color, &colorVal);

	gs_technique_begin(tech);
	gs_technique_begin_pass(tech, 0);
	gs_matrix_push();
	gs_matrix_identity();
	gs_matrix_scale3f(float(cx), float(cy), 1.0f);

	gs_load_vertexbuffer(box);
	gs_draw(GS_TRISTRIP, 0, 0);

	gs_matrix_pop();
	gs_technique_end_pass(tech);
	gs_technique_end(tech);

	gs_load_vertexbuffer(nullptr);
}

 其中, vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f),設置渲染顏色,而 gs繪製,用到了頂點數組

 

    gs_load_vertexbuffer(box);
    gs_draw(GS_TRISTRIP, 0, 0);

   

    看看這個頂點數組是怎麼來的,

    gs_vertbuffer_t *box = nullptr;

    在void OBSBasic::InitPrimitives()中,初始化這個數組

   

	gs_vertex2f(0.0f, 0.0f);
	gs_vertex2f(0.0f, 1.0f);
	gs_vertex2f(1.0f, 1.0f);
	gs_vertex2f(1.0f, 0.0f);
	gs_vertex2f(0.0f, 0.0f);
	box = gs_render_save();

   然後以GS_TRISTRIP的方式 繪製兩個三角形組成四邊形,也就是繪製黑色視頻區域

 

6) 工作室模式 一般模式的渲染

    if (window->IsPreviewProgramMode()) {
        OBSScene scene = window->GetCurrentScene();
        obs_source_t *source = obs_scene_get_source(scene);
        if (source)
            obs_source_video_render(source);
    } else {
        obs_render_main_view();
    }

  

   工作室模式渲染

    obs_source_t *source = obs_scene_get_source(scene);

    obs_source_video_render(source);

void obs_source_video_render(obs_source_t *source)
{
	if (!obs_source_valid(source, "obs_source_video_render"))
		return;

	obs_source_addref(source);
	render_video(source);
	obs_source_release(source);
}


/** Renders a video source. */
EXPORT void obs_source_video_render(obs_source_t *source);

一般模式渲染

obs_render_main_view();

void obs_render_main_view(void)
{
	if (!obs) return;
	obs_view_render(&obs->data.main_view);
}

/** Renders the sources of this view context */
EXPORT void obs_view_render(obs_view_t *view);

7) 重置視口

    gs_load_vertexbuffer(nullptr);

    /* --------------------------------------- */

    QSize previewSize = GetPixelSize(window->ui->preview);
    float right  = float(previewSize.width())  - window->previewX;
    float bottom = float(previewSize.height()) - window->previewY;

    gs_ortho(-window->previewX, right,
             -window->previewY, bottom,
             -100.0f, 100.0f);
    gs_reset_viewport();

    window->ui->preview->DrawSceneEditing();

    /* --------------------------------------- */

   其中 QSize previewSize = GetPixelSize(window->ui->preview); 保存着預覽窗口的寬高信息

 

4 渲染窗口中涉及的重要參數

 

	int           previewX = 0,  previewY = 0;
	int           previewCX = 0, previewCY = 0;
	float         previewScale = 0.0f;

 

 這幾個參數的含義如下

 

 

而 previewScale 比例呢,指的是 preview的寬高與視頻輸出(1280 720)的比例

比如:

obs_video_info ovi;

obs_get_video_info(&ovi);  

得到視頻信息 ,比如 寬1280  高720

 

當預覽視圖更寬時,此時黑色視頻區域上下充滿

       此時的 previewScale 爲預覽窗口的高度與視頻高度之比

當預覽視圖更高時,此時黑色視頻區域左右充滿,

       因此,此時的previewScale 爲 預覽窗口的寬度與視頻寬度之比

 

 

這些值是在哪統計的呢?

在OBS窗口resize時,會重新計算更新這些值

void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy)
{
	QSize  targetSize;
	bool isFixedScaling;
	obs_video_info ovi;

	/* resize preview panel to fix to the top section of the window */
	targetSize = GetPixelSize(ui->preview);

	isFixedScaling = ui->preview->IsFixedScaling();
	obs_get_video_info(&ovi);

	if (isFixedScaling) {
		previewScale = ui->preview->GetScalingAmount();
		GetCenterPosFromFixedScale(int(cx), int(cy),
				targetSize.width() - PREVIEW_EDGE_SIZE * 2,
				targetSize.height() - PREVIEW_EDGE_SIZE * 2,
				previewX, previewY, previewScale);
		previewX += ui->preview->GetScrollX();
		previewY += ui->preview->GetScrollY();

	} else {
		GetScaleAndCenterPos(int(cx), int(cy),
				targetSize.width() - PREVIEW_EDGE_SIZE * 2,
				targetSize.height() - PREVIEW_EDGE_SIZE * 2,
				previewX, previewY, previewScale);
	}

	previewX += float(PREVIEW_EDGE_SIZE);
	previewY += float(PREVIEW_EDGE_SIZE);

 

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

  這些參數的使用

  之所以介紹這些參數,是因爲OBS源中的座標是以視頻左上角爲座標系的,

  所以需要將preview視圖中的座標,轉換爲OBS視頻中的座標,這些轉換需要這些參數輔助

 

    inline void GetDisplayRect(int &x, int &y, int &cx, int &cy)
    {
        x  = previewX;
        y  = previewY;
        cx = previewCX;
        cy = previewCY;
    }

  

 將prewive下的座標轉換爲OBS源座標

 


vec2 OBSBasicPreview::GetMouseEventPos(QMouseEvent *event)
{
	OBSBasic *main = reinterpret_cast<OBSBasic*>(App()->GetMainWindow());
	float pixelRatio = main->devicePixelRatio();
	float scale = pixelRatio / main->previewScale;
	vec2 pos;
	vec2_set(&pos,
		(float(event->x()) - main->previewX / pixelRatio) * scale,
		(float(event->y()) - main->previewY / pixelRatio) * scale);

	return pos;
}


比如,在釋放鼠標事件中,選中 相應的源

 

void OBSBasicPreview::mouseReleaseEvent(QMouseEvent *event)
{
	if (scrollMode)
		setCursor(Qt::OpenHandCursor);

	if (locked) {
		OBSQTDisplay::mouseReleaseEvent(event);
		return;
	}

	if (mouseDown) {
		vec2 pos = GetMouseEventPos(event);

		if (!mouseMoved)
			ProcessClick(pos);

 

//選中該位置處的源

void OBSBasicPreview::ProcessClick(const vec2 &pos)
{
	Qt::KeyboardModifiers modifiers = QGuiApplication::keyboardModifiers();

	if (modifiers & Qt::ControlModifier)
		DoCtrlSelect(pos);
	else
		DoSelect(pos);
}

 

    OBSSceneItem item  = GetItemAtPos(pos, true); 獲得POS處的場景項

void OBSBasicPreview::DoSelect(const vec2 &pos)
{
	OBSBasic *main = reinterpret_cast<OBSBasic*>(App()->GetMainWindow());

	OBSScene     scene = main->GetCurrentScene();
	OBSSceneItem item  = GetItemAtPos(pos, true);

	obs_scene_enum_items(scene, select_one, (obs_sceneitem_t*)item);
}

 

OBSSceneItem OBSBasicPreview::GetItemAtPos(const vec2 &pos, bool selectBelow)
{
	OBSBasic *main = reinterpret_cast<OBSBasic*>(App()->GetMainWindow());

	OBSScene scene = main->GetCurrentScene();
	if (!scene)
		return OBSSceneItem();

	SceneFindData data(pos, selectBelow);
	obs_scene_enum_items(scene, FindItemAtPos, &data);
	return data.item;
}

 

static bool FindItemAtPos(obs_scene_t *scene, obs_sceneitem_t *item,
		void *param)
{
	SceneFindData *data = reinterpret_cast<SceneFindData*>(param);
	matrix4       transform;
	matrix4       invTransform;
	vec3          transformedPos;
	vec3          pos3;
	vec3          pos3_;

	if (!SceneItemHasVideo(item))
		return true;
	if (obs_sceneitem_locked(item))
		return true;

	vec3_set(&pos3, data->pos.x, data->pos.y, 0.0f);

	obs_sceneitem_get_box_transform(item, &transform);

	matrix4_inv(&invTransform, &transform);
	vec3_transform(&transformedPos, &pos3, &invTransform);
	vec3_transform(&pos3_, &transformedPos, &transform);

	if (CloseFloat(pos3.x, pos3_.x) && CloseFloat(pos3.y, pos3_.y) &&
	    transformedPos.x >= 0.0f && transformedPos.x <= 1.0f &&
	    transformedPos.y >= 0.0f && transformedPos.y <= 1.0f) {
		if (data->selectBelow && obs_sceneitem_selected(item)) {
			if (data->item)
				return false;
			else
				data->selectBelow = false;
		}

		data->item = item;
	}

	UNUSED_PARAMETER(scene);
	return true;
}

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章