Skip to content

Commit

Permalink
Add support for VP9 and H.264 profile negotiation (#2080)
Browse files Browse the repository at this point in the history
  • Loading branch information
lminiero committed May 18, 2020
1 parent e905e5f commit fa12368
Show file tree
Hide file tree
Showing 18 changed files with 556 additions and 71 deletions.
2 changes: 2 additions & 0 deletions conf/janus.plugin.videoroom.jcfg.sample
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
# can be a comma separated list in order of preference, e.g., opus,pcmu)
# videocodec = vp8|vp9|h264 (video codec(s) to force on publishers, default=vp8
# can be a comma separated list in order of preference, e.g., vp9,vp8,h264)
# vp9_profile = VP9-specific profile to prefer (e.g., "2" for "profile-id=2")
# h264_profile = H.264-specific profile to prefer (e.g., "42e01f" for "profile-level-id=42e01f")
# opus_fec = true|false (whether inband FEC must be negotiated; only works for Opus, default=false)
# video_svc = true|false (whether SVC support must be enabled; only works for VP9, default=false)
# audiolevel_ext = true|false (whether the ssrc-audio-level RTP extension must
Expand Down
5 changes: 5 additions & 0 deletions html/echotest.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ var doSimulcast = (getQueryStringValue("simulcast") === "yes" || getQueryStringV
var doSimulcast2 = (getQueryStringValue("simulcast2") === "yes" || getQueryStringValue("simulcast2") === "true");
var acodec = (getQueryStringValue("acodec") !== "" ? getQueryStringValue("acodec") : null);
var vcodec = (getQueryStringValue("vcodec") !== "" ? getQueryStringValue("vcodec") : null);
var vprofile = (getQueryStringValue("vprofile") !== "" ? getQueryStringValue("vprofile") : null);
var simulcastStarted = false;

$(document).ready(function() {
Expand Down Expand Up @@ -106,6 +107,10 @@ $(document).ready(function() {
body["audiocodec"] = acodec;
if(vcodec)
body["videocodec"] = vcodec;
// For the codecs that support them (VP9 and H.264) you can specify a codec
// profile as well (e.g., ?vprofile=2 for VP9, or ?vprofile=42e01f for H.264)
if(vprofile)
body["videoprofile"] = vprofile;
Janus.debug("Sending message (" + JSON.stringify(body) + ")");
echotest.send({"message": body});
Janus.debug("Trying a createOffer too (audio/video sendrecv)");
Expand Down
13 changes: 13 additions & 0 deletions html/recordplaytest.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ var recordingId = null;
var selectedRecording = null;
var selectedRecordingInfo = null;

var acodec = (getQueryStringValue("acodec") !== "" ? getQueryStringValue("acodec") : null);
var vcodec = (getQueryStringValue("vcodec") !== "" ? getQueryStringValue("vcodec") : null);
var vprofile = (getQueryStringValue("vprofile") !== "" ? getQueryStringValue("vprofile") : null);
var doSimulcast = (getQueryStringValue("simulcast") === "yes" || getQueryStringValue("simulcast") === "true");
var doSimulcast2 = (getQueryStringValue("simulcast2") === "yes" || getQueryStringValue("simulcast2") === "true");

Expand Down Expand Up @@ -431,6 +434,16 @@ function startRecording() {
Janus.debug("Got SDP!");
Janus.debug(jsep);
var body = { "request": "record", "name": myname };
// We can try and force a specific codec, by telling the plugin what we'd prefer
// For simplicity, you can set it via a query string (e.g., ?vcodec=vp9)
if(acodec)
body["audiocodec"] = acodec;
if(vcodec)
body["videocodec"] = vcodec;
// For the codecs that support them (VP9 and H.264) you can specify a codec
// profile as well (e.g., ?vprofile=2 for VP9, or ?vprofile=42e01f for H.264)
if(vprofile)
body["videoprofile"] = vprofile;
recordplay.send({"message": body, "jsep": jsep});
},
error: function(error) {
Expand Down
9 changes: 8 additions & 1 deletion plugins/duktape/echotest.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,11 @@ function processRequest(id, msg) {
if(!fnbase) {
fnbase = "duktape-echotest-" + id + "-" + new Date().getTime();
}
// For the sake of simplicity, we're assuming Opus/VP8 here; in
// practice, you'll need to check what was negotiated. If you
// want the codec-specific info to be saved to the .mjr file as
// well, you'll need to add the '/fmtp=<info>' to the codec name,
// e.g.: "vp9/fmtp=profile-id=2"
startRecording(id,
"audio", "opus", "/tmp", fnbase + "-audio",
"video", "vp8", "/tmp", fnbase + "-video",
Expand All @@ -263,7 +268,9 @@ function processAsync(task) {
}
var offer = sdpUtils.parse(jsep.sdp)
console.log("Got offer:", offer);
var answer = sdpUtils.generateAnswer(offer, { audio: true, video: true, data: true });
var answer = sdpUtils.generateAnswer(offer, { audio: true, video: true, data: true,
audioCodec: msg["audiocodec"], videoCodec: msg["videocodec"],
vp9Profile: msg["videoprofile"], h264Profile: msg["videoprofile"] });
console.log("Generated answer:", answer);
console.log("Processing request:", msg);
processRequest(id, msg);
Expand Down
58 changes: 48 additions & 10 deletions plugins/duktape/janus-sdp.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,39 @@ JANUSSDP.render = function(sdp) {
return sdpString;
}

JANUSSDP.findPayloadType = function(sdp, codec) {
JANUSSDP.findPayloadType = function(sdp, codec, profile) {
if(!sdp || !codec)
return -1
var pt = -1;
var codecUpper = codec.toUpperCase();
var codecLower = codec.toLowerCase();
var checkProfile = false;
for(var index in sdp) {
var a = sdp[index];
if(a.name === "rtpmap" && a.value) {
if(checkProfile && a.name === "fmtp" && a.value) {
checkProfile = false;
if(codec === "vp9") {
if(a.value.indexOf("profile-level="+profile) != -1) {
// Found
break;
}
} else if(codec === "h264") {
if(a.value.indexOf("profile-level-id="+profile.toLowerCase()) != -1 ||
a.value.indexOf("profile-level-id="+profile.toUpperCase()) != -1) {
// Found
break;
}
}
} else if(a.name === "rtpmap" && a.value) {
if(a.value.indexOf(codecLower) != -1 || a.value.indexOf(codecUpper) !== -1) {
pt = parseInt(a.value);
break;
if(!profile) {
// We're done
break;
} else {
// We need to make sure the profile matches
checkProfile = true;
}
}
}
}
Expand Down Expand Up @@ -210,21 +231,30 @@ JANUSSDP.generateOffer = function(options) {
offer.push({ type: "c", name: "IN " + (options.ipv6 ? "IP6 " : "IP4 ") + options.address });
offer.push({ type: "a", name: options.audioDir });
offer.push({ type: "a", name: "rtpmap", value: options.audioPt + " " + options.audioRtpmap });
if(options.audioFmtp) {
offer.push({ type: "a", name: "fmtp", value: options.audioPt + " " + options.audioFmtp });
}
}
if(options.video) {
offer.push({ type: "m", name: "video 9 UDP/TLS/RTP/SAVPF " + options.videoPt });
offer.push({ type: "c", name: "IN " + (options.ipv6 ? "IP6 " : "IP4 ") + options.address });
offer.push({ type: "a", name: options.videoDir });
offer.push({ type: "a", name: "rtpmap", value: options.videoPt + " " + options.videoRtpmap });
if(options.videoCodec === "h264") {
offer.push({ type: "a", name: "fmtp", value: options.videoPt + " profile-level-id=42e01f;packetization-mode=1" });
}
if(options.videoRtcpfb) {
offer.push({ type: "a", name: "rtcp-fb", value: options.videoPt + " ccm fir" });
offer.push({ type: "a", name: "rtcp-fb", value: options.videoPt + " nack" });
offer.push({ type: "a", name: "rtcp-fb", value: options.videoPt + " nack pli" });
offer.push({ type: "a", name: "rtcp-fb", value: options.videoPt + " goog-remb" });
}
if(options.videoCodec === "vp9" && options.vp9Profile) {
offer.push({ type: "a", name: "fmtp", value: options.videoPt + " profile-id=" + options.vp9Profile });
} else if(options.videoCodec === "h264" && options.h264Profile) {
offer.push({ type: "a", name: "fmtp", value: options.videoPt + " profile-level-id=" + options.h264Profile + ";packetization-mode=1" });
} else if(options.videoFmtp) {
offer.push({ type: "a", name: "fmtp", value: options.videoPt + " " + options.videoFmtp });
} else if(options.videoCodec === "h264") {
offer.push({ type: "a", name: "fmtp", value: options.videoPt + " profile-level-id=42e01f;packetization-mode=1" });
}
}
if(options.data) {
offer.push({ type: "m", name: "application 9 DTLS/SCTP 5000" });
Expand Down Expand Up @@ -265,9 +295,9 @@ JANUSSDP.generateAnswer = function(offer, options) {
if(options.video && !options.videoCodec) {
if(JANUSSDP.findPayloadType(offer, "vp8") !== -1) {
options.videoCodec = "vp8";
} else if(JANUSSDP.findPayloadType(offer, "vp9") !== -1) {
} else if(JANUSSDP.findPayloadType(offer, "vp9", options.vp9Profile) !== -1) {
options.videoCodec = "vp9";
} else if(JANUSSDP.findPayloadType(offer, "h264") !== -1) {
} else if(JANUSSDP.findPayloadType(offer, "h264", options.h264Profile) !== -1) {
options.videoCodec = "h264";
}
}
Expand Down Expand Up @@ -306,8 +336,16 @@ JANUSSDP.generateAnswer = function(offer, options) {
} else if(a.name.indexOf("video") !== -1) {
medium = "video";
video++;
if(videoPt < 0)
videoPt = JANUSSDP.findPayloadType(offer, options.videoCodec);
if(videoPt < 0) {
if(options.videoCodec === "vp9") {
videoPt = JANUSSDP.findPayloadType(offer, options.videoCodec, options.vp9Profile);
} else if(options.videoCodec == "h264") {
videoPt = JANUSSDP.findPayloadType(offer, options.videoCodec, options.h264Profile);
} else {
videoPt = JANUSSDP.findPayloadType(offer, options.videoCodec);
}
}

if(videoPt < 0)
video++;
if(video > 1) {
Expand Down
13 changes: 12 additions & 1 deletion plugins/janus_duktape.c
Original file line number Diff line number Diff line change
Expand Up @@ -1158,7 +1158,18 @@ static duk_ret_t janus_duktape_method_startrecording(duk_context *ctx) {
const char *filename = duk_get_string(ctx, i);
if(type == NULL)
continue;
janus_recorder *rc = janus_recorder_create(folder, codec, filename);
/* Check if the codec contains some fmtp stuff too */
const char *c = codec, *f = NULL;
gchar **parts = NULL;
if(strstr(codec, "/fmtp=") != NULL) {
parts = g_strsplit(codec, "/fmtp=", 2);
c = parts[0];
f = parts[1];
}
/* Create the recorder */
janus_recorder *rc = janus_recorder_create_full(folder, c, f, filename);
if(parts != NULL)
g_strfreev(parts);
if(rc == NULL) {
JANUS_LOG(LOG_ERR, "Error creating '%s' recorder...\n", type);
goto error;
Expand Down
37 changes: 31 additions & 6 deletions plugins/janus_echotest.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"audiocodec" : "<optional codec name; only used when creating a PeerConnection>",
"video" : true|false,
"videocodec" : "<optional codec name; only used when creating a PeerConnection>",
"videoprofile" : "<optional codec profile to force; only used when creating a PeerConnection, only valid for VP9 (0 or 2) and H.264 (e.g., 42e01f)>",
"bitrate" : <numeric bitrate value>,
"record" : true|false,
"filename" : <base path/filename to use for the recording>,
Expand All @@ -51,7 +52,9 @@
* use the preferred audio codecs as set by the user; if for any reason you
* want to override what the browsers offered first and use a different
* codec instead (e.g., to try VP9 instead of VP8), you can use the
* \c audiocodec property for audio, and \c videocodec for video.
* \c audiocodec property for audio, and \c videocodec for video. For video
* codecs supporting a specific profile negotiation (VP9 and H.264), you can
* specify which profile you're interested in using the \c videoprofile property.
*
* All the other settings can be applied dynamically during the session:
* \c audio instructs the plugin to do or do not bounce back audio
Expand Down Expand Up @@ -210,6 +213,7 @@ typedef struct janus_echotest_session {
gboolean video_active;
janus_audiocodec acodec;/* Codec used for audio, if available */
janus_videocodec vcodec;/* Codec used for video, if available */
char *vfmtp;
uint32_t bitrate, peer_bitrate;
janus_rtp_switching_context context;
uint32_t ssrc[3]; /* Only needed in case VP8 (or H.264) simulcasting is involved */
Expand Down Expand Up @@ -238,6 +242,7 @@ static void janus_echotest_session_free(const janus_refcount *session_ref) {
/* Remove the reference to the core plugin session */
janus_refcount_decrease(&session->handle->ref);
/* This session can be destroyed, free all the resources */
g_free(session->vfmtp);
g_free(session);
}

Expand Down Expand Up @@ -791,6 +796,8 @@ static void janus_echotest_hangup_media_internal(janus_plugin_session *handle) {
session->video_active = TRUE;
session->acodec = JANUS_AUDIOCODEC_NONE;
session->vcodec = JANUS_VIDEOCODEC_NONE;
g_free(session->vfmtp);
session->vfmtp = NULL;
session->bitrate = 0;
session->peer_bitrate = 0;
int i=0;
Expand Down Expand Up @@ -932,6 +939,13 @@ static void *janus_echotest_handler(void *data) {
g_snprintf(error_cause, 512, "Invalid value (videocodec should be a string)");
goto error;
}
json_t *videoprofile = json_object_get(root, "videoprofile");
if(videoprofile && !json_is_string(videoprofile)) {
JANUS_LOG(LOG_ERR, "Invalid element (videoprofile should be a string)\n");
error_code = JANUS_ECHOTEST_ERROR_INVALID_ELEMENT;
g_snprintf(error_cause, 512, "Invalid value (videoprofile should be a string)");
goto error;
}
/* Enforce request */
if(audio) {
session->audio_active = json_is_true(audio);
Expand Down Expand Up @@ -1002,10 +1016,10 @@ static void *janus_echotest_handler(void *data) {
session->has_data = (strstr(msg_sdp, "DTLS/SCTP") != NULL);
}

if(!audio && !video && !bitrate && !substream && !temporal && !fallback && !record && !msg_sdp) {
JANUS_LOG(LOG_ERR, "No supported attributes (audio, video, bitrate, substream, temporal, fallback, record, jsep) found\n");
if(!audio && !video && !videocodec && !videoprofile && !bitrate && !substream && !temporal && !fallback && !record && !msg_sdp) {
JANUS_LOG(LOG_ERR, "No supported attributes (audio, video, videocodec, videoprofile, bitrate, substream, temporal, fallback, record, jsep) found\n");
error_code = JANUS_ECHOTEST_ERROR_INVALID_ELEMENT;
g_snprintf(error_cause, 512, "Message error: no supported attributes (audio, video, bitrate, simulcast, temporal, fallback, record, jsep) found");
g_snprintf(error_cause, 512, "Message error: no supported attributes (audio, video, videocodec, videoprofile, bitrate, simulcast, temporal, fallback, record, jsep) found");
goto error;
}

Expand Down Expand Up @@ -1055,6 +1069,8 @@ static void *janus_echotest_handler(void *data) {
JANUS_SDP_OA_AUDIO_CODEC, json_string_value(audiocodec),
JANUS_SDP_OA_AUDIO_FMTP, opus_fec ? "useinbandfec=1" : NULL,
JANUS_SDP_OA_VIDEO_CODEC, json_string_value(videocodec),
JANUS_SDP_OA_VP9_PROFILE, json_string_value(videoprofile),
JANUS_SDP_OA_H264_PROFILE, json_string_value(videoprofile),
JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_MID,
JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_RID,
JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_REPAIRED_RID,
Expand Down Expand Up @@ -1088,6 +1104,13 @@ static void *janus_echotest_handler(void *data) {
session->rid[0] = NULL;
}
}
g_free(session->vfmtp);
session->vfmtp = NULL;
if(session->has_video) {
const char *vfmtp = janus_sdp_get_fmtp(answer, janus_sdp_get_codec_pt(answer, vcodec));
if(vfmtp != NULL)
session->vfmtp = g_strdup(vfmtp);
}
/* Done */
char *sdp = janus_sdp_write(answer);
janus_sdp_destroy(offer);
Expand Down Expand Up @@ -1142,15 +1165,17 @@ static void *janus_echotest_handler(void *data) {
if(recording_base) {
/* Use the filename and path we have been provided */
g_snprintf(filename, 255, "%s-video", recording_base);
session->vrc = janus_recorder_create(NULL, janus_videocodec_name(session->vcodec), filename);
session->vrc = janus_recorder_create_full(NULL,
janus_videocodec_name(session->vcodec), session->vfmtp, filename);
if(session->vrc == NULL) {
/* FIXME We should notify the fact the recorder could not be created */
JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this EchoTest user!\n");
}
} else {
/* Build a filename */
g_snprintf(filename, 255, "echotest-%p-%"SCNi64"-video", session, now);
session->vrc = janus_recorder_create(NULL, janus_videocodec_name(session->vcodec), filename);
session->vrc = janus_recorder_create_full(NULL,
janus_videocodec_name(session->vcodec), session->vfmtp, filename);
if(session->vrc == NULL) {
/* FIXME We should notify the fact the recorder could not be created */
JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this EchoTest user!\n");
Expand Down
13 changes: 12 additions & 1 deletion plugins/janus_lua.c
Original file line number Diff line number Diff line change
Expand Up @@ -1025,7 +1025,18 @@ static int janus_lua_method_startrecording(lua_State *s) {
const char *folder = lua_tostring(s, i);
i++; n--;
const char *filename = lua_tostring(s, i);
janus_recorder *rc = janus_recorder_create(folder, codec, filename);
/* Check if the codec contains some fmtp stuff too */
const char *c = codec, *f = NULL;
gchar **parts = NULL;
if(strstr(codec, "/fmtp=") != NULL) {
parts = g_strsplit(codec, "/fmtp=", 2);
c = parts[0];
f = parts[1];
}
/* Create the recorder */
janus_recorder *rc = janus_recorder_create_full(folder, c, f, filename);
if(parts != NULL)
g_strfreev(parts);
if(rc == NULL) {
JANUS_LOG(LOG_ERR, "Error creating '%s' recorder...\n", type);
goto error;
Expand Down
Loading

0 comments on commit fa12368

Please sign in to comment.