Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

feature: idle frame generation when no input #12

Closed
wants to merge 30 commits into from

2 participants

@an146

Hi, I've implemented the subj functionality (repeating last received frame or sending a placeholder picture when writer process dies/doesn't send frames in time, more explanation in README).
It has involved some changes in existing code; what worries me is read()/write() implementations, I have changed them appropriately (they might work), but haven't found a way to test them yet; all testing was done with gstreamer's v4l2src/sink using q/dqbuf(). Can you provide me with a hint on how to do it using some existing software?
Anyway, I'll continue playing with it, and probably there are more changes to come, so any your thoughts on code/cooperation will be helpful

@umlaeute
Owner

thanks, i'll have a look at it (and will have to learn how to deal with "merge pull requests" ion github...)

@umlaeute
Owner

actually, what i don't like in your patch is that "idle_fps" is a integer.

shouldn't it be like that:

  • allow for arbitrary values, in a v4l2 (nominator/denominator) fashion?
  • if the producer sets the fps of the stream, should that be taken?

i think this could also solve issue#11

@umlaeute
Owner

so i changed a number of things

  • renamed "idle_fps" to "fps", and linking it to the devices frameduration settings
  • allow to set/get the "fps" via sysfs
  • renamed "placeholder_delay" to "timeout"

what i'm still unsure about is the way how the placeholder frame is injected.
i think using the current way is a bit clumsy (writing to an extra non-announce mmaped buffer). maybe a better way would be to use sysfs for that (have to read the docs about whether it is actually "allowed" to use sysfs for that)

@an146

it's impossible to do it with sysfs, the maximum size of files read/written through it is fixed to PAGE_SIZE (e.g. 4096 bytes)
and I think there's no real need for sustaining stream fps at idle times, as long as we set correct timestamps and some existing software chokes at decreased real fps. Also, if no frame arrives in expected 1/fps seconds time, how do we know that it won't be received a millisecond later, and should we really produce a copy of last received frame? Current code actually produces extra frame copies even with local frame input when idle_fps is set equal to stream fps, imagine input stream being read from some unstable network source. I think default of, like, idle_fps <= stream_fps / 2 is reasonable; and if user really wants to override it, he can, through sysfs parameter

@umlaeute
Owner

thanks for the clarification about sysfs.

however, i cannot really follow your explanation about real fps vs idle_fps.
i cannot see any harm done in having idle_fps==fps.
in a causal system, we will never know whether a real frame will arrive 1ms in the future.
therefore we have a timeout.

i think the problem you describe can only happen if you set the timeout to "0".

@umlaeute umlaeute closed this
@umlaeute umlaeute reopened this
@umlaeute
Owner

sorry, i incidentally clicked on "close&comment" rather than "comment"

@umlaeute
Owner

hmm, i'm getting more of your picture.
i included your patch (heavily modified by me) into the "experimental" branch of v4l2loopback.

i re-added the "idle_fps" attribute (though allowed fractions rather than integers), which - if not set (or rather, if the denominator==0) falls back to fps/2.

please check whether this works for you
(esp. i haven't done any tests since i merged with feature/fix-format)

@an146 an146 closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 10, 2012
  1. @an146

    added vim modeline

    an146 authored
  2. @an146
Commits on Feb 11, 2012
  1. @an146

    idle_frame_timer

    an146 authored
  2. @an146

    timer deletion at shutting down

    an146 authored
    also refactored v4l2_loopback_free() out
  3. @an146
  4. @an146

    added Makefile dependency

    an146 authored
  5. @an146
  6. @an146

    25 idle fps hardcoded

    an146 authored
  7. @an146

    idle_fps parameter in sysfs

    an146 authored
  8. @an146
  9. @an146

    moved functions

    an146 authored
  10. @an146
Commits on Feb 12, 2012
  1. @an146

    placeholder_frame

    an146 authored
  2. @an146

    DEFAULT_IDLE_FPS=10

    an146 authored
  3. @an146
  4. @an146

    v4l2loopback_io sketch

    an146 authored
  5. @an146
  6. @an146

    mmapping works

    an146 authored
  7. @an146

    setting placeholder image works

    an146 authored
Commits on Feb 13, 2012
  1. @an146

    .gitignore

    an146 authored
  2. @an146
  3. @an146

    Makefile fix

    an146 authored
  4. @an146

    fixed idle_fps=0 crash

    an146 authored
  5. @an146

    correct placeholder frame index

    an146 authored
  6. @an146

    another dependencies fix

    an146 authored
  7. @an146

    added placeholder_delay attribute

    an146 authored
    also little idle_fps behaviour fix
  8. @an146
  9. @an146
Commits on Feb 15, 2012
  1. @an146

    added some copylefts

    an146 authored
  2. @an146

    installing control scripts

    an146 authored
This page is out of date. Refresh to see the latest.
View
9 .gitignore
@@ -0,0 +1,9 @@
+.tmp_versions/
+*.cmd
+Module.symvers
+modules.order
+v4l2loopback.ko
+v4l2loopback.mod.c
+v4l2loopback.mod.o
+v4l2loopback.o
+v4l2loopback_io
View
17 Makefile
@@ -6,21 +6,32 @@ PWD := $(shell pwd)
obj-m := v4l2loopback.o
+PREFIX = "/usr/bin"
MODULE_OPTIONS = devices=2
-all: v4l2loopback
+all: v4l2loopback.ko v4l2loopback_io
+
v4l2loopback:
+ @touch $@
+
+v4l2loopback.ko: v4l2loopback.c Makefile
@echo "Building v4l2-loopback driver..."
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules
-install:
+
+v4l2loopback_io: v4l2loopback_io.c Makefile
+ $(CC) -o $@ $<
+
+install: v4l2loopback v4l2loopback_io
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules_install
depmod -ae
+ cp $^ $(PREFIX)
+
clean:
rm -f *~
rm -f Module.symvers Module.markers modules.order
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) clean
-modprobe: v4l2loopback
+modprobe: v4l2loopback.ko
chmod a+r v4l2loopback.ko
sudo modprobe videodev
-sudo rmmod $<
View
14 README
@@ -74,6 +74,20 @@ you can set and/or query some per-device attributes via sysfs, in a human
readable format. see
/sys/devices/virtual/video4linux/video*/
+--- IDLE FRAME GENERATION ---
+when the writer process dies or doesn't feed enough frames, the module will
+continue producing video output on its own, in order to sustain FPS specified
+in 'idle_fps' sysfs attribute. It will be sending duplicates of last received
+frame; if 'placeholder_delay' msecs pass without any new input seen,
+contents of the placeholder buffer will be sent. By default it is zero-filled;
+you can override it with a custom picture using command like:
+# v4l2loopback set-placeholder service_unavailable.png [/dev/videoX]
+it relies on GStreamer for format conversion.
+
+this feature can be disabled by setting 'idle_fps' to 0.
+switching to placeholder can be disabled by setting 'placeholder_delay' attr
+to -1.
+
--- KERNELs ---
the original module has been developed for linux-2.6.28;
i don't have a system with such an old kernel anymore, so i don't know whether
View
65 v4l2loopback
@@ -0,0 +1,65 @@
+#!/bin/sh
+
+usage() {
+ echo "v4l2loopback control script"
+ echo "---------------------------"
+ echo
+ echo "Usage: $0 set-placeholder image [device]"
+ exit 1
+}
+
+die() {
+ echo $1
+ exit 1
+}
+
+set_placeholder() {
+ GSTL=`which gst-launch-0.10`
+ [ -x "$GSTL" ] || die "gst-launch executable not found"
+
+ LOOPBACK_IO="`dirname $(which $0)`/v4l2loopback_io"
+ [ -x "$LOOPBACK_IO" ] || die "v4l2loopback_io executable not found"
+
+ image="$1"
+ device="$2"
+ [ -r "$image" ] || usage
+ [ -n "$device" ] || device="/dev/video0"
+ absimage=`readlink -f "$image"`
+
+ echo "Setting placeholder image of $device to $image"
+ caps=`$GSTL -v v4l2src num-buffers=1 ! fakesink | grep 'src: caps = video/'`
+ [ "$?" = "0" ] || die "can't get device caps; does it have writer/readers running?"
+
+ mime=`echo $caps | sed 's!.*caps = \([A-Za-z0-9/-]*\).*!\1!'`
+ [ -n "$mime" ] || die "can't get mime type"
+ fourcc=`echo $caps | sed 's!.*format=(fourcc)\([A-Za-z0-9]*\).*!\1!'`
+ [ -n "$fourcc" ] || die "can't get fourcc"
+ width=`echo $caps | sed 's!.*width=(int)\([0-9]*\).*!\1!'`
+ [ -n "$width" ] || die "can't get width"
+ height=`echo $caps | sed 's!.*height=(int)\([0-9]*\).*!\1!'`
+ [ -n "$height" ] || die "can't get height"
+ echo "Device caps:"
+ echo "mime: $mime"
+ echo "fourcc: $fourcc"
+ echo "width: $width"
+ echo "height: $height"
+ caps="$mime, format=(fourcc)$fourcc, width=(int)$width, height=(int)$height"
+ frame_file=`mktemp`
+
+ $GSTL uridecodebin uri="file://$absimage" ! ffmpegcolorspace ! videoscale ! "$caps" ! filesink location="$frame_file"
+ [ "$?" = "0" ] || die "image conversion failed"
+
+ cat "$frame_file" | $LOOPBACK_IO -w placeholder "$device"
+ [ "$?" = "0" ] || die "v4l2loopback_io failed"
+ echo "Placeholder set!"
+}
+
+command="$1"
+shift
+case "$command" in
+ "set-placeholder")
+ set_placeholder $@
+ ;;
+ *)
+ usage
+esac
View
326 v4l2loopback.c
@@ -4,6 +4,7 @@
* Copyright (C) 2005-2009 Vasily Levin (vasaka@gmail.com)
* Copyright (C) 2010-2011 IOhannes m zmoelnig (zmoelnig@iem.at)
* Copyright (C) 2011 Stefan Diewald (stefan.diewald@mytum.de)
+ * Copyright (C) 2012 Anton Novikov (random.plant@gmail.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,6 +15,7 @@
#include <linux/version.h>
#include <linux/vmalloc.h>
#include <linux/mm.h>
+#include <linux/mutex.h>
#include <linux/time.h>
#include <linux/module.h>
#include <linux/videodev2.h>
@@ -54,8 +56,10 @@ MODULE_LICENSE("GPL");
printk(KERN_INFO "v4l2-loopback[" STRINGIFY2(__LINE__)"]: " fmt, ##args); \
} } while (0)
+#define MS_TO_NS(ms) (ms * 1000000LL)
/* module constants */
+#define PLACEHOLDER_FRAME 1
/* module parameters */
static int debug = 0;
@@ -80,6 +84,14 @@ static int max_openers = 10;
module_param(max_openers, int, S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(max_openers, "how many users can open loopback device");
+static int idle_fps = 10;
+module_param(idle_fps, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(idle_fps, "idle frames per second");
+
+static int placeholder_delay = 3000;
+module_param(placeholder_delay, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(placeholder_delay, "the delay before starting producing placeholder frames, in ms");
+
#define MAX_DEVICES 8
static int devices = -1;
@@ -132,8 +144,15 @@ struct v4l2_loopback_device {
int used_buffers; /* number of the actually used buffers */
int max_openers; /* how many times can this device be opened */
+ struct mutex write_mutex;
+ struct timeval last_write_timestamp;
int write_position; /* number of last written frame + 1 */
long buffer_size;
+ int idle_fps;
+ int placeholder_delay;
+ u8 *placeholder_frame;
+ int idle_frame_needed;
+ struct timer_list idle_frame_timer;
/* sync stuff */
atomic_t open_count;
@@ -375,12 +394,86 @@ static ssize_t attr_store_maxopeners(struct device* cd,
return len;
}
+static DEVICE_ATTR(max_openers, S_IRUGO | S_IWUSR, attr_show_maxopeners, attr_store_maxopeners);
+static ssize_t attr_show_idlefps(struct device *cd,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct v4l2_loopback_device *dev = v4l2loopback_cd2dev(cd);
+ return sprintf(buf, "%d\n", dev->idle_fps);
+}
+static ssize_t attr_store_idlefps(struct device* cd,
+ struct device_attribute *attr,
+ const char* buf, size_t len)
+{
+ struct v4l2_loopback_device *dev = NULL;
+ unsigned long curr=0;
-static DEVICE_ATTR(max_openers, S_IRUGO | S_IWUSR, attr_show_maxopeners, attr_store_maxopeners);
+ if (strict_strtoul(buf, 0, &curr))
+ return -EINVAL;
+
+ dev = v4l2loopback_cd2dev(cd);
+
+ if (dev->idle_fps == curr)
+ return len;
+
+ if (curr > 1000) {
+ /* something insane */
+ return -EINVAL;
+ }
+
+ dev->idle_fps = (int)curr;
+
+ return len;
+}
+static DEVICE_ATTR(idle_fps, S_IRUGO | S_IWUSR, attr_show_idlefps, attr_store_idlefps);
+
+static ssize_t attr_show_placeholderdelay(struct device *cd,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct v4l2_loopback_device *dev = v4l2loopback_cd2dev(cd);
+ return sprintf(buf, "%d\n", dev->placeholder_delay);
+}
+static ssize_t attr_store_placeholderdelay(struct device* cd,
+ struct device_attribute *attr,
+ const char* buf, size_t len)
+{
+ struct v4l2_loopback_device *dev = NULL;
+ unsigned long curr=0;
+
+ if (strict_strtol(buf, 0, &curr))
+ return -EINVAL;
+
+ dev = v4l2loopback_cd2dev(cd);
+ if (dev->placeholder_delay == curr)
+ return len;
+ dev->placeholder_delay = (int)curr;
+ return len;
+}
+static DEVICE_ATTR(placeholder_delay, S_IRUGO | S_IWUSR,
+ attr_show_placeholderdelay, attr_store_placeholderdelay);
+
+static ssize_t attr_show_maxbuffers(struct device *cd,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct v4l2_loopback_device *dev = v4l2loopback_cd2dev(cd);
+ return sprintf(buf, "%d\n", dev->buffers_number);
+}
+static DEVICE_ATTR(max_buffers, S_IRUGO, attr_show_maxbuffers, NULL);
+static ssize_t attr_show_buffersize(struct device *cd,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct v4l2_loopback_device *dev = v4l2loopback_cd2dev(cd);
+ return sprintf(buf, "%ld\n", dev->buffer_size);
+}
+static DEVICE_ATTR(buffer_size, S_IRUGO, attr_show_buffersize, NULL);
static void v4l2loopback_remove_sysfs(struct video_device *vdev)
@@ -391,6 +484,10 @@ static void v4l2loopback_remove_sysfs(struct video_device *vdev)
V4L2_SYSFS_DESTROY(fourcc);
V4L2_SYSFS_DESTROY(buffers);
V4L2_SYSFS_DESTROY(max_openers);
+ V4L2_SYSFS_DESTROY(idle_fps);
+ V4L2_SYSFS_DESTROY(placeholder_delay);
+ V4L2_SYSFS_DESTROY(max_buffers);
+ V4L2_SYSFS_DESTROY(buffer_size);
/* ... */
}
}
@@ -403,6 +500,10 @@ static void v4l2loopback_create_sysfs(struct video_device *vdev)
V4L2_SYSFS_CREATE(fourcc);
V4L2_SYSFS_CREATE(buffers);
V4L2_SYSFS_CREATE(max_openers);
+ V4L2_SYSFS_CREATE(idle_fps);
+ V4L2_SYSFS_CREATE(placeholder_delay);
+ V4L2_SYSFS_CREATE(max_buffers);
+ V4L2_SYSFS_CREATE(buffer_size);
/* ... */
} while(0);
@@ -439,9 +540,13 @@ v4l2loopback_getdevice (struct file*f)
}
/* forward declarations */
+static int get_capture_buffer(struct v4l2_loopback_device *dev,
+ struct v4l2_loopback_opener *opener,
+ struct file *file);
static void init_buffers(struct v4l2_loopback_device *dev);
static int allocate_buffers(struct v4l2_loopback_device *dev);
static int free_buffers(struct v4l2_loopback_device *dev);
+static void schedule_idle_frame(struct v4l2_loopback_device *dev);
static const struct v4l2_file_operations v4l2_loopback_fops;
static const struct v4l2_ioctl_ops v4l2_loopback_ioctl_ops;
@@ -762,8 +867,10 @@ vidioc_g_fmt_out (struct file *file,
pix_format_set_size(&fmt->fmt.pix, defaultfmt,
dev->pix_format.width, dev->pix_format.height);
+ dev->pix_format.sizeimage = fmt->fmt.pix.sizeimage;
dev->buffer_size = PAGE_ALIGN(dev->pix_format.sizeimage);
+ dprintkrw("buffer_size: %u -> %ld", dev->pix_format.sizeimage, dev->buffer_size);
allocate_buffers(dev);
}
fmt->fmt.pix = dev->pix_format;
@@ -852,6 +959,7 @@ vidioc_s_fmt_out (struct file *file,
if (!dev->ready_for_capture) {
dev->buffer_size = PAGE_ALIGN(dev->pix_format.sizeimage);
+ dprintkrw("buffer_size: %u -> %ld", dev->pix_format.sizeimage, dev->buffer_size);
fmt->fmt.pix.sizeimage = dev->buffer_size;
allocate_buffers(dev);
}
@@ -1152,7 +1260,7 @@ vidioc_reqbufs (struct file *file,
opener = file->private_data;
dprintk("reqbufs: %d\t%d=%d", b->memory, b->count, dev->buffers_number);
- init_buffers(dev);
+ allocate_buffers(dev);
switch (b->memory) {
case V4L2_MEMORY_MMAP:
/* do nothing here, buffers are always allocated*/
@@ -1231,8 +1339,10 @@ vidioc_qbuf (struct file *file,
case V4L2_BUF_TYPE_VIDEO_OUTPUT:
dprintkrw("output QBUF index: %d\n", index);
do_gettimeofday(&b->buffer.timestamp);
+ dev->last_write_timestamp = b->buffer.timestamp;
set_done(b);
wake_up_all(&dev->read_event);
+ schedule_idle_frame(dev);
return 0;
default:
return -EINVAL;
@@ -1256,14 +1366,9 @@ vidioc_dqbuf (struct file *file,
switch (buf->type) {
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
- if ((dev->write_position <= opener->read_position) &&
- (file->f_flags&O_NONBLOCK))
+ index = get_capture_buffer(dev, opener, file);
+ if (index < 0)
return -EAGAIN;
- wait_event_interruptible(dev->read_event, (dev->write_position >
- opener->read_position));
- if (dev->write_position > opener->read_position+2)
- opener->read_position = dev->write_position - 1;
- index = opener->read_position % dev->used_buffers;
dprintkrw("capture DQBUF index: %d\n", index);
if (!(dev->buffers[index].buffer.flags&V4L2_BUF_FLAG_MAPPED)) {
dprintk("trying to return not mapped buf\n");
@@ -1272,15 +1377,26 @@ vidioc_dqbuf (struct file *file,
++opener->read_position;
unset_flags(&dev->buffers[index]);
*buf = dev->buffers[index].buffer;
+ dprintkrw("buffer out: %d %d %d %ld %ld",
+ index,
+ opener->read_position - 1,
+ dev->write_position,
+ (long)buf->timestamp.tv_sec,
+ (long)buf->timestamp.tv_usec);
return 0;
case V4L2_BUF_TYPE_VIDEO_OUTPUT:
+ if (mutex_lock_interruptible(&dev->write_mutex) < 0)
+ return -EAGAIN;
index = dev->write_position % dev->used_buffers;
dprintkrw("output DQBUF index: %d\n", index);
unset_flags(&dev->buffers[index]);
*buf = dev->buffers[index].buffer;
- ++dev->write_position;
+ buf->sequence = dev->write_position++;
+ dev->idle_frame_needed = 0;
+ mutex_unlock(&dev->write_mutex);
return 0;
default:
+ dprintk("trying to dequeue buffer of unknown type: %x\n", buf->type);
return -EINVAL;
}
}
@@ -1383,8 +1499,9 @@ vm_open (struct vm_area_struct *vma)
struct v4l2l_buffer *buf;
MARK();
- buf=vma->vm_private_data;
- buf->use_count++;
+ buf = vma->vm_private_data;
+ if (buf != NULL)
+ buf->use_count++;
}
static void
@@ -1393,8 +1510,9 @@ vm_close (struct vm_area_struct *vma)
struct v4l2l_buffer *buf;
MARK();
- buf=vma->vm_private_data;
- buf->use_count--;
+ buf = vma->vm_private_data;
+ if (buf != NULL)
+ buf->use_count--;
}
static struct vm_operations_struct vm_ops = {
@@ -1406,26 +1524,29 @@ static int
v4l2_loopback_mmap (struct file *file,
struct vm_area_struct *vma)
{
- int i;
- unsigned long addr;
+ u8 *addr;
unsigned long start;
unsigned long size;
+ unsigned long offset;
+ unsigned long buf;
struct v4l2_loopback_device *dev;
- struct v4l2l_buffer *buffer = NULL;
+ struct v4l2l_buffer *buffer;
MARK();
+ dev = v4l2loopback_getdevice(file);
+
start = (unsigned long) vma->vm_start;
size = (unsigned long) (vma->vm_end - vma->vm_start);
+ offset = vma->vm_pgoff << PAGE_SHIFT;
+ addr = dev->image + offset;
+ buf = offset / dev->buffer_size;
- dev=v4l2loopback_getdevice(file);
-
- if (size > dev->buffer_size) {
- dprintk("userspace tries to mmap too much, fail\n");
+ if (offset != buf * dev->buffer_size) {
+ dprintk("userspace tries to mmap the middle of frame, fail\n");
return -EINVAL;
}
- if ((vma->vm_pgoff << PAGE_SHIFT) >
- dev->buffer_size * (dev->buffers_number - 1)) {
- dprintk("userspace tries to mmap too far, fail\n");
+ if (size > dev->buffer_size) {
+ dprintk("userspace tries to mmap too much, fail\n");
return -EINVAL;
}
@@ -1436,23 +1557,28 @@ v4l2_loopback_mmap (struct file *file,
}
}
- for (i = 0; i < dev->buffers_number; ++i) {
- buffer = &dev->buffers[i];
- if ((buffer->buffer.m.offset >> PAGE_SHIFT) == vma->vm_pgoff)
- break;
- }
-
- if(NULL == buffer) {
+ if (buf < dev->buffers_number) {
+ buffer = &dev->buffers[buf];
+ if (offset != buffer->buffer.m.offset) {
+ dprintk("assertion failed; something's wrong\n");
+ return -EFAULT;
+ }
+ } else if (buf == dev->buffers_number) {
+ /* placeholder frame */
+ buffer = NULL;
+ if (offset != dev->placeholder_frame - dev->image) {
+ dprintk("assertion failed; something's wrong\n");
+ return -EFAULT;
+ }
+ } else {
+ dprintk("userspace tries to mmap too far, fail\n");
return -EINVAL;
- }
-
- addr = (unsigned long) dev->image + (vma->vm_pgoff << PAGE_SHIFT);
+ }
while (size > 0) {
struct page *page;
- page = (void *) vmalloc_to_page((void *) addr);
-
+ page = vmalloc_to_page(addr);
if (vm_insert_page(vma, start, page) < 0)
return -EAGAIN;
@@ -1463,8 +1589,8 @@ v4l2_loopback_mmap (struct file *file,
vma->vm_ops = &vm_ops;
vma->vm_private_data = buffer;
- dev->buffers[(vma->vm_pgoff<<PAGE_SHIFT)/dev->buffer_size].buffer.flags |=
- V4L2_BUF_FLAG_MAPPED;
+ if (buffer != NULL)
+ buffer->buffer.flags |= V4L2_BUF_FLAG_MAPPED;
vm_open(vma);
@@ -1490,7 +1616,7 @@ v4l2_loopback_poll (struct file *file,
break;
case READER:
poll_wait(file, &dev->read_event, pts);
- if (dev->write_position > opener->read_position)
+ if (dev->write_position > opener->read_position || dev->idle_frame_needed)
ret_mask = POLLIN | POLLRDNORM;
break;
default:
@@ -1561,17 +1687,11 @@ v4l2_loopback_read (struct file *file,
opener = file->private_data;
dev = v4l2loopback_getdevice(file);
- if ((dev->write_position <= opener->read_position) &&
- (file->f_flags&O_NONBLOCK)) {
+ read_index = get_capture_buffer(dev, opener, file);
+ if (read_index < 0)
return -EAGAIN;
- }
- wait_event_interruptible(dev->read_event,
- (dev->write_position > opener->read_position));
if (count > dev->buffer_size)
count = dev->buffer_size;
- if (dev->write_position > opener->read_position+2)
- opener->read_position = dev->write_position - 1;
- read_index = opener->read_position % dev->used_buffers;
if (copy_to_user((void *) buf, (void *) (dev->image +
dev->buffers[read_index].buffer.m.offset), count)) {
printk(KERN_ERR "v4l2-loopback: "
@@ -1607,6 +1727,8 @@ v4l2_loopback_write (struct file *file,
if (count > dev->buffer_size)
count = dev->buffer_size;
+ if (mutex_lock_interruptible(&dev->write_mutex) < 0)
+ return -EAGAIN;
write_index = dev->write_position % dev->used_buffers;
b=&dev->buffers[write_index].buffer;
@@ -1616,15 +1738,97 @@ v4l2_loopback_write (struct file *file,
printk(KERN_ERR "v4l2-loopback: "
"failed copy_from_user() in write buf, could not write %zu\n",
count);
+ mutex_unlock(&dev->write_mutex);
return -EFAULT;
}
do_gettimeofday(&b->timestamp);
+ dev->last_write_timestamp = b->timestamp;
b->sequence = dev->write_position++;
+ dev->idle_frame_needed = 0;
+ mutex_unlock(&dev->write_mutex);
wake_up_all(&dev->read_event);
dprintkrw("leave v4l2_loopback_write()\n");
return count;
}
+static void
+generate_idle_frame(struct v4l2_loopback_device *dev)
+{
+ struct v4l2l_buffer *src, *dst;
+ s64 idle_ns;
+ struct timeval idle_time;
+ u8 *frame;
+
+ MARK();
+ if (!dev->idle_frame_needed)
+ dprintk("!dev->idle_frame_needed; shoudn't happen");
+ src = &dev->buffers[dev->write_position % dev->used_buffers];
+ dst = &dev->buffers[(dev->write_position + 1) % dev->used_buffers];
+ if (!(dst->buffer.flags & V4L2_BUF_FLAG_QUEUED))
+ dprintk("destination buffer not queued; will cross fingers and use it anyway");
+ do_gettimeofday(&dst->buffer.timestamp);
+ idle_ns = timeval_to_ns(&dst->buffer.timestamp) - timeval_to_ns(&dev->last_write_timestamp);
+ idle_time = ns_to_timeval(idle_ns);
+ dprintkrw("generate_idle_frame %ld %ld",
+ (long)idle_time.tv_sec,
+ (long)idle_time.tv_usec);
+
+ frame = (dev->placeholder_delay >= 0 && idle_ns > MS_TO_NS(dev->placeholder_delay)) ?
+ dev->placeholder_frame :
+ (dev->image + src->buffer.m.offset);
+ memcpy(dev->image + dst->buffer.m.offset, frame, dev->buffer_size);
+ set_done(dst);
+ dev->idle_frame_needed = 0;
+ dev->write_position++;
+}
+
+static int
+get_capture_buffer(struct v4l2_loopback_device *dev,
+ struct v4l2_loopback_opener *opener,
+ struct file *file)
+{
+ if ((dev->write_position <= opener->read_position) &&
+ (file->f_flags&O_NONBLOCK))
+ return -EAGAIN;
+ if (wait_event_interruptible(
+ dev->read_event,
+ (dev->write_position > opener->read_position) || dev->idle_frame_needed) < 0)
+ return -EAGAIN;
+ if (dev->write_position == opener->read_position) {
+ if (mutex_lock_interruptible(&dev->write_mutex) < 0)
+ return -EAGAIN;
+ /* check again */
+ if (dev->write_position == opener->read_position) {
+ generate_idle_frame(dev);
+ }
+ mutex_unlock(&dev->write_mutex);
+ }
+ if (dev->write_position > opener->read_position+2)
+ opener->read_position = dev->write_position - 1;
+ return opener->read_position % dev->used_buffers;
+}
+
+static void schedule_idle_frame(struct v4l2_loopback_device *dev)
+{
+ if (dev->idle_fps > 0) {
+ long frame_jiffies = msecs_to_jiffies(1000) / dev->idle_fps;
+ mod_timer(&dev->idle_frame_timer, jiffies + frame_jiffies);
+ } else {
+ del_timer(&dev->idle_frame_timer);
+ }
+}
+
+static void idle_frame_callback(unsigned long nr)
+{
+ struct v4l2_loopback_device *dev;
+
+ dev = devs[nr];
+ dprintkrw("idle frame: %d", dev->write_position);
+ dev->idle_frame_needed = 1;
+ wake_up_all(&dev->read_event);
+ schedule_idle_frame(devs[nr]);
+}
+
/* init functions */
/* frees buffers, if already allocated */
static int free_buffers(struct v4l2_loopback_device *dev)
@@ -1633,6 +1837,7 @@ static int free_buffers(struct v4l2_loopback_device *dev)
if(dev->image) {
vfree(dev->image);
dev->image=NULL;
+ dev->placeholder_frame = NULL;
}
dev->imagesize=0;
@@ -1650,7 +1855,7 @@ allocate_buffers (struct v4l2_loopback_device *dev)
if (dev->image) {
dprintk("allocating buffers again: %ld %ld", dev->buffer_size * dev->buffers_number, dev->imagesize);
/* FIXME: prevent double allocation more intelligently! */
- if(dev->buffer_size * dev->buffers_number == dev->imagesize)
+ if(dev->buffer_size * (dev->buffers_number + PLACEHOLDER_FRAME) == dev->imagesize)
return 0;
/* if there is only one writer, no problem should occur */
@@ -1660,13 +1865,15 @@ allocate_buffers (struct v4l2_loopback_device *dev)
return -EINVAL;
}
- dev->imagesize=dev->buffer_size * dev->buffers_number;
+ dev->imagesize=dev->buffer_size * (dev->buffers_number + PLACEHOLDER_FRAME);
+ dprintkrw("vmallocating %ld*%d bytes\n", dev->buffer_size, (dev->buffers_number + PLACEHOLDER_FRAME));
dev->image = vmalloc(dev->imagesize);
if (dev->image == NULL)
return -ENOMEM;
dprintk("vmallocated %ld bytes\n",
dev->imagesize);
+ memset(dev->image, 0, dev->imagesize);
MARK();
init_buffers(dev);
return 0;
@@ -1701,6 +1908,7 @@ init_buffers (struct v4l2_loopback_device *dev)
do_gettimeofday(&b->timestamp);
}
+ dev->placeholder_frame = dev->image + dev->buffers_number * buffer_size;
dev->write_position = 0;
MARK();
}
@@ -1762,6 +1970,10 @@ v4l2_loopback_init (struct v4l2_loopback_device *dev,
dev->buffer_size = 0;
dev->image = NULL;
dev->imagesize = 0;
+ dev->idle_fps = idle_fps;
+ dev->placeholder_delay = placeholder_delay;
+ mutex_init(&dev->write_mutex);
+ setup_timer(&dev->idle_frame_timer, idle_frame_callback, nr);
/* FIXME set buffers to 0 */
@@ -1769,6 +1981,16 @@ v4l2_loopback_init (struct v4l2_loopback_device *dev,
return 0;
};
+static void
+v4l2_loopback_free(struct v4l2_loopback_device *dev)
+{
+ del_timer_sync(&dev->idle_frame_timer);
+ v4l2loopback_remove_sysfs(dev->vdev);
+ kfree(video_get_drvdata(dev->vdev));
+ video_unregister_device(dev->vdev);
+ kfree(dev);
+}
+
/* LINUX KERNEL */
static const struct v4l2_file_operations v4l2_loopback_fops = {
.owner = THIS_MODULE,
@@ -1853,10 +2075,7 @@ free_devices (void)
int i;
for(i=0; i<devices; i++) {
if(NULL!=devs[i]) {
- v4l2loopback_remove_sysfs(devs[i]->vdev);
- kfree(video_get_drvdata(devs[i]->vdev));
- video_unregister_device(devs[i]->vdev);
- kfree(devs[i]);
+ v4l2_loopback_free(devs[i]);
devs[i]=NULL;
}
}
@@ -1940,3 +2159,4 @@ cleanup_module (void)
dprintk("module removed\n");
}
+/* vim: set ts=2 sw=2: */
View
180 v4l2loopback_io.c
@@ -0,0 +1,180 @@
+/*
+ * v4l2loopback_io.c -- video4linux2 userspace i/o utility
+ *
+ * Copyright (C) 2012 Anton Novikov (random.plant@gmail.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#define DEV "/dev/"
+
+/* arguments */
+static int mode = '\0';
+static int buf = 0;
+static const char *device = "/dev/video0";
+
+/* state */
+static int device_fd = -1;
+static void *buffer = NULL;
+static unsigned long buffer_size = 0;
+
+static void
+die(const char *fmt, ...)
+{
+ va_list va;
+
+ va_start(va, fmt);
+ vfprintf(stderr, fmt, va);
+ va_end(va);
+
+ exit(EXIT_FAILURE);
+}
+
+static void
+die_perror(const char *desc)
+{
+ perror(desc);
+ exit(EXIT_FAILURE);
+}
+
+static long
+read_long_attr(const char *filename_fmt, ...)
+{
+ va_list va;
+ char filename[FILENAME_MAX];
+ FILE *file;
+ long value;
+
+ va_start(va, filename_fmt);
+ vsprintf(filename, filename_fmt, va);
+ va_end(va);
+
+ if ((file = fopen(filename, "r")) == NULL)
+ die("can't open file: %s\n", filename);
+ if (fscanf(file, "%ld", &value) != 1)
+ die("can't read value from %s\n", filename);
+ fclose(file);
+ return value;
+}
+
+static int
+open_buffer()
+{
+ const char *device_name;
+ int max_buffers;
+ int open_mode;
+ int mmap_prot;
+ int mmap_flags;
+
+ if (memcmp(device, DEV, sizeof(DEV) - 1) != 0)
+ goto cant_parse_device;
+ device_name = device + sizeof(DEV) - 1;
+ if (strchr(device_name, '/') != NULL)
+ goto cant_parse_device;
+
+ /* using O_WRONLY doesn't allow PROT_WRITE mmapping for me */
+ open_mode = mode == 'r' ? O_RDONLY : O_RDWR;
+
+ if ((device_fd = open(device, open_mode)) < 0)
+ die_perror("open() failed");
+ max_buffers = (int)read_long_attr("/sys/devices/virtual/video4linux/%s/max_buffers", device_name);
+ buffer_size = (unsigned long)read_long_attr("/sys/devices/virtual/video4linux/%s/buffer_size", device_name);
+
+ if (buf == -1)
+ buf = max_buffers;
+
+ if (buf == max_buffers)
+ fprintf(stderr, "mmapping placeholder frame...\n");
+ else if (buf < 0 || buf > max_buffers)
+ die("buffer index out of range\n");
+ else
+ fprintf(stderr, "mmapping frame %d...\n", buf);
+
+ mmap_prot = mode == 'r' ? PROT_READ : PROT_WRITE;
+ mmap_flags = MAP_SHARED;
+ buffer = mmap(NULL, buffer_size, mmap_prot, mmap_flags, device_fd, buf * buffer_size);
+ if (buffer == MAP_FAILED)
+ die_perror("mmap() failed");
+ fprintf(stderr, "mmapped %ld bytes\n", buffer_size);
+ return 0;
+
+cant_parse_device:
+ die("can't parse device name\n");
+}
+
+static void
+cleanup()
+{
+ if (buffer != NULL)
+ munmap(buffer, buffer_size);
+ if (device_fd >= 0)
+ close(device_fd);
+}
+
+static int
+do_read()
+{
+ die("reading unimplemented\n");
+ return 0;
+}
+
+static int
+do_write()
+{
+ if (freopen(NULL, "rb", stdin) == NULL)
+ die("can't reopen stdin for binary input\n");
+
+ ssize_t written = fread(buffer, 1, buffer_size, stdin);
+ if (written < 0)
+ die("fread() failed\n");
+
+ fprintf(stderr, "written %ld bytes\n", written);
+ return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+ if (argc < 3)
+ goto usage;
+
+ if (sscanf(argv[1], "-%c", &mode) != 1)
+ goto usage;
+ if (sscanf(argv[2], "%i", &buf) != 1) {
+ if (!strcmp(argv[2], "placeholder"))
+ buf = -1;
+ else
+ goto usage;
+ }
+ if (argc >= 4)
+ device = argv[3];
+
+ open_buffer();
+ switch (mode) {
+ case 'r':
+ do_read();
+ break;
+ case 'w':
+ do_write();
+ break;
+ default:
+ goto usage;
+ }
+
+ cleanup();
+ return 0;
+
+usage:
+ die("usage: %s (-r|-w) (buffer_number|'placeholder') [device]\n");
+}
Something went wrong with that request. Please try again.