[asterisk-commits] rizzo: branch rizzo/video_v2 r82753 - /team/rizzo/video_v2/channels/

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Tue Sep 18 10:45:22 CDT 2007


Author: rizzo
Date: Tue Sep 18 10:45:22 2007
New Revision: 82753

URL: http://svn.digium.com/view/asterisk?view=rev&rev=82753
Log:
lots of cleanup of this module, including the ability
to run in "headless" configuration i.e. without X or SDL.


Modified:
    team/rizzo/video_v2/channels/console_video.c

Modified: team/rizzo/video_v2/channels/console_video.c
URL: http://svn.digium.com/view/asterisk/team/rizzo/video_v2/channels/console_video.c?view=diff&rev=82753&r1=82752&r2=82753
==============================================================================
--- team/rizzo/video_v2/channels/console_video.c (original)
+++ team/rizzo/video_v2/channels/console_video.c Tue Sep 18 10:45:22 2007
@@ -1,24 +1,33 @@
 /* header for console video */
 /*
- * experimental support for video sessions. We use:
- * - SDL for rendering both incoming and outgoing data,
- * - ffmpeg for encoding and decoding;
- * - video4linux for accessing the source.
- */
+ * Experimental support for video sessions. We use SDL for rendering, ffmpeg
+ * as the codec library for encoding and decoding, and Video4Linux and X11
+ * to generate the local video stream.
+ *
+ * If one of these pieces is not available, either at compile time or at
+ * runtime, we do our best to run without it. Of course, no codec library
+ * means we can only deal with raw data, no SDL means we cannot do rendering,
+ * no V4L or X11 means we cannot generate data (but eventually we could
+ * stream from a file!).
+ *
+ * We need a recent (2007.07.12 or newer) version of ffmpeg to avoid warnings.
+ * Older versions might give 'deprecated' messages during compilation,
+ * thus not compiling in AST_DEVMODE, or don't have swscale, in which case
+ * you can try to compile #defining OLD_FFMPEG here.
+ */
+
+
 //#define DROP_PACKETS 5       // if set, simulate this percentage of lost video packets
 #define HAVE_V4L	1
 #define HAVE_SDL	1
 #define HAVE_FFMPEG	1
-//#define OLD_FFMPEG	1	/* set if no swscale */
-
-/*
- * You need a recent (2007.07.12 or so) version of ffmpeg to avoid warning.
- * Older versions might give 'deprecated' messages during compilation,
- * thus not compiling in AST_DEVMODE, or don't have swscale, in which case
- * you can try to compile #defining OLD_FFMPEG here.
- */
+//#define OLD_FFMPEG	1	/* set for old ffmpeg with no swscale */
+
 #if !defined(HAVE_FFMPEG) || HAVE_FFMPEG == 0 || !defined(HAVE_SDL) || HAVE_SDL == 0
-/* stubs for everything */
+/*
+ * Neither SDL nor codec means we cannot do video, so we only give stubs to
+ * keep the compiler happy (basically, a placeholder for config file info).
+ */
 struct video_desc {	/* empty */
 	int 	w;
 	int 	h;
@@ -28,22 +37,22 @@
 };
 #define CONSOLE_FORMAT_VIDEO	0
 #define	console_write_video	NULL
-static void console_video_start(struct video_desc *env,
-	struct ast_channel *owner)
-{
-}
-
-static void console_video_uninit(struct video_desc *env)
-{
-}
-
-static struct ast_frame *get_video_frames(struct video_desc *env)
+static void console_video_start(struct video_desc *e, struct ast_channel *c)
+{
+}
+
+static void console_video_uninit(struct video_desc *e)
+{
+}
+
+static struct ast_frame *get_video_frames(struct video_desc *e)
 {
 	return NULL;
 }
 
-#else	/* the body of the file */
-/*
+#else
+/*
+ * The real thing, with support for SDL and codecs (and possibly V4l/X11)
  * In order to decode video you need the following patch to the
  * main Makefile:
 
@@ -66,33 +75,39 @@
 		videosupport=yes
 
  */
-/*
- * Examples for video encoding are at http://www.irisa.fr/texmex/people/dufouil/ffmpegdoxy/apiexample_8c-source.html
- */
-
-#include <X11/Xlib.h>
+
+#include <X11/Xlib.h>		/* this should be conditional */
 
 #include <ffmpeg/avcodec.h>
 #ifndef OLD_FFMPEG
 #include <ffmpeg/swscale.h>	/* requires a recent ffmpeg */
 #endif
+
 #include <SDL/SDL.h>
 
-/* Structures for ffmpeg processing */
-/*
- * Information for decoding incoming video stream.
- * We need:
- * - one descriptor per output stream (camera or file, plus encoding context)
- * - one descriptor per incoming stream (network data, plus decoding context)
- * - one descriptor per window (SDL).
- */
-
+/*
+ * In many places we use buffers to store the raw frames (but not only),
+ * so here is a structure to keep all the info. data = NULL means the
+ * structure is not initialized, so the other fields are invalid.
+ */
 struct fbuf_t {		/* frame buffers, dynamically allocated */
-	uint8_t	*data;
-	int	size;
-	int	used;
+	uint8_t	*data;	/* malloc-ed memory */
+	int	size;	/* total size in bytes */
+	int	used;	/* space used so far */
 };
 
+/*
+ * Descriptor of the local source, made of the following pieces:
+ *  + configuration info (geometry, device name, fps...). These are read
+ *    from the config file and copied here before calling video_out_init();
+ *  + the frame buffer (buf) and source pixel format, allocated at init time;
+ *  + the encoding and RTP info, including timestamps to generate
+ *    frames at the correct rate;
+ *  + source-specific info, i.e. fd for /dev/video, dpy-image for x11, etc,
+ *    filled in by video_open
+ * NOTE: buf.data == NULL means the rest of the struct is invalid, and
+ *	the video source is not available.
+ */
 struct video_out_desc {
 	/* video device support.
 	 * videodevice and geometry are read from the config file.
@@ -106,65 +121,85 @@
 	int		fps;
 	int		bitrate;
 
-	/* this is generated here */
-	int 		fd;		/* file descriptor */
+	struct fbuf_t	buf;		/* frame buffer */
 	int pix_fmt;			/* source pixel format */
 
-	struct fbuf_t	buf;
 	AVCodecContext	*context;	/* encoding context */
 	AVCodec		*codec;
 	AVFrame		*frame;
 	int		lasttxframe;	/* XXX - useless: RTP overwrite seqno */
 	struct fbuf_t	encbuf;		/* encoding buffer */
 	int		mtu;
-
-	struct timeval	last_frame;
-	Display *dpy;
+	struct timeval	last_frame;	/* when we read the last frame ? */
+
+	/* device specific info */
+	int 		fd;		/* file descriptor, for webcam */
+	Display *dpy;			/* x11 grabber info */
 	XImage      *image;
 };
 
+/*
+ * Descriptor for the incoming stream, with a buffer for the bitstream
+ * extracted by the RTP packets, RTP reassembly info, and a frame buffer
+ * for the decoded frame (buf).
+ * and store the result in a suitable frame buffer for later display.
+ * NOTE: context == NULL means the rest is invalid (e.g. because no
+ *	codec, no memory, etc.) and we must drop all incoming frames.
+ */
 struct video_in_desc {
 	AVCodecContext          *context;	/* information about the codec in the stream */
 	AVCodec                 *codec;		/* reference to the codec */
 	AVFrame                 *frame;		/* place to store the frame */
 	AVCodecParserContext    *parser;
 	int                     completed;	/* probably unnecessary */
-	struct fbuf_t buf;
 	uint16_t 		next_seq;	/* must be 16 bit */
 	int                     discard;
 	struct timeval	ts;
 	int received;
+	struct fbuf_t buf;		/* decoded frame */
 };
 
+/*
+ * The overall descriptor, with room for config info, video source and
+ * received data descriptors, SDL info, etc.
+ */
 struct video_desc {
-	ast_mutex_t lock;
-
-	pthread_t vthread;
-	int	shutdown;	/* set to ask the thread to shutdown */
-	struct ast_channel *owner;	/* owner channel */
-
 	int 	w;
 	int 	h;
 	int 	fps;
 	int 	bitrate;
 	char videodevice[64];
 
+	ast_mutex_t lock;
+
+	pthread_t vthread;
+	int	shutdown;	/* set to ask the thread to shutdown */
+	struct ast_channel *owner;	/* owner channel */
+
 	struct video_in_desc in;
 	struct video_out_desc out;
 
 	SDL_Surface             *screen;
-	int                     initialized;
+	int                     sdl_ok;
 	SDL_Overlay             *bmp[2];
 
 };
 
+/*
+ * Table of translation between asterisk and ffmpeg formats.
+ * We need also a field for read and write (encoding and decoding), because
+ * e.g. H263+ uses different codec IDs in ffmpeg when encoding or decoding.
+ */
 struct _cm {	/* map ffmpeg codec types to asterisk formats */
 	uint32_t	ast_format;	/* 0 is a terminator */
 	enum CodecID	codec;
 	enum { CM_RD = 1, CM_WR = 2, CM_RDWD = 3 } rw;	/* read or write or both ? */
 };
 
-/*! the list of video formats we support */
+/*!
+ * The list of video formats we support. You can OR the formats, and
+ * provide corresponding ffmpeg mappings in the table below.
+ */
 #define CONSOLE_FORMAT_VIDEO	AST_FORMAT_H263_PLUS
 
 static struct _cm video_formats[] = {
@@ -173,11 +208,13 @@
 	{ 0,			0, 0 },
 };
 
+/* Video4Linux stuff is only used in video_open() */
 #if defined(HAVE_V4L) && HAVE_V4L > 0
 #include <linux/videodev.h>
 #endif
 
-/* open the local video source and allocate a buffer
+/*!
+ * Open the local video source and allocate a buffer
  * for storing the image. Return 0 on success, -1 on error
  */
 static int video_open(struct video_out_desc *v)
@@ -186,6 +223,7 @@
 
 	if (v->buf.data)	/* buffer allocated means device already open */
 		return v->fd;
+	v->fd = -1;
 	/*
 	 * if the device is "X11", then open the x11 grabber
 	 */
@@ -195,7 +233,10 @@
 	XImage *im;
 
 	v->dpy = XOpenDisplay(NULL);
-	ast_log(LOG_WARNING, "XOpenDisplay %pd\n", v->dpy);
+	if (v->dpy == NULL) {
+		ast_log(LOG_WARNING, "error opening display\n");
+		goto error;
+	}
 	v->image = im = XGetImage(v->dpy,
 		RootWindow(v->dpy, DefaultScreen(v->dpy)),
 		x_ofs, y_ofs, v->w, v->h, AllPlanes, ZPixmap);
@@ -210,7 +251,7 @@
 	v->pix_fmt = PIX_FMT_RGB565;
 	v->fd = -2;
     }
-#if defined(HAVE_V4L) && HAVE_V4L != 0
+#if defined(HAVE_V4L) && HAVE_V4L > 0
     else {
 	struct video_window vw = { 0 };	/* camera attributes */
 	struct video_picture vp;
@@ -248,6 +289,8 @@
 	}
     }
 #endif /* HAVE_V4L */
+	if (v->image == NULL && v->fd < 0)
+		goto error;
 	v->buf.size = (v->w * v->h * 3)/2;	/* yuv411 */
 	ast_log(LOG_WARNING, "videodev %s opened, size %dx%d %d\n", v->device, v->w, v->h, v->buf.size);
 	v->buf.data = ast_calloc(1, v->buf.size);
@@ -257,9 +300,11 @@
 	}
 
 	v->buf.used = 0;
-	return (v->image || v->fd >= 0) ? 0 : -1 ;
+	return 0;
 
 error:
+	ast_log(LOG_WARNING, "fd %d dpy %p img %p data %p\n",
+		v->fd, v->dpy, v->image, v->buf.data);
 	if (v->dpy)
 		XCloseDisplay(v->dpy);
 	v->dpy = NULL;
@@ -274,13 +319,12 @@
 }
 
 /*! \brief complete a buffer from the local video source.
- * called by get_video_frame(), in turn called by asterisk
+ * called by get_video_frames(), in turn called by asterisk
  * as part of the read callback
  */
 static int video_read(struct video_out_desc *v)
 {
 	struct timeval now = ast_tvnow();
-	// fprintf(stderr, "video_read %p buf %p image %p\n", v, v->buf.data, v->image);
 	if (v->buf.data == NULL)	/* not initialized */
 		return 0;
 
@@ -347,7 +391,6 @@
  * to display the frame.
  */
 
-static struct video_desc *get_video_desc(struct ast_channel *c);
 
 /*! \brief map an asterisk format into an ffmpeg one */
 static enum CodecID map_video_format(uint32_t ast_format, int rw)
@@ -391,7 +434,6 @@
 
 	v->ts = ast_tvnow();
 	codec = map_video_format(format, CM_RD);
-	ast_log(LOG_WARNING, "init for format 0x%x gives %d\n", format, codec);
 
 	v->codec = avcodec_find_decoder(codec);
 	if (!v->codec) {
@@ -433,23 +475,19 @@
 
 	if (v->frame) 
 		av_free(v->frame);
-
 	if (v->buf.data) 
 		free(v->buf.data);
-
 	if (v->image) {	/* X11 grabber */
 		XCloseDisplay(v->dpy);
 		v->dpy = NULL;
 		v->image = NULL;
 	}
-	if(v->fd >= 0) 
+	if (v->fd >= 0) 
 		close(v->fd);
-
-	if(v->encbuf.data)
+	if (v->encbuf.data)
 		free(v->encbuf.data);
-
+	bzero(v, sizeof(*v));
 	v->fd = -1;
-
 	return -1;
 }
 
@@ -499,7 +537,7 @@
 	v->context->bit_rate = v->bitrate;
 	v->context->gop_size = (int) v->fps*5; // emit I frame every 5 seconds
  
-	fprintf(stderr, "w: %d...h: %d...fps: %d...", v->w, v->h, v->fps);
+	ast_log(LOG_WARNING, "w: %d h: %d fps: %d\n", v->w, v->h, v->fps);
 	v->context->time_base = (AVRational){1, v->fps};
 	if (avcodec_open(v->context, v->codec) < 0) {
 		ast_log(LOG_WARNING, "Unable to initialize the encoder parser\n");
@@ -547,18 +585,13 @@
 	ast_log(LOG_WARNING, "thread terminated\n");
 	video_in_uninit(&env->in);
 	video_out_uninit(&env->out);
-	if (env->bmp[0])
+	if (env->sdl_ok) {
+	    if (env->bmp[0])
 		SDL_FreeYUVOverlay(env->bmp[0]);
-	if (env->bmp[1])
+	    if (env->bmp[1])
 		SDL_FreeYUVOverlay(env->bmp[1]);
-	SDL_Quit();
-
-	/*if (env->out.buf.data) {
-		free(env->out.buf.data);
-		env->out.buf.data = NULL;
-		if (env->out.fd >= 0)
-		    close(env->out.fd);
-	}*/
+	    SDL_Quit();
+	}
 	{	/* clear the struct but restore some fields */
 		struct video_desc x = *env; /* make a copy */
 		bzero(env, sizeof(struct video_desc));
@@ -695,11 +728,7 @@
 	char *src = NULL;	/* pixel input */
 	int pix_fmt;
 
-
-	if (out == 2)
-		ast_log(LOG_WARNING, "out %d initialized %d\n", out, env->initialized);
-
-	if (!env->initialized)
+	if (!env->sdl_ok)
 		return;
 
 	if (out) {	/* webcam/x11 to sdl */
@@ -715,13 +744,7 @@
 		in_w = c->width;
 		in_h = c->height;
 	}
-#if 0
-	fprintf(stderr, "thd %p env->out %dx%d in.ctx %dx%d default %dx%d\n",
-		pthread_self(),
-		env->out.w, env->out.h, c->width, c->height, env->w, env->h);
-#endif
-
-	bmp = env->bmp[out ? 1 : 0];
+	bmp = env->bmp[out];
 	SDL_LockYUVOverlay(bmp);
 	pict.data[0] = bmp->pixels[0];
 	pict.data[1] = bmp->pixels[1];
@@ -741,7 +764,7 @@
 		}
 	} else {	/* decode */
 #ifdef OLD_FFMPEG /* XXX img_convert is deprecated */
-		/* env->initialized guarantees that in.frame exists */
+		/* env->sdl_ok guarantees that in.frame exists */
 		img_convert(&pict, PIX_FMT_YUV420P, pict_in, pix_fmt, in_w, in_h);
 #else /* XXX replacement */
 		struct SwsContext *convert_ctx;
@@ -767,14 +790,12 @@
 	rect.y = 0;
 	rect.w = w;
 	rect.h = h;
-	if (out > 1)
-		ast_log(LOG_WARNING, "about to display\n");
 
 	SDL_DisplayYUVOverlay(bmp, &rect);
-	if (out > 1)
-		ast_log(LOG_WARNING, "SDL_DisplayYUVOverlay done\n");
 	SDL_UnlockYUVOverlay(bmp);
 }
+
+static struct video_desc *get_video_desc(struct ast_channel *c);
 
 /*
  * This function is called (by asterisk) for each video packet
@@ -789,20 +810,20 @@
 static int console_write_video(struct ast_channel *chan, struct ast_frame *f)
 {
 	uint8_t *data;
-	int len;
+	int len, need;
 	struct video_desc *env = get_video_desc(chan);
 	struct video_in_desc *v = &env->in;
 
-	if(!env->initialized) {
-		ast_log(LOG_WARNING, "here env should be already initialized\n");
-		return -1;	/* error */
+	if (v->context == NULL) {
+		ast_log(LOG_WARNING, "cannot decode, dropping frame\n");
+		return 0;	/* error */
 	}
 
 	v->received++;
 #if defined(DROP_PACKETS) && DROP_PACKETS > 0
 	/*
-	* Fragment of code to simulate lost/delayed packets
-	*/
+	 * Fragment of code to simulate lost/delayed packets
+	 */
 	if((random() % 10000) <= 100*DROP_PACKETS) {
 		ast_log(LOG_NOTICE, "Packet lost [%d]\n", f->seqno);
 		return 0;
@@ -852,15 +873,16 @@
 		return 0;
 	}
 
-	/* allocate buffer as we see fit. ffmpeg wants an extra FF_INPUT_BUFFER_PADDING_SIZE
-	 * and a 0 as a buffer terminator to prevent trouble.
+	/* Allocate buffer. ffmpeg wants an extra FF_INPUT_BUFFER_PADDING_SIZE,
+	 * and also wants 0 as a buffer terminator to prevent trouble.
 	 */
+	need = len + FF_INPUT_BUFFER_PADDING_SIZE;
 	if (v->buf.data == NULL) {
-		v->buf.size = len + FF_INPUT_BUFFER_PADDING_SIZE;
+		v->buf.size = need;
 		v->buf.used = 0;
-		v->buf.data = ast_malloc(len);
-	} else if (v->buf.used + len + FF_INPUT_BUFFER_PADDING_SIZE> v->buf.size) {
-		v->buf.size = v->buf.used + len + FF_INPUT_BUFFER_PADDING_SIZE;
+		v->buf.data = ast_malloc(v->buf.size);
+	} else if (v->buf.used + need > v->buf.size) {
+		v->buf.size = v->buf.used + need;
 		v->buf.data = ast_realloc(v->buf.data, v->buf.size);
 	}
 	if (v->buf.data == NULL) {
@@ -872,7 +894,6 @@
 	memcpy(v->buf.data + v->buf.used, data, len);
 	v->buf.used += len;
 	v->buf.data[v->buf.used] = '\0';
-	v->buf.data[v->buf.used+1] = '\0';	/* just in case... */
 	if (f->subclass & 0x01) {	// RTP Marker
 		if (decode_video(v)) {
 			show_frame(env, 0);
@@ -978,14 +999,20 @@
 	struct video_out_desc *v = &env->out;
 	struct fbuf_t *b = &v->encbuf;
 
+	if (!v->buf.data) {
+		ast_log(LOG_WARNING, "fail, no buffer\n");
+		return NULL;
+	}
+
 	if (!video_read(v))
-		return NULL;
-	// fprintf(stderr, "video read\n");
+		return NULL;	/* can happen, e.g. we are reading too early */
 
 	/* get frame and put them in the queue */
 	show_frame(env, 1);
-	if (b->data == NULL)
+	if (b->data == NULL) {
+		ast_log(LOG_WARNING, "fail, no encbuf\n");
 		return NULL;
+	}
 	buflen = avcodec_encode_video(v->context, b->data, b->size, v->frame);
 	return split_frame(v, buflen);
 }
@@ -1005,10 +1032,9 @@
 		struct ast_frame *f;
 
 		if (env->shutdown) {
-			fprintf(stderr, "video_thread shutting down%d\n", i);
+			ast_log(LOG_WARNING, "video_thread shutting down%d\n", i);
 			break;
 		}
-		// fprintf(stderr, "video_thread %d owner %p\n", i, env->owner);
 		ast_select(0, NULL, NULL, NULL, &t);
 		if (0) {
 		    f = get_video_frames(env);
@@ -1020,11 +1046,11 @@
 }
 
 /*!
- * The first call to the video code, called by the *_new()
- * call (in turn, called by .answer or .dial) when the channel
- * is created. Here we do the following:
- *   - make sure that avcodec is properly initialized;
- *   - try to open the local video source
+ * The first call to the video code, called by oss_new() or similar.
+ * Here we initialize the various components we use, namely SDL for display,
+ * ffmpeg for encoding/decoding, and a local video source.
+ * We do our best to progress even if some of the components are not
+ * available.
  */
 static void console_video_start(struct video_desc *env,
 	struct ast_channel *owner)
@@ -1033,6 +1059,10 @@
 
 	if (owner == NULL)	/* nothing to do if we don't have a channel */
 		return;
+	env->owner = owner;
+	bzero(&env->in, sizeof(env->in));
+	bzero(&env->out, sizeof(env->out));
+
 	/*
 	 * Register all codecs supported by the ffmpeg library.
 	 * Doing it once is enough.
@@ -1042,7 +1072,9 @@
 	avcodec_init();
 	avcodec_register_all();
 	if (video_in_init(&env->in, CONSOLE_FORMAT_VIDEO)) {
-		fprintf(stderr, "Could not initialize input decoder - %s\n", SDL_GetError());
+		/* This is not fatal, but we won't have incoming video */
+		ast_log(LOG_WARNING, "Cannot initialize input decoder - %s\n",
+			SDL_GetError());
 	}
 
 	/*
@@ -1054,39 +1086,40 @@
 	env->bmp[0] = env->bmp[1] = NULL;
 
 	if (SDL_Init(SDL_INIT_VIDEO)) {
-		fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
-		return;
+		ast_log(LOG_WARNING, "Could not initialize SDL - %s\n",
+			SDL_GetError());
+		/* again not fatal, just we won't display anything */
+		goto no_sdl;
 	}
 	env->screen = SDL_SetVideoMode(2 * env->w, env->h, 0, 0);
 	if (!env->screen) {
 		ast_log(LOG_ERROR, "SDL: could not set video mode - exiting\n");
-		return;
+		goto no_sdl;
 	}
 	SDL_WM_SetCaption("Asterisk console Video Output", NULL);
-
 	env->bmp[0] = SDL_CreateYUVOverlay(env->w, env->h, fmt, env->screen);
 	env->bmp[1] = SDL_CreateYUVOverlay(env->w, env->h, fmt, env->screen);
-
-	/* create windows ? */
-	env->owner = owner;
-	env->initialized = 1;
-
+	if (env->bmp[0] && env->bmp[1])
+		env->sdl_ok = 1;
+	/* otherwise should release the screen */
+
+no_sdl:
 	/*
 	 * now handle the local video source
 	 */
-        ast_pthread_create_background(&env->vthread, NULL, video_thread, env);
-	/* copy config from parent */
+	ast_pthread_create_background(&env->vthread, NULL, video_thread, env);
+	/* copy video source config from parent */
 	env->out.device = env->videodevice;
 	env->out.w = env->w;
 	env->out.h = env->h;
 	if (env->fps == 0) {
-		ast_log(LOG_WARNING, "fps unset, forcing to 5\n");
-		env->fps = 5;
+		env->fps = 15;
+		ast_log(LOG_WARNING, "fps unset, forcing to %d\n", env->fps);
 	}
 	env->out.fps = env->fps;
 	if (env->bitrate == 0) {
-		ast_log(LOG_WARNING, "bitrate unset, forcing to 65000\n");
 		env->bitrate = 65000;
+		ast_log(LOG_WARNING, "bitrate unset, forcing to %d\n", env->bitrate);
 	}
 	env->out.bitrate = env->bitrate;
 




More information about the asterisk-commits mailing list