From b2dfba5577f22966a030f41ed02ee3d87b73d7e9 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sat, 3 Sep 2022 12:14:01 +0200 Subject: [PATCH] cmd: add `/status.json` to describe what and how it works --- cmd/camera-streamer/http.c | 3 + cmd/camera-streamer/main.c | 5 +- cmd/camera-streamer/opts.c | 4 + cmd/camera-streamer/status.cc | 139 ++++++++++++++++++++++++++++++++++ output/rtsp/rtsp.cc | 1 + output/rtsp/rtsp.h | 1 + output/webrtc/webrtc.cc | 12 ++- output/webrtc/webrtc.h | 15 ++-- util/http/http.c | 10 ++- util/http/http.h | 1 + util/opts/log.c | 15 ++++ util/opts/log.h | 1 + 12 files changed, 195 insertions(+), 12 deletions(-) create mode 100644 cmd/camera-streamer/status.cc diff --git a/cmd/camera-streamer/http.c b/cmd/camera-streamer/http.c index 7f06ad4..f7fe9c7 100644 --- a/cmd/camera-streamer/http.c +++ b/cmd/camera-streamer/http.c @@ -13,6 +13,8 @@ extern unsigned char html_webrtc_html[]; extern unsigned int html_webrtc_html_len; extern camera_t *camera; +void camera_status_json(http_worker_t *worker, FILE *stream); + static void http_once(FILE *stream, void (*fn)(FILE *stream, const char *data), void *headersp) { bool *headers = headersp; @@ -101,6 +103,7 @@ http_method_t http_methods[] = { { "GET", "/webrtc", http_content, "text/html", html_webrtc_html, 0, &html_webrtc_html_len }, { "POST", "/webrtc", http_webrtc_offer }, { "GET", "/option", camera_http_option }, + { "GET", "/status.json", camera_status_json }, { "GET", "/", http_content, "text/html", html_index_html, 0, &html_index_html_len }, { "OPTIONS", "*/", http_cors_options }, { } diff --git a/cmd/camera-streamer/main.c b/cmd/camera-streamer/main.c index 05da0af..c1391ef 100644 --- a/cmd/camera-streamer/main.c +++ b/cmd/camera-streamer/main.c @@ -13,6 +13,7 @@ extern camera_options_t camera_options; extern http_server_options_t http_options; extern http_method_t http_methods[]; extern rtsp_options_t rtsp_options; +extern webrtc_options_t webrtc_options; camera_t *camera; @@ -79,7 +80,9 @@ int main(int argc, char *argv[]) goto error; } - webrtc_server(); + if (!webrtc_options.disabled && webrtc_server(&webrtc_options) < 0) { + goto error; + } while (true) { camera = camera_open(&camera_options); diff --git a/cmd/camera-streamer/opts.c b/cmd/camera-streamer/opts.c index 6d5984b..d03a964 100644 --- a/cmd/camera-streamer/opts.c +++ b/cmd/camera-streamer/opts.c @@ -4,6 +4,7 @@ #include "util/opts/fourcc.h" #include "device/camera/camera.h" #include "output/rtsp/rtsp.h" +#include "output/webrtc/webrtc.h" #include "output/output.h" camera_options_t camera_options = { @@ -53,6 +54,9 @@ rtsp_options_t rtsp_options = { .port = 0, }; +webrtc_options_t webrtc_options = { +}; + option_value_t camera_formats[] = { { "DEFAULT", 0 }, { "YUYV", V4L2_PIX_FMT_YUYV }, diff --git a/cmd/camera-streamer/status.cc b/cmd/camera-streamer/status.cc new file mode 100644 index 0000000..d9e675b --- /dev/null +++ b/cmd/camera-streamer/status.cc @@ -0,0 +1,139 @@ +extern "C" { + +#include "util/http/http.h" +#include "util/opts/fourcc.h" +#include "device/buffer_list.h" +#include "device/buffer_lock.h" +#include "device/camera/camera.h" +#include "output/rtsp/rtsp.h" +#include "output/webrtc/webrtc.h" +#include "output/output.h" + +extern camera_t *camera; +extern http_server_options_t http_options; +extern rtsp_options_t rtsp_options; +extern webrtc_options_t webrtc_options; + +}; + +#include + +static nlohmann::json serialize_buf_list(buffer_list_t *buf_list) +{ + if (!buf_list) + return false; + + nlohmann::json output; + output["name"] = buf_list->name; + output["width"] = buf_list->fmt.width; + output["height"] = buf_list->fmt.height; + output["format"] = fourcc_to_string(buf_list->fmt.format).buf; + output["nbufs"] = buf_list->nbufs; + + return output; +} + +static nlohmann::json serialize_buf_lock(buffer_lock_t *buf_lock) +{ + if (!buf_lock) + return false; + + nlohmann::json output; + output["name"] = buf_lock->name; + output["enabled"] = (buf_lock->buf_list != NULL); + + if (buf_lock->buf_list) { + output["width"] = buf_lock->buf_list->fmt.width; + output["height"] = buf_lock->buf_list->fmt.height; + output["source"] = buf_lock->buf_list->name; + output["frames"] = buf_lock->counter; + output["refs"] = buf_lock->refs; + output["dropped"] = buf_lock->dropped; + } + return output; +} + +static nlohmann::json devices_status_json() +{ + nlohmann::json devices; + + for (int i = 0; i < MAX_DEVICES; i++) { + if (!camera->devices[i]) + continue; + + device_t *device = camera->devices[i]; + nlohmann::json device_json; + device_json["name"] = device->name; + device_json["path"] = device->path; + device_json["allow_dma"] = device->opts.allow_dma; + device_json["output"] = serialize_buf_list(device->output_list); + for (int j = 0; j < device->n_capture_list; j++) { + device_json["captures"][j] = serialize_buf_list(device->capture_lists[j]); + } + devices += device_json; + } + + return devices; +} + +static nlohmann::json links_status_json() +{ + nlohmann::json links; + + for (int i = 0; i < camera->nlinks; i++) { + link_t *link = &camera->links[i]; + + nlohmann::json link_json; + link_json["source"] = link->source->name; + for (int j = 0; link->sinks[j]; j++) { + link_json["sinks"][j] = link->sinks[j]->name; + } + for (int j = 0; j < link->n_callbacks; j++) { + link_json["callbacks"][j] = link->callbacks[j].name; + } + links += link_json; + } + + return links; +} + +static std::string strip_host_port(const char *host) +{ + const char *sep = strchr(host, ':'); + + return sep ? std::string(host, sep) : host; +} + +static nlohmann::json get_url(bool running, const char *output, const char *protocol, const char *host, int port, const char *path) +{ + nlohmann::json endpoint; + + endpoint["enabled"] = running; + + if (running) { + endpoint["output"] = output; + endpoint["uri"] = std::string(protocol) + "://" + strip_host_port(host) + ":" + std::to_string(port) + path; + } + + return endpoint; +} + +extern "C" void camera_status_json(http_worker_t *worker, FILE *stream) +{ + nlohmann::json message; + + message["outputs"]["snapshot"] = serialize_buf_lock(&snapshot_lock); + message["outputs"]["stream"] = serialize_buf_lock(&stream_lock); + message["outputs"]["video"] = serialize_buf_lock(&video_lock); + + message["devices"] = devices_status_json(); + message["links"] = links_status_json(); + + message["endpoints"]["rtsp"] = get_url(video_lock.buf_list != NULL && rtsp_options.running, "video", "rtsp", worker->host, rtsp_options.port, "/stream.h264"); + message["endpoints"]["webrtc"] = get_url(video_lock.buf_list != NULL && webrtc_options.running, "video", "http", worker->host, http_options.port, "/webrtc"); + message["endpoints"]["video"] = get_url(video_lock.buf_list != NULL, "video", "http", worker->host, http_options.port, "/video"); + message["endpoints"]["stream"] = get_url(stream_lock.buf_list != NULL, "stream", "http", worker->host, http_options.port, "/stream"); + message["endpoints"]["snapshot"] = get_url(snapshot_lock.buf_list != NULL, "snapshot", "http", worker->host, http_options.port, "/stream"); + + http_write_response(stream, "200 OK", "application/json", message.dump().c_str(), 0); +} diff --git a/output/rtsp/rtsp.cc b/output/rtsp/rtsp.cc index 9207b24..ee13c2c 100644 --- a/output/rtsp/rtsp.cc +++ b/output/rtsp/rtsp.cc @@ -244,6 +244,7 @@ extern "C" int rtsp_server(rtsp_options_t *options) buffer_lock_register_notify_buffer(&video_lock, rtsp_h264_capture); pthread_create(&rtsp_thread, NULL, rtsp_server_thread, env); + options->running = true; return 0; error: diff --git a/output/rtsp/rtsp.h b/output/rtsp/rtsp.h index 9d7bf57..478af80 100644 --- a/output/rtsp/rtsp.h +++ b/output/rtsp/rtsp.h @@ -1,6 +1,7 @@ #pragma once typedef struct rtsp_options_s { + bool running; uint port; } rtsp_options_t; diff --git a/output/webrtc/webrtc.cc b/output/webrtc/webrtc.cc index 5bf22db..1e06c20 100644 --- a/output/webrtc/webrtc.cc +++ b/output/webrtc/webrtc.cc @@ -5,6 +5,11 @@ extern "C" { #include "device/buffer_lock.h" #include "device/device.h" #include "output/output.h" +#include "util/http/http.h" +#include "util/opts/log.h" +#include "util/opts/fourcc.h" +#include "util/opts/control.h" +#include "device/buffer.h" }; #ifdef USE_LIBDATACHANNEL @@ -381,10 +386,12 @@ extern "C" void http_webrtc_offer(http_worker_t *worker, FILE *stream) } } -extern "C" void webrtc_server() +extern "C" int webrtc_server(webrtc_options_t *options) { buffer_lock_register_check_streaming(&video_lock, webrtc_h264_needs_buffer); buffer_lock_register_notify_buffer(&video_lock, webrtc_h264_capture); + options->running = true; + return 0; } #else // USE_LIBDATACHANNEL @@ -394,8 +401,9 @@ extern "C" void http_webrtc_offer(http_worker_t *worker, FILE *stream) http_404(stream, NULL); } -extern "C" void webrtc_server() +extern "C" int webrtc_server(webrtc_options_t *options) { + return 0; } #endif // USE_LIBDATACHANNEL diff --git a/output/webrtc/webrtc.h b/output/webrtc/webrtc.h index dd5ca86..cb5f3e8 100644 --- a/output/webrtc/webrtc.h +++ b/output/webrtc/webrtc.h @@ -1,11 +1,14 @@ #pragma once -#include "util/http/http.h" -#include "util/opts/log.h" -#include "util/opts/fourcc.h" -#include "util/opts/control.h" -#include "device/buffer.h" +#include + +typedef struct http_worker_s http_worker_t; + +typedef struct webrtc_options_s { + bool running; + bool disabled; +} webrtc_options_t; // WebRTC void http_webrtc_offer(http_worker_t *worker, FILE *stream); -void webrtc_server(); +int webrtc_server(webrtc_options_t *options); diff --git a/util/http/http.c b/util/http/http.c index 9a7bddb..1c5ea28 100644 --- a/util/http/http.c +++ b/util/http/http.c @@ -17,6 +17,7 @@ #define HEADER_RANGE "Range:" #define HEADER_CONTENT_LENGTH "Content-Length:" #define HEADER_USER_AGENT "User-Agent:" +#define HEADER_HOST "Host:" static int http_listen(int port, int maxcons) { @@ -92,6 +93,7 @@ static void http_process(http_worker_t *worker, FILE *stream) worker->range_header[0] = 0; worker->user_agent[0] = 0; + worker->host[0] = 0; worker->content_length = -1; // request_uri @@ -124,11 +126,13 @@ static void http_process(http_worker_t *worker, FILE *stream) break; if (strcasestr(line, HEADER_RANGE) == line) { - strcpy(worker->range_header, line); + strcpy(worker->range_header, trim(line)); } else if (strcasestr(line, HEADER_CONTENT_LENGTH) == line) { - worker->content_length = atoi(line + strlen(HEADER_CONTENT_LENGTH)); + worker->content_length = atoi(trim(line + strlen(HEADER_CONTENT_LENGTH))); } else if (strcasestr(line, HEADER_USER_AGENT) == line) { - strcpy(worker->user_agent, line + strlen(HEADER_USER_AGENT)); + strcpy(worker->user_agent, trim(line + strlen(HEADER_USER_AGENT))); + } else if (strcasestr(line, HEADER_HOST) == line) { + strcpy(worker->host, trim(line + strlen(HEADER_HOST))); } } diff --git a/util/http/http.h b/util/http/http.h index dc4a9e4..1dc4d6a 100644 --- a/util/http/http.h +++ b/util/http/http.h @@ -44,6 +44,7 @@ typedef struct http_worker_s { char client_method[BUFSIZE]; char range_header[BUFSIZE]; char user_agent[BUFSIZE]; + char host[BUFSIZE]; char *request_method; char *request_uri; char *request_params; diff --git a/util/opts/log.c b/util/opts/log.c index 47426f8..eec4d47 100644 --- a/util/opts/log.c +++ b/util/opts/log.c @@ -2,6 +2,7 @@ #include "util/opts/opts.h" #include +#include char * strstrn(const char *s, const char *find, size_t len) @@ -101,3 +102,17 @@ int ioctl_retried(const char *name, int fd, int request, void *arg) } return ret; } + +char *trim(char *s) +{ + // skip left side white spaces + while (isspace (*s)) + s++; + + // skip right side white spaces + char *e = s + strlen(s) - 1; + while (e >= s && isspace(*e)) + *e-- = 0; + + return s; +} diff --git a/util/opts/log.h b/util/opts/log.h index 21aac88..370db43 100644 --- a/util/opts/log.h +++ b/util/opts/log.h @@ -49,6 +49,7 @@ bool filter_log(const char *filename); uint64_t get_monotonic_time_us(struct timespec *ts, struct timeval *tv); uint64_t get_time_us(clockid_t clock, struct timespec *ts, struct timeval *tv, int64_t delays_us); int shrink_to_block(int size, int block); +char *trim(char *s); int ioctl_retried(const char *name, int fd, int request, void *arg);