diff --git a/tutorials/tcp/rtsp-video-stream/Makefile b/tutorials/tcp/rtsp-video-stream/Makefile new file mode 100644 index 00000000..e9c70119 --- /dev/null +++ b/tutorials/tcp/rtsp-video-stream/Makefile @@ -0,0 +1,25 @@ +PROG ?= example # Program we are building +DELETE = rm -rf # Command to remove files +OUT ?= -o $(PROG) # Compiler argument for output file +SOURCES = main.c mongoose.c rtsp.c # Source code files +CFLAGS = -W -Wall -Wextra -g -I. -include rtsp.h # Build options + +# Mongoose build options. See https://mongoose.ws/documentation/#build-options +#CFLAGS_MONGOOSE += -DMG_ENABLE_LINES + +ifeq ($(OS),Windows_NT) # Windows settings. Assume MinGW compiler. To use VC: make CC=cl CFLAGS=/MD OUT=/Feprog.exe + PROG ?= example.exe # Use .exe suffix for the binary + CC = gcc # Use MinGW gcc compiler + CFLAGS += -lws2_32 # Link against Winsock library + DELETE = cmd /C del /Q /F /S # Command prompt command to delete files + OUT ?= -o $(PROG) # Build output +endif + +all: $(PROG) # Default target. Build and run program + $(RUN) ./$(PROG) $(ARGS) + +$(PROG): $(SOURCES) # Build program from sources + $(CC) $(SOURCES) $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA) $(OUT) + +clean: # Cleanup. Delete built program and all build artifacts + $(DELETE) $(PROG) *.o *.obj *.exe *.dSYM diff --git a/tutorials/tcp/rtsp-video-stream/main.c b/tutorials/tcp/rtsp-video-stream/main.c new file mode 100644 index 00000000..a41adf33 --- /dev/null +++ b/tutorials/tcp/rtsp-video-stream/main.c @@ -0,0 +1,28 @@ +// Copyright (c) 2026 Cesanta Software Limited +// All rights reserved + +#include "mongoose.h" + +int main(void) { + struct mg_mgr mgr; + + struct mg_rtsp_opts opts = { + 0, + 0, + 0, + mg_rtsp_get_mp4_frame, + (void *) "test.mp4", + mg_rtsp_get_mp4_audio, + (void *) "test.mp4", + {0x12, 0x10}, + }; + + mg_mgr_init(&mgr); + mg_rtsp_listen(&mgr, "rtsp://0.0.0.0:8554/cam", &opts); + + while (true) { + mg_mgr_poll(&mgr, 10); + } + mg_mgr_free(&mgr); + return 0; +} diff --git a/tutorials/tcp/rtsp-video-stream/mongoose.c b/tutorials/tcp/rtsp-video-stream/mongoose.c new file mode 120000 index 00000000..5e522bbc --- /dev/null +++ b/tutorials/tcp/rtsp-video-stream/mongoose.c @@ -0,0 +1 @@ +../../../mongoose.c \ No newline at end of file diff --git a/tutorials/tcp/rtsp-video-stream/mongoose.h b/tutorials/tcp/rtsp-video-stream/mongoose.h new file mode 120000 index 00000000..ee4ac823 --- /dev/null +++ b/tutorials/tcp/rtsp-video-stream/mongoose.h @@ -0,0 +1 @@ +../../../mongoose.h \ No newline at end of file diff --git a/tutorials/tcp/rtsp-video-stream/rtsp.c b/tutorials/tcp/rtsp-video-stream/rtsp.c new file mode 100644 index 00000000..cc2d9955 --- /dev/null +++ b/tutorials/tcp/rtsp-video-stream/rtsp.c @@ -0,0 +1,2089 @@ +#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; +} diff --git a/tutorials/tcp/rtsp-video-stream/rtsp.h b/tutorials/tcp/rtsp-video-stream/rtsp.h new file mode 100644 index 00000000..948798d3 --- /dev/null +++ b/tutorials/tcp/rtsp-video-stream/rtsp.h @@ -0,0 +1,54 @@ +#pragma once +#include "mongoose.h" + +// Encoded video frame provided by user code +struct mg_rtsp_frame { + const void *data; // H.264 encoded frame (one access unit) + size_t len; // Frame size in bytes + uint64_t pts_us; // Presentation timestamp, microseconds + bool keyframe; // true for IDR frame +}; + +// Encoded audio frame provided by user code +struct mg_rtsp_audio { + const void *data; // AAC encoded frame, one access unit, no ADTS header + size_t len; // Frame size in bytes + uint64_t pts_us; // Presentation timestamp, microseconds +}; + +// Called by Mongoose when it needs the next video frame +// - frame->data must stay valid until the callback returns +// - Mongoose copies/packetizes it before returning +typedef bool (*mg_rtsp_next_frame_fn)(struct mg_rtsp_frame *frame, + void *fn_data); + +// Called by Mongoose when it needs the next audio frame +// - audio->data must stay valid until the callback returns +// - Mongoose copies/packetizes it before returning +typedef bool (*mg_rtsp_next_audio_fn)(struct mg_rtsp_audio *audio, + void *fn_data); + +// RTSP listener options (H.264 video, optional AAC audio) +struct mg_rtsp_opts { + unsigned width; // Optional, 0 if unknown + unsigned height; // Optional, 0 if unknown + unsigned fps; // Optional, 0 if unknown/variable + + mg_rtsp_next_frame_fn fn; // User callback to provide frames + void *fn_data; // User callback data + + mg_rtsp_next_audio_fn audio_fn; // Optional AAC callback, NULL if no audio + void *audio_fn_data; // Audio callback data + uint8_t aac_lc_config[2]; // AAC-LC config, e.g. {0x12, 0x10}; + // sample rate and channels are inferred + // e.g. {0x12, 0x10} is 44.1 kHz, stereo. +}; +// Start RTSP server listener +// E.g. mg_rtsp_listen(&mgr, "rtsp://0.0.0.0:8554/cam", &opts); +struct mg_connection *mg_rtsp_listen(struct mg_mgr *mgr, const char *url, + const struct mg_rtsp_opts *opts); + +// Helper callbacks that get H.264 video audio from the MP4 file. +// fn_data must be a const char * path to the MP4 file. E.g. "test.mp4" +bool mg_rtsp_get_mp4_frame(struct mg_rtsp_frame *frame, void *fn_data); +bool mg_rtsp_get_mp4_audio(struct mg_rtsp_audio *audio, void *fn_data); diff --git a/tutorials/tcp/rtsp-video-stream/test.mp4 b/tutorials/tcp/rtsp-video-stream/test.mp4 new file mode 100644 index 00000000..bbe52d88 Binary files /dev/null and b/tutorials/tcp/rtsp-video-stream/test.mp4 differ