diff --git a/device.c b/device.c index a61d7cf..193c37c 100644 --- a/device.c +++ b/device.c @@ -151,3 +151,40 @@ int device_consume_event(device_t *dev) error: return -1; } + +int device_force_key(device_t *dev) +{ + struct v4l2_control ctl = {0}; + ctl.id = V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME; + ctl.value = 1; + E_LOG_DEBUG(dev, "Forcing keyframe ..."); + E_XIOCTL(dev, dev->fd, VIDIOC_S_CTRL, &ctl, "Can't force keyframe"); + return 0; +error: + return -1; +} + +int device_set_fps(device_t *dev, int desired_fps) +{ + struct v4l2_streamparm setfps = {0}; + setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + setfps.parm.output.timeperframe.numerator = 1; + setfps.parm.output.timeperframe.denominator = desired_fps; + E_LOG_DEBUG(dev, "Configuring FPS ..."); + E_XIOCTL(dev, dev->fd, VIDIOC_S_PARM, &setfps, "Can't set FPS"); + return 0; +error: + return -1; +} + +int device_set_option(device_t *dev, const char *name, uint32_t id, int32_t value) +{ + struct v4l2_control ctl = {0}; + ctl.id = id; + ctl.value = value; + E_LOG_DEBUG(dev, "Configuring option %s ...", name); + E_XIOCTL(dev, dev->fd, VIDIOC_S_CTRL, &ctl, "Can't set option %s", name); + return 0; +error: + return -1; +} \ No newline at end of file diff --git a/device.h b/device.h index f13c00a..4ede414 100644 --- a/device.h +++ b/device.h @@ -22,3 +22,12 @@ int device_open_buffer_list(device_t *dev, bool do_capture, unsigned width, unsi int device_consume_event(device_t *device); int device_stream(device_t *dev, bool do_on); +int device_force_key(device_t *dev); +int device_set_option(device_t *dev, const char *name, uint32_t id, int32_t value); +int device_set_fps(device_t *dev, int desired_fps); + +#define DEVICE_SET_OPTION(dev, type, name, value) \ + device_set_option(dev, #name, V4L2_CID_##type##_##name, value) + +#define DEVICE_SET_OPTION_FATAL(dev, type, name, value) \ + do { if (DEVICE_SET_OPTION(dev, type, name, value) < 0) return -1; } while(0) diff --git a/http.c b/http.c index 7bd1e2e..e5979fd 100644 --- a/http.c +++ b/http.c @@ -104,8 +104,6 @@ static void http_client(http_worker_t *worker) static int http_worker(http_worker_t *worker) { - printf("http_worker=%d\n", worker->listen_fd); - while (1) { int addrlen = sizeof(worker->client_addr); worker->client_fd = accept(worker->listen_fd, (struct sockaddr *)&worker->client_addr, &addrlen); diff --git a/http.h b/http.h index 61ce861..e609834 100644 --- a/http.h +++ b/http.h @@ -36,3 +36,7 @@ void http_404(http_worker_t *worker, FILE *stream); void http_snapshot(http_worker_t *worker, FILE *stream); void http_stream(http_worker_t *worker, FILE *stream); void http_jpeg_capture(struct buffer_s *buf); + +// H264 +void http_h264_capture(buffer_t *buf); +void http_video(http_worker_t *worker, FILE *stream); diff --git a/http_h264.c b/http_h264.c new file mode 100644 index 0000000..30360e0 --- /dev/null +++ b/http_h264.c @@ -0,0 +1,49 @@ +#include +#include + +#include "http.h" +#include "buffer.h" +#include "buffer_lock.h" +#include "buffer_list.h" +#include "device.h" + +DEFINE_BUFFER_LOCK(http_h264); + +static const char *const VIDEO_HEADER = "HTTP/1.0 200 OK\r\n" + "Access-Control-Allow-Origin: *\r\n" + "Connection: close\r\n" + "Content-Type: video/webm;codecs=h264\r\n" + "\r\n"; + +void http_h264_capture(buffer_t *buf) +{ + buffer_lock_capture(&http_h264, buf); +} + +void http_video(http_worker_t *worker, FILE *stream) +{ + bool had_key_frame = false; + bool requested_key_frame = false; + int counter = 0; + fprintf(stream, VIDEO_HEADER); + + while (!feof(stream)) { + buffer_t *buf = buffer_lock_get(&http_h264, 3, &counter); + if (!buf) { + return; + } + + if (buf->v4l2_buffer.flags & V4L2_BUF_FLAG_KEYFRAME) { + had_key_frame = true; + E_LOG_DEBUG(buf, "Got key frame!"); + } + + if (had_key_frame) { + fwrite(buf->start, buf->used, 1, stream); + } else if (!requested_key_frame) { + device_force_key(buf->buf_list->device); + requested_key_frame = true; + } + buffer_consumed(buf); + } +} diff --git a/main.c b/main.c index cd04e5f..8fdfb52 100644 --- a/main.c +++ b/main.c @@ -5,6 +5,8 @@ #include "v4l2.h" #include "http.h" +#include + int camera_width = 1920; int camera_height = 1080; int camera_format = V4L2_PIX_FMT_SRGGB10P; @@ -36,6 +38,7 @@ int open_jpeg(buffer_list_t *src, const char *tmp) return -1; } + DEVICE_SET_OPTION(codec_jpeg, JPEG, COMPRESSION_QUALITY, 80); return 0; } @@ -46,6 +49,12 @@ int open_h264(buffer_list_t *src, const char *tmp) return -1; } + DEVICE_SET_OPTION(codec_h264, MPEG_VIDEO, BITRATE, 5000 * 1000); + DEVICE_SET_OPTION(codec_h264, MPEG_VIDEO, H264_I_PERIOD, 30); + DEVICE_SET_OPTION(codec_h264, MPEG_VIDEO, H264_LEVEL, V4L2_MPEG_VIDEO_H264_LEVEL_4_0); + DEVICE_SET_OPTION(codec_h264, MPEG_VIDEO, REPEAT_SEQ_HEADER, 1); + DEVICE_SET_OPTION(codec_h264, MPEG_VIDEO, H264_MIN_QP, 16); + DEVICE_SET_OPTION(codec_h264, MPEG_VIDEO, H264_MIN_QP, 32); return 0; } @@ -126,7 +135,7 @@ int main(int argc, char *argv[]) { codec_h264, { }, - { NULL, check_streaming } + { http_h264_capture, check_streaming } }, { NULL } }; @@ -137,9 +146,12 @@ int main(int argc, char *argv[]) { "GET /stream ", http_stream }, { "GET /?action=snapshot ", http_snapshot }, { "GET /?action=stream ", http_stream }, + { "GET /video ", http_video }, { NULL, NULL } }; + sigaction(SIGPIPE, &(struct sigaction){SIG_IGN}, NULL); + int http_fd = http_server(9092, 5, http_methods); bool running = false;