mp4-output.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. /******************************************************************************
  2. Copyright (C) 2024 by Dennis Sädtler <[email protected]>
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published by
  5. the Free Software Foundation, either version 2 of the License, or
  6. (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. ******************************************************************************/
  14. #include "mp4-mux.h"
  15. #include <inttypes.h>
  16. #include <obs-module.h>
  17. #include <util/platform.h>
  18. #include <util/deque.h>
  19. #include <util/dstr.h>
  20. #include <util/threading.h>
  21. #include <util/buffered-file-serializer.h>
  22. #include <bpm.h>
  23. #include <opts-parser.h>
  24. #define do_log(level, format, ...) \
  25. blog(level, "[%s output: '%s'] " format, out->muxer_flavor == FLAVOR_MOV ? "mov" : "mp4", \
  26. obs_output_get_name(out->output), ##__VA_ARGS__)
  27. #define warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__)
  28. #define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__)
  29. struct chapter {
  30. uint64_t ts;
  31. char *name;
  32. };
  33. struct mp4_output {
  34. obs_output_t *output;
  35. struct dstr path;
  36. /* File serializer buffer configuration */
  37. size_t buffer_size;
  38. size_t chunk_size;
  39. struct serializer serializer;
  40. bool enable_bpm;
  41. volatile bool active;
  42. volatile bool stopping;
  43. uint64_t stop_ts;
  44. bool allow_overwrite;
  45. uint64_t total_bytes;
  46. pthread_mutex_t mutex;
  47. struct mp4_mux *muxer;
  48. enum mp4_flavor muxer_flavor;
  49. int flags;
  50. size_t chapter_ctr;
  51. struct deque chapters;
  52. /* File splitting stuff */
  53. bool split_file_enabled;
  54. bool split_file_ready;
  55. volatile bool manual_split;
  56. size_t cur_size;
  57. size_t max_size;
  58. int64_t start_time;
  59. int64_t max_time;
  60. /* Buffer for packets while we reinitialise the muxer after splitting */
  61. DARRAY(struct encoder_packet) split_buffer;
  62. };
  63. static inline bool stopping(struct mp4_output *out)
  64. {
  65. return os_atomic_load_bool(&out->stopping);
  66. }
  67. static inline bool active(struct mp4_output *out)
  68. {
  69. return os_atomic_load_bool(&out->active);
  70. }
  71. static inline int64_t packet_pts_usec(struct encoder_packet *packet)
  72. {
  73. return packet->pts * 1000000 / packet->timebase_den;
  74. }
  75. static const char *mp4_output_name(void *unused)
  76. {
  77. UNUSED_PARAMETER(unused);
  78. return obs_module_text("MP4Output");
  79. }
  80. static const char *mov_output_name(void *unused)
  81. {
  82. UNUSED_PARAMETER(unused);
  83. return obs_module_text("MOVOutput");
  84. }
  85. static void mp4_clear_chapters(struct mp4_output *out)
  86. {
  87. while (out->chapters.size) {
  88. struct chapter *chap = deque_data(&out->chapters, 0);
  89. bfree(chap->name);
  90. deque_pop_front(&out->chapters, NULL, sizeof(struct chapter));
  91. }
  92. out->chapter_ctr = 0;
  93. }
  94. static void mp4_output_destroy(void *data)
  95. {
  96. struct mp4_output *out = data;
  97. pthread_mutex_destroy(&out->mutex);
  98. mp4_clear_chapters(out);
  99. deque_free(&out->chapters);
  100. dstr_free(&out->path);
  101. bfree(out);
  102. }
  103. void mp4_pkt_callback(obs_output_t *output, struct encoder_packet *pkt, struct encoder_packet_time *pkt_time,
  104. void *param)
  105. {
  106. UNUSED_PARAMETER(output);
  107. struct mp4_output *out = param;
  108. /* Only the primary video track is used as the reference for the chapter track. */
  109. if (!pkt_time || !pkt || pkt->type != OBS_ENCODER_VIDEO || pkt->track_idx != 0)
  110. return;
  111. pthread_mutex_lock(&out->mutex);
  112. struct chapter *chap = NULL;
  113. /* Write all queued chapters with a timestamp <= the current frame's composition time. */
  114. while (out->chapters.size) {
  115. chap = deque_data(&out->chapters, 0);
  116. if (chap->ts > pkt_time->cts)
  117. break;
  118. /* Video frames can be out of order (b-frames), so instead of using the video packet's dts_usec we need to calculate
  119. * the chapter DTS from the frame's PTS (for chapters DTS == PTS). */
  120. int64_t chap_dts_usec = pkt->pts * 1000000 / pkt->timebase_den;
  121. int64_t chap_dts_msec = chap_dts_usec / 1000;
  122. int64_t chap_dts_sec = chap_dts_msec / 1000;
  123. int milliseconds = (int)(chap_dts_msec % 1000);
  124. int seconds = (int)chap_dts_sec % 60;
  125. int minutes = ((int)chap_dts_sec / 60) % 60;
  126. int hours = (int)chap_dts_sec / 3600;
  127. info("Adding chapter \"%s\" at %02d:%02d:%02d.%03d", chap->name, hours, minutes, seconds, milliseconds);
  128. mp4_mux_add_chapter(out->muxer, chap_dts_usec, chap->name);
  129. /* Free name and remove chapter from queue. */
  130. bfree(chap->name);
  131. deque_pop_front(&out->chapters, NULL, sizeof(struct chapter));
  132. }
  133. pthread_mutex_unlock(&out->mutex);
  134. }
  135. static void mp4_add_chapter_proc(void *data, calldata_t *cd)
  136. {
  137. struct mp4_output *out = data;
  138. struct dstr name = {0};
  139. dstr_copy(&name, calldata_string(cd, "chapter_name"));
  140. if (name.len == 0) {
  141. /* Generate name if none provided. */
  142. dstr_catf(&name, "%s %zu", obs_module_text("MP4Output.UnnamedChapter"), out->chapter_ctr + 1);
  143. }
  144. pthread_mutex_lock(&out->mutex);
  145. /* Enqueue chapter marker to be inserted once a packet containing the video frame with the corresponding timestamp is
  146. * being processed. */
  147. struct chapter chap = {0};
  148. chap.ts = obs_get_video_frame_time();
  149. chap.name = name.array;
  150. deque_push_back(&out->chapters, &chap, sizeof(struct chapter));
  151. out->chapter_ctr++;
  152. pthread_mutex_unlock(&out->mutex);
  153. }
  154. static void split_file_proc(void *data, calldata_t *cd)
  155. {
  156. struct mp4_output *out = data;
  157. calldata_set_bool(cd, "split_file_enabled", out->split_file_enabled);
  158. if (!out->split_file_enabled)
  159. return;
  160. os_atomic_set_bool(&out->manual_split, true);
  161. }
  162. static void *mp4_output_create_internal(obs_data_t *settings, obs_output_t *output, enum mp4_flavor flavor)
  163. {
  164. struct mp4_output *out = bzalloc(sizeof(struct mp4_output));
  165. out->output = output;
  166. out->muxer_flavor = flavor;
  167. pthread_mutex_init(&out->mutex, NULL);
  168. signal_handler_t *sh = obs_output_get_signal_handler(output);
  169. signal_handler_add(sh, "void file_changed(string next_file)");
  170. proc_handler_t *ph = obs_output_get_proc_handler(output);
  171. proc_handler_add(ph, "void split_file(out bool split_file_enabled)", split_file_proc, out);
  172. proc_handler_add(ph, "void add_chapter(string chapter_name)", mp4_add_chapter_proc, out);
  173. UNUSED_PARAMETER(settings);
  174. return out;
  175. }
  176. static void *mp4_output_create(obs_data_t *settings, obs_output_t *output)
  177. {
  178. return mp4_output_create_internal(settings, output, FLAVOR_MP4);
  179. }
  180. static void *mov_output_create(obs_data_t *settings, obs_output_t *output)
  181. {
  182. return mp4_output_create_internal(settings, output, FLAVOR_MOV);
  183. }
  184. static inline void apply_flag(int *flags, const char *value, int flag_value)
  185. {
  186. if (atoi(value))
  187. *flags |= flag_value;
  188. else
  189. *flags &= ~flag_value;
  190. }
  191. static void parse_custom_options(struct mp4_output *out, const char *opts_str)
  192. {
  193. int flags = MP4_USE_NEGATIVE_CTS;
  194. struct obs_options opts = obs_parse_options(opts_str);
  195. for (size_t i = 0; i < opts.count; i++) {
  196. struct obs_option opt = opts.options[i];
  197. if (strcmp(opt.name, "skip_soft_remux") == 0) {
  198. apply_flag(&flags, opt.value, MP4_SKIP_FINALISATION);
  199. } else if (strcmp(opt.name, "write_encoder_info") == 0) {
  200. apply_flag(&flags, opt.value, MP4_WRITE_ENCODER_INFO);
  201. } else if (strcmp(opt.name, "use_metadata_tags") == 0) {
  202. apply_flag(&flags, opt.value, MP4_USE_MDTA_KEY_VALUE);
  203. } else if (strcmp(opt.name, "use_negative_cts") == 0) {
  204. apply_flag(&flags, opt.value, MP4_USE_NEGATIVE_CTS);
  205. } else if (strcmp(opt.name, "buffer_size") == 0) {
  206. out->buffer_size = strtoull(opt.value, 0, 10) * 1048576ULL;
  207. } else if (strcmp(opt.name, "chunk_size") == 0) {
  208. out->chunk_size = strtoull(opt.value, 0, 10) * 1048576ULL;
  209. } else if (strcmp(opt.name, "bpm") == 0) {
  210. out->enable_bpm = !!atoi(opt.value);
  211. } else {
  212. blog(LOG_WARNING, "Unknown muxer option: %s = %s", opt.name, opt.value);
  213. }
  214. }
  215. obs_free_options(opts);
  216. out->flags = flags;
  217. }
  218. static void generate_filename(struct mp4_output *out, struct dstr *dst, bool overwrite);
  219. static bool mp4_output_start(void *data)
  220. {
  221. struct mp4_output *out = data;
  222. if (!obs_output_can_begin_data_capture(out->output, 0))
  223. return false;
  224. if (!obs_output_initialize_encoders(out->output, 0))
  225. return false;
  226. os_atomic_set_bool(&out->stopping, false);
  227. obs_data_t *settings = obs_output_get_settings(out->output);
  228. out->max_time = obs_data_get_int(settings, "max_time_sec") * 1000000LL;
  229. out->max_size = obs_data_get_int(settings, "max_size_mb") * 1024 * 1024;
  230. out->split_file_enabled = obs_data_get_bool(settings, "split_file");
  231. out->allow_overwrite = obs_data_get_bool(settings, "allow_overwrite");
  232. out->cur_size = 0;
  233. out->start_time = 0;
  234. /* Get path */
  235. const char *path = obs_data_get_string(settings, "path");
  236. if (path && *path) {
  237. dstr_copy(&out->path, path);
  238. } else {
  239. generate_filename(out, &out->path, out->allow_overwrite);
  240. info("Output path not specified. Using generated path '%s'", out->path.array);
  241. }
  242. /* Allow skipping the remux step for debugging purposes. */
  243. const char *muxer_settings = obs_data_get_string(settings, "muxer_settings");
  244. parse_custom_options(out, muxer_settings);
  245. obs_data_release(settings);
  246. if (out->enable_bpm) {
  247. info("Enabling BPM");
  248. obs_output_add_packet_callback(out->output, bpm_inject, NULL);
  249. }
  250. if (!buffered_file_serializer_init(&out->serializer, out->path.array, out->buffer_size, out->chunk_size)) {
  251. warn("Unable to open file '%s'", out->path.array);
  252. return false;
  253. }
  254. /* Add packet callback for accurate chapter markers. */
  255. obs_output_add_packet_callback(out->output, mp4_pkt_callback, (void *)out);
  256. /* Initialise muxer and start capture */
  257. out->muxer = mp4_mux_create(out->output, &out->serializer, out->flags, out->muxer_flavor);
  258. os_atomic_set_bool(&out->active, true);
  259. obs_output_begin_data_capture(out->output, 0);
  260. info("Writing Hybrid MP4/MOV file '%s'...", out->path.array);
  261. return true;
  262. }
  263. static inline bool should_split(struct mp4_output *out, struct encoder_packet *packet)
  264. {
  265. /* split at video frame on primary track */
  266. if (packet->type != OBS_ENCODER_VIDEO || packet->track_idx > 0)
  267. return false;
  268. /* don't split group of pictures */
  269. if (!packet->keyframe)
  270. return false;
  271. if (os_atomic_load_bool(&out->manual_split))
  272. return true;
  273. /* reached maximum file size */
  274. if (out->max_size > 0 && out->cur_size + (int64_t)packet->size >= out->max_size)
  275. return true;
  276. /* reached maximum duration */
  277. if (out->max_time > 0 && packet->dts_usec - out->start_time >= out->max_time)
  278. return true;
  279. return false;
  280. }
  281. static void find_best_filename(struct dstr *path, bool space)
  282. {
  283. int num = 2;
  284. if (!os_file_exists(path->array))
  285. return;
  286. const char *ext = strrchr(path->array, '.');
  287. if (!ext)
  288. return;
  289. size_t extstart = ext - path->array;
  290. struct dstr testpath;
  291. dstr_init_copy_dstr(&testpath, path);
  292. for (;;) {
  293. dstr_resize(&testpath, extstart);
  294. dstr_catf(&testpath, space ? " (%d)" : "_%d", num++);
  295. dstr_cat(&testpath, ext);
  296. if (!os_file_exists(testpath.array)) {
  297. dstr_free(path);
  298. dstr_init_move(path, &testpath);
  299. break;
  300. }
  301. }
  302. }
  303. static void generate_filename(struct mp4_output *out, struct dstr *dst, bool overwrite)
  304. {
  305. obs_data_t *settings = obs_output_get_settings(out->output);
  306. const char *dir = obs_data_get_string(settings, "directory");
  307. const char *fmt = obs_data_get_string(settings, "format");
  308. const char *ext = obs_data_get_string(settings, "extension");
  309. bool space = obs_data_get_bool(settings, "allow_spaces");
  310. char *filename = os_generate_formatted_filename(ext, space, fmt);
  311. dstr_copy(dst, dir);
  312. dstr_replace(dst, "\\", "/");
  313. if (dstr_end(dst) != '/')
  314. dstr_cat_ch(dst, '/');
  315. dstr_cat(dst, filename);
  316. char *slash = strrchr(dst->array, '/');
  317. if (slash) {
  318. *slash = 0;
  319. os_mkdirs(dst->array);
  320. *slash = '/';
  321. }
  322. if (!overwrite)
  323. find_best_filename(dst, space);
  324. bfree(filename);
  325. obs_data_release(settings);
  326. }
  327. static bool change_file(struct mp4_output *out, struct encoder_packet *pkt)
  328. {
  329. uint64_t start_time = os_gettime_ns();
  330. /* finalise file */
  331. mp4_mux_finalise(out->muxer);
  332. info("Waiting for file writer to finish...");
  333. /* flush/close file and destroy old muxer */
  334. buffered_file_serializer_free(&out->serializer);
  335. mp4_mux_destroy(out->muxer);
  336. mp4_clear_chapters(out);
  337. info("File split complete. Finalization took %" PRIu64 " ms.", (os_gettime_ns() - start_time) / 1000000);
  338. /* open new file */
  339. generate_filename(out, &out->path, out->allow_overwrite);
  340. info("Changing output file to '%s'", out->path.array);
  341. if (!buffered_file_serializer_init(&out->serializer, out->path.array, out->buffer_size, out->chunk_size)) {
  342. warn("Unable to open file '%s'", out->path.array);
  343. return false;
  344. }
  345. out->muxer = mp4_mux_create(out->output, &out->serializer, out->flags, out->muxer_flavor);
  346. calldata_t cd = {0};
  347. signal_handler_t *sh = obs_output_get_signal_handler(out->output);
  348. calldata_set_string(&cd, "next_file", out->path.array);
  349. signal_handler_signal(sh, "file_changed", &cd);
  350. calldata_free(&cd);
  351. out->cur_size = 0;
  352. out->start_time = pkt->dts_usec;
  353. return true;
  354. }
  355. static void mp4_output_stop(void *data, uint64_t ts)
  356. {
  357. struct mp4_output *out = data;
  358. out->stop_ts = ts / 1000;
  359. os_atomic_set_bool(&out->stopping, true);
  360. }
  361. static void mp4_mux_destroy_task(void *ptr)
  362. {
  363. struct mp4_mux *muxer = ptr;
  364. mp4_mux_destroy(muxer);
  365. }
  366. static void mp4_output_actual_stop(struct mp4_output *out, int code)
  367. {
  368. os_atomic_set_bool(&out->active, false);
  369. obs_output_remove_packet_callback(out->output, mp4_pkt_callback, NULL);
  370. uint64_t start_time = os_gettime_ns();
  371. mp4_mux_finalise(out->muxer);
  372. if (out->enable_bpm) {
  373. obs_output_remove_packet_callback(out->output, bpm_inject, NULL);
  374. bpm_destroy(out->output);
  375. }
  376. if (code) {
  377. obs_output_signal_stop(out->output, code);
  378. } else {
  379. obs_output_end_data_capture(out->output);
  380. }
  381. info("Waiting for file writer to finish...");
  382. /* Flush/close output file and destroy muxer */
  383. buffered_file_serializer_free(&out->serializer);
  384. obs_queue_task(OBS_TASK_DESTROY, mp4_mux_destroy_task, out->muxer, false);
  385. out->muxer = NULL;
  386. /* Clear chapter data */
  387. mp4_clear_chapters(out);
  388. info("File output complete. Finalization took %" PRIu64 " ms.", (os_gettime_ns() - start_time) / 1000000);
  389. }
  390. static void push_back_packet(struct mp4_output *out, struct encoder_packet *packet)
  391. {
  392. struct encoder_packet pkt;
  393. obs_encoder_packet_ref(&pkt, packet);
  394. da_push_back(out->split_buffer, &pkt);
  395. }
  396. static inline bool submit_packet(struct mp4_output *out, struct encoder_packet *pkt)
  397. {
  398. out->total_bytes += pkt->size;
  399. out->cur_size += pkt->size;
  400. return mp4_mux_submit_packet(out->muxer, pkt);
  401. }
  402. static void mp4_output_packet(void *data, struct encoder_packet *packet)
  403. {
  404. struct mp4_output *out = data;
  405. pthread_mutex_lock(&out->mutex);
  406. if (!active(out))
  407. goto unlock;
  408. if (!packet) {
  409. mp4_output_actual_stop(out, OBS_OUTPUT_ENCODE_ERROR);
  410. goto unlock;
  411. }
  412. if (stopping(out)) {
  413. if (packet->sys_dts_usec >= (int64_t)out->stop_ts) {
  414. mp4_output_actual_stop(out, 0);
  415. goto unlock;
  416. }
  417. }
  418. if (out->split_file_enabled) {
  419. if (out->split_buffer.num) {
  420. int64_t pts_usec = packet_pts_usec(packet);
  421. struct encoder_packet *first_pkt = out->split_buffer.array;
  422. int64_t first_pts_usec = packet_pts_usec(first_pkt);
  423. if (pts_usec >= first_pts_usec) {
  424. if (packet->type != OBS_ENCODER_AUDIO) {
  425. push_back_packet(out, packet);
  426. goto unlock;
  427. }
  428. if (!change_file(out, first_pkt)) {
  429. mp4_output_actual_stop(out, OBS_OUTPUT_ERROR);
  430. goto unlock;
  431. }
  432. out->split_file_ready = true;
  433. }
  434. } else if (should_split(out, packet)) {
  435. push_back_packet(out, packet);
  436. goto unlock;
  437. }
  438. }
  439. if (out->split_file_ready) {
  440. for (size_t i = 0; i < out->split_buffer.num; i++) {
  441. struct encoder_packet *pkt = &out->split_buffer.array[i];
  442. submit_packet(out, pkt);
  443. obs_encoder_packet_release(pkt);
  444. }
  445. da_free(out->split_buffer);
  446. out->split_file_ready = false;
  447. os_atomic_set_bool(&out->manual_split, false);
  448. }
  449. submit_packet(out, packet);
  450. if (serializer_get_pos(&out->serializer) == -1)
  451. mp4_output_actual_stop(out, OBS_OUTPUT_ERROR);
  452. unlock:
  453. pthread_mutex_unlock(&out->mutex);
  454. }
  455. static obs_properties_t *mp4_output_properties(void *unused)
  456. {
  457. UNUSED_PARAMETER(unused);
  458. obs_properties_t *props = obs_properties_create();
  459. obs_properties_add_text(props, "path", obs_module_text("MP4Output.FilePath"), OBS_TEXT_DEFAULT);
  460. obs_properties_add_text(props, "muxer_settings", "muxer_settings", OBS_TEXT_DEFAULT);
  461. return props;
  462. }
  463. uint64_t mp4_output_total_bytes(void *data)
  464. {
  465. struct mp4_output *out = data;
  466. return out->total_bytes;
  467. }
  468. struct obs_output_info mp4_output_info = {
  469. .id = "mp4_output",
  470. .flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED | OBS_OUTPUT_MULTI_TRACK_AV | OBS_OUTPUT_CAN_PAUSE,
  471. .encoded_video_codecs = "h264;hevc;av1",
  472. .encoded_audio_codecs = "aac;alac;flac;opus",
  473. .get_name = mp4_output_name,
  474. .create = mp4_output_create,
  475. .destroy = mp4_output_destroy,
  476. .start = mp4_output_start,
  477. .stop = mp4_output_stop,
  478. .encoded_packet = mp4_output_packet,
  479. .get_properties = mp4_output_properties,
  480. .get_total_bytes = mp4_output_total_bytes,
  481. };
  482. struct obs_output_info mov_output_info = {
  483. .id = "mov_output",
  484. .flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED | OBS_OUTPUT_MULTI_TRACK_AV | OBS_OUTPUT_CAN_PAUSE,
  485. .encoded_video_codecs = "h264;hevc;prores",
  486. .encoded_audio_codecs = "aac;alac",
  487. .get_name = mov_output_name,
  488. .create = mov_output_create,
  489. .destroy = mp4_output_destroy,
  490. .start = mp4_output_start,
  491. .stop = mp4_output_stop,
  492. .encoded_packet = mp4_output_packet,
  493. .get_properties = mp4_output_properties,
  494. .get_total_bytes = mp4_output_total_bytes,
  495. };