diff --git a/doc/config.md b/doc/config.md index cab0b10b..7ff89819 100644 --- a/doc/config.md +++ b/doc/config.md @@ -43,7 +43,7 @@ This document describes the fields that can be found within a configuration file ## ONVIF section - **enable**: Boolean to activate ONVIF services (default: `false`). -- **enable_auth**: Boolean to turn on ONVIF authentication (default: `false`). +- **enable_auth**: Boolean to turn on ONVIF authentication (default: `false`). When this is off, a client may still attach a username token and the request is answered without checking it. - **auth_user**: Username for ONVIF authentication (default: `admin`). - **auth_pass**: Password for ONVIF authentication (default: `12345`). @@ -89,6 +89,10 @@ This document describes the fields that can be found within a configuration file - **profile**: Encoding profile. - **bitrate**: Bitrate in kbps. +On FH8852 the H.264 rate controller ignores that target and stays at its highest quantizer, so the value is turned into a fixed quantizer: 38 at 1024 kbps, 36 at 2048 kbps, 34 at 4096 kbps, and 32 at 8192 kbps. It never goes below 30. A higher bitrate is a lower quantizer and a larger stream. H.265 keeps the driver's constant-bitrate control. + +On FH8852, `gc4653_mipi` starts from the 1920×1080 tables in the sensor library: format 0x203 at 25 fps and 0x204 at 30 fps. The nearest listed rate is used. A request for a larger picture, including 2560×1440, stays at 1920×1080, which is the resolution of the shipped IQ file. The 4MP sensor tables need an ISP buffer that does not fit beside the encoder in the 35 MB memory zone on this board. The sensor mode is chosen when the pipeline starts, so a frame-rate change needs a restart. Other sensors in this HAL keep a single mode. + ## OSD section - **enable**: Boolean to turn on On-Screen Display regions globally, used to reduce resource usage or let another app manage the functionality (default: `true`). @@ -120,6 +124,8 @@ This document describes the fields that can be found within a configuration file - **bitrate**: Bitrate in kbps. - **qfactor**: JPEG compression quality factor. +On FH8852 a higher quality selects a lower encoder quantizer: quality 1 is quantizer 98 and quality 99 is quantizer 0. The encoder does not use the bitrate field. + ## HTTP POST section - **enable**: Boolean to activate HTTP POST snapshots. diff --git a/res/onvif/capabilities.xml b/res/onvif/capabilities.xml index 6601faa3..3f2a13fd 100644 --- a/res/onvif/capabilities.xml +++ b/res/onvif/capabilities.xml @@ -42,8 +42,8 @@ false false false - true - true + %s + false @@ -60,7 +60,7 @@ false false - false + true 2 diff --git a/res/onvif/devicecaps.xml b/res/onvif/devicecaps.xml new file mode 100644 index 00000000..6cb4b4f7 --- /dev/null +++ b/res/onvif/devicecaps.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/res/onvif/dns.xml b/res/onvif/dns.xml new file mode 100644 index 00000000..5c19a491 --- /dev/null +++ b/res/onvif/dns.xml @@ -0,0 +1,10 @@ + + + + + + true + + + + diff --git a/res/onvif/encoder.xml b/res/onvif/encoder.xml new file mode 100644 index 00000000..49700ba3 --- /dev/null +++ b/res/onvif/encoder.xml @@ -0,0 +1,26 @@ + + VideoEncoderConfig_%d + 1 + %s + + %d + %d + + 5 + + %d + 1 + %d + +%s + + + IPv4 + 0.0.0.0 + + 0 + 1 + false + + PT60S + diff --git a/res/onvif/encoderoptions.xml b/res/onvif/encoderoptions.xml new file mode 100644 index 00000000..db3f6c4d --- /dev/null +++ b/res/onvif/encoderoptions.xml @@ -0,0 +1,36 @@ + + + + + + + 1 + 5 + + + + %d + %d + + + %d + %d + + + 1 + 200 + + + 1 + %d + + + 1 + 1 + + Main + + + + + diff --git a/res/onvif/encoders.xml b/res/onvif/encoders.xml new file mode 100644 index 00000000..58cafc97 --- /dev/null +++ b/res/onvif/encoders.xml @@ -0,0 +1,8 @@ + + + + +%s + + + diff --git a/res/onvif/gateway.xml b/res/onvif/gateway.xml new file mode 100644 index 00000000..d6f1c0c9 --- /dev/null +++ b/res/onvif/gateway.xml @@ -0,0 +1,10 @@ + + + + + + %s + + + + diff --git a/res/onvif/hostname.xml b/res/onvif/hostname.xml new file mode 100644 index 00000000..31ca5820 --- /dev/null +++ b/res/onvif/hostname.xml @@ -0,0 +1,11 @@ + + + + + + false + %s + + + + diff --git a/res/onvif/mediacaps.xml b/res/onvif/mediacaps.xml new file mode 100644 index 00000000..797dd4c3 --- /dev/null +++ b/res/onvif/mediacaps.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/res/onvif/mediaprofile.xml b/res/onvif/mediaprofile.xml index 66c3e53d..0894b11e 100644 --- a/res/onvif/mediaprofile.xml +++ b/res/onvif/mediaprofile.xml @@ -20,5 +20,16 @@ 1 %d +%s + + + IPv4 + 0.0.0.0 + + 0 + 1 + false + + PT60S diff --git a/res/onvif/network.xml b/res/onvif/network.xml new file mode 100644 index 00000000..762f2d72 --- /dev/null +++ b/res/onvif/network.xml @@ -0,0 +1,24 @@ + + + + + + true + + %s + %s + + + true + + + %s + %d + + true + + + + + + diff --git a/res/onvif/profile.xml b/res/onvif/profile.xml new file mode 100644 index 00000000..13c8fdfd --- /dev/null +++ b/res/onvif/profile.xml @@ -0,0 +1,8 @@ + + + + +%s + + + diff --git a/res/onvif/protocols.xml b/res/onvif/protocols.xml new file mode 100644 index 00000000..f17202bb --- /dev/null +++ b/res/onvif/protocols.xml @@ -0,0 +1,17 @@ + + + + + + HTTP + true + %d + + + RTSP + %s + %d + + + + diff --git a/res/onvif/scopes.xml b/res/onvif/scopes.xml new file mode 100644 index 00000000..941239b6 --- /dev/null +++ b/res/onvif/scopes.xml @@ -0,0 +1,23 @@ + + + + + + Fixed + onvif://www.onvif.org/Profile/Streaming + + + Fixed + onvif://www.onvif.org/type/video_encoder + + + Fixed + onvif://www.onvif.org/name/Divinus + + + Fixed + onvif://www.onvif.org/hardware/%s + + + + diff --git a/res/onvif/services.xml b/res/onvif/services.xml new file mode 100644 index 00000000..fc1f5a19 --- /dev/null +++ b/res/onvif/services.xml @@ -0,0 +1,23 @@ + + + + + + http://www.onvif.org/ver10/device/wsdl + http://%s:%d/onvif/device_service + + 2 + 42 + + + + http://www.onvif.org/ver10/media/wsdl + http://%s:%d/onvif/media_service + + 2 + 42 + + + + + diff --git a/res/onvif/settime.xml b/res/onvif/settime.xml new file mode 100644 index 00000000..827cc4d4 --- /dev/null +++ b/res/onvif/settime.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/res/onvif/sourceconfigs.xml b/res/onvif/sourceconfigs.xml new file mode 100644 index 00000000..576247cb --- /dev/null +++ b/res/onvif/sourceconfigs.xml @@ -0,0 +1,13 @@ + + + + + + VideoSourceConfig_1 + 1 + VideoSource_1 + + + + + diff --git a/res/onvif/systemtime.xml b/res/onvif/systemtime.xml index ec2035fe..6eda67b6 100644 --- a/res/onvif/systemtime.xml +++ b/res/onvif/systemtime.xml @@ -1,25 +1,25 @@ - + - NTP - false - - UTC - - - - %02d - %02d - %02d - - - %d - %02d - %02d - - + Manual + false + + UTC + + + + %02d + %02d + %02d + + + %d + %02d + %02d + + diff --git a/res/onvif/videosources.xml b/res/onvif/videosources.xml index 5949bf03..1b57e341 100644 --- a/res/onvif/videosources.xml +++ b/res/onvif/videosources.xml @@ -1,44 +1,14 @@ - + - - - %d - - %d - %d - - - - OFF - 0 - - 50 - 50 - 50 - - AUTO - 10 - 30000 - 0 - 0 - 100 - - - AUTO - 1 - 0 - 0 - - - AUTO - 0 - 0 - - - + + %d + + %d + %d + - \ No newline at end of file + diff --git a/src/hal/fhan/f2_hal.c b/src/hal/fhan/f2_hal.c index 638e0cf0..7ef88dcc 100644 --- a/src/hal/fhan/f2_hal.c +++ b/src/hal/fhan/f2_hal.c @@ -2,6 +2,8 @@ #include "f2_hal.h" +#include + f2_sys_impl f2_sys; f2_isp_impl f2_isp; f2_vpss_impl f2_vpss; @@ -192,22 +194,35 @@ int f2_channel_create(char index, short width, short height, char framerate, cha if (index >= F2_VENC_CHN_NUM) HAL_ERROR("f2_venc", "Only %d encoder channels are available!\n", F2_VENC_CHN_NUM); - _f2_want_dim[index].width = width; - _f2_want_dim[index].height = height; - if (jpeg || index >= _f2_vpss_count) { - _f2_vpss_src[index] = -1; - return EXIT_SUCCESS; - } - _f2_vpss_src[index] = index; - if (width > _f2_snr_dim.width || height > _f2_snr_dim.height) { HAL_WARNING("f2_hal", "Channel %d: requested %dx%d too large, using sensor %dx%d\n", index, width, height, _f2_snr_dim.width, _f2_snr_dim.height); width = _f2_snr_dim.width; height = _f2_snr_dim.height; } + _f2_want_dim[index].width = width; + _f2_want_dim[index].height = height; + if (!jpeg && index < _f2_vpss_count) { + _f2_vpss_src[index] = index; + return f2_vpss_setup(index, width, height, framerate); + } - return f2_vpss_setup(index, width, height, framerate); + for (char i = 0; i < _f2_vpss_count; i++) { + if (_f2_vpss_open[i] && _f2_vpss_dim[i].width == (unsigned int)width && + _f2_vpss_dim[i].height == (unsigned int)height) { + _f2_vpss_src[index] = -1; + return EXIT_SUCCESS; + } + } + for (char i = 0; i < _f2_vpss_count; i++) { + if (_f2_vpss_open[i]) continue; + _f2_vpss_src[index] = i; + return f2_vpss_setup(i, width, height, framerate); + } + _f2_vpss_src[index] = -1; + HAL_WARNING("f2_vpss", "No free channel for %dx%d, sharing the main picture\n", + width, height); + return EXIT_SUCCESS; } static void f2_grayscale_apply(void) { @@ -309,11 +324,75 @@ int f2_night_thresholds(unsigned short night, unsigned short day) { } +static const f2_snr_mode *f2_snr_largest(const char *name) { + const f2_snr_mode *best = NULL; + unsigned int bestArea = 0; + + for (unsigned int i = 0; i < sizeof(f2_snr_modes) / sizeof(*f2_snr_modes); i++) { + unsigned int area; + if (strcmp(f2_snr_modes[i].name, name)) continue; + area = f2_snr_modes[i].dim.width * f2_snr_modes[i].dim.height; + if (!best || area > bestArea) { + best = &f2_snr_modes[i]; + bestArea = area; + } + } + return best; +} + +static const f2_snr_mode *f2_snr_match(const char *name, unsigned int width, + unsigned int height, unsigned int fps) { + const f2_snr_mode *exact = NULL, *cover = NULL, *largest = NULL; + unsigned int exactDiff = ~0u, coverArea = ~0u, coverDiff = ~0u; + unsigned int largeArea = 0, largeDiff = ~0u; + + for (unsigned int i = 0; i < sizeof(f2_snr_modes) / sizeof(*f2_snr_modes); i++) { + const f2_snr_mode *mode = &f2_snr_modes[i]; + unsigned int area, diff; + if (strcmp(mode->name, name)) continue; + area = mode->dim.width * mode->dim.height; + diff = mode->fps ? (fps > mode->fps ? fps - mode->fps : mode->fps - fps) : 0; + if (!largest || area > largeArea || (area == largeArea && diff < largeDiff)) { + largest = mode; + largeArea = area; + largeDiff = diff; + } + if (mode->dim.width == width && mode->dim.height == height && + (!exact || diff < exactDiff)) { + exact = mode; + exactDiff = diff; + } + if (mode->dim.width >= width && mode->dim.height >= height && + (!cover || area < coverArea || (area == coverArea && diff < coverDiff))) { + cover = mode; + coverArea = area; + coverDiff = diff; + } + } + if (exact) return exact; + if (cover) return cover; + return largest; +} + int f2_pipeline_create(short width, short height, char mirror, char flip, char framerate, char antiflicker) { - int snrFmt; + const f2_snr_mode *mode; + unsigned int snrFmt; int ret; char path[128]; + mode = f2_snr_match(f2_snr.name, (unsigned int)width, (unsigned int)height, + (unsigned int)(unsigned char)framerate); + snrFmt = mode ? mode->format : F2_SNR_FMT; + if (mode) { + _f2_snr_dim = mode->dim; + if (mode->fps) + HAL_INFO("f2_snr", "Using %s %ux%u at %u fps (format %#x)\n", + f2_snr.name, mode->dim.width, mode->dim.height, mode->fps, mode->format); + else + HAL_INFO("f2_snr", "Using %s %ux%u (format %#x)\n", + f2_snr.name, mode->dim.width, mode->dim.height, mode->format); + } + /* The drivers take their buffer sizes before the SDK starts. */ { char value[64]; @@ -367,13 +446,6 @@ int f2_pipeline_create(short width, short height, char mirror, char flip, char f f2_snr.fnCreate(); - if (f2_isp.fnAdvInit) { - int ar = f2_isp.fnAdvInit(); - } - if (f2_isp.fnAdvSensorInit) { - int sr = f2_isp.fnAdvSensorInit(); - } - if (ret = f2_isp.fnMemInit(_f2_snr_dim.width, _f2_snr_dim.height)) { HAL_DANGER("f2_isp", "Operation failed with %#x!\n", ret); return ret; @@ -389,7 +461,6 @@ int f2_pipeline_create(short width, short height, char mirror, char flip, char f return ret; } - snrFmt = F2_SNR_FMT; if (ret = f2_isp.fnSetSensorFormat(snrFmt)) { HAL_DANGER("f2_isp", "Operation failed with %#x!\n", ret); return ret; @@ -408,6 +479,26 @@ int f2_pipeline_create(short width, short height, char mirror, char flip, char f HAL_WARNING("f2_isp", "Can't load the ISP config for %s\n", f2_snr.name); } + if (f2_isp.fnAdvSensorInit && f2_snr.ops) { + int sr = f2_isp.fnAdvSensorInit(f2_snr.ops); + if (sr) + HAL_WARNING("f2_isp", "Sensor helper init failed with %#x\n", sr); + } + if (f2_isp.fnAdvInit) { + int ar = f2_isp.fnAdvInit(); + if (ar) + HAL_WARNING("f2_isp", "ISP helper init failed with %#x\n", ar); + } + if (!strcmp(f2_snr.name, "gc4653_mipi")) { + int (*setMode)(int) = dlsym(f2_isp.handleAdv, "FHAdv_Isp_SetAwbGain"); + int (*setGain)(void *) = dlsym(f2_isp.handle, "API_ISP_SetAwbGain"); + unsigned short neutral[3] = { 256, 256, 256 }; + if (setMode) + setMode(1); + if (setGain && !setGain(neutral)) + HAL_INFO("f2_isp", "GC4653 white balance held at neutral gains\n"); + } + if (mirror || flip) f2_isp.fnSetFlipMirrorEx(mirror ? 1 : 0, flip ? 1 : 0, 0); @@ -575,8 +666,9 @@ int f2_video_create(char index, hal_vidconfig *config) { } if (_f2_vpss_src[index] >= 0) { - if (ret = f2_vpss.fnSetOutputMode(index, type == F2_VENC_CODEC_H265 ? 1 : 0)) - HAL_DANGER("f2_vpss", "Setting output mode on channel %d failed with %#x!\n", index, ret); + if (ret = f2_vpss.fnSetOutputMode(_f2_vpss_src[index], type == F2_VENC_CODEC_H265 ? 1 : 0)) + HAL_DANGER("f2_vpss", "Setting output mode on channel %d failed with %#x!\n", + _f2_vpss_src[index], ret); } else { char src = f2_vpss_pick(index); config->width = _f2_vpss_dim[src].width; @@ -616,9 +708,18 @@ int f2_video_create(char index, hal_vidconfig *config) { attr.encSpeed = 4; attr.rotate = 0; attr.mode = 0; - attr.qp = MAX(config->maxQual, 1); - attr.frameNum = 1; - attr.frameTime = config->framerate ? config->framerate : 25; + { + unsigned int quality = config->maxQual ? config->maxQual : 50; + if (quality > 99) + quality = 99; + attr.qp = 99 - quality; + if (attr.qp > 98) + attr.qp = 98; + HAL_INFO("f2_venc", "Channel %d MJPEG %ux%u uses quantizer %u for quality %u\n", + index, config->width, config->height, attr.qp, quality); + } + attr.frameNum = config->framerate ? config->framerate : 25; + attr.frameTime = 1; if (ret = f2_venc.fnSetChannelConfig(index, &attr)) { HAL_DANGER("f2_venc", "Configuring MJPEG channel %d failed with %#x!\n", index, ret); return ret; @@ -658,20 +759,43 @@ int f2_video_create(char index, hal_vidconfig *config) { rate->param[0] = 35; rate->param[1] = (unsigned int)MAX(config->bitrate, config->maxBitrate) << 10; rate->param[2] = (unsigned int)config->framerate | (1u << 16); - rate->param[3] = 120; + rate->param[3] = 51; rate->param[6] = 5; rate->param[7] = 1; break; default: cbr: - rate->mode = h265 ? F2_VENC_RATEMODE_H265CBR : F2_VENC_RATEMODE_H264CBR; + if (!h265) { + unsigned int kbps = config->bitrate ? config->bitrate : 1024; + if (kbps < 256) + kbps = 256; + unsigned int log = 0; + for (unsigned int x = kbps; x > 1; x >>= 1) + log++; + int qp = 42; + if (log > 8) { + qp -= 2 * (int)(log - 8); + if (kbps & (1u << (log - 1))) + qp--; + } + if (qp < 30) + qp = 30; + rate->mode = F2_VENC_RATEMODE_H264FIXQP; + rate->param[0] = qp; + rate->param[1] = qp; + rate->param[2] = (unsigned int)(config->framerate ? config->framerate : 25) | (1u << 16); + HAL_INFO("f2_venc", "Channel %d uses H.264 quantizer %u for %u kbps\n", + index, qp, kbps); + break; + } + rate->mode = F2_VENC_RATEMODE_H265CBR; rate->param[0] = 35; rate->param[1] = (unsigned int)config->bitrate << 10; - rate->param[2] = 28; + rate->param[2] = (unsigned int)(config->framerate ? config->framerate : 25) | (1u << 16); rate->param[3] = 50; rate->param[4] = 35; rate->param[5] = 50; - rate->param[6] = (unsigned int)config->framerate | (1u << 16); + rate->param[6] = rate->param[2]; rate->param[7] = 120; rate->param[10] = 5; rate->param[11] = 1; @@ -680,6 +804,10 @@ int f2_video_create(char index, hal_vidconfig *config) { if (ret = f2_venc.fnSetChannelConfig(index, &attr)) return ret; + if (ret = f2_venc.fnSetRateControl(index, &attr.rate)) { + HAL_DANGER("f2_venc", "Setting the rate on channel %d failed with %#x!\n", index, ret); + return ret; + } } @@ -831,6 +959,8 @@ void *f2_video_thread(void) { strm.naluCnt = 1; } unsigned int count = strm.naluCnt; + if (count > 28) + count = 28; if (!logged) { logged = 1; @@ -838,19 +968,66 @@ void *f2_video_thread(void) { int copied = 0; { - static unsigned char *vbuf; static unsigned int vcap; + static unsigned char *vbuf; + static unsigned int vcap; + static struct { unsigned long a, b; } maps[48]; + static int nmaps, mapfresh; + unsigned int span[28]; unsigned int need = 0, off = 0; - for (unsigned int i = 0; i < count && i < 28; i++) need += strm.nalu[i].length; + + if (--mapfresh <= 0) { + FILE *mf = fopen("/proc/self/maps", "r"); + char line[192]; + mapfresh = 64; + nmaps = 0; + if (mf) { + while (nmaps < 48 && fgets(line, sizeof line, mf)) { + unsigned long a, b; + if (sscanf(line, "%lx-%lx", &a, &b) == 2) { + maps[nmaps].a = a; + maps[nmaps].b = b; + nmaps++; + } + } + fclose(mf); + } + } + for (unsigned int i = 0; i < count; i++) { + unsigned long addr = strm.nalu[i].addr; + unsigned long len = strm.nalu[i].length; + int fits = 0; + span[i] = 0; + if (!addr || !len || len > 2ul * 1024ul * 1024ul) + continue; + for (int m = 0; m < nmaps; m++) { + if (addr < maps[m].a || addr >= maps[m].b) + continue; + if (addr + len + 64 < addr) + break; + if (addr + len + 64 <= maps[m].b) + fits = 1; + break; + } + if (!fits) + continue; + span[i] = (unsigned int)len; + need += span[i]; + } if (need) { if (need > vcap) { unsigned char *n = realloc(vbuf, need + 4096); if (n) { vbuf = n; vcap = need + 4096; } } if (vbuf && need <= vcap) { - for (unsigned int i = 0; i < count && i < 28; i++) { - memcpy(vbuf + off, (void*)strm.nalu[i].addr, strm.nalu[i].length); + for (unsigned int i = 0; i < count; i++) { + if (!span[i]) { + strm.nalu[i].length = 0; + continue; + } + memcpy(vbuf + off, (void*)strm.nalu[i].addr, span[i]); strm.nalu[i].addr = (unsigned int)(vbuf + off); - off += strm.nalu[i].length; + strm.nalu[i].length = span[i]; + off += span[i]; } copied = 1; } @@ -861,21 +1038,25 @@ void *f2_video_thread(void) { if (copied && index < F2_VENC_CHN_NUM && f2_state[index].enable && f2_vid_cb) { hal_vidstream outStrm; hal_vidpack outPack[28]; - unsigned int n = count > 28 ? 28 : count; + unsigned int n = 0; memset(outPack, 0, sizeof(outPack)); - outStrm.count = n; outStrm.seq = 0; outStrm.pack = outPack; - for (unsigned int i = 0; i < n; i++) { - outPack[i].data = (unsigned char*)strm.nalu[i].addr; - outPack[i].length = strm.nalu[i].length; - outPack[i].timestamp = 0; - outPack[i].nalu[0].type = strm.nalu[i].type; - outPack[i].nalu[0].offset = 0; - outPack[i].nalu[0].length = strm.nalu[i].length; - outPack[i].naluCnt = 1; + for (unsigned int i = 0; i < count; i++) { + if (!strm.nalu[i].length) + continue; + outPack[n].data = (unsigned char*)strm.nalu[i].addr; + outPack[n].length = strm.nalu[i].length; + outPack[n].timestamp = 0; + outPack[n].nalu[0].type = strm.nalu[i].type; + outPack[n].nalu[0].offset = 0; + outPack[n].nalu[0].length = strm.nalu[i].length; + outPack[n].naluCnt = 1; + n++; } - (*f2_vid_cb)(index, &outStrm); + outStrm.count = n; + if (n) + (*f2_vid_cb)(index, &outStrm); } } HAL_INFO("f2_venc", "Shutting down encoding thread...\n"); @@ -919,14 +1100,14 @@ static int f2_sensor_select(void) { return EXIT_FAILURE; } - for (unsigned int i = 0; i < sizeof(f2_snrs) / sizeof(*f2_snrs); i++) { - if (strcmp(f2_snrs[i].name, f2_snr.name)) continue; - _f2_snr_dim = f2_snrs[i].dim; + { + const f2_snr_mode *mode = f2_snr_largest(f2_snr.name); + if (!mode) + HAL_ERROR("f2_hal", "Unknown sensor %s, please update the app!\n", f2_snr.name); + _f2_snr_dim = mode->dim; strncpy(sensor, f2_snr.name, sizeof(sensor) - 1); return EXIT_SUCCESS; } - - HAL_ERROR("f2_hal", "Unknown sensor %s, please update the app!\n", f2_snr.name); } int f2_system_init(void) { diff --git a/src/hal/fhan/f2_isp.h b/src/hal/fhan/f2_isp.h index f999daa1..ecc770b1 100644 --- a/src/hal/fhan/f2_isp.h +++ b/src/hal/fhan/f2_isp.h @@ -71,21 +71,23 @@ typedef struct { unsigned int overflow; } f2_isp_vistate; -/* Native sensor output sizes, used to size the VI and the OSD plane. */ typedef struct { const char *name; f2_common_dim dim; -} f2_snr_info; - -static const f2_snr_info f2_snrs[] = { - { "gc4653_mipi", { .width = 1920, .height = 1080 } }, - { "gc5603_mipi", { .width = 2880, .height = 1616 } }, - { "jxf32_mipi", { .width = 1920, .height = 1080 } }, - { "mn34425_mipi", { .width = 1920, .height = 1080 } }, - { "ovos02k_mipi", { .width = 1920, .height = 1080 } }, - { "ovos04c10_mipi", { .width = 2560, .height = 1440 } }, - { "ovos05_mipi", { .width = 2592, .height = 1944 } }, - { "ovos08_mipi", { .width = 3840, .height = 2160 } }, + unsigned char fps; + unsigned int format; +} f2_snr_mode; + +static const f2_snr_mode f2_snr_modes[] = { + { "gc4653_mipi", { .width = 1920, .height = 1080 }, 25, 0x203 }, + { "gc4653_mipi", { .width = 1920, .height = 1080 }, 30, 0x204 }, + { "gc5603_mipi", { .width = 2880, .height = 1616 }, 0, F2_SNR_FMT }, + { "jxf32_mipi", { .width = 1920, .height = 1080 }, 0, F2_SNR_FMT }, + { "mn34425_mipi", { .width = 1920, .height = 1080 }, 0, F2_SNR_FMT }, + { "ovos02k_mipi", { .width = 1920, .height = 1080 }, 0, F2_SNR_FMT }, + { "ovos04c10_mipi", { .width = 2560, .height = 1440 }, 0, F2_SNR_FMT }, + { "ovos05_mipi", { .width = 2592, .height = 1944 }, 0, F2_SNR_FMT }, + { "ovos08_mipi", { .width = 3840, .height = 2160 }, 0, F2_SNR_FMT }, }; typedef struct { @@ -108,7 +110,7 @@ typedef struct { int (*fnUnregisterSensor)(unsigned int index); int (*fnAdvInit)(void); - int (*fnAdvSensorInit)(void); + int (*fnAdvSensorInit)(f2_isp_snrops *ops); int (*fnAdvSetColorMode)(int color); int (*fnGetSaturation)(void *sat); /* record, byte 5 nonzero = colour */ int (*fnSetSaturation)(void *sat); @@ -204,7 +206,7 @@ static int f2_isp_load(f2_isp_impl *isp_lib) { hal_symbol_load("f2_isp", isp_lib->handleAdv, "FHAdv_Isp_Init"))) return EXIT_FAILURE; - if (!(isp_lib->fnAdvSensorInit = (int(*)(void)) + if (!(isp_lib->fnAdvSensorInit = (int(*)(f2_isp_snrops *)) hal_symbol_load("f2_isp", isp_lib->handleAdv, "FHAdv_Isp_SensorInit"))) return EXIT_FAILURE; diff --git a/src/hal/fhan/f2_venc.h b/src/hal/fhan/f2_venc.h index 3d5e5519..2d2e02a8 100644 --- a/src/hal/fhan/f2_venc.h +++ b/src/hal/fhan/f2_venc.h @@ -14,7 +14,7 @@ typedef struct { /* * Rate control block, also the payload of FH_VENC_SetRCAttr(). * Layouts (vendor application defaults): - * H264/H265 CBR: { mode, 35, bitrate_bps, minQp(28), maxQp(50), targetQp, 50, fps | den << 16, 120, 0, 0, 5, 1, 0 } + * H264/H265 CBR: { mode, 35, bitrate_bps, fps | den << 16, maxQp(50), targetQp, 50, fps | den << 16, 120, 0, 0, 5, 1, 0 } * H264/H265 VBR: { mode, 35, bitrate_bps, fps | den << 16, 120, 0, 0, 5, 1 } * H264 fixed QP: { mode, iQp, pQp, fps | den << 16 } */ diff --git a/src/media.c b/src/media.c index 6161cbd7..010bcd83 100644 --- a/src/media.c +++ b/src/media.c @@ -1,6 +1,8 @@ #include "media.h" char audioOn = 0, udpOn = 0; +int media_main_ch = -1; +int media_sub_ch = -1; pthread_mutex_t aencMtx, chnMtx, mp4Mtx; pthread_t aencPid = 0, audPid = 0, ispPid = 0, vidPid = 0; @@ -186,8 +188,10 @@ int save_video_stream(char index, hal_vidstream *stream) { case HAL_VIDCODEC_H265: { char isH265 = chnState[index].payload == HAL_VIDCODEC_H265 ? 1 : 0; + int is_sub = media_sub_ch >= 0 && index == media_sub_ch; + int is_main = !is_sub && (media_main_ch < 0 || index == media_main_ch); - if (app_config.mp4_enable) { + if (is_main && app_config.mp4_enable) { pthread_mutex_lock(&mp4Mtx); send_mp4_to_client(index, stream, isH265); if (record_active()) record_ingest_stream(stream, isH265); @@ -195,10 +199,10 @@ int save_video_stream(char index, hal_vidstream *stream) { send_h26x_to_client(index, stream); } - if (app_config.rtsp_enable) - rtp_send_h26x(rtspHandle, stream, isH265); + if (app_config.rtsp_enable && (is_main || is_sub)) + rtp_send_h26x(rtspHandle, stream, isH265, is_sub ? 1 : 0); - if (app_config.stream_enable) { + if (is_main && app_config.stream_enable) { for (int i = 0; i < stream->count; i++) { udp_stream_send_nal(stream->pack[i].data + stream->pack[i].offset, stream->pack[i].length - stream->pack[i].offset, @@ -317,16 +321,14 @@ void media_stop(void) { } void request_idr(void) { - signed char index = -1; pthread_mutex_lock(&chnMtx); for (int i = 0; i < chnCount; i++) { + signed char index; if (!chnState[i].enable) continue; if (chnState[i].payload != HAL_VIDCODEC_H264 && chnState[i].payload != HAL_VIDCODEC_H265) continue; index = i; - break; - } - if (index != -1) switch (plat) { + switch (plat) { #if defined(__ARM_PCS_VFP) case HAL_PLATFORM_I6: i6_video_request_idr(index); break; case HAL_PLATFORM_I6C: i6c_video_request_idr(index); break; @@ -347,6 +349,7 @@ void request_idr(void) { #elif defined(__riscv) || defined(__riscv__) case HAL_PLATFORM_CVI: cvi_video_request_idr(index); break; #endif + } } pthread_mutex_unlock(&chnMtx); } @@ -652,6 +655,10 @@ int media_mjpeg_enable(void) { { hal_vidconfig config; + unsigned int quality = app_config.mjpeg_qfactor ? app_config.mjpeg_qfactor : 50; + if (quality > 99) + quality = 99; + memset(&config, 0, sizeof(config)); config.width = app_config.mjpeg_width; config.height = app_config.mjpeg_height; config.codec = HAL_VIDCODEC_MJPG; @@ -659,6 +666,7 @@ int media_mjpeg_enable(void) { config.framerate = app_config.mjpeg_fps; config.bitrate = app_config.mjpeg_bitrate; config.maxBitrate = app_config.mjpeg_bitrate * 5 / 4; + config.minQual = config.maxQual = quality; switch (plat) { #if defined(__ARM_PCS_VFP) @@ -708,6 +716,9 @@ int media_mjpeg_enable(void) { int media_mp4_disable(void) { int ret; + media_main_ch = -1; + media_sub_ch = -1; + for (char i = 0; i < chnCount; i++) { if (!chnState[i].enable) continue; if (chnState[i].payload != HAL_VIDCODEC_H264 && @@ -798,6 +809,80 @@ int media_mp4_enable(void) { index, ret, errstr(ret)); } + media_main_ch = index; + return EXIT_SUCCESS; +} + +int media_sub_enable(void) { + int ret; + int index; + hal_vidconfig config; + + if (media_sub_ch >= 0) + return EXIT_SUCCESS; + + index = take_next_free_channel(true); + if (index < 0) + HAL_ERROR("media", "No free channel for the sub stream!\n"); + + if (ret = create_channel(index, MEDIA_SUB_WIDTH, MEDIA_SUB_HEIGHT, MEDIA_SUB_FPS, 0)) { + chnState[index].enable = false; + HAL_ERROR("media", "Creating sub channel %d failed with %#x!\n%s\n", + index, ret, errstr(ret)); + } + + memset(&config, 0, sizeof(config)); + config.width = MEDIA_SUB_WIDTH; + config.height = MEDIA_SUB_HEIGHT; + config.codec = HAL_VIDCODEC_H264; + config.mode = app_config.mp4_mode; + config.profile = app_config.mp4_profile; + config.gop = MEDIA_SUB_GOP; + config.framerate = MEDIA_SUB_FPS; + config.bitrate = MEDIA_SUB_BITRATE; + config.maxBitrate = MEDIA_SUB_BITRATE * 5 / 4; + ret = 0; + + switch (plat) { +#if defined(__ARM_PCS_VFP) + case HAL_PLATFORM_I6: ret = i6_video_create(index, &config); break; + case HAL_PLATFORM_I6C: ret = i6c_video_create(index, &config); break; + case HAL_PLATFORM_M6: ret = m6_video_create(index, &config); break; + case HAL_PLATFORM_RK: ret = rk_video_create(index, &config); break; +#elif defined(__arm__) && !defined(__ARM_PCS_VFP) + case HAL_PLATFORM_AK: ret = ak_video_create(index, &config); break; + case HAL_PLATFORM_GM: ret = gm_video_create(index, &config); break; + case HAL_PLATFORM_V1: ret = v1_video_create(index, &config); break; + case HAL_PLATFORM_V2: ret = v2_video_create(index, &config); break; + case HAL_PLATFORM_V3: ret = v3_video_create(index, &config); break; + case HAL_PLATFORM_V4: ret = v4_video_create(index, &config); break; + #if __ARM_ARCH == 6 + case HAL_PLATFORM_F2: ret = f2_video_create(index, &config); break; + #endif +#elif defined(__mips__) + case HAL_PLATFORM_T31: ret = t31_video_create(index, &config); break; +#elif defined(__riscv) || defined(__riscv__) + case HAL_PLATFORM_CVI: ret = cvi_video_create(index, &config); break; +#endif + } + + if (ret) { + media_video_disable(index, 0); + chnState[index].enable = false; + HAL_ERROR("media", "Creating sub encoder %d failed with %#x!\n%s\n", + index, ret, errstr(ret)); + } + + if (ret = bind_channel(index, MEDIA_SUB_FPS, 0)) { + media_video_disable(index, 0); + chnState[index].enable = false; + HAL_ERROR("media", "Binding sub channel %d failed with %#x!\n%s\n", + index, ret, errstr(ret)); + } + + media_sub_ch = index; + HAL_INFO("media", "Sub stream H.264 %dx%d on channel %d\n", + MEDIA_SUB_WIDTH, MEDIA_SUB_HEIGHT, index); return EXIT_SUCCESS; } @@ -981,6 +1066,9 @@ int sdk_start(void) { if (app_config.mjpeg_enable && (ret = media_mjpeg_enable())) HAL_ERROR("media", "MJPEG initialization failed with %#x!\n", ret); + if (app_config.mp4_enable && (ret = media_sub_enable())) + HAL_WARNING("media", "Sub stream was not created\n"); + if (app_config.jpeg_enable && (ret = jpeg_init())) HAL_ERROR("media", "JPEG initialization failed with %#x!\n", ret); diff --git a/src/media.h b/src/media.h index f7172e4b..526bfcad 100644 --- a/src/media.h +++ b/src/media.h @@ -2,6 +2,11 @@ #define AUD_RING_CAP (1 << 16) /* 64 KB, roughly 0.7 s of 48 kHz mono */ #define AUD_FRAME_MAX 16384 /* largest frame any HAL produces */ +#define MEDIA_SUB_WIDTH 640 +#define MEDIA_SUB_HEIGHT 360 +#define MEDIA_SUB_FPS 15 +#define MEDIA_SUB_BITRATE 512 +#define MEDIA_SUB_GOP 15 #include #include @@ -47,5 +52,9 @@ int media_mjpeg_disable(void); int media_mjpeg_enable(void); int media_mp4_disable(void); int media_mp4_enable(void); +int media_sub_enable(void); + +extern int media_main_ch; +extern int media_sub_ch; void rtsp_latch_audio_codec(void); diff --git a/src/onvif.c b/src/onvif.c index 10fd5b54..afb7ad4e 100644 --- a/src/onvif.c +++ b/src/onvif.c @@ -1,4 +1,10 @@ #include "onvif.h" +#include "media.h" + +#include +#include +#include +#include IMPORT_STR(.rodata, "../res/onvif/capabilities.xml", capabilitiesxml); extern const char capabilitiesxml[]; @@ -16,8 +22,38 @@ IMPORT_STR(.rodata, "../res/onvif/stream.xml", streamxml); extern const char streamxml[]; IMPORT_STR(.rodata, "../res/onvif/systemtime.xml", systemtimexml); extern const char systemtimexml[]; +IMPORT_STR(.rodata, "../res/onvif/settime.xml", settimexml); +extern const char settimexml[]; IMPORT_STR(.rodata, "../res/onvif/videosources.xml", videosourcesxml); extern const char videosourcesxml[]; +IMPORT_STR(.rodata, "../res/onvif/services.xml", servicesxml); +extern const char servicesxml[]; +IMPORT_STR(.rodata, "../res/onvif/scopes.xml", scopesxml); +extern const char scopesxml[]; +IMPORT_STR(.rodata, "../res/onvif/network.xml", networkxml); +extern const char networkxml[]; +IMPORT_STR(.rodata, "../res/onvif/hostname.xml", hostnamexml); +extern const char hostnamexml[]; +IMPORT_STR(.rodata, "../res/onvif/dns.xml", dnsxml); +extern const char dnsxml[]; +IMPORT_STR(.rodata, "../res/onvif/protocols.xml", protocolsxml); +extern const char protocolsxml[]; +IMPORT_STR(.rodata, "../res/onvif/devicecaps.xml", devicecapsxml); +extern const char devicecapsxml[]; +IMPORT_STR(.rodata, "../res/onvif/mediacaps.xml", mediacapsxml); +extern const char mediacapsxml[]; +IMPORT_STR(.rodata, "../res/onvif/profile.xml", profilexml); +extern const char profilexml[]; +IMPORT_STR(.rodata, "../res/onvif/encoders.xml", encodersxml); +extern const char encodersxml[]; +IMPORT_STR(.rodata, "../res/onvif/encoder.xml", encoderxml); +extern const char encoderxml[]; +IMPORT_STR(.rodata, "../res/onvif/sourceconfigs.xml", sourceconfigsxml); +extern const char sourceconfigsxml[]; +IMPORT_STR(.rodata, "../res/onvif/gateway.xml", gatewayxml); +extern const char gatewayxml[]; +IMPORT_STR(.rodata, "../res/onvif/encoderoptions.xml", encoderoptionsxml); +extern const char encoderoptionsxml[]; const char onvifgood[] = "HTTP/1.1 200 OK\r\n" \ "Content-Type: application/soap+xml; charset=utf-8\r\n" \ @@ -137,8 +173,8 @@ void *onvif_thread(void) { char* onvif_extract_soap_action(const char* soap_data) { static char action[128]; - char *action_start = NULL; + if (!soap_data) return NULL; char *body_start = strstr(soap_data, "Body"); if (!body_start) return NULL; @@ -151,9 +187,11 @@ char* onvif_extract_soap_action(const char* soap_data) { if (*body_start != '<') return NULL; body_start++; - char *action_end = strchr(body_start, ' '); - if (!action_end) action_end = strchr(body_start, '>'); - if (!action_end) return NULL; + char *action_end = body_start; + while (*action_end && *action_end != ' ' && *action_end != '>' && + *action_end != '/' && *action_end != '\r' && *action_end != '\n') + action_end++; + if (action_end == body_start) return NULL; int action_len = action_end - body_start; if (action_len >= sizeof(action)) action_len = sizeof(action) - 1; @@ -161,6 +199,10 @@ char* onvif_extract_soap_action(const char* soap_data) { strncpy(action, body_start, action_len); action[action_len] = '\0'; + char *colon = strrchr(action, ':'); + if (colon && colon[1]) + memmove(action, colon + 1, strlen(colon + 1) + 1); + return action; } @@ -169,6 +211,7 @@ bool onvif_is_preauth_action(const char *action) { return false; if (EQUALS(action, "GetCapabilities") || + EQUALS(action, "GetServices") || EQUALS(action, "GetSystemDateAndTime")) return true; @@ -245,6 +288,123 @@ bool onvif_validate_soap_auth(const char *soap_data) { } } +static void onvif_hwaddr(char *out, size_t outlen) { + snprintf(out, outlen, "00:00:00:00:00:00"); + if (!netinfo.count || !outlen) return; + char path[64]; + snprintf(path, sizeof(path), "/sys/class/net/%s/address", netinfo.intf[0]); + FILE *file = fopen(path, "r"); + if (!file) return; + if (fgets(out, outlen, file)) { + char *nl = strchr(out, '\n'); + if (nl) *nl = '\0'; + } + fclose(file); +} + +static int onvif_prefix(const char *ifname) { + int prefix = 24; + int fd; + struct ifreq ifr; + if (!ifname) return prefix; + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) return prefix; + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); + if (ioctl(fd, SIOCGIFNETMASK, &ifr) == 0) { + unsigned int mask = ntohl(((struct sockaddr_in *)&ifr.ifr_netmask)->sin_addr.s_addr); + prefix = 0; + while (mask & 0x80000000u) { + prefix++; + mask <<= 1; + } + } + close(fd); + return prefix; +} + +static void onvif_codec_block(char *dst, size_t dstlen, const char *encoding, int gop) { + dst[0] = '\0'; + if (!encoding || gop <= 0) return; + if (!strcmp(encoding, "H264")) + snprintf(dst, dstlen, + " \n" + " %d\n" + " Main\n" + " \n", gop); + else if (!strcmp(encoding, "H265")) + snprintf(dst, dstlen, + " \n" + " %d\n" + " Main\n" + " \n", gop); +} + +static int onvif_append_profile(char *dst, int used, const char *token, const char *name, + int src, int enc, int src_h, int src_w, const char *encoding, + int width, int height, int fps, int bitrate, int gop) { + char codec[320]; + onvif_codec_block(codec, sizeof(codec), encoding, gop); + return used + sprintf(dst + used, mediaprofilexml, + token, name, src, src, src_h, src_w, enc, enc, + encoding, width, height, fps, bitrate, codec); +} + +static int onvif_fill_profiles(char *dst, int which) { + int used = 0; + int gop; + const char *encoding; + dst[0] = '\0'; + if (app_config.mp4_enable) { + gop = app_config.mp4_gop ? app_config.mp4_gop : app_config.mp4_fps * 2; + encoding = app_config.mp4_codecH265 ? "H265" : "H264"; + if (which != 2) + used = onvif_append_profile(dst, used, "stream=0", "MainStream", 1, 1, + app_config.mp4_height, app_config.mp4_width, + encoding, app_config.mp4_width, app_config.mp4_height, + app_config.mp4_fps, app_config.mp4_bitrate, gop); + if (which != 1 && media_sub_ch >= 0) + used = onvif_append_profile(dst, used, "stream=1", "SubStream", 1, 2, + app_config.mp4_height, app_config.mp4_width, + "H264", MEDIA_SUB_WIDTH, MEDIA_SUB_HEIGHT, + MEDIA_SUB_FPS, MEDIA_SUB_BITRATE, MEDIA_SUB_GOP); + return used; + } + if (app_config.mjpeg_enable && which != 1) + used = onvif_append_profile(dst, used, "stream=1", "SubStream", 1, 1, + app_config.mjpeg_height, app_config.mjpeg_width, + "JPEG", app_config.mjpeg_width, app_config.mjpeg_height, + app_config.mjpeg_fps, app_config.mjpeg_bitrate, 0); + return used; +} + +static int onvif_fill_encoders(char *dst) { + int used = 0; + int index = 1; + char codec[320]; + dst[0] = '\0'; + if (app_config.mp4_enable) { + int gop = app_config.mp4_gop ? app_config.mp4_gop : app_config.mp4_fps * 2; + const char *encoding = app_config.mp4_codecH265 ? "H265" : "H264"; + onvif_codec_block(codec, sizeof(codec), encoding, gop); + used += sprintf(dst + used, encoderxml, index, index, encoding, + app_config.mp4_width, app_config.mp4_height, + app_config.mp4_fps, app_config.mp4_bitrate, codec); + if (media_sub_ch >= 0) { + onvif_codec_block(codec, sizeof(codec), "H264", MEDIA_SUB_GOP); + used += sprintf(dst + used, encoderxml, 2, 2, "H264", + MEDIA_SUB_WIDTH, MEDIA_SUB_HEIGHT, + MEDIA_SUB_FPS, MEDIA_SUB_BITRATE, codec); + } + } else if (app_config.mjpeg_enable) { + codec[0] = '\0'; + used += sprintf(dst + used, encoderxml, index, index, "JPEG", + app_config.mjpeg_width, app_config.mjpeg_height, + app_config.mjpeg_fps, app_config.mjpeg_bitrate, codec); + } + return used; +} + void onvif_respond_capabilities(char *response, int *respLen) { if (!response || !respLen) return; @@ -255,12 +415,13 @@ void onvif_respond_capabilities(char *response, int *respLen) { *respLen += snprintf(response + headerLen, maxLen - headerLen, capabilitiesxml, - netinfo.ipaddr[0], app_config.web_port, // Analytics - netinfo.ipaddr[0], app_config.web_port, // Device - netinfo.ipaddr[0], app_config.web_port, // Events - netinfo.ipaddr[0], app_config.web_port, // Imaging - netinfo.ipaddr[0], app_config.web_port, // Media - netinfo.ipaddr[0], app_config.web_port); // PTZ + netinfo.ipaddr[0], app_config.web_port, + netinfo.ipaddr[0], app_config.web_port, + app_config.onvif_enable_auth ? "true" : "false", + netinfo.ipaddr[0], app_config.web_port, + netinfo.ipaddr[0], app_config.web_port, + netinfo.ipaddr[0], app_config.web_port, + netinfo.ipaddr[0], app_config.web_port); } void onvif_respond_deviceinfo(char *response, int *respLen) { @@ -271,40 +432,18 @@ void onvif_respond_deviceinfo(char *response, int *respLen) { memcpy(response, onvifgood, headerLen); *respLen = headerLen; + char serial[32]; + onvif_hwaddr(serial, sizeof(serial)); *respLen += snprintf(response + headerLen, maxLen - headerLen, deviceinfoxml, - "OpenIPC", "IP Camera", "1.0", "To be replaced", chip); + "OpenIPC", "IP Camera", "1.0", serial, chip); } void onvif_respond_mediaprofiles(char *response, int *respLen) { if (!response || !respLen) return; char profile[4096]; - char profileCnt = 0; - int profileLen = 0; - - if (app_config.mp4_enable) { - profileLen += sprintf(&profile[profileLen], mediaprofilexml, - "MainStream", "profile_1", - profileCnt + 1, profileCnt + 1, - app_config.mp4_height, app_config.mp4_width, - profileCnt + 1, profileCnt + 1, - app_config.mp4_codecH265 ? "H265" : "H264", - app_config.mp4_width, app_config.mp4_height, - app_config.mp4_fps, app_config.mp4_bitrate); - profileCnt++; - } - - if (app_config.mjpeg_enable) { - profileLen += sprintf(&profile[profileLen], mediaprofilexml, - "SubStream", "profile_2", - profileCnt + 1, profileCnt + 1, - app_config.mjpeg_height, app_config.mjpeg_width, - profileCnt + 1, profileCnt + 1, - "JPEG", app_config.mjpeg_width, app_config.mjpeg_height, - app_config.mjpeg_fps, app_config.mjpeg_bitrate); - profileCnt++; - } + onvif_fill_profiles(profile, 0); int maxLen = *respLen; int headerLen = strlen(onvifgood); @@ -342,21 +481,25 @@ void onvif_respond_snapshot(char *response, int *respLen) { snapshot_url); } -void onvif_respond_stream(char *response, int *respLen) { +void onvif_respond_stream(char *response, int *respLen, const char *soap) { if (!response || !respLen) return; char stream_url[256]; + const char *name = "stream=0"; + + if (soap && strstr(soap, "stream=1") && media_sub_ch >= 0) + name = "stream=1"; if (app_config.rtsp_enable_auth && *app_config.rtsp_auth_user && *app_config.rtsp_auth_pass) { char user[96], pass[96]; escape_url(user, app_config.rtsp_auth_user, sizeof(user)); escape_url(pass, app_config.rtsp_auth_pass, sizeof(pass)); - snprintf(stream_url, sizeof(stream_url), "rtsp://%s:%s@%s:%d/", - user, pass, netinfo.ipaddr[0], app_config.rtsp_port); + snprintf(stream_url, sizeof(stream_url), "rtsp://%s:%s@%s:%d/%s", + user, pass, netinfo.ipaddr[0], app_config.rtsp_port, name); } else - snprintf(stream_url, sizeof(stream_url), "rtsp://%s:%d/", - netinfo.ipaddr[0], app_config.rtsp_port); + snprintf(stream_url, sizeof(stream_url), "rtsp://%s:%d/%s", + netinfo.ipaddr[0], app_config.rtsp_port, name); int maxLen = *respLen; int headerLen = strlen(onvifgood); @@ -388,6 +531,61 @@ void onvif_respond_systemtime(char *response, int *respLen) { tm_info->tm_year + 1900, tm_info->tm_mon + 1, tm_info->tm_mday); } +static int onvif_xml_int(const char *xml, const char *tag) { + char key[48]; + const char *pos; + if (!xml || !tag) return -1; + snprintf(key, sizeof(key), "%s>", tag); + pos = strstr(xml, key); + if (!pos) return -1; + pos += strlen(key); + while (*pos == ' ' || *pos == '\t' || *pos == '\r' || *pos == '\n') pos++; + if (*pos < '0' || *pos > '9') return -1; + return atoi(pos); +} + +static time_t onvif_timegm(int year, int month, int day, int hour, int min, int sec) { + static const int mdays[] = {0,31,59,90,120,151,181,212,243,273,304,334}; + int leaps, leaps0; + long days; + if (month < 1 || month > 12 || day < 1 || day > 31) return (time_t)-1; + leaps = ((year - 1) / 4) - ((year - 1) / 100) + ((year - 1) / 400); + leaps0 = (1969 / 4) - (1969 / 100) + (1969 / 400); + days = (year - 1970) * 365L + (leaps - leaps0) + mdays[month - 1] + (day - 1); + if (month > 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)) + days++; + return days * 86400L + hour * 3600 + min * 60 + sec; +} + +static void onvif_begin(char *response, int *respLen, int *headerLen, int *maxLen); + +void onvif_respond_settime(char *response, int *respLen, const char *soap) { + int headerLen, maxLen; + const char *when; + int year, month, day, hour, min, sec; + if (!response || !respLen) return; + when = soap ? strstr(soap, "UTCDateTime") : NULL; + if (!when) when = soap; + year = onvif_xml_int(when, "Year"); + month = onvif_xml_int(when, "Month"); + day = onvif_xml_int(when, "Day"); + hour = onvif_xml_int(when, "Hour"); + min = onvif_xml_int(when, "Minute"); + sec = onvif_xml_int(when, "Second"); + if (year > 2000 && hour >= 0 && min >= 0 && sec >= 0) { + time_t stamp = onvif_timegm(year, month, day, hour, min, sec); + struct timeval tv = { .tv_sec = stamp, .tv_usec = 0 }; + if (stamp == (time_t)-1 || settimeofday(&tv, NULL) != 0) + HAL_WARNING("onvif", "Could not set the clock to %04d-%02d-%02d %02d:%02d:%02d\n", + year, month, day, hour, min, sec); + else + HAL_INFO("onvif", "Clock set to %04d-%02d-%02d %02d:%02d:%02d UTC\n", + year, month, day, hour, min, sec); + } + onvif_begin(response, respLen, &headerLen, &maxLen); + *respLen += snprintf(response + headerLen, maxLen - headerLen, "%s", settimexml); +} + void onvif_respond_videosources(char *response, int *respLen) { if (!response || !respLen) return; @@ -406,4 +604,407 @@ void onvif_respond_videosources(char *response, int *respLen) { *respLen += snprintf(response + headerLen, maxLen - headerLen, videosourcesxml, framerate, width, height); +} + +static void onvif_begin(char *response, int *respLen, int *headerLen, int *maxLen) { + *maxLen = *respLen; + *headerLen = strlen(onvifgood); + memcpy(response, onvifgood, *headerLen); + *respLen = *headerLen; +} + +void onvif_respond_services(char *response, int *respLen) { + int headerLen, maxLen; + if (!response || !respLen) return; + onvif_begin(response, respLen, &headerLen, &maxLen); + *respLen += snprintf(response + headerLen, maxLen - headerLen, servicesxml, + netinfo.ipaddr[0], app_config.web_port, + netinfo.ipaddr[0], app_config.web_port); +} + +void onvif_respond_scopes(char *response, int *respLen) { + int headerLen, maxLen; + if (!response || !respLen) return; + onvif_begin(response, respLen, &headerLen, &maxLen); + *respLen += snprintf(response + headerLen, maxLen - headerLen, scopesxml, chip); +} + +void onvif_respond_network(char *response, int *respLen) { + int headerLen, maxLen; + char mac[32]; + const char *name = netinfo.count ? netinfo.intf[0] : "eth0"; + const char *ip = netinfo.count ? netinfo.ipaddr[0] : "0.0.0.0"; + if (!response || !respLen) return; + onvif_hwaddr(mac, sizeof(mac)); + onvif_begin(response, respLen, &headerLen, &maxLen); + *respLen += snprintf(response + headerLen, maxLen - headerLen, networkxml, + name, name, mac, ip, onvif_prefix(name)); +} + +void onvif_respond_hostname(char *response, int *respLen) { + int headerLen, maxLen; + if (!response || !respLen) return; + onvif_begin(response, respLen, &headerLen, &maxLen); + *respLen += snprintf(response + headerLen, maxLen - headerLen, hostnamexml, + netinfo.host[0] ? netinfo.host : "divinus"); +} + +void onvif_respond_dns(char *response, int *respLen) { + int headerLen, maxLen; + if (!response || !respLen) return; + onvif_begin(response, respLen, &headerLen, &maxLen); + *respLen += snprintf(response + headerLen, maxLen - headerLen, "%s", dnsxml); +} + +void onvif_respond_protocols(char *response, int *respLen) { + int headerLen, maxLen; + if (!response || !respLen) return; + onvif_begin(response, respLen, &headerLen, &maxLen); + *respLen += snprintf(response + headerLen, maxLen - headerLen, protocolsxml, + app_config.web_port, + app_config.rtsp_enable ? "true" : "false", + app_config.rtsp_port); +} + +void onvif_respond_device_caps(char *response, int *respLen) { + int headerLen, maxLen; + if (!response || !respLen) return; + onvif_begin(response, respLen, &headerLen, &maxLen); + *respLen += snprintf(response + headerLen, maxLen - headerLen, devicecapsxml, + app_config.onvif_enable_auth ? "true" : "false"); +} + +void onvif_respond_media_caps(char *response, int *respLen) { + int headerLen, maxLen; + if (!response || !respLen) return; + onvif_begin(response, respLen, &headerLen, &maxLen); + *respLen += snprintf(response + headerLen, maxLen - headerLen, "%s", mediacapsxml); +} + +void onvif_respond_profile(char *response, int *respLen, const char *soap) { + int headerLen, maxLen; + char profile[4096]; + char *close; + int which = (soap && strstr(soap, "stream=1")) ? 2 : 1; + if (!response || !respLen) return; + onvif_fill_profiles(profile, which); + if (!profile[0]) + onvif_fill_profiles(profile, 1); + if ((close = strstr(profile, ""))) { + memmove(close + 14, close + 15, strlen(close + 15) + 1); + memcpy(close, "", 14); + } + onvif_begin(response, respLen, &headerLen, &maxLen); + *respLen += snprintf(response + headerLen, maxLen - headerLen, profilexml, profile); +} + +void onvif_respond_encoders(char *response, int *respLen) { + int headerLen, maxLen; + char body[4096]; + if (!response || !respLen) return; + onvif_fill_encoders(body); + onvif_begin(response, respLen, &headerLen, &maxLen); + *respLen += snprintf(response + headerLen, maxLen - headerLen, encodersxml, body); +} + +void onvif_respond_source_configs(char *response, int *respLen) { + int headerLen, maxLen; + int width = app_config.mp4_enable ? app_config.mp4_width : app_config.mjpeg_width; + int height = app_config.mp4_enable ? app_config.mp4_height : app_config.mjpeg_height; + if (!response || !respLen) return; + onvif_begin(response, respLen, &headerLen, &maxLen); + *respLen += snprintf(response + headerLen, maxLen - headerLen, sourceconfigsxml, + height, width); +} + +static void onvif_gateway_addr(char *out, size_t outlen) { + FILE *file; + char line[256]; + snprintf(out, outlen, "0.0.0.0"); + file = fopen("/proc/net/route", "r"); + if (!file) return; + if (!fgets(line, sizeof(line), file)) { + fclose(file); + return; + } + while (fgets(line, sizeof(line), file)) { + char iface[16]; + unsigned int dest = 0, gateway = 0; + if (sscanf(line, "%15s %x %x", iface, &dest, &gateway) == 3 && + dest == 0 && gateway) { + snprintf(out, outlen, "%u.%u.%u.%u", + gateway & 255, (gateway >> 8) & 255, + (gateway >> 16) & 255, (gateway >> 24) & 255); + break; + } + } + fclose(file); +} + +void onvif_respond_gateway(char *response, int *respLen) { + int headerLen, maxLen; + char gateway[32]; + if (!response || !respLen) return; + onvif_gateway_addr(gateway, sizeof(gateway)); + onvif_begin(response, respLen, &headerLen, &maxLen); + *respLen += snprintf(response + headerLen, maxLen - headerLen, gatewayxml, gateway); +} + +void onvif_respond_encoder_options(char *response, int *respLen) { + int headerLen, maxLen; + int width = app_config.mp4_enable ? app_config.mp4_width : app_config.mjpeg_width; + int height = app_config.mp4_enable ? app_config.mp4_height : app_config.mjpeg_height; + int fps = app_config.mp4_enable ? app_config.mp4_fps : app_config.mjpeg_fps; + if (!response || !respLen) return; + if (fps <= 0) fps = 25; + onvif_begin(response, respLen, &headerLen, &maxLen); + *respLen += snprintf(response + headerLen, maxLen - headerLen, encoderoptionsxml, + width, height, MEDIA_SUB_WIDTH, MEDIA_SUB_HEIGHT, fps); +} + +static int onvif_static(char *response, int *respLen, const char *body) { + int headerLen, maxLen; + onvif_begin(response, respLen, &headerLen, &maxLen); + *respLen += snprintf(response + headerLen, maxLen - headerLen, "%s", body); + return 1; +} + +static const char onvif_relay[] = + "" + "" + ""; + +static const char onvif_setnetif[] = + "" + "" + "false" + ""; + +static const char onvif_ntp[] = + "" + "" + "false" + ""; + +static const char onvif_discover[] = + "" + "" + "Discoverable" + ""; + +static const char onvif_osds[] = + "" + "" + ""; + +static const char onvif_osdopt[] = + "" + "" + "" + "" + ""; + +static const char onvif_audiosrc[] = + "" + "" + ""; + +static const char onvif_audioenc[] = + "" + "" + ""; + +static const char onvif_audioopt[] = + "" + "" + "" + ""; + +extern void request_idr(void); + +static const char onvif_sync[] = + "" + "" + ""; + +static const char onvif_meta[] = + "" + "" + ""; + +static const char onvif_image[] = + "" + "" + "" + "5050" + "5050" + ""; + +static const char onvif_imageopt[] = + "" + "" + "" + "0100" + "0100" + "0100" + "0100" + ""; + +static const char onvif_imageset[] = + "" + "" + ""; + +static const char onvif_events[] = + "" + "" + "" + "http://www.onvif.org/onvif/ver10/topics/topicns.xml" + "true" + "http://www.onvif.org/ver10/tev/topicExpression/ConcreteSet" + "http://www.onvif.org/ver10/tev/messageContentFilter/ItemFilter" + "http://www.onvif.org/onvif/ver10/schema/onvif.xsd" + ""; + +static const char onvif_ptzcfg[] = + "" + "" + ""; + +static const char onvif_ptzpresets[] = + "" + "" + ""; + +static const char onvif_ptznodes[] = + "" + "" + ""; + +static const char onvif_ptzmove[] = + "" + "" + ""; + +static const char onvif_ptzstop[] = + "" + "" + ""; + +static int onvif_source_one(char *response, int *respLen) { + int headerLen, maxLen; + int width = app_config.mp4_enable ? app_config.mp4_width : app_config.mjpeg_width; + int height = app_config.mp4_enable ? app_config.mp4_height : app_config.mjpeg_height; + onvif_begin(response, respLen, &headerLen, &maxLen); + *respLen += snprintf(response + headerLen, maxLen - headerLen, + "" + "" + "" + "VideoSourceConfig_11" + "VideoSource_1" + "", + height, width); + return 1; +} + +static int onvif_source_options(char *response, int *respLen) { + int headerLen, maxLen; + int width = app_config.mp4_enable ? app_config.mp4_width : app_config.mjpeg_width; + int height = app_config.mp4_enable ? app_config.mp4_height : app_config.mjpeg_height; + onvif_begin(response, respLen, &headerLen, &maxLen); + *respLen += snprintf(response + headerLen, maxLen - headerLen, + "" + "" + "" + "00" + "00" + "%d%d" + "%d%d" + "VideoSource_1" + "", + width, width, height, height); + return 1; +} + +static void onvif_keep_encoder(char *body, const char *soap) { + char *first = strstr(body, ""))) { + memmove(close + 20, close + 21, strlen(close + 21) + 1); + memcpy(close, "", 20); + } + onvif_begin(response, respLen, &headerLen, &maxLen); + *respLen += snprintf(response + headerLen, maxLen - headerLen, + "" + "" + "%s", + wrapper, body, wrapper); + return 1; +} + +int onvif_dispatch_extra(const char *path, const char *action, + char *response, int *respLen, const char *soap) { + if (!path || !action || !response || !respLen) return 0; + if (EQUALS(path, "device_service")) { + if (EQUALS(action, "GetRelayOutputs")) return onvif_static(response, respLen, onvif_relay); + if (EQUALS(action, "SetNetworkInterfaces")) return onvif_static(response, respLen, onvif_setnetif); + if (EQUALS(action, "GetNTP")) return onvif_static(response, respLen, onvif_ntp); + if (EQUALS(action, "GetDiscoveryMode")) return onvif_static(response, respLen, onvif_discover); + } else if (EQUALS(path, "media_service")) { + if (EQUALS(action, "GetVideoSourceConfiguration")) return onvif_source_one(response, respLen); + if (EQUALS(action, "GetVideoSourceConfigurationOptions")) return onvif_source_options(response, respLen); + if (EQUALS(action, "GetVideoEncoderConfiguration")) + return onvif_encoder_one(response, respLen, "GetVideoEncoderConfiguration", 1, soap); + if (EQUALS(action, "GetCompatibleVideoEncoderConfigurations")) + return onvif_encoder_one(response, respLen, "GetCompatibleVideoEncoderConfigurations", 0, soap); + if (EQUALS(action, "GetOSDs")) return onvif_static(response, respLen, onvif_osds); + if (EQUALS(action, "GetOSDOptions")) return onvif_static(response, respLen, onvif_osdopt); + if (EQUALS(action, "GetAudioSources")) return onvif_static(response, respLen, onvif_audiosrc); + if (EQUALS(action, "GetAudioEncoderConfigurations") || + EQUALS(action, "GetAudioEncoderConfiguration")) + return onvif_static(response, respLen, onvif_audioenc); + if (EQUALS(action, "GetAudioEncoderConfigurationOptions")) + return onvif_static(response, respLen, onvif_audioopt); + if (EQUALS(action, "GetMetadataConfigurations")) return onvif_static(response, respLen, onvif_meta); + if (EQUALS(action, "SetSynchronizationPoint")) { + request_idr(); + return onvif_static(response, respLen, onvif_sync); + } + } else if (EQUALS(path, "imaging_service")) { + if (EQUALS(action, "GetImagingSettings")) return onvif_static(response, respLen, onvif_image); + if (EQUALS(action, "GetOptions")) return onvif_static(response, respLen, onvif_imageopt); + if (EQUALS(action, "SetImagingSettings")) return onvif_static(response, respLen, onvif_imageset); + } else if (EQUALS(path, "event_service")) { + if (EQUALS(action, "GetEventProperties")) return onvif_static(response, respLen, onvif_events); + } else if (EQUALS(path, "ptz_service")) { + if (EQUALS(action, "GetConfigurations") || EQUALS(action, "GetConfiguration")) + return onvif_static(response, respLen, onvif_ptzcfg); + if (EQUALS(action, "GetPresets")) return onvif_static(response, respLen, onvif_ptzpresets); + if (EQUALS(action, "GetNodes") || EQUALS(action, "GetNode")) + return onvif_static(response, respLen, onvif_ptznodes); + if (EQUALS(action, "ContinuousMove")) return onvif_static(response, respLen, onvif_ptzmove); + if (EQUALS(action, "Stop")) return onvif_static(response, respLen, onvif_ptzstop); + } + return 0; } \ No newline at end of file diff --git a/src/onvif.h b/src/onvif.h index 2aadf692..c65679cf 100644 --- a/src/onvif.h +++ b/src/onvif.h @@ -21,6 +21,22 @@ void onvif_respond_capabilities(char *response, int *respLen); void onvif_respond_deviceinfo(char *response, int *respLen); void onvif_respond_mediaprofiles(char *response, int *respLen); void onvif_respond_snapshot(char *response, int *respLen); -void onvif_respond_stream(char *response, int *respLen); +void onvif_respond_stream(char *response, int *respLen, const char *soap); void onvif_respond_systemtime(char *response, int *respLen); -void onvif_respond_videosources(char *response, int *respLen); \ No newline at end of file +void onvif_respond_settime(char *response, int *respLen, const char *soap); +void onvif_respond_videosources(char *response, int *respLen); +void onvif_respond_services(char *response, int *respLen); +void onvif_respond_scopes(char *response, int *respLen); +void onvif_respond_network(char *response, int *respLen); +void onvif_respond_hostname(char *response, int *respLen); +void onvif_respond_dns(char *response, int *respLen); +void onvif_respond_protocols(char *response, int *respLen); +void onvif_respond_device_caps(char *response, int *respLen); +void onvif_respond_media_caps(char *response, int *respLen); +void onvif_respond_profile(char *response, int *respLen, const char *soap); +void onvif_respond_encoders(char *response, int *respLen); +void onvif_respond_source_configs(char *response, int *respLen); +void onvif_respond_gateway(char *response, int *respLen); +void onvif_respond_encoder_options(char *response, int *respLen); +int onvif_dispatch_extra(const char *path, const char *action, + char *response, int *respLen, const char *soap); \ No newline at end of file diff --git a/src/rtsp/rtp.c b/src/rtsp/rtp.c index 242228e4..d48711c7 100644 --- a/src/rtsp/rtp.c +++ b/src/rtsp/rtp.c @@ -28,12 +28,13 @@ static inline int __rtp_send_eachconnection(struct list_t *e, void *v); static inline int __rtp_setup_transfer(struct list_t *e, void *v); static inline int __transfer_nal_h26x(struct list_head_t *trans_list, unsigned char *nalptr, size_t nalsize, char isH265); static inline int __transfer_nal_mpga(struct list_head_t *trans_list, unsigned char *ptr, size_t size); -static inline int __retrieve_sprop(rtsp_handle h, unsigned char *buf, size_t len); +static inline int __retrieve_sprop(rtsp_handle h, unsigned char *buf, size_t len, int slot); struct __transfer_set_t { struct list_head_t list_head; rtsp_handle h; int track_id; + int slot; }; static unsigned int __frame_ts_video, __frame_ts_audio; @@ -323,7 +324,8 @@ static inline int __rtp_setup_transfer(struct list_t *e, void *v) MUST(bufpool_attach(con->pool, con) == SUCCESS, return FAILURE); - if (con->con_state == __CON_S_PLAYING) { + if (con->con_state == __CON_S_PLAYING && + (trans_set->track_id != 0 || con->stream_slot == trans_set->slot)) { ASSERT(bufpool_get_free(trans_set->h->transfer_pool, &trans) == SUCCESS, ({ ERR("transfer object resource starvation detected. possibly connection limits are wrongfully setup\n"); @@ -351,7 +353,7 @@ static inline int __rtp_setup_transfer(struct list_t *e, void *v) return ret; } -static inline int __retrieve_sprop(rtsp_handle h, unsigned char *buf, size_t len) +static inline int __retrieve_sprop(rtsp_handle h, unsigned char *buf, size_t len, int slot) { unsigned char *nalptr; size_t single_len; @@ -359,7 +361,7 @@ static inline int __retrieve_sprop(rtsp_handle h, unsigned char *buf, size_t len mime_encoded_handle base16 = NULL; /* check VPS is set */ - if (h->isH265 && !(h->sprop_vps_b64)) { + if (h->isH265[slot] && !(h->sprop_vps_b64[slot])) { nalptr = buf; single_len = 0; while (nal_split(buf, &nalptr, &single_len, len) == SUCCESS) { @@ -371,11 +373,11 @@ static inline int __retrieve_sprop(rtsp_handle h, unsigned char *buf, size_t len /* optimistic lock */ rtsp_lock(h); - if (h->sprop_vps_b64) { + if (h->sprop_vps_b64[slot]) { DBG("vps is set by another thread?\n"); mime_encoded_delete(base64); } else { - h->sprop_vps_b64 = base64; + h->sprop_vps_b64[slot] = base64; } rtsp_unlock(h); } @@ -386,13 +388,13 @@ static inline int __retrieve_sprop(rtsp_handle h, unsigned char *buf, size_t len } /* check SPS is set */ - if (!(h->sprop_sps_b64)) { + if (!(h->sprop_sps_b64[slot])) { nalptr = buf; single_len = 0; while (nal_split(buf, &nalptr, &single_len, len) == SUCCESS) { - if ((!(h->isH265) && (nalptr[0] & 0x1F) == H264_NAL_TYPE_SPS) || - (h->isH265 && (nalptr[0] >> 1 & 0x3F) == H265_NAL_TYPE_SPS)) { + if ((!(h->isH265[slot]) && (nalptr[0] & 0x1F) == H264_NAL_TYPE_SPS) || + (h->isH265[slot] && (nalptr[0] >> 1 & 0x3F) == H265_NAL_TYPE_SPS)) { ASSERT(base64 = mime_base64_create((char *)&(nalptr[0]), single_len), return FAILURE); ASSERT(base16 = mime_base16_create((char *)&(nalptr[1]), 3), return FAILURE); @@ -401,18 +403,18 @@ static inline int __retrieve_sprop(rtsp_handle h, unsigned char *buf, size_t len /* optimistic lock */ rtsp_lock(h); - if (h->sprop_sps_b64) { + if (h->sprop_sps_b64[slot]) { DBG("sps is set by another thread?\n"); mime_encoded_delete(base64); } else { - h->sprop_sps_b64 = base64; + h->sprop_sps_b64[slot] = base64; } - if (h->sprop_sps_b16) { + if (h->sprop_sps_b16[slot]) { DBG("sps is set by another thread?\n"); mime_encoded_delete(base16); } else { - h->sprop_sps_b16 = base16; + h->sprop_sps_b16[slot] = base16; } rtsp_unlock(h); } @@ -423,12 +425,12 @@ static inline int __retrieve_sprop(rtsp_handle h, unsigned char *buf, size_t len } /* check PPS is set */ - if (!(h->sprop_pps_b64)) { + if (!(h->sprop_pps_b64[slot])) { nalptr = buf; single_len = 0; while (nal_split(buf, &nalptr, &single_len, len) == SUCCESS) { - if ((!(h->isH265) && (nalptr[0] & 0x1F) == H264_NAL_TYPE_PPS) || - (h->isH265 && (nalptr[0] >> 1 & 0x3F) == H265_NAL_TYPE_PPS)) { + if ((!(h->isH265[slot]) && (nalptr[0] & 0x1F) == H264_NAL_TYPE_PPS) || + (h->isH265[slot] && (nalptr[0] >> 1 & 0x3F) == H265_NAL_TYPE_PPS)) { ASSERT(single_len >= 4, return FAILURE); ASSERT(base64 = mime_base64_create((char *)&(nalptr[0]), single_len), return FAILURE); @@ -436,11 +438,11 @@ static inline int __retrieve_sprop(rtsp_handle h, unsigned char *buf, size_t len /* optimistic lock */ rtsp_lock(h); - if (h->sprop_pps_b64) { + if (h->sprop_pps_b64[slot]) { DBG("pps is set by another thread?\n"); mime_encoded_delete(base64); } else { - h->sprop_pps_b64 = base64; + h->sprop_pps_b64[slot] = base64; } rtsp_unlock(h); } @@ -482,12 +484,14 @@ void rtp_disable_audio(rtsp_handle h) h->audioPt = 255; } -int rtp_send_h26x(rtsp_handle h, hal_vidstream *stream, char isH265) +int rtp_send_h26x(rtsp_handle h, hal_vidstream *stream, char isH265, int slot) { int ret = FAILURE; int track_id = 0; struct __transfer_set_t trans = {}; + if (slot < 0 || slot > 1) slot = 0; + /* checkout RTP packet */ DASSERT(h, return FAILURE); @@ -498,7 +502,7 @@ int rtp_send_h26x(rtsp_handle h, hal_vidstream *stream, char isH265) return FAILURE; } - h->isH265 = isH265; + h->isH265[slot] = isH265; /* Stamp with the encoder's capture time (pack timestamps are microseconds) * so receivers pace frames correctly even when a large keyframe makes the * sender deliver the following frames in a burst; fall back to the send @@ -508,11 +512,12 @@ int rtp_send_h26x(rtsp_handle h, hal_vidstream *stream, char isH265) for (int i = 0; i < stream->count; i++) { ASSERT(__retrieve_sprop(h, stream->pack[i].data + stream->pack[i].offset, - stream->pack[i].length - stream->pack[i].offset) == SUCCESS, goto error); + stream->pack[i].length - stream->pack[i].offset, slot) == SUCCESS, goto error); } trans.h = h; trans.track_id = track_id; + trans.slot = slot; /* setup transmission object */ rtsp_lock(h); @@ -526,7 +531,7 @@ int rtp_send_h26x(rtsp_handle h, hal_vidstream *stream, char isH265) unsigned char *nalptr = data; size_t single_len = 0; while (nal_split(data, &nalptr, &single_len, length) == SUCCESS) { - ASSERT(__transfer_nal_h26x(&(trans.list_head), nalptr, single_len, h->isH265) == SUCCESS, goto error); + ASSERT(__transfer_nal_h26x(&(trans.list_head), nalptr, single_len, h->isH265[slot]) == SUCCESS, goto error); } } ASSERT(list_map_inline(&(trans.list_head), (__tcp_flush_each), NULL) == SUCCESS, goto error); diff --git a/src/rtsp/rtsp.c b/src/rtsp/rtsp.c index 4a61d68d..2b04062e 100644 --- a/src/rtsp/rtsp.c +++ b/src/rtsp/rtsp.c @@ -38,6 +38,8 @@ extern void request_idr(); #define __STR_SESSION "SESSION" #define __STR_PAUSE "PAUSE" #define __STR_RECORDING "RECORDING" +#define __STR_GET_PARAMETER "GET_PARAMETER" +#define __STR_SET_PARAMETER "SET_PARAMETER" #define __STR_RANGE "RANGE" #define __SPACE " " @@ -160,7 +162,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, PAUSE, GET_PARAMETER, SET_PARAMETER\r\n" "\r\n", p->cseq); } @@ -178,6 +180,13 @@ static void __method_describe(struct connection_item_t *p, rtsp_handle h) char audioRtp[256] = "\r\n"; char audioRtpfmt[16]; + int slot = p->stream_slot > 1 ? 0 : p->stream_slot; + mime_encoded_handle vps = h->sprop_vps_b64[slot]; + mime_encoded_handle sps = h->sprop_sps_b64[slot]; + mime_encoded_handle pps = h->sprop_pps_b64[slot]; + mime_encoded_handle sps16 = h->sprop_sps_b16[slot]; + char hevc = h->isH265[slot]; + if (h->audioPt != 255) { switch (h->audioPt) { case 0: strncpy(audioRtpfmt, "PCMU", 16 - 1); break; @@ -195,17 +204,16 @@ static void __method_describe(struct connection_item_t *p, rtsp_handle h) h->audioPt, h->audioPt, audioRtpfmt, (h->audioPt == 8 || h->audioPt == 0) ? 8000 : 90000); } - if (h->isH265 && - h->sprop_vps_b64 && h->sprop_sps_b64 && h->sprop_sps_b16 && h->sprop_pps_b64) { - DASSERT(h->sprop_vps_b64->result, return); - DASSERT(h->sprop_sps_b64->result, return); - DASSERT(h->sprop_sps_b16->result, return); - DASSERT(h->sprop_pps_b64->result, return); + if (hevc && vps && sps && sps16 && pps) { + DASSERT(vps->result, return); + DASSERT(sps->result, return); + DASSERT(sps16->result, return); + DASSERT(pps->result, return); - DBG("VPS BASE64:%s\n", h->sprop_vps_b64->result); - DBG("SPS BASE64:%s\n", h->sprop_sps_b64->result); - DBG("SPS BASE16:%s\n", h->sprop_sps_b16->result); - DBG("PPS BASE64:%s\n", h->sprop_pps_b64->result); + DBG("VPS BASE64:%s\n", vps->result); + DBG("SPS BASE64:%s\n", sps->result); + DBG("SPS BASE16:%s\n", sps16->result); + DBG("PPS BASE64:%s\n", pps->result); snprintf(sdp, __RTSP_TCP_BUF_SIZE- 1, "%sm=video 0 RTP/AVP 96\r\n" @@ -215,20 +223,19 @@ static void __method_describe(struct connection_item_t *p, rtsp_handle h) " packetization-mode=1;" " sprop-parameter-sets=%s,%s,%s;%s", baseRtp, - h->sprop_sps_b16->result, - h->sprop_vps_b64->result, - h->sprop_sps_b64->result, - h->sprop_pps_b64->result, + sps16->result, + vps->result, + sps->result, + pps->result, audioRtp); - } else if (!h->isH265 && - h->sprop_sps_b64 && h->sprop_sps_b16 && h->sprop_pps_b64) { - DASSERT(h->sprop_sps_b64->result, return); - DASSERT(h->sprop_sps_b16->result, return); - DASSERT(h->sprop_pps_b64->result, return); + } else if (!hevc && sps && sps16 && pps) { + DASSERT(sps->result, return); + DASSERT(sps16->result, return); + DASSERT(pps->result, return); - DBG("SPS BASE64:%s\n", h->sprop_sps_b64->result); - DBG("SPS BASE16:%s\n", h->sprop_sps_b16->result); - DBG("PPS BASE64:%s\n", h->sprop_pps_b64->result); + DBG("SPS BASE64:%s\n", sps->result); + DBG("SPS BASE16:%s\n", sps16->result); + DBG("PPS BASE64:%s\n", pps->result); snprintf(sdp, __RTSP_TCP_BUF_SIZE - 1, "%sm=video 0 RTP/AVP 96\r\n" @@ -238,9 +245,9 @@ static void __method_describe(struct connection_item_t *p, rtsp_handle h) " packetization-mode=1;" " sprop-parameter-sets=%s,%s;%s", baseRtp, - h->sprop_sps_b16->result, - h->sprop_sps_b64->result, - h->sprop_pps_b64->result, + sps16->result, + sps->result, + pps->result, audioRtp); } else { snprintf(sdp, __RTSP_TCP_BUF_SIZE - 1, @@ -249,7 +256,7 @@ static void __method_describe(struct connection_item_t *p, rtsp_handle h) "a=rtpmap:96 %s/90000\r\n" "a=fmtp:96 packetization-mode=1;%s", baseRtp, - h->isH265 ? "H265" : "H264", + hevc ? "H265" : "H264", audioRtp); } @@ -265,8 +272,14 @@ static void __method_setup(struct connection_item_t *p, rtsp_handle h) { /* make randomized session id */ if (!p->session_id) { + if (!h->ctx) + h->ctx = (unsigned)millis() | 1u; p->session_id = __get_random_llu(&h->ctx); - p->ssrc = (unsigned int)(__get_random_llu(&h->ctx)); + if (!p->session_id) + p->session_id = 0x4e5652ull; + p->ssrc = (unsigned int)__get_random_llu(&h->ctx); + if (!p->ssrc) + p->ssrc = 0x44560001u; } DBG("created session id %llx\n", p->session_id); @@ -314,7 +327,18 @@ static void __method_record(struct connection_item_t *p, rtsp_handle h) 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_BADREQUEST "\r\n" + "CSeq: %d\r\n" + "\r\n", p->cseq); +} + +static void __method_parameter(struct connection_item_t *p, rtsp_handle h) +{ + __rtsp_write(p, + "RTSP/1.0 200 OK\r\n" + "CSeq: %d\r\n" + "Session: %llx\r\n" + "\r\n", p->cseq, p->session_id); } static void __method_play(struct connection_item_t *p, rtsp_handle h) @@ -325,20 +349,22 @@ static void __method_play(struct connection_item_t *p, rtsp_handle h) "Range: npt=now-\r\n" "\r\n" , p->cseq); - for (int i = 0; i < sizeof(p->trans) / sizeof(*p->trans); i++) { - if (!p->trans[i].server_port_rtp) continue; + for (int i = 0; i < (int)(sizeof(p->trans) / sizeof(*p->trans)); i++) { + if (!p->trans[i].server_port_rtp && !p->trans[i].is_tcp) continue; p->track_id = i; - ASSERT(__bind_rtcp(p) == SUCCESS, return); - ASSERT(__bind_rtp(p) == SUCCESS, return); - p->trans[p->track_id].rtp_timestamp = (millis() * 90) & UINT32_MAX; - p->trans[p->track_id].rtp_seq = rand_r(&h->ctx); - p->trans[p->track_id].rtcp_octet = 0; - p->trans[p->track_id].rtcp_packet_cnt = 0; - p->trans[p->track_id].rtcp_tick_org = 150; // TODO: must be variant - p->trans[p->track_id].rtcp_tick = p->trans[p->track_id].rtcp_tick_org; - - ASSERT(__rtcp_send_sr(p, p->track_id) == SUCCESS, return); + if (!p->trans[i].is_tcp) { + ASSERT(__bind_rtcp(p) == SUCCESS, return); + ASSERT(__bind_rtp(p) == SUCCESS, return); + } + p->trans[i].rtp_timestamp = (millis() * 90) & UINT32_MAX; + p->trans[i].rtp_seq = rand_r(&h->ctx); + p->trans[i].rtcp_octet = 0; + p->trans[i].rtcp_packet_cnt = 0; + p->trans[i].rtcp_tick_org = 150; + p->trans[i].rtcp_tick = p->trans[i].rtcp_tick_org; + + ASSERT(__rtcp_send_sr(p, i) == SUCCESS, return); } p->con_state = __CON_S_PLAYING; @@ -419,7 +445,16 @@ static int __message_proc_sock(struct list_t *e, void *p) } else if (SCMP(__STR_RECORDING, buf)) { con->method = __METHOD_RECORDING; } else if (SCMP(__STR_PAUSE, buf)) { con->method = __METHOD_PAUSE; } else if (SCMP(__STR_TEARDOWN, buf)) { con->method = __METHOD_TEARDOWN; + } else if (SCMP(__STR_GET_PARAMETER, buf) || + SCMP(__STR_SET_PARAMETER, buf)) { con->method = __METHOD_PARAMETER; } header++; + if (strstr(buf, "rtsp:")) { + const char *stream = strstr(buf, "stream="); + con->stream_slot = (stream && stream[7] == '1' && + (stream[8] < '0' || stream[8] > '9')) ? 1 : 0; + } + if (strstr(buf, "trackID=")) + STR_KEY_NUM(buf, "trackID=", con->track_id); } if (SCMP(__STR_AUTH, buf) && h->isAuthOn) { @@ -429,9 +464,12 @@ static int __message_proc_sock(struct list_t *e, void *p) base64_encode(valid + 6, cred, strlen(cred)); isAuthValid = !strncmp(buf + strlen(__STR_AUTH) + 2, valid, strlen(valid)); } else if (SCMP(__STR_CSEQ, buf)) { - ASSERT(tok = strtok_r(buf, ": ", &last), goto error); - ASSERT(tok = strtok_r(NULL, ": ", &last), goto error); - ASSERT((con->cseq = atoi(tok)) > 0, goto error); + char *value = strchr(buf, ':'); + int seq; + if (!value) goto error; + seq = atoi(value + 1); + if (seq < 0) goto error; + con->cseq = seq; con->parser_state = __PARSER_S_CSEQ; } else if (SCMP(__STR_RANGE, buf)) { con->parser_state = __PARSER_S_RANGE; @@ -482,10 +520,15 @@ static int __message_proc_sock(struct list_t *e, void *p) case __METHOD_PAUSE: __method_pause(con, h); break; case __METHOD_RECORDING: __method_record(con, h); break; case __METHOD_TEARDOWN: __method_teardown(con, h); break; + case __METHOD_PARAMETER: __method_parameter(con, h); break; case __METHOD_NONE: - /* state DISCONNECTED connections should be garbage collected immediately. - but sending thread might watches the connection right now. - so the connection might live at here */ + if (con->parser_state != __PARSER_S_INIT && + con->con_state != __CON_S_DISCONNECTED) { + __rtsp_write(con, "RTSP/1.0 " __RESPONCE_STR_METHODNOTALLOWED "\r\n" + "CSeq: %d\r\n" + "\r\n", con->cseq); + break; + } if (con->con_state != __CON_S_DISCONNECTED) { ERR("unexpected empty request, forcing disconnect\n"); con->con_state = __CON_S_DISCONNECTED; @@ -534,6 +577,7 @@ static int __connection_reset(void *v) p->given_session_id = 0; p->cseq = 0; + p->stream_slot = 0; ctx = p->trans[0].rtp_timestamp; @@ -866,10 +910,12 @@ void rtsp_finish(rtsp_handle h) bufpool_delete(h->con_pool); bufpool_delete(h->transfer_pool); - mime_encoded_delete(h->sprop_vps_b64); - mime_encoded_delete(h->sprop_sps_b64); - mime_encoded_delete(h->sprop_sps_b16); - mime_encoded_delete(h->sprop_pps_b64); + for (int s = 0; s < 2; s++) { + mime_encoded_delete(h->sprop_vps_b64[s]); + mime_encoded_delete(h->sprop_sps_b64[s]); + mime_encoded_delete(h->sprop_sps_b16[s]); + mime_encoded_delete(h->sprop_pps_b64[s]); + } threadpool_delete(h->pool); } diff --git a/src/rtsp/rtsp.h b/src/rtsp/rtsp.h index dbd2e120..3c539e31 100644 --- a/src/rtsp/rtsp.h +++ b/src/rtsp/rtsp.h @@ -59,6 +59,7 @@ enum __method_e { __METHOD_PAUSE, __METHOD_RECORDING, __METHOD_AUTH, + __METHOD_PARAMETER, __METHOD_NONE, __METHOD_COUNT }; @@ -102,6 +103,7 @@ struct connection_item_t { int client_fd; int track_id; int cseq; + unsigned char stream_slot; transport_t trans[2]; @@ -135,12 +137,12 @@ struct __rtsp_obj_t { bufpool_handle transfer_pool; unsigned short port; struct __time_stat_t stat; - char isH265; + char isH265[2]; unsigned char audioPt; - mime_encoded_handle sprop_vps_b64; - mime_encoded_handle sprop_sps_b64; - mime_encoded_handle sprop_pps_b64; - mime_encoded_handle sprop_sps_b16; + mime_encoded_handle sprop_vps_b64[2]; + mime_encoded_handle sprop_sps_b64[2]; + mime_encoded_handle sprop_pps_b64[2]; + mime_encoded_handle sprop_sps_b16[2]; unsigned ctx; /* for rand_r */ int con_num; unsigned char max_con; @@ -201,8 +203,7 @@ static inline int __read_line(struct connection_item_t *p, char *buf) DBG(">%s", buf); - /* check end of request */ - return !(SCMP(__TERM, buf)); + return !(buf[0] == '\0' || buf[0] == '\r' || buf[0] == '\n'); } static inline unsigned long long __get_random_byte(unsigned *ctx) diff --git a/src/rtsp/rtsp_server.h b/src/rtsp/rtsp_server.h index 8e179601..c6f9c74c 100644 --- a/src/rtsp/rtsp_server.h +++ b/src/rtsp/rtsp_server.h @@ -30,7 +30,7 @@ typedef struct __rtsp_obj_t *rtsp_handle; #include "../hal/types.h" void rtp_disable_audio(rtsp_handle h); -int rtp_send_h26x(rtsp_handle h, hal_vidstream *stream, char isH265); +int rtp_send_h26x(rtsp_handle h, hal_vidstream *stream, char isH265, int slot); int rtp_send_mp3(rtsp_handle h, unsigned char *buf, size_t len); int rtp_send_pcma(rtsp_handle h, unsigned char *buf, size_t len); int rtp_send_pcmu(rtsp_handle h, unsigned char *buf, size_t len); diff --git a/src/server.c b/src/server.c index 56c06497..dc3b6a23 100644 --- a/src/server.c +++ b/src/server.c @@ -618,7 +618,10 @@ void respond_request(http_request_t *req) { return; } + if (req->input) + req->input[req->total] = '\0'; char *action = onvif_extract_soap_action(req->payload); + if (!action) action = ""; HAL_INFO("onvif", "\x1b[32mAction: %s\x1b[0m\n", action); respLen = sizeof(response); @@ -647,27 +650,88 @@ void respond_request(http_request_t *req) { onvif_respond_systemtime((char*)response, &respLen); send_and_close(req->clntFd, response, respLen); return; + } else if (EQUALS(action, "SetSystemDateAndTime")) { + onvif_respond_settime((char*)response, &respLen, req->payload); + send_and_close(req->clntFd, response, respLen); + return; + } else if (EQUALS(action, "GetServices")) { + onvif_respond_services((char*)response, &respLen); + send_and_close(req->clntFd, response, respLen); + return; + } else if (EQUALS(action, "GetScopes")) { + onvif_respond_scopes((char*)response, &respLen); + send_and_close(req->clntFd, response, respLen); + return; + } else if (EQUALS(action, "GetNetworkInterfaces")) { + onvif_respond_network((char*)response, &respLen); + send_and_close(req->clntFd, response, respLen); + return; + } else if (EQUALS(action, "GetNetworkDefaultGateway")) { + onvif_respond_gateway((char*)response, &respLen); + send_and_close(req->clntFd, response, respLen); + return; + } else if (EQUALS(action, "GetHostname")) { + onvif_respond_hostname((char*)response, &respLen); + send_and_close(req->clntFd, response, respLen); + return; + } else if (EQUALS(action, "GetDNS")) { + onvif_respond_dns((char*)response, &respLen); + send_and_close(req->clntFd, response, respLen); + return; + } else if (EQUALS(action, "GetNetworkProtocols")) { + onvif_respond_protocols((char*)response, &respLen); + send_and_close(req->clntFd, response, respLen); + return; + } else if (EQUALS(action, "GetServiceCapabilities")) { + onvif_respond_device_caps((char*)response, &respLen); + send_and_close(req->clntFd, response, respLen); + return; } } else if (EQUALS(path, "media_service")) { if (EQUALS(action, "GetProfiles")) { onvif_respond_mediaprofiles((char*)response, &respLen); send_and_close(req->clntFd, response, respLen); return; + } else if (EQUALS(action, "GetProfile")) { + onvif_respond_profile((char*)response, &respLen, req->payload); + send_and_close(req->clntFd, response, respLen); + return; } else if (EQUALS(action, "GetSnapshotUri")) { onvif_respond_snapshot((char*)response, &respLen); send_and_close(req->clntFd, response, respLen); return; } else if (EQUALS(action, "GetStreamUri")) { - onvif_respond_stream((char*)response, &respLen); + onvif_respond_stream((char*)response, &respLen, req->payload); send_and_close(req->clntFd, response, respLen); return; } else if (EQUALS(action, "GetVideoSources")) { onvif_respond_videosources((char*)response, &respLen); send_and_close(req->clntFd, response, respLen); return; + } else if (EQUALS(action, "GetVideoSourceConfigurations")) { + onvif_respond_source_configs((char*)response, &respLen); + send_and_close(req->clntFd, response, respLen); + return; + } else if (EQUALS(action, "GetVideoEncoderConfigurations")) { + onvif_respond_encoders((char*)response, &respLen); + send_and_close(req->clntFd, response, respLen); + return; + } else if (EQUALS(action, "GetVideoEncoderConfigurationOptions")) { + onvif_respond_encoder_options((char*)response, &respLen); + send_and_close(req->clntFd, response, respLen); + return; + } else if (EQUALS(action, "GetServiceCapabilities")) { + onvif_respond_media_caps((char*)response, &respLen); + send_and_close(req->clntFd, response, respLen); + return; } } + if (onvif_dispatch_extra(path, action, (char*)response, &respLen, req->payload)) { + send_and_close(req->clntFd, response, respLen); + return; + } + if (!EMPTY(action)) HAL_WARNING("server", "Unknown ONVIF request: %s->%s\n", path, action); send_http_error(req->clntFd, 501); @@ -1065,13 +1129,13 @@ void respond_request(http_request_t *req) { if (remain != value) app_config.mjpeg_fps = result; } else if (EQUALS(key, "bitrate")) { - short result = strtol(value, &remain, 10); - if (remain != value) - app_config.mjpeg_bitrate = result; + long result = strtol(value, &remain, 10); + if (remain != value && result > 0) + app_config.mjpeg_bitrate = (unsigned int)result; } else if (EQUALS(key, "qfactor")) { - short result = strtol(value, &remain, 10); - if (remain != value) - app_config.mjpeg_qfactor = result; + long result = strtol(value, &remain, 10); + if (remain != value && result > 0 && result <= 99) + app_config.mjpeg_qfactor = (unsigned int)result; } else if (EQUALS(key, "mode")) { if (EQUALS_CASE(value, "CBR")) app_config.mjpeg_mode = HAL_VIDMODE_CBR; @@ -1133,9 +1197,9 @@ void respond_request(http_request_t *req) { if (remain != value) app_config.mp4_fps = result; } else if (EQUALS(key, "bitrate")) { - short result = strtol(value, &remain, 10); - if (remain != value) - app_config.mp4_bitrate = result; + long result = strtol(value, &remain, 10); + if (remain != value && result > 0) + app_config.mp4_bitrate = (unsigned int)result; } else if (EQUALS(key, "h265")) { if (EQUALS_CASE(value, "true") || EQUALS(value, "1")) app_config.mp4_codecH265 = 1; @@ -1163,7 +1227,10 @@ void respond_request(http_request_t *req) { } media_mp4_disable(); - if (app_config.mp4_enable) media_mp4_enable(); + if (app_config.mp4_enable) { + media_mp4_enable(); + media_sub_enable(); + } } char h265[6] = "false";