diff --git a/src/app_config.c b/src/app_config.c index 9d163672..a0008ce5 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)) { @@ -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'; } diff --git a/src/media.c b/src/media.c index 461ed13c..8e2e24e4 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++; @@ -590,6 +588,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 +600,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 +618,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; } @@ -737,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; @@ -797,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/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; 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 { 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) diff --git a/src/server.c b/src/server.c index c50af083..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, @@ -1358,27 +1362,46 @@ 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); + strcpy(osds[id].img, path); strcpy(osds[id].text, ""); osds[id].updt = 1; } else { 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; } }