void janus_rabbitmq_destroy(void) { if(!g_atomic_int_get(&initialized)) return; g_atomic_int_set(&stopping, 1); if(rmq_client) { rmq_client->destroy = 1; g_async_queue_push(rmq_client->messages, &exit_message); if(rmq_client->in_thread) g_thread_join(rmq_client->in_thread); if(rmq_client->out_thread) g_thread_join(rmq_client->out_thread); if(rmq_client->rmq_conn && rmq_client->rmq_channel) { amqp_channel_close(rmq_client->rmq_conn, rmq_client->rmq_channel, AMQP_REPLY_SUCCESS); amqp_connection_close(rmq_client->rmq_conn, AMQP_REPLY_SUCCESS); amqp_destroy_connection(rmq_client->rmq_conn); } } g_free(rmq_client); janus_transport_session_destroy(rmq_session); g_free(rmqhost); g_free(vhost); g_free(username); g_free(password); g_free(janus_exchange); g_free(to_janus); g_free(from_janus); g_free(to_janus_admin); g_free(from_janus_admin); g_free(ssl_cacert_file); g_free(ssl_cert_file); g_free(ssl_key_file); g_atomic_int_set(&initialized, 0); g_atomic_int_set(&stopping, 0); JANUS_LOG(LOG_INFO, "%s destroyed!\n", JANUS_RABBITMQ_NAME); }
const char *janus_rtp_header_extension_get_from_id(const char *sdp, int id) { if(!sdp || id < 0) return NULL; /* Look for the mapping */ char extmap[100]; g_snprintf(extmap, 100, "a=extmap:%d ", id); const char *line = strstr(sdp, "m="); while(line) { char *next = strchr(line, '\n'); if(next) { *next = '\0'; if(strstr(line, extmap)) { /* Gotcha! */ char extension[100]; if(sscanf(line, "a=extmap:%d %s", &id, extension) == 2) { *next = '\n'; if(strstr(extension, JANUS_RTP_EXTMAP_AUDIO_LEVEL)) return JANUS_RTP_EXTMAP_AUDIO_LEVEL; if(strstr(extension, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION)) return JANUS_RTP_EXTMAP_VIDEO_ORIENTATION; if(strstr(extension, JANUS_RTP_EXTMAP_PLAYOUT_DELAY)) return JANUS_RTP_EXTMAP_PLAYOUT_DELAY; if(strstr(extension, JANUS_RTP_EXTMAP_TOFFSET)) return JANUS_RTP_EXTMAP_TOFFSET; if(strstr(extension, JANUS_RTP_EXTMAP_ABS_SEND_TIME)) return JANUS_RTP_EXTMAP_ABS_SEND_TIME; if(strstr(extension, JANUS_RTP_EXTMAP_CC_EXTENSIONS)) return JANUS_RTP_EXTMAP_CC_EXTENSIONS; JANUS_LOG(LOG_ERR, "Unsupported extension '%s'\n", extension); return NULL; } } *next = '\n'; } line = next ? (next+1) : NULL; } return NULL; }
static gchar * janus_source_do_codec_negotiation(janus_source_session * session, gchar * orig_sdp) { gchar * sdp = NULL; idilia_codec preferred_codec = janus_source_select_video_codec_by_priority_list(orig_sdp); sdp = sdp_set_video_codec(orig_sdp, preferred_codec); g_free(orig_sdp); for (int stream = 0; stream < JANUS_SOURCE_STREAM_MAX; stream++) { if (stream == JANUS_SOURCE_STREAM_VIDEO) { session->codec[stream] = sdp_get_video_codec(sdp); } else if (stream == JANUS_SOURCE_STREAM_AUDIO) { session->codec[stream] = sdp_get_audio_codec(sdp); } session->codec_pt[stream] = sdp_get_codec_pt(sdp, session->codec[stream]); JANUS_LOG(LOG_INFO, "Codec used: %s\n", get_codec_name(session->codec[stream])); } return sdp; }
void janus_videocall_destroy(void) { if(!g_atomic_int_get(&initialized)) return; g_atomic_int_set(&stopping, 1); if(handler_thread != NULL) { g_thread_join(handler_thread); handler_thread = NULL; } if(watchdog != NULL) { g_thread_join(watchdog); watchdog = NULL; } /* FIXME We should destroy the sessions cleanly */ janus_mutex_lock(&sessions_mutex); g_hash_table_destroy(sessions); janus_mutex_unlock(&sessions_mutex); g_async_queue_unref(messages); messages = NULL; sessions = NULL; g_atomic_int_set(&initialized, 0); g_atomic_int_set(&stopping, 0); JANUS_LOG(LOG_INFO, "%s destroyed!\n", JANUS_VIDEOCALL_NAME); }
void janus_echotest_create_session(janus_plugin_session *handle, int *error) { if(stopping || !initialized) { *error = -1; return; } janus_echotest_session *session = (janus_echotest_session *)calloc(1, sizeof(janus_echotest_session)); if(session == NULL) { JANUS_LOG(LOG_FATAL, "Memory error!\n"); *error = -2; return; } session->handle = handle; session->audio_active = TRUE; session->video_active = TRUE; session->bitrate = 0; /* No limit */ session->destroyed = 0; handle->plugin_handle = session; janus_mutex_lock(&sessions_mutex); g_hash_table_insert(sessions, handle, session); janus_mutex_unlock(&sessions_mutex); return; }
void janus_serial_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len) { if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) return; /* Simple echo test */ if(gateway) { /* Honour the audio/video active flags */ 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((!video && session->audio_active) || (video && session->video_active)) { /* Save the frame if we're recording */ if(video && session->vrc) janus_recorder_save_frame(session->vrc, buf, len); else if(!video && session->arc) janus_recorder_save_frame(session->arc, buf, len); /* Send the frame back */ gateway->relay_rtp(handle, video, buf, len); } } }
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; }
/* DTLS alert callback */ void janus_dtls_callback(const SSL *ssl, int where, int ret) { /* We only care about alerts */ if (!(where & SSL_CB_ALERT)) { return; } janus_dtls_srtp *dtls = SSL_get_ex_data(ssl, 0); if(!dtls) { JANUS_LOG(LOG_ERR, "No DTLS session related to this alert...\n"); return; } janus_ice_component *component = dtls->component; if(component == NULL) { JANUS_LOG(LOG_ERR, "No ICE component related to this alert...\n"); return; } janus_ice_stream *stream = component->stream; if(!stream) { JANUS_LOG(LOG_ERR, "No ICE stream related to this alert...\n"); return; } janus_ice_handle *handle = stream->handle; if(!handle) { JANUS_LOG(LOG_ERR, "No ICE handle related to this alert...\n"); return; } JANUS_LOG(LOG_VERB, "[%"SCNu64"] DTLS alert triggered on stream %"SCNu16" (component %"SCNu16"), closing...\n", handle->handle_id, stream->stream_id, component->component_id); janus_flags_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_CLEANING); if(!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_ALERT)) { janus_flags_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_ALERT); if(handle->iceloop) g_main_loop_quit(handle->iceloop); janus_plugin *plugin = (janus_plugin *)handle->app; if(plugin != NULL) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Telling the plugin about it (%s)\n", handle->handle_id, plugin->get_name()); if(plugin && plugin->hangup_media) plugin->hangup_media(handle->app_handle); janus_ice_notify_hangup(handle, "DTLS alert"); } } }
/* Helper to create a named Unix Socket out of the path to link to */ static int janus_pfunix_create_socket(char *pfname, gboolean use_dgram) { if(pfname == NULL) return -1; int fd = -1; if(strlen(pfname) > UNIX_PATH_MAX) { JANUS_LOG(LOG_WARN, "The provided path name (%s) is longer than %lu characters, it will be truncated\n", pfname, UNIX_PATH_MAX); pfname[UNIX_PATH_MAX] = '\0'; } /* Create socket */ int flags = use_dgram ? SOCK_DGRAM | SOCK_NONBLOCK : SOCK_SEQPACKET | SOCK_NONBLOCK; fd = socket(use_dgram ? AF_UNIX : PF_UNIX, flags, 0); if(fd < 0) { JANUS_LOG(LOG_FATAL, "Unix Sockets %s creation failed: %d, %s\n", pfname, errno, strerror(errno)); } else { /* Unlink before binding */ unlink(pfname); /* Let's bind to the provided path now */ struct sockaddr_un address; memset(&address, 0, sizeof(address)); address.sun_family = AF_UNIX; g_snprintf(address.sun_path, UNIX_PATH_MAX, "%s", pfname); JANUS_LOG(LOG_VERB, "Binding Unix Socket %s... (Janus API)\n", pfname); if(bind(fd, (struct sockaddr *)&address, sizeof(address)) != 0) { JANUS_LOG(LOG_FATAL, "Bind for Unix Socket %s failed: %d, %s\n", pfname, errno, strerror(errno)); close(fd); fd = -1; return fd; } if(!use_dgram) { JANUS_LOG(LOG_VERB, "Listening on Unix Socket %s...\n", pfname); if(listen(fd, 128) != 0) { JANUS_LOG(LOG_FATAL, "Listening on Unix Socket %s failed: %d, %s\n", pfname, errno, strerror(errno)); close(fd); fd = -1; } } } return fd; }
void janus_sctp_handle_send_failed_event(struct sctp_send_failed_event *ssfe) { size_t i, n; if(ssfe->ssfe_flags & SCTP_DATA_UNSENT) { JANUS_LOG(LOG_VERB, "Unsent "); } if(ssfe->ssfe_flags & SCTP_DATA_SENT) { JANUS_LOG(LOG_VERB, "Sent "); } if(ssfe->ssfe_flags & ~(SCTP_DATA_SENT | SCTP_DATA_UNSENT)) { JANUS_LOG(LOG_VERB, "(flags = %x) ", ssfe->ssfe_flags); } JANUS_LOG(LOG_VERB, "message with PPID = %d, SID = %d, flags: 0x%04x due to error = 0x%08x", ntohl(ssfe->ssfe_info.snd_ppid), ssfe->ssfe_info.snd_sid, ssfe->ssfe_info.snd_flags, ssfe->ssfe_error); n = ssfe->ssfe_length - sizeof(struct sctp_send_failed_event); for(i = 0; i < n; i++) { JANUS_LOG(LOG_VERB, " 0x%02x", ssfe->ssfe_data[i]); } JANUS_LOG(LOG_VERB, ".\n"); return; }
/* Transport creator */ janus_transport *create(void) { JANUS_LOG(LOG_VERB, "%s created!\n", JANUS_WEBSOCKETS_NAME); return &janus_websockets_transport; }
void janus_serial_slow_link(janus_plugin_session *handle, int uplink, int video) { /* The core is informing us that our peer got or sent too many NACKs, are we pushing media too hard? */ if(handle == NULL || handle->stopped || 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; session->slowlink_count++; if(uplink && !video && !session->audio_active) { /* We're not relaying audio and the peer is expecting it, so NACKs are normal */ JANUS_LOG(LOG_VERB, "Getting a lot of NACKs (slow uplink) for audio, but that's expected, a configure disabled the audio forwarding\n"); } else if(uplink && video && !session->video_active) { /* We're not relaying video and the peer is expecting it, so NACKs are normal */ JANUS_LOG(LOG_VERB, "Getting a lot of NACKs (slow uplink) for video, but that's expected, a configure disabled the video forwarding\n"); } else { /* Slow uplink or downlink, maybe we set the bitrate cap too high? */ if(video) { /* Halve the bitrate, but don't go too low... */ session->bitrate = session->bitrate > 0 ? session->bitrate : 512*1024; session->bitrate = session->bitrate/2; if(session->bitrate < 64*1024) session->bitrate = 64*1024; JANUS_LOG(LOG_WARN, "Getting a lot of NACKs (slow %s) for %s, forcing a lower REMB: %"SCNu64"\n", uplink ? "uplink" : "downlink", video ? "video" : "audio", session->bitrate); /* ... and send a new REMB back */ char rtcpbuf[200]; memset(rtcpbuf, 0, 200); /* FIXME First put a RR (fake)... */ int rrlen = 32; rtcp_rr *rr = (rtcp_rr *)&rtcpbuf; rr->header.version = 2; rr->header.type = RTCP_RR; rr->header.rc = 1; rr->header.length = htons((rrlen/4)-1); /* ... then put a SDES... */ int sdeslen = janus_rtcp_sdes((char *)(&rtcpbuf)+rrlen, 200-rrlen, "janusvideo", 10); if(sdeslen > 0) { /* ... and then finally a REMB */ janus_rtcp_remb((char *)(&rtcpbuf)+rrlen+sdeslen, 24, session->bitrate); gateway->relay_rtcp(handle, 1, rtcpbuf, rrlen+sdeslen+24); } /* As a last thing, notify the user about this */ json_t *event = json_object(); json_object_set_new(event, "serial", json_string("event")); json_t *result = json_object(); json_object_set_new(result, "status", json_string("slow_link")); json_object_set_new(result, "bitrate", json_integer(session->bitrate)); json_object_set_new(event, "result", result); char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER); json_decref(event); json_decref(result); event = NULL; gateway->push_event(session->handle, &janus_serial_plugin, NULL, event_text, NULL, NULL); g_free(event_text); } } }
/* Plugin implementation */ int janus_serial_init(janus_callbacks *callback, const char *config_path) { if(g_atomic_int_get(&stopping)) { /* Still stopping from before */ return -1; } if(callback == NULL || config_path == NULL) { /* Invalid arguments */ return -1; } /* Read configuration */ char filename[255]; g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_SERIAL_PACKAGE); JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename); janus_config *config = janus_config_parse(filename); if(config != NULL) janus_config_print(config); /* This plugin actually has nothing to configure... */ janus_config_destroy(config); config = NULL; sessions = g_hash_table_new(NULL, NULL); janus_mutex_init(&sessions_mutex); messages = g_async_queue_new_full((GDestroyNotify) janus_serial_message_free); /* This is the callback we'll need to invoke to contact the gateway */ gateway = callback; g_atomic_int_set(&initialized, 1); GError *error = NULL; /* Start the sessions watchdog */ watchdog = g_thread_try_new("serial watchdog", &janus_serial_watchdog, NULL, &error); if(error != NULL) { g_atomic_int_set(&initialized, 0); JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the Serial watchdog thread...\n", error->code, error->message ? error->message : "??"); return -1; } /* Launch the thread that will handle incoming messages */ handler_thread = g_thread_try_new("janus serial handler", janus_serial_handler, NULL, &error); if(error != NULL) { g_atomic_int_set(&initialized, 0); JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the serial handler thread...\n", error->code, error->message ? error->message : "??"); return -1; } // init part /* Open the file descriptor in non-blocking mode */ if(fd = open(portname,O_RDWR | O_NOCTTY | O_NONBLOCK)){ //printf("stream aperto\n"); JANUS_LOG(LOG_INFO, "stream aperto - Janus Serial\n"); }else{ //printf("errora nell'apertura dello stream\n"); JANUS_LOG(LOG_INFO, "errore nell'apertura dello stream\n"); return 0; } /* Set up the control structure */ struct termios toptions; /* Get currently set options for the tty */ tcgetattr(fd, &toptions); /* Set custom options */ /* 9600 baud */ cfsetispeed(&toptions, B9600); cfsetospeed(&toptions, B9600); /* 8 bits, no parity, no stop bits */ toptions.c_cflag &= ~PARENB; toptions.c_cflag &= ~CSTOPB; toptions.c_cflag &= ~CSIZE; toptions.c_cflag |= CS8; /* no hardware flow control */ toptions.c_cflag &= ~CRTSCTS; /* enable receiver, ignore status lines */ toptions.c_cflag |= CREAD | CLOCAL; /* disable input/output flow control, disable restart chars */ toptions.c_iflag &= ~(IXON | IXOFF | IXANY); /* disable canonical input, disable echo, disable visually erase chars, disable terminal-generated signals */ toptions.c_iflag &= ~(ICANON | ECHO | ECHOE | ISIG); /* disable output processing */ toptions.c_oflag &= ~OPOST; /* wait for 24 characters to come in before read returns */ toptions.c_cc[VMIN] = 12; /* no minimum time to wait before read returns */ toptions.c_cc[VTIME] = 0; /* commit the options */ tcsetattr(fd, TCSANOW, &toptions); /* Wait for the Arduino to reset */ usleep(1000*1000); /* Flush anything already in the serial buffer */ tcflush(fd, TCIFLUSH); write(fd,"k",1); JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_SERIAL_NAME); return 0; }
/* 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; }
/* FIXME Thread to send RTP packets from a file (on demand) */ static void *janus_streaming_ondemand_thread(void *data) { JANUS_LOG(LOG_VERB, "Filesource (on demand) RTP thread starting...\n"); janus_streaming_session *session = (janus_streaming_session *)data; if(!session) { JANUS_LOG(LOG_ERR, "Invalid session!\n"); return NULL; } janus_streaming_mountpoint *mountpoint = session->mountpoint; if(!mountpoint) { JANUS_LOG(LOG_ERR, "Invalid mountpoint!\n"); return NULL; } if(mountpoint->streaming_source != janus_streaming_source_file) { JANUS_LOG(LOG_ERR, "Not an file source mountpoint!\n"); return NULL; } if(mountpoint->streaming_type != janus_streaming_type_on_demand) { JANUS_LOG(LOG_ERR, "Not an on-demand file source mountpoint!\n"); return NULL; } janus_streaming_file_source *source = mountpoint->source; if(source == NULL || source->filename == NULL) { JANUS_LOG(LOG_ERR, "Invalid file source mountpoint!\n"); return NULL; } JANUS_LOG(LOG_VERB, "Opening file source %s...\n", source->filename); FILE *audio = fopen(source->filename, "rb"); if(!audio) { JANUS_LOG(LOG_ERR, "Ooops, audio file missing!\n"); return NULL; } JANUS_LOG(LOG_VERB, "Streaming audio file: %s\n", source->filename); /* Buffer */ char *buf = calloc(1024, sizeof(char)); if(buf == NULL) { JANUS_LOG(LOG_FATAL, "Memory error!\n"); return NULL; } /* Set up RTP */ gint16 seq = 1; gint32 ts = 0; rtp_header *header = (rtp_header *)buf; header->version = 2; header->markerbit = 1; header->type = mountpoint->codecs.audio_pt; header->seq_number = htons(seq); header->timestamp = htonl(ts); header->ssrc = htonl(1); /* The gateway will fix this anyway */ /* Timer */ struct timeval now, before; gettimeofday(&before, NULL); now.tv_sec = before.tv_sec; now.tv_usec = before.tv_usec; time_t passed, d_s, d_us; /* Loop */ gint read = 0; janus_streaming_rtp_relay_packet packet; while(!stopping && !session->stopping && !session->destroy) { /* See if it's time to prepare a frame */ gettimeofday(&now, NULL); d_s = now.tv_sec - before.tv_sec; d_us = now.tv_usec - before.tv_usec; if(d_us < 0) { d_us += 1000000; --d_s; } passed = d_s*1000000 + d_us; if(passed < 18000) { /* Let's wait about 18ms */ usleep(1000); continue; } /* Update the reference time */ before.tv_usec += 20000; if(before.tv_usec > 1000000) { before.tv_sec++; before.tv_usec -= 1000000; } /* If not started or paused, wait some more */ if(!session->started) continue; /* Read frame from file... */ read = fread(buf + RTP_HEADER_SIZE, sizeof(char), 160, audio); if(feof(audio)) { /* FIXME We're doing this forever... should this be configurable? */ JANUS_LOG(LOG_VERB, "Rewind! (%s)\n", source->filename); fseek(audio, 0, SEEK_SET); continue; } if(read < 0) break; if(mountpoint->active == FALSE) mountpoint->active = TRUE; //~ JANUS_LOG(LOG_VERB, " ... Preparing RTP packet (pt=%u, seq=%u, ts=%u)...\n", //~ header->type, ntohs(header->seq_number), ntohl(header->timestamp)); //~ JANUS_LOG(LOG_VERB, " ... Read %d bytes from the audio file...\n", read); /* Relay on all sessions */ packet.data = header; packet.length = RTP_HEADER_SIZE + read; packet.is_video = 0; janus_streaming_relay_rtp_packet(session, &packet); /* Update header */ seq++; header->seq_number = htons(seq); ts += 160; header->timestamp = htonl(ts); header->markerbit = 0; } JANUS_LOG(LOG_VERB, "Leaving filesource thread\n"); g_free(buf); fclose(audio); return NULL; }
/* Plugin creator */ janus_plugin *create(void) { JANUS_LOG(LOG_VERB, "%s created!\n", JANUS_STREAMING_NAME); return &janus_streaming_plugin; }
/* Plugin implementation */ int janus_streaming_init(janus_callbacks *callback, const char *config_path) { if(stopping) { /* Still stopping from before */ return -1; } if(callback == NULL || config_path == NULL) { /* Invalid arguments */ return -1; } /* Read configuration */ char filename[255]; sprintf(filename, "%s/%s.cfg", config_path, JANUS_STREAMING_PACKAGE); JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename); janus_config *config = janus_config_parse(filename); if(config != NULL) janus_config_print(config); mountpoints = g_hash_table_new(NULL, NULL); /* Parse configuration to populate the mountpoints */ if(config != NULL) { janus_config_category *cat = janus_config_get_categories(config); while(cat != NULL) { if(cat->name == NULL) { cat = cat->next; continue; } JANUS_LOG(LOG_VERB, "Adding stream '%s'\n", cat->name); janus_config_item *type = janus_config_get_item(cat, "type"); if(type == NULL || type->value == NULL) { JANUS_LOG(LOG_VERB, " -- Invalid type, skipping stream...\n"); cat = cat->next; continue; } if(!strcasecmp(type->value, "rtp")) { /* RTP live source (e.g., from gstreamer/ffmpeg/vlc/etc.) */ janus_config_item *id = janus_config_get_item(cat, "id"); janus_config_item *desc = janus_config_get_item(cat, "description"); janus_config_item *audio = janus_config_get_item(cat, "audio"); janus_config_item *video = janus_config_get_item(cat, "video"); janus_config_item *aport = janus_config_get_item(cat, "audioport"); janus_config_item *acodec = janus_config_get_item(cat, "audiopt"); janus_config_item *artpmap = janus_config_get_item(cat, "audiortpmap"); janus_config_item *vport = janus_config_get_item(cat, "videoport"); janus_config_item *vcodec = janus_config_get_item(cat, "videopt"); janus_config_item *vrtpmap = janus_config_get_item(cat, "videortpmap"); janus_streaming_mountpoint *live_rtp = calloc(1, sizeof(janus_streaming_mountpoint)); if(live_rtp == NULL) { JANUS_LOG(LOG_FATAL, "Memory error!\n"); continue; } if(id == NULL || id->value == NULL) { JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream, missing mandatory information...\n"); cat = cat->next; continue; } gboolean doaudio = audio && audio->value && !strcasecmp(audio->value, "yes"); gboolean dovideo = video && video->value && !strcasecmp(video->value, "yes"); if(!doaudio && !dovideo) { JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream, no audio or video have to be streamed...\n"); g_free(live_rtp); cat = cat->next; continue; } if(doaudio && (aport == NULL || aport->value == NULL || acodec == NULL || acodec->value == NULL || artpmap == NULL || artpmap->value == NULL)) { JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream, missing mandatory information for audio...\n"); cat = cat->next; continue; } if(dovideo && (vport == NULL || vport->value == NULL || vcodec == NULL || vcodec->value == NULL || vrtpmap == NULL || vrtpmap->value == NULL)) { JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream, missing mandatory information for video...\n"); cat = cat->next; continue; } JANUS_LOG(LOG_VERB, "Audio %s, Video %s\n", doaudio ? "enabled" : "NOT enabled", dovideo ? "enabled" : "NOT enabled"); live_rtp->name = g_strdup(cat->name); live_rtp->id = atoi(id->value); char *description = NULL; if(desc != NULL && desc->value != NULL) description = g_strdup(desc->value); else description = g_strdup(cat->name); live_rtp->description = description; live_rtp->active = FALSE; live_rtp->streaming_type = janus_streaming_type_live; live_rtp->streaming_source = janus_streaming_source_rtp; janus_streaming_rtp_source *live_rtp_source = calloc(1, sizeof(janus_streaming_rtp_source)); if(live_rtp->name == NULL || description == NULL || live_rtp_source == NULL) { JANUS_LOG(LOG_FATAL, "Memory error!\n"); if(live_rtp->name) g_free(live_rtp->name); if(description) g_free(description); if(live_rtp_source); g_free(live_rtp_source); g_free(live_rtp); continue; } live_rtp_source->audio_port = doaudio ? atoi(aport->value) : -1; live_rtp_source->video_port = dovideo ? atoi(vport->value) : -1; live_rtp->source = live_rtp_source; live_rtp->codecs.audio_pt = doaudio ? atoi(acodec->value) : -1; live_rtp->codecs.audio_rtpmap = doaudio ? g_strdup(artpmap->value) : NULL; live_rtp->codecs.video_pt = dovideo ? atoi(vcodec->value) : -1; live_rtp->codecs.video_rtpmap = dovideo ? g_strdup(vrtpmap->value) : NULL; live_rtp->listeners = NULL; g_hash_table_insert(mountpoints, GINT_TO_POINTER(live_rtp->id), live_rtp); g_thread_new(live_rtp->name, &janus_streaming_relay_thread, live_rtp); } else if(!strcasecmp(type->value, "live")) { /* a-Law file live source */ janus_config_item *id = janus_config_get_item(cat, "id"); janus_config_item *desc = janus_config_get_item(cat, "description"); janus_config_item *file = janus_config_get_item(cat, "filename"); janus_config_item *audio = janus_config_get_item(cat, "audio"); janus_config_item *video = janus_config_get_item(cat, "video"); if(id == NULL || id->value == NULL || file == NULL || file->value == NULL) { JANUS_LOG(LOG_ERR, "Can't add 'live' stream, missing mandatory information...\n"); cat = cat->next; continue; } gboolean doaudio = audio && audio->value && !strcasecmp(audio->value, "yes"); gboolean dovideo = video && video->value && !strcasecmp(video->value, "yes"); /* TODO We should support something more than raw a-Law and mu-Law streams... */ if(!doaudio || dovideo) { JANUS_LOG(LOG_ERR, "Can't add 'live' stream, we only support audio file streaming right now...\n"); cat = cat->next; continue; } if(!strstr(file->value, ".alaw") && !strstr(file->value, ".mulaw")) { JANUS_LOG(LOG_ERR, "Can't add 'ondemand' stream, unsupported format (we only support raw mu-Law and a-Law files right now)\n"); cat = cat->next; continue; } janus_streaming_mountpoint *live_file = calloc(1, sizeof(janus_streaming_mountpoint)); if(live_file == NULL) { JANUS_LOG(LOG_FATAL, "Memory error!\n"); continue; } live_file->name = g_strdup(cat->name); live_file->id = atoi(id->value); char *description = NULL; if(desc != NULL && desc->value != NULL) description = g_strdup(desc->value); else description = g_strdup(cat->name); live_file->description = description; live_file->active = FALSE; live_file->streaming_type = janus_streaming_type_live; live_file->streaming_source = janus_streaming_source_file; janus_streaming_file_source *live_file_source = calloc(1, sizeof(janus_streaming_file_source)); if(live_file->name == NULL || description == NULL || live_file_source == NULL) { JANUS_LOG(LOG_FATAL, "Memory error!\n"); if(live_file->name) g_free(live_file->name); if(description) g_free(description); if(live_file_source); g_free(live_file_source); g_free(live_file); continue; } live_file_source->filename = g_strdup(file->value); live_file->source = live_file_source; live_file->codecs.audio_pt = strstr(file->value, ".alaw") ? 8 : 0; live_file->codecs.audio_rtpmap = strstr(file->value, ".alaw") ? "PCMA/8000" : "PCMU/8000"; live_file->codecs.video_pt = -1; /* FIXME We don't support video for this type yet */ live_file->codecs.video_rtpmap = NULL; live_file->listeners = NULL; g_hash_table_insert(mountpoints, GINT_TO_POINTER(live_file->id), live_file); g_thread_new(live_file->name, &janus_streaming_filesource_thread, live_file); } else if(!strcasecmp(type->value, "ondemand")) { /* mu-Law file on demand source */ janus_config_item *id = janus_config_get_item(cat, "id"); janus_config_item *desc = janus_config_get_item(cat, "description"); janus_config_item *file = janus_config_get_item(cat, "filename"); janus_config_item *audio = janus_config_get_item(cat, "audio"); janus_config_item *video = janus_config_get_item(cat, "video"); if(id == NULL || id->value == NULL || file == NULL || file->value == NULL) { JANUS_LOG(LOG_ERR, "Can't add 'ondemand' stream, missing mandatory information...\n"); cat = cat->next; continue; } gboolean doaudio = audio && audio->value && !strcasecmp(audio->value, "yes"); gboolean dovideo = video && video->value && !strcasecmp(video->value, "yes"); /* TODO We should support something more than raw a-Law and mu-Law streams... */ if(!doaudio || dovideo) { JANUS_LOG(LOG_ERR, "Can't add 'ondemand' stream, we only support audio file streaming right now...\n"); cat = cat->next; continue; } if(!strstr(file->value, ".alaw") && !strstr(file->value, ".mulaw")) { JANUS_LOG(LOG_ERR, "Can't add 'ondemand' stream, unsupported format (we only support raw mu-Law and a-Law files right now)\n"); cat = cat->next; continue; } janus_streaming_mountpoint *ondemand_file = calloc(1, sizeof(janus_streaming_mountpoint)); if(ondemand_file == NULL) { JANUS_LOG(LOG_FATAL, "Memory error!\n"); continue; } ondemand_file->name = g_strdup(cat->name); ondemand_file->id = atoi(id->value); char *description = NULL; if(desc != NULL && desc->value != NULL) description = g_strdup(desc->value); else description = g_strdup(cat->name); ondemand_file->description = description; ondemand_file->active = FALSE; ondemand_file->streaming_type = janus_streaming_type_on_demand; ondemand_file->streaming_source = janus_streaming_source_file; janus_streaming_file_source *ondemand_file_source = calloc(1, sizeof(janus_streaming_file_source)); if(ondemand_file->name == NULL || description == NULL || ondemand_file_source == NULL) { JANUS_LOG(LOG_FATAL, "Memory error!\n"); if(ondemand_file->name) g_free(ondemand_file->name); if(description) g_free(description); if(ondemand_file_source); g_free(ondemand_file_source); g_free(ondemand_file); continue; } ondemand_file_source->filename = g_strdup(file->value); ondemand_file->source = ondemand_file_source; ondemand_file->codecs.audio_pt = strstr(file->value, ".alaw") ? 8 : 0; ondemand_file->codecs.audio_rtpmap = strstr(file->value, ".alaw") ? "PCMA/8000" : "PCMU/8000"; ondemand_file->codecs.video_pt = -1; /* FIXME We don't support video for this type yet */ ondemand_file->codecs.video_rtpmap = NULL; ondemand_file->listeners = NULL; g_hash_table_insert(mountpoints, GINT_TO_POINTER(ondemand_file->id), ondemand_file); } cat = cat->next; } /* Done */ janus_config_destroy(config); config = NULL; } /* Show 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; JANUS_LOG(LOG_VERB, " ::: [%"SCNu64"][%s] %s (%s, %s)\n", mp->id, mp->name, mp->description, mp->streaming_type == janus_streaming_type_live ? "live" : "on demand", mp->streaming_source == janus_streaming_source_rtp ? "RTP source" : "file source"); m = m->next; } g_list_free(mountpoints_list); sessions = g_hash_table_new(NULL, NULL); janus_mutex_init(&sessions_mutex); messages = g_queue_new(); /* This is the callback we'll need to invoke to contact the gateway */ gateway = callback; initialized = 1; /* Launch the thread that will handle incoming messages */ GError *error = NULL; handler_thread = g_thread_try_new("janus streaming handler", janus_streaming_handler, NULL, &error); if(error != NULL) { initialized = 0; /* Something went wrong... */ JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch thread...\n", error->code, error->message ? error->message : "??"); return -1; } JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_STREAMING_NAME); return 0; }
int janus_recorder_save_frame(janus_recorder *recorder, char *buffer, uint length) { if(!recorder) return -1; janus_mutex_lock_nodebug(&recorder->mutex); if(!buffer || length < 1) { janus_mutex_unlock_nodebug(&recorder->mutex); return -2; } if(!recorder->file) { janus_mutex_unlock_nodebug(&recorder->mutex); return -3; } if(!recorder->writable) { janus_mutex_unlock_nodebug(&recorder->mutex); return -4; } if(!recorder->header) { /* Write info header as a JSON formatted info */ json_t *info = json_object(); /* FIXME Codecs should be configurable in the future */ const char *type = NULL; if(recorder->type == JANUS_RECORDER_AUDIO) type = "a"; else if(recorder->type == JANUS_RECORDER_VIDEO) type = "v"; else if(recorder->type == JANUS_RECORDER_DATA) type = "d"; json_object_set_new(info, "t", json_string(type)); /* Audio/Video/Data */ json_object_set_new(info, "c", json_string(recorder->codec)); /* Media codec */ json_object_set_new(info, "s", json_integer(recorder->created)); /* Created time */ json_object_set_new(info, "u", json_integer(janus_get_real_time())); /* First frame written time */ gchar *info_text = json_dumps(info, JSON_PRESERVE_ORDER); json_decref(info); uint16_t info_bytes = htons(strlen(info_text)); fwrite(&info_bytes, sizeof(uint16_t), 1, recorder->file); fwrite(info_text, sizeof(char), strlen(info_text), recorder->file); free(info_text); /* Done */ recorder->header = 1; } /* Write frame header */ fwrite(frame_header, sizeof(char), strlen(frame_header), recorder->file); uint16_t header_bytes = htons(recorder->type == JANUS_RECORDER_DATA ? (length+sizeof(gint64)) : length); fwrite(&header_bytes, sizeof(uint16_t), 1, recorder->file); if(recorder->type == JANUS_RECORDER_DATA) { /* If it's data, then we need to prepend timing related info, as it's not there by itself */ gint64 now = htonll(janus_get_real_time()); fwrite(&now, sizeof(gint64), 1, recorder->file); } /* Save packet on file */ int temp = 0, tot = length; while(tot > 0) { temp = fwrite(buffer+length-tot, sizeof(char), tot, recorder->file); if(temp <= 0) { JANUS_LOG(LOG_ERR, "Error saving frame...\n"); janus_mutex_unlock_nodebug(&recorder->mutex); return -5; } tot -= temp; } /* Done */ janus_mutex_unlock_nodebug(&recorder->mutex); return 0; }
static const char *janus_websockets_reason_string(enum lws_callback_reasons reason) { #else static const char *janus_websockets_reason_string(enum libwebsocket_callback_reasons reason) { #endif switch(reason) { CASE_STR(LWS_CALLBACK_ESTABLISHED); CASE_STR(LWS_CALLBACK_CLIENT_CONNECTION_ERROR); CASE_STR(LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH); CASE_STR(LWS_CALLBACK_CLIENT_ESTABLISHED); CASE_STR(LWS_CALLBACK_CLOSED); CASE_STR(LWS_CALLBACK_CLOSED_HTTP); CASE_STR(LWS_CALLBACK_RECEIVE); CASE_STR(LWS_CALLBACK_CLIENT_RECEIVE); CASE_STR(LWS_CALLBACK_CLIENT_RECEIVE_PONG); CASE_STR(LWS_CALLBACK_CLIENT_WRITEABLE); CASE_STR(LWS_CALLBACK_SERVER_WRITEABLE); CASE_STR(LWS_CALLBACK_HTTP); CASE_STR(LWS_CALLBACK_HTTP_BODY); CASE_STR(LWS_CALLBACK_HTTP_BODY_COMPLETION); CASE_STR(LWS_CALLBACK_HTTP_FILE_COMPLETION); CASE_STR(LWS_CALLBACK_HTTP_WRITEABLE); CASE_STR(LWS_CALLBACK_FILTER_NETWORK_CONNECTION); CASE_STR(LWS_CALLBACK_FILTER_HTTP_CONNECTION); CASE_STR(LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED); CASE_STR(LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION); CASE_STR(LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS); CASE_STR(LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS); CASE_STR(LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION); CASE_STR(LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER); CASE_STR(LWS_CALLBACK_CONFIRM_EXTENSION_OKAY); CASE_STR(LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED); CASE_STR(LWS_CALLBACK_PROTOCOL_INIT); CASE_STR(LWS_CALLBACK_PROTOCOL_DESTROY); CASE_STR(LWS_CALLBACK_WSI_CREATE); CASE_STR(LWS_CALLBACK_WSI_DESTROY); CASE_STR(LWS_CALLBACK_GET_THREAD_ID); CASE_STR(LWS_CALLBACK_ADD_POLL_FD); CASE_STR(LWS_CALLBACK_DEL_POLL_FD); CASE_STR(LWS_CALLBACK_CHANGE_MODE_POLL_FD); CASE_STR(LWS_CALLBACK_LOCK_POLL); CASE_STR(LWS_CALLBACK_UNLOCK_POLL); CASE_STR(LWS_CALLBACK_OPENSSL_CONTEXT_REQUIRES_PRIVATE_KEY); CASE_STR(LWS_CALLBACK_USER); default: break; } return NULL; } /* Helper method to return the interface associated with a local IP address */ static char *janus_websockets_get_interface_name(const char *ip) { struct ifaddrs *addrs = NULL, *iap = NULL; getifaddrs(&addrs); for(iap = addrs; iap != NULL; iap = iap->ifa_next) { if(iap->ifa_addr && (iap->ifa_flags & IFF_UP)) { if(iap->ifa_addr->sa_family == AF_INET) { struct sockaddr_in *sa = (struct sockaddr_in *)(iap->ifa_addr); char buffer[16]; inet_ntop(iap->ifa_addr->sa_family, (void *)&(sa->sin_addr), buffer, sizeof(buffer)); if(!strcmp(ip, buffer)) return g_strdup(iap->ifa_name); } else if(iap->ifa_addr->sa_family == AF_INET6) { struct sockaddr_in6 *sa = (struct sockaddr_in6 *)(iap->ifa_addr); char buffer[48]; inet_ntop(iap->ifa_addr->sa_family, (void *)&(sa->sin6_addr), buffer, sizeof(buffer)); if(!strcmp(ip, buffer)) return g_strdup(iap->ifa_name); } } } freeifaddrs(addrs); return NULL; } /* WebSockets ACL list for both Janus and Admin API */ GList *janus_websockets_access_list = NULL, *janus_websockets_admin_access_list = NULL; janus_mutex access_list_mutex; static void janus_websockets_allow_address(const char *ip, gboolean admin) { if(ip == NULL) return; /* Is this an IP or an interface? */ janus_mutex_lock(&access_list_mutex); if(!admin) janus_websockets_access_list = g_list_append(janus_websockets_access_list, (gpointer)ip); else janus_websockets_admin_access_list = g_list_append(janus_websockets_admin_access_list, (gpointer)ip); janus_mutex_unlock(&access_list_mutex); } static gboolean janus_websockets_is_allowed(const char *ip, gboolean admin) { JANUS_LOG(LOG_VERB, "Checking if %s is allowed to contact %s interface\n", ip, admin ? "admin" : "janus"); if(ip == NULL) return FALSE; if(!admin && janus_websockets_access_list == NULL) { JANUS_LOG(LOG_VERB, "Yep\n"); return TRUE; } if(admin && janus_websockets_admin_access_list == NULL) { JANUS_LOG(LOG_VERB, "Yeah\n"); return TRUE; } janus_mutex_lock(&access_list_mutex); GList *temp = admin ? janus_websockets_admin_access_list : janus_websockets_access_list; while(temp) { const char *allowed = (const char *)temp->data; if(allowed != NULL && strstr(ip, allowed)) { janus_mutex_unlock(&access_list_mutex); return TRUE; } temp = temp->next; } janus_mutex_unlock(&access_list_mutex); JANUS_LOG(LOG_VERB, "Nope...\n"); return FALSE; }
janus_recorder *janus_recorder_create(const char *dir, const char *codec, const char *filename) { janus_recorder_medium type = JANUS_RECORDER_AUDIO; if(codec == NULL) { JANUS_LOG(LOG_ERR, "Missing codec information\n"); return NULL; } if(!strcasecmp(codec, "vp8") || !strcasecmp(codec, "vp9") || !strcasecmp(codec, "h264")) { type = JANUS_RECORDER_VIDEO; } else if(!strcasecmp(codec, "opus") || !strcasecmp(codec, "g711") || !strcasecmp(codec, "pcmu") || !strcasecmp(codec, "pcma") || !strcasecmp(codec, "g722")) { type = JANUS_RECORDER_AUDIO; } else if(!strcasecmp(codec, "text")) { /* FIXME We only handle text on data channels, so that's the only thing we can save too */ type = JANUS_RECORDER_DATA; } else { /* We don't recognize the codec: while we might go on anyway, we'd rather fail instead */ JANUS_LOG(LOG_ERR, "Unsupported codec '%s'\n", codec); return NULL; } /* Create the recorder */ janus_recorder *rc = g_malloc0(sizeof(janus_recorder)); rc->dir = NULL; rc->filename = NULL; rc->file = NULL; rc->codec = g_strdup(codec); rc->created = janus_get_real_time(); const char *rec_dir = NULL; const char *rec_file = NULL; char *copy_for_parent = NULL; char *copy_for_base = NULL; /* Check dir and filename values */ if (filename != NULL) { /* Helper copies to avoid overwriting */ copy_for_parent = g_strdup(filename); copy_for_base = g_strdup(filename); /* Get filename parent folder */ const char *filename_parent = dirname(copy_for_parent); /* Get filename base file */ const char *filename_base = basename(copy_for_base); if (!dir) { /* If dir is NULL we have to create filename_parent and filename_base */ rec_dir = filename_parent; rec_file = filename_base; } else { /* If dir is valid we have to create dir and filename*/ rec_dir = dir; rec_file = filename; if (strcasecmp(filename_parent, ".") || strcasecmp(filename_base, filename)) { JANUS_LOG(LOG_WARN, "Unsupported combination of dir and filename %s %s\n", dir, filename); } } } if(rec_dir != NULL) { /* Check if this directory exists, and create it if needed */ struct stat s; int err = stat(rec_dir, &s); if(err == -1) { if(ENOENT == errno) { /* Directory does not exist, try creating it */ if(janus_mkdir(rec_dir, 0755) < 0) { JANUS_LOG(LOG_ERR, "mkdir error: %d\n", errno); return NULL; } } else { JANUS_LOG(LOG_ERR, "stat error: %d\n", errno); return NULL; } } else { if(S_ISDIR(s.st_mode)) { /* Directory exists */ JANUS_LOG(LOG_VERB, "Directory exists: %s\n", rec_dir); } else { /* File exists but it's not a directory? */ JANUS_LOG(LOG_ERR, "Not a directory? %s\n", rec_dir); return NULL; } } } char newname[1024]; memset(newname, 0, 1024); if(rec_file == NULL) { /* Choose a random username */ if(!rec_tempname) { /* Use .mjr as an extension right away */ g_snprintf(newname, 1024, "janus-recording-%"SCNu32".mjr", janus_random_uint32()); } else { /* Append the temporary extension to .mjr, we'll rename when closing */ g_snprintf(newname, 1024, "janus-recording-%"SCNu32".mjr.%s", janus_random_uint32(), rec_tempext); } } else { /* Just append the extension */ if(!rec_tempname) { /* Use .mjr as an extension right away */ g_snprintf(newname, 1024, "%s.mjr", rec_file); } else { /* Append the temporary extension to .mjr, we'll rename when closing */ g_snprintf(newname, 1024, "%s.mjr.%s", rec_file, rec_tempext); } } /* Try opening the file now */ if(rec_dir == NULL) { rc->file = fopen(newname, "wb"); } else { char path[1024]; memset(path, 0, 1024); g_snprintf(path, 1024, "%s/%s", rec_dir, newname); rc->file = fopen(path, "wb"); } if(rc->file == NULL) { JANUS_LOG(LOG_ERR, "fopen error: %d\n", errno); return NULL; } if(rec_dir) rc->dir = g_strdup(rec_dir); rc->filename = g_strdup(newname); rc->type = type; /* Write the first part of the header */ fwrite(header, sizeof(char), strlen(header), rc->file); g_atomic_int_set(&rc->writable, 1); /* We still need to also write the info header first */ g_atomic_int_set(&rc->header, 0); janus_mutex_init(&rc->mutex); /* Done */ g_atomic_int_set(&rc->destroyed, 0); janus_refcount_init(&rc->ref, janus_recorder_free); g_free(copy_for_parent); g_free(copy_for_base); return rc; }
/* 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; }
int janus_websockets_send_message(void *transport, void *request_id, gboolean admin, json_t *message) { if(message == NULL) return -1; if(transport == NULL) { json_decref(message); return -1; } /* Make sure this is not related to a closed /freed WebSocket session */ janus_mutex_lock(&old_wss_mutex); janus_websockets_client *client = (janus_websockets_client *)transport; #ifdef HAVE_LIBWEBSOCKETS_NEWAPI if(g_list_find(old_wss, client) != NULL || !client->wsi) { #else if(g_list_find(old_wss, client) != NULL || !client->context || !client->wsi) { #endif json_decref(message); message = NULL; transport = NULL; janus_mutex_unlock(&old_wss_mutex); return -1; } janus_mutex_lock(&client->mutex); /* Convert to string and enqueue */ char *payload = json_dumps(message, json_format); g_async_queue_push(client->messages, payload); #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_callback_on_writable(client->wsi); #else libwebsocket_callback_on_writable(client->context, client->wsi); #endif janus_mutex_unlock(&client->mutex); janus_mutex_unlock(&old_wss_mutex); json_decref(message); return 0; } void janus_websockets_session_created(void *transport, guint64 session_id) { /* We don't care */ } void janus_websockets_session_over(void *transport, guint64 session_id, gboolean timeout) { if(transport == NULL || !timeout) return; /* We only care if it's a timeout: if so, close the connection */ janus_websockets_client *client = (janus_websockets_client *)transport; /* Make sure this is not related to a closed WebSocket session */ janus_mutex_lock(&old_wss_mutex); #ifdef HAVE_LIBWEBSOCKETS_NEWAPI if(g_list_find(old_wss, client) == NULL && client->wsi){ #else if(g_list_find(old_wss, client) == NULL && client->context && client->wsi){ #endif janus_mutex_lock(&client->mutex); client->session_timeout = 1; #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_callback_on_writable(client->wsi); #else libwebsocket_callback_on_writable(client->context, client->wsi); #endif janus_mutex_unlock(&client->mutex); } janus_mutex_unlock(&old_wss_mutex); } /* Thread */ void *janus_websockets_thread(void *data) { #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws_context *service = (struct lws_context *)data; #else struct libwebsocket_context *service = (struct libwebsocket_context *)data; #endif if(service == NULL) { JANUS_LOG(LOG_ERR, "Invalid service\n"); return NULL; } const char *type = NULL; if(service == wss) type = "WebSocket (Janus API)"; else if(service == swss) type = "Secure WebSocket (Janus API)"; else if(service == admin_wss) type = "WebSocket (Admin API)"; else if(service == admin_swss) type = "Secure WebSocket (Admin API)"; JANUS_LOG(LOG_INFO, "%s thread started\n", type); while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) { /* libwebsockets is single thread, we cycle through events here */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_service(service, 50); #else libwebsocket_service(service, 50); #endif } /* Get rid of the WebSockets server */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_cancel_service(service); #else libwebsocket_cancel_service(service); #endif /* Done */ JANUS_LOG(LOG_INFO, "%s thread ended\n", type); return NULL; } /* WebSockets */ static int janus_websockets_callback_http( #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws *wsi, enum lws_callback_reasons reason, #else struct libwebsocket_context *this, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, #endif void *user, void *in, size_t len) { /* This endpoint cannot be used for HTTP */ switch(reason) { case LWS_CALLBACK_HTTP: JANUS_LOG(LOG_VERB, "Rejecting incoming HTTP request on WebSockets endpoint\n"); #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_return_http_status(wsi, 403, NULL); #else libwebsockets_return_http_status(this, wsi, 403, NULL); #endif /* Close and free connection */ return -1; case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: if (!in) { JANUS_LOG(LOG_VERB, "Rejecting incoming HTTP request on WebSockets endpoint: no sub-protocol specified\n"); return -1; } break; default: break; } return 0; } static int janus_websockets_callback_https( #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws *wsi, enum lws_callback_reasons reason, #else struct libwebsocket_context *this, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, #endif void *user, void *in, size_t len) { /* We just forward the event to the HTTP handler */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI return janus_websockets_callback_http(wsi, reason, user, in, len); #else return janus_websockets_callback_http(this, wsi, reason, user, in, len); #endif } /* This callback handles Janus API requests */ static int janus_websockets_common_callback( #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws *wsi, enum lws_callback_reasons reason, #else struct libwebsocket_context *this, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, #endif void *user, void *in, size_t len, gboolean admin) { const char *log_prefix = admin ? "AdminWSS" : "WSS"; janus_websockets_client *ws_client = (janus_websockets_client *)user; switch(reason) { case LWS_CALLBACK_ESTABLISHED: { /* Is there any filtering we should apply? */ char name[256], ip[256]; #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), name, 256, ip, 256); #else libwebsockets_get_peer_addresses(this, wsi, libwebsocket_get_socket_fd(wsi), name, 256, ip, 256); #endif JANUS_LOG(LOG_VERB, "[%s-%p] WebSocket connection opened from %s by %s\n", log_prefix, wsi, ip, name); if(!janus_websockets_is_allowed(ip, admin)) { JANUS_LOG(LOG_ERR, "[%s-%p] IP %s is unauthorized to connect to the WebSockets %s API interface\n", log_prefix, wsi, ip, admin ? "Admin" : "Janus"); /* Close the connection */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_callback_on_writable(wsi); #else libwebsocket_callback_on_writable(this, wsi); #endif return -1; } JANUS_LOG(LOG_VERB, "[%s-%p] WebSocket connection accepted\n", log_prefix, wsi); if(ws_client == NULL) { JANUS_LOG(LOG_ERR, "[%s-%p] Invalid WebSocket client instance...\n", log_prefix, wsi); return -1; } /* Clean the old sessions list, in case this pointer was used before */ janus_mutex_lock(&old_wss_mutex); if(g_list_find(old_wss, ws_client) != NULL) old_wss = g_list_remove(old_wss, ws_client); janus_mutex_unlock(&old_wss_mutex); /* Prepare the session */ #ifndef HAVE_LIBWEBSOCKETS_NEWAPI ws_client->context = this; #endif ws_client->wsi = wsi; ws_client->messages = g_async_queue_new(); ws_client->buffer = NULL; ws_client->buflen = 0; ws_client->bufpending = 0; ws_client->bufoffset = 0; ws_client->session_timeout = 0; ws_client->destroy = 0; janus_mutex_init(&ws_client->mutex); /* Let us know when the WebSocket channel becomes writeable */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_callback_on_writable(wsi); #else libwebsocket_callback_on_writable(this, wsi); #endif JANUS_LOG(LOG_VERB, "[%s-%p] -- Ready to be used!\n", log_prefix, wsi); /* Notify handlers about this new transport */ if(notify_events && gateway->events_is_enabled()) { json_t *info = json_object(); json_object_set_new(info, "event", json_string("connected")); json_object_set_new(info, "admin_api", admin ? json_true() : json_false()); json_object_set_new(info, "ip", json_string(ip)); gateway->notify_event(&janus_websockets_transport, ws_client, info); } return 0; } case LWS_CALLBACK_RECEIVE: { JANUS_LOG(LOG_HUGE, "[%s-%p] Got %zu bytes:\n", log_prefix, wsi, len); #ifdef HAVE_LIBWEBSOCKETS_NEWAPI if(ws_client == NULL || ws_client->wsi == NULL) { #else if(ws_client == NULL || ws_client->context == NULL || ws_client->wsi == NULL) { #endif JANUS_LOG(LOG_ERR, "[%s-%p] Invalid WebSocket client instance...\n", log_prefix, wsi); return -1; } /* Is this a new message, or part of a fragmented one? */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI const size_t remaining = lws_remaining_packet_payload(wsi); #else const size_t remaining = libwebsockets_remaining_packet_payload(wsi); #endif if(ws_client->incoming == NULL) { JANUS_LOG(LOG_HUGE, "[%s-%p] First fragment: %zu bytes, %zu remaining\n", log_prefix, wsi, len, remaining); ws_client->incoming = g_malloc0(len+1); memcpy(ws_client->incoming, in, len); ws_client->incoming[len] = '\0'; JANUS_LOG(LOG_HUGE, "%s\n", ws_client->incoming); } else { size_t offset = strlen(ws_client->incoming); JANUS_LOG(LOG_HUGE, "[%s-%p] Appending fragment: offset %zu, %zu bytes, %zu remaining\n", log_prefix, wsi, offset, len, remaining); ws_client->incoming = g_realloc(ws_client->incoming, offset+len+1); memcpy(ws_client->incoming+offset, in, len); ws_client->incoming[offset+len] = '\0'; JANUS_LOG(LOG_HUGE, "%s\n", ws_client->incoming+offset); } #ifdef HAVE_LIBWEBSOCKETS_NEWAPI if(remaining > 0 || !lws_is_final_fragment(wsi)) { #else if(remaining > 0 || !libwebsocket_is_final_fragment(wsi)) { #endif /* Still waiting for some more fragments */ JANUS_LOG(LOG_HUGE, "[%s-%p] Waiting for more fragments\n", log_prefix, wsi); return 0; } JANUS_LOG(LOG_HUGE, "[%s-%p] Done, parsing message: %zu bytes\n", log_prefix, wsi, strlen(ws_client->incoming)); /* If we got here, the message is complete: parse the JSON payload */ json_error_t error; json_t *root = json_loads(ws_client->incoming, 0, &error); g_free(ws_client->incoming); ws_client->incoming = NULL; /* Notify the core, passing both the object and, since it may be needed, the error */ gateway->incoming_request(&janus_websockets_transport, ws_client, NULL, admin, root, &error); return 0; } case LWS_CALLBACK_SERVER_WRITEABLE: { #ifdef HAVE_LIBWEBSOCKETS_NEWAPI if(ws_client == NULL || ws_client->wsi == NULL) { #else if(ws_client == NULL || ws_client->context == NULL || ws_client->wsi == NULL) { #endif JANUS_LOG(LOG_ERR, "[%s-%p] Invalid WebSocket client instance...\n", log_prefix, wsi); return -1; } if(!ws_client->destroy && !g_atomic_int_get(&stopping)) { janus_mutex_lock(&ws_client->mutex); /* Check if we have a pending/partial write to complete first */ if(ws_client->buffer && ws_client->bufpending > 0 && ws_client->bufoffset > 0 && !ws_client->destroy && !g_atomic_int_get(&stopping)) { JANUS_LOG(LOG_HUGE, "[%s-%p] Completing pending WebSocket write (still need to write last %d bytes)...\n", log_prefix, wsi, ws_client->bufpending); #ifdef HAVE_LIBWEBSOCKETS_NEWAPI int sent = lws_write(wsi, ws_client->buffer + ws_client->bufoffset, ws_client->bufpending, LWS_WRITE_TEXT); #else int sent = libwebsocket_write(wsi, ws_client->buffer + ws_client->bufoffset, ws_client->bufpending, LWS_WRITE_TEXT); #endif JANUS_LOG(LOG_HUGE, "[%s-%p] -- Sent %d/%d bytes\n", log_prefix, wsi, sent, ws_client->bufpending); if(sent > -1 && sent < ws_client->bufpending) { /* We still couldn't send everything that was left, we'll try and complete this in the next round */ ws_client->bufpending -= sent; ws_client->bufoffset += sent; } else { /* Clear the pending/partial write queue */ ws_client->bufpending = 0; ws_client->bufoffset = 0; } /* Done for this round, check the next response/notification later */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_callback_on_writable(wsi); #else libwebsocket_callback_on_writable(this, wsi); #endif janus_mutex_unlock(&ws_client->mutex); return 0; } /* Shoot all the pending messages */ char *response = g_async_queue_try_pop(ws_client->messages); if(response && !ws_client->destroy && !g_atomic_int_get(&stopping)) { /* Gotcha! */ int buflen = LWS_SEND_BUFFER_PRE_PADDING + strlen(response) + LWS_SEND_BUFFER_POST_PADDING; if(ws_client->buffer == NULL) { /* Let's allocate a shared buffer */ JANUS_LOG(LOG_HUGE, "[%s-%p] Allocating %d bytes (response is %zu bytes)\n", log_prefix, wsi, buflen, strlen(response)); ws_client->buflen = buflen; ws_client->buffer = g_malloc0(buflen); } else if(buflen > ws_client->buflen) { /* We need a larger shared buffer */ JANUS_LOG(LOG_HUGE, "[%s-%p] Re-allocating to %d bytes (was %d, response is %zu bytes)\n", log_prefix, wsi, buflen, ws_client->buflen, strlen(response)); ws_client->buflen = buflen; ws_client->buffer = g_realloc(ws_client->buffer, buflen); } memcpy(ws_client->buffer + LWS_SEND_BUFFER_PRE_PADDING, response, strlen(response)); JANUS_LOG(LOG_HUGE, "[%s-%p] Sending WebSocket message (%zu bytes)...\n", log_prefix, wsi, strlen(response)); #ifdef HAVE_LIBWEBSOCKETS_NEWAPI int sent = lws_write(wsi, ws_client->buffer + LWS_SEND_BUFFER_PRE_PADDING, strlen(response), LWS_WRITE_TEXT); #else int sent = libwebsocket_write(wsi, ws_client->buffer + LWS_SEND_BUFFER_PRE_PADDING, strlen(response), LWS_WRITE_TEXT); #endif JANUS_LOG(LOG_HUGE, "[%s-%p] -- Sent %d/%zu bytes\n", log_prefix, wsi, sent, strlen(response)); if(sent > -1 && sent < (int)strlen(response)) { /* We couldn't send everything in a single write, we'll complete this in the next round */ ws_client->bufpending = strlen(response) - sent; ws_client->bufoffset = LWS_SEND_BUFFER_PRE_PADDING + sent; JANUS_LOG(LOG_HUGE, "[%s-%p] -- Couldn't write all bytes (%d missing), setting offset %d\n", log_prefix, wsi, ws_client->bufpending, ws_client->bufoffset); } /* We can get rid of the message */ free(response); /* Done for this round, check the next response/notification later */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_callback_on_writable(wsi); #else libwebsocket_callback_on_writable(this, wsi); #endif janus_mutex_unlock(&ws_client->mutex); return 0; } janus_mutex_unlock(&ws_client->mutex); } return 0; } case LWS_CALLBACK_CLOSED: { JANUS_LOG(LOG_VERB, "[%s-%p] WS connection down, closing\n", log_prefix, wsi); janus_websockets_destroy_client(ws_client, wsi, log_prefix); JANUS_LOG(LOG_VERB, "[%s-%p] -- closed\n", log_prefix, wsi); return 0; } case LWS_CALLBACK_WSI_DESTROY: { JANUS_LOG(LOG_VERB, "[%s-%p] WS connection down, destroying\n", log_prefix, wsi); janus_websockets_destroy_client(ws_client, wsi, log_prefix); JANUS_LOG(LOG_VERB, "[%s-%p] -- destroyed\n", log_prefix, wsi); return 0; } default: if(wsi != NULL) { JANUS_LOG(LOG_HUGE, "[%s-%p] %d (%s)\n", log_prefix, wsi, reason, janus_websockets_reason_string(reason)); } else { JANUS_LOG(LOG_HUGE, "[%s] %d (%s)\n", log_prefix, reason, janus_websockets_reason_string(reason)); } break; } return 0; } /* This callback handles Janus API requests */ static int janus_websockets_callback( #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws *wsi, enum lws_callback_reasons reason, #else struct libwebsocket_context *this, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, #endif void *user, void *in, size_t len) { #ifdef HAVE_LIBWEBSOCKETS_NEWAPI return janus_websockets_common_callback(wsi, reason, user, in, len, FALSE); #else return janus_websockets_common_callback(this, wsi, reason, user, in, len, FALSE); #endif } static int janus_websockets_callback_secure( #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws *wsi, enum lws_callback_reasons reason, #else struct libwebsocket_context *this, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, #endif void *user, void *in, size_t len) { /* We just forward the event to the Janus API handler */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI return janus_websockets_callback(wsi, reason, user, in, len); #else return janus_websockets_callback(this, wsi, reason, user, in, len); #endif } /* This callback handles Admin API requests */ static int janus_websockets_admin_callback( #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws *wsi, enum lws_callback_reasons reason, #else struct libwebsocket_context *this, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, #endif void *user, void *in, size_t len) { #ifdef HAVE_LIBWEBSOCKETS_NEWAPI return janus_websockets_common_callback(wsi, reason, user, in, len, TRUE); #else return janus_websockets_common_callback(this, wsi, reason, user, in, len, TRUE); #endif } static int janus_websockets_admin_callback_secure( #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws *wsi, enum lws_callback_reasons reason, #else struct libwebsocket_context *this, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, #endif void *user, void *in, size_t len) { /* We just forward the event to the Admin API handler */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI return janus_websockets_admin_callback(wsi, reason, user, in, len); #else return janus_websockets_admin_callback(this, wsi, reason, user, in, len); #endif }
void janus_websockets_destroy(void) { if(!g_atomic_int_get(&initialized)) return; g_atomic_int_set(&stopping, 1); /* Stop the service threads */ if(wss_thread != NULL) { g_thread_join(wss_thread); wss_thread = NULL; } if(swss_thread != NULL) { g_thread_join(swss_thread); swss_thread = NULL; } if(admin_wss_thread != NULL) { g_thread_join(admin_wss_thread); admin_wss_thread = NULL; } if(admin_swss_thread != NULL) { g_thread_join(admin_swss_thread); admin_swss_thread = NULL; } /* Destroy the contexts */ if(wss != NULL) { #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_context_destroy(wss); #else libwebsocket_context_destroy(wss); #endif wss = NULL; } if(swss != NULL) { #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_context_destroy(swss); #else libwebsocket_context_destroy(swss); #endif swss = NULL; } if(admin_wss != NULL) { #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_context_destroy(admin_wss); #else libwebsocket_context_destroy(admin_wss); #endif admin_wss = NULL; } if(admin_swss != NULL) { #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_context_destroy(admin_swss); #else libwebsocket_context_destroy(admin_swss); #endif admin_swss = NULL; } g_atomic_int_set(&initialized, 0); g_atomic_int_set(&stopping, 0); JANUS_LOG(LOG_INFO, "%s destroyed!\n", JANUS_WEBSOCKETS_NAME); }
/* Transport implementation */ int janus_websockets_init(janus_transport_callbacks *callback, const char *config_path) { if(g_atomic_int_get(&stopping)) { /* Still stopping from before */ return -1; } if(callback == NULL || config_path == NULL) { /* Invalid arguments */ return -1; } /* This is the callback we'll need to invoke to contact the gateway */ gateway = callback; #ifdef HAVE_LIBWEBSOCKETS_NEWAPI JANUS_LOG(LOG_INFO, "libwebsockets >= 1.6 available, using new API\n"); #else JANUS_LOG(LOG_INFO, "libwebsockets < 1.6 available, using old API\n"); #endif /* Read configuration */ char filename[255]; g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_WEBSOCKETS_PACKAGE); JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename); janus_config *config = janus_config_parse(filename); if(config != NULL) { janus_config_print(config); /* Handle configuration */ janus_config_item *item = janus_config_get_item_drilldown(config, "general", "json"); if(item && item->value) { /* Check how we need to format/serialize the JSON output */ if(!strcasecmp(item->value, "indented")) { /* Default: indented, we use three spaces for that */ json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER; } else if(!strcasecmp(item->value, "plain")) { /* Not indented and no new lines, but still readable */ json_format = JSON_INDENT(0) | JSON_PRESERVE_ORDER; } else if(!strcasecmp(item->value, "compact")) { /* Compact, so no spaces between separators */ json_format = JSON_COMPACT | JSON_PRESERVE_ORDER; } else { JANUS_LOG(LOG_WARN, "Unsupported JSON format option '%s', using default (indented)\n", item->value); json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER; } } /* Check if we need to send events to handlers */ janus_config_item *events = janus_config_get_item_drilldown(config, "general", "events"); if(events != NULL && events->value != NULL) notify_events = janus_is_true(events->value); if(!notify_events && callback->events_is_enabled()) { JANUS_LOG(LOG_WARN, "Notification of events to handlers disabled for %s\n", JANUS_WEBSOCKETS_NAME); } item = janus_config_get_item_drilldown(config, "general", "ws_logging"); if(item && item->value) { ws_log_level = atoi(item->value); if(ws_log_level < 0) ws_log_level = 0; } JANUS_LOG(LOG_VERB, "libwebsockets logging: %d\n", ws_log_level); lws_set_log_level(ws_log_level, NULL); old_wss = NULL; janus_mutex_init(&old_wss_mutex); /* Any ACL for either the Janus or Admin API? */ item = janus_config_get_item_drilldown(config, "general", "ws_acl"); if(item && item->value) { gchar **list = g_strsplit(item->value, ",", -1); gchar *index = list[0]; if(index != NULL) { int i=0; while(index != NULL) { if(strlen(index) > 0) { JANUS_LOG(LOG_INFO, "Adding '%s' to the Janus API allowed list...\n", index); janus_websockets_allow_address(g_strdup(index), FALSE); } i++; index = list[i]; } } g_strfreev(list); list = NULL; } item = janus_config_get_item_drilldown(config, "admin", "admin_ws_acl"); if(item && item->value) { gchar **list = g_strsplit(item->value, ",", -1); gchar *index = list[0]; if(index != NULL) { int i=0; while(index != NULL) { if(strlen(index) > 0) { JANUS_LOG(LOG_INFO, "Adding '%s' to the Admin/monitor allowed list...\n", index); janus_websockets_allow_address(g_strdup(index), TRUE); } i++; index = list[i]; } } g_strfreev(list); list = NULL; } /* Setup the Janus API WebSockets server(s) */ item = janus_config_get_item_drilldown(config, "general", "ws"); if(!item || !item->value || !janus_is_true(item->value)) { JANUS_LOG(LOG_WARN, "WebSockets server disabled\n"); } else { int wsport = 8188; item = janus_config_get_item_drilldown(config, "general", "ws_port"); if(item && item->value) wsport = atoi(item->value); char *interface = NULL; item = janus_config_get_item_drilldown(config, "general", "ws_interface"); if(item && item->value) interface = (char *)item->value; char *ip = NULL; item = janus_config_get_item_drilldown(config, "general", "ws_ip"); if(item && item->value) { ip = (char *)item->value; char *iface = janus_websockets_get_interface_name(ip); if(iface == NULL) { JANUS_LOG(LOG_WARN, "No interface associated with %s? Falling back to no interface...\n", ip); } ip = iface; } /* Prepare context */ struct lws_context_creation_info info; memset(&info, 0, sizeof info); info.port = wsport; info.iface = ip ? ip : interface; info.protocols = wss_protocols; #ifdef HAVE_LIBWEBSOCKETS_NEWAPI info.extensions = NULL; #else info.extensions = libwebsocket_get_internal_extensions(); #endif info.ssl_cert_filepath = NULL; info.ssl_private_key_filepath = NULL; info.gid = -1; info.uid = -1; info.options = 0; /* Create the WebSocket context */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI wss = lws_create_context(&info); #else wss = libwebsocket_create_context(&info); #endif if(wss == NULL) { JANUS_LOG(LOG_FATAL, "Error initializing libwebsockets...\n"); } else { JANUS_LOG(LOG_INFO, "WebSockets server started (port %d)...\n", wsport); } g_free(ip); } item = janus_config_get_item_drilldown(config, "general", "wss"); if(!item || !item->value || !janus_is_true(item->value)) { JANUS_LOG(LOG_WARN, "Secure WebSockets server disabled\n"); } else { int wsport = 8989; item = janus_config_get_item_drilldown(config, "general", "wss_port"); if(item && item->value) wsport = atoi(item->value); char *interface = NULL; item = janus_config_get_item_drilldown(config, "general", "wss_interface"); if(item && item->value) interface = (char *)item->value; char *ip = NULL; item = janus_config_get_item_drilldown(config, "general", "wss_ip"); if(item && item->value) { ip = (char *)item->value; char *iface = janus_websockets_get_interface_name(ip); if(iface == NULL) { JANUS_LOG(LOG_WARN, "No interface associated with %s? Falling back to no interface...\n", ip); } ip = iface; } item = janus_config_get_item_drilldown(config, "certificates", "cert_pem"); if(!item || !item->value) { JANUS_LOG(LOG_FATAL, "Missing certificate/key path\n"); } else { char *server_pem = (char *)item->value; char *server_key = (char *)item->value; item = janus_config_get_item_drilldown(config, "certificates", "cert_key"); if(item && item->value) server_key = (char *)item->value; JANUS_LOG(LOG_VERB, "Using certificates:\n\t%s\n\t%s\n", server_pem, server_key); /* Prepare secure context */ struct lws_context_creation_info info; memset(&info, 0, sizeof info); info.port = wsport; info.iface = ip ? ip : interface; info.protocols = swss_protocols; #ifdef HAVE_LIBWEBSOCKETS_NEWAPI info.extensions = NULL; #else info.extensions = libwebsocket_get_internal_extensions(); #endif info.ssl_cert_filepath = server_pem; info.ssl_private_key_filepath = server_key; info.gid = -1; info.uid = -1; #if LWS_LIBRARY_VERSION_MAJOR >= 2 info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; #else info.options = 0; #endif /* Create the secure WebSocket context */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI swss = lws_create_context(&info); #else swss = libwebsocket_create_context(&info); #endif if(swss == NULL) { JANUS_LOG(LOG_FATAL, "Error initializing libwebsockets...\n"); } else { JANUS_LOG(LOG_INFO, "Secure WebSockets server started (port %d)...\n", wsport); } g_free(ip); } } /* Do the same for the Admin API, if enabled */ item = janus_config_get_item_drilldown(config, "admin", "admin_ws"); if(!item || !item->value || !janus_is_true(item->value)) { JANUS_LOG(LOG_WARN, "Admin WebSockets server disabled\n"); } else { int wsport = 7188; item = janus_config_get_item_drilldown(config, "admin", "admin_ws_port"); if(item && item->value) wsport = atoi(item->value); char *interface = NULL; item = janus_config_get_item_drilldown(config, "admin", "admin_ws_interface"); if(item && item->value) interface = (char *)item->value; char *ip = NULL; item = janus_config_get_item_drilldown(config, "admin", "admin_ws_ip"); if(item && item->value) { ip = (char *)item->value; char *iface = janus_websockets_get_interface_name(ip); if(iface == NULL) { JANUS_LOG(LOG_WARN, "No interface associated with %s? Falling back to no interface...\n", ip); } ip = iface; } /* Prepare context */ struct lws_context_creation_info info; memset(&info, 0, sizeof info); info.port = wsport; info.iface = ip ? ip : interface; info.protocols = admin_wss_protocols; #ifdef HAVE_LIBWEBSOCKETS_NEWAPI info.extensions = NULL; #else info.extensions = libwebsocket_get_internal_extensions(); #endif info.ssl_cert_filepath = NULL; info.ssl_private_key_filepath = NULL; info.gid = -1; info.uid = -1; info.options = 0; /* Create the WebSocket context */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI admin_wss = lws_create_context(&info); #else admin_wss = libwebsocket_create_context(&info); #endif if(admin_wss == NULL) { JANUS_LOG(LOG_FATAL, "Error initializing libwebsockets...\n"); } else { JANUS_LOG(LOG_INFO, "Admin WebSockets server started (port %d)...\n", wsport); } g_free(ip); } item = janus_config_get_item_drilldown(config, "admin", "admin_wss"); if(!item || !item->value || !janus_is_true(item->value)) { JANUS_LOG(LOG_WARN, "Secure Admin WebSockets server disabled\n"); } else { int wsport = 7989; item = janus_config_get_item_drilldown(config, "admin", "admin_wss_port"); if(item && item->value) wsport = atoi(item->value); char *interface = NULL; item = janus_config_get_item_drilldown(config, "admin", "admin_wss_interface"); if(item && item->value) interface = (char *)item->value; char *ip = NULL; item = janus_config_get_item_drilldown(config, "admin", "admin_wss_ip"); if(item && item->value) { ip = (char *)item->value; char *iface = janus_websockets_get_interface_name(ip); if(iface == NULL) { JANUS_LOG(LOG_WARN, "No interface associated with %s? Falling back to no interface...\n", ip); } ip = iface; } item = janus_config_get_item_drilldown(config, "certificates", "cert_pem"); if(!item || !item->value) { JANUS_LOG(LOG_FATAL, "Missing certificate/key path\n"); } else { char *server_pem = (char *)item->value; char *server_key = (char *)item->value; item = janus_config_get_item_drilldown(config, "certificates", "cert_key"); if(item && item->value) server_key = (char *)item->value; JANUS_LOG(LOG_VERB, "Using certificates:\n\t%s\n\t%s\n", server_pem, server_key); /* Prepare secure context */ struct lws_context_creation_info info; memset(&info, 0, sizeof info); info.port = wsport; info.iface = ip ? ip : interface; info.protocols = admin_swss_protocols; #ifdef HAVE_LIBWEBSOCKETS_NEWAPI info.extensions = NULL; #else info.extensions = libwebsocket_get_internal_extensions(); #endif info.ssl_cert_filepath = server_pem; info.ssl_private_key_filepath = server_key; info.gid = -1; info.uid = -1; #if LWS_LIBRARY_VERSION_MAJOR >= 2 info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; #else info.options = 0; #endif /* Create the secure WebSocket context */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI admin_swss = lws_create_context(&info); #else admin_swss = libwebsocket_create_context(&info); #endif if(admin_swss == NULL) { JANUS_LOG(LOG_FATAL, "Error initializing libwebsockets...\n"); } else { JANUS_LOG(LOG_INFO, "Secure Admin WebSockets server started (port %d)...\n", wsport); } g_free(ip); } } } janus_config_destroy(config); config = NULL; if(!wss && !swss && !admin_wss && !admin_swss) { JANUS_LOG(LOG_FATAL, "No WebSockets server started, giving up...\n"); return -1; /* No point in keeping the plugin loaded */ } wss_janus_api_enabled = wss || swss; wss_admin_api_enabled = admin_wss || admin_swss; GError *error = NULL; /* Start the WebSocket service threads */ if(wss != NULL) { wss_thread = g_thread_try_new("ws thread", &janus_websockets_thread, wss, &error); if(!wss_thread) { g_atomic_int_set(&initialized, 0); JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the WebSockets thread...\n", error->code, error->message ? error->message : "??"); return -1; } } if(swss != NULL) { swss_thread = g_thread_try_new("sws thread", &janus_websockets_thread, swss, &error); if(!swss_thread) { g_atomic_int_set(&initialized, 0); JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the Secure WebSockets thread...\n", error->code, error->message ? error->message : "??"); return -1; } } if(admin_wss != NULL) { admin_wss_thread = g_thread_try_new("admin ws thread", &janus_websockets_thread, admin_wss, &error); if(!admin_wss_thread) { g_atomic_int_set(&initialized, 0); JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the Admin WebSockets thread...\n", error->code, error->message ? error->message : "??"); return -1; } } if(admin_swss != NULL) { admin_swss_thread = g_thread_try_new("admin sws thread", &janus_websockets_thread, admin_swss, &error); if(!admin_swss_thread) { g_atomic_int_set(&initialized, 0); JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the Secure Admin WebSockets thread...\n", error->code, error->message ? error->message : "??"); return -1; } } /* Done */ g_atomic_int_set(&initialized, 1); JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_WEBSOCKETS_NAME); return 0; }
/* Plugin creator */ janus_plugin *create(void) { JANUS_LOG(LOG_VERB, "%s created!\n", JANUS_SERIAL_NAME); return &janus_serial_plugin; }
gboolean janus_config_save(janus_config *config, const char *folder, const char *filename) { if(config == NULL) return -1; FILE *file = NULL; char path[1024]; if(folder != NULL) { /* Create folder, if needed */ if(janus_mkdir(folder, 0755) < 0) { JANUS_LOG(LOG_ERR, "Couldn't save configuration file, error creating folder '%s'...\n", folder); return -2; } g_snprintf(path, 1024, "%s/%s.cfg", folder, filename); } else { g_snprintf(path, 1024, "%s.cfg", filename); } file = fopen(path, "wt"); if(file == NULL) { JANUS_LOG(LOG_ERR, "Couldn't save configuration file, error opening file '%s'...\n", path); return -3; } /* Print a header */ char date[64], header[256]; struct tm tmresult; time_t ltime = time(NULL); localtime_r(<ime, &tmresult); strftime(date, sizeof(date), "%a %b %e %T %Y", &tmresult); g_snprintf(header, 256, ";\n; File automatically generated on %s\n;\n\n", date); fwrite(header, sizeof(char), strlen(header), file); /* Go on with the configuration */ if(config->items) { GList *l = config->items; while(l) { janus_config_item *i = (janus_config_item *)l->data; if(i->name && i->value) { fwrite(i->name, sizeof(char), strlen(i->name), file); fwrite(" = ", sizeof(char), 3, file); fwrite(i->value, sizeof(char), strlen(i->value), file); fwrite("\n", sizeof(char), 1, file); } l = l->next; } } if(config->categories) { GList *l = config->categories; while(l) { janus_config_category *c = (janus_config_category *)l->data; if(c->name) { fwrite("[", sizeof(char), 1, file); fwrite(c->name, sizeof(char), strlen(c->name), file); fwrite("]\n", sizeof(char), 2, file); if(c->items) { GList *li = c->items; while(li) { janus_config_item *i = (janus_config_item *)li->data; if(i->name && i->value) { fwrite(i->name, sizeof(char), strlen(i->name), file); fwrite(" = ", sizeof(char), 3, file); /* If the value contains a semicolon, escape it */ if(strchr(i->value, ';')) { char *value = g_strdup(i->value); value = janus_string_replace((char *)value, ";", "\\;"); fwrite(value, sizeof(char), strlen(value), file); fwrite("\n", sizeof(char), 1, file); g_free(value); } else { /* No need to escape */ fwrite(i->value, sizeof(char), strlen(i->value), file); fwrite("\n", sizeof(char), 1, file); } } li = li->next; } } } fwrite("\r\n", sizeof(char), 2, file); l = l->next; } } fclose(file); return 0; }
/* Main Code */ int main(int argc, char *argv[]) { janus_log_init(FALSE, TRUE, NULL); /* Check the JANUS_PPREC_DEBUG environment variable for the debugging level */ if(g_getenv("JANUS_PPREC_DEBUG") != NULL) { int val = atoi(g_getenv("JANUS_PPREC_DEBUG")); if(val > 0 && val < LOG_MAX) janus_log_level = val; JANUS_LOG(LOG_INFO, "Logging level: %d\n", janus_log_level); } /* Evaluate arguments */ if(argc != 3) { JANUS_LOG(LOG_INFO, "Usage: %s source.mjr destination.[opus|webm]\n", argv[0]); JANUS_LOG(LOG_INFO, " %s --header source.mjr\n", argv[0]); return -1; } char *source = NULL, *destination = NULL; if(!strcmp(argv[1], "--header")) { /* Only parse the .mjr header */ source = argv[2]; } else { /* Post-process the .mjr recording */ source = argv[1]; destination = argv[2]; JANUS_LOG(LOG_INFO, "%s --> %s\n", source, destination); } FILE *file = fopen(source, "rb"); if(file == NULL) { JANUS_LOG(LOG_ERR, "Could not open file %s\n", source); return -1; } fseek(file, 0L, SEEK_END); long fsize = ftell(file); fseek(file, 0L, SEEK_SET); JANUS_LOG(LOG_INFO, "File is %zu bytes\n", fsize); /* Pre-parse */ JANUS_LOG(LOG_INFO, "Pre-parsing file to generate ordered index...\n"); gboolean parsed_header = FALSE; int video = 0; gint64 c_time = 0, w_time = 0; int bytes = 0, skip = 0; long offset = 0; uint16_t len = 0, count = 0; uint32_t first_ts = 0, last_ts = 0, reset = 0; /* To handle whether there's a timestamp reset in the recording */ char prebuffer[1500]; memset(prebuffer, 0, 1500); /* Let's look for timestamp resets first */ while(offset < fsize) { if(destination == NULL && parsed_header) { /* We only needed to parse the header */ exit(0); } /* Read frame header */ skip = 0; fseek(file, offset, SEEK_SET); bytes = fread(prebuffer, sizeof(char), 8, file); if(bytes != 8 || prebuffer[0] != 'M') { JANUS_LOG(LOG_WARN, "Invalid header at offset %ld (%s), the processing will stop here...\n", offset, bytes != 8 ? "not enough bytes" : "wrong prefix"); break; } if(prebuffer[1] == 'E') { /* Either the old .mjr format header ('MEETECHO' header followed by 'audio' or 'video'), or a frame */ offset += 8; bytes = fread(&len, sizeof(uint16_t), 1, file); len = ntohs(len); offset += 2; if(len == 5 && !parsed_header) { /* This is the main header */ parsed_header = TRUE; JANUS_LOG(LOG_WARN, "Old .mjr header format\n"); bytes = fread(prebuffer, sizeof(char), 5, file); if(prebuffer[0] == 'v') { JANUS_LOG(LOG_INFO, "This is a video recording, assuming VP8\n"); video = 1; } else if(prebuffer[0] == 'a') { JANUS_LOG(LOG_INFO, "This is an audio recording, assuming Opus\n"); video = 0; } else { JANUS_LOG(LOG_WARN, "Unsupported recording media type...\n"); exit(1); } offset += len; continue; } else if(len < 12) { /* Not RTP, skip */ JANUS_LOG(LOG_VERB, "Skipping packet (not RTP?)\n"); offset += len; continue; } } else if(prebuffer[1] == 'J') { /* New .mjr format, the header may contain useful info */ offset += 8; bytes = fread(&len, sizeof(uint16_t), 1, file); len = ntohs(len); offset += 2; if(len > 0 && !parsed_header) { /* This is the info header */ JANUS_LOG(LOG_WARN, "New .mjr header format\n"); bytes = fread(prebuffer, sizeof(char), len, file); parsed_header = TRUE; prebuffer[len] = '\0'; json_error_t error; json_t *info = json_loads(prebuffer, 0, &error); if(!info) { JANUS_LOG(LOG_ERR, "JSON error: on line %d: %s\n", error.line, error.text); JANUS_LOG(LOG_WARN, "Error parsing info header...\n"); exit(1); } /* Is it audio or video? */ json_t *type = json_object_get(info, "t"); if(!type || !json_is_string(type)) { JANUS_LOG(LOG_WARN, "Missing/invalid recording type in info header...\n"); exit(1); } const char *t = json_string_value(type); if(!strcasecmp(t, "v")) { video = 1; } else if(!strcasecmp(t, "a")) { video = 0; } else { JANUS_LOG(LOG_WARN, "Unsupported recording type '%s' in info header...\n", t); exit(1); } /* What codec was used? */ json_t *codec = json_object_get(info, "c"); if(!codec || !json_is_string(codec)) { JANUS_LOG(LOG_WARN, "Missing recording codec in info header...\n"); exit(1); } const char *c = json_string_value(codec); if(video && strcasecmp(c, "vp8")) { JANUS_LOG(LOG_WARN, "The post-processor only suupports VP8 video for now (was '%s')...\n", c); exit(1); } else if(!video && strcasecmp(c, "opus")) { JANUS_LOG(LOG_WARN, "The post-processor only suupports Opus audio for now (was '%s')...\n", c); exit(1); } /* When was the file created? */ json_t *created = json_object_get(info, "s"); if(!created || !json_is_integer(created)) { JANUS_LOG(LOG_WARN, "Missing recording created time in info header...\n"); exit(1); } c_time = json_integer_value(created); /* When was the first frame written? */ json_t *written = json_object_get(info, "u"); if(!written || !json_is_integer(written)) { JANUS_LOG(LOG_WARN, "Missing recording written time in info header...\n"); exit(1); } w_time = json_integer_value(written); /* Summary */ JANUS_LOG(LOG_INFO, "This is %s recording:\n", video ? "a video" : "an audio"); JANUS_LOG(LOG_INFO, " -- Codec: %s\n", c); JANUS_LOG(LOG_INFO, " -- Created: %"SCNi64"\n", c_time); JANUS_LOG(LOG_INFO, " -- Written: %"SCNi64"\n", w_time); } } else { JANUS_LOG(LOG_ERR, "Invalid header...\n"); exit(1); } /* Only read RTP header */ bytes = fread(prebuffer, sizeof(char), 16, file); janus_pp_rtp_header *rtp = (janus_pp_rtp_header *)prebuffer; if(last_ts == 0) { first_ts = ntohl(rtp->timestamp); if(first_ts > 1000*1000) /* Just used to check whether a packet is pre- or post-reset */ first_ts -= 1000*1000; } else { if(ntohl(rtp->timestamp) < last_ts) { /* The new timestamp is smaller than the next one, is it a timestamp reset or simply out of order? */ if(last_ts-ntohl(rtp->timestamp) > 2*1000*1000*1000) { reset = ntohl(rtp->timestamp); JANUS_LOG(LOG_INFO, "Timestamp reset: %"SCNu32"\n", reset); } } else if(ntohl(rtp->timestamp) < reset) { JANUS_LOG(LOG_INFO, "Updating timestamp reset: %"SCNu32" (was %"SCNu32")\n", ntohl(rtp->timestamp), reset); reset = ntohl(rtp->timestamp); } } last_ts = ntohl(rtp->timestamp); /* Skip data for now */ offset += len; } /* Now let's parse the frames and order them */ offset = 0; while(offset < fsize) { /* Read frame header */ skip = 0; fseek(file, offset, SEEK_SET); bytes = fread(prebuffer, sizeof(char), 8, file); if(bytes != 8 || prebuffer[0] != 'M') { /* Broken packet? Stop here */ break; } prebuffer[8] = '\0'; JANUS_LOG(LOG_VERB, "Header: %s\n", prebuffer); offset += 8; bytes = fread(&len, sizeof(uint16_t), 1, file); len = ntohs(len); JANUS_LOG(LOG_VERB, " -- Length: %"SCNu16"\n", len); offset += 2; if(prebuffer[1] == 'J' || len < 12) { /* Not RTP, skip */ JANUS_LOG(LOG_VERB, " -- Not RTP, skipping\n"); offset += len; continue; } if(len > 2000) { /* Way too large, very likely not RTP, skip */ JANUS_LOG(LOG_VERB, " -- Too large packet (%d bytes), skipping\n", len); offset += len; continue; } /* Only read RTP header */ bytes = fread(prebuffer, sizeof(char), 16, file); janus_pp_rtp_header *rtp = (janus_pp_rtp_header *)prebuffer; JANUS_LOG(LOG_VERB, " -- RTP packet (ssrc=%"SCNu32", pt=%"SCNu16", ext=%"SCNu16", seq=%"SCNu16", ts=%"SCNu32")\n", ntohl(rtp->ssrc), rtp->type, rtp->extension, ntohs(rtp->seq_number), ntohl(rtp->timestamp)); if(rtp->extension) { janus_pp_rtp_header_extension *ext = (janus_pp_rtp_header_extension *)(prebuffer+12); JANUS_LOG(LOG_VERB, " -- -- RTP extension (type=%"SCNu16", length=%"SCNu16")\n", ntohs(ext->type), ntohs(ext->length)); skip = 4 + ntohs(ext->length)*4; } /* Generate frame packet and insert in the ordered list */ janus_pp_frame_packet *p = g_malloc0(sizeof(janus_pp_frame_packet)); if(p == NULL) { JANUS_LOG(LOG_ERR, "Memory error!\n"); return -1; } p->seq = ntohs(rtp->seq_number); if(reset == 0) { /* Simple enough... */ p->ts = ntohl(rtp->timestamp); } else { /* Is this packet pre- or post-reset? */ if(ntohl(rtp->timestamp) > first_ts) { /* Pre-reset... */ p->ts = ntohl(rtp->timestamp); } else { /* Post-reset... */ uint64_t max32 = UINT32_MAX; max32++; p->ts = max32+ntohl(rtp->timestamp); } } p->len = len; p->offset = offset; p->skip = skip; p->next = NULL; p->prev = NULL; if(list == NULL) { /* First element becomes the list itself (and the last item), at least for now */ list = p; last = p; } else { /* Check where we should insert this, starting from the end */ int added = 0; janus_pp_frame_packet *tmp = last; while(tmp) { if(tmp->ts < p->ts) { /* The new timestamp is greater than the last one we have, append */ added = 1; if(tmp->next != NULL) { /* We're inserting */ tmp->next->prev = p; p->next = tmp->next; } else { /* Update the last packet */ last = p; } tmp->next = p; p->prev = tmp; break; } else if(tmp->ts == p->ts) { /* Same timestamp, check the sequence number */ if(tmp->seq < p->seq && (abs(tmp->seq - p->seq) < 10000)) { /* The new sequence number is greater than the last one we have, append */ added = 1; if(tmp->next != NULL) { /* We're inserting */ tmp->next->prev = p; p->next = tmp->next; } else { /* Update the last packet */ last = p; } tmp->next = p; p->prev = tmp; break; } else if(tmp->seq > p->seq && (abs(tmp->seq - p->seq) > 10000)) { /* The new sequence number (resetted) is greater than the last one we have, append */ added = 1; if(tmp->next != NULL) { /* We're inserting */ tmp->next->prev = p; p->next = tmp->next; } else { /* Update the last packet */ last = p; } tmp->next = p; p->prev = tmp; break; } } /* If either the timestamp ot the sequence number we just got is smaller, keep going back */ tmp = tmp->prev; } if(!added) { /* We reached the start */ p->next = list; list->prev = p; list = p; } } /* Skip data for now */ offset += len; count++; } JANUS_LOG(LOG_INFO, "Counted %"SCNu16" RTP packets\n", count); janus_pp_frame_packet *tmp = list; count = 0; while(tmp) { count++; JANUS_LOG(LOG_VERB, "[%10lu][%4d] seq=%"SCNu16", ts=%"SCNu64", time=%"SCNu64"s\n", tmp->offset, tmp->len, tmp->seq, tmp->ts, (tmp->ts-list->ts)/90000); tmp = tmp->next; } JANUS_LOG(LOG_INFO, "Counted %"SCNu16" frame packets\n", count); if(!video) { /* We don't need any pre-parsing for audio */ if(janus_pp_opus_create(destination) < 0) { JANUS_LOG(LOG_ERR, "Error creating .opus file...\n"); exit(1); } } else { /* Look for maximum width and height, and for the mean framerate */ if(janus_pp_webm_preprocess(file, list) < 0) { JANUS_LOG(LOG_ERR, "Error pre-processing VP8 RTP frames...\n"); exit(1); } /* Now we can write the WebM file */ if(janus_pp_webm_create(destination) < 0) { JANUS_LOG(LOG_ERR, "Error creating .webm file...\n"); exit(1); } } /* Handle SIGINT */ signal(SIGINT, janus_pp_handle_signal); /* Loop */ working = 1; if(!video) { if(janus_pp_opus_process(file, list, &working) < 0) { JANUS_LOG(LOG_ERR, "Error processing Opus RTP frames...\n"); } } else { if(janus_pp_webm_process(file, list, &working) < 0) { JANUS_LOG(LOG_ERR, "Error processing Opus RTP frames...\n"); } } /* Clean up */ if(video) { janus_pp_webm_close(); } else { janus_pp_opus_close(); } fclose(file); file = fopen(destination, "rb"); if(file == NULL) { JANUS_LOG(LOG_INFO, "No destination file %s??\n", destination); } else { fseek(file, 0L, SEEK_END); fsize = ftell(file); fseek(file, 0L, SEEK_SET); JANUS_LOG(LOG_INFO, "%s is %zu bytes\n", destination, fsize); fclose(file); } janus_pp_frame_packet *temp = list, *next = NULL; while(temp) { next = temp->next; g_free(temp); temp = next; } JANUS_LOG(LOG_INFO, "Bye!\n"); janus_log_destroy(); return 0; }
/* Public methods */ janus_config *janus_config_parse(const char *config_file) { if(config_file == NULL) return NULL; char *filename = get_filename(config_file); if(filename == NULL) { JANUS_LOG(LOG_ERR, "Invalid filename %s\n", config_file); return NULL; } /* Open file */ FILE *file = fopen(config_file, "rt"); if(!file) { JANUS_LOG(LOG_ERR, " -- Error reading configuration file '%s'... error %d (%s)\n", filename, errno, strerror(errno)); return NULL; } /* Create configuration instance */ janus_config *jc = g_malloc0(sizeof(janus_config)); jc->name = g_strdup(filename); /* Traverse and parse it */ int line_number = 0; char line_buffer[BUFSIZ]; janus_config_category *cg = NULL; while(fgets(line_buffer, sizeof(line_buffer), file)) { line_number++; if(strlen(line_buffer) == 0) continue; /* Strip comments */ char *line = line_buffer, *sc = line, *c = NULL; while((c = strchr(sc, ';')) != NULL) { if(c == line) { /* Comment starts here */ *c = '\0'; break; } c--; if(*c != '\\') { /* Comment starts here */ *c = '\0'; break; } /* Escaped semicolon, remove the slash */ sc = c; int len = strlen(line)-(sc-line), pos = 0; for(pos = 0; pos < len; pos++) sc[pos] = sc[pos+1]; sc[len-1] = '\0'; if(len == 2) break; /* Go on */ sc++; } /* Trim (will remove newline characters too) */ line = trim(line); if(strlen(line) == 0) continue; /* Parse */ if(line[0] == '[') { /* Category */ line++; char *end = strchr(line, ']'); if(end == NULL) { JANUS_LOG(LOG_ERR, "Error parsing category at line %d: syntax error (%s)\n", line_number, filename); janus_config_destroy(jc); return NULL; } *end = '\0'; line = trim(line); if(strlen(line) == 0) { JANUS_LOG(LOG_ERR, "Error parsing category at line %d: no name (%s)\n", line_number, filename); janus_config_destroy(jc); return NULL; } cg = janus_config_add_category(jc, line); if(cg == NULL) { JANUS_LOG(LOG_ERR, "Error adding category %s (%s)\n", line, filename); janus_config_destroy(jc); return NULL; } } else { /* Item */ char *name = line, *value = strchr(line, '='); if(value == NULL || value == line) { JANUS_LOG(LOG_ERR, "Error parsing item at line %d (%s)\n", line_number, filename); janus_config_destroy(jc); return NULL; } *value = '\0'; name = trim(name); if(strlen(name) == 0) { JANUS_LOG(LOG_ERR, "Error parsing item at line %d: no name (%s)\n", line_number, filename); janus_config_destroy(jc); return NULL; } value++; value = trim(value); if(strlen(value) == 0) { JANUS_LOG(LOG_ERR, "Error parsing item at line %d: no value (%s)\n", line_number, filename); janus_config_destroy(jc); return NULL; } if(*value == '>') { value++; value = trim(value); if(strlen(value) == 0) { JANUS_LOG(LOG_ERR, "Error parsing item at line %d: no value (%s)\n", line_number, filename); janus_config_destroy(jc); return NULL; } } if(janus_config_add_item(jc, cg ? cg->name : NULL, name, value) == NULL) { if(cg == NULL) JANUS_LOG(LOG_ERR, "Error adding item %s (%s)\n", name, filename); else JANUS_LOG(LOG_ERR, "Error adding item %s to category %s (%s)\n", name, cg->name, filename); janus_config_destroy(jc); return NULL; } } } fclose(file); return jc; }
janus_recorder *janus_recorder_create(const char *dir, const char *codec, const char *filename) { janus_recorder_medium type = JANUS_RECORDER_AUDIO; if(codec == NULL) { JANUS_LOG(LOG_ERR, "Missing codec information\n"); return NULL; } if(!strcasecmp(codec, "vp8") || !strcasecmp(codec, "vp9") || !strcasecmp(codec, "h264")) { type = JANUS_RECORDER_VIDEO; if(!strcasecmp(codec, "vp9")) { JANUS_LOG(LOG_WARN, "The post-processor currently doesn't support VP9: recording anyway for the future\n"); } } else if(!strcasecmp(codec, "opus") || !strcasecmp(codec, "g711") || !strcasecmp(codec, "pcmu") || !strcasecmp(codec, "pcma")) { type = JANUS_RECORDER_AUDIO; if(!strcasecmp(codec, "pcmu") || !strcasecmp(codec, "pcma")) codec = "g711"; } else if(!strcasecmp(codec, "text")) { /* FIXME We only handle text on data channels, so that's the only thing we can save too */ type = JANUS_RECORDER_DATA; } else { /* We don't recognize the codec: while we might go on anyway, we'd rather fail instead */ JANUS_LOG(LOG_ERR, "Unsupported codec '%s'\n", codec); return NULL; } /* Create the recorder */ janus_recorder *rc = g_malloc0(sizeof(janus_recorder)); if(rc == NULL) { JANUS_LOG(LOG_FATAL, "Memory error!\n"); return NULL; } rc->dir = NULL; rc->filename = NULL; rc->file = NULL; rc->codec = g_strdup(codec); rc->created = janus_get_real_time(); if(dir != NULL) { /* Check if this directory exists, and create it if needed */ struct stat s; int err = stat(dir, &s); if(err == -1) { if(ENOENT == errno) { /* Directory does not exist, try creating it */ if(janus_mkdir(dir, 0755) < 0) { JANUS_LOG(LOG_ERR, "mkdir error: %d\n", errno); return NULL; } } else { JANUS_LOG(LOG_ERR, "stat error: %d\n", errno); return NULL; } } else { if(S_ISDIR(s.st_mode)) { /* Directory exists */ JANUS_LOG(LOG_VERB, "Directory exists: %s\n", dir); } else { /* File exists but it's not a directory? */ JANUS_LOG(LOG_ERR, "Not a directory? %s\n", dir); return NULL; } } } char newname[1024]; memset(newname, 0, 1024); if(filename == NULL) { /* Choose a random username */ g_snprintf(newname, 1024, "janus-recording-%"SCNu32".mjr", janus_random_uint32()); } else { /* Just append the extension */ g_snprintf(newname, 1024, "%s.mjr", filename); } /* Try opening the file now */ if(dir == NULL) { rc->file = fopen(newname, "wb"); } else { char path[1024]; memset(path, 0, 1024); g_snprintf(path, 1024, "%s/%s", dir, newname); rc->file = fopen(path, "wb"); } if(rc->file == NULL) { JANUS_LOG(LOG_ERR, "fopen error: %d\n", errno); return NULL; } if(dir) rc->dir = g_strdup(dir); rc->filename = g_strdup(newname); rc->type = type; /* Write the first part of the header */ fwrite(header, sizeof(char), strlen(header), rc->file); rc->writable = 1; /* We still need to also write the info header first */ rc->header = 0; janus_mutex_init(&rc->mutex); return rc; }
/* FIXME Test thread to relay RTP frames coming from gstreamer */ static void *janus_streaming_relay_thread(void *data) { JANUS_LOG(LOG_VERB, "Starting relay thread\n"); janus_streaming_mountpoint *mountpoint = (janus_streaming_mountpoint *)data; if(!mountpoint) { JANUS_LOG(LOG_ERR, "Invalid mountpoint!\n"); return NULL; } if(mountpoint->streaming_source != janus_streaming_source_rtp) { JANUS_LOG(LOG_ERR, "[%s] Not an RTP source mountpoint!\n", mountpoint->name); return NULL; } janus_streaming_rtp_source *source = mountpoint->source; if(source == NULL) { JANUS_LOG(LOG_ERR, "[%s] Invalid RTP source mountpoint!\n", mountpoint->name); return NULL; } gint audio_port = source->audio_port; gint video_port = source->video_port; /* Socket stuff */ struct sockaddr_in audio_address, video_address; int audio_fd = 0; if(audio_port >= 0) { audio_fd = socket(AF_INET, SOCK_DGRAM, 0); int yes = 1; /* For setsockopt() SO_REUSEADDR */ setsockopt(audio_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)); audio_address.sin_family = AF_INET; audio_address.sin_port = htons(audio_port); audio_address.sin_addr.s_addr = INADDR_ANY; if(bind(audio_fd, (struct sockaddr *)(&audio_address), sizeof(struct sockaddr)) < 0) { JANUS_LOG(LOG_ERR, "[%s] Bind failed for audio (port %d)...\n", mountpoint->name, audio_port); return NULL; } JANUS_LOG(LOG_VERB, "[%s] Audio listener bound to port %d\n", mountpoint->name, audio_port); } int video_fd = 0; if(video_port >= 0) { video_fd = socket(AF_INET, SOCK_DGRAM, 0); int yes = 1; /* For setsockopt() SO_REUSEADDR */ setsockopt(video_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)); video_address.sin_family = AF_INET; video_address.sin_port = htons(video_port); video_address.sin_addr.s_addr = INADDR_ANY; if(bind(video_fd, (struct sockaddr *)(&video_address), sizeof(struct sockaddr)) < 0) { JANUS_LOG(LOG_ERR, "[%s] Bind failed for video (%d)...\n", mountpoint->name, video_port); return NULL; } JANUS_LOG(LOG_VERB, "[%s] Video listener bound to port %d\n", mountpoint->name, video_port); } int maxfd = (audio_fd > video_fd) ? audio_fd : video_fd; /* Needed to fix seq and ts */ uint32_t a_last_ssrc = 0, a_last_ts = 0, a_base_ts = 0, a_base_ts_prev = 0, v_last_ssrc = 0, v_last_ts = 0, v_base_ts = 0, v_base_ts_prev = 0; uint16_t a_last_seq = 0, a_base_seq = 0, a_base_seq_prev = 0, v_last_seq = 0, v_base_seq = 0, v_base_seq_prev = 0; /* Loop */ socklen_t addrlen; struct sockaddr_in remote; int resfd = 0, bytes = 0; struct timeval timeout; fd_set readfds; FD_ZERO(&readfds); char buffer[1500]; memset(buffer, 0, 1500); janus_streaming_rtp_relay_packet packet; while(!stopping) { /* FIXME We need a per-mountpoint watchdog as well */ /* Wait for some data */ if(audio_fd > 0) FD_SET(audio_fd, &readfds); if(video_fd > 0) FD_SET(video_fd, &readfds); timeout.tv_sec = 1; timeout.tv_usec = 0; resfd = select(maxfd+1, &readfds, NULL, NULL, &timeout); if(resfd < 0) break; if(audio_fd > 0 && FD_ISSET(audio_fd, &readfds)) { if(mountpoint->active == FALSE) mountpoint->active = TRUE; /* Got something audio */ addrlen = sizeof(remote); bytes = recvfrom(audio_fd, buffer, 1500, 0, (struct sockaddr*)&remote, &addrlen); // JANUS_LOG(LOG_VERB, "************************\nGot %d bytes on the audio channel...\n", bytes); rtp_header *rtp = (rtp_header *)buffer; // JANUS_LOG(LOG_VERB, " ... parsed RTP packet (ssrc=%u, pt=%u, seq=%u, ts=%u)...\n", // ntohl(rtp->ssrc), rtp->type, ntohs(rtp->seq_number), ntohl(rtp->timestamp)); /* Relay on all sessions */ packet.data = rtp; packet.length = bytes; packet.is_video = 0; /* Do we have a new stream? */ if(ntohl(packet.data->ssrc) != a_last_ssrc) { a_last_ssrc = ntohl(packet.data->ssrc); JANUS_LOG(LOG_INFO, "[%s] New audio stream! (ssrc=%u)\n", mountpoint->name, a_last_ssrc); a_base_ts_prev = a_last_ts; a_base_ts = ntohl(packet.data->timestamp); a_base_seq_prev = a_last_seq; a_base_seq = ntohs(packet.data->seq_number); } a_last_ts = (ntohl(packet.data->timestamp)-a_base_ts)+a_base_ts_prev+960; /* FIXME We're assuming Opus here... */ packet.data->timestamp = htonl(a_last_ts); a_last_seq = (ntohs(packet.data->seq_number)-a_base_seq)+a_base_seq_prev+1; packet.data->seq_number = htons(a_last_seq); // JANUS_LOG(LOG_VERB, " ... updated RTP packet (ssrc=%u, pt=%u, seq=%u, ts=%u)...\n", // ntohl(rtp->ssrc), rtp->type, ntohs(rtp->seq_number), ntohl(rtp->timestamp)); packet.data->type = mountpoint->codecs.audio_pt; /* Go! */ g_list_foreach(mountpoint->listeners, janus_streaming_relay_rtp_packet, &packet); continue; } if(video_fd > 0 && FD_ISSET(video_fd, &readfds)) { if(mountpoint->active == FALSE) mountpoint->active = TRUE; /* Got something video */ addrlen = sizeof(remote); bytes = recvfrom(video_fd, buffer, 1500, 0, (struct sockaddr*)&remote, &addrlen); //~ JANUS_LOG(LOG_VERB, "************************\nGot %d bytes on the video channel...\n", bytes); rtp_header *rtp = (rtp_header *)buffer; //~ JANUS_LOG(LOG_VERB, " ... parsed RTP packet (ssrc=%u, pt=%u, seq=%u, ts=%u)...\n", //~ ntohl(rtp->ssrc), rtp->type, ntohs(rtp->seq_number), ntohl(rtp->timestamp)); /* Relay on all sessions */ packet.data = rtp; packet.length = bytes; packet.is_video = 1; /* Do we have a new stream? */ if(ntohl(packet.data->ssrc) != v_last_ssrc) { v_last_ssrc = ntohl(packet.data->ssrc); JANUS_LOG(LOG_INFO, "[%s] New video stream! (ssrc=%u)\n", mountpoint->name, v_last_ssrc); v_base_ts_prev = v_last_ts; v_base_ts = ntohl(packet.data->timestamp); v_base_seq_prev = v_last_seq; v_base_seq = ntohs(packet.data->seq_number); } v_last_ts = (ntohl(packet.data->timestamp)-v_base_ts)+v_base_ts_prev+4500; /* FIXME We're assuming 15fps here... */ packet.data->timestamp = htonl(v_last_ts); v_last_seq = (ntohs(packet.data->seq_number)-v_base_seq)+v_base_seq_prev+1; packet.data->seq_number = htons(v_last_seq); //~ JANUS_LOG(LOG_VERB, " ... updated RTP packet (ssrc=%u, pt=%u, seq=%u, ts=%u)...\n", //~ ntohl(rtp->ssrc), rtp->type, ntohs(rtp->seq_number), ntohl(rtp->timestamp)); packet.data->type = mountpoint->codecs.video_pt; /* Go! */ g_list_foreach(mountpoint->listeners, janus_streaming_relay_rtp_packet, &packet); continue; } } JANUS_LOG(LOG_VERB, "Leaving relay thread\n"); return NULL; }