• janus-gateway的videoroom插件的RTP包录制功能源码详解


    引:

    janus-gateway在配置文件设置后,可以实现对videoroom插件的每个publisher的音频,视频,数据的RTP流录制成mjr文件。

    对于音频,视频的mjr文件,可以使用自带的postprocessing工具janus-pp-rec转成mp4文件。

    每个publisher音频和视频mjr文件是分立的两个文件,需要使用ffmpeg将两个合成一个mp4文件。

    janus-gateway的原生代码中的录制功能是通过配置文件实现,只能配置成要么录,要么不录。如果要通过客户端的信令进行可控的频繁开关,则需要修改源码实现。

    如果要对videoroom的publisher的RTP流转成RTMP流推送出去,可以使用第三方的enhanced-videoroom插件实现。

    一、配置文件的录制参数设置

    etc/janus/janus.plugin.videoroom.jcfg
    房间中和录制相关的参数

    1. # room-<unique room ID>: {
    2. # description = This is my awesome room
    3. ...
    4. # record = true|false (whether this room should be recorded, default=false)
    5. # rec_dir = <folder where recordings should be stored, when enabled>
    6. # lock_record = true|false (whether recording can only be started/stopped if the secret
    7. #            is provided, or using the global enable_recording request, default=false)
    8. #}
    1. 配置实例
    2. room-1234: {
    3. description = "Demo Room"
    4. secret = "adminpwd"
    5. publishers = 6
    6. bitrate = 128000
    7. fir_freq = 10
    8. audiocodec = "opus"
    9. videocodec = "h264"
    10. record = true
    11. rec_dir = "/data/PJT-janus/record-samples"
    12. }

    二、录制初始化

    当客户端为发布者,且发送的message为"configure"类型时,
    将初始化录制, 将初始化音频、视频和数据文件的存储路径、文件名后,
    打开文件以获得文件句柄后,写入文件头。

    1. // janus_videoroom.c
    2. static json_t *janus_videoroom_process_synchronous_request(janus_videoroom_session *session, json_t *message) {
    3. if(!strcasecmp(request_text, "create")) {
    4. /* Create a new VideoRoom */
    5. /* Added by Hank, For recording: */
    6. // if(rec_dir) {
    7. // videoroom->rec_dir = g_strdup(json_string_value(rec_dir));
    8. if (g_record_root_path != NULL) {
    9. videoroom->rec_dir = g_strdup(g_record_root_path);
    10. // 修改文件存储路径,在原有的录制根目录下,添加 /年月日/房间号/
    11. char new_rec_dir_arr[255] = {0};
    12. time_t timestamp = time(NULL);
    13. struct tm *local_time = localtime(×tamp);
    14. char formatted_date[11]={0};
    15. strftime(formatted_date,sizeof(formatted_date), "%Y%m%d",local_time);
    16. g_snprintf(new_rec_dir_arr, 255, "%s/%s/%s/",
    17. videoroom->rec_dir, formatted_date, videoroom->room_id_str);
    18. char *old_rec_dir = videoroom->rec_dir;
    19. char *new_rec_dir = g_strdup(new_rec_dir_arr);
    20. videoroom->rec_dir = new_rec_dir;
    21. g_free(old_rec_dir);
    22. /* END-OF-Hank */
    23. }
    24. }
    25. }
    1. /* Thread to handle incoming messages
    2. * 当有房间“configure"消息时,
    3. * 进行本房间的发布者对应的视频、音频、数据录制文件创建
    4. */
    5. static void *janus_videoroom_handler(void *data) {
    6. while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) {
    7. msg = g_async_queue_pop(messages);
    8. janus_videoroom *videoroom = NULL;
    9. janus_videoroom_publisher *participant = NULL;
    10. janus_videoroom_subscriber *subscriber = NULL;
    11. janus_mutex_lock(&sessions_mutex);
    12. janus_videoroom_session *session = janus_videoroom_lookup_session(msg->handle);
    13. janus_mutex_unlock(&sessions_mutex);
    14. if(session->participant_type == janus_videoroom_p_type_none) {
    15. ...
    16. } else if(session->participant_type == janus_videoroom_p_type_publisher) {
    17. /* 当 request_text = "configure" 时 */
    18. json_t *request = json_object_get(root, "request");
    19. const char *request_text = json_string_value(request);
    20. if(!strcasecmp(request_text, "join")
    21. || !strcasecmp(request_text, "joinandconfigure")) {
    22. ...
    23. } else if(!strcasecmp(request_text, "configure")
    24. || !strcasecmp(request_text, "publish")) {
    25. /* 录制相关配置,并创建本publisher的Video/Audio/Data录制文件 */
    26. gboolean record_locked = FALSE;
    27. if((record || recfile) && participant->room->lock_record && participant->room->room_secret) {
    28. JANUS_CHECK_SECRET(participant->room->room_secret, root, "secret", error_code, error_cause,
    29. JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT, JANUS_VIDEOROOM_ERROR_UNAUTHORIZED);
    30. if(error_code != 0) {
    31. /* Wrong secret provided, we'll prevent the recording state from being changed */
    32. record_locked = TRUE;
    33. }
    34. }
    35. janus_mutex_lock(&participant->rec_mutex);
    36. gboolean prev_recording_active = participant->recording_active;
    37. if(record && !record_locked) {
    38. participant->recording_active = json_is_true(record);
    39. JANUS_LOG(LOG_VERB, "Setting record property: %s (room %s, user %s)\n",
    40. participant->recording_active ? "true" : "false", participant->room_id_str, participant->user_id_str);
    41. }
    42. if(recfile && !record_locked) {
    43. participant->recording_base = g_strdup(json_string_value(recfile));
    44. JANUS_LOG(LOG_VERB, "Setting recording basename: %s (room %s, user %s)\n",
    45. participant->recording_base, participant->room_id_str, participant->user_id_str);
    46. }
    47. /* Do we need to do something with the recordings right now? */
    48. if(participant->recording_active != prev_recording_active) {
    49. /* Something changed */
    50. if(!participant->recording_active) {
    51. /* Not recording (anymore?) */
    52. janus_videoroom_recorder_close(participant);
    53. } else if(participant->recording_active && g_atomic_int_get(&participant->session->started)) {
    54. /* We've started recording, send a PLI/FIR and go on */
    55. GList *temp = participant->streams;
    56. while(temp) {
    57. janus_videoroom_publisher_stream *ps = (janus_videoroom_publisher_stream *)temp->data;
    58. janus_videoroom_recorder_create(participant,
    59. participant->audio, participant->video,
    60. participant->data);
    61. }
    62. janus_mutex_unlock(&participant->rec_mutex);
    63. 。。。
    64. }
    65. }
    66. janus_videoroom_message_free(msg);
    67. continue;
    68. }
    69. }
    70. } // end of while(g_atomic_int_get(&initialized) ...)
    71. return NULL;
    72. }
    1. /********** 创建本发布者对应的音频、视频、数据录制文件 *******************/
    2. static void janus_videoroom_recorder_create(janus_videoroom_publisher *participant,
    3. gboolean audio, gboolean video,
    4. gboolean data) {
    5. char filename[255];
    6. janus_recorder *rc = NULL;
    7. gint64 now = janus_get_real_time();
    8. // 设置音频文件的存储路径和文件名
    9. if(audio && participant->arc == NULL) {
    10. memset(filename, 0, 255);
    11. if(participant->recording_base) {
    12. /* Use the filename and path we have been provided */
    13. g_snprintf(filename, 255, "%s-audio", participant->recording_base);
    14. rc = janus_recorder_create(participant->room->rec_dir,
    15. janus_audiocodec_name(participant->acodec), filename);
    16. if(rc == NULL) {
    17. JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this publisher!\n");
    18. }
    19. } else {
    20. /* Build a filename */
    21. g_snprintf(filename, 255, "videoroom-%s-user-%s-%"SCNi64"-audio",
    22. participant->room_id_str, participant->user_id_str, now);
    23. rc = janus_recorder_create(participant->room->rec_dir,
    24. janus_audiocodec_name(participant->acodec), filename);
    25. if(rc == NULL) {
    26. JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this publisher!\n");
    27. }
    28. }
    29. /* If media is encrypted, mark it in the recording */
    30. if(participant->e2ee)
    31. janus_recorder_encrypted(rc);
    32. participant->arc = rc;
    33. }
    34. // 设置视频文件的存储路径和文件名
    35. if(video && participant->vrc == NULL) {
    36. janus_rtp_switching_context_reset(&participant->rec_ctx);
    37. janus_rtp_simulcasting_context_reset(&participant->rec_simctx);
    38. participant->rec_simctx.substream_target = 2;
    39. participant->rec_simctx.templayer_target = 2;
    40. memset(filename, 0, 255);
    41. if(participant->recording_base) {
    42. /* Use the filename and path we have been provided */
    43. g_snprintf(filename, 255, "%s-video", participant->recording_base);
    44. rc = janus_recorder_create_full(participant->room->rec_dir,
    45. janus_videocodec_name(participant->vcodec), participant->vfmtp, filename);
    46. if(rc == NULL) {
    47. JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this publisher!\n");
    48. }
    49. } else {
    50. /* Build a filename */
    51. g_snprintf(filename, 255, "videoroom-%s-user-%s-%"SCNi64"-video",
    52. participant->room_id_str, participant->user_id_str, now);
    53. rc = janus_recorder_create_full(participant->room->rec_dir,
    54. janus_videocodec_name(participant->vcodec), participant->vfmtp, filename);
    55. if(rc == NULL) {
    56. JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this publisher!\n");
    57. }
    58. }
    59. /* If media is encrypted, mark it in the recording */
    60. if(participant->e2ee)
    61. janus_recorder_encrypted(rc);
    62. participant->vrc = rc;
    63. }
    64. // 设置数据文件的存储路径和文件名
    65. if(data && participant->drc == NULL) {
    66. memset(filename, 0, 255);
    67. if(participant->recording_base) {
    68. /* Use the filename and path we have been provided */
    69. g_snprintf(filename, 255, "%s-data", participant->recording_base);
    70. rc = janus_recorder_create(participant->room->rec_dir,
    71. "text", filename);
    72. if(rc == NULL) {
    73. JANUS_LOG(LOG_ERR, "Couldn't open an data recording file for this publisher!\n");
    74. }
    75. } else {
    76. /* Build a filename */
    77. g_snprintf(filename, 255, "videoroom-%s-user-%s-%"SCNi64"-data",
    78. participant->room_id_str, participant->user_id_str, now);
    79. rc = janus_recorder_create(participant->room->rec_dir,
    80. "text", filename);
    81. if(rc == NULL) {
    82. JANUS_LOG(LOG_ERR, "Couldn't open an data recording file for this publisher!\n");
    83. }
    84. }
    85. /* Media encryption doesn't apply to data channels */
    86. participant->drc = rc;
    87. }
    88. }
    1. // record.c
    2. /* Info header in the structured recording */
    3. static const char *header = "MJR00002";
    4. /* Frame header in the structured recording */
    5. static const char *frame_header = "MEET";
    6. janus_recorder *janus_recorder_create(const char *dir, const char *codec, const char *filename) {
    7. /* Same as janus_recorder_create_full, but with no fmtp */
    8. return janus_recorder_create_full(dir, codec, NULL, filename);
    9. }
    10. /*
    11. 打开文件;
    12. 写入文件头;MJR00002
    13. */
    14. janus_recorder *janus_recorder_create_full(const char *dir, const char *codec, const char *fmtp, const char *filename) {
    15. janus_recorder_medium type = JANUS_RECORDER_AUDIO;
    16. if(codec == NULL) {
    17. JANUS_LOG(LOG_ERR, "Missing codec information\n");
    18. return NULL;
    19. }
    20. if(!strcasecmp(codec, "vp8") || !strcasecmp(codec, "vp9") || !strcasecmp(codec, "h264")
    21. || !strcasecmp(codec, "av1") || !strcasecmp(codec, "h265")) {
    22. type = JANUS_RECORDER_VIDEO;
    23. } else if(!strcasecmp(codec, "opus") || !strcasecmp(codec, "multiopus")
    24. || !strcasecmp(codec, "g711") || !strcasecmp(codec, "pcmu") || !strcasecmp(codec, "pcma")
    25. || !strcasecmp(codec, "g722")) {
    26. type = JANUS_RECORDER_AUDIO;
    27. } else if(!strcasecmp(codec, "text")) {
    28. /* FIXME We only handle text on data channels, so that's the only thing we can save too */
    29. type = JANUS_RECORDER_DATA;
    30. } else {
    31. /* We don't recognize the codec: while we might go on anyway, we'd rather fail instead */
    32. JANUS_LOG(LOG_ERR, "Unsupported codec '%s'\n", codec);
    33. return NULL;
    34. }
    35. /* Create the recorder */
    36. janus_recorder *rc = g_malloc0(sizeof(janus_recorder));
    37. janus_refcount_init(&rc->ref, janus_recorder_free);
    38. rc->dir = NULL;
    39. rc->filename = NULL;
    40. rc->file = NULL;
    41. rc->codec = g_strdup(codec);
    42. rc->fmtp = fmtp ? g_strdup(fmtp) : NULL;
    43. rc->created = janus_get_real_time();
    44. const char *rec_dir = NULL;
    45. const char *rec_file = NULL;
    46. char *copy_for_parent = NULL;
    47. char *copy_for_base = NULL;
    48. /* 检查路径和文件名是否合规 */
    49. if(filename != NULL) {
    50. /* Helper copies to avoid overwriting */
    51. copy_for_parent = g_strdup(filename);
    52. copy_for_base = g_strdup(filename);
    53. /* Get filename parent folder */
    54. const char *filename_parent = dirname(copy_for_parent);
    55. /* Get filename base file */
    56. const char *filename_base = basename(copy_for_base);
    57. if(!dir) {
    58. /* If dir is NULL we have to create filename_parent and filename_base */
    59. rec_dir = filename_parent;
    60. rec_file = filename_base;
    61. } else {
    62. /* If dir is valid we have to create dir and filename*/
    63. rec_dir = dir;
    64. rec_file = filename;
    65. if(strcasecmp(filename_parent, ".") || strcasecmp(filename_base, filename)) {
    66. JANUS_LOG(LOG_WARN, "Unsupported combination of dir and filename %s %s\n", dir, filename);
    67. }
    68. }
    69. }
    70. // 检查路径是否存在,如果不存在,则创建路径
    71. if(rec_dir != NULL) {
    72. /* Check if this directory exists, and create it if needed */
    73. struct stat s;
    74. int err = stat(rec_dir, &s);
    75. if(err == -1) {
    76. if(ENOENT == errno) {
    77. /* Directory does not exist, try creating it */
    78. if(janus_mkdir(rec_dir, 0755) < 0) {
    79. JANUS_LOG(LOG_ERR, "mkdir (%s) error: %d (%s)\n", rec_dir, errno, strerror(errno));
    80. janus_recorder_destroy(rc);
    81. g_free(copy_for_parent);
    82. g_free(copy_for_base);
    83. return NULL;
    84. }
    85. } else {
    86. JANUS_LOG(LOG_ERR, "stat (%s) error: %d (%s)\n", rec_dir, errno, strerror(errno));
    87. janus_recorder_destroy(rc);
    88. g_free(copy_for_parent);
    89. g_free(copy_for_base);
    90. return NULL;
    91. }
    92. } else {
    93. if(S_ISDIR(s.st_mode)) {
    94. /* Directory exists */
    95. JANUS_LOG(LOG_VERB, "Directory exists: %s\n", rec_dir);
    96. } else {
    97. /* File exists but it's not a directory? */
    98. JANUS_LOG(LOG_ERR, "Not a directory? %s\n", rec_dir);
    99. janus_recorder_destroy(rc);
    100. g_free(copy_for_parent);
    101. g_free(copy_for_base);
    102. return NULL;
    103. }
    104. }
    105. }
    106. char newname[1024];
    107. memset(newname, 0, 1024);
    108. // 给文件名加上.mjr的后缀
    109. if(rec_file == NULL) {
    110. /* Choose a random username */
    111. if(!rec_tempname) {
    112. /* Use .mjr as an extension right away */
    113. g_snprintf(newname, 1024, "janus-recording-%"SCNu32".mjr", janus_random_uint32());
    114. } else {
    115. /* Append the temporary extension to .mjr, we'll rename when closing */
    116. g_snprintf(newname, 1024, "janus-recording-%"SCNu32".mjr.%s", janus_random_uint32(), rec_tempext);
    117. }
    118. } else {
    119. /* Just append the extension */
    120. if(!rec_tempname) {
    121. /* Use .mjr as an extension right away */
    122. g_snprintf(newname, 1024, "%s.mjr", rec_file);
    123. } else {
    124. /* Append the temporary extension to .mjr, we'll rename when closing */
    125. g_snprintf(newname, 1024, "%s.mjr.%s", rec_file, rec_tempext);
    126. }
    127. }
    128. /* 打开文件,准备写入 */
    129. if(rec_dir == NULL) {
    130. /* Make sure folder to save to is not protected */
    131. if(janus_is_folder_protected(newname)) {
    132. JANUS_LOG(LOG_ERR, "Target recording path '%s' is in protected folder...\n", newname);
    133. janus_recorder_destroy(rc);
    134. g_free(copy_for_parent);
    135. g_free(copy_for_base);
    136. return NULL;
    137. }
    138. rc->file = fopen(newname, "wb");
    139. } else {
    140. char path[1024];
    141. memset(path, 0, 1024);
    142. g_snprintf(path, 1024, "%s/%s", rec_dir, newname);
    143. /* Make sure folder to save to is not protected */
    144. if(janus_is_folder_protected(path)) {
    145. JANUS_LOG(LOG_ERR, "Target recording path '%s' is in protected folder...\n", path);
    146. janus_recorder_destroy(rc);
    147. g_free(copy_for_parent);
    148. g_free(copy_for_base);
    149. return NULL;
    150. }
    151. rc->file = fopen(path, "wb");
    152. }
    153. if(rc->file == NULL) {
    154. JANUS_LOG(LOG_ERR, "fopen error: %d\n", errno);
    155. janus_recorder_destroy(rc);
    156. g_free(copy_for_parent);
    157. g_free(copy_for_base);
    158. return NULL;
    159. }
    160. if(rec_dir)
    161. rc->dir = g_strdup(rec_dir);
    162. rc->filename = g_strdup(newname);
    163. rc->type = type;
    164. /* 写入文件头:
    165. static const char *header = "MJR00002";
    166. */
    167. size_t res = fwrite(header, sizeof(char), strlen(header), rc->file);
    168. if(res != strlen(header)) {
    169. JANUS_LOG(LOG_ERR, "Couldn't write .mjr header (%zu != %zu, %s)\n",
    170. res, strlen(header), strerror(errno));
    171. janus_recorder_destroy(rc);
    172. g_free(copy_for_parent);
    173. g_free(copy_for_base);
    174. return NULL;
    175. }
    176. g_atomic_int_set(&rc->writable, 1);
    177. /* 除了写入上面的文件头外,还需要写入信息头,
    178. 所以在这里将写入信息头的标志置0
    179. */
    180. g_atomic_int_set(&rc->header, 0);
    181. janus_mutex_init(&rc->mutex);
    182. /* Done */
    183. g_atomic_int_set(&rc->destroyed, 0);
    184. g_free(copy_for_parent);
    185. g_free(copy_for_base);
    186. return rc;
    187. }

    三、录制数据

    对每个接收到的RTP包:
    首先:如果是第一个RTP包,则需要先写信息头到文件;
    然后:
          写入4字节的帧头"MEET";
          写入4字节的帧时间戳;
          写入2字节的帧长度;
    最后: 写入帧数据;

    1. void janus_videoroom_incoming_rtp(janus_plugin_session *handle, janus_plugin_rtp *pkt) {
    2. static void janus_videoroom_incoming_rtp_internal(janus_videoroom_session *session, janus_videoroom_publisher *participant, janus_plugin_rtp *pkt) {
    3. if(handle == NULL || g_atomic_int_get(&handle->stopped) || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
    4. return;
    5. janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
    6. if(!session || g_atomic_int_get(&session->destroyed) || session->participant_type != janus_videoroom_p_type_publisher)
    7. return;
    8. janus_videoroom_publisher *participant = janus_videoroom_session_get_publisher_nodebug(session);
    9. if(participant == NULL)
    10. return;
    11. if(g_atomic_int_get(&participant->destroyed) || participant->kicked || participant->room == NULL) {
    12. janus_videoroom_publisher_dereference_nodebug(participant);
    13. return;
    14. }
    15. janus_videoroom *videoroom = participant->room;
    16. gboolean video = pkt->video;
    17. char *buf = pkt->buffer;
    18. uint16_t len = pkt->length;
    19. /* 写入帧数据到录制文件 */
    20. if(!video || (participant->ssrc[0] == 0 && participant->rid[0] == NULL)) {
    21. janus_recorder_save_frame(video ? participant->vrc : participant->arc, buf, len);
    22. } else {
    23. /* We're simulcasting, save the best video quality */
    24. gboolean save = janus_rtp_simulcasting_context_process_rtp(&participant->rec_simctx,
    25. buf, len, participant->ssrc, participant->rid, participant->vcodec, &participant->rec_ctx);
    26. if(save) {
    27. uint32_t seq_number = ntohs(rtp->seq_number);
    28. uint32_t timestamp = ntohl(rtp->timestamp);
    29. uint32_t ssrc = ntohl(rtp->ssrc);
    30. janus_rtp_header_update(rtp, &participant->rec_ctx, TRUE, 0);
    31. /* We use a fixed SSRC for the whole recording */
    32. rtp->ssrc = participant->ssrc[0];
    33. janus_recorder_save_frame(participant->vrc, buf, len);
    34. /* Restore the header, as it will be needed by subscribers */
    35. rtp->ssrc = htonl(ssrc);
    36. rtp->timestamp = htonl(timestamp);
    37. rtp->seq_number = htons(seq_number);
    38. }
    39. }
    40. }
    41. // record.c
    42. int janus_recorder_save_frame(janus_recorder *recorder, char *buffer, uint length) {
    43. if(!recorder)
    44. return -1;
    45. janus_mutex_lock_nodebug(&recorder->mutex);
    46. if(!buffer || length < 1) {
    47. janus_mutex_unlock_nodebug(&recorder->mutex);
    48. return -2;
    49. }
    50. if(!recorder->file) {
    51. janus_mutex_unlock_nodebug(&recorder->mutex);
    52. return -3;
    53. }
    54. if(!g_atomic_int_get(&recorder->writable)) {
    55. janus_mutex_unlock_nodebug(&recorder->mutex);
    56. return -4;
    57. }
    58. gint64 now = janus_get_monotonic_time();
    59. // 如果是第一个包,则需要准备好信息头的数据, 将它的长度和内容写入到文件
    60. if(!g_atomic_int_get(&recorder->header)) {
    61. /* Write info header as a JSON formatted info */
    62. json_t *info = json_object();
    63. /* FIXME Codecs should be configurable in the future */
    64. const char *type = NULL;
    65. if(recorder->type == JANUS_RECORDER_AUDIO)
    66. type = "a";
    67. else if(recorder->type == JANUS_RECORDER_VIDEO)
    68. type = "v";
    69. else if(recorder->type == JANUS_RECORDER_DATA)
    70. type = "d";
    71. json_object_set_new(info, "t", json_string(type)); /* Audio/Video/Data */
    72. json_object_set_new(info, "c", json_string(recorder->codec)); /* Media codec */
    73. if(recorder->fmtp)
    74. json_object_set_new(info, "f", json_string(recorder->fmtp)); /* Codec-specific info */
    75. json_object_set_new(info, "s", json_integer(recorder->created)); /* Created time */
    76. json_object_set_new(info, "u", json_integer(janus_get_real_time())); /* First frame written time */
    77. /* If media will be end-to-end encrypted, mark it in the recording header */
    78. if(recorder->encrypted)
    79. json_object_set_new(info, "e", json_true());
    80. gchar *info_text = json_dumps(info, JSON_PRESERVE_ORDER);
    81. json_decref(info);
    82. uint16_t info_bytes = htons(strlen(info_text));
    83. // 将信息头的长度(info_bytes)写入文件
    84. size_t res = fwrite(&info_bytes, sizeof(uint16_t), 1, recorder->file);
    85. if(res != 1) {
    86. JANUS_LOG(LOG_WARN, "Couldn't write size of JSON header in .mjr file (%zu != %zu, %s), expect issues post-processing\n",
    87. res, sizeof(uint16_t), strerror(errno));
    88. }
    89. // 将信息头的内容(info_text) 写入文件
    90. res = fwrite(info_text, sizeof(char), strlen(info_text), recorder->file);
    91. if(res != strlen(info_text)) {
    92. JANUS_LOG(LOG_WARN, "Couldn't write JSON header in .mjr file (%zu != %zu, %s), expect issues post-processing\n",
    93. res, strlen(info_text), strerror(errno));
    94. }
    95. free(info_text);
    96. /* Done */
    97. recorder->started = now;
    98. // 将是否写入信息头的标志置 1 ;
    99. g_atomic_int_set(&recorder->header, 1);
    100. }
    101. /* Write frame header (fixed part[4], timestamp[4], length[2])
    102. 写入4个字节长度的固定内容的mjr包头:
    103. static const char *frame_header = "MEET";
    104. */
    105. size_t res = fwrite(frame_header, sizeof(char), strlen(frame_header), recorder->file);
    106. if(res != strlen(frame_header)) {
    107. JANUS_LOG(LOG_WARN, "Couldn't write frame header in .mjr file (%zu != %zu, %s), expect issues post-processing\n",
    108. res, strlen(frame_header), strerror(errno));
    109. }
    110. // 写入4个字节长度的时间戳
    111. uint32_t timestamp = (uint32_t)(now > recorder->started ? ((now - recorder->started)/1000) : 0);
    112. timestamp = htonl(timestamp);
    113. res = fwrite(×tamp, sizeof(uint32_t), 1, recorder->file);
    114. if(res != 1) {
    115. JANUS_LOG(LOG_WARN, "Couldn't write frame timestamp in .mjr file (%zu != %zu, %s), expect issues post-processing\n",
    116. res, sizeof(uint32_t), strerror(errno));
    117. }
    118. // 写入2个字节长度的帧长度
    119. uint16_t header_bytes = htons(recorder->type == JANUS_RECORDER_DATA ? (length+sizeof(gint64)) : length);
    120. res = fwrite(&header_bytes, sizeof(uint16_t), 1, recorder->file);
    121. if(res != 1) {
    122. JANUS_LOG(LOG_WARN, "Couldn't write size of frame in .mjr file (%zu != %zu, %s), expect issues post-processing\n",
    123. res, sizeof(uint16_t), strerror(errno));
    124. }
    125. if(recorder->type == JANUS_RECORDER_DATA) {
    126. /* If it's data, then we need to prepend timing related info, as it's not there by itself */
    127. gint64 now = htonll(janus_get_real_time());
    128. res = fwrite(&now, sizeof(gint64), 1, recorder->file);
    129. if(res != 1) {
    130. JANUS_LOG(LOG_WARN, "Couldn't write data timestamp in .mjr file (%zu != %zu, %s), expect issues post-processing\n",
    131. res, sizeof(gint64), strerror(errno));
    132. }
    133. }
    134. /* Save packet on file
    135. 写入帧数据到文件
    136. */
    137. int temp = 0, tot = length;
    138. while(tot > 0) {
    139. temp = fwrite(buffer+length-tot, sizeof(char), tot, recorder->file);
    140. if(temp <= 0) {
    141. JANUS_LOG(LOG_ERR, "Error saving frame...\n");
    142. janus_mutex_unlock_nodebug(&recorder->mutex);
    143. return -5;
    144. }
    145. tot -= temp;
    146. }
    147. /* Done */
    148. janus_mutex_unlock_nodebug(&recorder->mutex);
    149. return 0;
    150. }

    四、录制结束

    对录制文件重命名后,
    关闭文件句柄;

    1. /* Thread responsible for a specific remote publisher */
    2. static void *janus_videoroom_remote_publisher_thread(void *user_data) {
    3. /* If we got here, the remote publisher has been removed from the
    4. * room: let's notify all other publishers in the room */
    5. janus_mutex_lock(&publisher->rec_mutex);
    6. g_free(publisher->recording_base);
    7. publisher->recording_base = NULL;
    8. // 结束录制,看是否要对录制文件进行重命名
    9. janus_videoroom_recorder_close(publisher);
    10. janus_mutex_unlock(&publisher->rec_mutex);
    11. }
    12. // janus_videoroom.c
    13. static void janus_videoroom_recorder_close(janus_videoroom_publisher *participant) {
    14. if(participant->arc) {
    15. janus_recorder *rc = participant->arc;
    16. participant->arc = NULL;
    17. janus_recorder_close(rc);
    18. JANUS_LOG(LOG_INFO, "Closed audio recording %s\n", rc->filename ? rc->filename : "??");
    19. janus_recorder_destroy(rc);
    20. }
    21. if(participant->vrc) {
    22. janus_recorder *rc = participant->vrc;
    23. participant->vrc = NULL;
    24. janus_recorder_close(rc);
    25. JANUS_LOG(LOG_INFO, "Closed video recording %s\n", rc->filename ? rc->filename : "??");
    26. janus_recorder_destroy(rc);
    27. }
    28. if(participant->drc) {
    29. janus_recorder *rc = participant->drc;
    30. participant->drc = NULL;
    31. janus_recorder_close(rc);
    32. JANUS_LOG(LOG_INFO, "Closed data recording %s\n", rc->filename ? rc->filename : "??");
    33. janus_recorder_destroy(rc);
    34. }
    35. }
    36. //record.c
    37. // 结束录制,看是否要对录制文件进行重命名
    38. int janus_recorder_close(janus_recorder *recorder) {
    39. if(!recorder || !g_atomic_int_compare_and_exchange(&recorder->writable, 1, 0))
    40. return -1;
    41. janus_mutex_lock_nodebug(&recorder->mutex);
    42. if(recorder->file) {
    43. fseek(recorder->file, 0L, SEEK_END);
    44. size_t fsize = ftell(recorder->file);
    45. fseek(recorder->file, 0L, SEEK_SET);
    46. JANUS_LOG(LOG_INFO, "File is %zu bytes: %s\n", fsize, recorder->filename);
    47. }
    48. if(rec_tempname) {
    49. /* We need to rename the file, to remove the temporary extension */
    50. char newname[1024];
    51. memset(newname, 0, 1024);
    52. g_snprintf(newname, strlen(recorder->filename)-strlen(rec_tempext), "%s", recorder->filename);
    53. char oldpath[1024];
    54. memset(oldpath, 0, 1024);
    55. char newpath[1024];
    56. memset(newpath, 0, 1024);
    57. if(recorder->dir) {
    58. g_snprintf(newpath, 1024, "%s/%s", recorder->dir, newname);
    59. g_snprintf(oldpath, 1024, "%s/%s", recorder->dir, recorder->filename);
    60. } else {
    61. g_snprintf(newpath, 1024, "%s", newname);
    62. g_snprintf(oldpath, 1024, "%s", recorder->filename);
    63. }
    64. if(rename(oldpath, newpath) != 0) {
    65. JANUS_LOG(LOG_ERR, "Error renaming %s to %s...\n", recorder->filename, newname);
    66. } else {
    67. JANUS_LOG(LOG_INFO, "Recording renamed: %s\n", newname);
    68. g_free(recorder->filename);
    69. recorder->filename = g_strdup(newname);
    70. }
    71. }
    72. janus_mutex_unlock_nodebug(&recorder->mutex);
    73. return 0;
    74. }

  • 相关阅读:
    leaflet 地图遮罩、扣洞
    CPP-Templates-2nd--第十一章 泛型库
    运营攻略︱推动用户增长的方法
    【送书活动】强势挑战Java,Kotlin杀回TIOBE榜单Top 20!学Kotlin看哪些书?
    【C++从入门到精通】第1篇:C++基础知识(上)
    智慧工厂视频智能分析系统
    【JAVA刷题初阶】刷爆力扣第十弹——二叉树
    【红日靶场】vulnstack4-完整渗透过程
    LSTM和双向LSTM讲解及实践
    数学建模学习笔记(10):因子分析法
  • 原文地址:https://blog.csdn.net/fireroll/article/details/136274799