BeagleBoneBlack Webcam using a V4L2, FFMPEG and a Pipe

I’ve been working on streaming video from my BeagleBoneBlack (BBB) over WiFi and also keeping a copy of the video on the local flash. My starting point for capturing the video using Video For Linux 2 (V4L2) came from demonstration code from Derek Molloy’s site. He has some very well done instructional videos that are much more accessible and complete than most of what I’ve put together.

I am doing most of my development in C/C++ simply because it’s my strongest language. I’m doing the development on the BBB under linux which means that some of the skills I’m using are fairly rusty.

Because I wanted to have the program pulling the video from the camera in charge of the entire process, including managing which local output files are created, and making sure that there is disk storage space, I decided to use pipe(), fork(), and exec(), to manage ffmpeg as a child process of my program.

I started with the same demonstration program from V4L2 and then modified it so that instead of writing out to stdout, it’s writing to a file descriptor I supply. This all happens inside of the primary loop that generates the outputfilename for the ffmpeg command line and makes sure that there is enough free storage for an hours worth of video. Here’s the main code snippet.

int	pipefd[2];		/* This holds the fd for the input & output of the pipe */
/* Setup communication pipeline first */
if(pipe(pipefd))
{
	std::cerr << "[" << getTimeISO8601() << "] Pipe error! Exiting." << std::endl;
	exit(1);
}
/* Attempt to fork */
pid_t pid = fork();
if(pid == -1)
{
	std::cerr << "[" << getTimeISO8601() << "] Fork error! Exiting." << std::endl; /* something went wrong */
	exit(1);        
}
else if (pid == 0)
{
	/* A zero PID indicates that this is the child process */
	dup2(pipefd[0], 0);	/* Replace stdin with the in side of the pipe */
	close(pipefd[1]);	/* Close unused side of pipe (out side) */
	/* Replace the child fork with a new process */
	if(execlp("ffmpeg", "ffmpeg", "-i", "-", "-vcodec", "copy", "-f", "rtp", "rtp://239.8.8.8:8090/", "-metadata", "title=\"Wims BoneCam\"", "-vcodec", "copy", OutputFilename.c_str(), NULL) == -1)
	{
		std::cerr << "[" << getTimeISO8601() << "] execlp Error! Exiting." << std::endl;
		exit(1);
	}
}
else
{
	/* A positive (non-negative) PID indicates the parent process */
	close(pipefd[0]);		/* Close unused side of pipe (in side) */
	open_device();
	init_device();
	start_capturing();
	//mainloop();

	time_t CurrentTime;
	time(&CurrentTime);
	time_t FileStartTime = CurrentTime;
	while ((difftime(CurrentTime, FileStartTime) < (60 * 60)) && (bRun))
	//unsigned int count = frame_count;
	//while ((count-- > 0) && (bRun))
	{
		fd_set fds;
		struct timeval tv;
		int r;

		FD_ZERO(&fds);
		FD_SET(fd, &fds);

		/* Timeout. */
		tv.tv_sec = 2;
		tv.tv_usec = 0;

		r = select(fd + 1, &fds, NULL, NULL, &tv);
		if (-1 == r) 
		{
			if (EINTR == errno)
				continue;
			errno_exit("select");
		}
		if (0 == r) 
		{
			fprintf(stderr, "select timeout\n");
			exit(EXIT_FAILURE);
		}
		struct v4l2_buffer buf;
		WIMLABEL:
		CLEAR(buf);
		buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		buf.memory = V4L2_MEMORY_MMAP;

		if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) 
		{
			switch (errno) 
			{
			case EAGAIN:
				goto WIMLABEL;
			case EIO:
				/* Could ignore EIO, see spec. */
				/* fall through */
			default:
				errno_exit("VIDIOC_DQBUF");
			}
		}
		else
		{
			assert(buf.index < n_buffers);
			write(pipefd[1], buffers[buf.index].start, buf.bytesused);
		}
		if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
				errno_exit("VIDIOC_QBUF");
		time(&CurrentTime);
	}

	stop_capturing();
	uninit_device();
	close_device();
	close(pipefd[1]);		/* Close side of pipe I'm writing to, to get the child to recognize it's gone away */
	std::cerr << "\n[" << getTimeISO8601() << "] Pipe Closed, Waiting for FFMPEG to exit" << std::endl;
	int ffmpeg_exit_status;
	wait(&ffmpeg_exit_status);				/* Wait for child process to end */
	std::cerr << "[" << getTimeISO8601() << "] FFMPEG exited with a  " << ffmpeg_exit_status << " value" << std::endl;
}

You can see the calls to open_device(), init_device(), and start_capturing() that I didn’t change at all. My loop runs for a particular amount of time instead of number of frames. I flattened the call to mainloop() into this routine in a very quick and dirty fashion to make sure I had access to the file descriptor to write the data from the camera to the input for ffmpeg.

ffmpeg is started with the execlp() command. The parameters -i – tell ffmpeg to read it’s input from standard input, which in this case I’ve pointed to be the output end of the pipe I created.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s