From 3bb22d2bb13a1700617b6c40eac08811a1b5b775 Mon Sep 17 00:00:00 2001 From: "Alfred E. Heggestad" Date: Sat, 4 Jun 2016 23:22:55 +0200 Subject: vp9: add VP9 video codec --- modules/vp9/decode.c | 266 ++++++++++++++++++++++++++++++++++++++++++ modules/vp9/encode.c | 315 ++++++++++++++++++++++++++++++++++++++++++++++++++ modules/vp9/module.mk | 14 +++ modules/vp9/sdp.c | 39 +++++++ modules/vp9/vp9.c | 61 ++++++++++ modules/vp9/vp9.h | 30 +++++ 6 files changed, 725 insertions(+) create mode 100644 modules/vp9/decode.c create mode 100644 modules/vp9/encode.c create mode 100644 modules/vp9/module.mk create mode 100644 modules/vp9/sdp.c create mode 100644 modules/vp9/vp9.c create mode 100644 modules/vp9/vp9.h (limited to 'modules') diff --git a/modules/vp9/decode.c b/modules/vp9/decode.c new file mode 100644 index 0000000..38b2bbc --- /dev/null +++ b/modules/vp9/decode.c @@ -0,0 +1,266 @@ +/** + * @file vp9/decode.c VP9 Decode + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ + +#include +#include +#include +#include +#include +#include +#include "vp9.h" + + +enum { + DECODE_MAXSZ = 524288, +}; + + +struct hdr { + /* header: */ + unsigned i:1; /* I: Picture ID (PID) present */ + unsigned p:1; /* P: Inter-picture predicted layer frame */ + unsigned l:1; /* L: Layer indices present */ + unsigned f:1; /* F: Flexible mode */ + unsigned b:1; /* B: Start of a layer frame */ + unsigned e:1; /* E: End of a layer frame */ + unsigned v:1; /* V: Scalability structure (SS) data present */ + + /* extension fields */ + uint16_t picid; +}; + +struct viddec_state { + vpx_codec_ctx_t ctx; + struct mbuf *mb; + bool ctxup; + bool started; + uint16_t seq; + + unsigned n_frames; + size_t n_bytes; +}; + + +static void destructor(void *arg) +{ + struct viddec_state *vds = arg; + + re_printf("vp9: decoder: total frames %u, total bytes %zu\n", + vds->n_frames, vds->n_bytes); + + if (vds->ctxup) + vpx_codec_destroy(&vds->ctx); + + mem_deref(vds->mb); +} + + +int vp9_decode_update(struct viddec_state **vdsp, const struct vidcodec *vc, + const char *fmtp) +{ + struct viddec_state *vds; + vpx_codec_err_t res; + int err = 0; + (void)vc; + (void)fmtp; + + if (!vdsp) + return EINVAL; + + vds = *vdsp; + + if (vds) + return 0; + + vds = mem_zalloc(sizeof(*vds), destructor); + if (!vds) + return ENOMEM; + + vds->mb = mbuf_alloc(1024); + if (!vds->mb) { + err = ENOMEM; + goto out; + } + + res = vpx_codec_dec_init(&vds->ctx, &vpx_codec_vp9_dx_algo, NULL, 0); + if (res) { + err = ENOMEM; + goto out; + } + + vds->ctxup = true; + + out: + if (err) + mem_deref(vds); + else + *vdsp = vds; + + return err; +} + + +static inline int hdr_decode(struct hdr *hdr, struct mbuf *mb) +{ + uint8_t v; + + memset(hdr, 0, sizeof(*hdr)); + + if (mbuf_get_left(mb) < 1) + return EBADMSG; + + v = mbuf_read_u8(mb); + + hdr->i = v>>7 & 0x1; + hdr->p = v>>6 & 0x1; + hdr->l = v>>5 & 0x1; + hdr->f = v>>4 & 0x1; + hdr->b = v>>3 & 0x1; + hdr->e = v>>2 & 0x1; + hdr->v = v>>1 & 0x1; + + if (hdr->p) { + warning("vp9: decode: P-bit not supported\n"); + return EPROTO; + } + if (hdr->l) { + warning("vp9: decode: L-bit not supported\n"); + return EPROTO; + } + if (hdr->f) { + warning("vp9: decode: F-bit not supported\n"); + return EPROTO; + } + if (hdr->v) { + warning("vp9: decode: V-bit not supported\n"); + return EPROTO; + } + + if (hdr->i) { + + if (mbuf_get_left(mb) < 1) + return EBADMSG; + + v = mbuf_read_u8(mb); + + if (v>>7 & 0x1) { + + if (mbuf_get_left(mb) < 1) + return EBADMSG; + + hdr->picid = (v & 0x7f)<<8; + hdr->picid += mbuf_read_u8(mb); + } + else { + hdr->picid = v & 0x7f; + } + } + + return 0; +} + + +static inline int16_t seq_diff(uint16_t x, uint16_t y) +{ + return (int16_t)(y - x); +} + + +int vp9_decode(struct viddec_state *vds, struct vidframe *frame, + bool marker, uint16_t seq, struct mbuf *mb) +{ + vpx_codec_iter_t iter = NULL; + vpx_codec_err_t res; + vpx_image_t *img; + struct hdr hdr; + int err, i; + + if (!vds || !frame || !mb) + return EINVAL; + + vds->n_bytes += mbuf_get_left(mb); + + err = hdr_decode(&hdr, mb); + if (err) + return err; + +#if 1 + debug("vp9: [%c] header: i=%u start=%u end=%u " + "picid=%u \n", + marker ? 'M' : ' ', + hdr.i, hdr.b, hdr.e, + hdr.picid); +#endif + + if (hdr.b) { + + mbuf_rewind(vds->mb); + vds->started = true; + } + else { + if (!vds->started) + return 0; + + if (seq_diff(vds->seq, seq) != 1) { + mbuf_rewind(vds->mb); + vds->started = false; + return 0; + } + } + + vds->seq = seq; + + err = mbuf_write_mem(vds->mb, mbuf_buf(mb), mbuf_get_left(mb)); + if (err) + goto out; + + if (!marker) { + + if (vds->mb->end > DECODE_MAXSZ) { + warning("vp9: decode buffer size exceeded\n"); + err = ENOMEM; + goto out; + } + + return 0; + } + + res = vpx_codec_decode(&vds->ctx, vds->mb->buf, + (unsigned int)vds->mb->end, NULL, 1); + if (res) { + debug("vp9: decode error: %s\n", vpx_codec_err_to_string(res)); + err = EPROTO; + goto out; + } + + img = vpx_codec_get_frame(&vds->ctx, &iter); + if (!img) { + debug("vp9: no picture\n"); + goto out; + } + + if (img->fmt != VPX_IMG_FMT_I420) { + warning("vp9: bad pixel format (%i)\n", img->fmt); + goto out; + } + + for (i=0; i<4; i++) { + frame->data[i] = img->planes[i]; + frame->linesize[i] = img->stride[i]; + } + + frame->size.w = img->d_w; + frame->size.h = img->d_h; + frame->fmt = VID_FMT_YUV420P; + + ++vds->n_frames; + + out: + mbuf_rewind(vds->mb); + vds->started = false; + + return err; +} diff --git a/modules/vp9/encode.c b/modules/vp9/encode.c new file mode 100644 index 0000000..7e81b51 --- /dev/null +++ b/modules/vp9/encode.c @@ -0,0 +1,315 @@ +/** + * @file vp9/encode.c VP9 Encode + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ + +#include +#include +#include +#include +#include +#include +#include "vp9.h" + + +enum { + HDR_SIZE = 3, +}; + + +struct videnc_state { + vpx_codec_ctx_t ctx; + struct vidsz size; + vpx_codec_pts_t pts; + unsigned fps; + unsigned bitrate; + unsigned pktsize; + bool ctxup; + uint16_t picid; + videnc_packet_h *pkth; + void *arg; + + unsigned n_frames; + unsigned n_key_frames; + size_t n_bytes; + + uint64_t ts_start; +}; + + +static void destructor(void *arg) +{ + struct videnc_state *ves = arg; + uint64_t now = tmr_jiffies(); + + re_printf("vp9: encoder: total frames %u, total bytes %zu\n", + ves->n_frames, ves->n_bytes); + re_printf(" key frames %u\n", ves->n_key_frames); + + re_printf(" average frame rate: %.1f fps\n", + ves->n_frames / (.001 * (now - ves->ts_start))); + + if (ves->ctxup) + vpx_codec_destroy(&ves->ctx); +} + + +int vp9_encode_update(struct videnc_state **vesp, const struct vidcodec *vc, + struct videnc_param *prm, const char *fmtp, + videnc_packet_h *pkth, void *arg) +{ + const struct vp9_vidcodec *vp9 = (struct vp9_vidcodec *)vc; + struct videnc_state *ves; + uint32_t max_fs; + (void)vp9; + + if (!vesp || !vc || !prm || prm->pktsize < (HDR_SIZE + 1)) + return EINVAL; + + ves = *vesp; + + if (!ves) { + + ves = mem_zalloc(sizeof(*ves), destructor); + if (!ves) + return ENOMEM; + + ves->picid = rand_u16(); + + *vesp = ves; + } + else { + if (ves->ctxup && (ves->bitrate != prm->bitrate || + ves->fps != prm->fps)) { + + vpx_codec_destroy(&ves->ctx); + ves->ctxup = false; + } + } + + ves->bitrate = prm->bitrate; + ves->pktsize = prm->pktsize; + ves->fps = prm->fps; + ves->pkth = pkth; + ves->arg = arg; + + max_fs = vp9_max_fs(fmtp); + if (max_fs > 0) + prm->max_fs = max_fs * 256; + + return 0; +} + + +static int open_encoder(struct videnc_state *ves, const struct vidsz *size) +{ + vpx_codec_enc_cfg_t cfg; + vpx_codec_err_t res; + + res = vpx_codec_enc_config_default(&vpx_codec_vp9_cx_algo, &cfg, 0); + if (res) + return EPROTO; + + /* + Profile 0 = 8 bit yuv420p + Profile 1 = 8 bit yuv422/440/444p + Profile 2 = 10/12 bit yuv420p + Profile 3 = 10/12 bit yuv422/440/444p + */ + + cfg.g_profile = 0; + cfg.g_w = size->w; + cfg.g_h = size->h; + cfg.g_timebase.num = 1; + cfg.g_timebase.den = ves->fps; + cfg.rc_target_bitrate = ves->bitrate / 1000; + cfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT; + cfg.g_pass = VPX_RC_ONE_PASS; + cfg.g_lag_in_frames = 0; + cfg.rc_end_usage = VPX_VBR; + cfg.kf_mode = VPX_KF_AUTO; + cfg.g_bit_depth = 8; + cfg.g_input_bit_depth = 8; + + re_printf("vp9: encoder: rc_target_bitrate %d\n", + cfg.rc_target_bitrate); + + if (ves->ctxup) { + debug("vp9: re-opening encoder\n"); + vpx_codec_destroy(&ves->ctx); + ves->ctxup = false; + } + + res = vpx_codec_enc_init(&ves->ctx, &vpx_codec_vp9_cx_algo, &cfg, + 0); + if (res) { + warning("vp9: enc init: %s\n", vpx_codec_err_to_string(res)); + return EPROTO; + } + + ves->ctxup = true; + + res = vpx_codec_control(&ves->ctx, VP8E_SET_CPUUSED, 8); + if (res) { + warning("vp9: codec ctrl: %s\n", vpx_codec_err_to_string(res)); + } + res = vpx_codec_control(&ves->ctx, VP9E_SET_NOISE_SENSITIVITY, 0); + if (res) { + warning("vp9: codec ctrl: %s\n", vpx_codec_err_to_string(res)); + } + + info("vp9: encoder opened, picture size %u x %u\n", size->w, size->h); + + return 0; +} + + +static inline void hdr_encode(uint8_t hdr[HDR_SIZE], bool start, bool end, + uint16_t picid) +{ + hdr[0] = 1<<7 | start<<3 | end<<2; + hdr[1] = 1<<7 | (picid>>8 & 0x7f); + hdr[2] = picid & 0xff; +} + + +static int send_packet(struct videnc_state *ves, bool marker, + const uint8_t *hdr, size_t hdr_len, + const uint8_t *pld, size_t pld_len) +{ + ves->n_bytes += (hdr_len + pld_len); + + return ves->pkth(marker, hdr, hdr_len, pld, pld_len, ves->arg); +} + + +static inline int packetize(struct videnc_state *ves, + bool marker, const uint8_t *buf, size_t len, + size_t maxlen, uint16_t picid) +{ + uint8_t hdr[HDR_SIZE]; + bool start = true; + int err = 0; + + maxlen -= sizeof(hdr); + + while (len > maxlen) { + + hdr_encode(hdr, start, false, picid); + + err |= send_packet(ves, false, hdr, sizeof(hdr), buf, maxlen); + + buf += maxlen; + len -= maxlen; + start = false; + } + + hdr_encode(hdr, start, true, picid); + + err |= send_packet(ves, marker, hdr, sizeof(hdr), buf, len); + + return err; +} + + +int vp9_encode(struct videnc_state *ves, bool update, + const struct vidframe *frame) +{ + vpx_enc_frame_flags_t flags = 0; + vpx_codec_iter_t iter = NULL; + vpx_codec_err_t res; + vpx_image_t *img = NULL; + vpx_img_fmt_t img_fmt; + int err, i; + + if (!ves || !frame) + return EINVAL; + + switch (frame->fmt) { + + case VID_FMT_YUV420P: + img_fmt = VPX_IMG_FMT_I420; + break; + + default: + warning("vp9: pixel format not supported (%s)\n", + vidfmt_name(frame->fmt)); + return EINVAL; + } + + if (!ves->ctxup || !vidsz_cmp(&ves->size, &frame->size)) { + + err = open_encoder(ves, &frame->size); + if (err) + return err; + + ves->size = frame->size; + + ves->ts_start = tmr_jiffies(); + } + + ++ves->n_frames; + + if (update) { + /* debug("vp9: picture update\n"); */ + flags |= VPX_EFLAG_FORCE_KF; + } + + img = vpx_img_wrap(NULL, img_fmt, frame->size.w, frame->size.h, + 16, NULL); + if (!img) { + warning("vp9: encoder: could not allocate image\n"); + err = ENOMEM; + goto out; + } + + for (i=0; i<4; i++) { + img->stride[i] = frame->linesize[i]; + img->planes[i] = frame->data[i]; + } + + res = vpx_codec_encode(&ves->ctx, img, ves->pts++, 1, + flags, VPX_DL_REALTIME); + if (res) { + warning("vp9: enc error: %s\n", vpx_codec_err_to_string(res)); + err = ENOMEM; + goto out; + } + + ++ves->picid; + + for (;;) { + bool marker = true; + const vpx_codec_cx_pkt_t *pkt; + + pkt = vpx_codec_get_cx_data(&ves->ctx, &iter); + if (!pkt) + break; + + if (pkt->kind != VPX_CODEC_CX_FRAME_PKT) { + continue; + } + + if (pkt->data.frame.flags & VPX_FRAME_IS_KEY) { + ++ves->n_key_frames; + } + + if (pkt->data.frame.flags & VPX_FRAME_IS_FRAGMENT) + marker = false; + + err = packetize(ves, + marker, + pkt->data.frame.buf, + pkt->data.frame.sz, + ves->pktsize, ves->picid); + if (err) + return err; + } + + out: + if (img) + vpx_img_free(img); + + return err; +} diff --git a/modules/vp9/module.mk b/modules/vp9/module.mk new file mode 100644 index 0000000..41879a2 --- /dev/null +++ b/modules/vp9/module.mk @@ -0,0 +1,14 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := vp9 +$(MOD)_SRCS += decode.c +$(MOD)_SRCS += encode.c +$(MOD)_SRCS += vp9.c +$(MOD)_SRCS += sdp.c +$(MOD)_LFLAGS += -lvpx + +include mk/mod.mk diff --git a/modules/vp9/sdp.c b/modules/vp9/sdp.c new file mode 100644 index 0000000..25a3d12 --- /dev/null +++ b/modules/vp9/sdp.c @@ -0,0 +1,39 @@ +/** + * @file vp9/sdp.c VP9 SDP Functions + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ + +#include +#include +#include "vp9.h" + + +uint32_t vp9_max_fs(const char *fmtp) +{ + struct pl pl, max_fs; + + if (!fmtp) + return 0; + + pl_set_str(&pl, fmtp); + + if (fmt_param_get(&pl, "max-fs", &max_fs)) + return pl_u32(&max_fs); + + return 0; +} + + +int vp9_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt, + bool offer, void *arg) +{ + const struct vp9_vidcodec *vp9 = arg; + (void)offer; + + if (!mb || !fmt || !vp9 || !vp9->max_fs) + return 0; + + return mbuf_printf(mb, "a=fmtp:%s max-fs=%u\r\n", + fmt->id, vp9->max_fs); +} diff --git a/modules/vp9/vp9.c b/modules/vp9/vp9.c new file mode 100644 index 0000000..852a19a --- /dev/null +++ b/modules/vp9/vp9.c @@ -0,0 +1,61 @@ +/** + * @file vp9.c VP9 video codec + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ +#include +#include +#include +#include +#include "vp9.h" + + +/** + * @defgroup vp9 vp9 + * + * The VP9 video codec + * + * This module implements the VP9 video codec that is compatible + * with the WebRTC standard. + * + * References: + * + * http://www.webmproject.org/ + * + * draft-ietf-payload-vp9-02 + */ + + +static struct vp9_vidcodec vp9 = { + .vc = { + .name = "VP9", + .encupdh = vp9_encode_update, + .ench = vp9_encode, + .decupdh = vp9_decode_update, + .dech = vp9_decode, + .fmtp_ench = vp9_fmtp_enc, + }, + .max_fs = 3600 +}; + + +static int module_init(void) +{ + vidcodec_register((struct vidcodec *)&vp9); + return 0; +} + + +static int module_close(void) +{ + vidcodec_unregister((struct vidcodec *)&vp9); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(vp9) = { + "vp9", + "codec", + module_init, + module_close +}; diff --git a/modules/vp9/vp9.h b/modules/vp9/vp9.h new file mode 100644 index 0000000..c60def2 --- /dev/null +++ b/modules/vp9/vp9.h @@ -0,0 +1,30 @@ +/** + * @file vp9.h Private VP9 Interface + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ + +struct vp9_vidcodec { + struct vidcodec vc; + uint32_t max_fs; +}; + +/* Encode */ +int vp9_encode_update(struct videnc_state **vesp, const struct vidcodec *vc, + struct videnc_param *prm, const char *fmtp, + videnc_packet_h *pkth, void *arg); +int vp9_encode(struct videnc_state *ves, bool update, + const struct vidframe *frame); + + +/* Decode */ +int vp9_decode_update(struct viddec_state **vdsp, const struct vidcodec *vc, + const char *fmtp); +int vp9_decode(struct viddec_state *vds, struct vidframe *frame, + bool marker, uint16_t seq, struct mbuf *mb); + + +/* SDP */ +uint32_t vp9_max_fs(const char *fmtp); +int vp9_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt, + bool offer, void *arg); -- cgit v1.2.3