summaryrefslogtreecommitdiff
path: root/src/video.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/video.c')
-rw-r--r--src/video.c1073
1 files changed, 1073 insertions, 0 deletions
diff --git a/src/video.c b/src/video.c
new file mode 100644
index 0000000..f7eb032
--- /dev/null
+++ b/src/video.c
@@ -0,0 +1,1073 @@
+/**
+ * @file src/video.c Video stream
+ *
+ * Copyright (C) 2010 Creytiv.com
+ *
+ * \ref GenericVideoStream
+ */
+#include <string.h>
+#include <stdlib.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "core.h"
+
+
+#define DEBUG_MODULE "video"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/** Magic number */
+#define MAGIC 0x00070d10
+#include "magic.h"
+
+
+enum {
+ SRATE = 90000,
+ MAX_MUTED_FRAMES = 3,
+};
+
+
+/**
+ * \page GenericVideoStream Generic Video Stream
+ *
+ * Implements a generic video stream. The application can allocate multiple
+ * instances of a video stream, mapping it to a particular SDP media line.
+ * The video object has a Video Display and Source, and a video encoder
+ * and decoder. A particular video object is mapped to a generic media
+ * stream object.
+ *
+ *<pre>
+ * recv send
+ * | /|\
+ * \|/ |
+ * .---------. .-------.
+ * | video |--->|encoder|
+ * | | |-------|
+ * | object |--->|decoder|
+ * '---------' '-------'
+ * | /|\
+ * | |
+ * \|/ |
+ * .-------. .-------.
+ * |Video | |Video |
+ * |Display| |Source |
+ * '-------' '-------'
+ *</pre>
+ */
+
+/**
+ * Video stream - transmitter/encoder direction
+
+ \verbatim
+
+ Processing encoder pipeline:
+
+ . .--------. .- - - - -. .---------. .---------.
+ | ._O_. | | ! ! | | | |
+ | |___|-->| vidsrc |-->! vidconv !-->| vidfilt |-->| encoder |---> RTP
+ | | | ! ! | | | |
+ ' '--------' '- - - - -' '---------' '---------'
+ (optional)
+ \endverbatim
+ */
+struct vtx {
+ struct video *video; /**< Parent */
+ const struct vidcodec *vc; /**< Current Video encoder */
+ struct videnc_state *enc; /**< Video encoder state */
+ struct vidsrc_prm vsrc_prm; /**< Video source parameters */
+ struct vidsz vsrc_size; /**< Video source size */
+ struct vidsrc_st *vsrc; /**< Video source */
+ struct lock *lock; /**< Lock for encoder */
+ struct vidframe *frame; /**< Source frame */
+ struct vidframe *mute_frame; /**< Frame with muted video */
+ struct mbuf *mb; /**< Packetization buffer */
+ struct list filtl; /**< Filters in encoding order */
+ char device[64];
+ int muted_frames; /**< # of muted frames sent */
+ uint32_t ts_tx; /**< Outgoing RTP timestamp */
+ bool picup; /**< Send picture update */
+ bool muted; /**< Muted flag */
+ int frames; /**< Number of frames sent */
+ int efps; /**< Estimated frame-rate */
+};
+
+
+/**
+ * Video stream - receiver/decoder direction
+
+ \verbatim
+
+ Processing decoder pipeline:
+
+ .~~~~~~~~. .--------. .---------. .---------.
+ | _o_ | | | | | | |
+ | | |<--| vidisp |<--| vidfilt |<--| decoder |<--- RTP
+ | /'\ | | | | | | |
+ '~~~~~~~~' '--------' '---------' '---------'
+
+ \endverbatim
+
+ */
+struct vrx {
+ struct video *video; /**< Parent */
+ const struct vidcodec *vc; /**< Current video decoder */
+ struct viddec_state *dec; /**< Video decoder state */
+ struct vidisp_prm vidisp_prm; /**< Video display parameters */
+ struct vidisp_st *vidisp; /**< Video display */
+ struct lock *lock; /**< Lock for decoder */
+ struct list filtl; /**< Filters in decoding order */
+ enum vidorient orient; /**< Display orientation */
+ char device[64];
+ bool fullscreen; /**< Fullscreen flag */
+ int pt_rx; /**< Incoming RTP payload type */
+ int frames; /**< Number of frames received */
+ int efps; /**< Estimated frame-rate */
+};
+
+
+/** Generic Video stream */
+struct video {
+ MAGIC_DECL /**< Magic number for debugging */
+ struct config_video cfg;/**< Video configuration */
+ struct stream *strm; /**< Generic media stream */
+ struct vtx vtx; /**< Transmit/encoder direction */
+ struct vrx vrx; /**< Receive/decoder direction */
+ struct tmr tmr; /**< Timer for frame-rate estimation */
+ char *peer; /**< Peer URI */
+ bool nack_pli; /**< Send NACK/PLI to peer */
+};
+
+
+static void video_destructor(void *arg)
+{
+ struct video *v = arg;
+ struct vtx *vtx = &v->vtx;
+ struct vrx *vrx = &v->vrx;
+
+ /* transmit */
+ mem_deref(vtx->vsrc);
+ lock_write_get(vtx->lock);
+ mem_deref(vtx->frame);
+ mem_deref(vtx->mute_frame);
+ mem_deref(vtx->enc);
+ mem_deref(vtx->mb);
+ list_flush(&vtx->filtl);
+ lock_rel(vtx->lock);
+ mem_deref(vtx->lock);
+
+ /* receive */
+ lock_write_get(vrx->lock);
+ mem_deref(vrx->dec);
+ mem_deref(vrx->vidisp);
+ list_flush(&vrx->filtl);
+ lock_rel(vrx->lock);
+ mem_deref(vrx->lock);
+
+ tmr_cancel(&v->tmr);
+ mem_deref(v->strm);
+ mem_deref(v->peer);
+}
+
+
+static int get_fps(const struct video *v)
+{
+ const char *attr;
+
+ /* RFC4566 */
+ attr = sdp_media_rattr(stream_sdpmedia(v->strm), "framerate");
+ if (attr) {
+ /* NOTE: fractional values are ignored */
+ const double fps = atof(attr);
+ return (int)fps;
+ }
+ else
+ return v->cfg.fps;
+}
+
+
+static int packet_handler(bool marker, const uint8_t *hdr, size_t hdr_len,
+ const uint8_t *pld, size_t pld_len, void *arg)
+{
+ struct vtx *tx = arg;
+ int err = 0;
+
+ tx->mb->pos = tx->mb->end = STREAM_PRESZ;
+
+ if (hdr_len) err |= mbuf_write_mem(tx->mb, hdr, hdr_len);
+ if (pld_len) err |= mbuf_write_mem(tx->mb, pld, pld_len);
+
+ tx->mb->pos = STREAM_PRESZ;
+
+ if (!err) {
+ err = stream_send(tx->video->strm, marker, -1,
+ tx->ts_tx, tx->mb);
+ }
+
+ return err;
+}
+
+
+/**
+ * Encode video and send via RTP stream
+ *
+ * @note This function has REAL-TIME properties
+ *
+ * @param vtx Video transmit object
+ * @param frame Video frame to send
+ */
+static void encode_rtp_send(struct vtx *vtx, struct vidframe *frame)
+{
+ struct le *le;
+ int err = 0;
+
+ if (!vtx->enc)
+ return;
+
+ lock_write_get(vtx->lock);
+
+ /* Convert image */
+ if (frame->fmt != VID_FMT_YUV420P) {
+
+ vtx->vsrc_size = frame->size;
+
+ if (!vtx->frame) {
+
+ err = vidframe_alloc(&vtx->frame, VID_FMT_YUV420P,
+ &vtx->vsrc_size);
+ if (err)
+ goto unlock;
+ }
+
+ vidconv(vtx->frame, frame, 0);
+ frame = vtx->frame;
+ }
+
+ /* Process video frame through all Video Filters */
+ for (le = vtx->filtl.head; le; le = le->next) {
+
+ struct vidfilt_enc_st *st = le->data;
+
+ if (st->vf && st->vf->ench)
+ err |= st->vf->ench(st, frame);
+ }
+
+ unlock:
+ lock_rel(vtx->lock);
+
+ if (err)
+ return;
+
+ /* Encode the whole picture frame */
+ err = vtx->vc->ench(vtx->enc, vtx->picup, frame, packet_handler, vtx);
+ if (err) {
+ DEBUG_WARNING("encode: %m\n", err);
+ return;
+ }
+
+ vtx->ts_tx += (SRATE/vtx->vsrc_prm.fps);
+ vtx->picup = false;
+}
+
+
+/**
+ * Read frames from video source
+ *
+ * @param frame Video frame
+ * @param arg Handler argument
+ *
+ * @note This function has REAL-TIME properties
+ */
+static void vidsrc_frame_handler(struct vidframe *frame, void *arg)
+{
+ struct vtx *vtx = arg;
+
+ ++vtx->frames;
+
+ /* Is the video muted? If so insert video mute image */
+ if (vtx->muted)
+ frame = vtx->mute_frame;
+
+ if (vtx->muted && vtx->muted_frames >= MAX_MUTED_FRAMES)
+ return;
+
+ /* Encode and send */
+ encode_rtp_send(vtx, frame);
+ vtx->muted_frames++;
+}
+
+
+static void vidsrc_error_handler(int err, void *arg)
+{
+ struct vtx *vtx = arg;
+
+ DEBUG_WARNING("Video-source error: %m\n", err);
+
+ vtx->vsrc = mem_deref(vtx->vsrc);
+}
+
+
+static int vtx_alloc(struct vtx *vtx, struct video *video)
+{
+ int err;
+
+ err = lock_alloc(&vtx->lock);
+ if (err)
+ return err;
+
+ vtx->mb = mbuf_alloc(STREAM_PRESZ + 512);
+ if (!vtx->mb)
+ return ENOMEM;
+
+ vtx->video = video;
+ vtx->ts_tx = 160;
+
+ str_ncpy(vtx->device, video->cfg.src_dev, sizeof(vtx->device));
+
+ return err;
+}
+
+
+static int vrx_alloc(struct vrx *vrx, struct video *video)
+{
+ int err;
+
+ err = lock_alloc(&vrx->lock);
+ if (err)
+ return err;
+
+ vrx->video = video;
+ vrx->pt_rx = -1;
+ vrx->orient = VIDORIENT_PORTRAIT;
+
+ str_ncpy(vrx->device, video->cfg.disp_dev, sizeof(vrx->device));
+
+ return err;
+}
+
+
+/**
+ * Decode incoming RTP packets using the Video decoder
+ *
+ * NOTE: mb=NULL if no packet received
+ *
+ * @param vrx Video receive object
+ * @param hdr RTP Header
+ * @param mb Buffer with RTP payload
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+static int video_stream_decode(struct vrx *vrx, const struct rtp_header *hdr,
+ struct mbuf *mb)
+{
+ struct video *v = vrx->video;
+ struct vidframe frame;
+ struct le *le;
+ int err = 0;
+
+ if (!hdr || !mbuf_get_left(mb))
+ return 0;
+
+ lock_write_get(vrx->lock);
+
+ /* No decoder set */
+ if (!vrx->dec) {
+ DEBUG_WARNING("No video decoder!\n");
+ goto out;
+ }
+
+ frame.data[0] = NULL;
+ err = vrx->vc->dech(vrx->dec, &frame, hdr->m, hdr->seq, mb);
+ if (err) {
+
+ if (err != EPROTO) {
+ DEBUG_WARNING("%s decode error"
+ " (seq=%u, %u bytes): %m\n",
+ vrx->vc->name, hdr->seq,
+ mbuf_get_left(mb), err);
+ }
+
+ /* send RTCP FIR to peer */
+ stream_send_fir(v->strm, v->nack_pli);
+
+ /* XXX: if RTCP is not enabled, send XML in SIP INFO ? */
+
+ goto out;
+ }
+
+ /* Got a full picture-frame? */
+ if (!vidframe_isvalid(&frame))
+ goto out;
+
+ /* Process video frame through all Video Filters */
+ for (le = vrx->filtl.head; le; le = le->next) {
+
+ struct vidfilt_dec_st *st = le->data;
+
+ if (st->vf && st->vf->dech)
+ err |= st->vf->dech(st, &frame);
+ }
+
+ err = vidisp_display(vrx->vidisp, v->peer, &frame);
+
+ ++vrx->frames;
+
+out:
+ lock_rel(vrx->lock);
+
+ return err;
+}
+
+
+static int pt_handler(struct video *v, uint8_t pt_old, uint8_t pt_new)
+{
+ const struct sdp_format *lc;
+
+ lc = sdp_media_lformat(stream_sdpmedia(v->strm), pt_new);
+ if (!lc)
+ return ENOENT;
+
+ if (pt_old != (uint8_t)-1) {
+ info("Video decoder changed payload %u -> %u\n",
+ pt_old, pt_new);
+ }
+
+ v->vrx.pt_rx = pt_new;
+
+ return video_decoder_set(v, lc->data, lc->pt, lc->rparams);
+}
+
+
+/* Handle incoming stream data from the network */
+static void stream_recv_handler(const struct rtp_header *hdr,
+ struct mbuf *mb, void *arg)
+{
+ struct video *v = arg;
+ int err;
+
+ if (!mb)
+ goto out;
+
+ /* Video payload-type changed? */
+ if (hdr->pt == v->vrx.pt_rx)
+ goto out;
+
+ err = pt_handler(v, v->vrx.pt_rx, hdr->pt);
+ if (err)
+ return;
+
+ out:
+ (void)video_stream_decode(&v->vrx, hdr, mb);
+}
+
+
+static void rtcp_handler(struct rtcp_msg *msg, void *arg)
+{
+ struct video *v = arg;
+
+ switch (msg->hdr.pt) {
+
+ case RTCP_FIR:
+ v->vtx.picup = true;
+ break;
+
+ case RTCP_PSFB:
+ if (msg->hdr.count == RTCP_PSFB_PLI)
+ v->vtx.picup = true;
+ break;
+
+ case RTCP_RTPFB:
+ if (msg->hdr.count == RTCP_RTPFB_GNACK)
+ v->vtx.picup = true;
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+static int vtx_print_pipeline(struct re_printf *pf, const struct vtx *vtx)
+{
+ struct le *le;
+ struct vidsrc *vs;
+ int err;
+
+ if (!vtx)
+ return 0;
+
+ vs = vidsrc_get(vtx->vsrc);
+
+ err = re_hprintf(pf, "video tx pipeline: %10s",
+ vs ? vs->name : "src");
+
+ for (le = list_head(&vtx->filtl); le; le = le->next) {
+ struct vidfilt_enc_st *st = le->data;
+
+ if (st->vf->ench)
+ err |= re_hprintf(pf, " ---> %s", st->vf->name);
+ }
+
+ err |= re_hprintf(pf, " ---> %s\n",
+ vtx->vc ? vtx->vc->name : "encoder");
+
+ return err;
+}
+
+
+static int vrx_print_pipeline(struct re_printf *pf, const struct vrx *vrx)
+{
+ struct le *le;
+ struct vidisp *vd;
+ int err;
+
+ if (!vrx)
+ return 0;
+
+ vd = vidisp_get(vrx->vidisp);
+
+ err = re_hprintf(pf, "video rx pipeline: %10s",
+ vd ? vd->name : "disp");
+
+ for (le = list_head(&vrx->filtl); le; le = le->next) {
+ struct vidfilt_dec_st *st = le->data;
+
+ if (st->vf->dech)
+ err |= re_hprintf(pf, " <--- %s", st->vf->name);
+ }
+
+ err |= re_hprintf(pf, " <--- %s\n",
+ vrx->vc ? vrx->vc->name : "decoder");
+
+ return err;
+}
+
+
+int video_alloc(struct video **vp, const struct config *cfg,
+ struct call *call, struct sdp_session *sdp_sess, int label,
+ const struct mnat *mnat, struct mnat_sess *mnat_sess,
+ const struct menc *menc, struct menc_sess *menc_sess,
+ const char *content, const struct list *vidcodecl)
+{
+ struct video *v;
+ struct le *le;
+ int err = 0;
+
+ if (!vp || !cfg)
+ return EINVAL;
+
+ v = mem_zalloc(sizeof(*v), video_destructor);
+ if (!v)
+ return ENOMEM;
+
+ MAGIC_INIT(v);
+
+ v->cfg = cfg->video;
+ tmr_init(&v->tmr);
+
+ err = stream_alloc(&v->strm, &cfg->avt, call, sdp_sess, "video", label,
+ mnat, mnat_sess, menc, menc_sess,
+ call_localuri(call),
+ stream_recv_handler, rtcp_handler, v);
+ if (err)
+ goto out;
+
+ if (cfg->avt.rtp_bw.max >= AUDIO_BANDWIDTH) {
+ stream_set_bw(v->strm, cfg->avt.rtp_bw.max - AUDIO_BANDWIDTH);
+ }
+
+ err |= sdp_media_set_lattr(stream_sdpmedia(v->strm), true,
+ "framerate", "%d", v->cfg.fps);
+
+ /* RFC 4585 */
+ err |= sdp_media_set_lattr(stream_sdpmedia(v->strm), true,
+ "rtcp-fb", "* nack pli");
+
+ /* RFC 4796 */
+ if (content) {
+ err |= sdp_media_set_lattr(stream_sdpmedia(v->strm), true,
+ "content", "%s", content);
+ }
+
+ if (err)
+ goto out;
+
+ err = vtx_alloc(&v->vtx, v);
+ err |= vrx_alloc(&v->vrx, v);
+ if (err)
+ goto out;
+
+ /* Video codecs */
+ for (le = list_head(vidcodecl); le; le = le->next) {
+ struct vidcodec *vc = le->data;
+ err |= sdp_format_add(NULL, stream_sdpmedia(v->strm), false,
+ vc->pt, vc->name, 90000, 1,
+ vc->fmtp_ench, vc->fmtp_cmph, vc, false,
+ "%s", vc->fmtp);
+ }
+
+ /* Video filters */
+ for (le = list_head(vidfilt_list()); le; le = le->next) {
+ struct vidfilt *vf = le->data;
+ void *ctx = NULL;
+
+ err |= vidfilt_enc_append(&v->vtx.filtl, &ctx, vf);
+ err |= vidfilt_dec_append(&v->vrx.filtl, &ctx, vf);
+ if (err) {
+ DEBUG_WARNING("video-filter '%s' failed (%m)\n",
+ vf->name, err);
+ break;
+ }
+ }
+
+ out:
+ if (err)
+ mem_deref(v);
+ else
+ *vp = v;
+
+ return err;
+}
+
+
+static void vidisp_resize_handler(const struct vidsz *sz, void *arg)
+{
+ struct vrx *vrx = arg;
+ (void)vrx;
+
+ info("video: display resized: %u x %u\n", sz->w, sz->h);
+
+ /* XXX: update wanted picturesize and send re-invite to peer */
+}
+
+
+/* Set the video display - can be called multiple times */
+static int set_vidisp(struct vrx *vrx)
+{
+ struct vidisp *vd;
+
+ vrx->vidisp = mem_deref(vrx->vidisp);
+ vrx->vidisp_prm.view = NULL;
+
+ vd = (struct vidisp *)vidisp_find(vrx->video->cfg.disp_mod);
+ if (!vd)
+ return ENOENT;
+
+ return vd->alloch(&vrx->vidisp, vd, &vrx->vidisp_prm, vrx->device,
+ vidisp_resize_handler, vrx);
+}
+
+
+/* Set the encoder format - can be called multiple times */
+static int set_encoder_format(struct vtx *vtx, const char *src,
+ const char *dev, struct vidsz *size)
+{
+ struct vidsrc *vs = (struct vidsrc *)vidsrc_find(src);
+ int err;
+
+ if (!vs)
+ return ENOENT;
+
+ vtx->vsrc_size = *size;
+ vtx->vsrc_prm.fps = get_fps(vtx->video);
+ vtx->vsrc_prm.orient = VIDORIENT_PORTRAIT;
+
+ vtx->vsrc = mem_deref(vtx->vsrc);
+
+ err = vs->alloch(&vtx->vsrc, vs, NULL, &vtx->vsrc_prm,
+ &vtx->vsrc_size, NULL, dev, vidsrc_frame_handler,
+ vidsrc_error_handler, vtx);
+ if (err) {
+ info("video: no video source '%s': %m\n", src, err);
+ return err;
+ }
+
+ vtx->mute_frame = mem_deref(vtx->mute_frame);
+ err = vidframe_alloc(&vtx->mute_frame, VID_FMT_YUV420P, size);
+ if (err)
+ return err;
+
+ vidframe_fill(vtx->mute_frame, 0xff, 0xff, 0xff);
+
+ return err;
+}
+
+
+enum {TMR_INTERVAL = 5};
+static void tmr_handler(void *arg)
+{
+ struct video *v = arg;
+
+ tmr_start(&v->tmr, TMR_INTERVAL * 1000, tmr_handler, v);
+
+ /* Estimate framerates */
+ v->vtx.efps = v->vtx.frames / TMR_INTERVAL;
+ v->vrx.efps = v->vrx.frames / TMR_INTERVAL;
+
+ v->vtx.frames = 0;
+ v->vrx.frames = 0;
+}
+
+
+int video_start(struct video *v, const char *peer)
+{
+ struct vidsz size;
+ int err;
+
+ if (!v)
+ return EINVAL;
+
+ if (peer) {
+ mem_deref(v->peer);
+ err = str_dup(&v->peer, peer);
+ if (err)
+ return err;
+ }
+
+ stream_set_srate(v->strm, SRATE, SRATE);
+
+ err = set_vidisp(&v->vrx);
+ if (err) {
+ DEBUG_WARNING("could not set vidisp '%s': %m\n",
+ v->vrx.device, err);
+ }
+
+ size.w = v->cfg.width;
+ size.h = v->cfg.height;
+ err = set_encoder_format(&v->vtx, v->cfg.src_mod,
+ v->vtx.device, &size);
+ if (err) {
+ DEBUG_WARNING("could not set encoder format to"
+ " [%u x %u] %m\n",
+ size.w, size.h, err);
+ }
+
+ tmr_start(&v->tmr, TMR_INTERVAL * 1000, tmr_handler, v);
+
+ if (v->vtx.vc && v->vrx.vc) {
+ info("%H%H",
+ vtx_print_pipeline, &v->vtx,
+ vrx_print_pipeline, &v->vrx);
+ }
+
+ return 0;
+}
+
+
+void video_stop(struct video *v)
+{
+ if (!v)
+ return;
+
+ v->vtx.vsrc = mem_deref(v->vtx.vsrc);
+}
+
+
+/**
+ * Mute the video stream
+ *
+ * @param v Video stream
+ * @param muted True to mute, false to un-mute
+ */
+void video_mute(struct video *v, bool muted)
+{
+ struct vtx *vtx;
+
+ if (!v)
+ return;
+
+ vtx = &v->vtx;
+
+ vtx->muted = muted;
+ vtx->muted_frames = 0;
+ vtx->picup = true;
+
+ video_update_picture(v);
+}
+
+
+static int vidisp_update(struct vrx *vrx)
+{
+ struct vidisp *vd = vidisp_get(vrx->vidisp);
+ int err = 0;
+
+ if (vd->updateh) {
+ err = vd->updateh(vrx->vidisp, vrx->fullscreen,
+ vrx->orient, NULL);
+ }
+
+ return err;
+}
+
+
+/**
+ * Enable video display fullscreen
+ *
+ * @param v Video stream
+ * @param fs True for fullscreen, otherwise false
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int video_set_fullscreen(struct video *v, bool fs)
+{
+ if (!v)
+ return EINVAL;
+
+ v->vrx.fullscreen = fs;
+
+ return vidisp_update(&v->vrx);
+}
+
+
+static void vidsrc_update(struct vtx *vtx, const char *dev)
+{
+ struct vidsrc *vs = vidsrc_get(vtx->vsrc);
+
+ if (vs && vs->updateh)
+ vs->updateh(vtx->vsrc, &vtx->vsrc_prm, dev);
+}
+
+
+/**
+ * Set the orientation of the Video source and display
+ *
+ * @param v Video stream
+ * @param orient Video orientation (enum vidorient)
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int video_set_orient(struct video *v, int orient)
+{
+ if (!v)
+ return EINVAL;
+
+ v->vtx.vsrc_prm.orient = v->vrx.orient = orient;
+ vidsrc_update(&v->vtx, NULL);
+ return vidisp_update(&v->vrx);
+}
+
+
+int video_encoder_set(struct video *v, struct vidcodec *vc,
+ int pt_tx, const char *params)
+{
+ struct vtx *vtx;
+ int err = 0;
+
+ if (!v)
+ return EINVAL;
+
+ vtx = &v->vtx;
+
+ if (vc != vtx->vc) {
+
+ struct videnc_param prm;
+
+ prm.bitrate = v->cfg.bitrate;
+ prm.pktsize = 1024;
+ prm.fps = get_fps(v);
+ prm.max_fs = -1;
+
+ info("Set video encoder: %s %s (%u bit/s, %u fps)\n",
+ vc->name, vc->variant, prm.bitrate, prm.fps);
+
+ vtx->enc = mem_deref(vtx->enc);
+ err = vc->encupdh(&vtx->enc, vc, &prm, params);
+ if (err) {
+ DEBUG_WARNING("encoder alloc: %m\n", err);
+ return err;
+ }
+
+ vtx->vc = vc;
+ }
+
+ stream_update_encoder(v->strm, pt_tx);
+
+ return err;
+}
+
+
+int video_decoder_set(struct video *v, struct vidcodec *vc, int pt_rx,
+ const char *fmtp)
+{
+ struct vrx *vrx;
+ int err = 0;
+
+ if (!v)
+ return EINVAL;
+
+ vrx = &v->vrx;
+
+ vrx->pt_rx = pt_rx;
+
+ if (vc != vrx->vc) {
+
+ info("Set video decoder: %s %s\n", vc->name, vc->variant);
+
+ vrx->dec = mem_deref(vrx->dec);
+
+ err = vc->decupdh(&vrx->dec, vc, fmtp);
+ if (err) {
+ DEBUG_WARNING("decoder alloc: %m\n", err);
+ return err;
+ }
+
+ vrx->vc = vc;
+ }
+
+ return err;
+}
+
+
+/**
+ * Use the next video encoder in the local list of negotiated codecs
+ *
+ * @param video Video object
+ */
+void video_encoder_cycle(struct video *video)
+{
+ const struct sdp_format *rc = NULL;
+
+ if (!video)
+ return;
+
+ rc = sdp_media_format_cycle(stream_sdpmedia(video_strm(video)));
+ if (!rc) {
+ info("cycle video: no remote codec found\n");
+ return;
+ }
+
+ (void)video_encoder_set(video, rc->data, rc->pt, rc->params);
+}
+
+
+struct stream *video_strm(const struct video *v)
+{
+ return v ? v->strm : NULL;
+}
+
+
+void video_update_picture(struct video *v)
+{
+ if (!v)
+ return;
+ v->vtx.picup = true;
+}
+
+
+/**
+ * Get the driver-specific view of the video stream
+ *
+ * @param v Video stream
+ *
+ * @return Opaque view
+ */
+void *video_view(const struct video *v)
+{
+ if (!v)
+ return NULL;
+
+ return v->vrx.vidisp_prm.view;
+}
+
+
+/**
+ * Set the current Video Source device name
+ *
+ * @param v Video stream
+ * @param dev Device name
+ */
+void video_vidsrc_set_device(struct video *v, const char *dev)
+{
+ if (!v)
+ return;
+
+ vidsrc_update(&v->vtx, dev);
+}
+
+
+static bool sdprattr_contains(struct stream *s, const char *name,
+ const char *str)
+{
+ const char *attr = sdp_media_rattr(stream_sdpmedia(s), name);
+ return attr ? (NULL != strstr(attr, str)) : false;
+}
+
+
+void video_sdp_attr_decode(struct video *v)
+{
+ if (!v)
+ return;
+
+ /* RFC 4585 */
+ v->nack_pli = sdprattr_contains(v->strm, "rtcp-fb", "nack");
+}
+
+
+int video_debug(struct re_printf *pf, const struct video *v)
+{
+ const struct vtx *vtx;
+ const struct vrx *vrx;
+ int err;
+
+ if (!v)
+ return 0;
+
+ vtx = &v->vtx;
+ vrx = &v->vrx;
+
+ err = re_hprintf(pf, "\n--- Video stream ---\n");
+ err |= re_hprintf(pf, " tx: %u x %u, fps=%d\n",
+ vtx->vsrc_size.w,
+ vtx->vsrc_size.h, vtx->vsrc_prm.fps);
+ err |= re_hprintf(pf, " rx: pt=%d\n", vrx->pt_rx);
+
+ if (!list_isempty(vidfilt_list())) {
+ err |= vtx_print_pipeline(pf, vtx);
+ err |= vrx_print_pipeline(pf, vrx);
+ }
+
+ err |= stream_debug(pf, v->strm);
+
+ return err;
+}
+
+
+int video_print(struct re_printf *pf, const struct video *v)
+{
+ if (!v)
+ return 0;
+
+ return re_hprintf(pf, " efps=%d/%d", v->vtx.efps, v->vrx.efps);
+}
+
+
+int video_set_source(struct video *v, const char *name, const char *dev)
+{
+ struct vidsrc *vs = (struct vidsrc *)vidsrc_find(name);
+ struct vtx *vtx;
+
+ if (!v)
+ return EINVAL;
+
+ if (!vs)
+ return ENOENT;
+
+ vtx = &v->vtx;
+
+ vtx->vsrc = mem_deref(vtx->vsrc);
+
+ return vs->alloch(&vtx->vsrc, vs, NULL, &vtx->vsrc_prm,
+ &vtx->vsrc_size, NULL, dev,
+ vidsrc_frame_handler, vidsrc_error_handler, vtx);
+}
+
+
+void video_set_devicename(struct video *v, const char *src, const char *disp)
+{
+ if (!v)
+ return;
+
+ str_ncpy(v->vtx.device, src, sizeof(v->vtx.device));
+ str_ncpy(v->vrx.device, disp, sizeof(v->vrx.device));
+}