// Simple example of a mock IP camera. Answers WebRTC offer and streams H.264. #include #include #include #include #include #include #include "jsmn.h" #include "tinyrtc.h" // Helper function for parsing JSON static bool matches(const char *json, jsmntok_t *tok, const char *s) { if (JSMN_STRING != tok->type) return false; if (strlen(s) != tok->end - tok->start) return false; return 0 == strncmp(json + tok->start, s, tok->end - tok->start); } // Parses ICE candidate from stringified JSON struct rtc_ice_candidate parse_ice_candidate(const char *s) { struct rtc_ice_candidate c = { 0 }; int i, r; jsmn_parser p; jsmntok_t t[16]; jsmn_init(&p); r = jsmn_parse(&p, s, strlen(s), t, sizeof(t) / sizeof(t[0])); if (r < 0) { printf("%s:%d: failed to parse json", __func__, __LINE__); return c; } if (r < 1 || t[0].type != JSMN_OBJECT) { printf("%s:%d: object expected", __func__, __LINE__); return c; } for (i = 1; i < r; i++) { if (matches(s, &t[i], "candidate")) { strncpy(c.candidate, s + t[i + 1].start, t[i + 1].end - t[i + 1].start); i++; } else if (matches(s, &t[i], "sdpMid")) { strncpy(c.sdp_mid, s + t[i + 1].start, t[i + 1].end - t[i + 1].start); i++; } else if (matches(s, &t[i], "sdpMLineIndex")) { c.sdp_mline_index = atoi(s + t[i + 1].start); i++; } else if (matches(s, &t[i], "usernameFragment")) { strncpy(c.username_fragment, s + t[i + 1].start, t[i + 1].end - t[i + 1].start); i++; } } return c; } // (callback) Called for each discovered local ICE candidate void on_ice_candidate(const struct rtc_ice_candidate c, void *arg) { // send ice candidate over signaling channel printf("{" "\"candidate\":\"%s\"," "\"sdpMLineIndex\":%d," "\"sdpMid\":\"%s\"," "\"usernameFragment\":null" "}\n", c.candidate, c.sdp_mline_index, c.sdp_mid); } int main(int argc, char **argv) { openlog(NULL, LOG_PERROR, LOG_USER); // disable stdout buffering to ensure signaling messages send immediately setvbuf(stdout, NULL, _IONBF, 0); // initialize tinyrtc library rtc_init(); // create peer connection struct rtc_configuration cfg = { .ice_servers = { { .urls = { "stun:stun.liburtc.org", }, } } }; struct rtc_peer_connection* pc = rtc_peer_connection_create(cfg); if (!pc) { syslog(LOG_ERR, "rtc_peer_connection_create() failed"); return -1; } rtc_set_on_ice_candidate(pc, on_ice_candidate, NULL); while (true) { int n; char *msg = NULL; size_t msglen = 0; if (n = getline(&msg, &msglen, stdin), -1 == n) { syslog(LOG_ERR, "%m"); return -1; } // case: sdp offer if (strstr(msg, "\"sdp\"")) { // XXX rtc_set_remote_description(pc, offer); const char *answer = rtc_create_answer(pc); /* send SDP answer via stdout signaling channel */ printf("{\"sdp\":\"%s\",\"type\":\"answer\"}\n", answer); rtc_set_local_description(pc, answer); // case: ice candidate } else if (strstr(msg, "\"candidate\"")) { struct rtc_ice_candidate c = parse_ice_candidate(msg); rtc_add_ice_candidate(pc, c); // case: error } else { printf("unrecognized signaling message\n"); } } syslog(LOG_ERR, "terminating"); return 0; } /* vim: set et ts=4 sw=4 : */