| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- #define _GNU_SOURCE
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include <errno.h>
- #include <curl/curl.h>
- #include "hikvision.h"
- #include "flog.h"
- static void hurl(char *dst, size_t sz, const HvCam *cam, const char *path) {
- int hp = cam->http_port > 0 ? cam->http_port : 80;
- snprintf(dst, sz, "http://%s:%d/ISAPI/%s", cam->ip, hp,
- path && path[0] ? path : "");
- }
- /* Build the RTSP URL for a given logical encoding channel (e.g. 101/102). */
- static void build_url(char *dst, size_t sz, const HvCam *cam, int enc_id) {
- snprintf(dst, sz, "rtsp://%s:%s@%s:%d/Streaming/Channels/%d",
- cam->user, cam->pass, cam->ip, cam->port, enc_id);
- }
- void hv_build_urls(const HvCam *cam, HvUrls *out) {
- if (!cam || !out) return;
- build_url(out->main, sizeof(out->main), cam, cam->login_chan * 100 + 1);
- build_url(out->sub, sizeof(out->sub), cam, cam->login_chan * 100 + 2);
- }
- /* ---- libcurl HTTP client (digest auth) ---- */
- typedef struct {
- char *buf;
- size_t len, cap;
- } CurlStr;
- static size_t write_cb(char *ptr, size_t size, size_t nmemb, void *ud) {
- CurlStr *s = ud;
- size_t total = size * nmemb;
- if (s->len + total + 1 > s->cap) {
- size_t nc = s->cap ? s->cap : 8192;
- while (nc < s->len + total + 1) nc *= 2;
- char *nb = realloc(s->buf, nc);
- if (!nb) return 0;
- s->buf = nb;
- s->cap = nc;
- }
- memcpy(s->buf + s->len, ptr, total);
- s->len += total;
- s->buf[s->len] = '\0';
- return total;
- }
- /* Issue one ISAPI request. Returns 0 on HTTP 2xx; *outbuf receives a
- * NUL-terminated heap buffer the caller frees (unless outbuf is NULL).
- * Credentials are passed via CURLOPT_*, never via a shell command line. */
- int hv_request(const HvCam *cam, const char *method, const char *urlpath,
- const char *body, char **outbuf) {
- static int curl_ready = 0;
- if (!curl_ready) {
- if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) return -3;
- curl_ready = 1;
- }
- if (!cam || !cam->ip[0]) return -1;
- char full[512];
- hurl(full, sizeof(full), cam, urlpath);
- CURL *c = curl_easy_init();
- if (!c) return -3;
- CurlStr s = {0};
- struct curl_slist *hdr = NULL;
- curl_easy_setopt(c, CURLOPT_URL, full);
- /* cameras typically use digest; allow basic as fallback */
- curl_easy_setopt(c, CURLOPT_HTTPAUTH, (long)(CURLAUTH_DIGEST | CURLAUTH_BASIC));
- curl_easy_setopt(c, CURLOPT_USERNAME, cam->user);
- curl_easy_setopt(c, CURLOPT_PASSWORD, cam->pass);
- curl_easy_setopt(c, CURLOPT_TIMEOUT, 8L);
- curl_easy_setopt(c, CURLOPT_CONNECTTIMEOUT, 4L);
- curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, write_cb);
- curl_easy_setopt(c, CURLOPT_WRITEDATA, &s);
- curl_easy_setopt(c, CURLOPT_NOSIGNAL, 1L);
- curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L);
- curl_easy_setopt(c, CURLOPT_CUSTOMREQUEST, method);
- if (body && body[0]) {
- hdr = curl_slist_append(hdr, "Content-Type: application/xml");
- curl_easy_setopt(c, CURLOPT_HTTPHEADER, hdr);
- curl_easy_setopt(c, CURLOPT_POSTFIELDS, body);
- }
- CURLcode rc = curl_easy_perform(c);
- long http_code = 0;
- curl_easy_getinfo(c, CURLINFO_RESPONSE_CODE, &http_code);
- if (hdr) curl_slist_free_all(hdr);
- curl_easy_cleanup(c);
- if (rc != CURLE_OK) {
- free(s.buf);
- flog("ISAPI %s %s: %s", method, full, curl_easy_strerror(rc));
- return -4;
- }
- if (http_code < 200 || http_code >= 300) {
- free(s.buf);
- return -5;
- }
- if (outbuf) *outbuf = s.buf;
- else free(s.buf);
- return 0;
- }
- /* Extract integer value of an XML tag "<TAG>N</TAG>" (no attributes). */
- static int xml_int(const char *xml, const char *tag, int dflt) {
- char open[48], close[48];
- snprintf(open, sizeof(open), "<%s>", tag);
- snprintf(close, sizeof(close), "</%s>", tag);
- const char *a = strstr(xml, open);
- if (!a) return dflt;
- a += strlen(open);
- const char *b = strstr(a, close);
- if (!b) return dflt;
- long v = strtol(a, NULL, 10);
- return (int)v;
- }
- /* Replace "<TAG>old</TAG>" with "<TAG>new</TAG>" in-place (grow via realloc).
- * Returns new string (caller frees) or NULL if tag not found. */
- static char *xml_set_int(char *xml, const char *tag, int val) {
- char open[48], close[48];
- snprintf(open, sizeof(open), "<%s>", tag);
- snprintf(close, sizeof(close), "</%s>", tag);
- char *a = strstr(xml, open);
- if (!a) return NULL;
- char *b = strstr(a + strlen(open), close);
- if (!b) return NULL;
- char valbuf[24];
- snprintf(valbuf, sizeof(valbuf), "%d", val);
- size_t head = (size_t)(a - xml);
- size_t oldlen = strlen(open) + (size_t)(b - (a + strlen(open))) + strlen(close);
- size_t newlen = strlen(open) + strlen(valbuf) + strlen(close);
- size_t newcap = strlen(xml) - oldlen + newlen + 1;
- char *nx = malloc(newcap);
- if (!nx) return NULL;
- memcpy(nx, xml, head);
- strcpy(nx + head, open);
- strcat(nx + head, valbuf);
- strcat(nx + head, close);
- strcat(nx + head, b + strlen(close));
- free(xml);
- return nx;
- }
- int hv_read_and_log(const HvCam *cam, int chan_enc_id) {
- char path[128];
- snprintf(path, sizeof(path), "Streaming/channels/%d", chan_enc_id);
- char *xml = NULL;
- if (hv_request(cam, "GET", path, NULL, &xml) != 0 || !xml) {
- free(xml);
- return -1;
- }
- int w = xml_int(xml, "videoResolutionWidth", -1);
- int h = xml_int(xml, "videoResolutionHeight", -1);
- int bitrate = xml_int(xml, "constantBitRate", -1);
- int fps = xml_int(xml, "maxFrameRate", -1);
- const char *enc = strstr(xml, "<H265Profile>") ? "H265" :
- (strstr(xml, "<H264Profile>") ? "H264" : "?");
- flog("ISAPI 相机 %s 通道%d: %dx%d %s %dkbps %dfps",
- cam->ip, chan_enc_id, w, h, enc, bitrate < 0 ? 0 : bitrate, fps < 0 ? 0 : fps);
- free(xml);
- return 0;
- }
- int hv_write_params(const HvCam *cam, int enc_id,
- int width, int height, int fps, int bitrate_kbps) {
- if (width <= 0 || height <= 0) return -1;
- char path[128];
- snprintf(path, sizeof(path), "Streaming/channels/%d", enc_id);
- char *xml = NULL;
- if (hv_request(cam, "GET", path, NULL, &xml) != 0 || !xml) {
- free(xml);
- return -2;
- }
- /* Patch just the requested fields inside <Video>. Only fields the caller
- * specified (val > 0) are changed; everything else is preserved. */
- char *cur = xml;
- char *n1 = xml_set_int(cur, "videoResolutionWidth", width);
- if (!n1) { free(cur); return -4; }
- cur = n1;
- n1 = xml_set_int(cur, "videoResolutionHeight", height);
- if (!n1) { free(cur); return -4; }
- cur = n1;
- if (fps > 0) {
- n1 = xml_set_int(cur, "maxFrameRate", fps);
- if (n1) cur = n1;
- }
- if (bitrate_kbps > 0) {
- n1 = xml_set_int(cur, "constantBitRate", bitrate_kbps);
- if (n1) cur = n1;
- }
- char *resp = NULL;
- int rc = hv_request(cam, "PUT", path, cur, &resp);
- if (rc != 0) {
- flog("ISAPI 相机 %s 通道%d 写入失败 rc=%d%s", cam->ip, enc_id, rc,
- resp ? "" : " (无响应)");
- free(cur);
- free(resp);
- return -5;
- }
- flog("ISAPI 相机 %s 通道%d 已写 %dx%d %dkbps@%dfps", cam->ip, enc_id,
- width, height, bitrate_kbps, fps);
- free(cur);
- free(resp);
- return 0;
- }
|