-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Save media extmaps to recordings #2422
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While I applaud the effort (thanks!), I think there are several things done wrong here.
- First of all, using a list to store these mappings is IMO incorrect: since you want to know which numeric ID corresponds to which extension, it should be a table instead (which would make it easier to save and process in mjr files too).
- I don't like how the Record&Play plugin is pregenerating
janus_sdp_attribute
elements and storing them in a list, to append them brutally to the SDP. This will most likely cause issues when renegotiations are done, since it may result in those attributes being deallocated and still referenced by the list in the recording object. If all we keep is a map, we can regenerate the attributes any time we need them. - I added other notes inline on some of the things I noticed that are wrong or dangerous.
plugins/janus_recordplay.c
Outdated
GList *filtered = NULL; | ||
while(attributes != NULL) { | ||
janus_sdp_attribute *attr = (janus_sdp_attribute *)attributes->data; | ||
if(!strcasecmp(attr->name, "extmap")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Action is multiline, per code style there should be brackets (less ambiguous, more readable).
plugins/janus_recordplay.c
Outdated
@@ -562,16 +597,19 @@ static const char *janus_recordplay_parse_codec(const char *dir, const char *fil | |||
offset += 2; | |||
if(len > 0 && !parsed_header) { | |||
/* This is the info header */ | |||
bytes = fread(prebuffer, sizeof(char), len, file); | |||
char *json_header = malloc(sizeof(char)*(len+1)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use g_malloc
/g_free
. That said, not sure why you're allocating? 1500 bytes should be more than enough even if extensions are in the json.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In theory it's possible (even though it's highly unlikely) for JSON header to have more than 1500 chars if there are many extmaps with long URIs, so I added json_header buffer here just in case.
plugins/janus_recordplay.c
Outdated
char delim = ':', **ext_split = g_strsplit(ext_str, &delim, 2); | ||
janus_sdp_attribute *ext = janus_sdp_attribute_create( | ||
g_strdup(ext_split[0]), | ||
ext_split[1] ? g_strdup(ext_split[1]) : NULL); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is wrong. It's assuming g_strsplit
will succeed, that there are at least two parts and that both are not NULL
. This will segfault if any of those assumptions are incorrect. Besides, there's no need to g_strdup
when doing janus_sdp_attribute_create
, as that method already allocates new stuff for those strings: you're generating memory leaks, as those strings you're allocating are lost.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've slightly misread g_strsplit documentation, thanks for pointing this out.
plugins/janus_recordplay.c
Outdated
@@ -653,15 +706,24 @@ static int janus_recordplay_generate_offer(janus_recordplay_recording *rec) { | |||
JANUS_SDP_OA_AUDIO_PT, rec->audio_pt, | |||
JANUS_SDP_OA_AUDIO_FMTP, rec->afmtp, | |||
JANUS_SDP_OA_AUDIO_DIRECTION, JANUS_SDP_SENDONLY, | |||
JANUS_SDP_OA_AUDIO_EXTENSION, JANUS_RTP_EXTMAP_MID, 1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do NOT remove this: mid
is NOT one of the extensions you're notified about in the extmap. It's always added by the core, and so it must always be there.
JANUS_SDP_OA_VIDEO, offer_video, | ||
JANUS_SDP_OA_VIDEO_CODEC, janus_videocodec_name(rec->vcodec), | ||
JANUS_SDP_OA_VIDEO_FMTP, rec->vfmtp, | ||
JANUS_SDP_OA_VIDEO_PT, rec->video_pt, | ||
JANUS_SDP_OA_VIDEO_DIRECTION, JANUS_SDP_SENDONLY, | ||
JANUS_SDP_OA_VIDEO_EXTENSION, JANUS_RTP_EXTMAP_MID, 1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above.
plugins/janus_recordplay.c
Outdated
} | ||
if(offer_video) { | ||
janus_sdp_mline *video = janus_sdp_mline_find(offer, JANUS_SDP_VIDEO); | ||
video->attributes = g_list_concat(video->attributes, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above.
plugins/janus_recordplay.c
Outdated
} | ||
GList *attr = janus_sdp_mline_find(answer, JANUS_SDP_AUDIO)->attributes; | ||
rec->audio_extmaps = janus_recordplay_filter_extmaps(attr); | ||
session->arc->extmaps = janus_recordplay_bake_extmaps(rec->audio_extmaps); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not checking if session->arc
exists.
plugins/janus_recordplay.c
Outdated
} | ||
GList *attr = janus_sdp_mline_find(answer, JANUS_SDP_VIDEO)->attributes; | ||
rec->video_extmaps = janus_recordplay_filter_extmaps(attr); | ||
session->vrc->extmaps = janus_recordplay_bake_extmaps(rec->video_extmaps); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above.
record.h
Outdated
@@ -48,6 +49,8 @@ typedef struct janus_recorder { | |||
char *codec; | |||
/*! \brief Codec-specific info (e.g., H.264 or VP9 profile) */ | |||
char *fmtp; | |||
/*! \brief Media extmaps */ | |||
GList *extmaps; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think using a list here is wrong. It should be a table, if the purpose is mapping a numeric extension ID to the extension namespace. Not even sure what the value saved there is?
json_array_append_new(ext_json, json_string(ext->data)); | ||
ext = ext->next; | ||
} | ||
json_object_set(info, "x", ext_json); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the purpose is having mappings, here, this should be a key/value object, not a serialized string.
Thanks for the quick review, I'll try to update the PR to address the comments sometime today. |
Changed extmaps to use |
Sorry, we're busy with the IETF meeting, so we won't be able to do much before next week. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added some more notes. Thanks!
g_hash_table_remove_all(extmaps); | ||
for(; attributes != NULL; attributes = attributes->next) { | ||
janus_sdp_attribute *attr = (janus_sdp_attribute *)attributes->data; | ||
if(!strcasecmp(attr->name, "extmap")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to expect both attr->name
and attr->value
to be valid and not NULL
: there should be a check on both.
record.c
Outdated
GHashTableIter iter; | ||
char *ext_id, *ext_info; | ||
g_hash_table_iter_init(&iter, recorder->extmaps); | ||
while(g_hash_table_iter_next(&iter, (gpointer *)&ext_id, (gpointer *)&ext_info)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the &
before the variables? They're pointers already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a side note, this isn't checking whether either ext_id
or ext_info
are NULL
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because values saved within the table are of type char *
, so i pass pointer to value type char *
so it can be populated while iterating.
if(audio_level_extmap_id < 1 && strstr(ext[i], JANUS_RTP_EXTMAP_AUDIO_LEVEL)) | ||
audio_level_extmap_id = atoi(ext[i]); | ||
if(video_orient_extmap_id < 1 && strstr(ext[i], JANUS_RTP_EXTMAP_VIDEO_ORIENTATION)) | ||
video_orient_extmap_id = atoi(ext[i]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't checking if ext[i]
is NULL
.
postprocessing/janus-pp-rec.c
Outdated
exit(1); | ||
} | ||
ext_n = json_object_size(ext_json); | ||
ext = g_new(char *, ext_n); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if this is an empty object? ext_n
will be 0
.
plugins/janus_recordplay.c
Outdated
if(ext_json && json_is_object(ext_json)) { | ||
g_hash_table_remove_all(extmaps); | ||
json_object_foreach(ext_json, ext_id, ext_info) | ||
g_hash_table_insert(extmaps, g_strdup(ext_id), (gpointer)json_string_value(ext_info)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above: not checking if those strings are NULL
.
plugins/janus_recordplay.c
Outdated
g_hash_table_iter_init(&iter, extmaps); | ||
while(g_hash_table_iter_next(&iter, (gpointer *)&ext_id, (gpointer *)&ext_info)) | ||
g_hash_table_insert(mapped, g_strdup(ext_id), g_strdup(ext_info)); | ||
/* Remove existing extmaps */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is quite wrong: generate offer adds a mid
extmap, that you'd be removing here. The only thing that you should be doing here is possibly see if an attribute you're adding now already exists (maybe with a different ID), or if there are ID conflicts, so those are the only ones you should remove IMO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generated extmaps are merged with media attributes into temporary merged
hashmap, with former having priority, so this would not remove mid
extmap as it would be present in merged
hashmap and re-added further down. However, upon inspecting this part of code, it is not very clear how it does what it does, and has some possible edge case errors, so i will rewrite it.
0f0860e
to
1770a8f
Compare
Thanks for the review @lminiero. Sorry for pinging you while you were attending IETF meeting, hope it went well though! I have updated the PR per your comments, and rebased it to master to check for possible conflicts. |
@@ -1282,7 +1282,7 @@ janus_sdp *janus_sdp_generate_offer(const char *name, const char *address, ...) | |||
char *extmap = g_hash_table_lookup(audio_extids, iter->data); | |||
if(extmap != NULL) { | |||
janus_sdp_attribute *a = janus_sdp_attribute_create("extmap", | |||
"%d %s\r\n", GPOINTER_TO_INT(iter->data), extmap); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct me if newlines were supposed to be here, but they do not appear in any other janus_sdp_attribute_create
calls. I removed them because they mess up extmap value matching (I could've just trimmed extmap values when processing but this seemed to be more correct).
plugins/janus_recordplay.c
Outdated
JANUS_LOG(LOG_WARN, "Extmap has null fields\n"); | ||
continue; | ||
} | ||
JANUS_LOG(LOG_INFO, " > %d:%s\n", *ext_id, ext_info); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd make this log more specific than just id > info
.
Also consider lowering its level to verbose.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is one of debug logs I used for testing, it shouldn't be here. Thanks for spotting this.
To be honest after discussing with @atoppi this still feels very convoluted for us. It looks like everything is handled in a more complex way than it should. From what we gather, this basically needs these core functionality:
Point 1. is indeed there, but 2. seems implicit (plugins work on the hashtable directly) which can be problematic. Point 3. is the part where I think it has been implemented in a way that is way more complex than necessary, with duplicated tables, stuff that is regenerated, etc., all things that will add a maintainance burden and will prevent an easy integration in other plugins in the future (just imagine doing all this in the VideoRoom!). It feels like all the plugin needs is a simple way to add extensions one by one when a recording is being created, and iterating on those from existing recordings to create the attributes the first time, without complex structures to back the functionality. I understand we've asked for way too many changes already, so I don't expect you to completely refactor the effort now. I'll try to come up with a simpler way to address this requirement and publish a new PR (possibly by the end of the week), so that we can check if it solves your need in a cleaner way. We can keep this PR open for the time being until that happens. |
Thanks for the update. I agree with what has been said, especially with the third point - the current way of adding extmaps to SDP offer is not very elegant. I look forward to your PR. |
@isnumanagic apologies for the lack of feedback. I've been swamped with work so I had no time to work on this. I'll try to come up with something in the next few days or next week. |
@isnumanagic please see the PR mentioned above. I'll close this one in the meanwhile. Thanks again! |
This PR allows recordplay plugin and recorder to save and read media extmaps in .mjr header as array with
x
key. This in turn allows recordings with video-orient extmap to replay with correct orientation (among others), and allows rtp header extensions to be correctly mapped in janus-pp-rec tool without needing to pass--videoorient-ext
or--audiolevel-ext
params.