diff options
-rw-r--r-- | include/baresip.h | 5 | ||||
-rw-r--r-- | modules/v4l2_codec/README | 37 | ||||
-rw-r--r-- | modules/v4l2_codec/module.mk | 11 | ||||
-rw-r--r-- | modules/v4l2_codec/v4l2_codec.c | 537 |
4 files changed, 590 insertions, 0 deletions
diff --git a/include/baresip.h b/include/baresip.h index b607166..595070f 100644 --- a/include/baresip.h +++ b/include/baresip.h @@ -992,6 +992,11 @@ int h264_nal_send(bool first, bool last, bool marker, uint32_t ihdr, const uint8_t *buf, size_t size, size_t maxsz, videnc_packet_h *pkth, void *arg); +static inline bool h264_is_keyframe(int type) +{ + return type == H264_NAL_SPS; +} + /* * Modules diff --git a/modules/v4l2_codec/README b/modules/v4l2_codec/README new file mode 100644 index 0000000..64fb993 --- /dev/null +++ b/modules/v4l2_codec/README @@ -0,0 +1,37 @@ +README +------ + +This module is using V4L2 (Video for Linux 2) as a codec module +for devices that supports compressed formats such as H.264 +The module implements both the vidsrc API and the vidcodec API. + + +- encoder/decoder: Encoder only +- codec formats: H.264 +- keyframe refresh: Not supported + + + + +EXAMPLE CONFIG +-------------- + +# Video +video_source v4l2_codec,/dev/video0 +video_size 640x480 + + +# Video codec Modules (in order) +module v4l2_codec.so + + + + +SUPPORTED DEVICES +----------------- + +This webcam supports H.264 hardware acceleration: + +HD Pro Webcam C920 (usb-0000:00:1a.0-1.5): + /dev/video0 + diff --git a/modules/v4l2_codec/module.mk b/modules/v4l2_codec/module.mk new file mode 100644 index 0000000..b0b7f3e --- /dev/null +++ b/modules/v4l2_codec/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 - 2015 Creytiv.com +# + +MOD := v4l2_codec +$(MOD)_SRCS += v4l2_codec.c +$(MOD)_LFLAGS += + +include mk/mod.mk diff --git a/modules/v4l2_codec/v4l2_codec.c b/modules/v4l2_codec/v4l2_codec.c new file mode 100644 index 0000000..a2c37d5 --- /dev/null +++ b/modules/v4l2_codec/v4l2_codec.c @@ -0,0 +1,537 @@ +/** + * @file v4l2_codec.c Video4Linux2 video-source and video-codec + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#if defined (OPENBSD) || defined (NETBSD) +#include <sys/videoio.h> +#else +#include <linux/videodev2.h> +#endif + + +/* TODO: + * + * - timestamp syncronization + * - how to configure the wanted bitrate and framerate + * - how to handle Key-frame requests + */ + + +struct vidsrc_st { + const struct vidsrc *vs; /* inheritance */ + /* empty */ +}; + +struct videnc_state { + struct videnc_param encprm; + uint8_t *buffer; + size_t buffer_len; + int fd; + videnc_packet_h *pkth; + void *arg; + struct { + unsigned n_key; + unsigned n_delta; + } stats; +}; + + +/* TODO: global data, move to per vidsrc instance */ +static struct { + char device[256]; + unsigned width; + unsigned height; +} v4l2 = { + "/dev/video0", + 320, + 240 +}; + + +static struct vidsrc *vidsrc; + + +static int xioctl(int fd, unsigned long int request, void *arg) +{ + int r; + + do r = ioctl (fd, request, arg); + while (-1 == r && EINTR == errno); + + return r; +} + + +static int print_caps(int fd) +{ + struct v4l2_capability caps; + struct v4l2_fmtdesc fmtdesc; + struct v4l2_format fmt; + bool support_h264 = false; + char fourcc[5] = {0}; + char c; + int err; + + memset(&caps, 0, sizeof(caps)); + memset(&fmtdesc, 0, sizeof(fmtdesc)); + memset(&fmt, 0, sizeof(fmt)); + + if (-1 == xioctl(fd, VIDIOC_QUERYCAP, &caps)) { + err = errno; + warning("v4l2_codec: error Querying Capabilities (%m)\n", err); + return err; + } + + info("v4l2_codec: Driver Caps:\n" + " Driver: \"%s\"\n" + " Card: \"%s\"\n" + " Bus: \"%s\"\n" + " Version: %d.%d\n" + " Capabilities: 0x%08x\n", + caps.driver, + caps.card, + caps.bus_info, + (caps.version>>16) & 0xff, + (caps.version>>24) & 0xff, + caps.capabilities); + + + fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + info(" FMT : CE Desc\n--------------------\n"); + + while (0 == xioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc)) { + bool selected = fmtdesc.pixelformat == V4L2_PIX_FMT_H264; + + strncpy(fourcc, (char *)&fmtdesc.pixelformat, 4); + + if (fmtdesc.pixelformat == V4L2_PIX_FMT_H264) + support_h264 = true; + + c = fmtdesc.flags & V4L2_FMT_FLAG_COMPRESSED ? 'C' : ' '; + + info(" %c %s: %c '%s'\n", + selected ? '>' : ' ', + fourcc, c, fmtdesc.description); + + fmtdesc.index++; + } + + info("\n"); + + if (!support_h264) { + warning("v4l2_codec: Doesn't support H264.\n"); + return ENODEV; + } + + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt.fmt.pix.width = v4l2.width; + fmt.fmt.pix.height = v4l2.height; + fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_H264; + fmt.fmt.pix.field = V4L2_FIELD_NONE; + + if (-1 == xioctl(fd, VIDIOC_S_FMT, &fmt)) { + err = errno; + warning("v4l2_codec: Setting Pixel Format (%m)\n", err); + return err; + } + + strncpy(fourcc, (char *)&fmt.fmt.pix.pixelformat, 4); + info("v4l2_codec: Selected Camera Mode:\n" + " Width: %d\n" + " Height: %d\n" + " PixFmt: %s\n" + " Field: %d\n", + fmt.fmt.pix.width, + fmt.fmt.pix.height, + fourcc, + fmt.fmt.pix.field); + + return 0; +} + + +static int init_mmap(struct videnc_state *st, int fd) +{ + struct v4l2_requestbuffers req; + struct v4l2_buffer buf; + int err; + + memset(&req, 0, sizeof(req)); + memset(&buf, 0, sizeof(buf)); + + req.count = 1; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_MMAP; + + if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) { + err = errno; + warning("v4l2_codec: Requesting Buffer (%m)\n", err); + return err; + } + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = 0; + if (-1 == xioctl(fd, VIDIOC_QUERYBUF, &buf)) { + err = errno; + warning("v4l2_codec: Querying Buffer (%m)\n", err); + return err; + } + + st->buffer = mmap(NULL, buf.length, + PROT_READ | PROT_WRITE, MAP_SHARED, + fd, buf.m.offset); + if (st->buffer == MAP_FAILED) { + err = errno; + warning("v4l2_codec: mmap failed (%m)\n", err); + return err; + } + st->buffer_len = buf.length; + + return 0; +} + + +static int query_buffer(int fd) +{ + struct v4l2_buffer buf; + + memset(&buf, 0, sizeof(buf)); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = 0; + + if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) + return errno; + + return 0; +} + + +static int start_streaming(int fd) +{ + struct v4l2_buffer buf; + int err; + + memset(&buf, 0, sizeof(buf)); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = 0; + + if (-1 == xioctl(fd, VIDIOC_STREAMON, &buf.type)) { + err = errno; + warning("v4l2_codec: Start Capture (%m)\n", err); + return err; + } + + return 0; +} + + +static void stop_capturing(int fd) +{ + enum v4l2_buf_type type; + + if (fd < 0) + return; + + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + xioctl(fd, VIDIOC_STREAMOFF, &type); +} + + +static void enc_destructor(void *arg) +{ + struct videnc_state *st = arg; + + if (st->fd >=0 ) { + info("v4l2_codec: encoder stats" + " (keyframes:%u, deltaframes:%u)\n", + st->stats.n_key, st->stats.n_delta); + } + + stop_capturing(st->fd); + + if (st->buffer) + munmap(st->buffer, st->buffer_len); + + if (st->fd >= 0) { + fd_close(st->fd); + close(st->fd); + } +} + + +static void read_handler(int flags, void *arg) +{ + struct videnc_state *st = arg; + struct v4l2_buffer buf; + int err; + (void)flags; + + memset(&buf, 0, sizeof(buf)); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = 0; + + if (-1 == xioctl(st->fd, VIDIOC_DQBUF, &buf)) { + err = errno; + warning("v4l2_codec: Retrieving Frame (%m)\n", err); + return; + } + +#if 0 + debug("image captured at %ld, %ld\n", + buf.timestamp.tv_sec, buf.timestamp.tv_usec); +#endif + + { + struct mbuf mb = {0,0,0,0}; + struct h264_hdr hdr; + + mb.buf = st->buffer; + mb.pos = 4; + mb.end = buf.bytesused - 4; + mb.size = buf.bytesused; + + err = h264_hdr_decode(&hdr, &mb); + if (err) { + warning("could not decode H.264 header\n"); + } + else { + if (h264_is_keyframe(hdr.type)) + ++st->stats.n_key; + else + ++st->stats.n_delta; + } + } + + err = h264_packetize(st->buffer, buf.bytesused, + st->encprm.pktsize, st->pkth, st->arg); + if (err) { + warning("h264_packetize error (%m)\n", err); + } + + query_buffer(st->fd); +} + + +static int encode_update(struct videnc_state **vesp, const struct vidcodec *vc, + struct videnc_param *prm, const char *fmtp, + videnc_packet_h *pkth, void *arg) +{ + struct videnc_state *st; + int err = 0; + (void)fmtp; + + if (!vesp || !vc || !prm || !pkth) + return EINVAL; + + if (*vesp) + return 0; + + st = mem_zalloc(sizeof(*st), enc_destructor); + if (!st) + return ENOMEM; + + st->encprm = *prm; + st->pkth = pkth; + st->arg = arg; + + st->fd = open(v4l2.device, O_RDWR); + if (st->fd == -1) { + err = errno; + warning("Opening video device (%m)\n", err); + goto out; + } + + err = print_caps(st->fd); + if (err) + goto out; + + err = init_mmap(st, st->fd); + if (err) + goto out; + + err = query_buffer(st->fd); + if (err) + goto out; + + err = start_streaming(st->fd); + if (err) + goto out; + + err = fd_listen(st->fd, FD_READ, read_handler, st); + if (err) + goto out; + + info("v4l2_codec: video encoder %s: %d fps, %d bit/s, pktsize=%u\n", + vc->name, prm->fps, prm->bitrate, prm->pktsize); + + out: + if (err) + mem_deref(st); + else + *vesp = st; + + return err; +} + + +/* note: dummy function, the input is unused */ +static int encode_packet(struct videnc_state *st, bool update, + const struct vidframe *frame) +{ + (void)st; + (void)update; + (void)frame; + return 0; +} + + +static uint32_t packetization_mode(const char *fmtp) +{ + struct pl pl, mode; + + if (!fmtp) + return 0; + + pl_set_str(&pl, fmtp); + + if (fmt_param_get(&pl, "packetization-mode", &mode)) + return pl_u32(&mode); + + return 0; +} + + +static int h264_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt, + bool offer, void *arg) +{ + struct vidcodec *vc = arg; + const uint8_t profile_idc = 0x42; /* baseline profile */ + const uint8_t profile_iop = 0x80; + static const uint8_t h264_level_idc = 0x0c; + (void)offer; + + if (!mb || !fmt || !vc) + return 0; + + return mbuf_printf(mb, "a=fmtp:%s" + " packetization-mode=0" + ";profile-level-id=%02x%02x%02x" + "\r\n", + fmt->id, profile_idc, profile_iop, h264_level_idc); +} + + +static bool h264_fmtp_cmp(const char *fmtp1, const char *fmtp2, void *data) +{ + (void)data; + + return packetization_mode(fmtp1) == packetization_mode(fmtp2); +} + + +static void src_destructor(void *arg) +{ + struct vidsrc_st *st = arg; + (void)st; +} + + +static int src_alloc(struct vidsrc_st **stp, const struct vidsrc *vs, + struct media_ctx **ctx, struct vidsrc_prm *prm, + const struct vidsz *size, const char *fmt, + const char *dev, vidsrc_frame_h *frameh, + vidsrc_error_h *errorh, void *arg) +{ + struct vidsrc_st *st; + int err = 0; + + (void)ctx; + (void)prm; + (void)fmt; + (void)errorh; + (void)arg; + + if (!stp || !size || !frameh) + return EINVAL; + + st = mem_zalloc(sizeof(*st), src_destructor); + if (!st) + return ENOMEM; + + st->vs = vs; + + /* NOTE: copy instance data into global space */ + if (str_isset(dev)) + str_ncpy(v4l2.device, dev, sizeof(v4l2.device)); + v4l2.width = size->w; + v4l2.height = size->h; + + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static struct vidcodec h264 = { + LE_INIT, + NULL, + "H264", + "packetization-mode=0", + NULL, + encode_update, + encode_packet, + NULL, + NULL, + h264_fmtp_enc, + h264_fmtp_cmp, +}; + + +static int module_init(void) +{ + info("v4l2_codec inited\n"); + + vidcodec_register(&h264); + return vidsrc_register(&vidsrc, "v4l2_codec", src_alloc, NULL); +} + + +static int module_close(void) +{ + vidsrc = mem_deref(vidsrc); + vidcodec_unregister(&h264); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(v4l2_codec) = { + "v4l2_codec", + "vidcodec", + module_init, + module_close +}; |