Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pause/resume recording functionality to Record&Play and SIP plugins #2724

Merged
merged 7 commits into from
Oct 1, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion html/recordplaytest.html
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,11 @@ <h3 class="panel-title">Recorder/Playout</h3>
<div class="col-md-6 hide" id="video">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><span id="videotitle">Remote Video</span> <button class="btn-xs btn-danger pull-right" autocomplete="off" id="stop">Stop</button></h3>
<h3 class="panel-title">
<span id="videotitle">Remote Video</span>
<button class="btn-xs btn-danger pull-right" autocomplete="off" id="stop">Stop</button>
<button class="btn-xs btn-primary pull-right" autocomplete="off" id="pause-resume">Pause</button>
isnumanagic marked this conversation as resolved.
Show resolved Hide resolved
</h3>
</div>
<div class="panel-body" id="videobox"></div>
</div>
Expand Down
9 changes: 9 additions & 0 deletions html/recordplaytest.js
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,15 @@ function startRecording() {
recordplay.hangup();
}
});
$('#pause-resume').unbind('click').on('click', function() {
if($(this).text() === 'Pause') {
recordplay.send({message: {request: 'pause'}});
$(this).text('Resume');
} else {
recordplay.send({message: {request: 'resume'}});
$(this).text('Pause');
}
});
});
}

Expand Down
30 changes: 29 additions & 1 deletion plugins/janus_recordplay.c
Original file line number Diff line number Diff line change
Expand Up @@ -1104,7 +1104,8 @@ struct janus_plugin_result *janus_recordplay_handle_message(janus_plugin_session
json_object_set_new(response, "settings", settings);
goto plugin_response;
} else if(!strcasecmp(request_text, "record") || !strcasecmp(request_text, "play")
|| !strcasecmp(request_text, "start") || !strcasecmp(request_text, "stop")) {
|| !strcasecmp(request_text, "start") || !strcasecmp(request_text, "stop")
|| !strcasecmp(request_text, "pause") || !strcasecmp(request_text, "resume")) {
/* These messages are handled asynchronously */
janus_recordplay_message *msg = g_malloc(sizeof(janus_recordplay_message));
msg->handle = handle;
Expand Down Expand Up @@ -2007,6 +2008,33 @@ static void *janus_recordplay_handler(void *data) {
}
/* Tell the core to tear down the PeerConnection, hangup_media will do the rest */
gateway->close_pc(session->handle);
} else if (!strcasecmp(request_text, "pause") || !strcasecmp(request_text, "resume")) {
JANUS_LOG(LOG_VERB, "Record&Play: Got pause/resume request\n");
if(session->recording) {
gboolean pause = !strcasecmp(request_text, "pause");
isnumanagic marked this conversation as resolved.
Show resolved Hide resolved
result = json_object();
json_object_set_new(result, "status", json_string(pause ? "paused" : "resumed"));
json_object_set_new(result, "id", json_integer(session->recording->id));
/* Also notify event handlers */
if(notify_events && gateway->events_is_enabled()) {
json_t *info = json_object();
json_object_set_new(info, "event", json_string(pause ? "paused" : "resumed"));
json_object_set_new(info, "id", json_integer(session->recording->id));
gateway->notify_event(&janus_recordplay_plugin, session->handle, info);
isnumanagic marked this conversation as resolved.
Show resolved Hide resolved
}
if(pause) {
janus_recorder_pause(session->arc);
janus_recorder_pause(session->vrc);
janus_recorder_pause(session->drc);
} else {
janus_recorder_resume(session->arc);
janus_recorder_resume(session->vrc);
janus_recorder_resume(session->drc);
gateway->send_pli(session->handle);
}
} else {
JANUS_LOG(LOG_VERB, "Record&Play: Not recording, ignoring pause/resume request\n");
}
} else {
JANUS_LOG(LOG_ERR, "Unknown request '%s'\n", request_text);
error_code = JANUS_RECORDPLAY_ERROR_INVALID_REQUEST;
Expand Down
27 changes: 24 additions & 3 deletions plugins/janus_sip.c
Original file line number Diff line number Diff line change
Expand Up @@ -4357,10 +4357,11 @@ static void *janus_sip_handler(void *data) {
goto error;
json_t *action = json_object_get(root, "action");
const char *action_text = json_string_value(action);
if(strcasecmp(action_text, "start") && strcasecmp(action_text, "stop")) {
JANUS_LOG(LOG_ERR, "Invalid action (should be start|stop)\n");
if(strcasecmp(action_text, "start") && strcasecmp(action_text, "stop") &&
strcasecmp(action_text, "pause") && strcasecmp(action_text, "resume")) {
JANUS_LOG(LOG_ERR, "Invalid action (should be start|stop|pause|resume)\n");
error_code = JANUS_SIP_ERROR_INVALID_ELEMENT;
g_snprintf(error_cause, 512, "Invalid action (should be start|stop)");
g_snprintf(error_cause, 512, "Invalid action (should be start|stop|pause|resume)");
goto error;
}
gboolean record_audio = FALSE, record_video = FALSE, /* No media is recorded by default */
Expand Down Expand Up @@ -4502,6 +4503,26 @@ static void *janus_sip_handler(void *data) {
gateway->send_pli(session->handle);
}
}
} else if(!strcasecmp(action_text, "pause")) {
if(record_audio)
janus_recorder_pause(session->arc);
if(record_video)
janus_recorder_pause(session->vrc);
if(record_peer_audio)
janus_recorder_pause(session->arc_peer);
if(record_peer_video)
janus_recorder_pause(session->vrc_peer);
} else if(!strcasecmp(action_text, "resume")) {
if(record_audio)
janus_recorder_resume(session->arc);
if(record_video)
janus_recorder_resume(session->vrc);
if(record_peer_audio)
janus_recorder_resume(session->arc_peer);
if(record_peer_video)
janus_recorder_resume(session->vrc_peer);
if(record_video || record_peer_video)
gateway->send_pli(session->handle);
isnumanagic marked this conversation as resolved.
Show resolved Hide resolved
} else {
/* Stop recording something: notice that this never returns an error, even when we were not recording anything */
janus_sip_recorder_close(session, record_audio, record_peer_audio, record_video, record_peer_video);
Expand Down
52 changes: 52 additions & 0 deletions record.c
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ janus_recorder *janus_recorder_create_full(const char *dir, const char *codec, c
/* Create the recorder */
janus_recorder *rc = g_malloc0(sizeof(janus_recorder));
janus_refcount_init(&rc->ref, janus_recorder_free);
janus_rtp_switching_context_reset(&rc->context);
rc->dir = NULL;
rc->filename = NULL;
rc->file = NULL;
Expand Down Expand Up @@ -255,6 +256,31 @@ janus_recorder *janus_recorder_create_full(const char *dir, const char *codec, c
return rc;
}

int janus_recorder_pause(janus_recorder *recorder) {
if(!recorder)
return -1;
g_atomic_int_set(&recorder->paused, 1);
return 0;
}

int janus_recorder_resume(janus_recorder *recorder) {
if(!recorder)
return -1;
janus_mutex_lock_nodebug(&recorder->mutex);
if(recorder->type == JANUS_RECORDER_AUDIO) {
recorder->context.a_ts_reset = TRUE;
recorder->context.a_seq_reset = TRUE;
recorder->context.a_last_time = janus_get_monotonic_time();
} else if(recorder->type == JANUS_RECORDER_VIDEO) {
recorder->context.v_ts_reset = TRUE;
recorder->context.v_seq_reset = TRUE;
recorder->context.v_last_time = janus_get_monotonic_time();
}
g_atomic_int_set(&recorder->paused, 0);
janus_mutex_unlock_nodebug(&recorder->mutex);
isnumanagic marked this conversation as resolved.
Show resolved Hide resolved
return 0;
}

int janus_recorder_add_extmap(janus_recorder *recorder, int id, const char *extmap) {
if(!recorder || g_atomic_int_get(&recorder->header) || id < 1 || id > 15 || extmap == NULL)
return -1;
Expand Down Expand Up @@ -292,6 +318,10 @@ int janus_recorder_save_frame(janus_recorder *recorder, char *buffer, uint lengt
janus_mutex_unlock_nodebug(&recorder->mutex);
return -4;
}
if(g_atomic_int_get(&recorder->paused)) {
janus_mutex_unlock_nodebug(&recorder->mutex);
return -5;
}
gint64 now = janus_get_monotonic_time();
if(!g_atomic_int_get(&recorder->header)) {
/* Write info header as a JSON formatted info */
Expand Down Expand Up @@ -384,17 +414,39 @@ int janus_recorder_save_frame(janus_recorder *recorder, char *buffer, uint lengt
res, sizeof(gint64), g_strerror(errno));
}
}
/* Edit packet header if needed */
janus_rtp_header *header = (janus_rtp_header *)buffer;
uint32_t ssrc;
uint16_t seq;
isnumanagic marked this conversation as resolved.
Show resolved Hide resolved
if(recorder->type != JANUS_RECORDER_DATA) {
isnumanagic marked this conversation as resolved.
Show resolved Hide resolved
ssrc = ntohl(header->ssrc);
seq = ntohs(header->seq_number);
timestamp = ntohl(header->timestamp);
janus_rtp_header_update(header, &recorder->context, recorder->type == JANUS_RECORDER_VIDEO, 0);
}
isnumanagic marked this conversation as resolved.
Show resolved Hide resolved
/* Save packet on file */
int temp = 0, tot = length;
while(tot > 0) {
temp = fwrite(buffer+length-tot, sizeof(char), tot, recorder->file);
if(temp <= 0) {
JANUS_LOG(LOG_ERR, "Error saving frame...\n");
if(recorder->type != JANUS_RECORDER_DATA) {
/* Restore packet header data */
header->ssrc = htonl(ssrc);
header->seq_number = htons(seq);
header->timestamp = htonl(timestamp);
}
janus_mutex_unlock_nodebug(&recorder->mutex);
return -6;
}
tot -= temp;
}
if(recorder->type != JANUS_RECORDER_DATA) {
/* Restore packet header data */
header->ssrc = htonl(ssrc);
header->seq_number = htons(seq);
header->timestamp = htonl(timestamp);
}
/* Done */
janus_mutex_unlock_nodebug(&recorder->mutex);
return 0;
Expand Down
15 changes: 15 additions & 0 deletions record.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

#include "mutex.h"
#include "refcount.h"
#include "rtp.h"


/*! \brief Media types we can record */
Expand Down Expand Up @@ -60,6 +61,10 @@ typedef struct janus_recorder {
volatile int header;
/*! \brief Whether this recorder instance can be used for writing or not */
volatile int writable;
/*! \brief Whether writing s/RTP packets/data is paused */
volatile int paused;
/*! \brief RTP switching context for rewriting RTP headers */
janus_rtp_switching_context context;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not fond of the idea of having a context as part of the recorder itself. Our plugins all have a dedicated context which they use for recordings too, even though I guess in this case we need something that's specific to the recording itself. I'll need to think about this.

/*! \brief Mutex to lock/unlock this recorder instance */
janus_mutex mutex;
/*! \brief Atomic flag to check if this instance has been destroyed */
Expand Down Expand Up @@ -92,6 +97,16 @@ janus_recorder *janus_recorder_create(const char *dir, const char *codec, const
* @param[in] filename Filename to use for the recording
* @returns A valid janus_recorder instance in case of success, NULL otherwise */
janus_recorder *janus_recorder_create_full(const char *dir, const char *codec, const char *fmtp, const char *filename);
/*! \brief Pause recording packets
* \note This is to allow pause and resume recorder functionality.
* @param[in] recorder The janus_recorder to pause
* @returns 0 in case of success, a negative integer otherwise */
int janus_recorder_pause(janus_recorder *recorder);
/*! \brief Resume recording packets
* \note This is to allow pause and resume recorder functionality.
* @param[in] recorder The janus_recorder to resume
* @returns 0 in case of success, a negative integer otherwise */
int janus_recorder_resume(janus_recorder *recorder);
/*! \brief Add an RTP extension to this recording
* \note This will only be possible BEFORE the first frame is written, as it needs to
* be reflected in the .mjr header: doing this after that will return an error.
Expand Down
36 changes: 24 additions & 12 deletions rtp.c
Original file line number Diff line number Diff line change
Expand Up @@ -601,10 +601,17 @@ void janus_rtp_header_update(janus_rtp_header *header, janus_rtp_switching_conte
JANUS_LOG(LOG_VERB, "Video SSRC changed, %"SCNu32" --> %"SCNu32"\n",
context->v_last_ssrc, ssrc);
context->v_last_ssrc = ssrc;
context->v_ts_reset = TRUE;
context->v_seq_reset = TRUE;
/* Reset skew compensation data */
context->v_new_ssrc = TRUE;
}
if(context->v_ts_reset) {
/* Video timestamp was paused for a while */
JANUS_LOG(LOG_HUGE, "Video RTP timestamp reset requested");
context->v_ts_reset = FALSE;
context->v_base_ts_prev = context->v_last_ts;
context->v_base_ts = timestamp;
context->v_base_seq_prev = context->v_last_seq;
context->v_base_seq = seq;
/* How much time since the last video RTP packet? We compute an offset accordingly */
if(context->v_last_time > 0) {
gint64 time_diff = janus_get_monotonic_time() - context->v_last_time;
Expand All @@ -613,13 +620,12 @@ void janus_rtp_header_update(janus_rtp_header *header, janus_rtp_switching_conte
time_diff = 1;
context->v_base_ts_prev += (guint32)time_diff;
context->v_last_ts += (guint32)time_diff;
JANUS_LOG(LOG_VERB, "Computed offset for video RTP timestamp: %"SCNu32"\n", (guint32)time_diff);
JANUS_LOG(LOG_HUGE, "Computed offset for video RTP timestamp: %"SCNu32"\n", (guint32)time_diff);
}
/* Reset skew compensation data */
context->v_new_ssrc = TRUE;
}
if(context->v_seq_reset) {
/* Video sequence number was paused for a while: just update that */
/* Video sequence number was paused for a while */
JANUS_LOG(LOG_HUGE, "Video RTP sequence number reset requested");
context->v_seq_reset = FALSE;
context->v_base_seq_prev = context->v_last_seq;
context->v_base_seq = seq;
Expand All @@ -640,10 +646,17 @@ void janus_rtp_header_update(janus_rtp_header *header, janus_rtp_switching_conte
JANUS_LOG(LOG_VERB, "Audio SSRC changed, %"SCNu32" --> %"SCNu32"\n",
context->a_last_ssrc, ssrc);
context->a_last_ssrc = ssrc;
context->a_ts_reset = TRUE;
context->a_seq_reset = TRUE;
/* Reset skew compensation data */
context->a_new_ssrc = TRUE;
}
if(context->a_ts_reset) {
/* Audio timestamp was paused for a while */
JANUS_LOG(LOG_HUGE, "Audio RTP timestamp reset requested");
context->a_ts_reset = FALSE;
context->a_base_ts_prev = context->a_last_ts;
context->a_base_ts = timestamp;
context->a_base_seq_prev = context->a_last_seq;
context->a_base_seq = seq;
/* How much time since the last audio RTP packet? We compute an offset accordingly */
if(context->a_last_time > 0) {
gint64 time_diff = janus_get_monotonic_time() - context->a_last_time;
Expand All @@ -656,13 +669,12 @@ void janus_rtp_header_update(janus_rtp_header *header, janus_rtp_switching_conte
context->a_base_ts_prev += (guint32)time_diff;
context->a_prev_ts += (guint32)time_diff;
context->a_last_ts += (guint32)time_diff;
JANUS_LOG(LOG_VERB, "Computed offset for audio RTP timestamp: %"SCNu32"\n", (guint32)time_diff);
JANUS_LOG(LOG_HUGE, "Computed offset for audio RTP timestamp: %"SCNu32"\n", (guint32)time_diff);
}
/* Reset skew compensation data */
context->a_new_ssrc = TRUE;
}
if(context->a_seq_reset) {
/* Audio sequence number was paused for a while: just update that */
/* Audio sequence number was paused for a while */
JANUS_LOG(LOG_HUGE, "Audio RTP sequence number reset requested");
context->a_seq_reset = FALSE;
context->a_base_seq_prev = context->a_last_seq;
context->a_base_seq = seq;
Expand Down
4 changes: 2 additions & 2 deletions rtp.h
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,8 @@ typedef struct janus_rtp_switching_context {
v_last_ssrc, v_last_ts, v_base_ts, v_base_ts_prev, v_prev_ts, v_target_ts, v_start_ts;
uint16_t a_last_seq, a_prev_seq, a_base_seq, a_base_seq_prev,
v_last_seq, v_prev_seq, v_base_seq, v_base_seq_prev;
gboolean a_seq_reset, a_new_ssrc,
v_seq_reset, v_new_ssrc;
gboolean a_ts_reset, a_seq_reset, a_new_ssrc,
v_ts_reset, v_seq_reset, v_new_ssrc;
gint16 a_seq_offset,
v_seq_offset;
gint32 a_prev_delay, a_active_delay, a_ts_offset,
Expand Down