1
0

window-basic-status-bar.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  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. sessionTime (new QLabel),
  14. cpuUsage (new QLabel),
  15. transparentPixmap (20, 20),
  16. greenPixmap (20, 20),
  17. grayPixmap (20, 20),
  18. redPixmap (20, 20)
  19. {
  20. sessionTime->setText(QString("00:00:00"));
  21. cpuUsage->setText(QString("CPU: 0.0%, 0.00 fps"));
  22. QWidget *brWidget = new QWidget(this);
  23. QHBoxLayout *brLayout = new QHBoxLayout(brWidget);
  24. brLayout->setContentsMargins(0, 0, 0, 0);
  25. statusSquare = new QLabel(brWidget);
  26. brLayout->addWidget(statusSquare);
  27. kbps = new QLabel(brWidget);
  28. brLayout->addWidget(kbps);
  29. brWidget->setLayout(brLayout);
  30. delayInfo->setAlignment(Qt::AlignRight);
  31. delayInfo->setAlignment(Qt::AlignVCenter);
  32. droppedFrames->setAlignment(Qt::AlignRight);
  33. droppedFrames->setAlignment(Qt::AlignVCenter);
  34. sessionTime->setAlignment(Qt::AlignRight);
  35. sessionTime->setAlignment(Qt::AlignVCenter);
  36. cpuUsage->setAlignment(Qt::AlignRight);
  37. cpuUsage->setAlignment(Qt::AlignVCenter);
  38. kbps->setAlignment(Qt::AlignRight);
  39. kbps->setAlignment(Qt::AlignVCenter);
  40. delayInfo->setIndent(20);
  41. droppedFrames->setIndent(20);
  42. sessionTime->setIndent(20);
  43. cpuUsage->setIndent(20);
  44. kbps->setIndent(10);
  45. addPermanentWidget(droppedFrames);
  46. addPermanentWidget(sessionTime);
  47. addPermanentWidget(cpuUsage);
  48. addPermanentWidget(delayInfo);
  49. addPermanentWidget(brWidget);
  50. transparentPixmap.fill(QColor(0, 0, 0, 0));
  51. greenPixmap.fill(QColor(0, 255, 0));
  52. grayPixmap.fill(QColor(72, 72, 72));
  53. redPixmap.fill(QColor(255, 0, 0));
  54. statusSquare->setPixmap(transparentPixmap);
  55. }
  56. void OBSBasicStatusBar::Activate()
  57. {
  58. if (!active) {
  59. refreshTimer = new QTimer(this);
  60. connect(refreshTimer, SIGNAL(timeout()),
  61. this, SLOT(UpdateStatusBar()));
  62. int skipped = video_output_get_skipped_frames(obs_get_video());
  63. int total = video_output_get_total_frames(obs_get_video());
  64. totalSeconds = 0;
  65. lastSkippedFrameCount = 0;
  66. startSkippedFrameCount = skipped;
  67. startTotalFrameCount = total;
  68. refreshTimer->start(1000);
  69. active = true;
  70. if (streamOutput) {
  71. statusSquare->setPixmap(grayPixmap);
  72. }
  73. }
  74. }
  75. void OBSBasicStatusBar::Deactivate()
  76. {
  77. OBSBasic *main = qobject_cast<OBSBasic*>(parent());
  78. if (!main)
  79. return;
  80. if (!main->outputHandler->Active()) {
  81. delete refreshTimer;
  82. sessionTime->setText(QString("00:00:00"));
  83. delayInfo->setText("");
  84. droppedFrames->setText("");
  85. kbps->setText("");
  86. delaySecTotal = 0;
  87. delaySecStarting = 0;
  88. delaySecStopping = 0;
  89. reconnectTimeout = 0;
  90. active = false;
  91. overloadedNotify = true;
  92. statusSquare->setPixmap(transparentPixmap);
  93. }
  94. }
  95. void OBSBasicStatusBar::UpdateDelayMsg()
  96. {
  97. QString msg;
  98. if (delaySecTotal) {
  99. if (delaySecStarting && !delaySecStopping) {
  100. msg = QTStr("Basic.StatusBar.DelayStartingIn");
  101. msg = msg.arg(QString::number(delaySecStarting));
  102. } else if (!delaySecStarting && delaySecStopping) {
  103. msg = QTStr("Basic.StatusBar.DelayStoppingIn");
  104. msg = msg.arg(QString::number(delaySecStopping));
  105. } else if (delaySecStarting && delaySecStopping) {
  106. msg = QTStr("Basic.StatusBar.DelayStartingStoppingIn");
  107. msg = msg.arg(QString::number(delaySecStopping),
  108. QString::number(delaySecStarting));
  109. } else {
  110. msg = QTStr("Basic.StatusBar.Delay");
  111. msg = msg.arg(QString::number(delaySecTotal));
  112. }
  113. }
  114. delayInfo->setText(msg);
  115. }
  116. #define BITRATE_UPDATE_SECONDS 2
  117. void OBSBasicStatusBar::UpdateBandwidth()
  118. {
  119. if (!streamOutput)
  120. return;
  121. if (++bitrateUpdateSeconds < BITRATE_UPDATE_SECONDS)
  122. return;
  123. uint64_t bytesSent = obs_output_get_total_bytes(streamOutput);
  124. uint64_t bytesSentTime = os_gettime_ns();
  125. if (bytesSent < lastBytesSent)
  126. bytesSent = 0;
  127. if (bytesSent == 0)
  128. lastBytesSent = 0;
  129. uint64_t bitsBetween = (bytesSent - lastBytesSent) * 8;
  130. double timePassed = double(bytesSentTime - lastBytesSentTime) /
  131. 1000000000.0;
  132. double kbitsPerSec = double(bitsBetween) / timePassed / 1000.0;
  133. QString text;
  134. text += QString("kb/s: ") +
  135. QString::number(kbitsPerSec, 'f', 0);
  136. kbps->setText(text);
  137. kbps->setMinimumWidth(kbps->width());
  138. lastBytesSent = bytesSent;
  139. lastBytesSentTime = bytesSentTime;
  140. bitrateUpdateSeconds = 0;
  141. }
  142. void OBSBasicStatusBar::UpdateCPUUsage()
  143. {
  144. OBSBasic *main = qobject_cast<OBSBasic*>(parent());
  145. if (!main)
  146. return;
  147. QString text;
  148. text += QString("CPU: ") +
  149. QString::number(main->GetCPUUsage(), 'f', 1) + QString("%, ") +
  150. QString::number(obs_get_active_fps(), 'f', 2) + QString(" fps");
  151. cpuUsage->setText(text);
  152. cpuUsage->setMinimumWidth(cpuUsage->width());
  153. }
  154. void OBSBasicStatusBar::UpdateSessionTime()
  155. {
  156. totalSeconds++;
  157. int seconds = totalSeconds % 60;
  158. int totalMinutes = totalSeconds / 60;
  159. int minutes = totalMinutes % 60;
  160. int hours = totalMinutes / 60;
  161. QString text;
  162. text.sprintf("%02d:%02d:%02d", hours, minutes, seconds);
  163. sessionTime->setText(text);
  164. sessionTime->setMinimumWidth(sessionTime->width());
  165. if (reconnectTimeout > 0) {
  166. QString msg = QTStr("Basic.StatusBar.Reconnecting")
  167. .arg(QString::number(retries),
  168. QString::number(reconnectTimeout));
  169. showMessage(msg);
  170. reconnectTimeout--;
  171. } else if (retries > 0) {
  172. QString msg = QTStr("Basic.StatusBar.AttemptingReconnect");
  173. showMessage(msg.arg(QString::number(retries)));
  174. }
  175. if (delaySecStopping > 0 || delaySecStarting > 0) {
  176. if (delaySecStopping > 0)
  177. --delaySecStopping;
  178. if (delaySecStarting > 0)
  179. --delaySecStarting;
  180. UpdateDelayMsg();
  181. }
  182. }
  183. void OBSBasicStatusBar::UpdateDroppedFrames()
  184. {
  185. if (!streamOutput)
  186. return;
  187. int totalDropped = obs_output_get_frames_dropped(streamOutput);
  188. int totalFrames = obs_output_get_total_frames(streamOutput);
  189. double percent = (double)totalDropped / (double)totalFrames * 100.0;
  190. if (!totalFrames)
  191. return;
  192. QString text = QTStr("DroppedFrames");
  193. text = text.arg(QString::number(totalDropped),
  194. QString::number(percent, 'f', 1));
  195. droppedFrames->setText(text);
  196. droppedFrames->setMinimumWidth(droppedFrames->width());
  197. /* ----------------------------------- *
  198. * calculate congestion color */
  199. float congestion = obs_output_get_congestion(streamOutput);
  200. float avgCongestion = (congestion + lastCongestion) * 0.5f;
  201. if (avgCongestion < congestion)
  202. avgCongestion = congestion;
  203. if (avgCongestion > 1.0f)
  204. avgCongestion = 1.0f;
  205. if (avgCongestion < EPSILON) {
  206. statusSquare->setPixmap(greenPixmap);
  207. } else if (fabsf(avgCongestion - 1.0f) < EPSILON) {
  208. statusSquare->setPixmap(redPixmap);
  209. } else {
  210. QPixmap pixmap(20, 20);
  211. float red = avgCongestion * 2.0f;
  212. if (red > 1.0f) red = 1.0f;
  213. red *= 255.0;
  214. float green = (1.0f - avgCongestion) * 2.0f;
  215. if (green > 1.0f) green = 1.0f;
  216. green *= 255.0;
  217. pixmap.fill(QColor(int(red), int(green), 0));
  218. statusSquare->setPixmap(pixmap);
  219. }
  220. lastCongestion = congestion;
  221. }
  222. void OBSBasicStatusBar::OBSOutputReconnect(void *data, calldata_t *params)
  223. {
  224. OBSBasicStatusBar *statusBar =
  225. reinterpret_cast<OBSBasicStatusBar*>(data);
  226. int seconds = (int)calldata_int(params, "timeout_sec");
  227. QMetaObject::invokeMethod(statusBar, "Reconnect", Q_ARG(int, seconds));
  228. UNUSED_PARAMETER(params);
  229. }
  230. void OBSBasicStatusBar::OBSOutputReconnectSuccess(void *data, calldata_t *params)
  231. {
  232. OBSBasicStatusBar *statusBar =
  233. reinterpret_cast<OBSBasicStatusBar*>(data);
  234. QMetaObject::invokeMethod(statusBar, "ReconnectSuccess");
  235. UNUSED_PARAMETER(params);
  236. }
  237. void OBSBasicStatusBar::Reconnect(int seconds)
  238. {
  239. OBSBasic *main = qobject_cast<OBSBasic*>(parent());
  240. if (!retries)
  241. main->SysTrayNotify(
  242. QTStr("Basic.SystemTray.Message.Reconnecting"),
  243. QSystemTrayIcon::Warning);
  244. reconnectTimeout = seconds;
  245. if (streamOutput) {
  246. delaySecTotal = obs_output_get_active_delay(streamOutput);
  247. UpdateDelayMsg();
  248. retries++;
  249. }
  250. }
  251. void OBSBasicStatusBar::ReconnectClear()
  252. {
  253. retries = 0;
  254. reconnectTimeout = 0;
  255. bitrateUpdateSeconds = -1;
  256. lastBytesSent = 0;
  257. lastBytesSentTime = os_gettime_ns();
  258. delaySecTotal = 0;
  259. UpdateDelayMsg();
  260. }
  261. void OBSBasicStatusBar::ReconnectSuccess()
  262. {
  263. OBSBasic *main = qobject_cast<OBSBasic*>(parent());
  264. QString msg = QTStr("Basic.StatusBar.ReconnectSuccessful");
  265. showMessage(msg, 4000);
  266. main->SysTrayNotify(msg, QSystemTrayIcon::Information);
  267. ReconnectClear();
  268. if (streamOutput) {
  269. delaySecTotal = obs_output_get_active_delay(streamOutput);
  270. UpdateDelayMsg();
  271. }
  272. }
  273. void OBSBasicStatusBar::UpdateStatusBar()
  274. {
  275. OBSBasic *main = qobject_cast<OBSBasic*>(parent());
  276. UpdateBandwidth();
  277. UpdateSessionTime();
  278. UpdateDroppedFrames();
  279. int skipped = video_output_get_skipped_frames(obs_get_video());
  280. int total = video_output_get_total_frames(obs_get_video());
  281. skipped -= startSkippedFrameCount;
  282. total -= startTotalFrameCount;
  283. int diff = skipped - lastSkippedFrameCount;
  284. double percentage = double(skipped) / double(total) * 100.0;
  285. if (diff > 10 && percentage >= 0.1f) {
  286. showMessage(QTStr("HighResourceUsage"), 4000);
  287. if (!main->isVisible() && overloadedNotify) {
  288. main->SysTrayNotify(QTStr("HighResourceUsage"),
  289. QSystemTrayIcon::Warning);
  290. overloadedNotify = false;
  291. }
  292. }
  293. lastSkippedFrameCount = skipped;
  294. }
  295. void OBSBasicStatusBar::StreamDelayStarting(int sec)
  296. {
  297. OBSBasic *main = qobject_cast<OBSBasic*>(parent());
  298. if (!main || !main->outputHandler)
  299. return;
  300. streamOutput = main->outputHandler->streamOutput;
  301. delaySecTotal = delaySecStarting = sec;
  302. UpdateDelayMsg();
  303. Activate();
  304. }
  305. void OBSBasicStatusBar::StreamDelayStopping(int sec)
  306. {
  307. delaySecTotal = delaySecStopping = sec;
  308. UpdateDelayMsg();
  309. }
  310. void OBSBasicStatusBar::StreamStarted(obs_output_t *output)
  311. {
  312. streamOutput = output;
  313. signal_handler_connect(obs_output_get_signal_handler(streamOutput),
  314. "reconnect", OBSOutputReconnect, this);
  315. signal_handler_connect(obs_output_get_signal_handler(streamOutput),
  316. "reconnect_success", OBSOutputReconnectSuccess, this);
  317. retries = 0;
  318. lastBytesSent = 0;
  319. lastBytesSentTime = os_gettime_ns();
  320. Activate();
  321. }
  322. void OBSBasicStatusBar::StreamStopped()
  323. {
  324. if (streamOutput) {
  325. signal_handler_disconnect(
  326. obs_output_get_signal_handler(streamOutput),
  327. "reconnect", OBSOutputReconnect, this);
  328. signal_handler_disconnect(
  329. obs_output_get_signal_handler(streamOutput),
  330. "reconnect_success", OBSOutputReconnectSuccess,
  331. this);
  332. ReconnectClear();
  333. streamOutput = nullptr;
  334. clearMessage();
  335. Deactivate();
  336. }
  337. }
  338. void OBSBasicStatusBar::RecordingStarted(obs_output_t *output)
  339. {
  340. recordOutput = output;
  341. Activate();
  342. }
  343. void OBSBasicStatusBar::RecordingStopped()
  344. {
  345. recordOutput = nullptr;
  346. Deactivate();
  347. }