From 01ddbc1814f169d1cf7cc5063cc0be9090828735 Mon Sep 17 00:00:00 2001 From: Difan Zhang Date: Sun, 8 Nov 2026 02:01:25 -0800 Subject: [PATCH 01/11] media: set audioOn before starting the audio threads The capture and encoding threads loop while audioOn is set, but it was only set after both threads had been created. A thread scheduled before that returned at once and audio stayed silent until the next restart. --- src/media.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/media.c b/src/media.c index 461ed13c..702c2614 100644 --- a/src/media.c +++ b/src/media.c @@ -590,6 +590,9 @@ int media_audio_enable(void) { pcmSamp = shine_samples_per_pass(mp3Enc); } + /* The capture and encoding threads run only while audioOn is set. */ + audioOn = 1; + { pthread_attr_t thread_attr; pthread_attr_init(&thread_attr); @@ -599,8 +602,10 @@ int media_audio_enable(void) { if (pthread_attr_setstacksize(&thread_attr, new_stacksize)) HAL_DANGER("media", "Can't set stack size %zu\n", new_stacksize); if (pthread_create( - &audPid, &thread_attr, (void *(*)(void *))aud_thread, NULL)) + &audPid, &thread_attr, (void *(*)(void *))aud_thread, NULL)) { + audioOn = 0; HAL_ERROR("media", "Starting the audio capture thread failed!\n"); + } if (pthread_attr_setstacksize(&thread_attr, stacksize)) HAL_DANGER("media", "Can't set stack size %zu\n", stacksize); pthread_attr_destroy(&thread_attr); @@ -615,15 +620,15 @@ int media_audio_enable(void) { if (pthread_attr_setstacksize(&thread_attr, new_stacksize)) HAL_DANGER("media", "Can't set stack size %zu\n", new_stacksize); if (pthread_create( - &aencPid, &thread_attr, (void *(*)(void *))aenc_thread, NULL)) + &aencPid, &thread_attr, (void *(*)(void *))aenc_thread, NULL)) { + audioOn = 0; HAL_ERROR("media", "Starting the audio encoding thread failed!\n"); + } if (pthread_attr_setstacksize(&thread_attr, stacksize)) HAL_DANGER("media", "Can't set stack size %zu\n", stacksize); pthread_attr_destroy(&thread_attr); } - audioOn = 1; - return ret; } From 2e04f1582367fec6199c8741d023e7382ec146b8 Mon Sep 17 00:00:00 2001 From: Difan Zhang Date: Sun, 8 Nov 2026 02:01:25 -0800 Subject: [PATCH 02/11] media: follow sample rate changes when resampling G.711 The decimation step was computed once and kept in a static, so after /api/audio changed the sample rate RTSP G.711 audio was resampled at the old ratio and played at the wrong speed. The Q16 step also overflowed for sample rates of 65536 Hz and above. Compute it per call without overflow. --- src/media.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/media.c b/src/media.c index 702c2614..36ada84c 100644 --- a/src/media.c +++ b/src/media.c @@ -71,14 +71,12 @@ static char rtsp_g711_active(void) { return EQUALS(rtsp_codec, "pcma") || EQUALS static void rtsp_pcma_feed(short *pcm, unsigned int samples) { static unsigned char alaw[160]; - static unsigned int fill, phase, step; + static unsigned int fill, phase; static int acc, count; char ulaw = EQUALS(rtsp_codec, "pcmu"); - if (!step) { - unsigned int srate = app_config.audio_srate > 0 ? app_config.audio_srate : 8000; - step = (srate << 16) / 8000; - if (!step) step = 1 << 16; - } + unsigned int srate = app_config.audio_srate > 0 ? app_config.audio_srate : 8000; + /* Input samples per 8 kHz output sample in Q16, i.e. (srate << 16) / 8000 */ + unsigned int step = srate * 1024 / 125; for (unsigned int i = 0; i < samples; i++) { acc += pcm[i]; count++; From b0be5478beb698988d04d01bc02dac38d7d60ba6 Mon Sep 17 00:00:00 2001 From: Difan Zhang Date: Sun, 8 Nov 2026 02:01:25 -0800 Subject: [PATCH 03/11] rtsp: terminate error responses and echo CSeq The PAUSE, RECORD and error responses were a bare status line without CSeq or the empty line ending the headers, so a client that paused kept waiting for the rest of the response. OPTIONS also advertised PAUSE, which is not supported. Send complete responses and drop PAUSE from the Public header. --- src/rtsp/rtsp.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/rtsp/rtsp.c b/src/rtsp/rtsp.c index e26a3b8d..07463002 100644 --- a/src/rtsp/rtsp.c +++ b/src/rtsp/rtsp.c @@ -160,7 +160,7 @@ static void __method_options(struct connection_item_t *p, rtsp_handle h) { __rtsp_write(p, "RTSP/1.0 200 OK\r\n" "CSeq: %d\r\n" - "Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE\r\n" + "Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY\r\n" "\r\n", p->cseq); } @@ -299,19 +299,25 @@ static void __method_setup(struct connection_item_t *p, rtsp_handle h) static void __method_pause(struct connection_item_t *p, rtsp_handle h) { __rtsp_write(p, - "RTSP/1.0 "__RESPONCE_STR_METHODNOTALLOWED "\r\n"); + "RTSP/1.0 " __RESPONCE_STR_METHODNOTALLOWED "\r\n" + "CSeq: %d\r\n" + "\r\n", p->cseq); } static void __method_record(struct connection_item_t *p, rtsp_handle h) { __rtsp_write(p, - "RTSP/1.0 " __RESPONCE_STR_METHODNOTALLOWED "\r\n"); + "RTSP/1.0 " __RESPONCE_STR_METHODNOTALLOWED "\r\n" + "CSeq: %d\r\n" + "\r\n", p->cseq); } static void __method_error(struct connection_item_t *p, rtsp_handle h) { __rtsp_write(p, - "RTSP/1.0 " __RESPONCE_STR_SERVERERROR "\r\n"); + "RTSP/1.0 " __RESPONCE_STR_SERVERERROR "\r\n" + "CSeq: %d\r\n" + "\r\n", p->cseq); } static void __method_play(struct connection_item_t *p, rtsp_handle h) From 82ee8db7f84cab5aed0c11ba73baa566d8a433cf Mon Sep 17 00:00:00 2001 From: Difan Zhang Date: Sun, 8 Nov 2026 02:01:25 -0800 Subject: [PATCH 04/11] rtsp: drop stalled TCP clients instead of blocking all streams Interleaved RTP writes retried EAGAIN without limit, so a client that kept its connection open but stopped reading blocked the sending thread and every other client with it. A failed send also aborted delivery to the clients after it in the list. Give up on a client after 2 s without progress and shut its socket down, which ends the session through the normal EOF path, and keep sending to the remaining clients. --- src/rtsp/rtp.c | 73 +++++++++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/src/rtsp/rtp.c b/src/rtsp/rtp.c index 242228e4..f656c17a 100644 --- a/src/rtsp/rtp.c +++ b/src/rtsp/rtp.c @@ -38,23 +38,37 @@ struct __transfer_set_t { static unsigned int __frame_ts_video, __frame_ts_audio; -static inline void __wait_out(int fd) +/* A client that accepts no data for 20 polls of 100 ms is dropped. */ +#define TCP_STALL_POLLS 20 + +static int __tcp_send_all(int fd, const char *buf, size_t len) { - struct pollfd p = { .fd = fd, .events = POLLOUT }; - poll(&p, 1, 100); + int stalls = 0; + + while (len) { + int r = send(fd, buf, len, 0); + if (r > 0) { + buf += r; + len -= r; + stalls = 0; + } else if (r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK) && + stalls++ < TCP_STALL_POLLS) { + struct pollfd p = { .fd = fd, .events = POLLOUT }; + poll(&p, 1, 100); + } else { + /* Let the RTSP thread see EOF and tear the session down. */ + shutdown(fd, SHUT_RDWR); + return FAILURE; + } + } + return SUCCESS; } static int __tcp_flush(struct connection_item_t *con) { - unsigned int sent = 0; - while (sent < con->tx_len) { - int r = send(con->client_fd, con->tx_buf + sent, con->tx_len - sent, 0); - if (r > 0) sent += r; - else if (r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { __wait_out(con->client_fd); } - else { con->tx_len = 0; return FAILURE; } - } + int ret = __tcp_send_all(con->client_fd, (char *)con->tx_buf, con->tx_len); con->tx_len = 0; - return SUCCESS; + return ret; } static int __tcp_flush_each(struct list_t *e, void *v) @@ -68,9 +82,10 @@ static int __tcp_flush_each(struct list_t *e, void *v) * Testing track 0 here never flushed a client that set up audio alone. */ if (!con->tx_buf || !con->tx_len) return SUCCESS; pthread_mutex_lock(&con->write_mutex); - int ret = __tcp_flush(con); + __tcp_flush(con); pthread_mutex_unlock(&con->write_mutex); - return ret; + /* A failed client is shut down; keep flushing the others. */ + return SUCCESS; } /****************************************************************************** @@ -253,34 +268,20 @@ static inline int __rtp_send_eachconnection(struct list_t *e, void *v) return SUCCESS; } ERR("send (staged):%s\n", strerror(errno)); - return FAILURE; - } - int sent_h = 0; - while (sent_h < 4) { - int r = send(con->client_fd, head + sent_h, 4 - sent_h, 0); - if (r > 0) sent_h += r; - else if (r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { __wait_out(con->client_fd); } - else { sent_h = -1; break; } - } - if (sent_h == 4) { - int sent_b = 0; - while (sent_b < rtp->rtpsize) { - int r = send(con->client_fd, (char*)&(rtp->packet) + sent_b, rtp->rtpsize - sent_b, 0); - if (r > 0) sent_b += r; - else if (r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { __wait_out(con->client_fd); } - else { sent_b = -1; break; } - } - send_bytes = sent_b; - } else { - send_bytes = -1; + /* The client is shut down; keep sending to the others. */ + return SUCCESS; } + int ok = __tcp_send_all(con->client_fd, (char *)head, 4) == SUCCESS && + __tcp_send_all(con->client_fd, (char *)&(rtp->packet), rtp->rtpsize) == SUCCESS; pthread_mutex_unlock(&con->write_mutex); - if (send_bytes == rtp->rtpsize) { + if (ok) { con->trans[track_id].rtcp_packet_cnt += 1; con->trans[track_id].rtcp_octet += rtp->rtpsize; - return SUCCESS; - } + } else + ERR("send:%s\n", strerror(errno)); + /* A failed client is shut down; keep sending to the others. */ + return SUCCESS; } else { char attempts = 0; do { From d87c38613b3f7155737adf73fe04918d152d5fcd Mon Sep 17 00:00:00 2001 From: Difan Zhang Date: Sun, 8 Nov 2026 02:01:26 -0800 Subject: [PATCH 05/11] server: reject malformed OSD uploads instead of crashing A multipart POST to /api/osd without a boundary parameter, without the part header terminator or without the closing boundary dereferenced NULL or wrote with a bogus length, and a failed fopen() was passed to fwrite(). A boundary longer than 127 bytes also overflowed the char length passed to memstr() and read past the request. Any HTTP client could crash the streamer this way. Accept RFC 2046 boundaries only and answer 400 or 500 instead. --- src/server.c | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/server.c b/src/server.c index c50af083..8cf05bec 100644 --- a/src/server.c +++ b/src/server.c @@ -1358,24 +1358,42 @@ void respond_request(http_request_t *req) { if (EQUALS(req->method, "POST")) { char *type = request_header("Content-Type"); if (type && STARTS_WITH(type, "multipart/form-data")) { - char *bound = strstr(type, "boundary=") + strlen("boundary="); - - char *payloadb = strstr(req->payload, bound); - payloadb = memstr(payloadb, "\r\n\r\n", req->total - (payloadb - req->input), 4); - if (payloadb) payloadb += 4; - - char *payloade = memstr(payloadb, bound, - req->total - (payloadb - req->input), strlen(bound)); - if (payloade) payloade -= 4; + char *bound = strstr(type, "boundary="); + char *payloadb = NULL, *payloade = NULL; + + if (bound) { + bound += strlen("boundary="); + /* RFC 2046 boundaries are 1 to 70 characters long */ + if (*bound && strlen(bound) <= 70) + payloadb = strstr(req->payload, bound); + } + if (payloadb) + payloadb = memstr(payloadb, "\r\n\r\n", + req->total - (payloadb - req->input), 4); + if (payloadb) { + payloadb += 4; + payloade = memstr(payloadb, bound, + req->total - (payloadb - req->input), strlen(bound)); + } + if (!payloade || payloade - 4 < payloadb) { + send_http_error(req->clntFd, 400); + return; + } + payloade -= 4; char path[32]; - if (!memcmp(payloadb, "\x89\x50\x4E\x47\xD\xA\x1A\xA", 8)) + if (payloade - payloadb >= 8 && + !memcmp(payloadb, "\x89\x50\x4E\x47\xD\xA\x1A\xA", 8)) sprintf(path, "/tmp/osd%d.png", id); else sprintf(path, "/tmp/osd%d.bmp", id); FILE *img = fopen(path, "wb"); + if (!img) { + send_http_error(req->clntFd, 500); + return; + } fwrite(payloadb, sizeof(char), payloade - payloadb, img); fclose(img); From c8bd38ab15c2a316be0c390f8947d12d4f652b0e Mon Sep 17 00:00:00 2001 From: Difan Zhang Date: Sun, 8 Nov 2026 02:01:26 -0800 Subject: [PATCH 06/11] server: show uploaded OSD images An image uploaded to /api/osd was stored as /tmp/osdN.png or .bmp, but the region thread loads osds[N].img and only falls back to /tmp/osdN.bmp when that is empty. PNG uploads were therefore never shown, and neither was any upload to a region that already named an image. Point the region at the stored file. --- src/server.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/server.c b/src/server.c index 8cf05bec..ea3cd2e9 100644 --- a/src/server.c +++ b/src/server.c @@ -1397,6 +1397,7 @@ void respond_request(http_request_t *req) { fwrite(payloadb, sizeof(char), payloade - payloadb, img); fclose(img); + strcpy(osds[id].img, path); strcpy(osds[id].text, ""); osds[id].updt = 1; } else { From 1f16b80bbb91bc60cba420c1a50e4376e9cadf7c Mon Sep 17 00:00:00 2001 From: Difan Zhang Date: Sun, 8 Nov 2026 02:01:26 -0800 Subject: [PATCH 07/11] region: fix PNG decoding error handling Decode failures were tested through the buffer pointer instead of the returned error, so a corrupt image was converted from uninitialized memory. Errors raised before the buffers were allocated freed uninitialized pointers, and the decoder context leaked on success. --- src/region.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/region.c b/src/region.c index b6e7ca17..794c913c 100644 --- a/src/region.c +++ b/src/region.c @@ -162,7 +162,7 @@ int region_parse_bitmap(FILE **file, bitmapfile *bmpFile, bitmapinfo *bmpInfo) { int region_prepare_image(char *path, hal_bitmap *bitmap) { FILE *file; - unsigned char *bitmapdata, *bitmapout; + unsigned char *bitmapdata = NULL, *bitmapout = NULL; unsigned short *dest; if (!path) @@ -225,7 +225,7 @@ int region_prepare_image(char *path, hal_bitmap *bitmap) { } err = spng_decode_image(ctx, bitmapdata, bitmapsize, SPNG_FMT_RGBA8, 0); - if (!bitmapdata) { + if (err) { HAL_DANGER("server", "Decoding the PNG image failed!\nError: %s\n", spng_strerror(err)); goto png_error; } @@ -239,6 +239,7 @@ int region_prepare_image(char *path, hal_bitmap *bitmap) { free(bitmapdata); fclose(file); + spng_ctx_free(ctx); bitmap->data = bitmapout; bitmap->dim.width = ihdr.width; From 807eda5d71bc0cfa6c6e9063830a71354081ec46 Mon Sep 17 00:00:00 2001 From: Difan Zhang Date: Sun, 8 Nov 2026 02:01:26 -0800 Subject: [PATCH 08/11] text: clip glyphs to the OSD canvas The canvas leaves room for negative left bearings only, while outlined text is also drawn up to the outline thickness above, below and to either side of each glyph. With an outline set, the first glyph of a line was written in front of the canvas buffer and the last ones past its end, corrupting the heap. Clip every glyph to the canvas. --- src/text.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/text.c b/src/text.c index a4d190ae..742f3a4f 100644 --- a/src/text.c +++ b/src/text.c @@ -71,26 +71,27 @@ void text_copy_rendered(SFT_Image *dest, const SFT_Image *source, int x0, int y0 unsigned int maskg = (color & 0x3E0) >> 5; unsigned int maskb = color & 0x1F; - unsigned short *d = dest->pixels; + unsigned short *pixels = dest->pixels; unsigned char *s = source->pixels; - d += x0 + y0 * dest->width; - for (int y = 0; y < source->height; y++) { + for (int y = 0; y < source->height; y++, s += source->width) { + /* Outlines and negative bearings can reach past the canvas */ + if (y0 + y < 0 || y0 + y >= dest->height) continue; + unsigned short *d = pixels + (y0 + y) * dest->width; for (int x = 0; x < source->width; x++) { + if (x0 + x < 0 || x0 + x >= dest->width) continue; unsigned int a = s[x]; if (a == 0) continue; unsigned int inv_a = 255 - a; - unsigned short dst = d[x]; + unsigned short dst = d[x0 + x]; unsigned int r = maskr * a + ((dst & 0x7C00) >> 10) * inv_a; unsigned int g = maskg * a + ((dst & 0x3E0) >> 5) * inv_a; unsigned int b = maskb * a + (dst & 0x1F) * inv_a; r = (r + 1 + (r >> 8)) >> 8; g = (g + 1 + (g >> 8)) >> 8; b = (b + 1 + (b >> 8)) >> 8; - d[x] = (1 << 15) | (r << 10) | (g << 5) | b; + d[x0 + x] = (1 << 15) | (r << 10) | (g << 5) | b; } - d += dest->width; - s += source->width; } } From 7cee162ee348f12d92fbe691522cb7d6a2b856e4 Mon Sep 17 00:00:00 2001 From: Difan Zhang Date: Sun, 8 Nov 2026 02:01:26 -0800 Subject: [PATCH 09/11] config: terminate the executable path before taking its directory dirname() ran on the readlink() result before it was NUL-terminated, so it scanned uninitialized stack bytes and could derive the configuration path next to the executable from the wrong directory. --- src/app_config.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app_config.c b/src/app_config.c index 9d163672..d24aa11d 100644 --- a/src/app_config.c +++ b/src/app_config.c @@ -11,8 +11,8 @@ static inline void app_config_open(FILE **file, const char *flags) { ssize_t exe_len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1); if (exe_len != -1) { - char *dir = dirname(exe_path); exe_path[exe_len] = '\0'; + char *dir = dirname(exe_path); snprintf(conf_path, sizeof(conf_path), "%s/divinus.yaml", dir); if (!access(conf_path, F_OK)) { if (*flags == 'w') { @@ -44,8 +44,8 @@ void app_config_restore(void) { ssize_t exe_len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1); if (exe_len != -1) { - char bak_path[PATH_MAX], *dir = dirname(exe_path); exe_path[exe_len] = '\0'; + char bak_path[PATH_MAX], *dir = dirname(exe_path); snprintf(conf_path, sizeof(conf_path), "%s/divinus.yaml", dir); sprintf(bak_path, "%s.bak", conf_path); if (!access(bak_path, F_OK)) { From b9f23e24943ced4fd4006cedf12017f8e537a2e8 Mon Sep 17 00:00:00 2001 From: Difan Zhang Date: Sun, 8 Nov 2026 02:01:26 -0800 Subject: [PATCH 10/11] config: leave room for the list terminators The web whitelist and stream destination lists are read up to an empty entry, and the parser stored up to all four entries before the caller wrote that terminator. A fourth entry therefore put the terminator past the array and its readers went on into the following settings. Parse at most three entries. --- src/app_config.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app_config.c b/src/app_config.c index d24aa11d..a0008ce5 100644 --- a/src/app_config.c +++ b/src/app_config.c @@ -318,8 +318,9 @@ enum ConfigError app_config_parse(void) { if (err != CONFIG_OK) goto RET_ERR; app_config.web_port = (unsigned short)port; + /* Keep the last entry free for the empty terminator. */ parse_list(&ini, "system", "web_whitelist", - sizeof(app_config.web_whitelist) / sizeof(*app_config.web_whitelist), + sizeof(app_config.web_whitelist) / sizeof(*app_config.web_whitelist) - 1, &count, app_config.web_whitelist); *app_config.web_whitelist[count] = '\0'; parse_bool(&ini, "system", "web_enable_auth", &app_config.web_enable_auth); @@ -496,7 +497,7 @@ enum ConfigError app_config_parse(void) { err = parse_int(&ini, "stream", "udp_srcport", 0, USHRT_MAX, &val); if (err == CONFIG_OK) app_config.stream_udp_srcport = (unsigned short)val; parse_list(&ini, "stream", "dest", - sizeof(app_config.stream_dests) / sizeof(*app_config.stream_dests), + sizeof(app_config.stream_dests) / sizeof(*app_config.stream_dests) - 1, &count, app_config.stream_dests); *app_config.stream_dests[count] = '\0'; } From 00d5ff39c99672a9f221f009eef52b642cfaf068 Mon Sep 17 00:00:00 2001 From: Difan Zhang Date: Sun, 8 Nov 2026 02:01:26 -0800 Subject: [PATCH 11/11] media: refresh the MP4 audio settings after /api/audio changes /api/audio restarts audio with a new rate, bitrate or enable state, but the MP4 muxer kept describing the old audio track. Reapply the MP4 configuration there and request an IDR so the header is rebuilt, and take mp4Mtx while doing so since the video thread muxes concurrently. --- src/media.c | 12 +++++++++--- src/media.h | 1 + src/server.c | 4 ++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/media.c b/src/media.c index 36ada84c..8e2e24e4 100644 --- a/src/media.c +++ b/src/media.c @@ -740,6 +740,14 @@ int media_mp4_disable(void) { return EXIT_SUCCESS; } +void media_mp4_config(void) { + pthread_mutex_lock(&mp4Mtx); + mp4_set_config(app_config.mp4_width, app_config.mp4_height, app_config.mp4_fps, + app_config.audio_enable ? HAL_AUDCODEC_MP3 : HAL_AUDCODEC_UNSPEC, + app_config.audio_bitrate, 1, app_config.audio_srate); + pthread_mutex_unlock(&mp4Mtx); +} + int media_mp4_enable(void) { int ret; @@ -800,9 +808,7 @@ int media_mp4_enable(void) { index, ret, errstr(ret)); } - mp4_set_config(app_config.mp4_width, app_config.mp4_height, app_config.mp4_fps, - app_config.audio_enable ? HAL_AUDCODEC_MP3 : HAL_AUDCODEC_UNSPEC, - app_config.audio_bitrate, 1, app_config.audio_srate); + media_mp4_config(); } if (ret = bind_channel(index, app_config.mp4_fps, 0)) diff --git a/src/media.h b/src/media.h index f7172e4b..e3e2f160 100644 --- a/src/media.h +++ b/src/media.h @@ -45,6 +45,7 @@ void media_audio_disable(void); int media_audio_enable(void); int media_mjpeg_disable(void); int media_mjpeg_enable(void); +void media_mp4_config(void); int media_mp4_disable(void); int media_mp4_enable(void); diff --git a/src/server.c b/src/server.c index ea3cd2e9..6b90a939 100644 --- a/src/server.c +++ b/src/server.c @@ -907,6 +907,10 @@ void respond_request(http_request_t *req) { media_audio_disable(); if (app_config.audio_enable) media_audio_enable(); + if (app_config.mp4_enable) { + media_mp4_config(); + request_idr(); + } } respLen = sprintf(response,