blob: 72f46fe22dd27bfb3ef9bda00e75035449622d91 [file] [log] [blame]
Benny Prijonoc45d9512010-12-10 11:04:30 +00001/* $Id$ */
2/*
Nanang Izzuddinfad6f692011-08-19 09:35:25 +00003 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
Benny Prijonoc45d9512010-12-10 11:04:30 +00004 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19#include <pjmedia-videodev/videodev_imp.h>
20#include <pjmedia/errno.h>
21#include <pj/assert.h>
22#include <pj/errno.h>
23#include <pj/file_access.h>
24#include <pj/log.h>
25#include <pj/os.h>
26#include <pj/rand.h>
27
28#if PJMEDIA_VIDEO_DEV_HAS_V4L2
29
30#include <linux/videodev2.h>
31#include <libv4l2.h>
32#include <fcntl.h>
33#include <errno.h>
34#include <sys/mman.h>
35
36#define THIS_FILE "v4l2_dev.c"
37#define DRIVER_NAME "v4l2"
38#define V4L2_MAX_DEVS 4
39#define DEFAULT_WIDTH 640
40#define DEFAULT_HEIGHT 480
41#define DEFAULT_FPS 25
42#define DEFAULT_CLOCK_RATE 90000
43#define INVALID_FD -1
44#define BUFFER_CNT 2
45#define MAX_IOCTL_RETRY 20
46
47
48/* mapping between pjmedia_fmt_id and v4l2 pixel format */
49typedef struct vid4lin_fmt_map
50{
51 pj_uint32_t pjmedia_fmt_id;
52 pj_uint32_t v4l2_fmt_id;
53} vid4lin_fmt_map;
54
55/* I/O type being used */
56enum vid4lin_io_type
57{
58 IO_TYPE_NONE,
59 IO_TYPE_READ,
60 IO_TYPE_MMAP,
61 IO_TYPE_MMAP_USER
62};
63
64/* descriptor for each mmap-ed buffer */
65typedef struct vid4lin_buffer
66{
67 void *start;
68 size_t length;
69} vid4lin_buffer;
70
71/* v4l2 device info */
72typedef struct vid4lin_dev_info
73{
74 pjmedia_vid_dev_info info;
75 char dev_name[32];
76 struct v4l2_capability v4l2_cap;
77} vid4lin_dev_info;
78
79/* v4l2 factory */
80typedef struct vid4lin_factory
81{
82 pjmedia_vid_dev_factory base;
83 pj_pool_t *pool;
Sauw Ming7f7c5bd2011-06-21 09:33:01 +000084 pj_pool_t *dev_pool;
Benny Prijonoc45d9512010-12-10 11:04:30 +000085 pj_pool_factory *pf;
86
87 unsigned dev_count;
88 vid4lin_dev_info *dev_info;
89} vid4lin_factory;
90
91/* Video stream. */
92typedef struct vid4lin_stream
93{
Benny Prijono6c7e95e2011-03-15 11:22:04 +000094 pjmedia_vid_dev_stream base; /**< Base stream */
Sauw Ming5291a2d2011-07-15 07:52:44 +000095 pjmedia_vid_dev_param param; /**< Settings */
Benny Prijonoc45d9512010-12-10 11:04:30 +000096 pj_pool_t *pool; /**< Memory pool. */
97
98 int fd; /**< Video fd. */
99 char name[64]; /**< Name for log */
100 enum vid4lin_io_type io_type; /**< I/O method. */
101 unsigned buf_cnt; /**< MMap buf cnt. */
102 vid4lin_buffer *buffers; /**< MMap buffers. */
103 pj_time_val start_time; /**< Time when started */
104
Sauw Ming5291a2d2011-07-15 07:52:44 +0000105 pjmedia_vid_dev_cb vid_cb; /**< Stream callback */
Benny Prijonoc45d9512010-12-10 11:04:30 +0000106 void *user_data; /**< Application data */
107} vid4lin_stream;
108
109/* Use this to convert between pjmedia_format_id and V4L2 fourcc */
110static vid4lin_fmt_map v4l2_fmt_maps[] =
111{
112 { PJMEDIA_FORMAT_RGB24, V4L2_PIX_FMT_BGR24 },
113 { PJMEDIA_FORMAT_RGBA, V4L2_PIX_FMT_BGR32 },
114 { PJMEDIA_FORMAT_RGB32, V4L2_PIX_FMT_BGR32 },
115 { PJMEDIA_FORMAT_AYUV, V4L2_PIX_FMT_YUV32 },
116 { PJMEDIA_FORMAT_YUY2, V4L2_PIX_FMT_YUYV },
117 { PJMEDIA_FORMAT_UYVY, V4L2_PIX_FMT_UYVY }
118};
119
120/* Prototypes */
121static pj_status_t vid4lin_factory_init(pjmedia_vid_dev_factory *f);
122static pj_status_t vid4lin_factory_destroy(pjmedia_vid_dev_factory *f);
Sauw Ming7f7c5bd2011-06-21 09:33:01 +0000123static pj_status_t vid4lin_factory_refresh(pjmedia_vid_dev_factory *f);
Benny Prijonoc45d9512010-12-10 11:04:30 +0000124static unsigned vid4lin_factory_get_dev_count(pjmedia_vid_dev_factory *f);
125static pj_status_t vid4lin_factory_get_dev_info(pjmedia_vid_dev_factory *f,
126 unsigned index,
127 pjmedia_vid_dev_info *info);
128static pj_status_t vid4lin_factory_default_param(pj_pool_t *pool,
129 pjmedia_vid_dev_factory *f,
130 unsigned index,
Sauw Ming5291a2d2011-07-15 07:52:44 +0000131 pjmedia_vid_dev_param *param);
Benny Prijonoc45d9512010-12-10 11:04:30 +0000132static pj_status_t vid4lin_factory_create_stream(pjmedia_vid_dev_factory *f,
Sauw Ming5291a2d2011-07-15 07:52:44 +0000133 pjmedia_vid_dev_param *prm,
134 const pjmedia_vid_dev_cb *cb,
Benny Prijonoc45d9512010-12-10 11:04:30 +0000135 void *user_data,
Benny Prijono6c7e95e2011-03-15 11:22:04 +0000136 pjmedia_vid_dev_stream **p);
Benny Prijonoc45d9512010-12-10 11:04:30 +0000137
Benny Prijono6c7e95e2011-03-15 11:22:04 +0000138static pj_status_t vid4lin_stream_get_param(pjmedia_vid_dev_stream *strm,
Sauw Ming5291a2d2011-07-15 07:52:44 +0000139 pjmedia_vid_dev_param *param);
Benny Prijono6c7e95e2011-03-15 11:22:04 +0000140static pj_status_t vid4lin_stream_get_cap(pjmedia_vid_dev_stream *strm,
Benny Prijonoc45d9512010-12-10 11:04:30 +0000141 pjmedia_vid_dev_cap cap,
142 void *value);
Benny Prijono6c7e95e2011-03-15 11:22:04 +0000143static pj_status_t vid4lin_stream_set_cap(pjmedia_vid_dev_stream *strm,
Benny Prijonoc45d9512010-12-10 11:04:30 +0000144 pjmedia_vid_dev_cap cap,
145 const void *value);
Benny Prijono6c7e95e2011-03-15 11:22:04 +0000146static pj_status_t vid4lin_stream_get_frame(pjmedia_vid_dev_stream *strm,
Benny Prijonoc45d9512010-12-10 11:04:30 +0000147 pjmedia_frame *frame);
Benny Prijono6c7e95e2011-03-15 11:22:04 +0000148static pj_status_t vid4lin_stream_start(pjmedia_vid_dev_stream *strm);
149static pj_status_t vid4lin_stream_stop(pjmedia_vid_dev_stream *strm);
150static pj_status_t vid4lin_stream_destroy(pjmedia_vid_dev_stream *strm);
Benny Prijonoc45d9512010-12-10 11:04:30 +0000151
152/* Operations */
153static pjmedia_vid_dev_factory_op factory_op =
154{
155 &vid4lin_factory_init,
156 &vid4lin_factory_destroy,
157 &vid4lin_factory_get_dev_count,
158 &vid4lin_factory_get_dev_info,
159 &vid4lin_factory_default_param,
Sauw Ming7f7c5bd2011-06-21 09:33:01 +0000160 &vid4lin_factory_create_stream,
161 &vid4lin_factory_refresh
Benny Prijonoc45d9512010-12-10 11:04:30 +0000162};
163
Benny Prijono6c7e95e2011-03-15 11:22:04 +0000164static pjmedia_vid_dev_stream_op stream_op =
Benny Prijonoc45d9512010-12-10 11:04:30 +0000165{
166 &vid4lin_stream_get_param,
167 &vid4lin_stream_get_cap,
168 &vid4lin_stream_set_cap,
169 &vid4lin_stream_start,
170 &vid4lin_stream_get_frame,
171 NULL,
172 &vid4lin_stream_stop,
173 &vid4lin_stream_destroy
174};
175
176
177/****************************************************************************
178 * Factory operations
179 */
180/*
181 * Factory creation function.
182 */
183pjmedia_vid_dev_factory* pjmedia_v4l2_factory(pj_pool_factory *pf)
184{
185 vid4lin_factory *f;
186 pj_pool_t *pool;
187
188 pool = pj_pool_create(pf, DRIVER_NAME, 512, 512, NULL);
189 f = PJ_POOL_ZALLOC_T(pool, vid4lin_factory);
190 f->pf = pf;
191 f->pool = pool;
192 f->base.op = &factory_op;
193
194 return &f->base;
195}
196
197/* util: ioctl that tries harder. */
198static pj_status_t xioctl(int fh, int request, void *arg)
199{
200 enum { RETRY = MAX_IOCTL_RETRY };
201 int r, c=0;
202
203 do {
204 r = v4l2_ioctl(fh, request, arg);
205 } while (r==-1 && c++<RETRY && ((errno==EINTR) || (errno==EAGAIN)));
206
207 return (r == -1) ? pj_get_os_error() : PJ_SUCCESS;
208}
209
210/* Scan V4L2 devices */
211static pj_status_t v4l2_scan_devs(vid4lin_factory *f)
212{
213 vid4lin_dev_info vdi[V4L2_MAX_DEVS];
214 char dev_name[32];
215 unsigned i, old_count;
216 pj_status_t status;
217
Sauw Ming7f7c5bd2011-06-21 09:33:01 +0000218 if (f->dev_pool) {
219 pj_pool_release(f->dev_pool);
220 f->dev_pool = NULL;
221 }
222
Benny Prijonoc45d9512010-12-10 11:04:30 +0000223 pj_bzero(vdi, sizeof(vdi));
224 old_count = f->dev_count;
225 f->dev_count = 0;
Sauw Ming7f7c5bd2011-06-21 09:33:01 +0000226 f->dev_pool = pj_pool_create(f->pf, DRIVER_NAME, 500, 500, NULL);
Benny Prijonoc45d9512010-12-10 11:04:30 +0000227
228 for (i=0; i<V4L2_MAX_DEVS && f->dev_count < V4L2_MAX_DEVS; ++i) {
229 int fd;
230 vid4lin_dev_info *pdi;
Sauw Ming7f7c5bd2011-06-21 09:33:01 +0000231 pj_pool_t *pool = f->dev_pool;
Benny Prijonoc45d9512010-12-10 11:04:30 +0000232 pj_uint32_t fmt_cap[8];
233 int j, fmt_cnt=0;
234
235 pdi = &vdi[f->dev_count];
236
237 snprintf(dev_name, sizeof(dev_name), "/dev/video%d", i);
238 if (!pj_file_exists(dev_name))
239 continue;
240
241 fd = v4l2_open(dev_name, O_RDWR, 0);
242 if (fd == -1)
243 continue;
244
245 status = xioctl(fd, VIDIOC_QUERYCAP, &pdi->v4l2_cap);
246 if (status != PJ_SUCCESS) {
247 PJ_PERROR(4,(THIS_FILE, status, "Error querying %s", dev_name));
248 v4l2_close(fd);
249 continue;
250 }
251
252 if ((pdi->v4l2_cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0) {
253 v4l2_close(fd);
254 continue;
255 }
256
257 PJ_LOG(5,(THIS_FILE, "Found capture device %s", pdi->v4l2_cap.card));
258 PJ_LOG(5,(THIS_FILE, " Enumerating formats:"));
259 for (j=0; fmt_cnt<PJ_ARRAY_SIZE(fmt_cap); ++j) {
260 struct v4l2_fmtdesc fdesc;
261 unsigned k;
262
263 pj_bzero(&fdesc, sizeof(fdesc));
264 fdesc.index = j;
265 fdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
266
267 status = xioctl(fd, VIDIOC_ENUM_FMT, &fdesc);
268 if (status != PJ_SUCCESS)
269 break;
270
271 for (k=0; k<PJ_ARRAY_SIZE(v4l2_fmt_maps); ++k) {
272 if (v4l2_fmt_maps[k].v4l2_fmt_id == fdesc.pixelformat) {
273 fmt_cap[fmt_cnt++] = v4l2_fmt_maps[k].pjmedia_fmt_id;
274 PJ_LOG(5,(THIS_FILE, " Supported: %s",
275 fdesc.description));
276 break;
277 }
278 }
279 if (k==PJ_ARRAY_SIZE(v4l2_fmt_maps)) {
280 PJ_LOG(5,(THIS_FILE, " Unsupported: %s", fdesc.description));
281 }
282 }
283
284 v4l2_close(fd);
285
286 if (fmt_cnt==0) {
287 PJ_LOG(5,(THIS_FILE, " Found no common format"));
288 continue;
289 }
290
291 strncpy(pdi->dev_name, dev_name, sizeof(pdi->dev_name));
292 pdi->dev_name[sizeof(pdi->dev_name)-1] = '\0';
293 strncpy(pdi->info.name, (char*)pdi->v4l2_cap.card,
294 sizeof(pdi->info.name));
295 pdi->info.name[sizeof(pdi->info.name)-1] = '\0';
296 strncpy(pdi->info.driver, DRIVER_NAME, sizeof(pdi->info.driver));
297 pdi->info.driver[sizeof(pdi->info.driver)-1] = '\0';
298 pdi->info.dir = PJMEDIA_DIR_CAPTURE;
299 pdi->info.has_callback = PJ_FALSE;
300 pdi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT;
301
302 pdi->info.fmt_cnt = fmt_cnt;
Benny Prijonoc45d9512010-12-10 11:04:30 +0000303 for (j=0; j<fmt_cnt; ++j) {
304 pjmedia_format_init_video(&pdi->info.fmt[j],
305 fmt_cap[j],
306 DEFAULT_WIDTH,
307 DEFAULT_HEIGHT,
308 DEFAULT_FPS, 1);
309 }
310 if (j < fmt_cnt)
311 continue;
312
313 f->dev_count++;
314 }
315
316 if (f->dev_count == 0)
317 return PJ_SUCCESS;
318
319 if (f->dev_count > old_count || f->dev_info == NULL) {
320 f->dev_info = (vid4lin_dev_info*)
Sauw Ming7f7c5bd2011-06-21 09:33:01 +0000321 pj_pool_calloc(f->dev_pool,
Benny Prijonoc45d9512010-12-10 11:04:30 +0000322 f->dev_count,
323 sizeof(vid4lin_dev_info));
324 }
325 pj_memcpy(f->dev_info, vdi, f->dev_count * sizeof(vid4lin_dev_info));
326
327 return PJ_SUCCESS;
328}
329
330
331/* API: init factory */
332static pj_status_t vid4lin_factory_init(pjmedia_vid_dev_factory *f)
333{
Sauw Ming7f7c5bd2011-06-21 09:33:01 +0000334 return vid4lin_factory_refresh(f);
Benny Prijonoc45d9512010-12-10 11:04:30 +0000335}
336
337/* API: destroy factory */
338static pj_status_t vid4lin_factory_destroy(pjmedia_vid_dev_factory *f)
339{
340 vid4lin_factory *cf = (vid4lin_factory*)f;
341 pj_pool_t *pool = cf->pool;
342
Sauw Ming7f7c5bd2011-06-21 09:33:01 +0000343 if (cf->dev_pool)
344 pj_pool_release(cf->dev_pool);
Benny Prijonoc45d9512010-12-10 11:04:30 +0000345 if (cf->pool) {
346 cf->pool = NULL;
347 pj_pool_release(pool);
348 }
349
350 return PJ_SUCCESS;
351}
352
Sauw Ming7f7c5bd2011-06-21 09:33:01 +0000353/* API: refresh the list of devices */
354static pj_status_t vid4lin_factory_refresh(pjmedia_vid_dev_factory *f)
355{
356 vid4lin_factory *cf = (vid4lin_factory*)f;
357 pj_status_t status;
358
359 status = v4l2_scan_devs(cf);
360 if (status != PJ_SUCCESS)
361 return status;
362
363 PJ_LOG(4, (THIS_FILE, "Video4Linux2 has %d devices",
364 cf->dev_count));
365
366 return PJ_SUCCESS;
367}
368
Benny Prijonoc45d9512010-12-10 11:04:30 +0000369/* API: get number of devices */
370static unsigned vid4lin_factory_get_dev_count(pjmedia_vid_dev_factory *f)
371{
372 vid4lin_factory *cf = (vid4lin_factory*)f;
373 return cf->dev_count;
374}
375
376/* API: get device info */
377static pj_status_t vid4lin_factory_get_dev_info(pjmedia_vid_dev_factory *f,
378 unsigned index,
379 pjmedia_vid_dev_info *info)
380{
381 vid4lin_factory *cf = (vid4lin_factory*)f;
382
383 PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EVID_INVDEV);
384
385 pj_memcpy(info, &cf->dev_info[index].info, sizeof(*info));
386
387 return PJ_SUCCESS;
388}
389
390/* API: create default device parameter */
391static pj_status_t vid4lin_factory_default_param(pj_pool_t *pool,
392 pjmedia_vid_dev_factory *f,
393 unsigned index,
Sauw Ming5291a2d2011-07-15 07:52:44 +0000394 pjmedia_vid_dev_param *param)
Benny Prijonoc45d9512010-12-10 11:04:30 +0000395{
396 vid4lin_factory *cf = (vid4lin_factory*)f;
397
398 PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EVID_INVDEV);
399
400 pj_bzero(param, sizeof(*param));
401 param->dir = PJMEDIA_DIR_CAPTURE;
402 param->cap_id = index;
403 param->rend_id = PJMEDIA_VID_INVALID_DEV;
404 param->flags = PJMEDIA_VID_DEV_CAP_FORMAT;
405 param->clock_rate = DEFAULT_CLOCK_RATE;
Benny Prijonoc45d9512010-12-10 11:04:30 +0000406 pjmedia_format_copy(&param->fmt, &cf->dev_info[index].info.fmt[0]);
407
408 return PJ_SUCCESS;
409}
410
411static vid4lin_fmt_map* get_v4l2_format_info(pjmedia_format_id id)
412{
413 unsigned i;
414
415 for (i = 0; i < PJ_ARRAY_SIZE(v4l2_fmt_maps); i++) {
416 if (v4l2_fmt_maps[i].pjmedia_fmt_id == id)
417 return &v4l2_fmt_maps[i];
418 }
419
420 return NULL;
421}
422
423/* util: setup format */
424static pj_status_t vid4lin_stream_init_fmt(vid4lin_stream *stream,
Sauw Ming5291a2d2011-07-15 07:52:44 +0000425 const pjmedia_vid_dev_param *param,
Benny Prijonoc45d9512010-12-10 11:04:30 +0000426 pj_uint32_t pix_fmt)
427{
Benny Prijonoe9f70d82011-03-25 08:38:26 +0000428 pjmedia_video_format_detail *vfd;
Benny Prijonoc45d9512010-12-10 11:04:30 +0000429 struct v4l2_format v4l2_fmt;
430 pj_status_t status;
431
432 vfd = pjmedia_format_get_video_format_detail(&param->fmt, PJ_TRUE);
433 if (vfd == NULL)
434 return PJMEDIA_EVID_BADFORMAT;
435
436 pj_bzero(&v4l2_fmt, sizeof(v4l2_fmt));
437 v4l2_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
438 v4l2_fmt.fmt.pix.width = vfd->size.w;
439 v4l2_fmt.fmt.pix.height = vfd->size.h;
440 v4l2_fmt.fmt.pix.pixelformat = pix_fmt;
441 v4l2_fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
442 status = xioctl(stream->fd, VIDIOC_S_FMT, &v4l2_fmt);
443 if (status != PJ_SUCCESS)
444 return status;
445
446 if (v4l2_fmt.fmt.pix.pixelformat != pix_fmt) {
447 status = PJMEDIA_EVID_BADFORMAT;
448 return status;
449 }
450
451 if ((v4l2_fmt.fmt.pix.width != vfd->size.w) ||
452 (v4l2_fmt.fmt.pix.height != vfd->size.h))
453 {
Benny Prijonoe9f70d82011-03-25 08:38:26 +0000454 /* Size has changed */
455 vfd->size.w = v4l2_fmt.fmt.pix.width;
456 vfd->size.h = v4l2_fmt.fmt.pix.height;
Benny Prijonoc45d9512010-12-10 11:04:30 +0000457 }
458
459 return PJ_SUCCESS;
460}
461
462/* Util: initiate v4l2 streaming via mmap */
463static pj_status_t vid4lin_stream_init_streaming(vid4lin_stream *stream)
464{
465 struct v4l2_requestbuffers req;
466 unsigned i;
467 pj_status_t status;
468
469 pj_bzero(&req, sizeof(req));
470 req.count = BUFFER_CNT;
471 req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
472 req.memory = V4L2_MEMORY_MMAP;
473 status = xioctl(stream->fd, VIDIOC_REQBUFS, &req);
474 if (status != PJ_SUCCESS)
475 return status;
476
477 stream->buffers = pj_pool_calloc(stream->pool, req.count,
478 sizeof(*stream->buffers));
479 stream->buf_cnt = 0;
480
481 for (i = 0; i < req.count; ++i) {
482 struct v4l2_buffer buf;
483
484 pj_bzero(&buf, sizeof(buf));
485
486 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
487 buf.memory = V4L2_MEMORY_MMAP;
488 buf.index = i;
489
490 status = xioctl(stream->fd, VIDIOC_QUERYBUF, &buf);
491 if (status != PJ_SUCCESS)
492 goto on_error;
493
494 stream->buffers[i].length = buf.length;
495 stream->buffers[i].start = v4l2_mmap(NULL, buf.length,
496 PROT_READ | PROT_WRITE,
497 MAP_SHARED, stream->fd,
498 buf.m.offset);
499
500 if (MAP_FAILED == stream->buffers[i].start) {
501 status = pj_get_os_error();
502 goto on_error;
503 }
504
505 stream->buf_cnt++;
506 }
507
508 PJ_LOG(5,(THIS_FILE, " mmap streaming initialized"));
509
510 stream->io_type = IO_TYPE_MMAP;
511 return PJ_SUCCESS;
512
513on_error:
514 return status;
515}
516
517/* init streaming with user pointer */
518static pj_status_t vid4lin_stream_init_streaming_user(vid4lin_stream *stream)
519{
520 return PJ_ENOTSUP;
521}
522
523/* init streaming with read() */
524static pj_status_t vid4lin_stream_init_read_write(vid4lin_stream *stream)
525{
526 return PJ_ENOTSUP;
527}
528
529/* API: create stream */
530static pj_status_t vid4lin_factory_create_stream(pjmedia_vid_dev_factory *f,
Sauw Ming5291a2d2011-07-15 07:52:44 +0000531 pjmedia_vid_dev_param *param,
532 const pjmedia_vid_dev_cb *cb,
Benny Prijono6c7e95e2011-03-15 11:22:04 +0000533 void *user_data,
534 pjmedia_vid_dev_stream **p_vid_strm)
Benny Prijonoc45d9512010-12-10 11:04:30 +0000535{
536 vid4lin_factory *cf = (vid4lin_factory*)f;
537 pj_pool_t *pool;
538 vid4lin_stream *stream;
539 vid4lin_dev_info *vdi;
540 const vid4lin_fmt_map *fmt_map;
541 const pjmedia_video_format_info *fmt_info;
Benny Prijonoe9f70d82011-03-25 08:38:26 +0000542 pjmedia_video_format_detail *vfd;
Benny Prijonoc45d9512010-12-10 11:04:30 +0000543 pj_status_t status = PJ_SUCCESS;
544
Benny Prijonoe9f70d82011-03-25 08:38:26 +0000545
Benny Prijonoc45d9512010-12-10 11:04:30 +0000546 PJ_ASSERT_RETURN(f && param && p_vid_strm, PJ_EINVAL);
547 PJ_ASSERT_RETURN(param->fmt.type == PJMEDIA_TYPE_VIDEO &&
Sauw Ming83db7d62011-06-09 04:08:47 +0000548 param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO &&
549 param->dir == PJMEDIA_DIR_CAPTURE,
Benny Prijonoc45d9512010-12-10 11:04:30 +0000550 PJ_EINVAL);
551 PJ_ASSERT_RETURN(param->cap_id >= 0 && param->cap_id < cf->dev_count,
552 PJMEDIA_EVID_INVDEV);
553
554 fmt_info = pjmedia_get_video_format_info(NULL, param->fmt.id);
555 if (!fmt_info || (fmt_map=get_v4l2_format_info(param->fmt.id))==NULL)
556 return PJMEDIA_EVID_BADFORMAT;
557
558 vdi = &cf->dev_info[param->cap_id];
Benny Prijonoe9f70d82011-03-25 08:38:26 +0000559 vfd = pjmedia_format_get_video_format_detail(&param->fmt, PJ_TRUE);
Benny Prijonoc45d9512010-12-10 11:04:30 +0000560
561 /* Create and Initialize stream descriptor */
562 pool = pj_pool_create(cf->pf, vdi->info.name, 512, 512, NULL);
563 PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
564
565 stream = PJ_POOL_ZALLOC_T(pool, vid4lin_stream);
566 pj_memcpy(&stream->param, param, sizeof(*param));
567 stream->pool = pool;
568 pj_memcpy(&stream->vid_cb, cb, sizeof(*cb));
569 strncpy(stream->name, vdi->info.name, sizeof(stream->name));
570 stream->name[sizeof(stream->name)-1] = '\0';
571 stream->user_data = user_data;
572 stream->fd = INVALID_FD;
Benny Prijonofcf5db32011-07-14 04:56:08 +0000573 pjmedia_event_publisher_init(&stream->base.epub, PJMEDIA_SIG_VID_DEV_V4L2);
Benny Prijonoc45d9512010-12-10 11:04:30 +0000574
Benny Prijonoc45d9512010-12-10 11:04:30 +0000575 stream->fd = v4l2_open(vdi->dev_name, O_RDWR | O_NONBLOCK, 0);
576 if (stream->fd < 0)
577 goto on_error;
578
579 status = vid4lin_stream_init_fmt(stream, param, fmt_map->v4l2_fmt_id);
580 if (status != PJ_SUCCESS)
581 goto on_error;
582
583 if (vdi->v4l2_cap.capabilities & V4L2_CAP_STREAMING)
584 status = vid4lin_stream_init_streaming(stream);
585
586 if (status!=PJ_SUCCESS && vdi->v4l2_cap.capabilities & V4L2_CAP_STREAMING)
587 status = vid4lin_stream_init_streaming_user(stream);
588
589 if (status!=PJ_SUCCESS && vdi->v4l2_cap.capabilities & V4L2_CAP_READWRITE)
590 status = vid4lin_stream_init_read_write(stream);
591
592 if (status != PJ_SUCCESS) {
593 PJ_LOG(1,(THIS_FILE, "Error: unable to initiate I/O on %s",
594 stream->name));
595 goto on_error;
596 }
597
598 /* Done */
599 stream->base.op = &stream_op;
600 *p_vid_strm = &stream->base;
601
602 return PJ_SUCCESS;
603
604on_error:
605 if (status == PJ_SUCCESS)
606 status = PJ_RETURN_OS_ERROR(errno);
607
608 vid4lin_stream_destroy(&stream->base);
609 return status;
610}
611
612/* API: Get stream info. */
Benny Prijono6c7e95e2011-03-15 11:22:04 +0000613static pj_status_t vid4lin_stream_get_param(pjmedia_vid_dev_stream *s,
Sauw Ming5291a2d2011-07-15 07:52:44 +0000614 pjmedia_vid_dev_param *pi)
Benny Prijonoc45d9512010-12-10 11:04:30 +0000615{
616 vid4lin_stream *strm = (vid4lin_stream*)s;
617
618 PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
619
620 pj_memcpy(pi, &strm->param, sizeof(*pi));
621
622 return PJ_SUCCESS;
623}
624
625/* API: get capability */
Benny Prijono6c7e95e2011-03-15 11:22:04 +0000626static pj_status_t vid4lin_stream_get_cap(pjmedia_vid_dev_stream *s,
627 pjmedia_vid_dev_cap cap,
628 void *pval)
Benny Prijonoc45d9512010-12-10 11:04:30 +0000629{
630 vid4lin_stream *strm = (vid4lin_stream*)s;
631
632 PJ_UNUSED_ARG(strm);
633
634 PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
635
636 if (cap==PJMEDIA_VID_DEV_CAP_INPUT_SCALE)
637 {
638 return PJMEDIA_EVID_INVCAP;
639// return PJ_SUCCESS;
640 } else {
641 return PJMEDIA_EVID_INVCAP;
642 }
643}
644
645/* API: set capability */
Benny Prijono6c7e95e2011-03-15 11:22:04 +0000646static pj_status_t vid4lin_stream_set_cap(pjmedia_vid_dev_stream *s,
647 pjmedia_vid_dev_cap cap,
648 const void *pval)
Benny Prijonoc45d9512010-12-10 11:04:30 +0000649{
650 vid4lin_stream *strm = (vid4lin_stream*)s;
651
652
653 PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
654
655 /*
656 if (cap==PJMEDIA_VID_DEV_CAP_INPUT_SCALE)
657 {
658 return PJ_SUCCESS;
659 }
660 */
661 PJ_UNUSED_ARG(strm);
662 PJ_UNUSED_ARG(cap);
663 PJ_UNUSED_ARG(pval);
664
665 return PJMEDIA_EVID_INVCAP;
666}
667
668/* get frame from mmap */
669static pj_status_t vid4lin_stream_get_frame_mmap(vid4lin_stream *stream,
670 pjmedia_frame *frame)
671{
672 struct v4l2_buffer buf;
673 pj_time_val time;
674 pj_status_t status = PJ_SUCCESS;
675
676 pj_bzero(&buf, sizeof(buf));
677 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
678 buf.memory = V4L2_MEMORY_MMAP;
679 status = xioctl(stream->fd, VIDIOC_DQBUF, &buf);
680 if (status != PJ_SUCCESS)
681 return status;
682
683 if (frame->size < buf.bytesused) {
684 /* supplied buffer is too small */
685 pj_assert(!"frame buffer is too small for v4l2");
686 status = PJ_ETOOSMALL;
687 goto on_return;
688 }
689
690 time.sec = buf.timestamp.tv_sec;
691 time.msec = buf.timestamp.tv_usec / 1000;
692 PJ_TIME_VAL_SUB(time, stream->start_time);
693
694 frame->type = PJMEDIA_FRAME_TYPE_VIDEO;
695 frame->size = buf.bytesused;
Benny Prijono349037b2011-03-17 11:25:19 +0000696 frame->timestamp.u64 = PJ_UINT64(1) * PJ_TIME_VAL_MSEC(time) *
697 stream->param.clock_rate / PJ_UINT64(1000);
Benny Prijonoc45d9512010-12-10 11:04:30 +0000698 pj_memcpy(frame->buf, stream->buffers[buf.index].start, buf.bytesused);
699
700on_return:
701 pj_bzero(&buf, sizeof(buf));
702 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
703 buf.memory = V4L2_MEMORY_MMAP;
704 xioctl(stream->fd, VIDIOC_QBUF, &buf);
705
706 return status;
707}
708
709/* API: Get frame from stream */
Benny Prijono6c7e95e2011-03-15 11:22:04 +0000710static pj_status_t vid4lin_stream_get_frame(pjmedia_vid_dev_stream *strm,
Benny Prijonoc45d9512010-12-10 11:04:30 +0000711 pjmedia_frame *frame)
712{
713 vid4lin_stream *stream = (vid4lin_stream*)strm;
714
715 if (stream->io_type == IO_TYPE_MMAP)
716 return vid4lin_stream_get_frame_mmap(stream, frame);
717 else {
718 pj_assert(!"Unsupported i/o type");
719 return PJ_EINVALIDOP;
720 }
721}
722
723/* API: Start stream. */
Benny Prijono6c7e95e2011-03-15 11:22:04 +0000724static pj_status_t vid4lin_stream_start(pjmedia_vid_dev_stream *strm)
Benny Prijonoc45d9512010-12-10 11:04:30 +0000725{
726 vid4lin_stream *stream = (vid4lin_stream*)strm;
727 struct v4l2_buffer buf;
728 enum v4l2_buf_type type;
729 unsigned i;
730 pj_status_t status;
731
732 PJ_ASSERT_RETURN(stream->fd != -1, PJ_EINVALIDOP);
733
734 PJ_LOG(4, (THIS_FILE, "Starting v4l2 video stream %s", stream->name));
735
736 pj_gettimeofday(&stream->start_time);
737
738 for (i = 0; i < stream->buf_cnt; ++i) {
739 pj_bzero(&buf, sizeof(buf));
740 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
741 buf.memory = V4L2_MEMORY_MMAP;
742 buf.index = i;
743 status = xioctl(stream->fd, VIDIOC_QBUF, &buf);
744 if (status != PJ_SUCCESS)
745 goto on_error;
746 }
747 type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
748
749 status = xioctl(stream->fd, VIDIOC_STREAMON, &type);
750 if (status != PJ_SUCCESS)
751 goto on_error;
752
753 return PJ_SUCCESS;
754
755on_error:
756 if (i > 0) {
757 /* Dequeue already enqueued buffers. Can we do this while streaming
758 * is not started?
759 */
760 unsigned n = i;
761 for (i=0; i<n; ++i) {
762 pj_bzero(&buf, sizeof(buf));
763 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
764 buf.memory = V4L2_MEMORY_MMAP;
765 xioctl(stream->fd, VIDIOC_DQBUF, &buf);
766 }
767 }
768 return status;
769}
770
771/* API: Stop stream. */
Benny Prijono6c7e95e2011-03-15 11:22:04 +0000772static pj_status_t vid4lin_stream_stop(pjmedia_vid_dev_stream *strm)
Benny Prijonoc45d9512010-12-10 11:04:30 +0000773{
774 vid4lin_stream *stream = (vid4lin_stream*)strm;
775 enum v4l2_buf_type type;
776 pj_status_t status;
777
778 if (stream->fd < 0)
779 return PJ_SUCCESS;
780
781 PJ_LOG(4, (THIS_FILE, "Stopping v4l2 video stream %s", stream->name));
782
783 type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
784 status = xioctl(stream->fd, VIDIOC_STREAMOFF, &type);
785 if (status != PJ_SUCCESS)
786 return status;
787
788 return PJ_SUCCESS;
789}
790
791
792/* API: Destroy stream. */
Benny Prijono6c7e95e2011-03-15 11:22:04 +0000793static pj_status_t vid4lin_stream_destroy(pjmedia_vid_dev_stream *strm)
Benny Prijonoc45d9512010-12-10 11:04:30 +0000794{
795 vid4lin_stream *stream = (vid4lin_stream*)strm;
796 unsigned i;
797
798 PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
799
800 vid4lin_stream_stop(strm);
801
802 PJ_LOG(4, (THIS_FILE, "Destroying v4l2 video stream %s", stream->name));
803
804 for (i=0; i<stream->buf_cnt; ++i) {
805 if (stream->buffers[i].start != MAP_FAILED) {
806 v4l2_munmap(stream->buffers[i].start, stream->buffers[i].length);
807 stream->buffers[i].start = MAP_FAILED;
808 }
809 }
810
811 if (stream->fd >= 0) {
812 v4l2_close(stream->fd);
813 stream->fd = -1;
814 }
815 pj_pool_release(stream->pool);
816
817 return PJ_SUCCESS;
818}
819
820#endif /* PJMEDIA_VIDEO_DEV_HAS_V4L2 */