void janus_source_hangup_media(janus_plugin_session *handle) { JANUS_LOG(LOG_INFO, "No WebRTC media anymore\n"); if (g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) return; janus_source_session *session = (janus_source_session *)handle->plugin_handle; if (!session) { JANUS_LOG(LOG_ERR, "No session associated with this handle...\n"); return; } if (session->destroyed) return; if (g_atomic_int_add(&session->hangingup, 1)) return; /* Send an event to the browser and tell it's over */ json_t *event = json_object(); json_object_set_new(event, "source", json_string("event")); json_object_set_new(event, "result", json_string("done")); int ret = gateway->push_event(handle, &janus_source_plugin, NULL, event, NULL); JANUS_LOG(LOG_VERB, " >> Pushing event: %d (%s)\n", ret, janus_get_api_error(ret)); json_decref(event); /* Reset controls */ session->audio_active = TRUE; session->video_active = TRUE; session->bitrate = 0; }
void janus_streaming_setup_media(janus_plugin_session *handle) { JANUS_LOG(LOG_INFO, "WebRTC media is now available\n"); if(stopping || !initialized) return; janus_streaming_session *session = (janus_streaming_session *)handle->plugin_handle; if(!session) { JANUS_LOG(LOG_ERR, "No session associated with this handle...\n"); return; } if(session->destroy) return; /* TODO Only start streaming when we get this event */ session->started = TRUE; /* Prepare JSON event */ json_t *event = json_object(); json_object_set_new(event, "streaming", json_string("event")); json_t *result = json_object(); json_object_set_new(result, "status", json_string("started")); json_object_set(event, "result", result); char *event_text = json_dumps(event, JSON_INDENT(3)); json_decref(event); json_decref(result); JANUS_LOG(LOG_VERB, "Pushing event: %s\n", event_text); int ret = gateway->push_event(handle, &janus_streaming_plugin, NULL, event_text, NULL, NULL); JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret)); g_free(event_text); }
void janus_videocall_hangup_media(janus_plugin_session *handle) { JANUS_LOG(LOG_INFO, "No WebRTC media anymore\n"); if(stopping || !initialized) return; janus_videocall_session *session = (janus_videocall_session *)handle->plugin_handle; if(!session) { JANUS_LOG(LOG_ERR, "No session associated with this handle...\n"); return; } if(session->destroy) return; janus_mutex_lock(&sessions_mutex); if(session->peer) { /* Send event to our peer too */ json_t *call = json_object(); json_object_set_new(call, "videocall", json_string("event")); json_t *calling = json_object(); json_object_set_new(calling, "event", json_string("hangup")); json_object_set_new(calling, "username", json_string(session->username)); json_object_set_new(calling, "reason", json_string("Remote hangup")); json_object_set_new(call, "result", calling); char *call_text = json_dumps(call, JSON_INDENT(3) | JSON_PRESERVE_ORDER); json_decref(call); JANUS_LOG(LOG_VERB, "Pushing event to peer: %s\n", call_text); int ret = gateway->push_event(session->peer->handle, &janus_videocall_plugin, NULL, call_text, NULL, NULL); JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret)); g_free(call_text); } session->peer = NULL; /* Reset controls */ session->audio_active = TRUE; session->video_active = TRUE; session->bitrate = 0; janus_mutex_unlock(&sessions_mutex); }
void janus_echotest_hangup_media(janus_plugin_session *handle) { JANUS_LOG(LOG_INFO, "No WebRTC media anymore\n"); if(stopping || !initialized) return; janus_echotest_session *session = (janus_echotest_session *)handle->plugin_handle; if(!session) { JANUS_LOG(LOG_ERR, "No session associated with this handle...\n"); return; } if(session->destroyed) return; /* Send an event to the browser and tell it's over */ json_t *event = json_object(); json_object_set_new(event, "echotest", json_string("event")); json_object_set_new(event, "result", json_string("done")); char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER); json_decref(event); JANUS_LOG(LOG_VERB, "Pushing event: %s\n", event_text); int ret = gateway->push_event(handle, &janus_echotest_plugin, NULL, event_text, NULL, NULL); JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret)); g_free(event_text); /* Reset controls */ session->audio_active = TRUE; session->video_active = TRUE; session->bitrate = 0; }
void janus_source_send_id_error(janus_plugin_session *handle) { if (g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) return; janus_source_session *session = (janus_source_session *)handle->plugin_handle; if (!session) { JANUS_LOG(LOG_ERR, "No session associated with this handle...\n"); return; } if (session->destroyed) return; /* Send an event to the browser and tell it's over */ char *error_cause = g_malloc0(512); g_snprintf(error_cause, 512, "JSON error: URL ID %s already exist in the system.",session->id); json_t *event = json_object(); json_object_set_new(event, "source", json_string("event")); json_object_set_new(event, "error_code", json_integer(JANUS_SOURCE_ERROR_INVALID_URL_ID)); json_object_set_new(event, "error", json_string(error_cause)); char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER); json_decref(event); JANUS_LOG(LOG_VERB, "Pushing event: %s\n", event_text); int ret = gateway->push_event(handle, &janus_source_plugin, NULL, event, NULL); JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret)); g_free(event_text); }
void janus_videocall_hangup_media(janus_plugin_session *handle) { JANUS_LOG(LOG_INFO, "No WebRTC media anymore\n"); if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) return; janus_videocall_session *session = (janus_videocall_session *)handle->plugin_handle; if(!session) { JANUS_LOG(LOG_ERR, "No session associated with this handle...\n"); return; } if(session->destroyed) return; if(g_atomic_int_add(&session->hangingup, 1)) return; /* Get rid of the recorders, if available */ janus_mutex_lock(&session->rec_mutex); if(session->arc) { janus_recorder_close(session->arc); JANUS_LOG(LOG_INFO, "Closed audio recording %s\n", session->arc->filename ? session->arc->filename : "??"); janus_recorder_free(session->arc); } session->arc = NULL; if(session->vrc) { janus_recorder_close(session->vrc); JANUS_LOG(LOG_INFO, "Closed video recording %s\n", session->vrc->filename ? session->vrc->filename : "??"); janus_recorder_free(session->vrc); } session->vrc = NULL; janus_mutex_unlock(&session->rec_mutex); if(session->peer) { /* Send event to our peer too */ json_t *call = json_object(); json_object_set_new(call, "videocall", json_string("event")); json_t *calling = json_object(); json_object_set_new(calling, "event", json_string("hangup")); json_object_set_new(calling, "username", json_string(session->username)); json_object_set_new(calling, "reason", json_string("Remote WebRTC hangup")); json_object_set_new(call, "result", calling); gateway->close_pc(session->peer->handle); int ret = gateway->push_event(session->peer->handle, &janus_videocall_plugin, NULL, call, NULL); JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); json_decref(call); /* Also notify event handlers */ if(notify_events && gateway->events_is_enabled()) { json_t *info = json_object(); json_object_set_new(info, "event", json_string("hangup")); json_object_set_new(info, "reason", json_string("Remote WebRTC hangup")); gateway->notify_event(&janus_videocall_plugin, session->peer->handle, info); } } session->peer = NULL; /* Reset controls */ session->has_audio = FALSE; session->has_video = FALSE; session->audio_active = TRUE; session->video_active = TRUE; session->bitrate = 0; }
void janus_videocall_hangup_media(janus_plugin_session *handle) { JANUS_LOG(LOG_INFO, "No WebRTC media anymore\n"); if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) return; janus_videocall_session *session = (janus_videocall_session *)handle->plugin_handle; if(!session) { JANUS_LOG(LOG_ERR, "No session associated with this handle...\n"); return; } if(session->destroyed) return; if(g_atomic_int_add(&session->hangingup, 1)) return; /* Get rid of the recorders, if available */ if(session->arc) { janus_recorder_close(session->arc); JANUS_LOG(LOG_INFO, "Closed audio recording %s\n", session->arc->filename ? session->arc->filename : "??"); janus_recorder_free(session->arc); } session->arc = NULL; if(session->vrc) { janus_recorder_close(session->vrc); JANUS_LOG(LOG_INFO, "Closed video recording %s\n", session->vrc->filename ? session->vrc->filename : "??"); janus_recorder_free(session->vrc); } session->vrc = NULL; if(session->peer) { /* Send event to our peer too */ json_t *call = json_object(); json_object_set_new(call, "videocall", json_string("event")); json_t *calling = json_object(); json_object_set_new(calling, "event", json_string("hangup")); json_object_set_new(calling, "username", json_string(session->username)); json_object_set_new(calling, "reason", json_string("Remote hangup")); json_object_set_new(call, "result", calling); char *call_text = json_dumps(call, JSON_INDENT(3) | JSON_PRESERVE_ORDER); json_decref(call); JANUS_LOG(LOG_VERB, "Pushing event to peer: %s\n", call_text); int ret = gateway->push_event(session->peer->handle, &janus_videocall_plugin, NULL, call_text, NULL, NULL); JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret)); g_free(call_text); } session->peer = NULL; /* Reset controls */ session->has_audio = FALSE; session->has_video = FALSE; session->audio_active = TRUE; session->video_active = TRUE; session->bitrate = 0; }
void janus_serial_hangup_media(janus_plugin_session *handle) { JANUS_LOG(LOG_INFO, "No WebRTC media anymore\n"); if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) return; janus_serial_session *session = (janus_serial_session *)handle->plugin_handle; if(!session) { JANUS_LOG(LOG_ERR, "No session associated with this handle...\n"); return; } if(session->destroyed) return; if(g_atomic_int_add(&session->hangingup, 1)) return; /* Send an event to the browser and tell it's over */ json_t *event = json_object(); json_object_set_new(event, "serial", json_string("event")); json_object_set_new(event, "result", json_string("done")); char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER); json_decref(event); JANUS_LOG(LOG_VERB, "Pushing event: %s\n", event_text); int ret = gateway->push_event(handle, &janus_serial_plugin, NULL, event_text, NULL, NULL); JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret)); g_free(event_text); /* Get rid of the recorders, if available */ if(session->arc) { janus_recorder_close(session->arc); JANUS_LOG(LOG_INFO, "Closed audio recording %s\n", session->arc->filename ? session->arc->filename : "??"); janus_recorder_free(session->arc); } session->arc = NULL; if(session->vrc) { janus_recorder_close(session->vrc); JANUS_LOG(LOG_INFO, "Closed video recording %s\n", session->vrc->filename ? session->vrc->filename : "??"); janus_recorder_free(session->vrc); } session->vrc = NULL; /* Reset controls */ session->has_audio = FALSE; session->has_video = FALSE; session->audio_active = TRUE; session->video_active = TRUE; session->bitrate = 0; }
void ps_gstsink_hangup_media (ps_plugin_session * handle) { PS_LOG(LOG_INFO, "No WebRTC media anymore\n"); if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) return; ps_gstsink_session *session = (ps_gstsink_session *)handle->plugin_handle; if(!session) { PS_LOG(LOG_ERR, "No session associated with this handle...\n"); return; } if(session->destroyed) return; if(g_atomic_int_add(&session->hangingup, 1)) return; /* Send an event to the browser and tell it's over */ json_t *event = json_object(); json_object_set_new(event, "recordplay", json_string("event")); json_object_set_new(event, "result", json_string("done")); char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER); json_decref(event); PS_LOG(LOG_VERB, "Pushing event: %s\n", event_text); int ret = gateway->push_event(handle, &ps_gstsink_plugin, NULL, event_text, NULL, NULL); PS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret)); g_free(event_text); if (session->vpackets != NULL) g_async_queue_push(session->vpackets, &eos_vpacket); /* FIXME Simulate a "stop" coming from the browser, Could be a simulated EOS !! ps_gstsink_message *msg = g_malloc0(sizeof(ps_gstsink_message)); msg->handle = handle; msg->message = json_loads("{\"request\":\"stop\"}", 0, NULL); msg->transaction = NULL; msg->sdp_type = NULL; msg->sdp = NULL; g_async_queue_push(messages, msg);*/ }
/* Thread to handle incoming messages */ static void *janus_serial_handler(void *data) { JANUS_LOG(LOG_VERB, "Joining Serial handler thread\n"); janus_serial_message *msg = NULL; int error_code = 0; char *error_cause = calloc(512, sizeof(char)); if(error_cause == NULL) { JANUS_LOG(LOG_FATAL, "Memory error!\n"); return NULL; } json_t *root = NULL; while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) { if(!messages || (msg = g_async_queue_try_pop(messages)) == NULL) { usleep(50000); continue; } janus_serial_session *session = (janus_serial_session *)msg->handle->plugin_handle; if(!session) { JANUS_LOG(LOG_ERR, "No session associated with this handle...\n"); janus_serial_message_free(msg); continue; } if(session->destroyed) { janus_serial_message_free(msg); continue; } /* Handle request */ error_code = 0; root = NULL; JANUS_LOG(LOG_VERB, "Handling message: %s\n", msg->message); if(msg->message == NULL) { JANUS_LOG(LOG_ERR, "No message??\n"); error_code = JANUS_SERIAL_ERROR_NO_MESSAGE; g_snprintf(error_cause, 512, "%s", "No message??"); goto error; } json_error_t error; root = json_loads(msg->message, 0, &error); if(!root) { JANUS_LOG(LOG_ERR, "JSON error: on line %d: %s\n", error.line, error.text); error_code = JANUS_SERIAL_ERROR_INVALID_JSON; g_snprintf(error_cause, 512, "JSON error: on line %d: %s", error.line, error.text); goto error; } if(!json_is_object(root)) { JANUS_LOG(LOG_ERR, "JSON error: not an object\n"); error_code = JANUS_SERIAL_ERROR_INVALID_JSON; g_snprintf(error_cause, 512, "JSON error: not an object"); goto error; } /* Parse request */ json_t *audio = json_object_get(root, "audio"); if(audio && !json_is_boolean(audio)) { JANUS_LOG(LOG_ERR, "Invalid element (audio should be a boolean)\n"); error_code = JANUS_SERIAL_ERROR_INVALID_ELEMENT; g_snprintf(error_cause, 512, "Invalid value (audio should be a boolean)"); goto error; } json_t *video = json_object_get(root, "video"); if(video && !json_is_boolean(video)) { JANUS_LOG(LOG_ERR, "Invalid element (video should be a boolean)\n"); error_code = JANUS_SERIAL_ERROR_INVALID_ELEMENT; g_snprintf(error_cause, 512, "Invalid value (video should be a boolean)"); goto error; } json_t *bitrate = json_object_get(root, "bitrate"); if(bitrate && (!json_is_integer(bitrate) || json_integer_value(bitrate) < 0)) { JANUS_LOG(LOG_ERR, "Invalid element (bitrate should be a positive integer)\n"); error_code = JANUS_SERIAL_ERROR_INVALID_ELEMENT; g_snprintf(error_cause, 512, "Invalid value (bitrate should be a positive integer)"); goto error; } json_t *record = json_object_get(root, "record"); if(record && !json_is_boolean(record)) { JANUS_LOG(LOG_ERR, "Invalid element (record should be a boolean)\n"); error_code = JANUS_SERIAL_ERROR_INVALID_ELEMENT; g_snprintf(error_cause, 512, "Invalid value (record should be a boolean)"); goto error; } json_t *recfile = json_object_get(root, "filename"); if(recfile && !json_is_string(recfile)) { JANUS_LOG(LOG_ERR, "Invalid element (filename should be a string)\n"); error_code = JANUS_SERIAL_ERROR_INVALID_ELEMENT; g_snprintf(error_cause, 512, "Invalid value (filename should be a string)"); goto error; } /* Enforce request */ if(audio) { session->audio_active = json_is_true(audio); JANUS_LOG(LOG_VERB, "Setting audio property: %s\n", session->audio_active ? "true" : "false"); } if(video) { if(!session->video_active && json_is_true(video)) { /* Send a PLI */ JANUS_LOG(LOG_VERB, "Just (re-)enabled video, sending a PLI to recover it\n"); char buf[12]; memset(buf, 0, 12); janus_rtcp_pli((char *)&buf, 12); gateway->relay_rtcp(session->handle, 1, buf, 12); } session->video_active = json_is_true(video); JANUS_LOG(LOG_VERB, "Setting video property: %s\n", session->video_active ? "true" : "false"); } if(bitrate) { session->bitrate = json_integer_value(bitrate); JANUS_LOG(LOG_VERB, "Setting video bitrate: %"SCNu64"\n", session->bitrate); if(session->bitrate > 0) { /* FIXME Generate a new REMB (especially useful for Firefox, which doesn't send any we can cap later) */ char buf[24]; memset(buf, 0, 24); janus_rtcp_remb((char *)&buf, 24, session->bitrate); JANUS_LOG(LOG_VERB, "Sending REMB\n"); gateway->relay_rtcp(session->handle, 1, buf, 24); /* FIXME How should we handle a subsequent "no limit" bitrate? */ } } if(record) { if(msg->sdp) { session->has_audio = (strstr(msg->sdp, "m=audio") != NULL); session->has_video = (strstr(msg->sdp, "m=video") != NULL); } gboolean recording = json_is_true(record); const char *recording_base = json_string_value(recfile); JANUS_LOG(LOG_VERB, "Recording %s (base filename: %s)\n", recording ? "enabled" : "disabled", recording_base ? recording_base : "not provided"); if(!recording) { /* Not recording (anymore?) */ if(session->arc) { janus_recorder_close(session->arc); JANUS_LOG(LOG_INFO, "Closed audio recording %s\n", session->arc->filename ? session->arc->filename : "??"); janus_recorder_free(session->arc); } session->arc = NULL; if(session->vrc) { janus_recorder_close(session->vrc); JANUS_LOG(LOG_INFO, "Closed video recording %s\n", session->vrc->filename ? session->vrc->filename : "??"); janus_recorder_free(session->vrc); } session->vrc = NULL; } else { /* We've started recording, send a PLI and go on */ char filename[255]; gint64 now = janus_get_monotonic_time(); if(session->has_audio) { memset(filename, 0, 255); if(recording_base) { /* Use the filename and path we have been provided */ g_snprintf(filename, 255, "%s-audio", recording_base); session->arc = janus_recorder_create(NULL, 0, filename); if(session->arc == NULL) { /* FIXME We should notify the fact the recorder could not be created */ JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this EchoTest user!\n"); } } else { /* Build a filename */ g_snprintf(filename, 255, "serial-%p-%"SCNi64"-audio", session, now); session->arc = janus_recorder_create(NULL, 0, filename); if(session->arc == NULL) { /* FIXME We should notify the fact the recorder could not be created */ JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this EchoTest user!\n"); } } } if(session->has_video) { memset(filename, 0, 255); 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, 1, 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, "serial-%p-%"SCNi64"-video", session, now); session->vrc = janus_recorder_create(NULL, 1, 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"); } } /* Send a PLI */ JANUS_LOG(LOG_VERB, "Recording video, sending a PLI to kickstart it\n"); char buf[12]; memset(buf, 0, 12); janus_rtcp_pli((char *)&buf, 12); gateway->relay_rtcp(session->handle, 1, buf, 12); } } } /* Any SDP to handle? */ if(msg->sdp) { JANUS_LOG(LOG_VERB, "This is involving a negotiation (%s) as well:\n%s\n", msg->sdp_type, msg->sdp); session->has_audio = (strstr(msg->sdp, "m=audio") != NULL); session->has_video = (strstr(msg->sdp, "m=video") != NULL); } /* if(!audio && !video && !bitrate && !record && !msg->sdp) { JANUS_LOG(LOG_ERR, "No supported attributes (audio, video, bitrate, record, jsep) found\n"); error_code = JANUS_SERIAL_ERROR_INVALID_ELEMENT; g_snprintf(error_cause, 512, "Message error: no supported attributes (audio, video, bitrate, record, jsep) found"); goto error; } */ json_decref(root); /* Prepare JSON event */ json_t *event = json_object(); json_object_set_new(event, "serial", json_string("event")); json_object_set_new(event, "result", json_string("ok")); char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER); json_decref(event); JANUS_LOG(LOG_VERB, "Pushing event: %s\n", event_text); if(!msg->sdp) { int ret = gateway->push_event(msg->handle, &janus_serial_plugin, msg->transaction, event_text, NULL, NULL); JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret)); } else { /* Forward the same offer to the gateway, to start the echo test */ const char *type = NULL; if(!strcasecmp(msg->sdp_type, "offer")) type = "answer"; if(!strcasecmp(msg->sdp_type, "answer")) type = "offer"; /* Any media direction that needs to be fixed? */ char *sdp = g_strdup(msg->sdp); if(strstr(sdp, "a=recvonly")) { /* Turn recvonly to inactive, as we simply bounce media back */ sdp = janus_string_replace(sdp, "a=recvonly", "a=inactive"); } else if(strstr(sdp, "a=sendonly")) { /* Turn sendonly to recvonly */ sdp = janus_string_replace(sdp, "a=sendonly", "a=recvonly"); /* FIXME We should also actually not echo this media back, though... */ } /* Make also sure we get rid of ULPfec, red, etc. */ if(strstr(sdp, "ulpfec")) { sdp = janus_string_replace(sdp, "100 116 117 96", "100"); sdp = janus_string_replace(sdp, "a=rtpmap:116 red/90000\r\n", ""); sdp = janus_string_replace(sdp, "a=rtpmap:117 ulpfec/90000\r\n", ""); sdp = janus_string_replace(sdp, "a=rtpmap:96 rtx/90000\r\n", ""); sdp = janus_string_replace(sdp, "a=fmtp:96 apt=100\r\n", ""); } /* How long will the gateway take to push the event? */ gint64 start = janus_get_monotonic_time(); int res = gateway->push_event(msg->handle, &janus_serial_plugin, msg->transaction, event_text, type, sdp); JANUS_LOG(LOG_VERB, " >> Pushing event: %d (took %"SCNu64" us)\n", res, janus_get_monotonic_time()-start); g_free(sdp); } g_free(event_text); janus_serial_message_free(msg); continue; error: { if(root != NULL) json_decref(root); /* Prepare JSON error event */ json_t *event = json_object(); json_object_set_new(event, "serial", json_string("event")); json_object_set_new(event, "error_code", json_integer(error_code)); json_object_set_new(event, "error", json_string(error_cause)); char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER); json_decref(event); JANUS_LOG(LOG_VERB, "Pushing event: %s\n", event_text); int ret = gateway->push_event(msg->handle, &janus_serial_plugin, msg->transaction, event_text, NULL, NULL); JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret)); g_free(event_text); janus_serial_message_free(msg); } } g_free(error_cause); JANUS_LOG(LOG_VERB, "Leaving Serial handler thread\n"); return NULL; }
/* Thread to handle incoming messages */ static void *janus_streaming_handler(void *data) { JANUS_LOG(LOG_VERB, "Joining thread\n"); janus_streaming_message *msg = NULL; int error_code = 0; char *error_cause = calloc(1024, sizeof(char)); if(error_cause == NULL) { JANUS_LOG(LOG_FATAL, "Memory error!\n"); return NULL; } while(initialized && !stopping) { if(!messages || (msg = g_queue_pop_head(messages)) == NULL) { usleep(50000); continue; } janus_streaming_session *session = (janus_streaming_session *)msg->handle->plugin_handle; if(!session) { JANUS_LOG(LOG_ERR, "No session associated with this handle...\n"); janus_streaming_message_free(msg); continue; } if(session->destroy) { janus_streaming_message_free(msg); continue; } /* Handle request */ error_code = 0; JANUS_LOG(LOG_VERB, "Handling message: %s\n", msg->message); if(msg->message == NULL) { JANUS_LOG(LOG_ERR, "No message??\n"); error_code = JANUS_STREAMING_ERROR_NO_MESSAGE; sprintf(error_cause, "%s", "No message??"); goto error; } json_error_t error; json_t *root = json_loads(msg->message, 0, &error); if(!root) { JANUS_LOG(LOG_ERR, "JSON error: on line %d: %s\n", error.line, error.text); error_code = JANUS_STREAMING_ERROR_INVALID_JSON; sprintf(error_cause, "JSON error: on line %d: %s", error.line, error.text); goto error; } if(!json_is_object(root)) { JANUS_LOG(LOG_ERR, "JSON error: not an object\n"); error_code = JANUS_STREAMING_ERROR_INVALID_JSON; sprintf(error_cause, "JSON error: not an object"); goto error; } json_t *request = json_object_get(root, "request"); if(!request) { JANUS_LOG(LOG_ERR, "Missing element (request)\n"); error_code = JANUS_STREAMING_ERROR_MISSING_ELEMENT; sprintf(error_cause, "Missing element (request)"); goto error; } if(!json_is_string(request)) { JANUS_LOG(LOG_ERR, "Invalid element (request should be a string)\n"); error_code = JANUS_STREAMING_ERROR_INVALID_ELEMENT; sprintf(error_cause, "Invalid element (request should be a string)"); goto error; } const char *request_text = json_string_value(request); json_t *result = NULL; char *sdp_type = NULL, *sdp = NULL; if(!strcasecmp(request_text, "list")) { result = json_object(); json_t *list = json_array(); JANUS_LOG(LOG_VERB, "Request for the list of mountpoints\n"); /* Return a list of all available mountpoints */ GList *mountpoints_list = g_hash_table_get_values(mountpoints); GList *m = mountpoints_list; while(m) { janus_streaming_mountpoint *mp = (janus_streaming_mountpoint *)m->data; json_t *ml = json_object(); json_object_set_new(ml, "id", json_integer(mp->id)); json_object_set_new(ml, "description", json_string(mp->description)); json_object_set_new(ml, "type", json_string(mp->streaming_type == janus_streaming_type_live ? "live" : "on demand")); json_array_append_new(list, ml); m = m->next; } json_object_set_new(result, "list", list); g_list_free(mountpoints_list); } else if(!strcasecmp(request_text, "watch")) { json_t *id = json_object_get(root, "id"); if(id && !json_is_integer(id)) { JANUS_LOG(LOG_ERR, "Invalid element (id should be an integer)\n"); error_code = JANUS_STREAMING_ERROR_INVALID_ELEMENT; sprintf(error_cause, "Invalid element (id should be an integer)"); goto error; } gint64 id_value = json_integer_value(id); janus_streaming_mountpoint *mp = g_hash_table_lookup(mountpoints, GINT_TO_POINTER(id_value)); if(mp == NULL) { JANUS_LOG(LOG_VERB, "No such mountpoint/stream %"SCNu64"\n", id_value); error_code = JANUS_STREAMING_ERROR_NO_SUCH_MOUNTPOINT; sprintf(error_cause, "No such mountpoint/stream %"SCNu64"", id_value); goto error; } JANUS_LOG(LOG_VERB, "Request to watch mountpoint/stream %"SCNu64"\n", id_value); session->stopping = FALSE; session->mountpoint = mp; if(mp->streaming_type == janus_streaming_type_on_demand) { g_thread_new(session->mountpoint->name, &janus_streaming_ondemand_thread, session); } /* TODO Check if user is already watching a stream, if the video is active, etc. */ mp->listeners = g_list_append(mp->listeners, session); sdp_type = "offer"; /* We're always going to do the offer ourselves, never answer */ char sdptemp[1024]; memset(sdptemp, 0, 1024); gchar buffer[100]; memset(buffer, 0, 100); gint64 sessid = janus_get_monotonic_time(); gint64 version = sessid; /* FIXME This needs to be increased when it changes, so time should be ok */ g_sprintf(buffer, "v=0\r\no=%s %"SCNu64" %"SCNu64" IN IP4 127.0.0.1\r\n", "-", sessid, version); g_strlcat(sdptemp, buffer, 1024); g_strlcat(sdptemp, "s=Streaming Test\r\nt=0 0\r\n", 1024); if(mp->codecs.audio_pt >= 0) { /* Add audio line */ g_sprintf(buffer, "m=audio 1 RTP/SAVPF %d\r\n" "c=IN IP4 1.1.1.1\r\n", mp->codecs.audio_pt); g_strlcat(sdptemp, buffer, 1024); if(mp->codecs.audio_rtpmap) { g_sprintf(buffer, "a=rtpmap:%d %s\r\n", mp->codecs.audio_pt, mp->codecs.audio_rtpmap); g_strlcat(sdptemp, buffer, 1024); } g_strlcat(sdptemp, "a=sendonly\r\n", 1024); } if(mp->codecs.video_pt >= 0) { /* Add video line */ g_sprintf(buffer, "m=video 1 RTP/SAVPF %d\r\n" "c=IN IP4 1.1.1.1\r\n", mp->codecs.video_pt); g_strlcat(sdptemp, buffer, 1024); if(mp->codecs.video_rtpmap) { g_sprintf(buffer, "a=rtpmap:%d %s\r\n", mp->codecs.video_pt, mp->codecs.video_rtpmap); g_strlcat(sdptemp, buffer, 1024); } g_strlcat(sdptemp, "a=sendonly\r\n", 1024); } sdp = g_strdup(sdptemp); JANUS_LOG(LOG_VERB, "Going to offer this SDP:\n%s\n", sdp); result = json_object(); json_object_set_new(result, "status", json_string("preparing")); } else if(!strcasecmp(request_text, "start")) { JANUS_LOG(LOG_VERB, "Starting the streaming\n"); result = json_object(); /* We wait for the setup_media event to start: on the other hand, it may have already arrived */ json_object_set_new(result, "status", json_string(session->started ? "started" : "starting")); } else if(!strcasecmp(request_text, "pause")) { JANUS_LOG(LOG_VERB, "Pausing the streaming\n"); session->started = FALSE; result = json_object(); json_object_set_new(result, "status", json_string("pausing")); } else if(!strcasecmp(request_text, "stop")) { JANUS_LOG(LOG_VERB, "Stopping the streaming\n"); session->stopping = TRUE; session->started = FALSE; result = json_object(); json_object_set_new(result, "status", json_string("stopping")); if(session->mountpoint) { JANUS_LOG(LOG_VERB, " -- Removing the session from the mountpoint listeners\n"); if(g_list_find(session->mountpoint->listeners, session) != NULL) { JANUS_LOG(LOG_VERB, " -- -- Found!\n"); } session->mountpoint->listeners = g_list_remove_all(session->mountpoint->listeners, session); } session->mountpoint = NULL; } else { JANUS_LOG(LOG_VERB, "Unknown request '%s'\n", request_text); error_code = JANUS_STREAMING_ERROR_INVALID_REQUEST; sprintf(error_cause, "Unknown request '%s'", request_text); goto error; } /* Any SDP to handle? */ if(msg->sdp) { JANUS_LOG(LOG_VERB, "This is involving a negotiation (%s) as well (but we really don't care):\n%s\n", msg->sdp_type, msg->sdp); } json_decref(root); /* Prepare JSON event */ json_t *event = json_object(); json_object_set_new(event, "streaming", json_string("event")); if(result != NULL) json_object_set(event, "result", result); char *event_text = json_dumps(event, JSON_INDENT(3)); json_decref(event); if(result != NULL) json_decref(result); JANUS_LOG(LOG_VERB, "Pushing event: %s\n", event_text); int ret = gateway->push_event(msg->handle, &janus_streaming_plugin, msg->transaction, event_text, sdp_type, sdp); JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret)); g_free(event_text); if(sdp) g_free(sdp); janus_streaming_message_free(msg); continue; error: { if(root != NULL) json_decref(root); /* Prepare JSON error event */ json_t *event = json_object(); json_object_set_new(event, "streaming", json_string("event")); json_object_set_new(event, "error_code", json_integer(error_code)); json_object_set_new(event, "error", json_string(error_cause)); char *event_text = json_dumps(event, JSON_INDENT(3)); json_decref(event); JANUS_LOG(LOG_VERB, "Pushing event: %s\n", event_text); int ret = gateway->push_event(msg->handle, &janus_streaming_plugin, msg->transaction, event_text, NULL, NULL); JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret)); g_free(event_text); janus_streaming_message_free(msg); } } g_free(error_cause); JANUS_LOG(LOG_VERB, "Leaving thread\n"); return NULL; }
/* Thread to handle incoming messages */ static void *janus_videocall_handler(void *data) { JANUS_LOG(LOG_VERB, "Joining thread\n"); janus_videocall_message *msg = NULL; int error_code = 0; char *error_cause = calloc(512, sizeof(char)); /* FIXME 512 should be enough, but anyway... */ if(error_cause == NULL) { JANUS_LOG(LOG_FATAL, "Memory error!\n"); return NULL; } json_t *root = NULL; while(initialized && !stopping) { if(!messages || (msg = g_async_queue_try_pop(messages)) == NULL) { usleep(50000); continue; } janus_videocall_session *session = (janus_videocall_session *)msg->handle->plugin_handle; if(!session) { JANUS_LOG(LOG_ERR, "No session associated with this handle...\n"); janus_videocall_message_free(msg); continue; } if(session->destroy) { janus_videocall_message_free(msg); continue; } /* Handle request */ error_code = 0; root = NULL; JANUS_LOG(LOG_VERB, "Handling message: %s\n", msg->message); if(msg->message == NULL) { JANUS_LOG(LOG_ERR, "No message??\n"); error_code = JANUS_VIDEOCALL_ERROR_NO_MESSAGE; sprintf(error_cause, "%s", "No message??"); goto error; } json_error_t error; root = json_loads(msg->message, 0, &error); if(!root) { JANUS_LOG(LOG_ERR, "JSON error: on line %d: %s\n", error.line, error.text); error_code = JANUS_VIDEOCALL_ERROR_INVALID_JSON; sprintf(error_cause, "JSON error: on line %d: %s", error.line, error.text); goto error; } if(!json_is_object(root)) { JANUS_LOG(LOG_ERR, "JSON error: not an object\n"); error_code = JANUS_VIDEOCALL_ERROR_INVALID_JSON; sprintf(error_cause, "JSON error: not an object"); goto error; } json_t *request = json_object_get(root, "request"); if(!request) { JANUS_LOG(LOG_ERR, "Missing element (request)\n"); error_code = JANUS_VIDEOCALL_ERROR_MISSING_ELEMENT; sprintf(error_cause, "Missing element (request)"); goto error; } if(!json_is_string(request)) { JANUS_LOG(LOG_ERR, "Invalid element (request should be a string)\n"); error_code = JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT; sprintf(error_cause, "Invalid element (request should be a string)"); goto error; } const char *request_text = json_string_value(request); json_t *result = NULL; char *sdp_type = NULL, *sdp = NULL; if(!strcasecmp(request_text, "list")) { result = json_object(); json_t *list = json_array(); JANUS_LOG(LOG_VERB, "Request for the list of peers\n"); /* Return a list of all available mountpoints */ janus_mutex_lock(&sessions_mutex); GHashTableIter iter; gpointer value; g_hash_table_iter_init(&iter, sessions); while (g_hash_table_iter_next(&iter, NULL, &value)) { janus_videocall_session *user = value; if(user != NULL && user->username != NULL) json_array_append_new(list, json_string(user->username)); } json_object_set_new(result, "list", list); janus_mutex_unlock(&sessions_mutex); } else if(!strcasecmp(request_text, "register")) { /* Map this handle to a username */ if(session->username != NULL) { JANUS_LOG(LOG_ERR, "Already registered (%s)\n", session->username); error_code = JANUS_VIDEOCALL_ERROR_ALREADY_REGISTERED; sprintf(error_cause, "Already registered (%s)", session->username); goto error; } json_t *username = json_object_get(root, "username"); if(!username) { JANUS_LOG(LOG_ERR, "Missing element (username)\n"); error_code = JANUS_VIDEOCALL_ERROR_MISSING_ELEMENT; sprintf(error_cause, "Missing element (username)"); goto error; } if(!json_is_string(username)) { JANUS_LOG(LOG_ERR, "Invalid element (username should be a string)\n"); error_code = JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT; sprintf(error_cause, "Invalid element (username should be a string)"); goto error; } const char *username_text = json_string_value(username); janus_mutex_lock(&sessions_mutex); if(g_hash_table_lookup(sessions, username_text) != NULL) { janus_mutex_unlock(&sessions_mutex); JANUS_LOG(LOG_ERR, "Username '%s' already taken\n", username_text); error_code = JANUS_VIDEOCALL_ERROR_USERNAME_TAKEN; sprintf(error_cause, "Username '%s' already taken", username_text); goto error; } janus_mutex_unlock(&sessions_mutex); session->username = g_strdup(username_text); if(session->username == NULL) { JANUS_LOG(LOG_FATAL, "Memory error!\n"); error_code = JANUS_VIDEOCALL_ERROR_UNKNOWN_ERROR; sprintf(error_cause, "Memory error"); goto error; } janus_mutex_lock(&sessions_mutex); g_hash_table_insert(sessions, (gpointer)session->username, session); janus_mutex_unlock(&sessions_mutex); result = json_object(); json_object_set_new(result, "event", json_string("registered")); json_object_set_new(result, "username", json_string(username_text)); } else if(!strcasecmp(request_text, "call")) { /* Call another peer */ if(session->username == NULL) { JANUS_LOG(LOG_ERR, "Register a username first\n"); error_code = JANUS_VIDEOCALL_ERROR_REGISTER_FIRST; sprintf(error_cause, "Register a username first"); goto error; } if(session->peer != NULL) { JANUS_LOG(LOG_ERR, "Already in a call\n"); error_code = JANUS_VIDEOCALL_ERROR_ALREADY_IN_CALL; sprintf(error_cause, "Already in a call"); goto error; } json_t *username = json_object_get(root, "username"); if(!username) { JANUS_LOG(LOG_ERR, "Missing element (username)\n"); error_code = JANUS_VIDEOCALL_ERROR_MISSING_ELEMENT; sprintf(error_cause, "Missing element (username)"); goto error; } if(!json_is_string(username)) { JANUS_LOG(LOG_ERR, "Invalid element (username should be a string)\n"); error_code = JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT; sprintf(error_cause, "Invalid element (username should be a string)"); goto error; } const char *username_text = json_string_value(username); if(!strcmp(username_text, session->username)) { JANUS_LOG(LOG_ERR, "You can't call yourself... use the EchoTest for that\n"); error_code = JANUS_VIDEOCALL_ERROR_USE_ECHO_TEST; sprintf(error_cause, "You can't call yourself... use the EchoTest for that"); goto error; } janus_mutex_lock(&sessions_mutex); janus_videocall_session *peer = g_hash_table_lookup(sessions, username_text); if(peer == NULL || peer->destroy) { janus_mutex_unlock(&sessions_mutex); JANUS_LOG(LOG_ERR, "Username '%s' doesn't exist\n", username_text); error_code = JANUS_VIDEOCALL_ERROR_NO_SUCH_USERNAME; sprintf(error_cause, "Username '%s' doesn't exist", username_text); goto error; } if(peer->peer != NULL) { janus_mutex_unlock(&sessions_mutex); JANUS_LOG(LOG_VERB, "%s is busy\n", username_text); result = json_object(); json_object_set_new(result, "event", json_string("hangup")); json_object_set_new(result, "username", json_string(session->username)); json_object_set_new(result, "reason", json_string("User busy")); } else { janus_mutex_unlock(&sessions_mutex); /* Any SDP to handle? if not, something's wrong */ if(!msg->sdp) { JANUS_LOG(LOG_ERR, "Missing SDP\n"); error_code = JANUS_VIDEOCALL_ERROR_MISSING_SDP; sprintf(error_cause, "Missing SDP"); goto error; } janus_mutex_lock(&sessions_mutex); session->peer = peer; peer->peer = session; janus_mutex_unlock(&sessions_mutex); JANUS_LOG(LOG_VERB, "%s is calling %s\n", session->username, session->peer->username); JANUS_LOG(LOG_VERB, "This is involving a negotiation (%s) as well:\n%s\n", msg->sdp_type, msg->sdp); /* Send SDP to our peer */ json_t *call = json_object(); json_object_set_new(call, "videocall", json_string("event")); json_t *calling = json_object(); json_object_set_new(calling, "event", json_string("incomingcall")); json_object_set_new(calling, "username", json_string(session->username)); json_object_set_new(call, "result", calling); char *call_text = json_dumps(call, JSON_INDENT(3) | JSON_PRESERVE_ORDER); json_decref(call); JANUS_LOG(LOG_VERB, "Pushing event to peer: %s\n", call_text); int ret = gateway->push_event(peer->handle, &janus_videocall_plugin, NULL, call_text, msg->sdp_type, msg->sdp); JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret)); g_free(call_text); /* Send an ack back */ result = json_object(); json_object_set_new(result, "event", json_string("calling")); } } else if(!strcasecmp(request_text, "accept")) { /* Accept a call from another peer */ if(session->peer == NULL) { JANUS_LOG(LOG_ERR, "No incoming call to accept\n"); error_code = JANUS_VIDEOCALL_ERROR_NO_CALL; sprintf(error_cause, "No incoming call to accept"); goto error; } /* Any SDP to handle? if not, something's wrong */ if(!msg->sdp) { JANUS_LOG(LOG_ERR, "Missing SDP\n"); error_code = JANUS_VIDEOCALL_ERROR_MISSING_SDP; sprintf(error_cause, "Missing SDP"); goto error; } JANUS_LOG(LOG_VERB, "%s is accepting a call from %s\n", session->username, session->peer->username); JANUS_LOG(LOG_VERB, "This is involving a negotiation (%s) as well:\n%s\n", msg->sdp_type, msg->sdp); /* Send SDP to our peer */ json_t *call = json_object(); json_object_set_new(call, "videocall", json_string("event")); json_t *calling = json_object(); json_object_set_new(calling, "event", json_string("accepted")); json_object_set_new(calling, "username", json_string(session->username)); json_object_set_new(call, "result", calling); char *call_text = json_dumps(call, JSON_INDENT(3) | JSON_PRESERVE_ORDER); json_decref(call); JANUS_LOG(LOG_VERB, "Pushing event to peer: %s\n", call_text); int ret = gateway->push_event(session->peer->handle, &janus_videocall_plugin, NULL, call_text, msg->sdp_type, msg->sdp); JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret)); g_free(call_text); /* Send an ack back */ result = json_object(); json_object_set_new(result, "event", json_string("accepted")); } else if(!strcasecmp(request_text, "set")) { /* Update the local configuration (audio/video mute/unmute, or bitrate cap) */ json_t *audio = json_object_get(root, "audio"); if(audio && !json_is_boolean(audio)) { JANUS_LOG(LOG_ERR, "Invalid element (audio should be a boolean)\n"); error_code = JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT; sprintf(error_cause, "Invalid value (audio should be a boolean)"); goto error; } json_t *video = json_object_get(root, "video"); if(video && !json_is_boolean(video)) { JANUS_LOG(LOG_ERR, "Invalid element (video should be a boolean)\n"); error_code = JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT; sprintf(error_cause, "Invalid value (video should be a boolean)"); goto error; } json_t *bitrate = json_object_get(root, "bitrate"); if(bitrate && !json_is_integer(bitrate)) { JANUS_LOG(LOG_ERR, "Invalid element (bitrate should be an integer)\n"); error_code = JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT; sprintf(error_cause, "Invalid value (bitrate should be an integer)"); goto error; } if(audio) { session->audio_active = json_is_true(audio); JANUS_LOG(LOG_VERB, "Setting audio property: %s\n", session->audio_active ? "true" : "false"); } if(video) { session->video_active = json_is_true(video); JANUS_LOG(LOG_VERB, "Setting video property: %s\n", session->video_active ? "true" : "false"); } if(bitrate) { session->bitrate = json_integer_value(bitrate); JANUS_LOG(LOG_VERB, "Setting video bitrate: %"SCNu64"\n", session->bitrate); if(session->bitrate > 0) { /* FIXME Generate a new REMB (especially useful for Firefox, which doesn't send any we can cap later) */ char buf[24]; memset(buf, 0, 24); janus_rtcp_remb((char *)&buf, 24, session->bitrate); JANUS_LOG(LOG_VERB, "Sending REMB\n"); gateway->relay_rtcp(session->handle, 1, buf, 24); /* FIXME How should we handle a subsequent "no limit" bitrate? */ } } /* Send an ack back */ result = json_object(); json_object_set_new(result, "event", json_string("set")); } else if(!strcasecmp(request_text, "hangup")) { /* Hangup an ongoing call or reject an incoming one */ janus_mutex_lock(&sessions_mutex); janus_videocall_session *peer = session->peer; if(peer == NULL) { JANUS_LOG(LOG_WARN, "No call to hangup\n"); } else { JANUS_LOG(LOG_VERB, "%s is hanging up the call with %s\n", session->username, peer->username); session->peer = NULL; peer->peer = NULL; } janus_mutex_unlock(&sessions_mutex); /* Notify the success as an hangup message */ result = json_object(); json_object_set_new(result, "event", json_string("hangup")); json_object_set_new(result, "username", json_string(session->username)); json_object_set_new(result, "reason", json_string("We did the hangup")); if(peer != NULL) { /* Send event to our peer too */ json_t *call = json_object(); json_object_set_new(call, "videocall", json_string("event")); json_t *calling = json_object(); json_object_set_new(calling, "event", json_string("hangup")); json_object_set_new(calling, "username", json_string(session->username)); json_object_set_new(calling, "reason", json_string("Remote hangup")); json_object_set_new(call, "result", calling); char *call_text = json_dumps(call, JSON_INDENT(3) | JSON_PRESERVE_ORDER); json_decref(call); JANUS_LOG(LOG_VERB, "Pushing event to peer: %s\n", call_text); int ret = gateway->push_event(peer->handle, &janus_videocall_plugin, NULL, call_text, NULL, NULL); JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret)); g_free(call_text); } } else { JANUS_LOG(LOG_ERR, "Unknown request (%s)\n", request_text); error_code = JANUS_VIDEOCALL_ERROR_INVALID_REQUEST; sprintf(error_cause, "Unknown request (%s)", request_text); goto error; } json_decref(root); /* Prepare JSON event */ json_t *event = json_object(); json_object_set_new(event, "videocall", json_string("event")); if(result != NULL) json_object_set(event, "result", result); char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER); json_decref(event); if(result != NULL) json_decref(result); JANUS_LOG(LOG_VERB, "Pushing event: %s\n", event_text); int ret = gateway->push_event(msg->handle, &janus_videocall_plugin, msg->transaction, event_text, sdp_type, sdp); JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret)); g_free(event_text); if(sdp) g_free(sdp); janus_videocall_message_free(msg); continue; error: { if(root != NULL) json_decref(root); /* Prepare JSON error event */ json_t *event = json_object(); json_object_set_new(event, "videocall", json_string("event")); json_object_set_new(event, "error_code", json_integer(error_code)); json_object_set_new(event, "error", json_string(error_cause)); char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER); json_decref(event); JANUS_LOG(LOG_VERB, "Pushing event: %s\n", event_text); int ret = gateway->push_event(msg->handle, &janus_videocall_plugin, msg->transaction, event_text, NULL, NULL); JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret)); g_free(event_text); janus_videocall_message_free(msg); } } g_free(error_cause); JANUS_LOG(LOG_VERB, "Leaving thread\n"); return NULL; }
/* Thread to handle incoming messages */ static void *janus_videocall_handler(void *data) { JANUS_LOG(LOG_VERB, "Joining VideoCall handler thread\n"); janus_videocall_message *msg = NULL; int error_code = 0; char error_cause[512]; json_t *root = NULL; while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) { msg = g_async_queue_pop(messages); if(msg == NULL) continue; if(msg == &exit_message) break; if(msg->handle == NULL) { janus_videocall_message_free(msg); continue; } janus_videocall_session *session = (janus_videocall_session *)msg->handle->plugin_handle; if(!session) { JANUS_LOG(LOG_ERR, "No session associated with this handle...\n"); janus_videocall_message_free(msg); continue; } if(session->destroyed) { janus_videocall_message_free(msg); continue; } /* Handle request */ error_code = 0; root = NULL; JANUS_LOG(LOG_VERB, "Handling message: %s\n", msg->message); if(msg->message == NULL) { JANUS_LOG(LOG_ERR, "No message??\n"); error_code = JANUS_VIDEOCALL_ERROR_NO_MESSAGE; g_snprintf(error_cause, 512, "%s", "No message??"); goto error; } json_error_t error; root = json_loads(msg->message, 0, &error); if(!root) { JANUS_LOG(LOG_ERR, "JSON error: on line %d: %s\n", error.line, error.text); error_code = JANUS_VIDEOCALL_ERROR_INVALID_JSON; g_snprintf(error_cause, 512, "JSON error: on line %d: %s", error.line, error.text); goto error; } if(!json_is_object(root)) { JANUS_LOG(LOG_ERR, "JSON error: not an object\n"); error_code = JANUS_VIDEOCALL_ERROR_INVALID_JSON; g_snprintf(error_cause, 512, "JSON error: not an object"); goto error; } JANUS_VALIDATE_JSON_OBJECT(root, request_parameters, error_code, error_cause, TRUE, JANUS_VIDEOCALL_ERROR_MISSING_ELEMENT, JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT); if(error_code != 0) goto error; json_t *request = json_object_get(root, "request"); const char *request_text = json_string_value(request); json_t *result = NULL; char *sdp_type = NULL, *sdp = NULL; if(!strcasecmp(request_text, "list")) { result = json_object(); json_t *list = json_array(); JANUS_LOG(LOG_VERB, "Request for the list of peers\n"); /* Return a list of all available mountpoints */ janus_mutex_lock(&sessions_mutex); GHashTableIter iter; gpointer value; g_hash_table_iter_init(&iter, sessions); while (g_hash_table_iter_next(&iter, NULL, &value)) { janus_videocall_session *user = value; if(user != NULL && user->username != NULL) json_array_append_new(list, json_string(user->username)); } json_object_set_new(result, "list", list); janus_mutex_unlock(&sessions_mutex); } else if(!strcasecmp(request_text, "register")) { /* Map this handle to a username */ if(session->username != NULL) { JANUS_LOG(LOG_ERR, "Already registered (%s)\n", session->username); error_code = JANUS_VIDEOCALL_ERROR_ALREADY_REGISTERED; g_snprintf(error_cause, 512, "Already registered (%s)", session->username); goto error; } JANUS_VALIDATE_JSON_OBJECT(root, username_parameters, error_code, error_cause, TRUE, JANUS_VIDEOCALL_ERROR_MISSING_ELEMENT, JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT); if(error_code != 0) goto error; json_t *username = json_object_get(root, "username"); const char *username_text = json_string_value(username); janus_mutex_lock(&sessions_mutex); if(g_hash_table_lookup(sessions, username_text) != NULL) { janus_mutex_unlock(&sessions_mutex); JANUS_LOG(LOG_ERR, "Username '%s' already taken\n", username_text); error_code = JANUS_VIDEOCALL_ERROR_USERNAME_TAKEN; g_snprintf(error_cause, 512, "Username '%s' already taken", username_text); goto error; } janus_mutex_unlock(&sessions_mutex); session->username = g_strdup(username_text); if(session->username == NULL) { JANUS_LOG(LOG_FATAL, "Memory error!\n"); error_code = JANUS_VIDEOCALL_ERROR_UNKNOWN_ERROR; g_snprintf(error_cause, 512, "Memory error"); goto error; } janus_mutex_lock(&sessions_mutex); g_hash_table_insert(sessions, (gpointer)session->username, session); janus_mutex_unlock(&sessions_mutex); result = json_object(); json_object_set_new(result, "event", json_string("registered")); json_object_set_new(result, "username", json_string(username_text)); } else if(!strcasecmp(request_text, "call")) { /* Call another peer */ if(session->username == NULL) { JANUS_LOG(LOG_ERR, "Register a username first\n"); error_code = JANUS_VIDEOCALL_ERROR_REGISTER_FIRST; g_snprintf(error_cause, 512, "Register a username first"); goto error; } if(session->peer != NULL) { JANUS_LOG(LOG_ERR, "Already in a call\n"); error_code = JANUS_VIDEOCALL_ERROR_ALREADY_IN_CALL; g_snprintf(error_cause, 512, "Already in a call"); goto error; } JANUS_VALIDATE_JSON_OBJECT(root, username_parameters, error_code, error_cause, TRUE, JANUS_VIDEOCALL_ERROR_MISSING_ELEMENT, JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT); if(error_code != 0) goto error; json_t *username = json_object_get(root, "username"); const char *username_text = json_string_value(username); if(!strcmp(username_text, session->username)) { JANUS_LOG(LOG_ERR, "You can't call yourself... use the EchoTest for that\n"); error_code = JANUS_VIDEOCALL_ERROR_USE_ECHO_TEST; g_snprintf(error_cause, 512, "You can't call yourself... use the EchoTest for that"); goto error; } janus_mutex_lock(&sessions_mutex); janus_videocall_session *peer = g_hash_table_lookup(sessions, username_text); if(peer == NULL || peer->destroyed) { janus_mutex_unlock(&sessions_mutex); JANUS_LOG(LOG_ERR, "Username '%s' doesn't exist\n", username_text); error_code = JANUS_VIDEOCALL_ERROR_NO_SUCH_USERNAME; g_snprintf(error_cause, 512, "Username '%s' doesn't exist", username_text); goto error; } if(peer->peer != NULL) { janus_mutex_unlock(&sessions_mutex); JANUS_LOG(LOG_VERB, "%s is busy\n", username_text); result = json_object(); json_object_set_new(result, "event", json_string("hangup")); json_object_set_new(result, "username", json_string(session->username)); json_object_set_new(result, "reason", json_string("User busy")); } else { janus_mutex_unlock(&sessions_mutex); /* Any SDP to handle? if not, something's wrong */ if(!msg->sdp) { JANUS_LOG(LOG_ERR, "Missing SDP\n"); error_code = JANUS_VIDEOCALL_ERROR_MISSING_SDP; g_snprintf(error_cause, 512, "Missing SDP"); goto error; } janus_mutex_lock(&sessions_mutex); session->peer = peer; peer->peer = session; session->has_audio = (strstr(msg->sdp, "m=audio") != NULL); session->has_video = (strstr(msg->sdp, "m=video") != NULL); janus_mutex_unlock(&sessions_mutex); JANUS_LOG(LOG_VERB, "%s is calling %s\n", session->username, session->peer->username); JANUS_LOG(LOG_VERB, "This is involving a negotiation (%s) as well:\n%s\n", msg->sdp_type, msg->sdp); /* Send SDP to our peer */ json_t *call = json_object(); json_object_set_new(call, "videocall", json_string("event")); json_t *calling = json_object(); json_object_set_new(calling, "event", json_string("incomingcall")); json_object_set_new(calling, "username", json_string(session->username)); json_object_set_new(call, "result", calling); char *call_text = json_dumps(call, JSON_INDENT(3) | JSON_PRESERVE_ORDER); json_decref(call); /* Make also sure we get rid of ULPfec, red, etc. */ char *sdp = g_strdup(msg->sdp); if(strstr(sdp, "ulpfec")) { /* FIXME This really needs some better code */ sdp = janus_string_replace(sdp, "a=rtpmap:116 red/90000\r\n", ""); sdp = janus_string_replace(sdp, "a=rtpmap:117 ulpfec/90000\r\n", ""); sdp = janus_string_replace(sdp, "a=rtpmap:96 rtx/90000\r\n", ""); sdp = janus_string_replace(sdp, "a=fmtp:96 apt=100\r\n", ""); sdp = janus_string_replace(sdp, "a=rtpmap:97 rtx/90000\r\n", ""); sdp = janus_string_replace(sdp, "a=fmtp:97 apt=101\r\n", ""); sdp = janus_string_replace(sdp, "a=rtpmap:98 rtx/90000\r\n", ""); sdp = janus_string_replace(sdp, "a=fmtp:98 apt=116\r\n", ""); sdp = janus_string_replace(sdp, " 116", ""); sdp = janus_string_replace(sdp, " 117", ""); sdp = janus_string_replace(sdp, " 96", ""); sdp = janus_string_replace(sdp, " 97", ""); sdp = janus_string_replace(sdp, " 98", ""); } g_atomic_int_set(&session->hangingup, 0); JANUS_LOG(LOG_VERB, "Pushing event to peer: %s\n", call_text); int ret = gateway->push_event(peer->handle, &janus_videocall_plugin, NULL, call_text, msg->sdp_type, sdp); g_free(sdp); JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret)); g_free(call_text); /* Send an ack back */ result = json_object(); json_object_set_new(result, "event", json_string("calling")); } } else if(!strcasecmp(request_text, "accept")) { /* Accept a call from another peer */ if(session->peer == NULL) { JANUS_LOG(LOG_ERR, "No incoming call to accept\n"); error_code = JANUS_VIDEOCALL_ERROR_NO_CALL; g_snprintf(error_cause, 512, "No incoming call to accept"); goto error; } /* Any SDP to handle? if not, something's wrong */ if(!msg->sdp) { JANUS_LOG(LOG_ERR, "Missing SDP\n"); error_code = JANUS_VIDEOCALL_ERROR_MISSING_SDP; g_snprintf(error_cause, 512, "Missing SDP"); goto error; } JANUS_LOG(LOG_VERB, "%s is accepting a call from %s\n", session->username, session->peer->username); JANUS_LOG(LOG_VERB, "This is involving a negotiation (%s) as well:\n%s\n", msg->sdp_type, msg->sdp); session->has_audio = (strstr(msg->sdp, "m=audio") != NULL); session->has_video = (strstr(msg->sdp, "m=video") != NULL); /* Send SDP to our peer */ json_t *call = json_object(); json_object_set_new(call, "videocall", json_string("event")); json_t *calling = json_object(); json_object_set_new(calling, "event", json_string("accepted")); json_object_set_new(calling, "username", json_string(session->username)); json_object_set_new(call, "result", calling); char *call_text = json_dumps(call, JSON_INDENT(3) | JSON_PRESERVE_ORDER); json_decref(call); g_atomic_int_set(&session->hangingup, 0); JANUS_LOG(LOG_VERB, "Pushing event to peer: %s\n", call_text); int ret = gateway->push_event(session->peer->handle, &janus_videocall_plugin, NULL, call_text, msg->sdp_type, msg->sdp); JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret)); g_free(call_text); /* Send an ack back */ result = json_object(); json_object_set_new(result, "event", json_string("accepted")); } else if(!strcasecmp(request_text, "set")) { /* Update the local configuration (audio/video mute/unmute, bitrate cap or recording) */ JANUS_VALIDATE_JSON_OBJECT(root, set_parameters, error_code, error_cause, TRUE, JANUS_VIDEOCALL_ERROR_MISSING_ELEMENT, JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT); if(error_code != 0) goto error; json_t *audio = json_object_get(root, "audio"); json_t *video = json_object_get(root, "video"); json_t *bitrate = json_object_get(root, "bitrate"); json_t *record = json_object_get(root, "record"); json_t *recfile = json_object_get(root, "filename"); if(audio) { session->audio_active = json_is_true(audio); JANUS_LOG(LOG_VERB, "Setting audio property: %s\n", session->audio_active ? "true" : "false"); } if(video) { if(!session->video_active && json_is_true(video)) { /* Send a PLI */ JANUS_LOG(LOG_VERB, "Just (re-)enabled video, sending a PLI to recover it\n"); char buf[12]; memset(buf, 0, 12); janus_rtcp_pli((char *)&buf, 12); gateway->relay_rtcp(session->handle, 1, buf, 12); } session->video_active = json_is_true(video); JANUS_LOG(LOG_VERB, "Setting video property: %s\n", session->video_active ? "true" : "false"); } if(bitrate) { session->bitrate = json_integer_value(bitrate); JANUS_LOG(LOG_VERB, "Setting video bitrate: %"SCNu64"\n", session->bitrate); if(session->bitrate > 0) { /* FIXME Generate a new REMB (especially useful for Firefox, which doesn't send any we can cap later) */ char buf[24]; memset(buf, 0, 24); janus_rtcp_remb((char *)&buf, 24, session->bitrate); JANUS_LOG(LOG_VERB, "Sending REMB\n"); gateway->relay_rtcp(session->handle, 1, buf, 24); /* FIXME How should we handle a subsequent "no limit" bitrate? */ } } if(record) { if(msg->sdp) { session->has_audio = (strstr(msg->sdp, "m=audio") != NULL); session->has_video = (strstr(msg->sdp, "m=video") != NULL); } gboolean recording = json_is_true(record); const char *recording_base = json_string_value(recfile); JANUS_LOG(LOG_VERB, "Recording %s (base filename: %s)\n", recording ? "enabled" : "disabled", recording_base ? recording_base : "not provided"); janus_mutex_lock(&session->rec_mutex); if(!recording) { /* Not recording (anymore?) */ if(session->arc) { janus_recorder_close(session->arc); JANUS_LOG(LOG_INFO, "Closed audio recording %s\n", session->arc->filename ? session->arc->filename : "??"); janus_recorder_free(session->arc); } session->arc = NULL; if(session->vrc) { janus_recorder_close(session->vrc); JANUS_LOG(LOG_INFO, "Closed video recording %s\n", session->vrc->filename ? session->vrc->filename : "??"); janus_recorder_free(session->vrc); } session->vrc = NULL; } else { /* We've started recording, send a PLI and go on */ char filename[255]; gint64 now = janus_get_real_time(); if(session->has_audio) { /* FIXME We assume we're recording Opus, here */ memset(filename, 0, 255); if(recording_base) { /* Use the filename and path we have been provided */ g_snprintf(filename, 255, "%s-audio", recording_base); session->arc = janus_recorder_create(NULL, "opus", filename); if(session->arc == NULL) { /* FIXME We should notify the fact the recorder could not be created */ JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this VideoCall user!\n"); } } else { /* Build a filename */ g_snprintf(filename, 255, "videocall-%s-%s-%"SCNi64"-audio", session->username ? session->username : "******", (session->peer && session->peer->username) ? session->peer->username : "******", now); session->arc = janus_recorder_create(NULL, "opus", filename); if(session->arc == NULL) { /* FIXME We should notify the fact the recorder could not be created */ JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this VideoCall user!\n"); } } } if(session->has_video) { /* FIXME We assume we're recording VP8, here */ memset(filename, 0, 255); 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, "vp8", 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 VideoCall user!\n"); } } else { /* Build a filename */ g_snprintf(filename, 255, "videocall-%s-%s-%"SCNi64"-video", session->username ? session->username : "******", (session->peer && session->peer->username) ? session->peer->username : "******", now); session->vrc = janus_recorder_create(NULL, "vp8", 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 VideoCall user!\n"); } } /* Send a PLI */ JANUS_LOG(LOG_VERB, "Recording video, sending a PLI to kickstart it\n"); char buf[12]; memset(buf, 0, 12); janus_rtcp_pli((char *)&buf, 12); gateway->relay_rtcp(session->handle, 1, buf, 12); } } janus_mutex_unlock(&session->rec_mutex); } /* Send an ack back */ result = json_object(); json_object_set_new(result, "event", json_string("set")); } else if(!strcasecmp(request_text, "hangup")) { /* Hangup an ongoing call or reject an incoming one */ janus_mutex_lock(&sessions_mutex); janus_videocall_session *peer = session->peer; if(peer == NULL) { JANUS_LOG(LOG_WARN, "No call to hangup\n"); } else { JANUS_LOG(LOG_VERB, "%s is hanging up the call with %s\n", session->username, peer->username); session->peer = NULL; peer->peer = NULL; } janus_mutex_unlock(&sessions_mutex); /* Notify the success as an hangup message */ result = json_object(); json_object_set_new(result, "event", json_string("hangup")); json_object_set_new(result, "username", json_string(session->username)); json_object_set_new(result, "reason", json_string("We did the hangup")); if(peer != NULL) { /* Send event to our peer too */ json_t *call = json_object(); json_object_set_new(call, "videocall", json_string("event")); json_t *calling = json_object(); json_object_set_new(calling, "event", json_string("hangup")); json_object_set_new(calling, "username", json_string(session->username)); json_object_set_new(calling, "reason", json_string("Remote hangup")); json_object_set_new(call, "result", calling); char *call_text = json_dumps(call, JSON_INDENT(3) | JSON_PRESERVE_ORDER); json_decref(call); JANUS_LOG(LOG_VERB, "Pushing event to peer: %s\n", call_text); int ret = gateway->push_event(peer->handle, &janus_videocall_plugin, NULL, call_text, NULL, NULL); JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret)); g_free(call_text); } } else { JANUS_LOG(LOG_ERR, "Unknown request (%s)\n", request_text); error_code = JANUS_VIDEOCALL_ERROR_INVALID_REQUEST; g_snprintf(error_cause, 512, "Unknown request (%s)", request_text); goto error; } json_decref(root); /* Prepare JSON event */ json_t *event = json_object(); json_object_set_new(event, "videocall", json_string("event")); if(result != NULL) json_object_set_new(event, "result", result); char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER); json_decref(event); JANUS_LOG(LOG_VERB, "Pushing event: %s\n", event_text); int ret = gateway->push_event(msg->handle, &janus_videocall_plugin, msg->transaction, event_text, sdp_type, sdp); JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret)); g_free(event_text); if(sdp) g_free(sdp); janus_videocall_message_free(msg); continue; error: { if(root != NULL) json_decref(root); /* Prepare JSON error event */ json_t *event = json_object(); json_object_set_new(event, "videocall", json_string("event")); json_object_set_new(event, "error_code", json_integer(error_code)); json_object_set_new(event, "error", json_string(error_cause)); char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER); json_decref(event); JANUS_LOG(LOG_VERB, "Pushing event: %s\n", event_text); int ret = gateway->push_event(msg->handle, &janus_videocall_plugin, msg->transaction, event_text, NULL, NULL); JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret)); g_free(event_text); janus_videocall_message_free(msg); } } JANUS_LOG(LOG_VERB, "Leaving VideoCall handler thread\n"); return NULL; }
/* Thread to handle incoming messages */ static void *janus_source_handler(void *data) { JANUS_LOG(LOG_VERB, "Joining SourcePlugin handler thread\n"); janus_source_message *msg = NULL; int error_code = 0; char *error_cause = g_malloc0(512); json_t *root = NULL; while (g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) { msg = g_async_queue_pop(messages); if (msg == NULL) continue; if (msg == &exit_message) break; if (msg->handle == NULL) { janus_source_message_free(msg); continue; } janus_source_session *session = NULL; janus_mutex_lock(&sessions_mutex); if (g_hash_table_lookup(sessions, msg->handle) != NULL) { session = (janus_source_session *)msg->handle->plugin_handle; } janus_mutex_unlock(&sessions_mutex); if (!session) { JANUS_LOG(LOG_ERR, "No session associated with this handle...\n"); janus_source_message_free(msg); continue; } if (session->destroyed) { janus_source_message_free(msg); continue; } /* Handle request */ error_code = 0; root = msg->message; if (msg->message == NULL) { JANUS_LOG(LOG_ERR, "No message??\n"); error_code = JANUS_SOURCE_ERROR_NO_MESSAGE; g_snprintf(error_cause, 512, "%s", "No message??"); goto error; } if (!json_is_object(root)) { JANUS_LOG(LOG_ERR, "JSON error: not an object\n"); error_code = JANUS_SOURCE_ERROR_INVALID_JSON; g_snprintf(error_cause, 512, "JSON error: not an object"); goto error; } /* Parse request */ const char *msg_sdp_type = json_string_value(json_object_get(msg->jsep, "type")); const char *msg_sdp = json_string_value(json_object_get(msg->jsep, "sdp")); json_t *audio = json_object_get(root, "audio"); if (audio && !json_is_boolean(audio)) { JANUS_LOG(LOG_ERR, "Invalid element (audio should be a boolean)\n"); error_code = JANUS_SOURCE_ERROR_INVALID_ELEMENT; g_snprintf(error_cause, 512, "Invalid value (audio should be a boolean)"); goto error; } json_t *video = json_object_get(root, "video"); if (video && !json_is_boolean(video)) { JANUS_LOG(LOG_ERR, "Invalid element (video should be a boolean)\n"); error_code = JANUS_SOURCE_ERROR_INVALID_ELEMENT; g_snprintf(error_cause, 512, "Invalid value (video should be a boolean)"); goto error; } json_t *bitrate = json_object_get(root, "bitrate"); if (bitrate && (!json_is_integer(bitrate) || json_integer_value(bitrate) < 0)) { JANUS_LOG(LOG_ERR, "Invalid element (bitrate should be a positive integer)\n"); error_code = JANUS_SOURCE_ERROR_INVALID_ELEMENT; g_snprintf(error_cause, 512, "Invalid value (bitrate should be a positive integer)"); goto error; } json_t *record = json_object_get(root, "record"); if (record && !json_is_boolean(record)) { JANUS_LOG(LOG_ERR, "Invalid element (record should be a boolean)\n"); error_code = JANUS_SOURCE_ERROR_INVALID_ELEMENT; g_snprintf(error_cause, 512, "Invalid value (record should be a boolean)"); goto error; } json_t *recfile = json_object_get(root, "filename"); if (recfile && !json_is_string(recfile)) { JANUS_LOG(LOG_ERR, "Invalid element (filename should be a string)\n"); error_code = JANUS_SOURCE_ERROR_INVALID_ELEMENT; g_snprintf(error_cause, 512, "Invalid value (filename should be a string)"); goto error; } json_t *id = json_object_get(root, "id"); if(id && !json_is_string(id)) { JANUS_LOG(LOG_ERR, "Invalid element (id should be a string)\n"); error_code = JANUS_SOURCE_ERROR_INVALID_ELEMENT; g_snprintf(error_cause, 512, "Invalid value (id should be positive string)"); goto error; } /* Enforce request */ if (audio) { session->audio_active = json_is_true(audio); JANUS_LOG(LOG_VERB, "Setting audio property: %s\n", session->audio_active ? "true" : "false"); } if (video) { if (!session->video_active && json_is_true(video)) { /* Send a PLI */ JANUS_LOG(LOG_VERB, "Just (re-)enabled video, sending a PLI to recover it\n"); char buf[12]; memset(buf, 0, 12); janus_rtcp_pli((char *)&buf, 12); gateway->relay_rtcp(session->handle, 1, buf, 12); } session->video_active = json_is_true(video); JANUS_LOG(LOG_VERB, "Setting video property: %s\n", session->video_active ? "true" : "false"); } if (bitrate) { session->bitrate = json_integer_value(bitrate); JANUS_LOG(LOG_VERB, "Setting video bitrate: %"SCNu64"\n", session->bitrate); if (session->bitrate > 0) { /* FIXME Generate a new REMB (especially useful for Firefox, which doesn't send any we can cap later) */ char buf[24]; memset(buf, 0, 24); janus_rtcp_remb((char *)&buf, 24, session->bitrate); JANUS_LOG(LOG_VERB, "Sending REMB\n"); gateway->relay_rtcp(session->handle, 1, buf, 24); /* FIXME How should we handle a subsequent "no limit" bitrate? */ } } if(id) { session->id = g_strdup(json_string_value(id)); } if (!audio && !video && !bitrate && !record && !id && !msg_sdp) { JANUS_LOG(LOG_ERR, "No supported attributes (audio, video, bitrate, record, id, jsep) found\n"); error_code = JANUS_SOURCE_ERROR_INVALID_ELEMENT; g_snprintf(error_cause, 512, "Message error: no supported attributes (audio, video, bitrate, record, id, jsep) found"); goto error; } /* Prepare JSON event */ json_t *event = json_object(); json_object_set_new(event, "source", json_string("event")); json_object_set_new(event, "result", json_string("ok")); if(!msg_sdp) { int ret = gateway->push_event(msg->handle, &janus_source_plugin, msg->transaction, event, NULL); JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret)); json_decref(event); } else { /* Forward the same offer to the gateway, to start the source plugin */ const char *type = NULL; if (!strcasecmp(msg_sdp_type, "offer")) type = "answer"; if (!strcasecmp(msg_sdp_type, "answer")) type = "offer"; /* Any media direction that needs to be fixed? */ char *sdp = g_strdup(msg_sdp); if (strstr(sdp, "a=recvonly")) { /* Turn recvonly to inactive, as we simply bounce media back */ sdp = janus_string_replace(sdp, "a=recvonly", "a=inactive"); } else if (strstr(sdp, "a=sendonly")) { /* Turn sendonly to recvonly */ sdp = janus_string_replace(sdp, "a=sendonly", "a=recvonly"); /* FIXME We should also actually not echo this media back, though... */ } /* Make also sure we get rid of ULPfec, red, etc. */ if (strstr(sdp, "ulpfec")) { /* FIXME This really needs some better code */ sdp = janus_string_replace(sdp, "a=rtpmap:116 red/90000\r\n", ""); sdp = janus_string_replace(sdp, "a=rtpmap:117 ulpfec/90000\r\n", ""); sdp = janus_string_replace(sdp, "a=rtpmap:96 rtx/90000\r\n", ""); sdp = janus_string_replace(sdp, "a=fmtp:96 apt=100\r\n", ""); sdp = janus_string_replace(sdp, "a=rtpmap:97 rtx/90000\r\n", ""); sdp = janus_string_replace(sdp, "a=fmtp:97 apt=101\r\n", ""); sdp = janus_string_replace(sdp, "a=rtpmap:98 rtx/90000\r\n", ""); sdp = janus_string_replace(sdp, "a=fmtp:98 apt=116\r\n", ""); sdp = janus_string_replace(sdp, " 116", ""); sdp = janus_string_replace(sdp, " 117", ""); sdp = janus_string_replace(sdp, " 96", ""); sdp = janus_string_replace(sdp, " 97", ""); sdp = janus_string_replace(sdp, " 98", ""); } json_t *jsep = json_pack("{ssss}", "type", type, "sdp", sdp); sdp = janus_source_do_codec_negotiation(session, sdp); /* How long will the gateway take to push the event? */ g_atomic_int_set(&session->hangingup, 0); gint64 start = janus_get_monotonic_time(); int res = gateway->push_event(msg->handle, &janus_source_plugin, msg->transaction, event, jsep); JANUS_LOG(LOG_VERB, " >> Pushing event: %d (took %"SCNu64" us)\n", res, janus_get_monotonic_time() - start); g_free(sdp); /* We don't need the event and jsep anymore */ json_decref(event); json_decref(jsep); } janus_source_message_free(msg); continue; error: { /* Prepare JSON error event */ json_t *event = json_object(); json_object_set_new(event, "source", json_string("event")); json_object_set_new(event, "error_code", json_integer(error_code)); json_object_set_new(event, "error", json_string(error_cause)); int ret = gateway->push_event(msg->handle, &janus_source_plugin, msg->transaction, event, NULL); JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret)); janus_source_message_free(msg); /* We don't need the event anymore */ json_decref(event); } } g_free(error_cause); JANUS_LOG(LOG_VERB, "Leaving SourcePlugin handler thread\n"); return NULL; }
static void * ps_gstsink_handler (void * data) { PS_LOG (LOG_VERB, "Joining GstSink handler thread..\n"); ps_gstsink_message * msg = NULL; int error_code = 0; char * error_cause = g_malloc0(1024); if (error_cause == NULL) { PS_LOG(LOG_FATAL,"Memory error!\n"); return NULL; } json_t * root = NULL; while (g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) { msg = g_async_queue_pop(messages); if (msg == NULL) continue; if (msg == &exit_message) break; if (msg->handle == NULL) { ps_gstsink_message_free(msg); continue; } ps_gstsink_session * session = NULL; ps_mutex_lock (&sessions_mutex); if (g_hash_table_lookup(sessions, msg->handle) != NULL) { session = (ps_gstsink_session *)msg->handle->plugin_handle; } ps_mutex_unlock (&sessions_mutex); if (!session) { PS_LOG (LOG_ERR, "no session associated with this handle...\n"); ps_gstsink_message_free (msg); continue; } if (session->destroyed) { ps_gstsink_message_free (msg); continue; } error_code = 0; root = NULL; if (msg->message == NULL) { PS_LOG (LOG_ERR, "No message??\n"); error_code = PS_GSTSINK_ERROR_NO_MESSAGE; g_snprintf(error_cause, 512, "%s","No Message??"); goto error; } root = msg->message; json_t * request = json_object_get(root, "request"); if (!request) { PS_LOG (LOG_ERR, "Missing element (request)\n"); error_code = PS_GSTSINK_ERROR_MISSING_ELEMENT; g_snprintf (error_cause, 512, "Missing element (request)"); goto error; } if (!json_is_string(request)) { PS_LOG (LOG_ERR, "Invalid element (request must be a string)\n"); error_code = PS_GSTSINK_ERROR_MISSING_ELEMENT; g_snprintf (error_cause, 512, "Invalid element (request must be string)"); goto error; } const char * request_text = json_string_value (request); json_t * result = NULL; //const char * sdp_type = NULL; char * sdp = NULL; if(!strcasecmp(request_text, "start")) { if(!msg->sdp) { PS_LOG(LOG_ERR, "Missing SDP offer\n"); error_code = PS_GSTSINK_ERROR_MISSING_ELEMENT; g_snprintf(error_cause, 512, "Missing SDP offer"); goto error; } guint64 id = 100; if(strstr(msg->sdp, "m=audio")) { PS_LOG (LOG_VERB, "Audio requested\n"); } if(strstr(msg->sdp, "m=video")) { PS_LOG (LOG_VERB, "Video requested\n"); } session->recorder = TRUE; /* We need to prepare an answer */ int opus_pt = 0, vp8_pt = 0; char * opus_dir = NULL; char * vp8_dir = NULL; opus_dir = ps_get_opus_dir (msg->sdp); vp8_dir = ps_get_vp8_dir (msg->sdp); PS_LOG (LOG_VERB, "Audio direction: %s, Video direction: %s\n", opus_dir, vp8_dir); opus_pt = ps_get_opus_pt(msg->sdp); PS_LOG(LOG_VERB, "Opus payload type is %d\n", opus_pt); vp8_pt = ps_get_vp8_pt(msg->sdp); PS_LOG(LOG_VERB, "VP8 payload type is %d\n", vp8_pt); char sdptemp[1024], audio_mline[256], video_mline[512]; if(opus_pt > 0 && opus_dir != NULL) { if (!strcasecmp(opus_dir, "sendrecv") || !strcasecmp(opus_dir, "sendonly")){ g_snprintf(audio_mline, 256, sdp_a_template, opus_pt, /* Opus payload type */ "recvonly", /* FIXME to check a= line */ opus_pt); /* Opus payload type */ session->play_audio = TRUE; } else { g_snprintf(audio_mline, 256, sdp_a_template, opus_pt, /* Opus payload type */ "inactive", /* FIXME to check a= line */ opus_pt); /* Opus payload type */ } } else { audio_mline[0] = '\0'; } if(vp8_pt > 0 && vp8_dir != NULL) { if (!strcasecmp(vp8_dir, "sendrecv") || !strcasecmp(vp8_dir, "sendonly")){ g_snprintf(video_mline, 512, sdp_v_template, vp8_pt, /* VP8 payload type */ "recvonly", /* FIXME to check a= line */ vp8_pt, /* VP8 payload type */ vp8_pt, /* VP8 payload type */ vp8_pt, /* VP8 payload type */ vp8_pt, /* VP8 payload type */ vp8_pt); /* VP8 payload type */ session->play_video = TRUE; } else { g_snprintf(video_mline, 512, sdp_v_template, vp8_pt, /* VP8 payload type */ "inactive", /* FIXME to check a= line */ vp8_pt, /* VP8 payload type */ vp8_pt, /* VP8 payload type */ vp8_pt, /* VP8 payload type */ vp8_pt, /* VP8 payload type */ vp8_pt); /* VP8 payload type */ } } else { video_mline[0] = '\0'; } g_snprintf(sdptemp, 1024, sdp_template, ps_get_real_time(), /* We need current time here */ ps_get_real_time(), /* We need current time here */ "PacketServo", /* Playout session */ audio_mline, /* Audio m-line, if any */ video_mline); /* Video m-line, if any */ sdp = g_strdup(sdptemp); PS_LOG(LOG_VERB, "Going to answer this SDP:\n%s\n", sdp); /* Done! */ result = json_object(); json_object_set_new(result, "status", json_string("recording")); json_object_set_new(result, "id", json_integer(id)); } else if (!strcasecmp(request_text,"stop")) { session->active = FALSE; /* Set gst pipeline to paused or set to null? */ /* session->aplayer?; session->vplayer?; */ session->recorder = FALSE; /* Done */ result = json_object(); json_object_set_new(result, "status", json_string("stopped")); //gateway->close_pc(session->handle); } else { PS_LOG(LOG_VERB, "Unknown request '%s'\n", request_text); error_code = PS_GSTSINK_ERROR_INVALID_REQUEST; g_snprintf(error_cause, 512, "Unknown request '%s'", request_text); goto error; } /* Any SDP to handle? */ if (msg->sdp) { session->firefox = strstr(msg->sdp, "Mozilla") ? TRUE : FALSE; PS_LOG (LOG_VERB, "This is involving negotiation (%s) as well: \n%s\n", msg->sdp_type, msg->sdp); } /* Prepare JSON event */ json_t * event = json_object(); json_object_set_new (event, "recordplay", json_string("event")); if (result != NULL) json_object_set_new (event, "result", result); char * event_text = json_dumps (event, JSON_INDENT(3) | JSON_PRESERVE_ORDER); json_decref (event); PS_LOG (LOG_VERB, "Pushing event: %s\n",event_text); if(!sdp) { int ret = gateway->push_event(msg->handle, &ps_gstsink_plugin, msg->transaction, event_text, NULL, NULL); PS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret)); } else { const char *type = session->recorder ? "answer" : "offer"; /* How long will the gateway take to push the event? */ g_atomic_int_set(&session->hangingup, 0); gint64 start = ps_get_monotonic_time(); int res = gateway->push_event(msg->handle, &ps_gstsink_plugin, msg->transaction, event_text, type, sdp); PS_LOG(LOG_VERB, " >> Pushing event: %d (took %"SCNu64" us)\n", res, ps_get_monotonic_time()-start); g_free(sdp); } g_free (event_text); ps_gstsink_message_free (msg); continue; error: { /* Prepare JSON error event */ json_t *event = json_object(); json_object_set_new(event, "recordplay", json_string("event")); json_object_set_new(event, "error_code", json_integer(error_code)); json_object_set_new(event, "error", json_string(error_cause)); char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER); json_decref(event); PS_LOG(LOG_VERB, "Pushing event: %s\n", event_text); int ret = gateway->push_event(msg->handle, &ps_gstsink_plugin, msg->transaction, event_text, NULL, NULL); PS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret)); g_free(event_text); ps_gstsink_message_free(msg); } } g_free(error_cause); PS_LOG (LOG_VERB, "Leaving GstSink handler thread..\n"); return NULL; }
/* Thread to handle incoming messages */ static void *janus_echotest_handler(void *data) { JANUS_LOG(LOG_VERB, "Joining thread\n"); janus_echotest_message *msg = NULL; int error_code = 0; char *error_cause = calloc(512, sizeof(char)); /* FIXME 512 should be enough, but anyway... */ if(error_cause == NULL) { JANUS_LOG(LOG_FATAL, "Memory error!\n"); return NULL; } json_t *root = NULL; while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) { if(!messages || (msg = g_async_queue_try_pop(messages)) == NULL) { usleep(50000); continue; } janus_echotest_session *session = (janus_echotest_session *)msg->handle->plugin_handle; if(!session) { JANUS_LOG(LOG_ERR, "No session associated with this handle...\n"); janus_echotest_message_free(msg); continue; } if(session->destroyed) { janus_echotest_message_free(msg); continue; } /* Handle request */ error_code = 0; root = NULL; JANUS_LOG(LOG_VERB, "Handling message: %s\n", msg->message); if(msg->message == NULL) { JANUS_LOG(LOG_ERR, "No message??\n"); error_code = JANUS_ECHOTEST_ERROR_NO_MESSAGE; g_snprintf(error_cause, 512, "%s", "No message??"); goto error; } json_error_t error; root = json_loads(msg->message, 0, &error); if(!root) { JANUS_LOG(LOG_ERR, "JSON error: on line %d: %s\n", error.line, error.text); error_code = JANUS_ECHOTEST_ERROR_INVALID_JSON; g_snprintf(error_cause, 512, "JSON error: on line %d: %s", error.line, error.text); goto error; } if(!json_is_object(root)) { JANUS_LOG(LOG_ERR, "JSON error: not an object\n"); error_code = JANUS_ECHOTEST_ERROR_INVALID_JSON; g_snprintf(error_cause, 512, "JSON error: not an object"); goto error; } json_t *audio = json_object_get(root, "audio"); if(audio && !json_is_boolean(audio)) { JANUS_LOG(LOG_ERR, "Invalid element (audio should be a boolean)\n"); error_code = JANUS_ECHOTEST_ERROR_INVALID_ELEMENT; g_snprintf(error_cause, 512, "Invalid value (audio should be a boolean)"); goto error; } json_t *video = json_object_get(root, "video"); if(video && !json_is_boolean(video)) { JANUS_LOG(LOG_ERR, "Invalid element (video should be a boolean)\n"); error_code = JANUS_ECHOTEST_ERROR_INVALID_ELEMENT; g_snprintf(error_cause, 512, "Invalid value (video should be a boolean)"); goto error; } json_t *bitrate = json_object_get(root, "bitrate"); if(bitrate && !json_is_integer(bitrate)) { JANUS_LOG(LOG_ERR, "Invalid element (bitrate should be an integer)\n"); error_code = JANUS_ECHOTEST_ERROR_INVALID_ELEMENT; g_snprintf(error_cause, 512, "Invalid value (bitrate should be an integer)"); goto error; } if(audio) { session->audio_active = json_is_true(audio); JANUS_LOG(LOG_VERB, "Setting audio property: %s\n", session->audio_active ? "true" : "false"); } if(video) { session->video_active = json_is_true(video); JANUS_LOG(LOG_VERB, "Setting video property: %s\n", session->video_active ? "true" : "false"); } if(bitrate) { session->bitrate = json_integer_value(bitrate); JANUS_LOG(LOG_VERB, "Setting video bitrate: %"SCNu64"\n", session->bitrate); if(session->bitrate > 0) { /* FIXME Generate a new REMB (especially useful for Firefox, which doesn't send any we can cap later) */ char buf[24]; memset(buf, 0, 24); janus_rtcp_remb((char *)&buf, 24, session->bitrate); JANUS_LOG(LOG_VERB, "Sending REMB\n"); gateway->relay_rtcp(session->handle, 1, buf, 24); /* FIXME How should we handle a subsequent "no limit" bitrate? */ } } /* Any SDP to handle? */ if(msg->sdp) { JANUS_LOG(LOG_VERB, "This is involving a negotiation (%s) as well:\n%s\n", msg->sdp_type, msg->sdp); } json_decref(root); /* Prepare JSON event */ json_t *event = json_object(); json_object_set_new(event, "echotest", json_string("event")); json_object_set_new(event, "result", json_string("ok")); char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER); json_decref(event); JANUS_LOG(LOG_VERB, "Pushing event: %s\n", event_text); if(!msg->sdp) { int ret = gateway->push_event(msg->handle, &janus_echotest_plugin, msg->transaction, event_text, NULL, NULL); JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret)); } else { /* Forward the same offer to the gateway, to start the echo test */ const char *type = NULL; if(!strcasecmp(msg->sdp_type, "offer")) type = "answer"; if(!strcasecmp(msg->sdp_type, "answer")) type = "offer"; /* How long will the gateway take to push the event? */ gint64 start = janus_get_monotonic_time(); int res = gateway->push_event(msg->handle, &janus_echotest_plugin, msg->transaction, event_text, type, msg->sdp); JANUS_LOG(LOG_VERB, " >> Pushing event: %d (took %"SCNu64" us)\n", res, janus_get_monotonic_time()-start); } g_free(event_text); janus_echotest_message_free(msg); continue; error: { if(root != NULL) json_decref(root); /* Prepare JSON error event */ json_t *event = json_object(); json_object_set_new(event, "echotest", json_string("event")); json_object_set_new(event, "error_code", json_integer(error_code)); json_object_set_new(event, "error", json_string(error_cause)); char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER); json_decref(event); JANUS_LOG(LOG_VERB, "Pushing event: %s\n", event_text); int ret = gateway->push_event(msg->handle, &janus_echotest_plugin, msg->transaction, event_text, NULL, NULL); JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret)); g_free(event_text); janus_echotest_message_free(msg); } } g_free(error_cause); JANUS_LOG(LOG_VERB, "Leaving thread\n"); return NULL; }