#include "rtsp.h" #define RTSP_TRACK_PREFIX "/trackID=" #define RTSP_VIDEO_TRACK 0U #define RTSP_AUDIO_TRACK 1U #define RTSP_TRACK_COUNT 2U #define RTSP_VIDEO_RTP_PT 96U #define RTSP_AUDIO_RTP_PT 97U #define RTSP_VIDEO_RTP_CLOCK 90000U #define RTSP_RTP_PAYLOAD_MAX 1400U #define RTSP_MAX_NALS 16U #define H264_MAX_AU (4U * 1024U * 1024U) #define H264_MAX_RBSP (4U * 1024U * 1024U) struct rtsp_request { struct mg_str method; struct mg_str uri; struct mg_str cseq; struct mg_str transport; struct mg_str session; size_t msg_len; }; struct rtsp_nal { const uint8_t *ptr; size_t len; }; struct rtsp_server { struct mg_rtsp_opts opts; char *path; bool has_audio; char sdp[1024]; }; struct rtsp_track_conn { bool setup_done; bool ended; uint16_t rtp_seq; uint32_t ssrc; unsigned rtp_channel; unsigned rtcp_channel; uint64_t deadline_ms; }; struct rtsp_conn { struct rtsp_server *srv; bool playing; struct rtsp_track_conn tracks[RTSP_TRACK_COUNT]; char session_id[24]; }; struct h264_bw { uint8_t *buf; size_t len; size_t cap; unsigned bitpos; bool oom; }; struct h264_color { uint8_t y; uint8_t cb; uint8_t cr; }; struct h264_testsrc_state { bool inited; unsigned width; unsigned height; unsigned fps; unsigned mb_width; unsigned mb_height; unsigned box_w; unsigned box_h; int box_x; int box_y; int dx; int dy; uint64_t frame_no; uint8_t sps[64]; uint8_t pps[32]; size_t sps_len; size_t pps_len; uint8_t rbsp[H264_MAX_RBSP]; uint8_t au[H264_MAX_AU]; }; #define MP4_TAG(a, b, c, d) \ (((uint32_t) (uint8_t) (a) << 24) | ((uint32_t) (uint8_t) (b) << 16) | \ ((uint32_t) (uint8_t) (c) << 8) | (uint32_t) (uint8_t) (d)) struct mp4_box { const uint8_t *buf; size_t len; uint32_t type; }; struct rtsp_mp4_stsc { uint32_t first_chunk; uint32_t samples_per_chunk; }; struct rtsp_mp4_sample { uint64_t offset; uint32_t size; uint64_t ts; uint64_t pts_us; bool keyframe; }; struct rtsp_mp4_state { struct rtsp_mp4_state *next; char *path; struct mg_str file; struct rtsp_mp4_sample *samples; size_t sample_count; size_t start_index; size_t sample_index; uint32_t width; uint32_t height; unsigned fps; uint32_t timescale; uint8_t nal_len_size; uint8_t *param_sets; size_t param_sets_len; uint8_t *sps; size_t sps_len; uint8_t *pps; size_t pps_len; uint8_t *au; size_t au_cap; struct rtsp_mp4_sample *audio_samples; size_t audio_sample_count; size_t audio_sample_index; uint32_t audio_timescale; unsigned audio_rate; unsigned audio_channels; uint8_t *audio_config; size_t audio_config_len; }; static struct rtsp_mp4_state *s_rtsp_mp4_states; static int rtsp_tolower(int ch) { return ch >= 'A' && ch <= 'Z' ? ch + ('a' - 'A') : ch; } static bool rtsp_memeq(const void *ptr1, const void *ptr2, size_t len) { return len == 0 || memcmp(ptr1, ptr2, len) == 0; } static bool rtsp_caseeq(struct mg_str s, const char *lit) { size_t i, len = strlen(lit); if (s.len != len) return false; for (i = 0; i < len; i++) { if (rtsp_tolower((unsigned char) s.buf[i]) != rtsp_tolower((unsigned char) lit[i])) { return false; } } return true; } static bool rtsp_casencontains(struct mg_str s, const char *lit) { size_t i, j, slen = s.len, llen = strlen(lit); if (llen == 0 || llen > slen) return false; for (i = 0; i + llen <= slen; i++) { for (j = 0; j < llen; j++) { if (rtsp_tolower((unsigned char) s.buf[i + j]) != rtsp_tolower((unsigned char) lit[j])) { break; } } if (j == llen) return true; } return false; } static struct mg_str rtsp_trim(struct mg_str s) { while (s.len > 0 && (s.buf[0] == ' ' || s.buf[0] == '\t' || s.buf[0] == '\r' || s.buf[0] == '\n')) { s.buf++; s.len--; } while (s.len > 0 && (s.buf[s.len - 1] == ' ' || s.buf[s.len - 1] == '\t' || s.buf[s.len - 1] == '\r' || s.buf[s.len - 1] == '\n')) { s.len--; } return s; } static bool rtsp_parse_u32(struct mg_str s, uint32_t *value) { size_t i; uint32_t n = 0; s = rtsp_trim(s); if (s.len == 0) return false; for (i = 0; i < s.len; i++) { unsigned ch = (unsigned char) s.buf[i]; if (ch < '0' || ch > '9') return false; n = n * 10U + (uint32_t) (ch - '0'); } *value = n; return true; } static char *rtsp_strdup(const char *s) { size_t len = strlen(s); char *copy = (char *) mg_calloc(len + 1U, 1U); if (copy != NULL) memcpy(copy, s, len); return copy; } static struct mg_str rtsp_uri_path(struct mg_str uri) { const char *p = uri.buf, *end = uri.buf + uri.len, *scheme = NULL; size_t i; for (i = 0; i + 2 < uri.len; i++) { if (uri.buf[i] == ':' && uri.buf[i + 1] == '/' && uri.buf[i + 2] == '/') { scheme = uri.buf + i; break; } } if (scheme != NULL) { p = scheme + 3; while (p < end && *p != '/') p++; if (p >= end) return mg_str("/"); } if (p < end && *p != '/') { while (p < end && *p != '/') p++; if (p >= end) return mg_str("/"); } if (p >= end) return mg_str("/"); end = p; while (end < uri.buf + uri.len && *end != '?') end++; return mg_str_n(p, (size_t) (end - p)); } static bool rtsp_path_eq(struct mg_str uri, const char *path) { struct mg_str upath = rtsp_uri_path(uri); size_t path_len = strlen(path); return upath.len == path_len && rtsp_memeq(upath.buf, path, path_len); } static int rtsp_track_path_eq(struct mg_str uri, const char *path) { struct mg_str upath = rtsp_uri_path(uri); struct mg_str id; uint32_t track_id; size_t path_len = strlen(path), prefix_len = strlen(RTSP_TRACK_PREFIX); if (upath.len <= path_len + prefix_len) return -1; if (!rtsp_memeq(upath.buf, path, path_len) || !rtsp_memeq(upath.buf + path_len, RTSP_TRACK_PREFIX, prefix_len)) { return -1; } id = mg_str_n(upath.buf + path_len + prefix_len, upath.len - path_len - prefix_len); if (!rtsp_parse_u32(id, &track_id)) return -1; return (int) track_id; } static bool rtsp_next_line(const char **cursor, const char *end, struct mg_str *line) { const char *p = *cursor; if (p >= end) return false; while (p < end && *p != '\n') p++; if (p >= end) return false; line->buf = (char *) *cursor; line->len = (size_t) (p - *cursor); if (line->len > 0 && line->buf[line->len - 1] == '\r') line->len--; *cursor = p + 1; return true; } static int rtsp_parse_request(const char *buf, size_t len, struct rtsp_request *req) { const char *cursor = buf, *end; struct mg_str line; int header_len = mg_http_get_request_len((const unsigned char *) buf, len); uint32_t content_length = 0; memset(req, 0, sizeof(*req)); if (header_len <= 0) return header_len; if ((size_t) header_len > len) return 0; end = buf + header_len; if (!rtsp_next_line(&cursor, end, &line)) return -1; { const char *p = line.buf, *line_end = line.buf + line.len; while (p < line_end && *p == ' ') p++; req->method.buf = (char *) p; while (p < line_end && *p != ' ') p++; req->method.len = (size_t) (p - req->method.buf); while (p < line_end && *p == ' ') p++; req->uri.buf = (char *) p; while (p < line_end && *p != ' ') p++; req->uri.len = (size_t) (p - req->uri.buf); while (p < line_end && *p == ' ') p++; if (!rtsp_caseeq(mg_str_n(p, (size_t) (line_end - p)), "RTSP/1.0")) return -1; if (req->method.len == 0 || req->uri.len == 0) return -1; } while (rtsp_next_line(&cursor, end, &line)) { struct mg_str name, value; char *colon; if (line.len == 0) break; colon = (char *) memchr(line.buf, ':', line.len); if (colon == NULL) return -1; name = mg_str_n(line.buf, (size_t) (colon - line.buf)); value = rtsp_trim(mg_str_n(colon + 1, line.len - (size_t) (colon - line.buf) - 1)); if (rtsp_caseeq(name, "CSeq")) { req->cseq = value; } else if (rtsp_caseeq(name, "Transport")) { req->transport = value; } else if (rtsp_caseeq(name, "Session")) { req->session = value; } else if (rtsp_caseeq(name, "Content-Length")) { if (!rtsp_parse_u32(value, &content_length)) return -1; } } req->msg_len = (size_t) header_len + (size_t) content_length; if (req->msg_len > len) return 0; return req->cseq.len > 0 ? 1 : -1; } static void rtsp_send_response(struct mg_connection *c, const struct rtsp_request *req, int status, const char *reason, const char *headers, const void *body, size_t body_len) { const char *extra = headers == NULL ? "" : headers; mg_printf(c, "RTSP/1.0 %d %s\r\n" "CSeq: %.*s\r\n" "Server: Mongoose RTSP\r\n" "%s" "Content-Length: %lu\r\n" "\r\n", status, reason, (int) req->cseq.len, req->cseq.buf, extra, (unsigned long) body_len); if (body_len > 0) mg_send(c, body, body_len); } static void rtsp_send_status(struct mg_connection *c, const struct rtsp_request *req, int status, const char *reason, const char *headers) { rtsp_send_response(c, req, status, reason, headers, NULL, 0); } static size_t h264_start_code_len(const uint8_t *buf, size_t len, size_t off) { if (off + 3 <= len && buf[off] == 0 && buf[off + 1] == 0 && buf[off + 2] == 1) { return 3; } if (off + 4 <= len && buf[off] == 0 && buf[off + 1] == 0 && buf[off + 2] == 0 && buf[off + 3] == 1) { return 4; } return 0; } static size_t h264_split_annexb(const uint8_t *buf, size_t len, struct rtsp_nal *nals, size_t max_nals) { size_t i = 0, count = 0; while (i < len) { size_t sc = h264_start_code_len(buf, len, i); if (sc == 0) { i++; continue; } i += sc; if (i >= len) break; { size_t start = i; while (i < len && h264_start_code_len(buf, len, i) == 0) i++; if (start < i && count < max_nals) { nals[count].ptr = buf + start; nals[count].len = i - start; count++; } } } if (count == 0 && len > 0 && max_nals > 0) { nals[0].ptr = buf; nals[0].len = len; count = 1; } return count; } static void rtsp_parse_interleaved_channels(struct mg_str transport, unsigned *rtp_channel, unsigned *rtcp_channel) { size_t i; *rtp_channel = 0; *rtcp_channel = 1; for (i = 0; i + 11 < transport.len; i++) { if (rtsp_tolower((unsigned char) transport.buf[i]) == 'i' && rtsp_tolower((unsigned char) transport.buf[i + 1]) == 'n' && rtsp_tolower((unsigned char) transport.buf[i + 2]) == 't' && rtsp_tolower((unsigned char) transport.buf[i + 3]) == 'e' && rtsp_tolower((unsigned char) transport.buf[i + 4]) == 'r' && rtsp_tolower((unsigned char) transport.buf[i + 5]) == 'l' && rtsp_tolower((unsigned char) transport.buf[i + 6]) == 'e' && rtsp_tolower((unsigned char) transport.buf[i + 7]) == 'a' && rtsp_tolower((unsigned char) transport.buf[i + 8]) == 'v' && rtsp_tolower((unsigned char) transport.buf[i + 9]) == 'e' && rtsp_tolower((unsigned char) transport.buf[i + 10]) == 'd' && transport.buf[i + 11] == '=') { uint32_t a = 0, b = 1; size_t j = i + 12; struct mg_str s1, s2; s1.buf = &transport.buf[j]; s1.len = 0; while (j < transport.len && transport.buf[j] >= '0' && transport.buf[j] <= '9') { s1.len++; j++; } if (j >= transport.len || transport.buf[j] != '-') return; j++; s2.buf = &transport.buf[j]; s2.len = 0; while (j < transport.len && transport.buf[j] >= '0' && transport.buf[j] <= '9') { s2.len++; j++; } if (rtsp_parse_u32(s1, &a) && rtsp_parse_u32(s2, &b)) { *rtp_channel = (unsigned) a; *rtcp_channel = (unsigned) b; } return; } } } static bool rtsp_session_matches(const struct rtsp_conn *rc, struct mg_str session) { size_t i; session = rtsp_trim(session); for (i = 0; i < session.len; i++) { if (session.buf[i] == ';') { session.len = i; break; } } return session.len == strlen(rc->session_id) && rtsp_memeq(session.buf, rc->session_id, session.len); } static uint16_t rtsp_mp4_u16(const uint8_t *p) { return (uint16_t) (((uint16_t) p[0] << 8) | p[1]); } static uint32_t rtsp_mp4_u32(const uint8_t *p) { return ((uint32_t) p[0] << 24) | ((uint32_t) p[1] << 16) | ((uint32_t) p[2] << 8) | p[3]; } static uint64_t rtsp_mp4_u64(const uint8_t *p) { return ((uint64_t) rtsp_mp4_u32(p) << 32) | rtsp_mp4_u32(p + 4); } static uint64_t rtsp_scale_us(uint64_t value, uint32_t timescale) { if (timescale == 0) return 0; return (value / timescale) * 1000000ULL + (((value % timescale) * 1000000ULL) + timescale / 2U) / timescale; } static bool rtsp_mp4_next_box(const uint8_t *buf, size_t len, size_t *ofs, struct mp4_box *box) { uint64_t start = *ofs, size, head = 8; if (start + 8U > len) return false; size = rtsp_mp4_u32(buf + start); box->type = rtsp_mp4_u32(buf + start + 4U); if (size == 1U) { if (start + 16U > len) return false; size = rtsp_mp4_u64(buf + start + 8U); head = 16; } else if (size == 0) { size = len - start; } if (size < head || start + size > len) return false; box->buf = buf + start + head; box->len = (size_t) (size - head); *ofs = (size_t) (start + size); return true; } static bool rtsp_mp4_find_child(const uint8_t *buf, size_t len, uint32_t type, struct mp4_box *box) { size_t ofs = 0; struct mp4_box tmp; while (rtsp_mp4_next_box(buf, len, &ofs, &tmp)) { if (tmp.type == type) { *box = tmp; return true; } } return false; } static uint32_t rtsp_mp4_track_type(const struct mp4_box *mdia) { struct mp4_box hdlr; return rtsp_mp4_find_child(mdia->buf, mdia->len, MP4_TAG('h', 'd', 'l', 'r'), &hdlr) && hdlr.len >= 12U ? rtsp_mp4_u32(hdlr.buf + 8U) : 0; } static void rtsp_mp4_free_state(struct rtsp_mp4_state *st) { if (st == NULL) return; mg_free(st->path); mg_free(st->file.buf); mg_free(st->samples); mg_free(st->audio_samples); mg_free(st->param_sets); mg_free(st->sps); mg_free(st->pps); mg_free(st->au); mg_free(st->audio_config); mg_free(st); } static bool rtsp_mp4_copy_blob(uint8_t **dst, size_t *dst_len, const uint8_t *src, size_t len) { uint8_t *copy = (uint8_t *) mg_calloc(len == 0 ? 1U : len, 1U); if (copy == NULL) return false; if (len > 0) memcpy(copy, src, len); mg_free(*dst); *dst = copy; *dst_len = len; return true; } static bool rtsp_mp4_parse_avcc(struct rtsp_mp4_state *st, const uint8_t *buf, size_t len) { size_t i, pos, out_len = 0, out_cap; uint8_t num_pps; uint8_t *out; if (len < 7U) return false; st->nal_len_size = (uint8_t) ((buf[4] & 3U) + 1U); out_cap = len * 2U + 32U; out = (uint8_t *) mg_calloc(out_cap, 1U); if (out == NULL) return false; pos = 6U; for (i = 0; i < (size_t) (buf[5] & 0x1fU); i++) { uint16_t n; if (pos + 2U > len) goto fail; n = rtsp_mp4_u16(buf + pos); pos += 2U; if (n == 0 || pos + n > len || out_len + 4U + n > out_cap) goto fail; if (st->sps == NULL && !rtsp_mp4_copy_blob(&st->sps, &st->sps_len, buf + pos, n)) goto fail; memcpy(out + out_len, "\0\0\0\1", 4U); out_len += 4U; memcpy(out + out_len, buf + pos, n); out_len += n; pos += n; } if (pos + 1U > len) goto fail; num_pps = buf[pos++]; for (i = 0; i < num_pps; i++) { uint16_t n; if (pos + 2U > len) goto fail; n = rtsp_mp4_u16(buf + pos); pos += 2U; if (n == 0 || pos + n > len || out_len + 4U + n > out_cap) goto fail; if (st->pps == NULL && !rtsp_mp4_copy_blob(&st->pps, &st->pps_len, buf + pos, n)) goto fail; memcpy(out + out_len, "\0\0\0\1", 4U); out_len += 4U; memcpy(out + out_len, buf + pos, n); out_len += n; pos += n; } if (st->sps == NULL || st->pps == NULL || out_len == 0) goto fail; st->param_sets = out; st->param_sets_len = out_len; return true; fail: mg_free(out); return false; } static bool rtsp_mp4_next_desc(const uint8_t *buf, size_t len, size_t *ofs, uint8_t *tag, const uint8_t **body, size_t *body_len) { size_t pos = *ofs, i; uint32_t size = 0; if (pos >= len) return false; *tag = buf[pos++]; for (i = 0; i < 4; i++) { uint8_t b; if (pos >= len) return false; b = buf[pos++]; size = (size << 7) | (uint32_t) (b & 0x7fU); if ((b & 0x80U) == 0) break; } if (pos + size > len) return false; *body = buf + pos; *body_len = size; *ofs = pos + size; return true; } static bool rtsp_mp4_parse_esds(struct rtsp_mp4_state *st, const uint8_t *buf, size_t len) { const uint8_t *body, *config; size_t body_len, config_len, ofs; uint8_t tag; if (len < 4U) return false; ofs = 0; if (!rtsp_mp4_next_desc(buf + 4U, len - 4U, &ofs, &tag, &body, &body_len) || tag != 0x03U || body_len < 3U) { return false; } ofs = 3U; if ((body[2] & 0x80U) != 0) ofs += 2U; if ((body[2] & 0x40U) != 0) { if (ofs >= body_len) return false; ofs += 1U + body[ofs]; } if ((body[2] & 0x20U) != 0) ofs += 2U; if (ofs > body_len || !rtsp_mp4_next_desc(body, body_len, &ofs, &tag, &body, &body_len) || tag != 0x04U || body_len < 13U) { return false; } ofs = 13U; if (!rtsp_mp4_next_desc(body, body_len, &ofs, &tag, &config, &config_len) || tag != 0x05U || config_len == 0) { return false; } while (config_len > 0 && config[config_len - 1] == 0) config_len--; return config_len > 0 && rtsp_mp4_copy_blob(&st->audio_config, &st->audio_config_len, config, config_len); } static bool rtsp_mp4_parse_stsd(struct rtsp_mp4_state *st, const struct mp4_box *stsd) { size_t ofs = 8U; struct mp4_box entry, avcc; if (stsd->len < 8U || rtsp_mp4_u32(stsd->buf + 4U) == 0) return false; while (rtsp_mp4_next_box(stsd->buf, stsd->len, &ofs, &entry)) { if (entry.type != MP4_TAG('a', 'v', 'c', '1') && entry.type != MP4_TAG('a', 'v', 'c', '3')) { continue; } if (entry.len < 78U) return false; st->width = rtsp_mp4_u16(entry.buf + 24U); st->height = rtsp_mp4_u16(entry.buf + 26U); if (!rtsp_mp4_find_child(entry.buf + 78U, entry.len - 78U, MP4_TAG('a', 'v', 'c', 'C'), &avcc)) { return false; } return rtsp_mp4_parse_avcc(st, avcc.buf, avcc.len); } return false; } static bool rtsp_mp4_parse_audio_stsd(struct rtsp_mp4_state *st, const struct mp4_box *stsd) { size_t ofs = 8U; struct mp4_box entry, esds; if (stsd->len < 8U || rtsp_mp4_u32(stsd->buf + 4U) == 0) return false; while (rtsp_mp4_next_box(stsd->buf, stsd->len, &ofs, &entry)) { uint16_t version; size_t child_ofs = 28U; if (entry.type != MP4_TAG('m', 'p', '4', 'a')) continue; if (entry.len < 28U) return false; version = rtsp_mp4_u16(entry.buf + 8U); if (version == 1U) { child_ofs = 44U; } else if (version == 2U) { child_ofs = 64U; } if (entry.len < child_ofs) return false; st->audio_channels = rtsp_mp4_u16(entry.buf + 16U); st->audio_rate = rtsp_mp4_u32(entry.buf + 24U) >> 16; if (st->audio_channels == 0 || st->audio_rate == 0) return false; if (!rtsp_mp4_find_child(entry.buf + child_ofs, entry.len - child_ofs, MP4_TAG('e', 's', 'd', 's'), &esds)) { return false; } return rtsp_mp4_parse_esds(st, esds.buf, esds.len); } return false; } static bool rtsp_mp4_parse_mdhd(struct rtsp_mp4_state *st, const struct mp4_box *mdhd) { if (mdhd->len < 16U) return false; if (mdhd->buf[0] == 1) { if (mdhd->len < 24U) return false; st->timescale = rtsp_mp4_u32(mdhd->buf + 20U); } else { st->timescale = rtsp_mp4_u32(mdhd->buf + 12U); } return st->timescale > 0; } static bool rtsp_mp4_parse_audio_mdhd(struct rtsp_mp4_state *st, const struct mp4_box *mdhd) { if (mdhd->len < 16U) return false; if (mdhd->buf[0] == 1) { if (mdhd->len < 24U) return false; st->audio_timescale = rtsp_mp4_u32(mdhd->buf + 20U); } else { st->audio_timescale = rtsp_mp4_u32(mdhd->buf + 12U); } return st->audio_timescale > 0; } static uint32_t *rtsp_mp4_parse_stsz(const struct mp4_box *stsz, size_t *sample_count) { uint32_t fixed_size, count; uint32_t *sizes; size_t i; if (stsz->len < 12U) return NULL; fixed_size = rtsp_mp4_u32(stsz->buf + 4U); count = rtsp_mp4_u32(stsz->buf + 8U); if (count == 0) return NULL; if (fixed_size == 0 && stsz->len < 12U + (size_t) count * 4U) return NULL; sizes = (uint32_t *) mg_calloc(count, sizeof(*sizes)); if (sizes == NULL) return NULL; if (fixed_size != 0) { for (i = 0; i < count; i++) sizes[i] = fixed_size; } else { for (i = 0; i < count; i++) sizes[i] = rtsp_mp4_u32(stsz->buf + 12U + i * 4U); } *sample_count = count; return sizes; } static uint64_t *rtsp_mp4_parse_chunk_offsets(const struct mp4_box *box, size_t *count) { uint32_t n; size_t i; uint64_t *offsets; bool is64 = box->type == MP4_TAG('c', 'o', '6', '4'); if (box->len < 8U) return NULL; n = rtsp_mp4_u32(box->buf + 4U); if (n == 0) return NULL; if (is64) { if (box->len < 8U + (size_t) n * 8U) return NULL; } else { if (box->len < 8U + (size_t) n * 4U) return NULL; } offsets = (uint64_t *) mg_calloc(n, sizeof(*offsets)); if (offsets == NULL) return NULL; for (i = 0; i < n; i++) { offsets[i] = is64 ? rtsp_mp4_u64(box->buf + 8U + i * 8U) : (uint64_t) rtsp_mp4_u32(box->buf + 8U + i * 4U); } *count = n; return offsets; } static struct rtsp_mp4_stsc *rtsp_mp4_parse_stsc(const struct mp4_box *stsc, size_t *count) { uint32_t n; size_t i; struct rtsp_mp4_stsc *entries; if (stsc->len < 8U) return NULL; n = rtsp_mp4_u32(stsc->buf + 4U); if (n == 0 || stsc->len < 8U + (size_t) n * 12U) return NULL; entries = (struct rtsp_mp4_stsc *) mg_calloc(n, sizeof(*entries)); if (entries == NULL) return NULL; for (i = 0; i < n; i++) { const uint8_t *p = stsc->buf + 8U + i * 12U; entries[i].first_chunk = rtsp_mp4_u32(p); entries[i].samples_per_chunk = rtsp_mp4_u32(p + 4U); } *count = n; return entries; } static int64_t *rtsp_mp4_parse_pts(const struct mp4_box *stts, const struct mp4_box *ctts, size_t sample_count, uint64_t *duration_units) { int64_t *pts; uint64_t dts = 0; uint32_t entry_count; size_t i, j, idx = 0, pos; (void) ctts; if (stts->len < 8U) return NULL; entry_count = rtsp_mp4_u32(stts->buf + 4U); pts = (int64_t *) mg_calloc(sample_count, sizeof(*pts)); if (pts == NULL) return NULL; pos = 8U; for (i = 0; i < entry_count; i++, pos += 8U) { uint32_t count, delta; if (pos + 8U > stts->len) goto fail; count = rtsp_mp4_u32(stts->buf + pos); delta = rtsp_mp4_u32(stts->buf + pos + 4U); for (j = 0; j < count; j++) { if (idx >= sample_count) goto fail; pts[idx++] = (int64_t) dts; dts += delta; } } if (idx != sample_count) goto fail; *duration_units = dts; return pts; fail: mg_free(pts); return NULL; } static bool rtsp_mp4_parse_samples(struct rtsp_mp4_state *st, const struct mp4_box *stbl) { struct mp4_box stsz, stsc, stts, ctts, stss, chunk_box; uint32_t *sizes = NULL; uint64_t *chunk_offsets = NULL, duration_units = 0; struct rtsp_mp4_stsc *stsc_entries = NULL; int64_t *pts = NULL, min_pts; size_t sample_count = 0, chunk_count = 0, stsc_count = 0; size_t i, chunk = 0, sample = 0, start_index = 0, active_count; uint64_t duration_us; bool has_ctts, has_stss; if (!rtsp_mp4_find_child(stbl->buf, stbl->len, MP4_TAG('s', 't', 's', 'z'), &stsz) || !rtsp_mp4_find_child(stbl->buf, stbl->len, MP4_TAG('s', 't', 's', 'c'), &stsc) || !rtsp_mp4_find_child(stbl->buf, stbl->len, MP4_TAG('s', 't', 't', 's'), &stts)) { return false; } has_ctts = rtsp_mp4_find_child(stbl->buf, stbl->len, MP4_TAG('c', 't', 't', 's'), &ctts); has_stss = rtsp_mp4_find_child(stbl->buf, stbl->len, MP4_TAG('s', 't', 's', 's'), &stss); if (!rtsp_mp4_find_child(stbl->buf, stbl->len, MP4_TAG('s', 't', 'c', 'o'), &chunk_box) && !rtsp_mp4_find_child(stbl->buf, stbl->len, MP4_TAG('c', 'o', '6', '4'), &chunk_box)) { return false; } sizes = rtsp_mp4_parse_stsz(&stsz, &sample_count); chunk_offsets = rtsp_mp4_parse_chunk_offsets(&chunk_box, &chunk_count); stsc_entries = rtsp_mp4_parse_stsc(&stsc, &stsc_count); pts = rtsp_mp4_parse_pts(&stts, has_ctts ? &ctts : NULL, sample_count, &duration_units); st->samples = (struct rtsp_mp4_sample *) mg_calloc(sample_count, sizeof(*st->samples)); if (sizes == NULL || chunk_offsets == NULL || stsc_entries == NULL || pts == NULL || st->samples == NULL) { goto fail; } for (i = 0; i < stsc_count; i++) { uint32_t first_chunk = stsc_entries[i].first_chunk; uint32_t last_chunk = i + 1U < stsc_count ? stsc_entries[i + 1U].first_chunk - 1U : (uint32_t) chunk_count; uint32_t chunk_no; if (first_chunk == 0 || first_chunk > last_chunk) goto fail; for (chunk_no = first_chunk; chunk_no <= last_chunk; chunk_no++) { uint64_t offset; uint32_t sample_no; if ((size_t) chunk_no > chunk_count) goto fail; offset = chunk_offsets[chunk_no - 1U]; for (sample_no = 0; sample_no < stsc_entries[i].samples_per_chunk; sample_no++) { if (sample >= sample_count) goto fail; st->samples[sample].offset = offset; st->samples[sample].size = sizes[sample]; if (offset > st->file.len || sizes[sample] > st->file.len - (size_t) offset) goto fail; offset += sizes[sample]; sample++; } chunk++; } } if (sample != sample_count || chunk != chunk_count) goto fail; for (i = 0; i < sample_count; i++) st->samples[i].keyframe = !has_stss; if (has_stss) { uint32_t sync_count; size_t pos = 8U; if (stss.len < 8U) goto fail; sync_count = rtsp_mp4_u32(stss.buf + 4U); for (i = 0; i < sync_count; i++, pos += 4U) { uint32_t sample_no; if (pos + 4U > stss.len) goto fail; sample_no = rtsp_mp4_u32(stss.buf + pos); if (sample_no > 0 && sample_no <= sample_count) st->samples[sample_no - 1U].keyframe = true; } } min_pts = pts[0]; for (i = 0; i < sample_count; i++) { if (pts[i] < min_pts) min_pts = pts[i]; } for (i = 0; i < sample_count; i++) { st->samples[i].ts = (uint64_t) (pts[i] - min_pts); st->samples[i].pts_us = rtsp_scale_us(st->samples[i].ts, st->timescale); } while (start_index < sample_count && !st->samples[start_index].keyframe) start_index++; if (start_index >= sample_count) start_index = 0; st->sample_count = sample_count; st->start_index = start_index; st->sample_index = start_index; duration_us = rtsp_scale_us(duration_units, st->timescale) > st->samples[start_index].pts_us ? rtsp_scale_us(duration_units, st->timescale) - st->samples[start_index].pts_us : rtsp_scale_us(duration_units, st->timescale); active_count = sample_count - start_index; if (active_count == 0) active_count = sample_count; if (duration_us == 0) duration_us = 1000000ULL / 30ULL; st->fps = (unsigned) ((active_count * 1000000ULL + duration_us / 2U) / duration_us); if (st->fps == 0) st->fps = 30U; mg_free(sizes); mg_free(chunk_offsets); mg_free(stsc_entries); mg_free(pts); return true; fail: mg_free(sizes); mg_free(chunk_offsets); mg_free(stsc_entries); mg_free(pts); mg_free(st->samples); st->samples = NULL; return false; } static bool rtsp_mp4_parse_audio_samples(struct rtsp_mp4_state *st, const struct mp4_box *stbl) { struct mp4_box stsz, stsc, stts, ctts, chunk_box; uint32_t *sizes = NULL; uint64_t *chunk_offsets = NULL, duration_units = 0; struct rtsp_mp4_stsc *stsc_entries = NULL; int64_t *pts = NULL, min_pts; size_t sample_count = 0, chunk_count = 0, stsc_count = 0; size_t i, chunk = 0, sample = 0; bool has_ctts; if (!rtsp_mp4_find_child(stbl->buf, stbl->len, MP4_TAG('s', 't', 's', 'z'), &stsz) || !rtsp_mp4_find_child(stbl->buf, stbl->len, MP4_TAG('s', 't', 's', 'c'), &stsc) || !rtsp_mp4_find_child(stbl->buf, stbl->len, MP4_TAG('s', 't', 't', 's'), &stts)) { return false; } has_ctts = rtsp_mp4_find_child(stbl->buf, stbl->len, MP4_TAG('c', 't', 't', 's'), &ctts); if (!rtsp_mp4_find_child(stbl->buf, stbl->len, MP4_TAG('s', 't', 'c', 'o'), &chunk_box) && !rtsp_mp4_find_child(stbl->buf, stbl->len, MP4_TAG('c', 'o', '6', '4'), &chunk_box)) { return false; } sizes = rtsp_mp4_parse_stsz(&stsz, &sample_count); chunk_offsets = rtsp_mp4_parse_chunk_offsets(&chunk_box, &chunk_count); stsc_entries = rtsp_mp4_parse_stsc(&stsc, &stsc_count); pts = rtsp_mp4_parse_pts(&stts, has_ctts ? &ctts : NULL, sample_count, &duration_units); st->audio_samples = (struct rtsp_mp4_sample *) mg_calloc(sample_count, sizeof(*st->audio_samples)); if (sizes == NULL || chunk_offsets == NULL || stsc_entries == NULL || pts == NULL || st->audio_samples == NULL) { goto fail; } for (i = 0; i < stsc_count; i++) { uint32_t first_chunk = stsc_entries[i].first_chunk; uint32_t last_chunk = i + 1U < stsc_count ? stsc_entries[i + 1U].first_chunk - 1U : (uint32_t) chunk_count; uint32_t chunk_no; if (first_chunk == 0 || first_chunk > last_chunk) goto fail; for (chunk_no = first_chunk; chunk_no <= last_chunk; chunk_no++) { uint64_t offset; uint32_t sample_no; if ((size_t) chunk_no > chunk_count) goto fail; offset = chunk_offsets[chunk_no - 1U]; for (sample_no = 0; sample_no < stsc_entries[i].samples_per_chunk; sample_no++) { if (sample >= sample_count) goto fail; st->audio_samples[sample].offset = offset; st->audio_samples[sample].size = sizes[sample]; st->audio_samples[sample].keyframe = true; if (offset > st->file.len || sizes[sample] > st->file.len - (size_t) offset) { goto fail; } offset += sizes[sample]; sample++; } chunk++; } } if (sample != sample_count || chunk != chunk_count) goto fail; min_pts = pts[0]; for (i = 0; i < sample_count; i++) { if (pts[i] < min_pts) min_pts = pts[i]; } for (i = 0; i < sample_count; i++) { st->audio_samples[i].ts = (uint64_t) (pts[i] - min_pts); st->audio_samples[i].pts_us = rtsp_scale_us(st->audio_samples[i].ts, st->audio_timescale); } st->audio_sample_count = sample_count; st->audio_sample_index = 0; mg_free(sizes); mg_free(chunk_offsets); mg_free(stsc_entries); mg_free(pts); return true; fail: mg_free(sizes); mg_free(chunk_offsets); mg_free(stsc_entries); mg_free(pts); mg_free(st->audio_samples); st->audio_samples = NULL; return false; } static bool rtsp_mp4_parse_file(struct rtsp_mp4_state *st) { struct mp4_box root_moov, trak; size_t ofs = 0; if (!rtsp_mp4_find_child((const uint8_t *) st->file.buf, st->file.len, MP4_TAG('m', 'o', 'o', 'v'), &root_moov)) { return false; } while (rtsp_mp4_next_box(root_moov.buf, root_moov.len, &ofs, &trak)) { struct mp4_box mdia, mdhd, minf, stbl, stsd; uint32_t track_type; if (trak.type != MP4_TAG('t', 'r', 'a', 'k')) continue; if (!rtsp_mp4_find_child(trak.buf, trak.len, MP4_TAG('m', 'd', 'i', 'a'), &mdia) || !rtsp_mp4_find_child(mdia.buf, mdia.len, MP4_TAG('m', 'd', 'h', 'd'), &mdhd) || !rtsp_mp4_find_child(mdia.buf, mdia.len, MP4_TAG('m', 'i', 'n', 'f'), &minf) || !rtsp_mp4_find_child(minf.buf, minf.len, MP4_TAG('s', 't', 'b', 'l'), &stbl) || !rtsp_mp4_find_child(stbl.buf, stbl.len, MP4_TAG('s', 't', 's', 'd'), &stsd)) { continue; } track_type = rtsp_mp4_track_type(&mdia); if (track_type == MP4_TAG('v', 'i', 'd', 'e') && st->samples == NULL) { if (!rtsp_mp4_parse_mdhd(st, &mdhd) || !rtsp_mp4_parse_stsd(st, &stsd) || !rtsp_mp4_parse_samples(st, &stbl)) { return false; } } else if (track_type == MP4_TAG('s', 'o', 'u', 'n') && st->audio_samples == NULL) { if (!rtsp_mp4_parse_audio_mdhd(st, &mdhd) || !rtsp_mp4_parse_audio_stsd(st, &stsd) || !rtsp_mp4_parse_audio_samples(st, &stbl)) { st->audio_rate = 0; st->audio_channels = 0; mg_free(st->audio_config); st->audio_config = NULL; st->audio_config_len = 0; mg_free(st->audio_samples); st->audio_samples = NULL; st->audio_sample_count = 0; st->audio_sample_index = 0; } } } return st->samples != NULL && st->sps != NULL && st->pps != NULL; } static struct rtsp_mp4_state *rtsp_mp4_find_state(const char *path) { struct rtsp_mp4_state *st; for (st = s_rtsp_mp4_states; st != NULL; st = st->next) { if (st->path != NULL && path != NULL && strcmp(st->path, path) == 0) return st; } return NULL; } static struct rtsp_mp4_state *rtsp_mp4_get_state(const char *path) { struct rtsp_mp4_state *st; if (path == NULL) return NULL; st = rtsp_mp4_find_state(path); if (st != NULL) return st; st = (struct rtsp_mp4_state *) mg_calloc(1, sizeof(*st)); if (st == NULL) return NULL; st->path = rtsp_strdup(path); if (st->path == NULL) goto fail; st->file = mg_file_read(&mg_fs_posix, path); if (st->file.buf == NULL || st->file.len == 0) goto fail; if (!rtsp_mp4_parse_file(st)) goto fail; st->next = s_rtsp_mp4_states; s_rtsp_mp4_states = st; return st; fail: rtsp_mp4_free_state(st); return NULL; } static void rtsp_mp4_reset(const char *path) { struct rtsp_mp4_state *st = rtsp_mp4_find_state(path); if (st == NULL) return; st->sample_index = st->start_index; st->audio_sample_index = 0; } static bool rtsp_mp4_ensure_au(struct rtsp_mp4_state *st, size_t cap) { uint8_t *buf; if (cap <= st->au_cap) return true; buf = (uint8_t *) mg_calloc(cap, 1U); if (buf == NULL) return false; mg_free(st->au); st->au = buf; st->au_cap = cap; return true; } bool mg_rtsp_get_mp4_frame(struct mg_rtsp_frame *frame, void *fn_data) { const char *path = (const char *) fn_data; struct rtsp_mp4_state *st = rtsp_mp4_get_state(path); struct rtsp_mp4_sample *sample; const uint8_t *src; size_t remaining, au_len = 0, prefix_len = 0; if (frame == NULL || st == NULL || st->sample_count == 0) return false; if (st->sample_index >= st->sample_count) return false; sample = &st->samples[st->sample_index]; prefix_len = sample->keyframe ? st->param_sets_len : 0; if (!rtsp_mp4_ensure_au(st, prefix_len + sample->size * 2U + 16U)) return false; if (prefix_len > 0) { memcpy(st->au, st->param_sets, prefix_len); au_len = prefix_len; } src = (const uint8_t *) st->file.buf + sample->offset; remaining = sample->size; while (remaining > (size_t) st->nal_len_size) { uint32_t nal_len = 0; unsigned i; for (i = 0; i < st->nal_len_size; i++) nal_len = (nal_len << 8) | src[i]; src += st->nal_len_size; remaining -= st->nal_len_size; if (nal_len == 0 || nal_len > remaining || au_len + 4U + nal_len > st->au_cap) return false; memcpy(st->au + au_len, "\0\0\0\1", 4U); au_len += 4U; memcpy(st->au + au_len, src, nal_len); au_len += nal_len; src += nal_len; remaining -= nal_len; } if (au_len == 0) return false; frame->data = st->au; frame->len = au_len; frame->pts_us = sample->pts_us >= st->samples[st->start_index].pts_us ? sample->pts_us - st->samples[st->start_index].pts_us : 0; frame->keyframe = sample->keyframe; st->sample_index++; return true; } bool mg_rtsp_get_mp4_audio(struct mg_rtsp_audio *audio, void *fn_data) { const char *path = (const char *) fn_data; struct rtsp_mp4_state *st = rtsp_mp4_get_state(path); struct rtsp_mp4_sample *sample; if (audio == NULL || st == NULL || st->audio_samples == NULL || st->audio_sample_count == 0 || st->audio_sample_index >= st->audio_sample_count) { return false; } sample = &st->audio_samples[st->audio_sample_index++]; audio->data = (const uint8_t *) st->file.buf + sample->offset; audio->len = sample->size; audio->pts_us = sample->pts_us; return true; } static uint64_t rtsp_mp4_track_period_ms(const struct rtsp_mp4_sample *samples, size_t sample_count, size_t sample_index, uint64_t fallback_ms) { uint64_t delta_us; if (samples == NULL || sample_count == 0 || sample_index >= sample_count) { return fallback_ms == 0 ? 1U : fallback_ms; } if (sample_index + 1U < sample_count && samples[sample_index + 1U].pts_us > samples[sample_index].pts_us) { delta_us = samples[sample_index + 1U].pts_us - samples[sample_index].pts_us; } else if (sample_index > 0 && samples[sample_index].pts_us > samples[sample_index - 1U].pts_us) { delta_us = samples[sample_index].pts_us - samples[sample_index - 1U].pts_us; } else { delta_us = (fallback_ms == 0 ? 1U : fallback_ms) * 1000ULL; } return (delta_us + 500ULL) / 1000ULL == 0 ? 1U : (delta_us + 500ULL) / 1000ULL; } static void bw_init(struct h264_bw *bw, uint8_t *buf, size_t cap) { memset(bw, 0, sizeof(*bw)); bw->buf = buf; bw->cap = cap; } static void bw_put_bit(struct h264_bw *bw, unsigned bit) { if (bw->oom) return; if (bw->len >= bw->cap) { bw->oom = true; return; } if (bw->bitpos == 0) bw->buf[bw->len] = 0; if (bit) bw->buf[bw->len] |= (uint8_t) (1U << (7U - bw->bitpos)); bw->bitpos++; if (bw->bitpos == 8) { bw->bitpos = 0; bw->len++; } } static void bw_put_bits(struct h264_bw *bw, unsigned count, uint32_t value) { while (count > 0) { count--; bw_put_bit(bw, (value >> count) & 1U); } } static void bw_put_ue(struct h264_bw *bw, uint32_t value) { uint32_t code_num = value + 1U; unsigned leading_zero_bits = 0; uint32_t tmp = code_num; while ((tmp >>= 1U) != 0U) leading_zero_bits++; { unsigned i; for (i = 0; i < leading_zero_bits; i++) bw_put_bit(bw, 0); } bw_put_bits(bw, leading_zero_bits + 1U, code_num); } static void bw_put_se(struct h264_bw *bw, int32_t value) { uint32_t code_num = value <= 0 ? (uint32_t) (-value * 2) : (uint32_t) (value * 2 - 1); bw_put_ue(bw, code_num); } static void bw_align_zero(struct h264_bw *bw) { if (bw->oom) return; if (bw->bitpos != 0) { bw->bitpos = 0; bw->len++; } } static void bw_put_byte(struct h264_bw *bw, uint8_t byte) { if (bw->oom) return; bw_align_zero(bw); if (bw->len >= bw->cap) { bw->oom = true; return; } bw->buf[bw->len++] = byte; } static void bw_rbsp_trailing_bits(struct h264_bw *bw) { bw_put_bit(bw, 1U); while (bw->bitpos != 0) bw_put_bit(bw, 0U); } static size_t h264_nal_from_rbsp(uint8_t nal_header, const uint8_t *rbsp, size_t rbsp_len, uint8_t *out, size_t out_cap) { size_t i, len = 0, zeros = 0; if (out_cap == 0) return 0; out[len++] = nal_header; for (i = 0; i < rbsp_len && len < out_cap; i++) { uint8_t byte = rbsp[i]; if (zeros >= 2 && byte <= 3) { if (len >= out_cap) return 0; out[len++] = 3; zeros = 0; } if (len >= out_cap) return 0; out[len++] = byte; zeros = byte == 0 ? zeros + 1 : 0; } return i == rbsp_len ? len : 0; } static bool h264_build_sps(struct h264_testsrc_state *st) { struct h264_bw bw; uint8_t rbsp[32]; bw_init(&bw, rbsp, sizeof(rbsp)); bw_put_bits(&bw, 8, 66U); bw_put_bit(&bw, 1U); bw_put_bit(&bw, 1U); bw_put_bit(&bw, 0U); bw_put_bit(&bw, 0U); bw_put_bit(&bw, 0U); bw_put_bit(&bw, 0U); bw_put_bits(&bw, 2, 0U); bw_put_bits(&bw, 8, 31U); bw_put_ue(&bw, 0U); bw_put_ue(&bw, 0U); bw_put_ue(&bw, 0U); bw_put_ue(&bw, 0U); bw_put_ue(&bw, 2U); bw_put_bit(&bw, 0U); bw_put_ue(&bw, st->mb_width - 1U); bw_put_ue(&bw, st->mb_height - 1U); bw_put_bit(&bw, 1U); bw_put_bit(&bw, 1U); bw_put_bit(&bw, 0U); bw_put_bit(&bw, 0U); bw_rbsp_trailing_bits(&bw); if (bw.oom) return false; st->sps_len = h264_nal_from_rbsp(0x67U, rbsp, bw.len, st->sps, sizeof(st->sps)); return st->sps_len > 0; } static bool h264_build_pps(struct h264_testsrc_state *st) { struct h264_bw bw; uint8_t rbsp[16]; bw_init(&bw, rbsp, sizeof(rbsp)); bw_put_ue(&bw, 0U); bw_put_ue(&bw, 0U); bw_put_bit(&bw, 0U); bw_put_bit(&bw, 0U); bw_put_ue(&bw, 0U); bw_put_ue(&bw, 0U); bw_put_ue(&bw, 0U); bw_put_bit(&bw, 0U); bw_put_bits(&bw, 2, 0U); bw_put_se(&bw, 0); bw_put_se(&bw, 0); bw_put_se(&bw, 0); bw_put_bit(&bw, 1U); bw_put_bit(&bw, 0U); bw_put_bit(&bw, 0U); bw_rbsp_trailing_bits(&bw); if (bw.oom) return false; st->pps_len = h264_nal_from_rbsp(0x68U, rbsp, bw.len, st->pps, sizeof(st->pps)); return st->pps_len > 0; } static bool h264_testsrc_init(struct h264_testsrc_state *st, unsigned width, unsigned height, unsigned fps) { memset(st, 0, sizeof(*st)); st->width = width == 0 ? 640U : width; st->height = height == 0 ? 480U : height; st->fps = fps == 0 ? 30U : fps; st->mb_width = (st->width + 15U) / 16U; st->mb_height = (st->height + 15U) / 16U; st->box_w = st->mb_width / 8U; st->box_h = st->mb_height / 8U; if (st->box_w < 4U) st->box_w = 4U; if (st->box_h < 4U) st->box_h = 4U; if (st->box_w > st->mb_width) st->box_w = st->mb_width; if (st->box_h > st->mb_height) st->box_h = st->mb_height; st->dx = 1; st->dy = 1; if (!h264_build_sps(st) || !h264_build_pps(st)) return false; st->inited = true; return true; } static void h264_append_start_code(uint8_t *buf, size_t *len) { buf[(*len)++] = 0; buf[(*len)++] = 0; buf[(*len)++] = 0; buf[(*len)++] = 1; } static void h264_emit_pcm_macroblock(struct h264_bw *bw, const struct h264_color *color) { unsigned i; bw_put_ue(bw, 25U); bw_align_zero(bw); for (i = 0; i < 16U * 16U; i++) bw_put_byte(bw, color->y); for (i = 0; i < 8U * 8U; i++) bw_put_byte(bw, color->cb); for (i = 0; i < 8U * 8U; i++) bw_put_byte(bw, color->cr); } static bool h264_build_access_unit(struct h264_testsrc_state *st, struct mg_rtsp_frame *frame) { static const struct h264_color kGreen = {145U, 54U, 34U}; static const struct h264_color kYellow = {210U, 16U, 146U}; struct h264_bw bw; uint8_t idr[H264_MAX_AU]; size_t au_len = 0, idr_len; unsigned mb_x, mb_y; bw_init(&bw, st->rbsp, sizeof(st->rbsp)); bw_put_ue(&bw, 0U); bw_put_ue(&bw, 2U); bw_put_ue(&bw, 0U); bw_put_bits(&bw, 4U, 0U); bw_put_ue(&bw, 0U); bw_put_bits(&bw, 4U, 0U); bw_put_bit(&bw, 0U); bw_put_bit(&bw, 0U); bw_put_se(&bw, 0); bw_put_ue(&bw, 1U); for (mb_y = 0; mb_y < st->mb_height; mb_y++) { for (mb_x = 0; mb_x < st->mb_width; mb_x++) { bool in_box = mb_x >= (unsigned) st->box_x && mb_x < (unsigned) st->box_x + st->box_w && mb_y >= (unsigned) st->box_y && mb_y < (unsigned) st->box_y + st->box_h; h264_emit_pcm_macroblock(&bw, in_box ? &kYellow : &kGreen); } } bw_rbsp_trailing_bits(&bw); if (bw.oom) return false; idr_len = h264_nal_from_rbsp(0x65U, st->rbsp, bw.len, idr, sizeof(idr)); if (idr_len == 0) return false; h264_append_start_code(st->au, &au_len); memcpy(st->au + au_len, st->sps, st->sps_len); au_len += st->sps_len; h264_append_start_code(st->au, &au_len); memcpy(st->au + au_len, st->pps, st->pps_len); au_len += st->pps_len; h264_append_start_code(st->au, &au_len); memcpy(st->au + au_len, idr, idr_len); au_len += idr_len; frame->data = st->au; frame->len = au_len; frame->pts_us = (st->frame_no * 1000000ULL) / st->fps; frame->keyframe = true; st->frame_no++; st->box_x += st->dx; st->box_y += st->dy; if (st->box_x < 0) { st->box_x = 0; st->dx = 1; } else if ((unsigned) st->box_x + st->box_w >= st->mb_width) { st->box_x = (int) (st->mb_width - st->box_w); st->dx = -1; } if (st->box_y < 0) { st->box_y = 0; st->dy = 1; } else if ((unsigned) st->box_y + st->box_h >= st->mb_height) { st->box_y = (int) (st->mb_height - st->box_h); st->dy = -1; } return true; } bool mg_rtsp_h264_testsrc_next_frame(struct mg_rtsp_frame *frame, unsigned width, unsigned height, unsigned fps) { static struct h264_testsrc_state st; if (frame == NULL) return false; if (!st.inited || st.width != width || st.height != height || st.fps != fps) { if (!h264_testsrc_init(&st, width, height, fps)) return false; } return h264_build_access_unit(&st, frame); } static size_t rtsp_hex_encode(const uint8_t *buf, size_t len, char *dst, size_t dst_len) { static const char *hex = "0123456789ABCDEF"; size_t i, out = 0; if (dst_len == 0) return 0; for (i = 0; i < len && out + 2U < dst_len; i++) { dst[out++] = hex[buf[i] >> 4]; dst[out++] = hex[buf[i] & 0x0fU]; } dst[out] = '\0'; return out; } static bool rtsp_aac_lc_config_parse(const uint8_t cfg[2], unsigned *rate, unsigned *channels) { static const unsigned rates[] = {96000U, 88200U, 64000U, 48000U, 44100U, 32000U, 24000U, 22050U, 16000U, 12000U, 11025U, 8000U, 7350U}; static const unsigned channel_counts[] = {0U, 1U, 2U, 3U, 4U, 5U, 6U, 8U}; unsigned aot = (cfg[0] >> 3) & 0x1fU; unsigned sfi = ((unsigned) (cfg[0] & 0x07U) << 1) | (cfg[1] >> 7); unsigned cc = (cfg[1] >> 3) & 0x0fU; if (rate == NULL || channels == NULL) return false; if (aot != 2U || sfi >= sizeof(rates) / sizeof(rates[0])) return false; if (cc == 0U || cc >= sizeof(channel_counts) / sizeof(channel_counts[0])) { return false; } *rate = rates[sfi]; *channels = channel_counts[cc]; return *channels > 0; } static uint64_t rtsp_aac_frame_period_ms(unsigned sample_rate) { uint64_t period_ms; if (sample_rate == 0U) return 20U; period_ms = (1024000ULL + sample_rate / 2U) / sample_rate; return period_ms == 0 ? 1U : period_ms; } static uint32_t rtsp_audio_rtp_timestamp(unsigned sample_rate, uint64_t pts_us) { if (sample_rate == 0U) return 0U; return (uint32_t) ((pts_us * sample_rate + 500000ULL) / 1000000ULL); } static bool rtsp_build_sdp(struct rtsp_server *srv) { char sps64[128], pps64[128], profile_level_id[7]; char audio_cfg[128]; struct mg_rtsp_frame frame; struct rtsp_nal nals[RTSP_MAX_NALS]; const uint8_t *sps = NULL, *pps = NULL; struct rtsp_mp4_state *st = NULL, *audio_st = NULL; size_t i, sps_len = 0, pps_len = 0, nals_count; unsigned fps, audio_rate = 0, audio_channels = 0; srv->has_audio = srv->opts.audio_fn != NULL; if (srv->has_audio && !rtsp_aac_lc_config_parse(srv->opts.aac_lc_config, &audio_rate, &audio_channels)) { return false; } if (srv->opts.fn == mg_rtsp_get_mp4_frame && srv->opts.fn_data != NULL) { st = rtsp_mp4_get_state((const char *) srv->opts.fn_data); if (st == NULL || st->sps == NULL || st->pps == NULL) return false; if (srv->opts.width == 0) srv->opts.width = st->width; if (srv->opts.height == 0) srv->opts.height = st->height; if (srv->opts.fps == 0) srv->opts.fps = st->fps; sps = st->sps; pps = st->pps; sps_len = st->sps_len; pps_len = st->pps_len; } else { if (srv->opts.fn == NULL) return false; if (!srv->opts.fn(&frame, srv->opts.fn_data) || frame.data == NULL || frame.len == 0) { return false; } nals_count = h264_split_annexb((const uint8_t *) frame.data, frame.len, nals, sizeof(nals) / sizeof(nals[0])); for (i = 0; i < nals_count; i++) { if (nals[i].len == 0) continue; switch (nals[i].ptr[0] & 0x1fU) { case 7: sps = nals[i].ptr; sps_len = nals[i].len; break; case 8: pps = nals[i].ptr; pps_len = nals[i].len; break; default: break; } } if (sps == NULL || pps == NULL || sps_len < 4) return false; } fps = srv->opts.fps == 0 ? 30U : srv->opts.fps; if (srv->has_audio && srv->opts.audio_fn == mg_rtsp_get_mp4_audio && srv->opts.audio_fn_data != NULL) { audio_st = rtsp_mp4_get_state((const char *) srv->opts.audio_fn_data); if (audio_st == NULL || audio_st->audio_samples == NULL || audio_st->audio_sample_count == 0) { return false; } } profile_level_id[0] = '\0'; mg_snprintf(profile_level_id, sizeof(profile_level_id), "%02X%02X%02X", sps[1], sps[2], sps[3]); sps64[mg_base64_encode(sps, sps_len, sps64, sizeof(sps64) - 1U)] = '\0'; pps64[mg_base64_encode(pps, pps_len, pps64, sizeof(pps64) - 1U)] = '\0'; mg_snprintf( srv->sdp, sizeof(srv->sdp), "v=0\r\n" "o=- 0 0 IN IP4 0.0.0.0\r\n" "s=Mongoose RTSP Stream\r\n" "t=0 0\r\n" "a=control:*\r\n" "m=video 0 RTP/AVP %u\r\n" "c=IN IP4 0.0.0.0\r\n" "a=rtpmap:%u H264/90000\r\n" "a=fmtp:%u packetization-mode=1;profile-level-id=%s;" "sprop-parameter-sets=%s,%s\r\n" "a=framerate:%u\r\n" "a=framesize:%u %u-%u\r\n" "a=control:trackID=0\r\n", RTSP_VIDEO_RTP_PT, RTSP_VIDEO_RTP_PT, RTSP_VIDEO_RTP_PT, profile_level_id, sps64, pps64, fps, RTSP_VIDEO_RTP_PT, srv->opts.width, srv->opts.height); if (srv->has_audio) { rtsp_hex_encode(srv->opts.aac_lc_config, sizeof(srv->opts.aac_lc_config), audio_cfg, sizeof(audio_cfg)); mg_snprintf( srv->sdp + strlen(srv->sdp), sizeof(srv->sdp) - strlen(srv->sdp), "m=audio 0 RTP/AVP %u\r\n" "c=IN IP4 0.0.0.0\r\n" "a=rtpmap:%u MPEG4-GENERIC/%u/%u\r\n" "a=fmtp:%u streamtype=5;profile-level-id=1;mode=AAC-hbr;" "sizelength=13;indexlength=3;indexdeltalength=3;config=%s\r\n" "a=control:trackID=1\r\n", RTSP_AUDIO_RTP_PT, RTSP_AUDIO_RTP_PT, audio_rate, audio_channels, RTSP_AUDIO_RTP_PT, audio_cfg); } return true; } static void rtsp_send_rtp_packet(struct mg_connection *c, struct rtsp_track_conn *tc, uint8_t pt, bool marker, uint32_t timestamp, const void *payload, size_t payload_len) { uint8_t packet[4 + 12 + RTSP_RTP_PAYLOAD_MAX + 2]; uint16_t packet_len = (uint16_t) (12U + payload_len); uint16_t net16; uint32_t net32; if (payload_len > sizeof(packet) - 16U) return; packet[0] = '$'; packet[1] = (uint8_t) tc->rtp_channel; net16 = mg_htons(packet_len); memcpy(packet + 2, &net16, sizeof(net16)); packet[4] = 0x80U; packet[5] = (uint8_t) ((marker ? 0x80U : 0U) | pt); net16 = mg_htons(tc->rtp_seq++); memcpy(packet + 6, &net16, sizeof(net16)); net32 = mg_htonl(timestamp); memcpy(packet + 8, &net32, sizeof(net32)); net32 = mg_htonl(tc->ssrc); memcpy(packet + 12, &net32, sizeof(net32)); memcpy(packet + 16, payload, payload_len); mg_send(c, packet, (size_t) packet_len + 4U); } static void rtsp_send_h264_nal(struct mg_connection *c, struct rtsp_track_conn *tc, const uint8_t *nal, size_t nal_len, bool last_nal, uint32_t timestamp) { if (nal_len == 0) return; if (nal_len <= RTSP_RTP_PAYLOAD_MAX) { rtsp_send_rtp_packet(c, tc, RTSP_VIDEO_RTP_PT, last_nal, timestamp, nal, nal_len); } else { size_t off = 1; uint8_t fu[2 + RTSP_RTP_PAYLOAD_MAX]; uint8_t nal_type = nal[0] & 0x1fU; uint8_t nri = nal[0] & 0x60U; size_t max_chunk = RTSP_RTP_PAYLOAD_MAX - 2U; fu[0] = (uint8_t) (nri | 28U); while (off < nal_len) { size_t chunk = nal_len - off; bool first = off == 1U; bool last = off + chunk >= nal_len; if (chunk > max_chunk) chunk = max_chunk; last = off + chunk >= nal_len; fu[1] = (uint8_t) ((first ? 0x80U : 0U) | (last ? 0x40U : 0U) | nal_type); memcpy(fu + 2, nal + off, chunk); rtsp_send_rtp_packet(c, tc, RTSP_VIDEO_RTP_PT, last_nal && last, timestamp, fu, chunk + 2U); off += chunk; } } } static void rtsp_send_access_unit(struct mg_connection *c, struct rtsp_track_conn *tc, const struct mg_rtsp_frame *frame) { struct rtsp_nal nals[RTSP_MAX_NALS]; size_t i, count; uint32_t timestamp = (uint32_t) ((frame->pts_us * RTSP_VIDEO_RTP_CLOCK) / 1000000ULL); count = h264_split_annexb((const uint8_t *) frame->data, frame->len, nals, sizeof(nals) / sizeof(nals[0])); for (i = 0; i < count; i++) { rtsp_send_h264_nal(c, tc, nals[i].ptr, nals[i].len, i + 1U == count, timestamp); } } static void rtsp_send_audio_sample(struct mg_connection *c, struct rtsp_track_conn *tc, const uint8_t *sample, size_t sample_len, uint32_t timestamp) { uint8_t payload[4 + RTSP_RTP_PAYLOAD_MAX]; if (sample == NULL || sample_len == 0 || sample_len + 4U > sizeof(payload)) return; payload[0] = 0; payload[1] = 16; payload[2] = (uint8_t) (sample_len >> 5); payload[3] = (uint8_t) ((sample_len & 0x1fU) << 3); memcpy(payload + 4, sample, sample_len); rtsp_send_rtp_packet(c, tc, RTSP_AUDIO_RTP_PT, true, timestamp, payload, sample_len + 4U); } static void rtsp_handle_options(struct mg_connection *c, struct rtsp_conn *rc, const struct rtsp_request *req) { char headers[192]; (void) rc; mg_snprintf(headers, sizeof(headers), "Public: OPTIONS, DESCRIBE, SETUP, PLAY, PAUSE, TEARDOWN, " "GET_PARAMETER\r\n"); rtsp_send_status(c, req, 200, "OK", headers); } static void rtsp_handle_describe(struct mg_connection *c, struct rtsp_conn *rc, const struct rtsp_request *req) { char headers[128]; if (!rtsp_path_eq(req->uri, rc->srv->path)) { rtsp_send_status(c, req, 404, "Not Found", NULL); return; } mg_snprintf(headers, sizeof(headers), "Content-Type: application/sdp\r\n" "Content-Base: %.*s/\r\n", (int) req->uri.len, req->uri.buf); rtsp_send_response(c, req, 200, "OK", headers, rc->srv->sdp, strlen(rc->srv->sdp)); } static bool rtsp_any_setup_done(const struct rtsp_conn *rc) { size_t i; for (i = 0; i < RTSP_TRACK_COUNT; i++) { if (rc->tracks[i].setup_done) return true; } return false; } static void rtsp_handle_setup(struct mg_connection *c, struct rtsp_conn *rc, const struct rtsp_request *req) { char headers[160]; int track_id = rtsp_track_path_eq(req->uri, rc->srv->path); struct rtsp_track_conn *tc; if (track_id < 0 || (unsigned) track_id >= RTSP_TRACK_COUNT || ((unsigned) track_id == RTSP_AUDIO_TRACK && !rc->srv->has_audio)) { rtsp_send_status(c, req, 404, "Not Found", NULL); return; } if (!rtsp_casencontains(req->transport, "RTP/AVP/TCP")) { rtsp_send_status(c, req, 461, "Unsupported Transport", NULL); return; } tc = &rc->tracks[track_id]; rtsp_parse_interleaved_channels(req->transport, &tc->rtp_channel, &tc->rtcp_channel); tc->setup_done = true; tc->ended = false; mg_snprintf(headers, sizeof(headers), "Transport: RTP/AVP/TCP;unicast;interleaved=%u-%u\r\n" "Session: %s\r\n", tc->rtp_channel, tc->rtcp_channel, rc->session_id); rtsp_send_status(c, req, 200, "OK", headers); } static void rtsp_handle_play(struct mg_connection *c, struct rtsp_conn *rc, const struct rtsp_request *req) { char headers[128]; size_t i; uint64_t now_ms; if (!rtsp_any_setup_done(rc)) { rtsp_send_status(c, req, 455, "Method Not Valid In This State", NULL); return; } if (!rtsp_session_matches(rc, req->session)) { rtsp_send_status(c, req, 454, "Session Not Found", NULL); return; } rc->playing = true; now_ms = mg_millis(); for (i = 0; i < RTSP_TRACK_COUNT; i++) { if (rc->tracks[i].setup_done) { rc->tracks[i].deadline_ms = now_ms; rc->tracks[i].ended = false; } } if (rc->srv->opts.fn == mg_rtsp_get_mp4_frame && rc->srv->opts.fn_data != NULL) { rtsp_mp4_reset((const char *) rc->srv->opts.fn_data); } if (rc->srv->opts.audio_fn == mg_rtsp_get_mp4_audio && rc->srv->opts.audio_fn_data != NULL) { rtsp_mp4_reset((const char *) rc->srv->opts.audio_fn_data); } mg_snprintf(headers, sizeof(headers), "Session: %s\r\nRange: npt=0.000-\r\n", rc->session_id); rtsp_send_status(c, req, 200, "OK", headers); } static void rtsp_handle_pause(struct mg_connection *c, struct rtsp_conn *rc, const struct rtsp_request *req) { char headers[64]; if (!rtsp_session_matches(rc, req->session)) { rtsp_send_status(c, req, 454, "Session Not Found", NULL); return; } rc->playing = false; mg_snprintf(headers, sizeof(headers), "Session: %s\r\n", rc->session_id); rtsp_send_status(c, req, 200, "OK", headers); } static void rtsp_handle_teardown(struct mg_connection *c, struct rtsp_conn *rc, const struct rtsp_request *req) { char headers[64]; if (!rtsp_session_matches(rc, req->session)) { rtsp_send_status(c, req, 454, "Session Not Found", NULL); return; } rc->playing = false; mg_snprintf(headers, sizeof(headers), "Session: %s\r\n", rc->session_id); rtsp_send_status(c, req, 200, "OK", headers); c->is_draining = 1; } static void rtsp_handle_get_parameter(struct mg_connection *c, struct rtsp_conn *rc, const struct rtsp_request *req) { char headers[64]; (void) rc; mg_snprintf(headers, sizeof(headers), "Session: %s\r\n", rc->session_id); rtsp_send_status(c, req, 200, "OK", headers); } static void rtsp_dispatch_request(struct mg_connection *c, struct rtsp_conn *rc, const struct rtsp_request *req) { if (rtsp_caseeq(req->method, "OPTIONS")) { rtsp_handle_options(c, rc, req); } else if (rtsp_caseeq(req->method, "DESCRIBE")) { rtsp_handle_describe(c, rc, req); } else if (rtsp_caseeq(req->method, "SETUP")) { rtsp_handle_setup(c, rc, req); } else if (rtsp_caseeq(req->method, "PLAY")) { rtsp_handle_play(c, rc, req); } else if (rtsp_caseeq(req->method, "PAUSE")) { rtsp_handle_pause(c, rc, req); } else if (rtsp_caseeq(req->method, "TEARDOWN")) { rtsp_handle_teardown(c, rc, req); } else if (rtsp_caseeq(req->method, "GET_PARAMETER")) { rtsp_handle_get_parameter(c, rc, req); } else { rtsp_send_status(c, req, 405, "Method Not Allowed", NULL); } } static void rtsp_handle_read(struct mg_connection *c) { struct rtsp_conn *rc = (struct rtsp_conn *) c->fn_data; for (;;) { if (c->recv.len >= 4 && ((uint8_t *) c->recv.buf)[0] == '$') { size_t frame_len = ((size_t) ((uint8_t *) c->recv.buf)[2] << 8) | ((uint8_t *) c->recv.buf)[3]; if (c->recv.len < frame_len + 4U) return; mg_iobuf_del(&c->recv, 0, frame_len + 4U); continue; } { struct rtsp_request req; int result = rtsp_parse_request((const char *) c->recv.buf, c->recv.len, &req); if (result == 0) return; if (result < 0) { c->is_closing = 1; return; } rtsp_dispatch_request(c, rc, &req); mg_iobuf_del(&c->recv, 0, req.msg_len); } } } static void rtsp_handle_poll(struct mg_connection *c, uint64_t now_ms) { struct rtsp_conn *rc = (struct rtsp_conn *) c->fn_data; struct rtsp_mp4_state *video_st = NULL, *audio_st = NULL; unsigned audio_rate = 0, audio_channels = 0; unsigned fps = rc->srv->opts.fps == 0 ? 30U : rc->srv->opts.fps; uint64_t video_fallback_ms = 1000U / fps; uint64_t audio_fallback_ms = 20U; unsigned burst = 0, max_burst = 128U; size_t send_limit = 256U * 1024U; if (!rc->playing) return; if (!rc->tracks[RTSP_VIDEO_TRACK].setup_done && !rc->tracks[RTSP_AUDIO_TRACK].setup_done) { return; } if (video_fallback_ms == 0) video_fallback_ms = 1; if (rc->srv->opts.fn == NULL) return; if (c->send.len >= send_limit) return; if (rc->srv->opts.fn == mg_rtsp_get_mp4_frame && rc->srv->opts.fn_data != NULL) { video_st = rtsp_mp4_get_state((const char *) rc->srv->opts.fn_data); if (video_st == NULL) { rc->playing = false; c->is_draining = 1; return; } } if (rc->srv->has_audio) { if (!rtsp_aac_lc_config_parse(rc->srv->opts.aac_lc_config, &audio_rate, &audio_channels)) { rc->playing = false; c->is_draining = 1; return; } (void) audio_channels; audio_fallback_ms = rtsp_aac_frame_period_ms(audio_rate); if (rc->srv->opts.audio_fn == mg_rtsp_get_mp4_audio && rc->srv->opts.audio_fn_data != NULL) { audio_st = rtsp_mp4_get_state((const char *) rc->srv->opts.audio_fn_data); if (audio_st == NULL) { rc->playing = false; c->is_draining = 1; return; } } } if (rc->tracks[RTSP_VIDEO_TRACK].setup_done && video_st != NULL && video_st->sample_index >= video_st->sample_count) { rc->tracks[RTSP_VIDEO_TRACK].ended = true; } if (rc->tracks[RTSP_AUDIO_TRACK].setup_done && audio_st != NULL && (audio_st->audio_samples == NULL || audio_st->audio_sample_index >= audio_st->audio_sample_count)) { rc->tracks[RTSP_AUDIO_TRACK].ended = true; } while (rc->tracks[RTSP_VIDEO_TRACK].setup_done && !rc->tracks[RTSP_VIDEO_TRACK].ended && c->send.len < send_limit && mg_timer_expired(&rc->tracks[RTSP_VIDEO_TRACK].deadline_ms, video_st != NULL ? rtsp_mp4_track_period_ms( video_st->samples, video_st->sample_count, video_st->sample_index, video_fallback_ms) : video_fallback_ms, now_ms)) { struct mg_rtsp_frame frame; if (!rc->srv->opts.fn(&frame, rc->srv->opts.fn_data) || frame.data == NULL || frame.len == 0) { rc->tracks[RTSP_VIDEO_TRACK].ended = true; break; } rtsp_send_access_unit(c, &rc->tracks[RTSP_VIDEO_TRACK], &frame); if (++burst >= max_burst) break; } while (rc->srv->has_audio && rc->tracks[RTSP_AUDIO_TRACK].setup_done && !rc->tracks[RTSP_AUDIO_TRACK].ended && c->send.len < send_limit && mg_timer_expired(&rc->tracks[RTSP_AUDIO_TRACK].deadline_ms, audio_st != NULL ? rtsp_mp4_track_period_ms( audio_st->audio_samples, audio_st->audio_sample_count, audio_st->audio_sample_index, audio_fallback_ms) : audio_fallback_ms, now_ms)) { struct mg_rtsp_audio audio; if (rc->srv->opts.audio_fn == NULL || !rc->srv->opts.audio_fn(&audio, rc->srv->opts.audio_fn_data) || audio.data == NULL || audio.len == 0) { rc->tracks[RTSP_AUDIO_TRACK].ended = true; break; } rtsp_send_audio_sample(c, &rc->tracks[RTSP_AUDIO_TRACK], audio.data, audio.len, rtsp_audio_rtp_timestamp(audio_rate, audio.pts_us)); if (++burst >= max_burst) break; } if ((!rc->tracks[RTSP_VIDEO_TRACK].setup_done || rc->tracks[RTSP_VIDEO_TRACK].ended) && (!rc->tracks[RTSP_AUDIO_TRACK].setup_done || rc->tracks[RTSP_AUDIO_TRACK].ended)) { rc->playing = false; c->is_draining = 1; } } static void rtsp_free_server(struct rtsp_server *srv) { if (srv == NULL) return; mg_free(srv->path); mg_free(srv); } static void rtsp_fn(struct mg_connection *c, int ev, void *ev_data) { if (c->is_listening) { if (ev == MG_EV_CLOSE) rtsp_free_server((struct rtsp_server *) c->fn_data); return; } switch (ev) { case MG_EV_ACCEPT: { struct rtsp_server *srv = (struct rtsp_server *) c->fn_data; struct rtsp_conn *rc = (struct rtsp_conn *) mg_calloc(1, sizeof(struct rtsp_conn)); if (rc == NULL) { c->is_closing = 1; break; } rc->srv = srv; rc->tracks[RTSP_VIDEO_TRACK].rtp_channel = 0; rc->tracks[RTSP_VIDEO_TRACK].rtcp_channel = 1; rc->tracks[RTSP_VIDEO_TRACK].rtp_seq = (uint16_t) c->id; rc->tracks[RTSP_VIDEO_TRACK].ssrc = 0x13572468U ^ (uint32_t) c->id; rc->tracks[RTSP_AUDIO_TRACK].rtp_channel = 2; rc->tracks[RTSP_AUDIO_TRACK].rtcp_channel = 3; rc->tracks[RTSP_AUDIO_TRACK].rtp_seq = (uint16_t) (c->id ^ 0x4000U); rc->tracks[RTSP_AUDIO_TRACK].ssrc = 0x24681357U ^ (uint32_t) c->id; mg_snprintf(rc->session_id, sizeof(rc->session_id), "%lu", c->id); c->fn_data = rc; break; } case MG_EV_READ: rtsp_handle_read(c); break; case MG_EV_POLL: rtsp_handle_poll(c, *(uint64_t *) ev_data); break; case MG_EV_CLOSE: mg_free(c->fn_data); break; default: break; } } struct mg_connection *mg_rtsp_listen(struct mg_mgr *mgr, const char *url, const struct mg_rtsp_opts *opts) { struct mg_connection *c; struct rtsp_server *srv; if (mgr == NULL || url == NULL || opts == NULL || opts->fn == NULL) return NULL; srv = (struct rtsp_server *) mg_calloc(1, sizeof(*srv)); if (srv == NULL) return NULL; srv->opts = *opts; srv->path = rtsp_strdup(mg_url_uri(url)); if (srv->path == NULL || !rtsp_build_sdp(srv)) { rtsp_free_server(srv); return NULL; } c = mg_listen(mgr, url, rtsp_fn, srv); if (c == NULL) { rtsp_free_server(srv); return NULL; } return c; }