window-basic-status-bar.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  1. #include <QLabel>
  2. #include <QHBoxLayout>
  3. #include <QPainter>
  4. #include <QPixmap>
  5. #include "obs-app.hpp"
  6. #include "window-basic-main.hpp"
  7. #include "window-basic-status-bar.hpp"
  8. #include "window-basic-main-outputs.hpp"
  9. OBSBasicStatusBar::OBSBasicStatusBar(QWidget *parent)
  10. : QStatusBar(parent),
  11. delayInfo(new QLabel),
  12. droppedFrames(new QLabel),
  13. streamIcon(new QLabel),
  14. streamTime(new QLabel),
  15. recordTime(new QLabel),
  16. recordIcon(new QLabel),
  17. cpuUsage(new QLabel),
  18. transparentPixmap(20, 20),
  19. greenPixmap(20, 20),
  20. grayPixmap(20, 20),
  21. redPixmap(20, 20),
  22. recordingActivePixmap(QIcon(":/res/images/recording-active.svg")
  23. .pixmap(QSize(20, 20))),
  24. recordingPausePixmap(QIcon(":/res/images/recording-pause.svg")
  25. .pixmap(QSize(20, 20))),
  26. recordingPauseInactivePixmap(
  27. QIcon(":/res/images/recording-pause-inactive.svg")
  28. .pixmap(QSize(20, 20))),
  29. recordingInactivePixmap(QIcon(":/res/images/recording-inactive.svg")
  30. .pixmap(QSize(20, 20))),
  31. streamingActivePixmap(QIcon(":/res/images/streaming-active.svg")
  32. .pixmap(QSize(20, 20))),
  33. streamingInactivePixmap(QIcon(":/res/images/streaming-inactive.svg")
  34. .pixmap(QSize(20, 20)))
  35. {
  36. streamTime->setText(QString("LIVE: 00:00:00"));
  37. recordTime->setText(QString("REC: 00:00:00"));
  38. cpuUsage->setText(QString("CPU: 0.0%, 0.00 fps"));
  39. streamIcon->setPixmap(streamingInactivePixmap);
  40. recordIcon->setPixmap(recordingInactivePixmap);
  41. QWidget *brWidget = new QWidget(this);
  42. QHBoxLayout *brLayout = new QHBoxLayout(brWidget);
  43. brLayout->setContentsMargins(0, 0, 0, 0);
  44. statusSquare = new QLabel(brWidget);
  45. brLayout->addWidget(statusSquare);
  46. kbps = new QLabel(brWidget);
  47. brLayout->addWidget(kbps);
  48. brWidget->setLayout(brLayout);
  49. delayInfo->setAlignment(Qt::AlignRight);
  50. delayInfo->setAlignment(Qt::AlignVCenter);
  51. droppedFrames->setAlignment(Qt::AlignRight);
  52. droppedFrames->setAlignment(Qt::AlignVCenter);
  53. streamIcon->setAlignment(Qt::AlignRight);
  54. streamIcon->setAlignment(Qt::AlignVCenter);
  55. streamTime->setAlignment(Qt::AlignRight);
  56. streamTime->setAlignment(Qt::AlignVCenter);
  57. recordIcon->setAlignment(Qt::AlignRight);
  58. recordIcon->setAlignment(Qt::AlignVCenter);
  59. recordTime->setAlignment(Qt::AlignRight);
  60. recordTime->setAlignment(Qt::AlignVCenter);
  61. cpuUsage->setAlignment(Qt::AlignRight);
  62. cpuUsage->setAlignment(Qt::AlignVCenter);
  63. kbps->setAlignment(Qt::AlignRight);
  64. kbps->setAlignment(Qt::AlignVCenter);
  65. delayInfo->setIndent(20);
  66. droppedFrames->setIndent(20);
  67. streamIcon->setIndent(20);
  68. recordIcon->setIndent(20);
  69. cpuUsage->setIndent(20);
  70. kbps->setIndent(10);
  71. addPermanentWidget(droppedFrames);
  72. addPermanentWidget(streamIcon);
  73. addPermanentWidget(streamTime);
  74. addPermanentWidget(recordIcon);
  75. addPermanentWidget(recordTime);
  76. addPermanentWidget(cpuUsage);
  77. addPermanentWidget(delayInfo);
  78. addPermanentWidget(brWidget);
  79. transparentPixmap.fill(QColor(0, 0, 0, 0));
  80. greenPixmap.fill(QColor(0, 255, 0));
  81. grayPixmap.fill(QColor(72, 72, 72));
  82. redPixmap.fill(QColor(255, 0, 0));
  83. statusSquare->setPixmap(transparentPixmap);
  84. }
  85. void OBSBasicStatusBar::Activate()
  86. {
  87. if (!active) {
  88. refreshTimer = new QTimer(this);
  89. connect(refreshTimer, SIGNAL(timeout()), this,
  90. SLOT(UpdateStatusBar()));
  91. int skipped = video_output_get_skipped_frames(obs_get_video());
  92. int total = video_output_get_total_frames(obs_get_video());
  93. totalStreamSeconds = 0;
  94. totalRecordSeconds = 0;
  95. lastSkippedFrameCount = 0;
  96. startSkippedFrameCount = skipped;
  97. startTotalFrameCount = total;
  98. refreshTimer->start(1000);
  99. active = true;
  100. if (streamOutput) {
  101. statusSquare->setPixmap(grayPixmap);
  102. }
  103. }
  104. if (streamOutput) {
  105. streamIcon->setPixmap(streamingActivePixmap);
  106. }
  107. if (recordOutput) {
  108. recordIcon->setPixmap(recordingActivePixmap);
  109. }
  110. }
  111. void OBSBasicStatusBar::Deactivate()
  112. {
  113. OBSBasic *main = qobject_cast<OBSBasic *>(parent());
  114. if (!main)
  115. return;
  116. if (!streamOutput) {
  117. streamTime->setText(QString("LIVE: 00:00:00"));
  118. streamIcon->setPixmap(streamingInactivePixmap);
  119. totalStreamSeconds = 0;
  120. }
  121. if (!recordOutput) {
  122. recordTime->setText(QString("REC: 00:00:00"));
  123. recordIcon->setPixmap(recordingInactivePixmap);
  124. totalRecordSeconds = 0;
  125. }
  126. if (main->outputHandler && !main->outputHandler->Active()) {
  127. delete refreshTimer;
  128. delayInfo->setText("");
  129. droppedFrames->setText("");
  130. kbps->setText("");
  131. delaySecTotal = 0;
  132. delaySecStarting = 0;
  133. delaySecStopping = 0;
  134. reconnectTimeout = 0;
  135. active = false;
  136. overloadedNotify = true;
  137. statusSquare->setPixmap(transparentPixmap);
  138. }
  139. }
  140. void OBSBasicStatusBar::UpdateDelayMsg()
  141. {
  142. QString msg;
  143. if (delaySecTotal) {
  144. if (delaySecStarting && !delaySecStopping) {
  145. msg = QTStr("Basic.StatusBar.DelayStartingIn");
  146. msg = msg.arg(QString::number(delaySecStarting));
  147. } else if (!delaySecStarting && delaySecStopping) {
  148. msg = QTStr("Basic.StatusBar.DelayStoppingIn");
  149. msg = msg.arg(QString::number(delaySecStopping));
  150. } else if (delaySecStarting && delaySecStopping) {
  151. msg = QTStr("Basic.StatusBar.DelayStartingStoppingIn");
  152. msg = msg.arg(QString::number(delaySecStopping),
  153. QString::number(delaySecStarting));
  154. } else {
  155. msg = QTStr("Basic.StatusBar.Delay");
  156. msg = msg.arg(QString::number(delaySecTotal));
  157. }
  158. }
  159. delayInfo->setText(msg);
  160. }
  161. #define BITRATE_UPDATE_SECONDS 2
  162. void OBSBasicStatusBar::UpdateBandwidth()
  163. {
  164. if (!streamOutput)
  165. return;
  166. if (++bitrateUpdateSeconds < BITRATE_UPDATE_SECONDS)
  167. return;
  168. uint64_t bytesSent = obs_output_get_total_bytes(streamOutput);
  169. uint64_t bytesSentTime = os_gettime_ns();
  170. if (bytesSent < lastBytesSent)
  171. bytesSent = 0;
  172. if (bytesSent == 0)
  173. lastBytesSent = 0;
  174. uint64_t bitsBetween = (bytesSent - lastBytesSent) * 8;
  175. double timePassed =
  176. double(bytesSentTime - lastBytesSentTime) / 1000000000.0;
  177. double kbitsPerSec = double(bitsBetween) / timePassed / 1000.0;
  178. QString text;
  179. text += QString("kb/s: ") + QString::number(kbitsPerSec, 'f', 0);
  180. kbps->setText(text);
  181. kbps->setMinimumWidth(kbps->width());
  182. lastBytesSent = bytesSent;
  183. lastBytesSentTime = bytesSentTime;
  184. bitrateUpdateSeconds = 0;
  185. }
  186. void OBSBasicStatusBar::UpdateCPUUsage()
  187. {
  188. OBSBasic *main = qobject_cast<OBSBasic *>(parent());
  189. if (!main)
  190. return;
  191. QString text;
  192. text += QString("CPU: ") +
  193. QString::number(main->GetCPUUsage(), 'f', 1) + QString("%, ") +
  194. QString::number(obs_get_active_fps(), 'f', 2) + QString(" fps");
  195. cpuUsage->setText(text);
  196. cpuUsage->setMinimumWidth(cpuUsage->width());
  197. }
  198. void OBSBasicStatusBar::UpdateStreamTime()
  199. {
  200. totalStreamSeconds++;
  201. int seconds = totalStreamSeconds % 60;
  202. int totalMinutes = totalStreamSeconds / 60;
  203. int minutes = totalMinutes % 60;
  204. int hours = totalMinutes / 60;
  205. QString text = QString::asprintf("LIVE: %02d:%02d:%02d", hours, minutes,
  206. seconds);
  207. streamTime->setText(text);
  208. streamTime->setMinimumWidth(streamTime->width());
  209. if (reconnectTimeout > 0) {
  210. QString msg = QTStr("Basic.StatusBar.Reconnecting")
  211. .arg(QString::number(retries),
  212. QString::number(reconnectTimeout));
  213. showMessage(msg);
  214. reconnectTimeout--;
  215. } else if (retries > 0) {
  216. QString msg = QTStr("Basic.StatusBar.AttemptingReconnect");
  217. showMessage(msg.arg(QString::number(retries)));
  218. }
  219. if (delaySecStopping > 0 || delaySecStarting > 0) {
  220. if (delaySecStopping > 0)
  221. --delaySecStopping;
  222. if (delaySecStarting > 0)
  223. --delaySecStarting;
  224. UpdateDelayMsg();
  225. }
  226. }
  227. extern volatile bool recording_paused;
  228. void OBSBasicStatusBar::UpdateRecordTime()
  229. {
  230. bool paused = os_atomic_load_bool(&recording_paused);
  231. if (!paused) {
  232. totalRecordSeconds++;
  233. int seconds = totalRecordSeconds % 60;
  234. int totalMinutes = totalRecordSeconds / 60;
  235. int minutes = totalMinutes % 60;
  236. int hours = totalMinutes / 60;
  237. QString text = QString::asprintf("REC: %02d:%02d:%02d", hours,
  238. minutes, seconds);
  239. recordTime->setText(text);
  240. recordTime->setMinimumWidth(recordTime->width());
  241. } else {
  242. recordIcon->setPixmap(streamPauseIconToggle
  243. ? recordingPauseInactivePixmap
  244. : recordingPausePixmap);
  245. streamPauseIconToggle = !streamPauseIconToggle;
  246. }
  247. }
  248. void OBSBasicStatusBar::UpdateDroppedFrames()
  249. {
  250. if (!streamOutput)
  251. return;
  252. int totalDropped = obs_output_get_frames_dropped(streamOutput);
  253. int totalFrames = obs_output_get_total_frames(streamOutput);
  254. double percent = (double)totalDropped / (double)totalFrames * 100.0;
  255. if (!totalFrames)
  256. return;
  257. QString text = QTStr("DroppedFrames");
  258. text = text.arg(QString::number(totalDropped),
  259. QString::number(percent, 'f', 1));
  260. droppedFrames->setText(text);
  261. droppedFrames->setMinimumWidth(droppedFrames->width());
  262. /* ----------------------------------- *
  263. * calculate congestion color */
  264. float congestion = obs_output_get_congestion(streamOutput);
  265. float avgCongestion = (congestion + lastCongestion) * 0.5f;
  266. if (avgCongestion < congestion)
  267. avgCongestion = congestion;
  268. if (avgCongestion > 1.0f)
  269. avgCongestion = 1.0f;
  270. if (avgCongestion < EPSILON) {
  271. statusSquare->setPixmap(greenPixmap);
  272. } else if (fabsf(avgCongestion - 1.0f) < EPSILON) {
  273. statusSquare->setPixmap(redPixmap);
  274. } else {
  275. QPixmap pixmap(20, 20);
  276. float red = avgCongestion * 2.0f;
  277. if (red > 1.0f)
  278. red = 1.0f;
  279. red *= 255.0;
  280. float green = (1.0f - avgCongestion) * 2.0f;
  281. if (green > 1.0f)
  282. green = 1.0f;
  283. green *= 255.0;
  284. pixmap.fill(QColor(int(red), int(green), 0));
  285. statusSquare->setPixmap(pixmap);
  286. }
  287. lastCongestion = congestion;
  288. }
  289. void OBSBasicStatusBar::OBSOutputReconnect(void *data, calldata_t *params)
  290. {
  291. OBSBasicStatusBar *statusBar =
  292. reinterpret_cast<OBSBasicStatusBar *>(data);
  293. int seconds = (int)calldata_int(params, "timeout_sec");
  294. QMetaObject::invokeMethod(statusBar, "Reconnect", Q_ARG(int, seconds));
  295. }
  296. void OBSBasicStatusBar::OBSOutputReconnectSuccess(void *data,
  297. calldata_t *params)
  298. {
  299. OBSBasicStatusBar *statusBar =
  300. reinterpret_cast<OBSBasicStatusBar *>(data);
  301. QMetaObject::invokeMethod(statusBar, "ReconnectSuccess");
  302. UNUSED_PARAMETER(params);
  303. }
  304. void OBSBasicStatusBar::Reconnect(int seconds)
  305. {
  306. OBSBasic *main = qobject_cast<OBSBasic *>(parent());
  307. if (!retries)
  308. main->SysTrayNotify(
  309. QTStr("Basic.SystemTray.Message.Reconnecting"),
  310. QSystemTrayIcon::Warning);
  311. reconnectTimeout = seconds;
  312. if (streamOutput) {
  313. delaySecTotal = obs_output_get_active_delay(streamOutput);
  314. UpdateDelayMsg();
  315. retries++;
  316. }
  317. }
  318. void OBSBasicStatusBar::ReconnectClear()
  319. {
  320. retries = 0;
  321. reconnectTimeout = 0;
  322. bitrateUpdateSeconds = -1;
  323. lastBytesSent = 0;
  324. lastBytesSentTime = os_gettime_ns();
  325. delaySecTotal = 0;
  326. UpdateDelayMsg();
  327. }
  328. void OBSBasicStatusBar::ReconnectSuccess()
  329. {
  330. OBSBasic *main = qobject_cast<OBSBasic *>(parent());
  331. QString msg = QTStr("Basic.StatusBar.ReconnectSuccessful");
  332. showMessage(msg, 4000);
  333. main->SysTrayNotify(msg, QSystemTrayIcon::Information);
  334. ReconnectClear();
  335. if (streamOutput) {
  336. delaySecTotal = obs_output_get_active_delay(streamOutput);
  337. UpdateDelayMsg();
  338. }
  339. }
  340. void OBSBasicStatusBar::UpdateStatusBar()
  341. {
  342. OBSBasic *main = qobject_cast<OBSBasic *>(parent());
  343. UpdateBandwidth();
  344. if (streamOutput)
  345. UpdateStreamTime();
  346. if (recordOutput)
  347. UpdateRecordTime();
  348. UpdateDroppedFrames();
  349. int skipped = video_output_get_skipped_frames(obs_get_video());
  350. int total = video_output_get_total_frames(obs_get_video());
  351. skipped -= startSkippedFrameCount;
  352. total -= startTotalFrameCount;
  353. int diff = skipped - lastSkippedFrameCount;
  354. double percentage = double(skipped) / double(total) * 100.0;
  355. if (diff > 10 && percentage >= 0.1f) {
  356. showMessage(QTStr("HighResourceUsage"), 4000);
  357. if (!main->isVisible() && overloadedNotify) {
  358. main->SysTrayNotify(QTStr("HighResourceUsage"),
  359. QSystemTrayIcon::Warning);
  360. overloadedNotify = false;
  361. }
  362. }
  363. lastSkippedFrameCount = skipped;
  364. }
  365. void OBSBasicStatusBar::StreamDelayStarting(int sec)
  366. {
  367. OBSBasic *main = qobject_cast<OBSBasic *>(parent());
  368. if (!main || !main->outputHandler)
  369. return;
  370. streamOutput = main->outputHandler->streamOutput;
  371. delaySecTotal = delaySecStarting = sec;
  372. UpdateDelayMsg();
  373. Activate();
  374. }
  375. void OBSBasicStatusBar::StreamDelayStopping(int sec)
  376. {
  377. delaySecTotal = delaySecStopping = sec;
  378. UpdateDelayMsg();
  379. }
  380. void OBSBasicStatusBar::StreamStarted(obs_output_t *output)
  381. {
  382. streamOutput = output;
  383. signal_handler_connect(obs_output_get_signal_handler(streamOutput),
  384. "reconnect", OBSOutputReconnect, this);
  385. signal_handler_connect(obs_output_get_signal_handler(streamOutput),
  386. "reconnect_success", OBSOutputReconnectSuccess,
  387. this);
  388. retries = 0;
  389. lastBytesSent = 0;
  390. lastBytesSentTime = os_gettime_ns();
  391. Activate();
  392. }
  393. void OBSBasicStatusBar::StreamStopped()
  394. {
  395. if (streamOutput) {
  396. signal_handler_disconnect(
  397. obs_output_get_signal_handler(streamOutput),
  398. "reconnect", OBSOutputReconnect, this);
  399. signal_handler_disconnect(
  400. obs_output_get_signal_handler(streamOutput),
  401. "reconnect_success", OBSOutputReconnectSuccess, this);
  402. ReconnectClear();
  403. streamOutput = nullptr;
  404. clearMessage();
  405. Deactivate();
  406. }
  407. }
  408. void OBSBasicStatusBar::RecordingStarted(obs_output_t *output)
  409. {
  410. recordOutput = output;
  411. Activate();
  412. }
  413. void OBSBasicStatusBar::RecordingStopped()
  414. {
  415. recordOutput = nullptr;
  416. Deactivate();
  417. }
  418. void OBSBasicStatusBar::RecordingPaused()
  419. {
  420. QString text = recordTime->text() + QStringLiteral(" (PAUSED)");
  421. recordTime->setText(text);
  422. if (recordOutput) {
  423. recordIcon->setPixmap(recordingPausePixmap);
  424. streamPauseIconToggle = true;
  425. }
  426. }
  427. void OBSBasicStatusBar::RecordingUnpaused()
  428. {
  429. if (recordOutput) {
  430. recordIcon->setPixmap(recordingActivePixmap);
  431. }
  432. }