AutoConfigTestPage.cpp 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188
  1. #include "AutoConfigTestPage.hpp"
  2. #include "AutoConfig.hpp"
  3. #include "TestMode.hpp"
  4. #include "ui_AutoConfigTestPage.h"
  5. #include <widgets/OBSBasic.hpp>
  6. #include <graphics/math-extra.h>
  7. #include <qt-wrappers.hpp>
  8. #include <QFormLayout>
  9. #include "moc_AutoConfigTestPage.cpp"
  10. #define TEST_STR(x) "Basic.AutoConfig.TestPage." x
  11. #define SUBTITLE_TESTING TEST_STR("SubTitle.Testing")
  12. #define SUBTITLE_COMPLETE TEST_STR("SubTitle.Complete")
  13. #define TEST_BW TEST_STR("TestingBandwidth")
  14. #define TEST_BW_NO_OUTPUT TEST_STR("TestingBandwidth.NoOutput")
  15. #define TEST_BW_CONNECTING TEST_STR("TestingBandwidth.Connecting")
  16. #define TEST_BW_CONNECT_FAIL TEST_STR("TestingBandwidth.ConnectFailed")
  17. #define TEST_BW_SERVER TEST_STR("TestingBandwidth.Server")
  18. #define TEST_RES_VAL TEST_STR("TestingRes.Resolution")
  19. #define TEST_RES_FAIL TEST_STR("TestingRes.Fail")
  20. #define TEST_SE TEST_STR("TestingStreamEncoder")
  21. #define TEST_RE TEST_STR("TestingRecordingEncoder")
  22. #define TEST_RESULT_SE TEST_STR("Result.StreamingEncoder")
  23. #define TEST_RESULT_RE TEST_STR("Result.RecordingEncoder")
  24. #define wiz reinterpret_cast<AutoConfig *>(wizard())
  25. using namespace std;
  26. void AutoConfigTestPage::StartBandwidthStage()
  27. {
  28. ui->progressLabel->setText(QTStr(TEST_BW));
  29. testThread = std::thread([this]() { TestBandwidthThread(); });
  30. }
  31. void AutoConfigTestPage::StartStreamEncoderStage()
  32. {
  33. ui->progressLabel->setText(QTStr(TEST_SE));
  34. testThread = std::thread([this]() { TestStreamEncoderThread(); });
  35. }
  36. void AutoConfigTestPage::StartRecordingEncoderStage()
  37. {
  38. ui->progressLabel->setText(QTStr(TEST_RE));
  39. testThread = std::thread([this]() { TestRecordingEncoderThread(); });
  40. }
  41. void AutoConfigTestPage::GetServers(std::vector<ServerInfo> &servers)
  42. {
  43. OBSDataAutoRelease settings = obs_data_create();
  44. obs_data_set_string(settings, "service", wiz->serviceName.c_str());
  45. obs_properties_t *ppts = obs_get_service_properties("rtmp_common");
  46. obs_property_t *p = obs_properties_get(ppts, "service");
  47. obs_property_modified(p, settings);
  48. p = obs_properties_get(ppts, "server");
  49. size_t count = obs_property_list_item_count(p);
  50. servers.reserve(count);
  51. for (size_t i = 0; i < count; i++) {
  52. const char *name = obs_property_list_item_name(p, i);
  53. const char *server = obs_property_list_item_string(p, i);
  54. if (wiz->CanTestServer(name)) {
  55. ServerInfo info(name, server);
  56. servers.push_back(info);
  57. }
  58. }
  59. obs_properties_destroy(ppts);
  60. }
  61. static inline void string_depad_key(string &key)
  62. {
  63. while (!key.empty()) {
  64. char ch = key.back();
  65. if (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r')
  66. key.pop_back();
  67. else
  68. break;
  69. }
  70. }
  71. extern const char *FindAudioEncoderFromCodec(const char *type);
  72. static bool return_first_id(void *data, const char *id)
  73. {
  74. const char **output = (const char **)data;
  75. *output = id;
  76. return false;
  77. }
  78. void AutoConfigTestPage::TestBandwidthThread()
  79. {
  80. bool connected = false;
  81. bool stopped = false;
  82. TestMode testMode;
  83. testMode.SetVideo(128, 128, 60, 1);
  84. QMetaObject::invokeMethod(this, "Progress", Q_ARG(int, 0));
  85. /*
  86. * create encoders
  87. * create output
  88. * test for 10 seconds
  89. */
  90. QMetaObject::invokeMethod(this, "UpdateMessage", Q_ARG(QString, QStringLiteral("")));
  91. /* -----------------------------------*/
  92. /* create obs objects */
  93. const char *serverType = wiz->customServer ? "rtmp_custom" : "rtmp_common";
  94. OBSEncoderAutoRelease vencoder = obs_video_encoder_create("obs_x264", "test_x264", nullptr, nullptr);
  95. OBSEncoderAutoRelease aencoder = obs_audio_encoder_create("ffmpeg_aac", "test_aac", nullptr, 0, nullptr);
  96. OBSServiceAutoRelease service = obs_service_create(serverType, "test_service", nullptr, nullptr);
  97. /* -----------------------------------*/
  98. /* configure settings */
  99. // service: "service", "server", "key"
  100. // vencoder: "bitrate", "rate_control",
  101. // obs_service_apply_encoder_settings
  102. // aencoder: "bitrate"
  103. // output: "bind_ip" via main config -> "Output", "BindIP"
  104. // obs_output_set_service
  105. OBSDataAutoRelease service_settings = obs_data_create();
  106. OBSDataAutoRelease vencoder_settings = obs_data_create();
  107. OBSDataAutoRelease aencoder_settings = obs_data_create();
  108. OBSDataAutoRelease output_settings = obs_data_create();
  109. std::string key = wiz->key;
  110. if (wiz->service == AutoConfig::Service::Twitch || wiz->service == AutoConfig::Service::AmazonIVS) {
  111. string_depad_key(key);
  112. key += "?bandwidthtest";
  113. } else if (wiz->serviceName == "Restream.io" || wiz->serviceName == "Restream.io - RTMP") {
  114. string_depad_key(key);
  115. key += "?test=true";
  116. }
  117. obs_data_set_string(service_settings, "service", wiz->serviceName.c_str());
  118. obs_data_set_string(service_settings, "key", key.c_str());
  119. obs_data_set_int(vencoder_settings, "bitrate", wiz->startingBitrate);
  120. obs_data_set_string(vencoder_settings, "rate_control", "CBR");
  121. obs_data_set_string(vencoder_settings, "preset", "veryfast");
  122. obs_data_set_int(vencoder_settings, "keyint_sec", 2);
  123. obs_data_set_int(aencoder_settings, "bitrate", 32);
  124. OBSBasic *main = OBSBasic::Get();
  125. const char *bind_ip = config_get_string(main->Config(), "Output", "BindIP");
  126. obs_data_set_string(output_settings, "bind_ip", bind_ip);
  127. const char *ip_family = config_get_string(main->Config(), "Output", "IPFamily");
  128. obs_data_set_string(output_settings, "ip_family", ip_family);
  129. /* -----------------------------------*/
  130. /* determine which servers to test */
  131. std::vector<ServerInfo> servers;
  132. if (wiz->customServer)
  133. servers.emplace_back(wiz->server.c_str(), wiz->server.c_str());
  134. else
  135. GetServers(servers);
  136. /* just use the first server if it only has one alternate server,
  137. * or if using Restream or Nimo TV due to their "auto" servers */
  138. if (servers.size() < 3 || wiz->serviceName.substr(0, 11) == "Restream.io" || wiz->serviceName == "Nimo TV") {
  139. servers.resize(1);
  140. } else if ((wiz->service == AutoConfig::Service::Twitch && wiz->twitchAuto) ||
  141. (wiz->service == AutoConfig::Service::AmazonIVS && wiz->amazonIVSAuto)) {
  142. /* if using Twitch and "Auto" is available, test 3 closest
  143. * server */
  144. servers.erase(servers.begin() + 1);
  145. servers.resize(3);
  146. } else if (wiz->service == AutoConfig::Service::YouTube) {
  147. /* Only test first set of primary + backup servers */
  148. servers.resize(2);
  149. }
  150. if (!wiz->serviceConfigServers.empty()) {
  151. if (wiz->service == AutoConfig::Service::Twitch && wiz->twitchAuto) {
  152. // servers from Twitch service config replace the "auto" entry
  153. servers.erase(servers.begin());
  154. }
  155. for (auto it = std::rbegin(wiz->serviceConfigServers); it != std::rend(wiz->serviceConfigServers);
  156. it++) {
  157. auto same_server =
  158. std::find_if(std::begin(servers), std::end(servers),
  159. [&](const ServerInfo &si) { return si.address == it->address; });
  160. if (same_server != std::end(servers))
  161. servers.erase(same_server);
  162. servers.emplace(std::begin(servers), it->name.c_str(), it->address.c_str());
  163. }
  164. if (wiz->service == AutoConfig::Service::Twitch && wiz->twitchAuto) {
  165. // see above, only test 3 servers
  166. // rtmps urls are currently counted as separate servers
  167. servers.resize(3);
  168. }
  169. }
  170. /* -----------------------------------*/
  171. /* apply service settings */
  172. obs_service_update(service, service_settings);
  173. obs_service_apply_encoder_settings(service, vencoder_settings, aencoder_settings);
  174. if (wiz->multitrackVideo.testSuccessful) {
  175. obs_data_set_int(vencoder_settings, "bitrate", wiz->startingBitrate);
  176. }
  177. /* -----------------------------------*/
  178. /* create output */
  179. /* Check if the service has a preferred output type */
  180. const char *output_type = obs_service_get_preferred_output_type(service);
  181. if (!output_type || (obs_get_output_flags(output_type) & OBS_OUTPUT_SERVICE) == 0) {
  182. /* Otherwise, prefer first-party output types */
  183. const char *protocol = obs_service_get_protocol(service);
  184. if (can_use_output(protocol, "rtmp_output", "RTMP", "RTMPS")) {
  185. output_type = "rtmp_output";
  186. } else if (can_use_output(protocol, "ffmpeg_hls_muxer", "HLS")) {
  187. output_type = "ffmpeg_hls_muxer";
  188. } else if (can_use_output(protocol, "ffmpeg_mpegts_muxer", "SRT", "RIST")) {
  189. output_type = "ffmpeg_mpegts_muxer";
  190. }
  191. /* If third-party protocol, use the first enumerated type */
  192. if (!output_type)
  193. obs_enum_output_types_with_protocol(protocol, &output_type, return_first_id);
  194. /* If none, fail */
  195. if (!output_type) {
  196. QMetaObject::invokeMethod(this, "Failure", Q_ARG(QString, QTStr(TEST_BW_NO_OUTPUT)));
  197. return;
  198. }
  199. }
  200. OBSOutputAutoRelease output = obs_output_create(output_type, "test_stream", nullptr, nullptr);
  201. obs_output_update(output, output_settings);
  202. const char *audio_codec = obs_output_get_supported_audio_codecs(output);
  203. if (strcmp(audio_codec, "aac") != 0) {
  204. const char *id = FindAudioEncoderFromCodec(audio_codec);
  205. aencoder = obs_audio_encoder_create(id, "test_audio", nullptr, 0, nullptr);
  206. }
  207. /* -----------------------------------*/
  208. /* connect encoders/services/outputs */
  209. obs_encoder_update(vencoder, vencoder_settings);
  210. obs_encoder_update(aencoder, aencoder_settings);
  211. obs_encoder_set_video(vencoder, obs_get_video());
  212. obs_encoder_set_audio(aencoder, obs_get_audio());
  213. obs_output_set_video_encoder(output, vencoder);
  214. obs_output_set_audio_encoder(output, aencoder, 0);
  215. obs_output_set_reconnect_settings(output, 0, 0);
  216. obs_output_set_service(output, service);
  217. /* -----------------------------------*/
  218. /* connect signals */
  219. auto on_started = [&]() {
  220. unique_lock<mutex> lock(m);
  221. connected = true;
  222. stopped = false;
  223. cv.notify_one();
  224. };
  225. auto on_stopped = [&]() {
  226. unique_lock<mutex> lock(m);
  227. connected = false;
  228. stopped = true;
  229. cv.notify_one();
  230. };
  231. using on_started_t = decltype(on_started);
  232. using on_stopped_t = decltype(on_stopped);
  233. auto pre_on_started = [](void *data, calldata_t *) {
  234. on_started_t &on_started = *static_cast<on_started_t *>(data);
  235. on_started();
  236. };
  237. auto pre_on_stopped = [](void *data, calldata_t *) {
  238. on_stopped_t &on_stopped = *static_cast<on_stopped_t *>(data);
  239. on_stopped();
  240. };
  241. signal_handler *sh = obs_output_get_signal_handler(output);
  242. signal_handler_connect(sh, "start", pre_on_started, &on_started);
  243. signal_handler_connect(sh, "stop", pre_on_stopped, &on_stopped);
  244. /* -----------------------------------*/
  245. /* test servers */
  246. bool success = false;
  247. for (size_t i = 0; i < servers.size(); i++) {
  248. auto &server = servers[i];
  249. connected = false;
  250. stopped = false;
  251. int per = int((i + 1) * 100 / servers.size());
  252. QMetaObject::invokeMethod(this, "Progress", Q_ARG(int, per));
  253. QMetaObject::invokeMethod(this, "UpdateMessage",
  254. Q_ARG(QString, QTStr(TEST_BW_CONNECTING).arg(server.name.c_str())));
  255. obs_data_set_string(service_settings, "server", server.address.c_str());
  256. obs_service_update(service, service_settings);
  257. if (!obs_output_start(output))
  258. continue;
  259. unique_lock<mutex> ul(m);
  260. if (cancel) {
  261. ul.unlock();
  262. obs_output_force_stop(output);
  263. return;
  264. }
  265. if (!stopped && !connected)
  266. cv.wait(ul);
  267. if (cancel) {
  268. ul.unlock();
  269. obs_output_force_stop(output);
  270. return;
  271. }
  272. if (!connected)
  273. continue;
  274. QMetaObject::invokeMethod(this, "UpdateMessage",
  275. Q_ARG(QString, QTStr(TEST_BW_SERVER).arg(server.name.c_str())));
  276. /* ignore first 2.5 seconds due to possible buffering skewing
  277. * the result */
  278. cv.wait_for(ul, chrono::milliseconds(2500));
  279. if (stopped)
  280. continue;
  281. if (cancel) {
  282. ul.unlock();
  283. obs_output_force_stop(output);
  284. return;
  285. }
  286. /* continue test */
  287. int start_bytes = (int)obs_output_get_total_bytes(output);
  288. uint64_t t_start = os_gettime_ns();
  289. cv.wait_for(ul, chrono::seconds(10));
  290. if (stopped)
  291. continue;
  292. if (cancel) {
  293. ul.unlock();
  294. obs_output_force_stop(output);
  295. return;
  296. }
  297. obs_output_stop(output);
  298. cv.wait(ul);
  299. uint64_t total_time = os_gettime_ns() - t_start;
  300. if (total_time == 0)
  301. total_time = 1;
  302. int total_bytes = (int)obs_output_get_total_bytes(output) - start_bytes;
  303. uint64_t bitrate = util_mul_div64(total_bytes, 8ULL * 1000000000ULL / 1000ULL, total_time);
  304. if (obs_output_get_frames_dropped(output) || (int)bitrate < (wiz->startingBitrate * 75 / 100)) {
  305. server.bitrate = (int)bitrate * 70 / 100;
  306. } else {
  307. server.bitrate = wiz->startingBitrate;
  308. }
  309. server.ms = obs_output_get_connect_time_ms(output);
  310. success = true;
  311. }
  312. if (!success) {
  313. QMetaObject::invokeMethod(this, "Failure", Q_ARG(QString, QTStr(TEST_BW_CONNECT_FAIL)));
  314. return;
  315. }
  316. int bestBitrate = 0;
  317. int bestMS = 0x7FFFFFFF;
  318. string bestServer;
  319. string bestServerName;
  320. for (auto &server : servers) {
  321. bool close = abs(server.bitrate - bestBitrate) < 400;
  322. if ((!close && server.bitrate > bestBitrate) || (close && server.ms < bestMS)) {
  323. bestServer = server.address;
  324. bestServerName = server.name;
  325. bestBitrate = server.bitrate;
  326. bestMS = server.ms;
  327. }
  328. }
  329. wiz->server = std::move(bestServer);
  330. wiz->serverName = std::move(bestServerName);
  331. wiz->idealBitrate = bestBitrate;
  332. QMetaObject::invokeMethod(this, "NextStage");
  333. }
  334. /* this is used to estimate the lower bitrate limit for a given
  335. * resolution/fps. yes, it is a totally arbitrary equation that gets
  336. * the closest to the expected values */
  337. static long double EstimateBitrateVal(int cx, int cy, int fps_num, int fps_den)
  338. {
  339. long fps = (long double)fps_num / (long double)fps_den;
  340. long double areaVal = pow((long double)(cx * cy), 0.85l);
  341. return areaVal * sqrt(pow(fps, 1.1l));
  342. }
  343. static long double EstimateMinBitrate(int cx, int cy, int fps_num, int fps_den)
  344. {
  345. long double val = EstimateBitrateVal(1920, 1080, 60, 1) / 5800.0l;
  346. return EstimateBitrateVal(cx, cy, fps_num, fps_den) / val;
  347. }
  348. static long double EstimateUpperBitrate(int cx, int cy, int fps_num, int fps_den)
  349. {
  350. long double val = EstimateBitrateVal(1280, 720, 30, 1) / 3000.0l;
  351. return EstimateBitrateVal(cx, cy, fps_num, fps_den) / val;
  352. }
  353. struct Result {
  354. int cx;
  355. int cy;
  356. int fps_num;
  357. int fps_den;
  358. inline Result(int cx_, int cy_, int fps_num_, int fps_den_)
  359. : cx(cx_),
  360. cy(cy_),
  361. fps_num(fps_num_),
  362. fps_den(fps_den_)
  363. {
  364. }
  365. };
  366. static void CalcBaseRes(int &baseCX, int &baseCY)
  367. {
  368. const int maxBaseArea = 1920 * 1200;
  369. const int clipResArea = 1920 * 1080;
  370. /* if base resolution unusually high, recalculate to a more reasonable
  371. * value to start the downscaling at, based upon 1920x1080's area.
  372. *
  373. * for 16:9 resolutions this will always change the starting value to
  374. * 1920x1080 */
  375. if ((baseCX * baseCY) > maxBaseArea) {
  376. long double xyAspect = (long double)baseCX / (long double)baseCY;
  377. baseCY = (int)sqrt((long double)clipResArea / xyAspect);
  378. baseCX = (int)((long double)baseCY * xyAspect);
  379. }
  380. }
  381. bool AutoConfigTestPage::TestSoftwareEncoding()
  382. {
  383. TestMode testMode;
  384. QMetaObject::invokeMethod(this, "UpdateMessage", Q_ARG(QString, QStringLiteral("")));
  385. /* -----------------------------------*/
  386. /* create obs objects */
  387. OBSEncoderAutoRelease vencoder = obs_video_encoder_create("obs_x264", "test_x264", nullptr, nullptr);
  388. OBSEncoderAutoRelease aencoder = obs_audio_encoder_create("ffmpeg_aac", "test_aac", nullptr, 0, nullptr);
  389. OBSOutputAutoRelease output = obs_output_create("null_output", "null", nullptr, nullptr);
  390. /* -----------------------------------*/
  391. /* configure settings */
  392. OBSDataAutoRelease aencoder_settings = obs_data_create();
  393. OBSDataAutoRelease vencoder_settings = obs_data_create();
  394. obs_data_set_int(aencoder_settings, "bitrate", 32);
  395. if (wiz->type != AutoConfig::Type::Recording) {
  396. obs_data_set_int(vencoder_settings, "keyint_sec", 2);
  397. obs_data_set_int(vencoder_settings, "bitrate", wiz->idealBitrate);
  398. obs_data_set_string(vencoder_settings, "rate_control", "CBR");
  399. obs_data_set_string(vencoder_settings, "profile", "main");
  400. obs_data_set_string(vencoder_settings, "preset", "veryfast");
  401. } else {
  402. obs_data_set_int(vencoder_settings, "crf", 20);
  403. obs_data_set_string(vencoder_settings, "rate_control", "CRF");
  404. obs_data_set_string(vencoder_settings, "profile", "high");
  405. obs_data_set_string(vencoder_settings, "preset", "veryfast");
  406. }
  407. /* -----------------------------------*/
  408. /* apply settings */
  409. obs_encoder_update(vencoder, vencoder_settings);
  410. obs_encoder_update(aencoder, aencoder_settings);
  411. /* -----------------------------------*/
  412. /* connect encoders/services/outputs */
  413. obs_output_set_video_encoder(output, vencoder);
  414. obs_output_set_audio_encoder(output, aencoder, 0);
  415. /* -----------------------------------*/
  416. /* connect signals */
  417. auto on_stopped = [&]() {
  418. unique_lock<mutex> lock(m);
  419. cv.notify_one();
  420. };
  421. using on_stopped_t = decltype(on_stopped);
  422. auto pre_on_stopped = [](void *data, calldata_t *) {
  423. on_stopped_t &on_stopped = *static_cast<on_stopped_t *>(data);
  424. on_stopped();
  425. };
  426. signal_handler *sh = obs_output_get_signal_handler(output);
  427. signal_handler_connect(sh, "deactivate", pre_on_stopped, &on_stopped);
  428. /* -----------------------------------*/
  429. /* calculate starting resolution */
  430. int baseCX = wiz->baseResolutionCX;
  431. int baseCY = wiz->baseResolutionCY;
  432. CalcBaseRes(baseCX, baseCY);
  433. /* -----------------------------------*/
  434. /* calculate starting test rates */
  435. int pcores = os_get_physical_cores();
  436. int lcores = os_get_logical_cores();
  437. int maxDataRate;
  438. if (lcores > 8 || pcores > 4) {
  439. /* superb */
  440. maxDataRate = 1920 * 1200 * 60 + 1000;
  441. } else if (lcores > 4 && pcores == 4) {
  442. /* great */
  443. maxDataRate = 1920 * 1080 * 60 + 1000;
  444. } else if (pcores == 4) {
  445. /* okay */
  446. maxDataRate = 1920 * 1080 * 30 + 1000;
  447. } else {
  448. /* toaster */
  449. maxDataRate = 960 * 540 * 30 + 1000;
  450. }
  451. /* -----------------------------------*/
  452. /* perform tests */
  453. vector<Result> results;
  454. int i = 0;
  455. int count = 1;
  456. auto testRes = [&](int cy, int fps_num, int fps_den, bool force) {
  457. int per = ++i * 100 / count;
  458. QMetaObject::invokeMethod(this, "Progress", Q_ARG(int, per));
  459. if (cy > baseCY)
  460. return true;
  461. /* no need for more than 3 tests max */
  462. if (results.size() >= 3)
  463. return true;
  464. if (!fps_num || !fps_den) {
  465. fps_num = wiz->specificFPSNum;
  466. fps_den = wiz->specificFPSDen;
  467. }
  468. long double fps = ((long double)fps_num / (long double)fps_den);
  469. int cx = int(((long double)baseCX / (long double)baseCY) * (long double)cy);
  470. if (!force && wiz->type != AutoConfig::Type::Recording) {
  471. int est = EstimateMinBitrate(cx, cy, fps_num, fps_den);
  472. if (est > wiz->idealBitrate)
  473. return true;
  474. }
  475. long double rate = (long double)cx * (long double)cy * fps;
  476. if (!force && rate > maxDataRate)
  477. return true;
  478. testMode.SetVideo(cx, cy, fps_num, fps_den);
  479. obs_encoder_set_video(vencoder, obs_get_video());
  480. obs_encoder_set_audio(aencoder, obs_get_audio());
  481. obs_encoder_update(vencoder, vencoder_settings);
  482. obs_output_set_media(output, obs_get_video(), obs_get_audio());
  483. QString cxStr = QString::number(cx);
  484. QString cyStr = QString::number(cy);
  485. QString fpsStr = (fps_den > 1) ? QString::number(fps, 'f', 2) : QString::number(fps, 'g', 2);
  486. QMetaObject::invokeMethod(this, "UpdateMessage",
  487. Q_ARG(QString, QTStr(TEST_RES_VAL).arg(cxStr, cyStr, fpsStr)));
  488. unique_lock<mutex> ul(m);
  489. if (cancel)
  490. return false;
  491. if (!obs_output_start(output)) {
  492. QMetaObject::invokeMethod(this, "Failure", Q_ARG(QString, QTStr(TEST_RES_FAIL)));
  493. return false;
  494. }
  495. cv.wait_for(ul, chrono::seconds(5));
  496. obs_output_stop(output);
  497. cv.wait(ul);
  498. int skipped = (int)video_output_get_skipped_frames(obs_get_video());
  499. if (force || skipped <= 10)
  500. results.emplace_back(cx, cy, fps_num, fps_den);
  501. return !cancel;
  502. };
  503. if (wiz->specificFPSNum && wiz->specificFPSDen) {
  504. count = 7;
  505. if (!testRes(2160, 0, 0, false))
  506. return false;
  507. if (!testRes(1440, 0, 0, false))
  508. return false;
  509. if (!testRes(1080, 0, 0, false))
  510. return false;
  511. if (!testRes(720, 0, 0, false))
  512. return false;
  513. if (!testRes(480, 0, 0, false))
  514. return false;
  515. if (!testRes(360, 0, 0, false))
  516. return false;
  517. if (!testRes(240, 0, 0, true))
  518. return false;
  519. } else {
  520. count = 14;
  521. if (!testRes(2160, 60, 1, false))
  522. return false;
  523. if (!testRes(2160, 30, 1, false))
  524. return false;
  525. if (!testRes(1440, 60, 1, false))
  526. return false;
  527. if (!testRes(1440, 30, 1, false))
  528. return false;
  529. if (!testRes(1080, 60, 1, false))
  530. return false;
  531. if (!testRes(1080, 30, 1, false))
  532. return false;
  533. if (!testRes(720, 60, 1, false))
  534. return false;
  535. if (!testRes(720, 30, 1, false))
  536. return false;
  537. if (!testRes(480, 60, 1, false))
  538. return false;
  539. if (!testRes(480, 30, 1, false))
  540. return false;
  541. if (!testRes(360, 60, 1, false))
  542. return false;
  543. if (!testRes(360, 30, 1, false))
  544. return false;
  545. if (!testRes(240, 60, 1, false))
  546. return false;
  547. if (!testRes(240, 30, 1, true))
  548. return false;
  549. }
  550. /* -----------------------------------*/
  551. /* find preferred settings */
  552. int minArea = 960 * 540 + 1000;
  553. if (!wiz->specificFPSNum && wiz->preferHighFPS && results.size() > 1) {
  554. Result &result1 = results[0];
  555. Result &result2 = results[1];
  556. if (result1.fps_num == 30 && result2.fps_num == 60) {
  557. int nextArea = result2.cx * result2.cy;
  558. if (nextArea >= minArea)
  559. results.erase(results.begin());
  560. }
  561. }
  562. Result result = results.front();
  563. wiz->idealResolutionCX = result.cx;
  564. wiz->idealResolutionCY = result.cy;
  565. wiz->idealFPSNum = result.fps_num;
  566. wiz->idealFPSDen = result.fps_den;
  567. long double fUpperBitrate = EstimateUpperBitrate(result.cx, result.cy, result.fps_num, result.fps_den);
  568. int upperBitrate = int(floor(fUpperBitrate / 50.0l) * 50.0l);
  569. if (wiz->streamingEncoder != AutoConfig::Encoder::x264) {
  570. upperBitrate *= 114;
  571. upperBitrate /= 100;
  572. }
  573. if (wiz->testMultitrackVideo && wiz->multitrackVideo.testSuccessful &&
  574. !wiz->multitrackVideo.bitrate.has_value())
  575. wiz->multitrackVideo.bitrate = wiz->idealBitrate;
  576. if (wiz->idealBitrate > upperBitrate)
  577. wiz->idealBitrate = upperBitrate;
  578. softwareTested = true;
  579. return true;
  580. }
  581. void AutoConfigTestPage::FindIdealHardwareResolution()
  582. {
  583. int baseCX = wiz->baseResolutionCX;
  584. int baseCY = wiz->baseResolutionCY;
  585. CalcBaseRes(baseCX, baseCY);
  586. vector<Result> results;
  587. int pcores = os_get_physical_cores();
  588. int maxDataRate;
  589. if (pcores >= 4) {
  590. maxDataRate = 1920 * 1200 * 60 + 1000;
  591. } else {
  592. maxDataRate = 1280 * 720 * 30 + 1000;
  593. }
  594. auto testRes = [&](int cy, int fps_num, int fps_den, bool force) {
  595. if (cy > baseCY)
  596. return;
  597. if (results.size() >= 3)
  598. return;
  599. if (!fps_num || !fps_den) {
  600. fps_num = wiz->specificFPSNum;
  601. fps_den = wiz->specificFPSDen;
  602. }
  603. long double fps = ((long double)fps_num / (long double)fps_den);
  604. int cx = int(((long double)baseCX / (long double)baseCY) * (long double)cy);
  605. long double rate = (long double)cx * (long double)cy * fps;
  606. if (!force && rate > maxDataRate)
  607. return;
  608. AutoConfig::Encoder encType = wiz->streamingEncoder;
  609. bool nvenc = encType == AutoConfig::Encoder::NVENC;
  610. int minBitrate = EstimateMinBitrate(cx, cy, fps_num, fps_den);
  611. /* most hardware encoders don't have a good quality to bitrate
  612. * ratio, so increase the minimum bitrate estimate for them.
  613. * NVENC currently is the exception because of the improvements
  614. * its made to its quality in recent generations. */
  615. if (!nvenc)
  616. minBitrate = minBitrate * 114 / 100;
  617. if (wiz->type == AutoConfig::Type::Recording)
  618. force = true;
  619. if (force || wiz->idealBitrate >= minBitrate)
  620. results.emplace_back(cx, cy, fps_num, fps_den);
  621. };
  622. if (wiz->specificFPSNum && wiz->specificFPSDen) {
  623. testRes(2160, 0, 0, false);
  624. testRes(1440, 0, 0, false);
  625. testRes(1080, 0, 0, false);
  626. testRes(720, 0, 0, false);
  627. testRes(480, 0, 0, false);
  628. testRes(360, 0, 0, false);
  629. testRes(240, 0, 0, true);
  630. } else {
  631. testRes(2160, 60, 1, false);
  632. testRes(2160, 30, 1, false);
  633. testRes(1440, 60, 1, false);
  634. testRes(1440, 30, 1, false);
  635. testRes(1080, 60, 1, false);
  636. testRes(1080, 30, 1, false);
  637. testRes(720, 60, 1, false);
  638. testRes(720, 30, 1, false);
  639. testRes(480, 60, 1, false);
  640. testRes(480, 30, 1, false);
  641. testRes(360, 60, 1, false);
  642. testRes(360, 30, 1, false);
  643. testRes(240, 60, 1, false);
  644. testRes(240, 30, 1, true);
  645. }
  646. int minArea = 960 * 540 + 1000;
  647. if (!wiz->specificFPSNum && wiz->preferHighFPS && results.size() > 1) {
  648. Result &result1 = results[0];
  649. Result &result2 = results[1];
  650. if (result1.fps_num == 30 && result2.fps_num == 60) {
  651. int nextArea = result2.cx * result2.cy;
  652. if (nextArea >= minArea)
  653. results.erase(results.begin());
  654. }
  655. }
  656. Result result = results.front();
  657. wiz->idealResolutionCX = result.cx;
  658. wiz->idealResolutionCY = result.cy;
  659. wiz->idealFPSNum = result.fps_num;
  660. wiz->idealFPSDen = result.fps_den;
  661. }
  662. void AutoConfigTestPage::TestStreamEncoderThread()
  663. {
  664. bool preferHardware = wiz->preferHardware;
  665. if (!softwareTested) {
  666. if (!preferHardware || !wiz->hardwareEncodingAvailable) {
  667. if (!TestSoftwareEncoding()) {
  668. return;
  669. }
  670. }
  671. }
  672. if (!softwareTested) {
  673. if (wiz->nvencAvailable)
  674. wiz->streamingEncoder = AutoConfig::Encoder::NVENC;
  675. else if (wiz->qsvAvailable)
  676. wiz->streamingEncoder = AutoConfig::Encoder::QSV;
  677. else if (wiz->appleAvailable)
  678. wiz->streamingEncoder = AutoConfig::Encoder::Apple;
  679. else
  680. wiz->streamingEncoder = AutoConfig::Encoder::AMD;
  681. } else {
  682. wiz->streamingEncoder = AutoConfig::Encoder::x264;
  683. }
  684. #ifdef __linux__
  685. // On linux CBR rate control is not guaranteed so fallback to x264.
  686. if (wiz->streamingEncoder == AutoConfig::Encoder::QSV) {
  687. wiz->streamingEncoder = AutoConfig::Encoder::x264;
  688. if (!TestSoftwareEncoding()) {
  689. return;
  690. }
  691. }
  692. #endif
  693. if (preferHardware && !softwareTested && wiz->hardwareEncodingAvailable)
  694. FindIdealHardwareResolution();
  695. QMetaObject::invokeMethod(this, "NextStage");
  696. }
  697. void AutoConfigTestPage::TestRecordingEncoderThread()
  698. {
  699. if (!wiz->hardwareEncodingAvailable && !softwareTested) {
  700. if (!TestSoftwareEncoding()) {
  701. return;
  702. }
  703. }
  704. if (wiz->type == AutoConfig::Type::Recording && wiz->hardwareEncodingAvailable)
  705. FindIdealHardwareResolution();
  706. wiz->recordingQuality = AutoConfig::Quality::High;
  707. bool recordingOnly = wiz->type == AutoConfig::Type::Recording;
  708. if (wiz->hardwareEncodingAvailable) {
  709. if (wiz->nvencAvailable)
  710. wiz->recordingEncoder = AutoConfig::Encoder::NVENC;
  711. else if (wiz->qsvAvailable)
  712. wiz->recordingEncoder = AutoConfig::Encoder::QSV;
  713. else if (wiz->appleAvailable)
  714. wiz->recordingEncoder = AutoConfig::Encoder::Apple;
  715. else
  716. wiz->recordingEncoder = AutoConfig::Encoder::AMD;
  717. } else {
  718. wiz->recordingEncoder = AutoConfig::Encoder::x264;
  719. }
  720. if (wiz->recordingEncoder != AutoConfig::Encoder::NVENC) {
  721. if (!recordingOnly) {
  722. wiz->recordingEncoder = AutoConfig::Encoder::Stream;
  723. wiz->recordingQuality = AutoConfig::Quality::Stream;
  724. }
  725. }
  726. QMetaObject::invokeMethod(this, "NextStage");
  727. }
  728. #define ENCODER_TEXT(x) "Basic.Settings.Output.Simple.Encoder." x
  729. #define ENCODER_SOFTWARE ENCODER_TEXT("Software")
  730. #define ENCODER_NVENC ENCODER_TEXT("Hardware.NVENC.H264")
  731. #define ENCODER_QSV ENCODER_TEXT("Hardware.QSV.H264")
  732. #define ENCODER_AMD ENCODER_TEXT("Hardware.AMD.H264")
  733. #define ENCODER_APPLE ENCODER_TEXT("Hardware.Apple.H264")
  734. #define QUALITY_SAME "Basic.Settings.Output.Simple.RecordingQuality.Stream"
  735. #define QUALITY_HIGH "Basic.Settings.Output.Simple.RecordingQuality.Small"
  736. void set_closest_res(int &cx, int &cy, struct obs_service_resolution *res_list, size_t count)
  737. {
  738. int best_pixel_diff = 0x7FFFFFFF;
  739. int start_cx = cx;
  740. int start_cy = cy;
  741. for (size_t i = 0; i < count; i++) {
  742. struct obs_service_resolution &res = res_list[i];
  743. int pixel_cx_diff = abs(start_cx - res.cx);
  744. int pixel_cy_diff = abs(start_cy - res.cy);
  745. int pixel_diff = pixel_cx_diff + pixel_cy_diff;
  746. if (pixel_diff < best_pixel_diff) {
  747. best_pixel_diff = pixel_diff;
  748. cx = res.cx;
  749. cy = res.cy;
  750. }
  751. }
  752. }
  753. void AutoConfigTestPage::FinalizeResults()
  754. {
  755. ui->stackedWidget->setCurrentIndex(1);
  756. setSubTitle(QTStr(SUBTITLE_COMPLETE));
  757. QFormLayout *form = results;
  758. auto encName = [](AutoConfig::Encoder enc) -> QString {
  759. switch (enc) {
  760. case AutoConfig::Encoder::x264:
  761. return QTStr(ENCODER_SOFTWARE);
  762. case AutoConfig::Encoder::NVENC:
  763. return QTStr(ENCODER_NVENC);
  764. case AutoConfig::Encoder::QSV:
  765. return QTStr(ENCODER_QSV);
  766. case AutoConfig::Encoder::AMD:
  767. return QTStr(ENCODER_AMD);
  768. case AutoConfig::Encoder::Apple:
  769. return QTStr(ENCODER_APPLE);
  770. case AutoConfig::Encoder::Stream:
  771. return QTStr(QUALITY_SAME);
  772. }
  773. return QTStr(ENCODER_SOFTWARE);
  774. };
  775. auto newLabel = [this](const char *str) -> QLabel * {
  776. return new QLabel(QTStr(str), this);
  777. };
  778. if (wiz->type == AutoConfig::Type::Streaming) {
  779. const char *serverType = wiz->customServer ? "rtmp_custom" : "rtmp_common";
  780. OBSServiceAutoRelease service = obs_service_create(serverType, "temp_service", nullptr, nullptr);
  781. OBSDataAutoRelease service_settings = obs_data_create();
  782. OBSDataAutoRelease vencoder_settings = obs_data_create();
  783. if (wiz->testMultitrackVideo && wiz->multitrackVideo.testSuccessful &&
  784. !wiz->multitrackVideo.bitrate.has_value())
  785. wiz->multitrackVideo.bitrate = wiz->idealBitrate;
  786. obs_data_set_int(vencoder_settings, "bitrate", wiz->idealBitrate);
  787. obs_data_set_string(service_settings, "service", wiz->serviceName.c_str());
  788. obs_service_update(service, service_settings);
  789. obs_service_apply_encoder_settings(service, vencoder_settings, nullptr);
  790. BPtr<obs_service_resolution> res_list;
  791. size_t res_count;
  792. int maxFPS;
  793. obs_service_get_supported_resolutions(service, &res_list, &res_count);
  794. obs_service_get_max_fps(service, &maxFPS);
  795. if (res_list) {
  796. set_closest_res(wiz->idealResolutionCX, wiz->idealResolutionCY, res_list, res_count);
  797. }
  798. if (maxFPS) {
  799. double idealFPS = (double)wiz->idealFPSNum / (double)wiz->idealFPSDen;
  800. if (idealFPS > (double)maxFPS) {
  801. wiz->idealFPSNum = maxFPS;
  802. wiz->idealFPSDen = 1;
  803. }
  804. }
  805. wiz->idealBitrate = (int)obs_data_get_int(vencoder_settings, "bitrate");
  806. if (!wiz->customServer)
  807. form->addRow(newLabel("Basic.AutoConfig.StreamPage.Service"),
  808. new QLabel(wiz->serviceName.c_str(), ui->finishPage));
  809. form->addRow(newLabel("Basic.AutoConfig.StreamPage.Server"),
  810. new QLabel(wiz->serverName.c_str(), ui->finishPage));
  811. form->addRow(newLabel("Basic.Settings.Stream.MultitrackVideoLabel"),
  812. newLabel(wiz->multitrackVideo.testSuccessful ? "Yes" : "No"));
  813. if (wiz->multitrackVideo.testSuccessful) {
  814. form->addRow(newLabel("Basic.Settings.Output.VideoBitrate"), newLabel("Automatic"));
  815. form->addRow(newLabel(TEST_RESULT_SE), newLabel("Automatic"));
  816. form->addRow(newLabel("Basic.AutoConfig.TestPage.Result.StreamingResolution"),
  817. newLabel("Automatic"));
  818. } else {
  819. form->addRow(newLabel("Basic.Settings.Output.VideoBitrate"),
  820. new QLabel(QString::number(wiz->idealBitrate), ui->finishPage));
  821. form->addRow(newLabel(TEST_RESULT_SE),
  822. new QLabel(encName(wiz->streamingEncoder), ui->finishPage));
  823. }
  824. }
  825. QString baseRes =
  826. QString("%1x%2").arg(QString::number(wiz->baseResolutionCX), QString::number(wiz->baseResolutionCY));
  827. QString scaleRes =
  828. QString("%1x%2").arg(QString::number(wiz->idealResolutionCX), QString::number(wiz->idealResolutionCY));
  829. if (wiz->recordingEncoder != AutoConfig::Encoder::Stream ||
  830. wiz->recordingQuality != AutoConfig::Quality::Stream)
  831. form->addRow(newLabel(TEST_RESULT_RE), new QLabel(encName(wiz->recordingEncoder), ui->finishPage));
  832. QString recQuality;
  833. switch (wiz->recordingQuality) {
  834. case AutoConfig::Quality::High:
  835. recQuality = QTStr(QUALITY_HIGH);
  836. break;
  837. case AutoConfig::Quality::Stream:
  838. recQuality = QTStr(QUALITY_SAME);
  839. break;
  840. }
  841. form->addRow(newLabel("Basic.Settings.Output.Simple.RecordingQuality"), new QLabel(recQuality, ui->finishPage));
  842. long double fps = (long double)wiz->idealFPSNum / (long double)wiz->idealFPSDen;
  843. QString fpsStr = (wiz->idealFPSDen > 1) ? QString::number(fps, 'f', 2) : QString::number(fps, 'g', 2);
  844. form->addRow(newLabel("Basic.Settings.Video.BaseResolution"), new QLabel(baseRes, ui->finishPage));
  845. form->addRow(newLabel("Basic.Settings.Video.ScaledResolution"), new QLabel(scaleRes, ui->finishPage));
  846. form->addRow(newLabel("Basic.Settings.Video.FPS"), new QLabel(fpsStr, ui->finishPage));
  847. // FIXME: form layout is super squished, probably need to set proper sizepolicy on all widgets?
  848. }
  849. #define STARTING_SEPARATOR "\n==== Auto-config wizard testing commencing ======\n"
  850. #define STOPPING_SEPARATOR "\n==== Auto-config wizard testing stopping ========\n"
  851. void AutoConfigTestPage::NextStage()
  852. {
  853. if (testThread.joinable())
  854. testThread.join();
  855. if (cancel)
  856. return;
  857. ui->subProgressLabel->setText(QString());
  858. /* make it skip to bandwidth stage if only set to config recording */
  859. if (stage == Stage::Starting) {
  860. if (!started) {
  861. blog(LOG_INFO, STARTING_SEPARATOR);
  862. started = true;
  863. }
  864. if (wiz->type != AutoConfig::Type::Streaming) {
  865. stage = Stage::StreamEncoder;
  866. } else if (!wiz->bandwidthTest) {
  867. stage = Stage::BandwidthTest;
  868. }
  869. }
  870. if (stage == Stage::Starting) {
  871. stage = Stage::BandwidthTest;
  872. StartBandwidthStage();
  873. } else if (stage == Stage::BandwidthTest) {
  874. stage = Stage::StreamEncoder;
  875. StartStreamEncoderStage();
  876. } else if (stage == Stage::StreamEncoder) {
  877. stage = Stage::RecordingEncoder;
  878. StartRecordingEncoderStage();
  879. } else {
  880. stage = Stage::Finished;
  881. FinalizeResults();
  882. emit completeChanged();
  883. }
  884. }
  885. void AutoConfigTestPage::UpdateMessage(QString message)
  886. {
  887. ui->subProgressLabel->setText(message);
  888. }
  889. void AutoConfigTestPage::Failure(QString message)
  890. {
  891. ui->errorLabel->setText(message);
  892. ui->stackedWidget->setCurrentIndex(2);
  893. }
  894. void AutoConfigTestPage::Progress(int percentage)
  895. {
  896. ui->progressBar->setValue(percentage);
  897. }
  898. AutoConfigTestPage::AutoConfigTestPage(QWidget *parent) : QWizardPage(parent), ui(new Ui_AutoConfigTestPage)
  899. {
  900. ui->setupUi(this);
  901. setTitle(QTStr("Basic.AutoConfig.TestPage"));
  902. setSubTitle(QTStr(SUBTITLE_TESTING));
  903. setCommitPage(true);
  904. }
  905. AutoConfigTestPage::~AutoConfigTestPage()
  906. {
  907. if (testThread.joinable()) {
  908. {
  909. unique_lock<mutex> ul(m);
  910. cancel = true;
  911. cv.notify_one();
  912. }
  913. testThread.join();
  914. }
  915. if (started)
  916. blog(LOG_INFO, STOPPING_SEPARATOR);
  917. }
  918. void AutoConfigTestPage::initializePage()
  919. {
  920. if (wiz->type == AutoConfig::Type::VirtualCam) {
  921. wiz->idealResolutionCX = wiz->baseResolutionCX;
  922. wiz->idealResolutionCY = wiz->baseResolutionCY;
  923. wiz->idealFPSNum = 30;
  924. wiz->idealFPSDen = 1;
  925. stage = Stage::Finished;
  926. } else {
  927. stage = Stage::Starting;
  928. }
  929. setSubTitle(QTStr(SUBTITLE_TESTING));
  930. softwareTested = false;
  931. cancel = false;
  932. DeleteLayout(results);
  933. results = new QFormLayout();
  934. results->setContentsMargins(0, 0, 0, 0);
  935. ui->finishPageLayout->insertLayout(1, results);
  936. ui->stackedWidget->setCurrentIndex(0);
  937. NextStage();
  938. }
  939. void AutoConfigTestPage::cleanupPage()
  940. {
  941. if (testThread.joinable()) {
  942. {
  943. unique_lock<mutex> ul(m);
  944. cancel = true;
  945. cv.notify_one();
  946. }
  947. testThread.join();
  948. }
  949. }
  950. bool AutoConfigTestPage::isComplete() const
  951. {
  952. return stage == Stage::Finished;
  953. }
  954. int AutoConfigTestPage::nextId() const
  955. {
  956. return -1;
  957. }