window-basic-status-bar.cpp 12 KB

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