bpm.c 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. #include "obs.h"
  2. #include "bpm-internal.h"
  3. static void render_metrics_time(struct metrics_time *m_time)
  4. {
  5. /* Generate the RFC3339 time string from the timespec struct, for example:
  6. *
  7. * "2024-05-31T12:26:03.591Z"
  8. */
  9. memset(&m_time->rfc3339_str, 0, sizeof(m_time->rfc3339_str));
  10. strftime(m_time->rfc3339_str, sizeof(m_time->rfc3339_str), "%Y-%m-%dT%T", gmtime(&m_time->tspec.tv_sec));
  11. sprintf(m_time->rfc3339_str + strlen(m_time->rfc3339_str), ".%03ldZ", m_time->tspec.tv_nsec / 1000000);
  12. m_time->valid = true;
  13. }
  14. static bool update_metrics(obs_output_t *output, const struct encoder_packet *pkt,
  15. const struct encoder_packet_time *ept, struct metrics_data *m_track)
  16. {
  17. if (!output || !pkt || !ept || !m_track) {
  18. blog(LOG_DEBUG, "%s: Null arguments for track %lu", __FUNCTION__, pkt->track_idx);
  19. return false;
  20. }
  21. // Perform reads on all the counters as close together as possible
  22. m_track->session_frames_output.curr = obs_output_get_total_frames(output);
  23. m_track->session_frames_dropped.curr = obs_output_get_frames_dropped(output);
  24. m_track->session_frames_rendered.curr = obs_get_total_frames();
  25. m_track->session_frames_lagged.curr = obs_get_lagged_frames();
  26. const video_t *video = obs_encoder_video(pkt->encoder);
  27. if (video) {
  28. /* video_output_get_total_frames() returns the number of frames
  29. * before the framerate decimator. For example, if the OBS session
  30. * is rendering at 60fps, and the rendition is set for 30 fps,
  31. * the counter will increment by 60 per second, not 30 per second.
  32. * For metrics we will consider this value to be the number of
  33. * frames input to the obs_encoder_t instance.
  34. */
  35. m_track->rendition_frames_input.curr = video_output_get_total_frames(video);
  36. m_track->rendition_frames_skipped.curr = video_output_get_skipped_frames(video);
  37. /* obs_encoder_get_encoded_frames() returns the number of frames
  38. * successfully encoded by the obs_encoder_t instance.
  39. */
  40. m_track->rendition_frames_output.curr = obs_encoder_get_encoded_frames(pkt->encoder);
  41. } else {
  42. m_track->rendition_frames_input.curr = 0;
  43. m_track->rendition_frames_skipped.curr = 0;
  44. m_track->rendition_frames_output.curr = 0;
  45. blog(LOG_ERROR, "update_metrics(): *video_t==null");
  46. }
  47. // Set the diff values to 0 if PTS is 0
  48. if (pkt->pts == 0) {
  49. m_track->session_frames_output.diff = 0;
  50. m_track->session_frames_dropped.diff = 0;
  51. m_track->session_frames_rendered.diff = 0;
  52. m_track->session_frames_lagged.diff = 0;
  53. m_track->rendition_frames_input.diff = 0;
  54. m_track->rendition_frames_skipped.diff = 0;
  55. m_track->rendition_frames_output.diff = 0;
  56. blog(LOG_DEBUG, "update_metrics(): Setting diffs to 0");
  57. } else {
  58. // Calculate diff's
  59. m_track->session_frames_output.diff =
  60. m_track->session_frames_output.curr - m_track->session_frames_output.ref;
  61. m_track->session_frames_dropped.diff =
  62. m_track->session_frames_dropped.curr - m_track->session_frames_dropped.ref;
  63. m_track->session_frames_rendered.diff =
  64. m_track->session_frames_rendered.curr - m_track->session_frames_rendered.ref;
  65. m_track->session_frames_lagged.diff =
  66. m_track->session_frames_lagged.curr - m_track->session_frames_lagged.ref;
  67. m_track->rendition_frames_input.diff =
  68. m_track->rendition_frames_input.curr - m_track->rendition_frames_input.ref;
  69. m_track->rendition_frames_skipped.diff =
  70. m_track->rendition_frames_skipped.curr - m_track->rendition_frames_skipped.ref;
  71. m_track->rendition_frames_output.diff =
  72. m_track->rendition_frames_output.curr - m_track->rendition_frames_output.ref;
  73. }
  74. // Update the reference values
  75. m_track->session_frames_output.ref = m_track->session_frames_output.curr;
  76. m_track->session_frames_dropped.ref = m_track->session_frames_dropped.curr;
  77. m_track->session_frames_rendered.ref = m_track->session_frames_rendered.curr;
  78. m_track->session_frames_lagged.ref = m_track->session_frames_lagged.curr;
  79. m_track->rendition_frames_input.ref = m_track->rendition_frames_input.curr;
  80. m_track->rendition_frames_skipped.ref = m_track->rendition_frames_skipped.curr;
  81. m_track->rendition_frames_output.ref = m_track->rendition_frames_output.curr;
  82. /* BPM Timestamp Message */
  83. m_track->cts.valid = false;
  84. m_track->ferts.valid = false;
  85. m_track->fercts.valid = false;
  86. /* Generate the timestamp representations for CTS, FER, and FERC.
  87. * Check if each is non-zero and that temporal consistency is correct:
  88. * FEC > FERC > CTS
  89. * FEC and FERC depends on CTS, and FERC depends on FER, so ensure
  90. * we only signal an integral set of timestamps.
  91. */
  92. os_nstime_to_timespec(ept->cts, &m_track->cts.tspec);
  93. render_metrics_time(&m_track->cts);
  94. if (ept->fer && (ept->fer > ept->cts)) {
  95. os_nstime_to_timespec(ept->fer, &m_track->ferts.tspec);
  96. render_metrics_time(&m_track->ferts);
  97. if (ept->ferc && (ept->ferc > ept->fer)) {
  98. os_nstime_to_timespec(ept->ferc, &m_track->fercts.tspec);
  99. render_metrics_time(&m_track->fercts);
  100. }
  101. }
  102. // Always generate the timestamp representation for PIR
  103. m_track->pirts.valid = false;
  104. os_nstime_to_timespec(ept->pir, &m_track->pirts.tspec);
  105. render_metrics_time(&m_track->pirts);
  106. /* Log the BPM timestamp and frame counter information. This
  107. * provides visibility into the metrics when OBS is started
  108. * with "--verbose" and "--unfiltered_log".
  109. */
  110. blog(LOG_DEBUG,
  111. "BPM: %s, trk %lu: [CTS|FER-CTS|FERC-FER|PIR-CTS]:[%" PRIu64 " ms|%" PRIu64 " ms|%" PRIu64 " us|%" PRIu64
  112. " ms], [dts|pts]:[%" PRId64 "|%" PRId64 "], S[R:O:D:L],R[I:S:O]:%d:%d:%d:%d:%d:%d:%d",
  113. obs_encoder_get_name(pkt->encoder), pkt->track_idx, ept->cts / 1000000, (ept->fer - ept->cts) / 1000000,
  114. (ept->ferc - ept->fer) / 1000, (ept->pir - ept->cts) / 1000000, pkt->dts, pkt->pts,
  115. m_track->session_frames_rendered.diff, m_track->session_frames_output.diff,
  116. m_track->session_frames_dropped.diff, m_track->session_frames_lagged.diff,
  117. m_track->rendition_frames_input.diff, m_track->rendition_frames_skipped.diff,
  118. m_track->rendition_frames_output.diff);
  119. return true;
  120. }
  121. void bpm_ts_sei_render(struct metrics_data *m_track)
  122. {
  123. uint8_t num_timestamps = 0;
  124. struct serializer s;
  125. m_track->sei_rendered[BPM_TS_SEI] = false;
  126. // Initialize the output array here; caller is responsible to free it
  127. array_output_serializer_init(&s, &m_track->sei_payload[BPM_TS_SEI]);
  128. // Write the UUID for this SEI message
  129. s_write(&s, bpm_ts_uuid, sizeof(bpm_ts_uuid));
  130. // Determine how many timestamps are valid
  131. if (m_track->cts.valid)
  132. num_timestamps++;
  133. if (m_track->ferts.valid)
  134. num_timestamps++;
  135. if (m_track->fercts.valid)
  136. num_timestamps++;
  137. if (m_track->pirts.valid)
  138. num_timestamps++;
  139. /* Encode number of timestamps for this SEI. Upper 4 bits are
  140. * set to b0000 (reserved); lower 4-bits num_timestamps - 1.
  141. */
  142. s_w8(&s, (num_timestamps - 1) & 0x0F);
  143. if (m_track->cts.valid) {
  144. // Timestamp type
  145. s_w8(&s, BPM_TS_RFC3339);
  146. // Write the timestamp event tag (Composition Time Event)
  147. s_w8(&s, BPM_TS_EVENT_CTS);
  148. // Write the RFC3339-formatted string, including the null terminator
  149. s_write(&s, m_track->cts.rfc3339_str, strlen(m_track->cts.rfc3339_str) + 1);
  150. }
  151. if (m_track->ferts.valid) {
  152. // Timestamp type
  153. s_w8(&s, BPM_TS_RFC3339);
  154. // Write the timestamp event tag (Frame Encode Request Event)
  155. s_w8(&s, BPM_TS_EVENT_FER);
  156. // Write the RFC3339-formatted string, including the null terminator
  157. s_write(&s, m_track->ferts.rfc3339_str, strlen(m_track->ferts.rfc3339_str) + 1);
  158. }
  159. if (m_track->fercts.valid) {
  160. // Timestamp type
  161. s_w8(&s, BPM_TS_RFC3339);
  162. // Write the timestamp event tag (Frame Encode Request Complete Event)
  163. s_w8(&s, BPM_TS_EVENT_FERC);
  164. // Write the RFC3339-formatted string, including the null terminator
  165. s_write(&s, m_track->fercts.rfc3339_str, strlen(m_track->fercts.rfc3339_str) + 1);
  166. }
  167. if (m_track->pirts.valid) {
  168. // Timestamp type
  169. s_w8(&s, BPM_TS_RFC3339);
  170. // Write the timestamp event tag (Packet Interleave Request Event)
  171. s_w8(&s, BPM_TS_EVENT_PIR);
  172. // Write the RFC3339-formatted string, including the null terminator
  173. s_write(&s, m_track->pirts.rfc3339_str, strlen(m_track->pirts.rfc3339_str) + 1);
  174. }
  175. m_track->sei_rendered[BPM_TS_SEI] = true;
  176. }
  177. void bpm_sm_sei_render(struct metrics_data *m_track)
  178. {
  179. uint8_t num_timestamps = 0;
  180. uint8_t num_counters = 0;
  181. struct serializer s;
  182. m_track->sei_rendered[BPM_SM_SEI] = false;
  183. // Initialize the output array here; caller is responsible to free it
  184. array_output_serializer_init(&s, &m_track->sei_payload[BPM_SM_SEI]);
  185. // Write the UUID for this SEI message
  186. s_write(&s, bpm_sm_uuid, sizeof(bpm_sm_uuid));
  187. // Encode number of timestamps for this SEI
  188. num_timestamps = 1;
  189. // Upper 4 bits are set to b0000 (reserved); lower 4-bits num_timestamps - 1
  190. s_w8(&s, (num_timestamps - 1) & 0x0F);
  191. // Timestamp type
  192. s_w8(&s, BPM_TS_RFC3339);
  193. /* Write the timestamp event tag (Packet Interleave Request Event).
  194. * Use the PIR_TS timestamp because the data was all collected at that time.
  195. */
  196. s_w8(&s, BPM_TS_EVENT_PIR);
  197. // Write the RFC3339-formatted string, including the null terminator
  198. s_write(&s, m_track->pirts.rfc3339_str, strlen(m_track->pirts.rfc3339_str) + 1);
  199. // Session metrics has 4 counters
  200. num_counters = 4;
  201. /* Send all the counters with a tag(8-bit):value(32-bit) configuration.
  202. * Upper 4 bits are set to b0000 (reserved); lower 4-bits num_counters - 1.
  203. */
  204. s_w8(&s, (num_counters - 1) & 0x0F);
  205. s_w8(&s, BPM_SM_FRAMES_RENDERED);
  206. s_wb32(&s, m_track->session_frames_rendered.diff);
  207. s_w8(&s, BPM_SM_FRAMES_LAGGED);
  208. s_wb32(&s, m_track->session_frames_lagged.diff);
  209. s_w8(&s, BPM_SM_FRAMES_DROPPED);
  210. s_wb32(&s, m_track->session_frames_dropped.diff);
  211. s_w8(&s, BPM_SM_FRAMES_OUTPUT);
  212. s_wb32(&s, m_track->session_frames_output.diff);
  213. m_track->sei_rendered[BPM_SM_SEI] = true;
  214. }
  215. void bpm_erm_sei_render(struct metrics_data *m_track)
  216. {
  217. uint8_t num_timestamps = 0;
  218. uint8_t num_counters = 0;
  219. struct serializer s;
  220. m_track->sei_rendered[BPM_ERM_SEI] = false;
  221. // Initialize the output array here; caller is responsible to free it
  222. array_output_serializer_init(&s, &m_track->sei_payload[BPM_ERM_SEI]);
  223. // Write the UUID for this SEI message
  224. s_write(&s, bpm_erm_uuid, sizeof(bpm_erm_uuid));
  225. // Encode number of timestamps for this SEI
  226. num_timestamps = 1;
  227. // Upper 4 bits are set to b0000 (reserved); lower 4-bits num_timestamps - 1
  228. s_w8(&s, (num_timestamps - 1) & 0x0F);
  229. // Timestamp type
  230. s_w8(&s, BPM_TS_RFC3339);
  231. /* Write the timestamp event tag (Packet Interleave Request Event).
  232. * Use the PIRTS timestamp because the data was all collected at that time.
  233. */
  234. s_w8(&s, BPM_TS_EVENT_PIR);
  235. // Write the RFC3339-formatted string, including the null terminator
  236. s_write(&s, m_track->pirts.rfc3339_str, strlen(m_track->pirts.rfc3339_str) + 1);
  237. // Encoder rendition metrics has 3 counters
  238. num_counters = 3;
  239. /* Send all the counters with a tag(8-bit):value(32-bit) configuration.
  240. * Upper 4 bits are set to b0000 (reserved); lower 4-bits num_counters - 1.
  241. */
  242. s_w8(&s, (num_counters - 1) & 0x0F);
  243. s_w8(&s, BPM_ERM_FRAMES_INPUT);
  244. s_wb32(&s, m_track->rendition_frames_input.diff);
  245. s_w8(&s, BPM_ERM_FRAMES_SKIPPED);
  246. s_wb32(&s, m_track->rendition_frames_skipped.diff);
  247. s_w8(&s, BPM_ERM_FRAMES_OUTPUT);
  248. s_wb32(&s, m_track->rendition_frames_output.diff);
  249. m_track->sei_rendered[BPM_ERM_SEI] = true;
  250. }
  251. /* Note : extract_buffer_from_sei() and nal_start are also defined
  252. * in obs-output.c, however they are not public APIs. When the caption
  253. * library is re-worked, this code should be refactored into that.
  254. */
  255. static size_t extract_buffer_from_sei(sei_t *sei, uint8_t **data_out)
  256. {
  257. if (!sei || !sei->head) {
  258. return 0;
  259. }
  260. /* We should only need to get one payload, because the SEI that was
  261. * generated should only have one message, so no need to iterate. If
  262. * we did iterate, we would need to generate multiple OBUs. */
  263. sei_message_t *msg = sei_message_head(sei);
  264. int payload_size = (int)sei_message_size(msg);
  265. uint8_t *payload_data = sei_message_data(msg);
  266. *data_out = bmalloc(payload_size);
  267. memcpy(*data_out, payload_data, payload_size);
  268. return payload_size;
  269. }
  270. static const uint8_t nal_start[4] = {0, 0, 0, 1};
  271. /* process_metrics() will update and insert unregistered
  272. * SEI (AVC/HEVC) or OBU (AV1) messages into the encoded
  273. * video bitstream.
  274. */
  275. static bool process_metrics(obs_output_t *output, struct encoder_packet *out, struct encoder_packet_time *ept,
  276. struct metrics_data *m_track)
  277. {
  278. struct encoder_packet backup = *out;
  279. sei_t sei;
  280. uint8_t *data = NULL;
  281. size_t size;
  282. long ref = 1;
  283. bool avc = false;
  284. bool hevc = false;
  285. bool av1 = false;
  286. if (!m_track) {
  287. blog(LOG_DEBUG, "Metrics track for index: %lu had not be initialized", out->track_idx);
  288. return false;
  289. }
  290. // Update the metrics for this track
  291. if (!update_metrics(output, out, ept, m_track)) {
  292. // Something went wrong; log it and return
  293. blog(LOG_DEBUG, "update_metrics() for track index: %lu failed", out->track_idx);
  294. return false;
  295. }
  296. if (strcmp(obs_encoder_get_codec(out->encoder), "h264") == 0) {
  297. avc = true;
  298. } else if (strcmp(obs_encoder_get_codec(out->encoder), "av1") == 0) {
  299. av1 = true;
  300. #ifdef ENABLE_HEVC
  301. } else if (strcmp(obs_encoder_get_codec(out->encoder), "hevc") == 0) {
  302. hevc = true;
  303. #endif
  304. }
  305. #ifdef ENABLE_HEVC
  306. uint8_t hevc_nal_header[2];
  307. if (hevc) {
  308. size_t nal_header_index_start = 4;
  309. // Skip past the annex-b start code
  310. if (memcmp(out->data, nal_start + 1, 3) == 0) {
  311. nal_header_index_start = 3;
  312. } else if (memcmp(out->data, nal_start, 4) == 0) {
  313. nal_header_index_start = 4;
  314. } else {
  315. /* We shouldn't ever see this unless we start getting
  316. * packets without annex-b start codes. */
  317. blog(LOG_DEBUG, "Annex-B start code not found, we may not "
  318. "generate a valid hevc nal unit header "
  319. "for our caption");
  320. return false;
  321. }
  322. /* We will use the same 2 byte NAL unit header for the SEI,
  323. * but swap the NAL types out. */
  324. hevc_nal_header[0] = out->data[nal_header_index_start];
  325. hevc_nal_header[1] = out->data[nal_header_index_start + 1];
  326. }
  327. #endif
  328. // Create array for the original packet data + the SEI appended data
  329. DARRAY(uint8_t) out_data;
  330. da_init(out_data);
  331. // Copy the original packet
  332. da_push_back_array(out_data, (uint8_t *)&ref, sizeof(ref));
  333. da_push_back_array(out_data, out->data, out->size);
  334. // Build the SEI metrics message payload
  335. bpm_ts_sei_render(m_track);
  336. bpm_sm_sei_render(m_track);
  337. bpm_erm_sei_render(m_track);
  338. // Iterate over all the BPM SEI types
  339. for (uint8_t i = 0; i < BPM_MAX_SEI; ++i) {
  340. // Create and inject the syntax specific SEI messages in the bitstream if the rendering was successful
  341. if (m_track->sei_rendered[i]) {
  342. // Send one SEI message per NALU or OBU
  343. sei_init(&sei, 0.0);
  344. // Generate the formatted SEI message
  345. sei_message_t *msg = sei_message_new(sei_type_user_data_unregistered,
  346. m_track->sei_payload[i].bytes.array,
  347. m_track->sei_payload[i].bytes.num);
  348. sei_message_append(&sei, msg);
  349. // Free the SEI payload buffer in the metrics track
  350. array_output_serializer_free(&m_track->sei_payload[i]);
  351. // Update for any codec specific syntax and add to the output bitstream
  352. if (avc || hevc || av1) {
  353. if (avc || hevc) {
  354. data = bmalloc(sei_render_size(&sei));
  355. size = sei_render(&sei, data);
  356. }
  357. /* In each of these specs there is an identical structure that
  358. * carries user private metadata. We have an AVC SEI wrapped
  359. * version of that here. We will strip it out and repackage
  360. * it slightly to fit the different codec carrying mechanisms.
  361. * A slightly modified SEI for HEVC and a metadata OBU for AV1.
  362. */
  363. if (avc) {
  364. /* TODO: SEI should come after AUD/SPS/PPS,
  365. * but before any VCL */
  366. da_push_back_array(out_data, nal_start, 4);
  367. da_push_back_array(out_data, data, size);
  368. #ifdef ENABLE_HEVC
  369. } else if (hevc) {
  370. /* Only first NAL (VPS/PPS/SPS) should use the 4 byte
  371. * start code. SEIs use 3 byte version */
  372. da_push_back_array(out_data, nal_start + 1, 3);
  373. /* nal_unit_header( ) {
  374. * forbidden_zero_bit f(1)
  375. * nal_unit_type u(6)
  376. * nuh_layer_id u(6)
  377. * nuh_temporal_id_plus1 u(3)
  378. * }
  379. */
  380. const uint8_t prefix_sei_nal_type = 39;
  381. /* The first bit is always 0, so we just need to
  382. * save the last bit off the original header and
  383. * add the SEI NAL type. */
  384. uint8_t first_byte = (prefix_sei_nal_type << 1) | (0x01 & hevc_nal_header[0]);
  385. hevc_nal_header[0] = first_byte;
  386. /* The HEVC NAL unit header is 2 byte instead of
  387. * one, otherwise everything else is the
  388. * same. */
  389. da_push_back_array(out_data, hevc_nal_header, 2);
  390. da_push_back_array(out_data, &data[1], size - 1);
  391. #endif
  392. } else if (av1) {
  393. uint8_t *obu_buffer = NULL;
  394. size_t obu_buffer_size = 0;
  395. size = extract_buffer_from_sei(&sei, &data);
  396. metadata_obu(data, size, &obu_buffer, &obu_buffer_size,
  397. METADATA_TYPE_USER_PRIVATE_6);
  398. if (obu_buffer) {
  399. da_push_back_array(out_data, obu_buffer, obu_buffer_size);
  400. bfree(obu_buffer);
  401. }
  402. }
  403. if (data) {
  404. bfree(data);
  405. }
  406. }
  407. sei_free(&sei);
  408. }
  409. }
  410. obs_encoder_packet_release(out);
  411. *out = backup;
  412. out->data = (uint8_t *)out_data.array + sizeof(ref);
  413. out->size = out_data.num - sizeof(ref);
  414. if (avc || hevc || av1) {
  415. return true;
  416. }
  417. return false;
  418. }
  419. static struct metrics_data *bpm_create_metrics_track(void)
  420. {
  421. struct metrics_data *rval = bzalloc(sizeof(struct metrics_data));
  422. pthread_mutex_init_value(&rval->metrics_mutex);
  423. if (pthread_mutex_init(&rval->metrics_mutex, NULL) != 0) {
  424. bfree(rval);
  425. rval = NULL;
  426. }
  427. return rval;
  428. }
  429. static bool bpm_get_track(obs_output_t *output, size_t track, struct metrics_data **m_track)
  430. {
  431. bool found = false;
  432. // Walk the DARRAY looking for the output pointer
  433. pthread_mutex_lock(&bpm_metrics_mutex);
  434. for (size_t i = bpm_metrics.num; i > 0; i--) {
  435. if (output == bpm_metrics.array[i - 1].output) {
  436. *m_track = bpm_metrics.array[i - 1].metrics_tracks[track];
  437. found = true;
  438. break;
  439. }
  440. }
  441. if (!found) {
  442. // Create the new BPM metrics entries
  443. struct output_metrics_link *oml = da_push_back_new(bpm_metrics);
  444. oml->output = output;
  445. for (size_t i = 0; i < MAX_OUTPUT_VIDEO_ENCODERS; ++i) {
  446. oml->metrics_tracks[i] = bpm_create_metrics_track();
  447. }
  448. *m_track = oml->metrics_tracks[track];
  449. found = true;
  450. }
  451. pthread_mutex_unlock(&bpm_metrics_mutex);
  452. return found;
  453. }
  454. static void bpm_init(void)
  455. {
  456. pthread_mutex_init_value(&bpm_metrics_mutex);
  457. da_init(bpm_metrics);
  458. }
  459. void bpm_destroy(obs_output_t *output)
  460. {
  461. int64_t idx = -1;
  462. pthread_once(&bpm_once, bpm_init);
  463. pthread_mutex_lock(&bpm_metrics_mutex);
  464. // Walk the DARRAY looking for the index that matches the output
  465. for (size_t i = bpm_metrics.num; i > 0; i--) {
  466. if (output == bpm_metrics.array[i - 1].output) {
  467. idx = i - 1;
  468. break;
  469. }
  470. }
  471. if (idx >= 0) {
  472. struct output_metrics_link *oml = &bpm_metrics.array[idx];
  473. for (size_t i = 0; i < MAX_OUTPUT_VIDEO_ENCODERS; i++) {
  474. if (oml->metrics_tracks[i]) {
  475. struct metrics_data *m_track = oml->metrics_tracks[i];
  476. for (uint8_t j = 0; j < BPM_MAX_SEI; ++j) {
  477. array_output_serializer_free(&m_track->sei_payload[j]);
  478. }
  479. pthread_mutex_destroy(&m_track->metrics_mutex);
  480. bfree(m_track);
  481. m_track = NULL;
  482. }
  483. }
  484. da_erase(bpm_metrics, idx);
  485. if (bpm_metrics.num == 0)
  486. da_free(bpm_metrics);
  487. }
  488. pthread_mutex_unlock(&bpm_metrics_mutex);
  489. }
  490. /* bpm_inject() is the callback function that needs to be registered
  491. * with each output needing Broadcast Performance Metrics injected
  492. * into the video bitstream, using SEI (AVC/HEVC) and OBU (AV1) syntax.
  493. */
  494. void bpm_inject(obs_output_t *output, struct encoder_packet *pkt, struct encoder_packet_time *pkt_time, void *param)
  495. {
  496. UNUSED_PARAMETER(param);
  497. pthread_once(&bpm_once, bpm_init);
  498. if (!output || !pkt) {
  499. blog(LOG_DEBUG, "%s: Null pointer arguments supplied, returning", __FUNCTION__);
  500. return;
  501. }
  502. /* Insert BPM on video frames and only when a keyframe
  503. * is detected.
  504. */
  505. if (pkt->type == OBS_ENCODER_VIDEO && pkt->keyframe) {
  506. /* Video packet must have pkt_timing supplied for BPM */
  507. if (!pkt_time) {
  508. blog(LOG_DEBUG, "%s: Packet timing missing for track %ld, PTS %" PRId64, __FUNCTION__,
  509. pkt->track_idx, pkt->pts);
  510. return;
  511. }
  512. /* Get the metrics track associated with the output.
  513. * Allocate BPM metrics structures for the output if needed.
  514. */
  515. struct metrics_data *m_track = NULL;
  516. if (!bpm_get_track(output, pkt->track_idx, &m_track)) {
  517. blog(LOG_DEBUG, "%s: BPM metrics track not found!", __FUNCTION__);
  518. return;
  519. }
  520. pthread_mutex_lock(&m_track->metrics_mutex);
  521. /* Update the metrics and generate BPM messages. */
  522. if (!process_metrics(output, pkt, pkt_time, m_track)) {
  523. blog(LOG_DEBUG, "%s: BPM injection processing failed", __FUNCTION__);
  524. }
  525. pthread_mutex_unlock(&m_track->metrics_mutex);
  526. }
  527. }