mp4-output.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613
  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/dstr.h>
  19. #include <util/threading.h>
  20. #include <util/buffered-file-serializer.h>
  21. #include <opts-parser.h>
  22. #define do_log(level, format, ...) \
  23. blog(level, "[mp4 output: '%s'] " format, \
  24. obs_output_get_name(out->output), ##__VA_ARGS__)
  25. #define warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__)
  26. #define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__)
  27. struct chapter {
  28. int64_t dts_usec;
  29. char *name;
  30. };
  31. struct mp4_output {
  32. obs_output_t *output;
  33. struct dstr path;
  34. struct serializer serializer;
  35. volatile bool active;
  36. volatile bool stopping;
  37. uint64_t stop_ts;
  38. bool allow_overwrite;
  39. uint64_t total_bytes;
  40. pthread_mutex_t mutex;
  41. struct mp4_mux *muxer;
  42. int flags;
  43. int64_t last_dts_usec;
  44. DARRAY(struct chapter) chapters;
  45. /* File splitting stuff */
  46. bool split_file_enabled;
  47. bool split_file_ready;
  48. volatile bool manual_split;
  49. size_t cur_size;
  50. size_t max_size;
  51. int64_t start_time;
  52. int64_t max_time;
  53. bool found_video[MAX_OUTPUT_VIDEO_ENCODERS];
  54. bool found_audio[MAX_OUTPUT_AUDIO_ENCODERS];
  55. int64_t video_pts_offsets[MAX_OUTPUT_VIDEO_ENCODERS];
  56. int64_t audio_dts_offsets[MAX_OUTPUT_AUDIO_ENCODERS];
  57. /* Buffer for packets while we reinitialise the muxer after splitting */
  58. DARRAY(struct encoder_packet) split_buffer;
  59. };
  60. static inline bool stopping(struct mp4_output *out)
  61. {
  62. return os_atomic_load_bool(&out->stopping);
  63. }
  64. static inline bool active(struct mp4_output *out)
  65. {
  66. return os_atomic_load_bool(&out->active);
  67. }
  68. static inline int64_t packet_pts_usec(struct encoder_packet *packet)
  69. {
  70. return packet->pts * 1000000 / packet->timebase_den;
  71. }
  72. static inline void ts_offset_clear(struct mp4_output *out)
  73. {
  74. for (size_t i = 0; i < MAX_OUTPUT_VIDEO_ENCODERS; i++) {
  75. out->found_video[i] = false;
  76. out->video_pts_offsets[i] = 0;
  77. }
  78. for (size_t i = 0; i < MAX_OUTPUT_AUDIO_ENCODERS; i++) {
  79. out->found_audio[i] = false;
  80. out->audio_dts_offsets[i] = 0;
  81. }
  82. }
  83. static inline void ts_offset_update(struct mp4_output *out,
  84. struct encoder_packet *packet)
  85. {
  86. int64_t *offset;
  87. bool *found;
  88. if (packet->type == OBS_ENCODER_VIDEO) {
  89. offset = &out->video_pts_offsets[packet->track_idx];
  90. found = &out->found_video[packet->track_idx];
  91. } else {
  92. offset = &out->audio_dts_offsets[packet->track_idx];
  93. found = &out->found_audio[packet->track_idx];
  94. }
  95. if (*found)
  96. return;
  97. *offset = packet->dts;
  98. *found = true;
  99. }
  100. static const char *mp4_output_name(void *unused)
  101. {
  102. UNUSED_PARAMETER(unused);
  103. return obs_module_text("MP4Output");
  104. }
  105. static void mp4_output_destory(void *data)
  106. {
  107. struct mp4_output *out = data;
  108. for (size_t i = 0; i < out->chapters.num; i++)
  109. bfree(out->chapters.array[i].name);
  110. da_free(out->chapters);
  111. pthread_mutex_destroy(&out->mutex);
  112. dstr_free(&out->path);
  113. bfree(out);
  114. }
  115. static void mp4_add_chapter_proc(void *data, calldata_t *cd)
  116. {
  117. struct mp4_output *out = data;
  118. struct dstr name = {0};
  119. dstr_copy(&name, calldata_string(cd, "chapter_name"));
  120. if (name.len == 0) {
  121. /* Generate name if none provided. */
  122. dstr_catf(&name, "%s %zu",
  123. obs_module_text("MP4Output.UnnamedChapter"),
  124. out->chapters.num + 1);
  125. }
  126. int64_t totalRecordSeconds = out->last_dts_usec / 1000 / 1000;
  127. int seconds = (int)totalRecordSeconds % 60;
  128. int totalMinutes = (int)totalRecordSeconds / 60;
  129. int minutes = totalMinutes % 60;
  130. int hours = totalMinutes / 60;
  131. info("Adding chapter \"%s\" at %02d:%02d:%02d", name.array, hours,
  132. minutes, seconds);
  133. pthread_mutex_lock(&out->mutex);
  134. struct chapter *chap = da_push_back_new(out->chapters);
  135. chap->dts_usec = out->last_dts_usec;
  136. chap->name = name.array;
  137. pthread_mutex_unlock(&out->mutex);
  138. }
  139. static void split_file_proc(void *data, calldata_t *cd)
  140. {
  141. struct mp4_output *out = data;
  142. calldata_set_bool(cd, "split_file_enabled", out->split_file_enabled);
  143. if (!out->split_file_enabled)
  144. return;
  145. os_atomic_set_bool(&out->manual_split, true);
  146. }
  147. static void *mp4_output_create(obs_data_t *settings, obs_output_t *output)
  148. {
  149. struct mp4_output *out = bzalloc(sizeof(struct mp4_output));
  150. out->output = output;
  151. pthread_mutex_init(&out->mutex, NULL);
  152. signal_handler_t *sh = obs_output_get_signal_handler(output);
  153. signal_handler_add(sh, "void file_changed(string next_file)");
  154. proc_handler_t *ph = obs_output_get_proc_handler(output);
  155. proc_handler_add(ph, "void split_file(out bool split_file_enabled)",
  156. split_file_proc, out);
  157. proc_handler_add(ph, "void add_chapter(string chapter_name)",
  158. mp4_add_chapter_proc, out);
  159. UNUSED_PARAMETER(settings);
  160. return out;
  161. }
  162. static inline void apply_flag(int *flags, const char *value, int flag_value)
  163. {
  164. if (atoi(value))
  165. *flags |= flag_value;
  166. else
  167. *flags &= ~flag_value;
  168. }
  169. static int parse_custom_options(const char *opts_str)
  170. {
  171. int flags = MP4_USE_NEGATIVE_CTS;
  172. struct obs_options opts = obs_parse_options(opts_str);
  173. for (size_t i = 0; i < opts.count; i++) {
  174. struct obs_option opt = opts.options[i];
  175. if (strcmp(opt.name, "skip_soft_remux") == 0) {
  176. apply_flag(&flags, opt.value, MP4_SKIP_FINALISATION);
  177. } else if (strcmp(opt.name, "write_encoder_info") == 0) {
  178. apply_flag(&flags, opt.value, MP4_WRITE_ENCODER_INFO);
  179. } else if (strcmp(opt.name, "use_metadata_tags") == 0) {
  180. apply_flag(&flags, opt.value, MP4_USE_MDTA_KEY_VALUE);
  181. } else if (strcmp(opt.name, "use_negative_cts") == 0) {
  182. apply_flag(&flags, opt.value, MP4_USE_NEGATIVE_CTS);
  183. } else {
  184. blog(LOG_WARNING, "Unknown muxer option: %s = %s",
  185. opt.name, opt.value);
  186. }
  187. }
  188. obs_free_options(opts);
  189. return flags;
  190. }
  191. static bool mp4_output_start(void *data)
  192. {
  193. struct mp4_output *out = data;
  194. if (!obs_output_can_begin_data_capture(out->output, 0))
  195. return false;
  196. if (!obs_output_initialize_encoders(out->output, 0))
  197. return false;
  198. os_atomic_set_bool(&out->stopping, false);
  199. /* get path */
  200. obs_data_t *settings = obs_output_get_settings(out->output);
  201. const char *path = obs_data_get_string(settings, "path");
  202. dstr_copy(&out->path, path);
  203. out->max_time = obs_data_get_int(settings, "max_time_sec") * 1000000LL;
  204. out->max_size = obs_data_get_int(settings, "max_size_mb") * 1024 * 1024;
  205. out->split_file_enabled = obs_data_get_bool(settings, "split_file");
  206. out->allow_overwrite = obs_data_get_bool(settings, "allow_overwrite");
  207. out->cur_size = 0;
  208. /* Allow skipping the remux step for debugging purposes. */
  209. const char *muxer_settings =
  210. obs_data_get_string(settings, "muxer_settings");
  211. out->flags = parse_custom_options(muxer_settings);
  212. obs_data_release(settings);
  213. if (!buffered_file_serializer_init_defaults(&out->serializer,
  214. out->path.array)) {
  215. warn("Unable to open MP4 file '%s'", out->path.array);
  216. return false;
  217. }
  218. /* Initialise muxer and start capture */
  219. out->muxer = mp4_mux_create(out->output, &out->serializer, out->flags);
  220. os_atomic_set_bool(&out->active, true);
  221. obs_output_begin_data_capture(out->output, 0);
  222. info("Writing Hybrid MP4 file '%s'...", out->path.array);
  223. return true;
  224. }
  225. static inline bool should_split(struct mp4_output *out,
  226. struct encoder_packet *packet)
  227. {
  228. /* split at video frame on primary track */
  229. if (packet->type != OBS_ENCODER_VIDEO || packet->track_idx > 0)
  230. return false;
  231. /* don't split group of pictures */
  232. if (!packet->keyframe)
  233. return false;
  234. if (os_atomic_load_bool(&out->manual_split))
  235. return true;
  236. /* reached maximum file size */
  237. if (out->max_size > 0 &&
  238. out->cur_size + (int64_t)packet->size >= out->max_size)
  239. return true;
  240. /* reached maximum duration */
  241. if (out->max_time > 0 &&
  242. packet->dts_usec - out->start_time >= out->max_time)
  243. return true;
  244. return false;
  245. }
  246. static void find_best_filename(struct dstr *path, bool space)
  247. {
  248. int num = 2;
  249. if (!os_file_exists(path->array))
  250. return;
  251. const char *ext = strrchr(path->array, '.');
  252. if (!ext)
  253. return;
  254. size_t extstart = ext - path->array;
  255. struct dstr testpath;
  256. dstr_init_copy_dstr(&testpath, path);
  257. for (;;) {
  258. dstr_resize(&testpath, extstart);
  259. dstr_catf(&testpath, space ? " (%d)" : "_%d", num++);
  260. dstr_cat(&testpath, ext);
  261. if (!os_file_exists(testpath.array)) {
  262. dstr_free(path);
  263. dstr_init_move(path, &testpath);
  264. break;
  265. }
  266. }
  267. }
  268. static void generate_filename(struct mp4_output *out, struct dstr *dst,
  269. bool overwrite)
  270. {
  271. obs_data_t *settings = obs_output_get_settings(out->output);
  272. const char *dir = obs_data_get_string(settings, "directory");
  273. const char *fmt = obs_data_get_string(settings, "format");
  274. const char *ext = obs_data_get_string(settings, "extension");
  275. bool space = obs_data_get_bool(settings, "allow_spaces");
  276. char *filename = os_generate_formatted_filename(ext, space, fmt);
  277. dstr_copy(dst, dir);
  278. dstr_replace(dst, "\\", "/");
  279. if (dstr_end(dst) != '/')
  280. dstr_cat_ch(dst, '/');
  281. dstr_cat(dst, filename);
  282. char *slash = strrchr(dst->array, '/');
  283. if (slash) {
  284. *slash = 0;
  285. os_mkdirs(dst->array);
  286. *slash = '/';
  287. }
  288. if (!overwrite)
  289. find_best_filename(dst, space);
  290. bfree(filename);
  291. obs_data_release(settings);
  292. }
  293. static bool change_file(struct mp4_output *out, struct encoder_packet *pkt)
  294. {
  295. uint64_t start_time = os_gettime_ns();
  296. /* finalise file */
  297. for (size_t i = 0; i < out->chapters.num; i++) {
  298. struct chapter *chap = &out->chapters.array[i];
  299. mp4_mux_add_chapter(out->muxer, chap->dts_usec, chap->name);
  300. }
  301. mp4_mux_finalise(out->muxer);
  302. info("Waiting for file writer to finish...");
  303. /* flush/close file and destroy old muxer */
  304. buffered_file_serializer_free(&out->serializer);
  305. mp4_mux_destroy(out->muxer);
  306. for (size_t i = 0; i < out->chapters.num; i++)
  307. bfree(out->chapters.array[i].name);
  308. da_clear(out->chapters);
  309. info("MP4 file split complete. Finalization took %" PRIu64 " ms.",
  310. (os_gettime_ns() - start_time) / 1000000);
  311. /* open new file */
  312. generate_filename(out, &out->path, out->allow_overwrite);
  313. info("Changing output file to '%s'", out->path.array);
  314. if (!buffered_file_serializer_init_defaults(&out->serializer,
  315. out->path.array)) {
  316. warn("Unable to open MP4 file '%s'", out->path.array);
  317. return false;
  318. }
  319. out->muxer = mp4_mux_create(out->output, &out->serializer, out->flags);
  320. calldata_t cd = {0};
  321. signal_handler_t *sh = obs_output_get_signal_handler(out->output);
  322. calldata_set_string(&cd, "next_file", out->path.array);
  323. signal_handler_signal(sh, "file_changed", &cd);
  324. calldata_free(&cd);
  325. out->cur_size = 0;
  326. out->start_time = pkt->dts_usec;
  327. ts_offset_clear(out);
  328. return true;
  329. }
  330. static void mp4_output_stop(void *data, uint64_t ts)
  331. {
  332. struct mp4_output *out = data;
  333. out->stop_ts = ts / 1000;
  334. os_atomic_set_bool(&out->stopping, true);
  335. }
  336. static void mp4_output_actual_stop(struct mp4_output *out, int code)
  337. {
  338. os_atomic_set_bool(&out->active, false);
  339. uint64_t start_time = os_gettime_ns();
  340. for (size_t i = 0; i < out->chapters.num; i++) {
  341. struct chapter *chap = &out->chapters.array[i];
  342. mp4_mux_add_chapter(out->muxer, chap->dts_usec, chap->name);
  343. }
  344. mp4_mux_finalise(out->muxer);
  345. if (code) {
  346. obs_output_signal_stop(out->output, code);
  347. } else {
  348. obs_output_end_data_capture(out->output);
  349. }
  350. info("Waiting for file writer to finish...");
  351. /* Flush/close output file and destroy muxer */
  352. buffered_file_serializer_free(&out->serializer);
  353. mp4_mux_destroy(out->muxer);
  354. out->muxer = NULL;
  355. /* Clear chapter data */
  356. for (size_t i = 0; i < out->chapters.num; i++)
  357. bfree(out->chapters.array[i].name);
  358. da_clear(out->chapters);
  359. info("MP4 file output complete. Finalization took %" PRIu64 " ms.",
  360. (os_gettime_ns() - start_time) / 1000000);
  361. }
  362. static void push_back_packet(struct mp4_output *out,
  363. struct encoder_packet *packet)
  364. {
  365. struct encoder_packet pkt;
  366. obs_encoder_packet_ref(&pkt, packet);
  367. da_push_back(out->split_buffer, &pkt);
  368. }
  369. static inline bool submit_packet(struct mp4_output *out,
  370. struct encoder_packet *pkt)
  371. {
  372. out->total_bytes += pkt->size;
  373. if (!out->split_file_enabled)
  374. return mp4_mux_submit_packet(out->muxer, pkt);
  375. out->cur_size += pkt->size;
  376. /* Apply DTS/PTS offset local packet copy */
  377. struct encoder_packet modified = *pkt;
  378. if (modified.type == OBS_ENCODER_VIDEO) {
  379. modified.dts -= out->video_pts_offsets[modified.track_idx];
  380. modified.pts -= out->video_pts_offsets[modified.track_idx];
  381. } else {
  382. modified.dts -= out->audio_dts_offsets[modified.track_idx];
  383. modified.pts -= out->audio_dts_offsets[modified.track_idx];
  384. }
  385. return mp4_mux_submit_packet(out->muxer, &modified);
  386. }
  387. static void mp4_output_packet(void *data, struct encoder_packet *packet)
  388. {
  389. struct mp4_output *out = data;
  390. pthread_mutex_lock(&out->mutex);
  391. if (!active(out))
  392. goto unlock;
  393. if (!packet) {
  394. mp4_output_actual_stop(out, OBS_OUTPUT_ENCODE_ERROR);
  395. goto unlock;
  396. }
  397. if (stopping(out)) {
  398. if (packet->sys_dts_usec >= (int64_t)out->stop_ts) {
  399. mp4_output_actual_stop(out, 0);
  400. goto unlock;
  401. }
  402. }
  403. if (out->split_file_enabled) {
  404. if (out->split_buffer.num) {
  405. int64_t pts_usec = packet_pts_usec(packet);
  406. struct encoder_packet *first_pkt =
  407. out->split_buffer.array;
  408. int64_t first_pts_usec = packet_pts_usec(first_pkt);
  409. if (pts_usec >= first_pts_usec) {
  410. if (packet->type != OBS_ENCODER_AUDIO) {
  411. push_back_packet(out, packet);
  412. goto unlock;
  413. }
  414. if (!change_file(out, first_pkt)) {
  415. mp4_output_actual_stop(
  416. out, OBS_OUTPUT_ERROR);
  417. goto unlock;
  418. }
  419. out->split_file_ready = true;
  420. }
  421. } else if (should_split(out, packet)) {
  422. push_back_packet(out, packet);
  423. goto unlock;
  424. }
  425. }
  426. if (out->split_file_ready) {
  427. for (size_t i = 0; i < out->split_buffer.num; i++) {
  428. struct encoder_packet *pkt =
  429. &out->split_buffer.array[i];
  430. ts_offset_update(out, pkt);
  431. submit_packet(out, pkt);
  432. obs_encoder_packet_release(pkt);
  433. }
  434. da_free(out->split_buffer);
  435. out->split_file_ready = false;
  436. os_atomic_set_bool(&out->manual_split, false);
  437. }
  438. if (out->split_file_enabled)
  439. ts_offset_update(out, packet);
  440. /* Update PTS for chapter markers */
  441. if (packet->type == OBS_ENCODER_VIDEO && packet->track_idx == 0)
  442. out->last_dts_usec = packet->dts_usec - out->start_time;
  443. submit_packet(out, packet);
  444. if (serializer_get_pos(&out->serializer) == -1)
  445. mp4_output_actual_stop(out, OBS_OUTPUT_ERROR);
  446. unlock:
  447. pthread_mutex_unlock(&out->mutex);
  448. }
  449. static obs_properties_t *mp4_output_properties(void *unused)
  450. {
  451. UNUSED_PARAMETER(unused);
  452. obs_properties_t *props = obs_properties_create();
  453. obs_properties_add_text(props, "path",
  454. obs_module_text("MP4Output.FilePath"),
  455. OBS_TEXT_DEFAULT);
  456. obs_properties_add_text(props, "muxer_settings", "muxer_settings",
  457. OBS_TEXT_DEFAULT);
  458. return props;
  459. }
  460. uint64_t mp4_output_total_bytes(void *data)
  461. {
  462. struct mp4_output *out = data;
  463. return out->total_bytes;
  464. }
  465. struct obs_output_info mp4_output_info = {
  466. .id = "mp4_output",
  467. .flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED |
  468. OBS_OUTPUT_MULTI_TRACK_AV | OBS_OUTPUT_CAN_PAUSE,
  469. .encoded_video_codecs = "h264;hevc;av1",
  470. .encoded_audio_codecs = "aac",
  471. .get_name = mp4_output_name,
  472. .create = mp4_output_create,
  473. .destroy = mp4_output_destory,
  474. .start = mp4_output_start,
  475. .stop = mp4_output_stop,
  476. .encoded_packet = mp4_output_packet,
  477. .get_properties = mp4_output_properties,
  478. .get_total_bytes = mp4_output_total_bytes,
  479. };