hikvision.c 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. #define _GNU_SOURCE
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include <unistd.h>
  6. #include <errno.h>
  7. #include <curl/curl.h>
  8. #include "hikvision.h"
  9. #include "flog.h"
  10. static void hurl(char *dst, size_t sz, const HvCam *cam, const char *path) {
  11. int hp = cam->http_port > 0 ? cam->http_port : 80;
  12. snprintf(dst, sz, "http://%s:%d/ISAPI/%s", cam->ip, hp,
  13. path && path[0] ? path : "");
  14. }
  15. /* Build the RTSP URL for a given logical encoding channel (e.g. 101/102). */
  16. static void build_url(char *dst, size_t sz, const HvCam *cam, int enc_id) {
  17. snprintf(dst, sz, "rtsp://%s:%s@%s:%d/Streaming/Channels/%d",
  18. cam->user, cam->pass, cam->ip, cam->port, enc_id);
  19. }
  20. void hv_build_urls(const HvCam *cam, HvUrls *out) {
  21. if (!cam || !out) return;
  22. build_url(out->main, sizeof(out->main), cam, cam->login_chan * 100 + 1);
  23. build_url(out->sub, sizeof(out->sub), cam, cam->login_chan * 100 + 2);
  24. }
  25. /* ---- libcurl HTTP client (digest auth) ---- */
  26. typedef struct {
  27. char *buf;
  28. size_t len, cap;
  29. } CurlStr;
  30. static size_t write_cb(char *ptr, size_t size, size_t nmemb, void *ud) {
  31. CurlStr *s = ud;
  32. size_t total = size * nmemb;
  33. if (s->len + total + 1 > s->cap) {
  34. size_t nc = s->cap ? s->cap : 8192;
  35. while (nc < s->len + total + 1) nc *= 2;
  36. char *nb = realloc(s->buf, nc);
  37. if (!nb) return 0;
  38. s->buf = nb;
  39. s->cap = nc;
  40. }
  41. memcpy(s->buf + s->len, ptr, total);
  42. s->len += total;
  43. s->buf[s->len] = '\0';
  44. return total;
  45. }
  46. /* Issue one ISAPI request. Returns 0 on HTTP 2xx; *outbuf receives a
  47. * NUL-terminated heap buffer the caller frees (unless outbuf is NULL).
  48. * Credentials are passed via CURLOPT_*, never via a shell command line. */
  49. int hv_request(const HvCam *cam, const char *method, const char *urlpath,
  50. const char *body, char **outbuf) {
  51. static int curl_ready = 0;
  52. if (!curl_ready) {
  53. if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) return -3;
  54. curl_ready = 1;
  55. }
  56. if (!cam || !cam->ip[0]) return -1;
  57. char full[512];
  58. hurl(full, sizeof(full), cam, urlpath);
  59. CURL *c = curl_easy_init();
  60. if (!c) return -3;
  61. CurlStr s = {0};
  62. struct curl_slist *hdr = NULL;
  63. curl_easy_setopt(c, CURLOPT_URL, full);
  64. /* cameras typically use digest; allow basic as fallback */
  65. curl_easy_setopt(c, CURLOPT_HTTPAUTH, (long)(CURLAUTH_DIGEST | CURLAUTH_BASIC));
  66. curl_easy_setopt(c, CURLOPT_USERNAME, cam->user);
  67. curl_easy_setopt(c, CURLOPT_PASSWORD, cam->pass);
  68. curl_easy_setopt(c, CURLOPT_TIMEOUT, 8L);
  69. curl_easy_setopt(c, CURLOPT_CONNECTTIMEOUT, 4L);
  70. curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, write_cb);
  71. curl_easy_setopt(c, CURLOPT_WRITEDATA, &s);
  72. curl_easy_setopt(c, CURLOPT_NOSIGNAL, 1L);
  73. curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L);
  74. curl_easy_setopt(c, CURLOPT_CUSTOMREQUEST, method);
  75. if (body && body[0]) {
  76. hdr = curl_slist_append(hdr, "Content-Type: application/xml");
  77. curl_easy_setopt(c, CURLOPT_HTTPHEADER, hdr);
  78. curl_easy_setopt(c, CURLOPT_POSTFIELDS, body);
  79. }
  80. CURLcode rc = curl_easy_perform(c);
  81. long http_code = 0;
  82. curl_easy_getinfo(c, CURLINFO_RESPONSE_CODE, &http_code);
  83. if (hdr) curl_slist_free_all(hdr);
  84. curl_easy_cleanup(c);
  85. if (rc != CURLE_OK) {
  86. free(s.buf);
  87. flog("ISAPI %s %s: %s", method, full, curl_easy_strerror(rc));
  88. return -4;
  89. }
  90. if (http_code < 200 || http_code >= 300) {
  91. free(s.buf);
  92. return -5;
  93. }
  94. if (outbuf) *outbuf = s.buf;
  95. else free(s.buf);
  96. return 0;
  97. }
  98. /* Extract integer value of an XML tag "<TAG>N</TAG>" (no attributes). */
  99. static int xml_int(const char *xml, const char *tag, int dflt) {
  100. char open[48], close[48];
  101. snprintf(open, sizeof(open), "<%s>", tag);
  102. snprintf(close, sizeof(close), "</%s>", tag);
  103. const char *a = strstr(xml, open);
  104. if (!a) return dflt;
  105. a += strlen(open);
  106. const char *b = strstr(a, close);
  107. if (!b) return dflt;
  108. long v = strtol(a, NULL, 10);
  109. return (int)v;
  110. }
  111. /* Replace "<TAG>old</TAG>" with "<TAG>new</TAG>" in-place (grow via realloc).
  112. * Returns new string (caller frees) or NULL if tag not found. */
  113. static char *xml_set_int(char *xml, const char *tag, int val) {
  114. char open[48], close[48];
  115. snprintf(open, sizeof(open), "<%s>", tag);
  116. snprintf(close, sizeof(close), "</%s>", tag);
  117. char *a = strstr(xml, open);
  118. if (!a) return NULL;
  119. char *b = strstr(a + strlen(open), close);
  120. if (!b) return NULL;
  121. char valbuf[24];
  122. snprintf(valbuf, sizeof(valbuf), "%d", val);
  123. size_t head = (size_t)(a - xml);
  124. size_t oldlen = strlen(open) + (size_t)(b - (a + strlen(open))) + strlen(close);
  125. size_t newlen = strlen(open) + strlen(valbuf) + strlen(close);
  126. size_t newcap = strlen(xml) - oldlen + newlen + 1;
  127. char *nx = malloc(newcap);
  128. if (!nx) return NULL;
  129. memcpy(nx, xml, head);
  130. strcpy(nx + head, open);
  131. strcat(nx + head, valbuf);
  132. strcat(nx + head, close);
  133. strcat(nx + head, b + strlen(close));
  134. free(xml);
  135. return nx;
  136. }
  137. int hv_read_and_log(const HvCam *cam, int chan_enc_id) {
  138. char path[128];
  139. snprintf(path, sizeof(path), "Streaming/channels/%d", chan_enc_id);
  140. char *xml = NULL;
  141. if (hv_request(cam, "GET", path, NULL, &xml) != 0 || !xml) {
  142. free(xml);
  143. return -1;
  144. }
  145. int w = xml_int(xml, "videoResolutionWidth", -1);
  146. int h = xml_int(xml, "videoResolutionHeight", -1);
  147. int bitrate = xml_int(xml, "constantBitRate", -1);
  148. int fps = xml_int(xml, "maxFrameRate", -1);
  149. const char *enc = strstr(xml, "<H265Profile>") ? "H265" :
  150. (strstr(xml, "<H264Profile>") ? "H264" : "?");
  151. flog("ISAPI 相机 %s 通道%d: %dx%d %s %dkbps %dfps",
  152. cam->ip, chan_enc_id, w, h, enc, bitrate < 0 ? 0 : bitrate, fps < 0 ? 0 : fps);
  153. free(xml);
  154. return 0;
  155. }
  156. int hv_write_params(const HvCam *cam, int enc_id,
  157. int width, int height, int fps, int bitrate_kbps) {
  158. if (width <= 0 || height <= 0) return -1;
  159. char path[128];
  160. snprintf(path, sizeof(path), "Streaming/channels/%d", enc_id);
  161. char *xml = NULL;
  162. if (hv_request(cam, "GET", path, NULL, &xml) != 0 || !xml) {
  163. free(xml);
  164. return -2;
  165. }
  166. /* Patch just the requested fields inside <Video>. Only fields the caller
  167. * specified (val > 0) are changed; everything else is preserved. */
  168. char *cur = xml;
  169. char *n1 = xml_set_int(cur, "videoResolutionWidth", width);
  170. if (!n1) { free(cur); return -4; }
  171. cur = n1;
  172. n1 = xml_set_int(cur, "videoResolutionHeight", height);
  173. if (!n1) { free(cur); return -4; }
  174. cur = n1;
  175. if (fps > 0) {
  176. n1 = xml_set_int(cur, "maxFrameRate", fps);
  177. if (n1) cur = n1;
  178. }
  179. if (bitrate_kbps > 0) {
  180. n1 = xml_set_int(cur, "constantBitRate", bitrate_kbps);
  181. if (n1) cur = n1;
  182. }
  183. char *resp = NULL;
  184. int rc = hv_request(cam, "PUT", path, cur, &resp);
  185. if (rc != 0) {
  186. flog("ISAPI 相机 %s 通道%d 写入失败 rc=%d%s", cam->ip, enc_id, rc,
  187. resp ? "" : " (无响应)");
  188. free(cur);
  189. free(resp);
  190. return -5;
  191. }
  192. flog("ISAPI 相机 %s 通道%d 已写 %dx%d %dkbps@%dfps", cam->ip, enc_id,
  193. width, height, bitrate_kbps, fps);
  194. free(cur);
  195. free(resp);
  196. return 0;
  197. }