1 /**
2  * @file vidloop.c  Video loop
3  *
4  * Copyright (C) 2010 Creytiv.com
5  */
6 #define _DEFAULT_SOURCE 1
7 #define _BSD_SOURCE 1
8 #include <string.h>
9 #include <time.h>
10 #include <re.h>
11 #include <rem.h>
12 #include <baresip.h>
13 
14 
15 /**
16  * @defgroup vidloop vidloop
17  *
18  * A video-loop module for testing
19  *
20  * Simple test module that loops back the video frames from a
21  * video-source to a video-display, optionally via a video codec.
22  *
23  * Example usage without codec:
24  \verbatim
25   baresip -e/vidloop
26  \endverbatim
27  *
28  * Example usage with codec:
29  \verbatim
30   baresip -e"/vidloop h264"
31  \endverbatim
32  */
33 
34 
35 /** Video Statistics */
36 struct vstat {
37 	uint64_t tsamp;
38 	uint32_t frames;
39 	size_t bytes;
40 	uint32_t bitrate;
41 	double efps;
42 	size_t n_intra;
43 };
44 
45 
46 /** Video loop */
47 struct video_loop {
48 	const struct vidcodec *vc_enc;
49 	const struct vidcodec *vc_dec;
50 	struct config_video cfg;
51 	struct videnc_state *enc;
52 	struct viddec_state *dec;
53 	struct vidisp_st *vidisp;
54 	struct vidsrc_st *vsrc;
55 	struct list filtencl;
56 	struct list filtdecl;
57 	struct vstat stat;
58 	struct tmr tmr_bw;
59 	uint16_t seq;
60 	bool need_conv;
61 	int err;
62 };
63 
64 
65 static struct video_loop *gvl;
66 
67 
display(struct video_loop * vl,struct vidframe * frame)68 static int display(struct video_loop *vl, struct vidframe *frame)
69 {
70 	struct vidframe *frame_filt = NULL;
71 	struct le *le;
72 	int err = 0;
73 
74 	if (!vidframe_isvalid(frame))
75 		return 0;
76 
77 	/* Process video frame through all Video Filters */
78 	for (le = vl->filtdecl.head; le; le = le->next) {
79 
80 		struct vidfilt_dec_st *st = le->data;
81 
82 		/* Some video decoders keeps the displayed video frame
83 		 * in memory and we should not write to that frame.
84 		 */
85 		if (!frame_filt) {
86 
87 			err = vidframe_alloc(&frame_filt, frame->fmt,
88 					     &frame->size);
89 			if (err)
90 				return err;
91 
92 			vidframe_copy(frame_filt, frame);
93 
94 			frame = frame_filt;
95 		}
96 
97 		if (st->vf->dech)
98 			err |= st->vf->dech(st, frame);
99 	}
100 
101 	if (err) {
102 		warning("vidloop: error in video-filters (%m)\n", err);
103 	}
104 
105 	/* display frame */
106 	err = vidisp_display(vl->vidisp, "Video Loop", frame);
107 	if (err == ENODEV) {
108 		info("vidloop: video-display was closed\n");
109 		vl->vidisp = mem_deref(vl->vidisp);
110 		vl->err = err;
111 	}
112 
113 	mem_deref(frame_filt);
114 
115 	return err;
116 }
117 
118 
packet_handler(bool marker,uint32_t rtp_ts,const uint8_t * hdr,size_t hdr_len,const uint8_t * pld,size_t pld_len,void * arg)119 static int packet_handler(bool marker, uint32_t rtp_ts,
120 			  const uint8_t *hdr, size_t hdr_len,
121 			  const uint8_t *pld, size_t pld_len,
122 			  void *arg)
123 {
124 	struct video_loop *vl = arg;
125 	struct vidframe frame;
126 	struct mbuf *mb;
127 	bool intra;
128 	int err = 0;
129 	(void)rtp_ts;
130 
131 	mb = mbuf_alloc(hdr_len + pld_len);
132 	if (!mb)
133 		return ENOMEM;
134 
135 	if (hdr_len)
136 		mbuf_write_mem(mb, hdr, hdr_len);
137 	mbuf_write_mem(mb, pld, pld_len);
138 
139 	mb->pos = 0;
140 
141 	vl->stat.bytes += mbuf_get_left(mb);
142 
143 	/* decode */
144 	frame.data[0] = NULL;
145 	if (vl->vc_dec && vl->dec) {
146 		err = vl->vc_dec->dech(vl->dec, &frame, &intra,
147 				       marker, vl->seq++, mb);
148 		if (err) {
149 			warning("vidloop: codec decode: %m\n", err);
150 			goto out;
151 		}
152 
153 		if (intra)
154 			++vl->stat.n_intra;
155 	}
156 
157 	if (vidframe_isvalid(&frame)) {
158 
159 		display(vl, &frame);
160 	}
161 
162  out:
163 	mem_deref(mb);
164 
165 	return 0;
166 }
167 
168 
vidsrc_frame_handler(struct vidframe * frame,void * arg)169 static void vidsrc_frame_handler(struct vidframe *frame, void *arg)
170 {
171 	struct video_loop *vl = arg;
172 	struct vidframe *f2 = NULL;
173 	struct le *le;
174 	int err = 0;
175 
176 	++vl->stat.frames;
177 
178 	if (frame->fmt != (enum vidfmt)vl->cfg.enc_fmt) {
179 
180 		if (!vl->need_conv) {
181 			info("vidloop: NOTE: pixel-format conversion"
182 			     " needed: %s  -->  %s\n",
183 			     vidfmt_name(frame->fmt),
184 			     vidfmt_name(vl->cfg.enc_fmt));
185 			vl->need_conv = true;
186 		}
187 
188 		if (vidframe_alloc(&f2, vl->cfg.enc_fmt, &frame->size))
189 			return;
190 
191 		vidconv(f2, frame, 0);
192 
193 		frame = f2;
194 	}
195 
196 	/* Process video frame through all Video Filters */
197 	for (le = vl->filtencl.head; le; le = le->next) {
198 
199 		struct vidfilt_enc_st *st = le->data;
200 
201 		if (st->vf->ench)
202 			err |= st->vf->ench(st, frame);
203 	}
204 
205 	if (vl->vc_enc && vl->enc) {
206 		err = vl->vc_enc->ench(vl->enc, false, frame);
207 		if (err) {
208 			warning("vidloop: encoder error (%m)\n", err);
209 		}
210 	}
211 	else {
212 		vl->stat.bytes += vidframe_size(frame->fmt, &frame->size);
213 		(void)display(vl, frame);
214 	}
215 
216 	mem_deref(f2);
217 }
218 
219 
vidloop_destructor(void * arg)220 static void vidloop_destructor(void *arg)
221 {
222 	struct video_loop *vl = arg;
223 
224 	tmr_cancel(&vl->tmr_bw);
225 	mem_deref(vl->vsrc);
226 	mem_deref(vl->enc);
227 	mem_deref(vl->dec);
228 	mem_deref(vl->vidisp);
229 	list_flush(&vl->filtencl);
230 	list_flush(&vl->filtdecl);
231 }
232 
233 
enable_codec(struct video_loop * vl,const char * name)234 static int enable_codec(struct video_loop *vl, const char *name)
235 {
236 	struct list *vidcodecl = baresip_vidcodecl();
237 	struct videnc_param prm;
238 	int err;
239 
240 	prm.fps     = vl->cfg.fps;
241 	prm.pktsize = 1480;
242 	prm.bitrate = vl->cfg.bitrate;
243 	prm.max_fs  = -1;
244 
245 	/* Use the first video codec */
246 
247 	vl->vc_enc = vidcodec_find_encoder(vidcodecl, name);
248 	if (!vl->vc_enc) {
249 		warning("vidloop: could not find encoder (%s)\n", name);
250 		return ENOENT;
251 	}
252 
253 	info("vidloop: enabled encoder %s (%u fps, %u bit/s)\n",
254 	     vl->vc_enc->name, prm.fps, prm.bitrate);
255 
256 	vl->vc_dec = vidcodec_find_decoder(vidcodecl, name);
257 	if (!vl->vc_dec) {
258 		warning("vidloop: could not find decoder (%s)\n", name);
259 		return ENOENT;
260 	}
261 
262 	info("vidloop: enabled decoder %s\n", vl->vc_dec->name);
263 
264 	err = vl->vc_enc->encupdh(&vl->enc, vl->vc_enc, &prm, NULL,
265 				  packet_handler, vl);
266 	if (err) {
267 		warning("vidloop: update encoder failed: %m\n", err);
268 		return err;
269 	}
270 
271 	if (vl->vc_dec->decupdh) {
272 		err = vl->vc_dec->decupdh(&vl->dec, vl->vc_dec, NULL);
273 		if (err) {
274 			warning("vidloop: update decoder failed: %m\n", err);
275 			return err;
276 		}
277 	}
278 
279 	return 0;
280 }
281 
282 
print_status(struct video_loop * vl)283 static void print_status(struct video_loop *vl)
284 {
285 	(void)re_fprintf(stdout,
286 			 "\rstatus:"
287 			 " [%s] [%s]  fmt=%s  intra=%zu "
288 			 " EFPS=%.1f      %u kbit/s       \r",
289 			 vl->vc_enc ? vl->vc_enc->name : "",
290 			 vl->vc_dec ? vl->vc_dec->name : "",
291 			 vidfmt_name(vl->cfg.enc_fmt),
292 			 vl->stat.n_intra,
293 			 vl->stat.efps, vl->stat.bitrate);
294 	fflush(stdout);
295 }
296 
297 
calc_bitrate(struct video_loop * vl)298 static void calc_bitrate(struct video_loop *vl)
299 {
300 	const uint64_t now = tmr_jiffies();
301 
302 	if (now > vl->stat.tsamp) {
303 
304 		const uint32_t dur = (uint32_t)(now - vl->stat.tsamp);
305 
306 		vl->stat.efps = 1000.0f * vl->stat.frames / dur;
307 
308 		vl->stat.bitrate = (uint32_t) (8 * vl->stat.bytes / dur);
309 	}
310 
311 	vl->stat.frames = 0;
312 	vl->stat.bytes = 0;
313 	vl->stat.tsamp = now;
314 }
315 
316 
timeout_bw(void * arg)317 static void timeout_bw(void *arg)
318 {
319 	struct video_loop *vl = arg;
320 
321 	if (vl->err) {
322 		info("error in video-loop -- closing (%m)\n", vl->err);
323 		gvl = mem_deref(gvl);
324 		return;
325 	}
326 
327 	tmr_start(&vl->tmr_bw, 2000, timeout_bw, vl);
328 
329 	calc_bitrate(vl);
330 	print_status(vl);
331 }
332 
333 
vsrc_reopen(struct video_loop * vl,const struct vidsz * sz)334 static int vsrc_reopen(struct video_loop *vl, const struct vidsz *sz)
335 {
336 	struct vidsrc_prm prm;
337 	int err;
338 
339 	info("vidloop: %s,%s: open video source: %u x %u at %u fps\n",
340 	     vl->cfg.src_mod, vl->cfg.src_dev,
341 	     sz->w, sz->h, vl->cfg.fps);
342 
343 	prm.orient = VIDORIENT_PORTRAIT;
344 	prm.fps    = vl->cfg.fps;
345 
346 	vl->vsrc = mem_deref(vl->vsrc);
347 	err = vidsrc_alloc(&vl->vsrc, baresip_vidsrcl(),
348 			   vl->cfg.src_mod, NULL, &prm, sz,
349 			   NULL, vl->cfg.src_dev, vidsrc_frame_handler,
350 			   NULL, vl);
351 	if (err) {
352 		warning("vidloop: vidsrc '%s' failed: %m\n",
353 			vl->cfg.src_dev, err);
354 	}
355 
356 	return err;
357 }
358 
359 
video_loop_alloc(struct video_loop ** vlp)360 static int video_loop_alloc(struct video_loop **vlp)
361 {
362 	struct video_loop *vl;
363 	struct config *cfg;
364 	struct le *le;
365 	int err = 0;
366 
367 	cfg = conf_config();
368 	if (!cfg)
369 		return EINVAL;
370 
371 	vl = mem_zalloc(sizeof(*vl), vidloop_destructor);
372 	if (!vl)
373 		return ENOMEM;
374 
375 	vl->cfg = cfg->video;
376 	tmr_init(&vl->tmr_bw);
377 
378 	/* Video filters */
379 	for (le = list_head(baresip_vidfiltl()); le; le = le->next) {
380 		struct vidfilt *vf = le->data;
381 		void *ctx = NULL;
382 
383 		info("vidloop: added video-filter `%s'\n", vf->name);
384 
385 		err |= vidfilt_enc_append(&vl->filtencl, &ctx, vf);
386 		err |= vidfilt_dec_append(&vl->filtdecl, &ctx, vf);
387 		if (err) {
388 			warning("vidloop: vidfilt error: %m\n", err);
389 		}
390 	}
391 
392 	info("vidloop: open video display (%s.%s)\n",
393 	     vl->cfg.disp_mod, vl->cfg.disp_dev);
394 
395 	err = vidisp_alloc(&vl->vidisp, baresip_vidispl(),
396 			   vl->cfg.disp_mod, NULL,
397 			   vl->cfg.disp_dev, NULL, vl);
398 	if (err) {
399 		warning("vidloop: video display failed: %m\n", err);
400 		goto out;
401 	}
402 
403 	tmr_start(&vl->tmr_bw, 1000, timeout_bw, vl);
404 
405  out:
406 	if (err)
407 		mem_deref(vl);
408 	else
409 		*vlp = vl;
410 
411 	return err;
412 }
413 
414 
415 /**
416  * Start the video loop (for testing)
417  */
vidloop_start(struct re_printf * pf,void * arg)418 static int vidloop_start(struct re_printf *pf, void *arg)
419 {
420 	const struct cmd_arg *carg = arg;
421 	struct vidsz size;
422 	struct config *cfg = conf_config();
423 	const char *codec_name = carg->prm;
424 	int err = 0;
425 
426 	size.w = cfg->video.width;
427 	size.h = cfg->video.height;
428 
429 	if (gvl) {
430 		return re_hprintf(pf, "video-loop already running.\n");
431 	}
432 
433 	(void)re_hprintf(pf, "Enable video-loop on %s,%s: %u x %u\n",
434 			 cfg->video.src_mod, cfg->video.src_dev,
435 			 size.w, size.h);
436 
437 	err = video_loop_alloc(&gvl);
438 	if (err) {
439 		warning("vidloop: alloc: %m\n", err);
440 		return err;
441 	}
442 
443 	if (str_isset(codec_name)) {
444 
445 		err = enable_codec(gvl, codec_name);
446 		if (err) {
447 			gvl = mem_deref(gvl);
448 			return err;
449 		}
450 
451 		(void)re_hprintf(pf, "%sabled codec: %s\n",
452 				 gvl->vc_enc ? "En" : "Dis",
453 				 gvl->vc_enc ? gvl->vc_enc->name : "");
454 	}
455 
456 	/* Start video source, after codecs are created */
457 	err = vsrc_reopen(gvl, &size);
458 	if (err) {
459 		gvl = mem_deref(gvl);
460 		return err;
461 	}
462 
463 	return err;
464 }
465 
466 
vidloop_stop(struct re_printf * pf,void * arg)467 static int vidloop_stop(struct re_printf *pf, void *arg)
468 {
469 	(void)arg;
470 
471 	if (gvl)
472 		(void)re_hprintf(pf, "Disable video-loop\n");
473 	gvl = mem_deref(gvl);
474 	return 0;
475 }
476 
477 
478 static const struct cmd cmdv[] = {
479 	{"vidloop",     0, CMD_PRM, "Start video-loop <codec>", vidloop_start},
480 	{"vidloop_stop",0, 0,       "Stop video-loop",          vidloop_stop },
481 };
482 
483 
module_init(void)484 static int module_init(void)
485 {
486 	return cmd_register(baresip_commands(), cmdv, ARRAY_SIZE(cmdv));
487 }
488 
489 
module_close(void)490 static int module_close(void)
491 {
492 	gvl = mem_deref(gvl);
493 	cmd_unregister(baresip_commands(), cmdv);
494 	return 0;
495 }
496 
497 
498 EXPORT_SYM const struct mod_export DECL_EXPORTS(vidloop) = {
499 	"vidloop",
500 	"application",
501 	module_init,
502 	module_close,
503 };
504