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