window-basic-main.cpp 25.7 KB
Newer Older
1
/******************************************************************************
2
    Copyright (C) 2013-2014 by Hugh Bailey <obs.jim@gmail.com>
3
    Copyright (C) 2014 by Zachary Lund <admin@computerquip.com>
4 5 6

    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
7
    the Free Software Foundation, either version 2 of the License, or
8 9 10
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
11
    but WITHOUT ANY WARRANTY; without even the implied warranty of
12 13 14 15 16 17 18
    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 <http://www.gnu.org/licenses/>.
******************************************************************************/

J
jp9000 已提交
19
#include <obs.hpp>
J
jp9000 已提交
20
#include <QMessageBox>
21
#include <QShowEvent>
J
jp9000 已提交
22
#include <QFileDialog>
23

24 25 26
#include <util/util.hpp>
#include <util/platform.h>

27
#include "obs-app.hpp"
28
#include "platform.hpp"
29
#include "window-basic-settings.hpp"
30
#include "window-namedialog.hpp"
J
jp9000 已提交
31
#include "window-basic-main.hpp"
32
#include "window-basic-properties.hpp"
J
jp9000 已提交
33
#include "qt-wrappers.hpp"
34
#include "display-helpers.hpp"
35

J
jp9000 已提交
36
#include "ui_OBSBasic.h"
37

38 39
#include <sstream>

40 41 42
#include <QScreen>
#include <QWindow>

43
using namespace std;
J
jp9000 已提交
44

J
jp9000 已提交
45 46 47
Q_DECLARE_METATYPE(OBSScene);
Q_DECLARE_METATYPE(OBSSceneItem);

48 49
OBSBasic::OBSBasic(QWidget *parent)
	: OBSMainWindow (parent),
50 51
	  streamOutput  (nullptr),
	  service       (nullptr),
52 53
	  aac           (nullptr),
	  x264          (nullptr),
54
	  sceneChanging (false),
55
	  resizeTimer   (0),
56
	  properties    (nullptr),
J
jp9000 已提交
57
	  ui            (new Ui::OBSBasic)
58 59
{
	ui->setupUi(this);
60 61 62 63 64 65 66

	connect(windowHandle(), &QWindow::screenChanged, [this]() {
		struct obs_video_info ovi;

		if (obs_get_video_info(&ovi))
			ResizePreview(ovi.base_width, ovi.base_height);
	});
67 68
}

69 70 71
static inline bool HasAudioDevices(const char *source_id)
{
	const char *output_id = source_id;
72
	obs_properties_t props = obs_get_source_properties(
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
			OBS_SOURCE_TYPE_INPUT, output_id, App()->GetLocale());
	size_t count = 0;

	if (!props)
		return false;

	obs_property_t devices = obs_properties_get(props, "device_id");
	if (devices)
		count = obs_property_list_item_count(devices);

	obs_properties_destroy(props);

	return count != 0;
}

88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
static void OBSStartStreaming(void *data, calldata_t params)
{
	UNUSED_PARAMETER(params);
	QMetaObject::invokeMethod(static_cast<OBSBasic*>(data),
			"StreamingStart");
}

static void OBSStopStreaming(void *data, calldata_t params)
{
	int code = (int)calldata_int(params, "errorcode");
	QMetaObject::invokeMethod(static_cast<OBSBasic*>(data),
			"StreamingStop", Q_ARG(int, code));
}

#define SERVICE_PATH "obs-studio/basic/service.json"

void OBSBasic::SaveService()
{
	if (!service)
		return;

	BPtr<char> serviceJsonPath(os_get_config_path(SERVICE_PATH));
	if (!serviceJsonPath)
		return;

	obs_data_t data     = obs_data_create();
	obs_data_t settings = obs_service_get_settings(service);

	obs_data_setstring(data, "type", obs_service_gettype(service));
	obs_data_setobj(data, "settings", settings);

	const char *json = obs_data_getjson(data);

	os_quick_write_utf8_file(serviceJsonPath, json, strlen(json), false);

	obs_data_release(settings);
	obs_data_release(data);
}

bool OBSBasic::LoadService()
{
	const char *type;

	BPtr<char> serviceJsonPath(os_get_config_path(SERVICE_PATH));
	if (!serviceJsonPath)
		return false;

	BPtr<char> jsonText = os_quick_read_utf8_file(serviceJsonPath);
	if (!jsonText)
		return false;

	obs_data_t data = obs_data_create_from_json(jsonText);

	obs_data_set_default_string(data, "type", "rtmp_common");
	type = obs_data_getstring(data, "type");

	obs_data_t settings = obs_data_getobj(data, "settings");

	service = obs_service_create(type, "default", settings);

	obs_data_release(settings);
	obs_data_release(data);

	return !!service;
}

bool OBSBasic::InitOutputs()
{
	streamOutput = obs_output_create("rtmp_output", "default", nullptr);
	if (!streamOutput)
		return false;

	signal_handler_connect(obs_output_signalhandler(streamOutput),
			"start", OBSStartStreaming, this);
	signal_handler_connect(obs_output_signalhandler(streamOutput),
			"stop", OBSStopStreaming, this);

	return true;
}

bool OBSBasic::InitEncoders()
{
	aac = obs_audio_encoder_create("ffmpeg_aac", "aac", nullptr);
	if (!aac)
		return false;

	x264 = obs_video_encoder_create("obs_x264", "h264", nullptr);
	if (!x264)
		return false;

	return true;
}

bool OBSBasic::InitService()
{
	if (LoadService())
		return true;

	service = obs_service_create("rtmp_common", nullptr, nullptr);
	if (!service)
		return false;

	return true;
}

193 194
bool OBSBasic::InitBasicConfigDefaults()
{
195 196 197
	bool hasDesktopAudio = HasAudioDevices(App()->OutputAudioSource());
	bool hasInputAudio   = HasAudioDevices(App()->InputAudioSource());

198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
	config_set_default_int(basicConfig, "Window", "PosX",  -1);
	config_set_default_int(basicConfig, "Window", "PosY",  -1);
	config_set_default_int(basicConfig, "Window", "SizeX", -1);
	config_set_default_int(basicConfig, "Window", "SizeY", -1);

	vector<MonitorInfo> monitors;
	GetMonitors(monitors);

	if (!monitors.size()) {
		OBSErrorBox(NULL, "There appears to be no monitors.  Er, this "
		                  "technically shouldn't be possible.");
		return false;
	}

	uint32_t cx = monitors[0].cx;
	uint32_t cy = monitors[0].cy;

215
	/* TODO: temporary */
216 217 218 219
	config_set_default_string(basicConfig, "SimpleOutput", "path", "");
	config_set_default_uint  (basicConfig, "SimpleOutput", "VBitrate",
			2500);
	config_set_default_uint  (basicConfig, "SimpleOutput", "ABitrate", 128);
220

221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
	config_set_default_uint  (basicConfig, "Video", "BaseCX",   cx);
	config_set_default_uint  (basicConfig, "Video", "BaseCY",   cy);

	cx = cx * 10 / 15;
	cy = cy * 10 / 15;
	config_set_default_uint  (basicConfig, "Video", "OutputCX", cx);
	config_set_default_uint  (basicConfig, "Video", "OutputCY", cy);

	config_set_default_uint  (basicConfig, "Video", "FPSType", 0);
	config_set_default_string(basicConfig, "Video", "FPSCommon", "30");
	config_set_default_uint  (basicConfig, "Video", "FPSInt", 30);
	config_set_default_uint  (basicConfig, "Video", "FPSNum", 30);
	config_set_default_uint  (basicConfig, "Video", "FPSDen", 1);

	config_set_default_uint  (basicConfig, "Audio", "SampleRate", 44100);
	config_set_default_string(basicConfig, "Audio", "ChannelSetup",
			"Stereo");
	config_set_default_uint  (basicConfig, "Audio", "BufferingTime", 1000);

J
jp9000 已提交
240
	config_set_default_string(basicConfig, "Audio", "DesktopDevice1",
241
			hasDesktopAudio ? "default" : "disabled");
J
jp9000 已提交
242 243 244
	config_set_default_string(basicConfig, "Audio", "DesktopDevice2",
			"disabled");
	config_set_default_string(basicConfig, "Audio", "AuxDevice1",
245
			hasInputAudio ? "default" : "disabled");
J
jp9000 已提交
246 247 248 249 250
	config_set_default_string(basicConfig, "Audio", "AuxDevice2",
			"disabled");
	config_set_default_string(basicConfig, "Audio", "AuxDevice3",
			"disabled");

251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
	return true;
}

bool OBSBasic::InitBasicConfig()
{
	BPtr<char> configPath(os_get_config_path("obs-studio/basic/basic.ini"));

	int errorcode = basicConfig.Open(configPath, CONFIG_OPEN_ALWAYS);
	if (errorcode != CONFIG_SUCCESS) {
		OBSErrorBox(NULL, "Failed to open basic.ini: %d", errorcode);
		return false;
	}

	return InitBasicConfigDefaults();
}

267 268 269 270 271 272 273 274
void OBSBasic::OBSInit()
{
	/* make sure it's fully displayed before doing any initialization */
	show();
	App()->processEvents();

	if (!obs_startup())
		throw "Failed to initialize libobs";
275 276
	if (!InitBasicConfig())
		throw "Failed to load basic.ini";
277 278 279
	if (!ResetVideo())
		throw "Failed to initialize video";
	if (!ResetAudio())
280 281
		throw "Failed to initialize audio";

282
	signal_handler_connect(obs_signalhandler(), "source_add",
283
			OBSBasic::SourceAdded, this);
284
	signal_handler_connect(obs_signalhandler(), "source_remove",
285
			OBSBasic::SourceRemoved, this);
286
	signal_handler_connect(obs_signalhandler(), "channel_change",
287 288
			OBSBasic::ChannelChanged, this);

J
jp9000 已提交
289 290
	/* TODO: this is a test, all modules will be searched for and loaded
	 * automatically later */
291
	obs_load_module("test-input");
292
	obs_load_module("obs-ffmpeg");
293 294
	obs_load_module("obs-x264");
	obs_load_module("obs-outputs");
295
	obs_load_module("rtmp-services");
J
jp9000 已提交
296 297
#ifdef __APPLE__
	obs_load_module("mac-capture");
J
jp9000 已提交
298 299
#elif _WIN32
	obs_load_module("win-wasapi");
J
jp9000 已提交
300
	obs_load_module("win-capture");
301 302 303
#else
	obs_load_module("linux-xshm");
	obs_load_module("linux-pulseaudio");
J
jp9000 已提交
304
#endif
J
jp9000 已提交
305

306 307 308 309 310 311 312
	if (!InitOutputs())
		throw "Failed to initialize outputs";
	if (!InitEncoders())
		throw "Failed to initialize encoders";
	if (!InitService())
		throw "Failed to initialize service";

J
jp9000 已提交
313
	ResetAudioDevices();
314 315 316 317
}

OBSBasic::~OBSBasic()
{
318 319
	SaveService();

320 321 322
	if (properties)
		delete properties;

323 324 325 326 327 328 329
	/* free the lists before shutting down to remove the scene/item
	 * references */
	ui->sources->clear();
	ui->scenes->clear();
	obs_shutdown();
}

J
jp9000 已提交
330
OBSScene OBSBasic::GetCurrentScene()
331
{
J
jp9000 已提交
332
	QListWidgetItem *item = ui->scenes->currentItem();
J
jp9000 已提交
333
	return item ? item->data(Qt::UserRole).value<OBSScene>() : nullptr;
334 335
}

J
jp9000 已提交
336
OBSSceneItem OBSBasic::GetCurrentSceneItem()
J
jp9000 已提交
337 338
{
	QListWidgetItem *item = ui->sources->currentItem();
J
jp9000 已提交
339
	return item ? item->data(Qt::UserRole).value<OBSSceneItem>() : nullptr;
J
jp9000 已提交
340 341
}

342 343 344 345 346 347 348 349
void OBSBasic::UpdateSources(OBSScene scene)
{
	ui->sources->clear();

	obs_scene_enum_items(scene,
			[] (obs_scene_t scene, obs_sceneitem_t item, void *p)
			{
				OBSBasic *window = static_cast<OBSBasic*>(p);
350
				window->InsertSceneItem(item);
J
jp9000 已提交
351 352

				UNUSED_PARAMETER(scene);
353 354 355 356
				return true;
			}, this);
}

357 358 359 360 361 362 363 364 365 366 367 368
void OBSBasic::InsertSceneItem(obs_sceneitem_t item)
{
	obs_source_t source = obs_sceneitem_getsource(item);
	const char   *name  = obs_source_getname(source);

	QListWidgetItem *listItem = new QListWidgetItem(QT_UTF8(name));
	listItem->setData(Qt::UserRole,
			QVariant::fromValue(OBSSceneItem(item)));

	ui->sources->insertItem(0, listItem);
}

369 370 371
/* Qt callbacks for invokeMethod */

void OBSBasic::AddScene(OBSSource source)
372 373 374
{
	const char *name  = obs_source_getname(source);
	obs_scene_t scene = obs_scene_fromsource(source);
J
jp9000 已提交
375 376

	QListWidgetItem *item = new QListWidgetItem(QT_UTF8(name));
J
jp9000 已提交
377
	item->setData(Qt::UserRole, QVariant::fromValue(OBSScene(scene)));
J
jp9000 已提交
378
	ui->scenes->addItem(item);
379 380

	signal_handler_t handler = obs_source_signalhandler(source);
381
	signal_handler_connect(handler, "item_add",
J
jp9000 已提交
382
			OBSBasic::SceneItemAdded, this);
383
	signal_handler_connect(handler, "item_remove",
J
jp9000 已提交
384
			OBSBasic::SceneItemRemoved, this);
385 386
}

387
void OBSBasic::RemoveScene(OBSSource source)
J
jp9000 已提交
388 389 390
{
	const char *name = obs_source_getname(source);

J
jp9000 已提交
391 392 393
	QListWidgetItem *sel = ui->scenes->currentItem();
	QList<QListWidgetItem*> items = ui->scenes->findItems(QT_UTF8(name),
			Qt::MatchExactly);
J
jp9000 已提交
394

J
jp9000 已提交
395 396 397 398
	if (sel != nullptr) {
		if (items.contains(sel))
			ui->sources->clear();
		delete sel;
J
jp9000 已提交
399
	}
400 401
}

402
void OBSBasic::AddSceneItem(OBSSceneItem item)
403
{
J
jp9000 已提交
404
	obs_scene_t  scene  = obs_sceneitem_getscene(item);
405
	obs_source_t source = obs_sceneitem_getsource(item);
J
jp9000 已提交
406

407 408
	if (GetCurrentScene() == scene)
		InsertSceneItem(item);
J
jp9000 已提交
409 410

	sourceSceneRefs[source] = sourceSceneRefs[source] + 1;
411 412
}

413
void OBSBasic::RemoveSceneItem(OBSSceneItem item)
414
{
J
jp9000 已提交
415
	obs_scene_t scene = obs_sceneitem_getscene(item);
416

J
jp9000 已提交
417
	if (GetCurrentScene() == scene) {
B
BtbN 已提交
418
		for (int i = 0; i < ui->sources->count(); i++) {
J
jp9000 已提交
419 420
			QListWidgetItem *listItem = ui->sources->item(i);
			QVariant userData = listItem->data(Qt::UserRole);
J
jp9000 已提交
421

J
jp9000 已提交
422
			if (userData.value<OBSSceneItem>() == item) {
J
jp9000 已提交
423
				delete listItem;
J
jp9000 已提交
424 425
				break;
			}
426 427
		}
	}
J
jp9000 已提交
428 429 430 431 432 433 434 435

	obs_source_t source = obs_sceneitem_getsource(item);

	int scenes = sourceSceneRefs[source] - 1;
	if (scenes == 0) {
		obs_source_remove(source);
		sourceSceneRefs.erase(source);
	}
436 437
}

438
void OBSBasic::UpdateSceneSelection(OBSSource source)
439 440 441 442 443
{
	if (source) {
		obs_source_type type;
		obs_source_gettype(source, &type, NULL);

J
jp9000 已提交
444
		if (type != OBS_SOURCE_TYPE_SCENE)
J
jp9000 已提交
445
			return;
446

J
jp9000 已提交
447 448 449 450 451 452
		obs_scene_t scene = obs_scene_fromsource(source);
		const char *name = obs_source_getname(source);

		QList<QListWidgetItem*> items =
			ui->scenes->findItems(QT_UTF8(name), Qt::MatchExactly);

453 454 455 456 457
		if (items.count()) {
			sceneChanging = true;
			ui->scenes->setCurrentItem(items.first());
			sceneChanging = false;

J
jp9000 已提交
458
			UpdateSources(scene);
459
		}
J
jp9000 已提交
460
	}
461 462 463 464 465 466 467 468 469
}

/* OBS Callbacks */

void OBSBasic::SceneItemAdded(void *data, calldata_t params)
{
	OBSBasic *window = static_cast<OBSBasic*>(data);

	obs_sceneitem_t item = (obs_sceneitem_t)calldata_ptr(params, "item");
J
jp9000 已提交
470

471 472
	QMetaObject::invokeMethod(window, "AddSceneItem",
			Q_ARG(OBSSceneItem, OBSSceneItem(item)));
J
jp9000 已提交
473 474
}

475
void OBSBasic::SceneItemRemoved(void *data, calldata_t params)
476
{
477
	OBSBasic *window = static_cast<OBSBasic*>(data);
478

479 480
	obs_sceneitem_t item = (obs_sceneitem_t)calldata_ptr(params, "item");

481 482
	QMetaObject::invokeMethod(window, "RemoveSceneItem",
			Q_ARG(OBSSceneItem, OBSSceneItem(item)));
483 484 485 486 487
}

void OBSBasic::SourceAdded(void *data, calldata_t params)
{
	obs_source_t source = (obs_source_t)calldata_ptr(params, "source");
488 489 490 491

	obs_source_type type;
	obs_source_gettype(source, &type, NULL);

J
jp9000 已提交
492
	if (type == OBS_SOURCE_TYPE_SCENE)
493 494 495
		QMetaObject::invokeMethod(static_cast<OBSBasic*>(data),
				"AddScene",
				Q_ARG(OBSSource, OBSSource(source)));
496 497
}

J
jp9000 已提交
498
void OBSBasic::SourceRemoved(void *data, calldata_t params)
499
{
500
	obs_source_t source = (obs_source_t)calldata_ptr(params, "source");
501

J
jp9000 已提交
502 503 504
	obs_source_type type;
	obs_source_gettype(source, &type, NULL);

J
jp9000 已提交
505
	if (type == OBS_SOURCE_TYPE_SCENE)
506 507 508
		QMetaObject::invokeMethod(static_cast<OBSBasic*>(data),
				"RemoveScene",
				Q_ARG(OBSSource, OBSSource(source)));
509 510
}

511 512 513
void OBSBasic::ChannelChanged(void *data, calldata_t params)
{
	obs_source_t source = (obs_source_t)calldata_ptr(params, "source");
514
	uint32_t channel = (uint32_t)calldata_int(params, "channel");
515 516

	if (channel == 0)
517 518 519
		QMetaObject::invokeMethod(static_cast<OBSBasic*>(data),
				"UpdateSceneSelection",
				Q_ARG(OBSSource, OBSSource(source)));
520 521
}

522 523
void OBSBasic::RenderMain(void *data, uint32_t cx, uint32_t cy)
{
J
jp9000 已提交
524
	OBSBasic *window = static_cast<OBSBasic*>(data);
525 526 527 528 529 530 531 532 533 534 535 536 537 538
	obs_video_info ovi;
	int newCX, newCY;

	obs_get_video_info(&ovi);

	newCX = int(window->previewScale * float(ovi.base_width));
	newCY = 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_setviewport(window->previewX, window->previewY, newCX, newCY);

J
jp9000 已提交
539
	obs_render_main_view();
540 541 542

	gs_projection_pop();
	gs_viewport_pop();
J
jp9000 已提交
543 544 545

	UNUSED_PARAMETER(cx);
	UNUSED_PARAMETER(cy);
546 547
}

548 549
/* Main class functions */

550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565
obs_service_t OBSBasic::GetService()
{
	if (!service)
		service = obs_service_create("rtmp_common", NULL, NULL);
	return service;
}

void OBSBasic::SetService(obs_service_t newService)
{
	if (newService) {
		if (service)
			obs_service_destroy(service);
		service = newService;
	}
}

566
bool OBSBasic::ResetVideo()
J
jp9000 已提交
567 568
{
	struct obs_video_info ovi;
J
jp9000 已提交
569

570
	GetConfigFPS(ovi.fps_num, ovi.fps_den);
J
jp9000 已提交
571 572

	ovi.graphics_module = App()->GetRenderModule();
573
	ovi.base_width     = (uint32_t)config_get_uint(basicConfig,
J
jp9000 已提交
574
			"Video", "BaseCX");
575
	ovi.base_height    = (uint32_t)config_get_uint(basicConfig,
J
jp9000 已提交
576
			"Video", "BaseCY");
577
	ovi.output_width   = (uint32_t)config_get_uint(basicConfig,
J
jp9000 已提交
578
			"Video", "OutputCX");
579
	ovi.output_height  = (uint32_t)config_get_uint(basicConfig,
J
jp9000 已提交
580
			"Video", "OutputCY");
581
	ovi.output_format  = VIDEO_FORMAT_NV12;
J
jp9000 已提交
582 583
	ovi.adapter        = 0;
	ovi.gpu_conversion = true;
584

J
jp9000 已提交
585
	QTToGSWindow(ui->preview->winId(), ovi.window);
J
jp9000 已提交
586 587

	//required to make opengl display stuff on osx(?)
J
jp9000 已提交
588
	ResizePreview(ovi.base_width, ovi.base_height);
J
jp9000 已提交
589

590
	QSize size = GetPixelSize(ui->preview);
J
jp9000 已提交
591 592
	ovi.window_width  = size.width();
	ovi.window_height = size.height();
J
jp9000 已提交
593

594 595 596 597 598
	if (!obs_reset_video(&ovi))
		return false;

	obs_add_draw_callback(OBSBasic::RenderMain, this);
	return true;
J
jp9000 已提交
599
}
J
jp9000 已提交
600

601
bool OBSBasic::ResetAudio()
J
jp9000 已提交
602
{
J
jp9000 已提交
603
	struct audio_output_info ai;
604 605 606
	ai.name = "Main Audio Track";
	ai.format = AUDIO_FORMAT_FLOAT;

607
	ai.samples_per_sec = config_get_uint(basicConfig, "Audio",
608 609
			"SampleRate");

610
	const char *channelSetupStr = config_get_string(basicConfig,
611 612 613 614 615 616 617
			"Audio", "ChannelSetup");

	if (strcmp(channelSetupStr, "Mono") == 0)
		ai.speakers = SPEAKERS_MONO;
	else
		ai.speakers = SPEAKERS_STEREO;

618
	ai.buffer_ms = config_get_uint(basicConfig, "Audio", "BufferingTime");
J
jp9000 已提交
619 620

	return obs_reset_audio(&ai);
J
jp9000 已提交
621 622
}

J
jp9000 已提交
623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658
void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceName,
		int channel)
{
	const char *deviceId = config_get_string(basicConfig, "Audio",
			deviceName);
	obs_source_t source;
	obs_data_t settings;
	bool same = false;

	source = obs_get_output_source(channel);
	if (source) {
		settings = obs_source_getsettings(source);
		const char *curId = obs_data_getstring(settings, "device_id");

		same = (strcmp(curId, deviceId) == 0);

		obs_data_release(settings);
		obs_source_release(source);
	}

	if (!same)
		obs_set_output_source(channel, nullptr);

	if (!same && strcmp(deviceId, "disabled") != 0) {
		obs_data_t settings = obs_data_create();
		obs_data_setstring(settings, "device_id", deviceId);
		source = obs_source_create(OBS_SOURCE_TYPE_INPUT,
				sourceId, deviceName, settings);
		obs_data_release(settings);

		obs_set_output_source(channel, source);
		obs_source_release(source);
	}
}

void OBSBasic::ResetAudioDevices()
J
jp9000 已提交
659
{
J
jp9000 已提交
660 661 662 663 664
	ResetAudioDevice(App()->OutputAudioSource(), "DesktopDevice1", 1);
	ResetAudioDevice(App()->OutputAudioSource(), "DesktopDevice2", 2);
	ResetAudioDevice(App()->InputAudioSource(),  "AuxDevice1", 3);
	ResetAudioDevice(App()->InputAudioSource(),  "AuxDevice2", 4);
	ResetAudioDevice(App()->InputAudioSource(),  "AuxDevice3", 5);
J
jp9000 已提交
665 666
}

J
jp9000 已提交
667
void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy)
668
{
669
	QSize  targetSize;
J
jp9000 已提交
670

671
	/* resize preview panel to fix to the top section of the window */
672
	targetSize = GetPixelSize(ui->preview);
673 674 675
	GetScaleAndCenterPos(int(cx), int(cy),
			targetSize.width(), targetSize.height(),
			previewX, previewY, previewScale);
J
jp9000 已提交
676

J
jp9000 已提交
677 678 679 680 681
	if (isVisible()) {
		if (resizeTimer)
			killTimer(resizeTimer);
		resizeTimer = startTimer(100);
	}
J
jp9000 已提交
682 683
}

J
jp9000 已提交
684
void OBSBasic::closeEvent(QCloseEvent *event)
J
jp9000 已提交
685
{
686 687 688 689 690 691 692
	QWidget::closeEvent(event);
	if (!event->isAccepted())
		return;

	// remove draw callback in case our drawable surfaces go away before
	// the destructor gets called
	obs_remove_draw_callback(OBSBasic::RenderMain, this);
693 694
}

J
jp9000 已提交
695
void OBSBasic::changeEvent(QEvent *event)
696
{
J
jp9000 已提交
697 698
	/* TODO */
	UNUSED_PARAMETER(event);
699 700
}

J
jp9000 已提交
701
void OBSBasic::resizeEvent(QResizeEvent *event)
702
{
J
jp9000 已提交
703 704 705 706
	struct obs_video_info ovi;

	if (obs_get_video_info(&ovi))
		ResizePreview(ovi.base_width, ovi.base_height);
J
jp9000 已提交
707 708

	UNUSED_PARAMETER(event);
709 710
}

J
jp9000 已提交
711 712 713 714 715 716
void OBSBasic::timerEvent(QTimerEvent *event)
{
	if (event->timerId() == resizeTimer) {
		killTimer(resizeTimer);
		resizeTimer = 0;

717
		QSize size = GetPixelSize(ui->preview);
J
jp9000 已提交
718 719 720 721
		obs_resize(size.width(), size.height());
	}
}

J
jp9000 已提交
722
void OBSBasic::on_action_New_triggered()
723
{
J
jp9000 已提交
724
	/* TODO */
725 726
}

J
jp9000 已提交
727
void OBSBasic::on_action_Open_triggered()
728
{
J
jp9000 已提交
729
	/* TODO */
730 731
}

J
jp9000 已提交
732
void OBSBasic::on_action_Save_triggered()
733
{
J
jp9000 已提交
734
	/* TODO */
735 736
}

P
Palana 已提交
737 738 739 740 741 742
void OBSBasic::on_action_Settings_triggered()
{
	OBSBasicSettings settings(this);
	settings.exec();
}

743 744
void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current,
		QListWidgetItem *prev)
745 746
{
	obs_source_t source = NULL;
J
jp9000 已提交
747

748 749 750 751
	if (sceneChanging)
		return;

	if (current) {
J
jp9000 已提交
752 753
		obs_scene_t scene;

754
		scene = current->data(Qt::UserRole).value<OBSScene>();
755 756 757
		source = obs_scene_getsource(scene);
	}

758
	/* TODO: allow transitions */
759
	obs_set_output_source(0, source);
760 761

	UNUSED_PARAMETER(prev);
762 763
}

J
jp9000 已提交
764
void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos)
765
{
J
jp9000 已提交
766 767
	/* TODO */
	UNUSED_PARAMETER(pos);
768 769
}

J
jp9000 已提交
770
void OBSBasic::on_actionAddScene_triggered()
771
{
772
	string name;
J
jp9000 已提交
773 774 775
	bool accepted = NameDialog::AskForName(this,
			QTStr("MainWindow.AddSceneDlg.Title"),
			QTStr("MainWindow.AddSceneDlg.Text"),
776 777
			name);

J
jp9000 已提交
778
	if (accepted) {
J
jp9000 已提交
779 780 781 782 783 784 785 786
		if (name.empty()) {
			QMessageBox::information(this,
					QTStr("MainWindow.NoNameEntered"),
					QTStr("MainWindow.NoNameEntered"));
			on_actionAddScene_triggered();
			return;
		}

787 788
		obs_source_t source = obs_get_source_by_name(name.c_str());
		if (source) {
J
jp9000 已提交
789 790 791
			QMessageBox::information(this,
					QTStr("MainWindow.NameExists.Title"),
					QTStr("MainWindow.NameExists.Text"));
792 793

			obs_source_release(source);
J
jp9000 已提交
794
			on_actionAddScene_triggered();
795 796 797
			return;
		}

798
		obs_scene_t scene = obs_scene_create(name.c_str());
799 800
		source = obs_scene_getsource(scene);
		obs_add_source(source);
801
		obs_scene_release(scene);
802 803

		obs_set_output_source(0, source);
804
	}
805 806
}

J
jp9000 已提交
807
void OBSBasic::on_actionRemoveScene_triggered()
808
{
J
jp9000 已提交
809 810
	QListWidgetItem *item = ui->scenes->currentItem();
	if (!item)
J
jp9000 已提交
811 812
		return;

J
jp9000 已提交
813
	QVariant userData = item->data(Qt::UserRole);
J
jp9000 已提交
814
	obs_scene_t scene = userData.value<OBSScene>();
J
jp9000 已提交
815 816
	obs_source_t source = obs_scene_getsource(scene);
	obs_source_remove(source);
817 818
}

J
jp9000 已提交
819
void OBSBasic::on_actionSceneProperties_triggered()
820
{
J
jp9000 已提交
821
	/* TODO */
822 823
}

J
jp9000 已提交
824
void OBSBasic::on_actionSceneUp_triggered()
825
{
J
jp9000 已提交
826
	/* TODO */
827 828
}

J
jp9000 已提交
829
void OBSBasic::on_actionSceneDown_triggered()
830
{
J
jp9000 已提交
831
	/* TODO */
832 833
}

834 835
void OBSBasic::on_sources_currentItemChanged(QListWidgetItem *current,
		QListWidgetItem *prev)
836
{
J
jp9000 已提交
837
	/* TODO */
838 839
	UNUSED_PARAMETER(current);
	UNUSED_PARAMETER(prev);
840 841
}

J
jp9000 已提交
842
void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos)
843
{
J
jp9000 已提交
844 845
	/* TODO */
	UNUSED_PARAMETER(pos);
846 847
}

848 849 850 851 852 853
void OBSBasic::AddSource(obs_scene_t scene, const char *id)
{
	string name;

	bool success = false;
	while (!success) {
J
jp9000 已提交
854
		bool accepted = NameDialog::AskForName(this,
855 856 857 858
				Str("MainWindow.AddSourceDlg.Title"),
				Str("MainWindow.AddSourceDlg.Text"),
				name);

J
jp9000 已提交
859
		if (!accepted)
860 861
			break;

J
jp9000 已提交
862 863 864 865 866 867 868
		if (name.empty()) {
			QMessageBox::information(this,
					QTStr("MainWindow.NoNameEntered"),
					QTStr("MainWindow.NoNameEntered"));
			continue;
		}

J
jp9000 已提交
869
		obs_source_t source = obs_get_source_by_name(name.c_str());
870 871
		if (!source) {
			success = true;
J
jp9000 已提交
872
			break;
873
		} else {
J
jp9000 已提交
874 875 876
			QMessageBox::information(this,
					QTStr("MainWindow.NameExists.Title"),
					QTStr("MainWindow.NameExists.Text"));
877 878 879 880 881
			obs_source_release(source);
		}
	}

	if (success) {
J
jp9000 已提交
882 883
		obs_source_t source = obs_source_create(OBS_SOURCE_TYPE_INPUT,
				id, name.c_str(), NULL);
J
jp9000 已提交
884 885 886

		sourceSceneRefs[source] = 0;

887
		obs_add_source(source);
J
jp9000 已提交
888
		obs_scene_add(scene, source);
889 890 891 892
		obs_source_release(source);
	}
}

J
jp9000 已提交
893
void OBSBasic::AddSourcePopupMenu(const QPoint &pos)
894
{
895
	OBSScene scene = GetCurrentScene();
896
	const char *type;
J
jp9000 已提交
897 898
	bool foundValues = false;
	size_t idx = 0;
899

J
jp9000 已提交
900
	if (!scene)
901 902
		return;

J
jp9000 已提交
903 904
	QMenu popup;
	while (obs_enum_input_types(idx++, &type)) {
J
jp9000 已提交
905 906
		const char *name = obs_source_getdisplayname(
				OBS_SOURCE_TYPE_INPUT,
J
jp9000 已提交
907
				type, App()->GetLocale());
908

J
jp9000 已提交
909 910 911
		QAction *popupItem = new QAction(QT_UTF8(name), this);
		popupItem->setData(QT_UTF8(type));
		popup.addAction(popupItem);
912

J
jp9000 已提交
913
		foundValues = true;
914 915
	}

J
jp9000 已提交
916 917 918 919
	if (foundValues) {
		QAction *ret = popup.exec(pos);
		if (ret)
			AddSource(scene, ret->data().toString().toUtf8());
920 921 922
	}
}

J
jp9000 已提交
923
void OBSBasic::on_actionAddSource_triggered()
924
{
J
jp9000 已提交
925
	AddSourcePopupMenu(QCursor::pos());
926 927
}

J
jp9000 已提交
928
void OBSBasic::on_actionRemoveSource_triggered()
929
{
930
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
931 932
	if (item)
		obs_sceneitem_remove(item);
933 934
}

J
jp9000 已提交
935
void OBSBasic::on_actionSourceProperties_triggered()
936
{
937 938 939 940 941 942 943 944
	OBSSceneItem item = GetCurrentSceneItem();
	OBSSource source = obs_sceneitem_getsource(item);

	if (source) {
		delete properties;
		properties = new OBSBasicProperties(this, source);
		properties->Init();
	}
945 946
}

J
jp9000 已提交
947
void OBSBasic::on_actionSourceUp_triggered()
948 949
{
}
J
jp9000 已提交
950

J
jp9000 已提交
951
void OBSBasic::on_actionSourceDown_triggered()
952 953 954
{
}

955
void OBSBasic::StreamingStart()
956 957
{
	ui->streamButton->setText("Stop Streaming");
958 959
}

960
void OBSBasic::StreamingStop(int errorcode)
961
{
962 963
	UNUSED_PARAMETER(errorcode);
	ui->streamButton->setText("Start Streaming");
J
jp9000 已提交
964 965
}

966 967
void OBSBasic::on_streamButton_clicked()
{
968 969 970
	if (obs_output_active(streamOutput)) {
		obs_output_stop(streamOutput);

971
	} else {
972 973 974 975
		obs_data_t x264Settings = obs_data_create();
		obs_data_t aacSettings  = obs_data_create();

		int videoBitrate = config_get_uint(basicConfig, "SimpleOutput",
976
				"VBitrate");
977
		int audioBitrate = config_get_uint(basicConfig, "SimpleOutput",
978 979
				"ABitrate");

980
		SaveService();
981

982 983
		obs_data_setint(x264Settings, "bitrate", videoBitrate);
		obs_data_setbool(x264Settings, "cbr", true);
984

985
		obs_data_setint(aacSettings, "bitrate", audioBitrate);
986

987 988
		obs_encoder_update(x264, x264Settings);
		obs_encoder_update(aac,  aacSettings);
J
jp9000 已提交
989

990 991
		obs_data_release(x264Settings);
		obs_data_release(aacSettings);
992

993 994 995 996 997 998
		obs_encoder_set_video(x264, obs_video());
		obs_encoder_set_audio(aac,  obs_audio());
		obs_output_set_video_encoder(streamOutput, x264);
		obs_output_set_audio_encoder(streamOutput, aac);
		obs_output_set_service(streamOutput, service);
		obs_output_start(streamOutput);
J
jp9000 已提交
999 1000 1001
	}
}

J
jp9000 已提交
1002
void OBSBasic::on_settingsButton_clicked()
J
jp9000 已提交
1003
{
1004 1005
	OBSBasicSettings settings(this);
	settings.exec();
J
jp9000 已提交
1006
}
1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074

void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const
{
	const char *val = config_get_string(basicConfig, "Video", "FPSCommon");

	if (strcmp(val, "10") == 0) {
		num = 10;
		den = 1;
	} else if (strcmp(val, "20") == 0) {
		num = 20;
		den = 1;
	} else if (strcmp(val, "25") == 0) {
		num = 25;
		den = 1;
	} else if (strcmp(val, "29.97") == 0) {
		num = 30000;
		den = 1001;
	} else if (strcmp(val, "48") == 0) {
		num = 48;
		den = 1;
	} else if (strcmp(val, "59.94") == 0) {
		num = 60000;
		den = 1001;
	} else if (strcmp(val, "60") == 0) {
		num = 60;
		den = 1;
	} else {
		num = 30;
		den = 1;
	}
}

void OBSBasic::GetFPSInteger(uint32_t &num, uint32_t &den) const
{
	num = (uint32_t)config_get_uint(basicConfig, "Video", "FPSInt");
	den = 1;
}

void OBSBasic::GetFPSFraction(uint32_t &num, uint32_t &den) const
{
	num = (uint32_t)config_get_uint(basicConfig, "Video", "FPSNum");
	den = (uint32_t)config_get_uint(basicConfig, "Video", "FPSDen");
}

void OBSBasic::GetFPSNanoseconds(uint32_t &num, uint32_t &den) const
{
	num = 1000000000;
	den = (uint32_t)config_get_uint(basicConfig, "Video", "FPSNS");
}

void OBSBasic::GetConfigFPS(uint32_t &num, uint32_t &den) const
{
	uint32_t type = config_get_uint(basicConfig, "Video", "FPSType");

	if (type == 1) //"Integer"
		GetFPSInteger(num, den);
	else if (type == 2) //"Fraction"
		GetFPSFraction(num, den);
	else if (false) //"Nanoseconds", currently not implemented
		GetFPSNanoseconds(num, den);
	else
		GetFPSCommon(num, den);
}

config_t OBSBasic::Config() const
{
	return basicConfig;
}