#!/usr/bin/perl # # capture a steady stream of video frames, compress it and split it into # multiple files, for long recording sessions # # configure it below # use Socket; use Fcntl; use Video::Capture::V4l; use Video::RTjpeg; use Time::HiRes 'time'; use File::Sync 'sync'; use IPC::SysV; use POSIX qw(nice WNOHANG); use IO::Select; BEGIN { require "linux-dsp-ioctl.ph" } $|=1; my $outprefix = "/tmp/vstream"; my $initial_fadein = 40; # initial settle time my $consec = 25; # push this many frames to each encoder my $syncfreq = 0.2; # pause this time between syncs my $vencoders = 8; # number of encoder processes my(@venc,@enc); #my ($w, $h, $vformat) = (704, 528, PALETTE_YUV420P); my ($cw, $ch, $vformat) = (576, 432, PALETTE_YUV420P); my ($cl, $cr, $ct, $cb) = (16,16, 16,16); #my ($cw, $ch, $vformat) = (320, 240, PALETTE_YUV420P); #my ($cl, $cr, $ct, $cb) = (0,0, 0,0); my $fps = 25; my $spf = 1/$fps; my ($x, $y, $w, $h) = ($cl, $ct, $cw-$cr-$cl, $ch-$cb-$ct); my ($rate, $channels, $aformat) = (44100, 2, &AFMT_S16_LE); my ($Q, $M) = (255, 0); my $fsize = $cw*$ch*2; my $buffers = int(32*1024*1024/$fsize); my $bufsize = $fsize * $buffers; my $shm = shmget IPC_PRIVATE, $bufsize, IPC_CREAT|0600; $shm or die "unable to allocate $bufsize shm segment"; END { shmctl $shm, IPC_RMID, 0 } $SIG{INT} = sub { exit }; my @buffers = (0..($buffers-1)); my $select = IO::Select->new(); my %cb; # audio setup sysopen DSP, "/dev/dsp", O_RDONLY or sysopen DSP, "/dev/dsp", O_RDONLY or die "unable to open /dev/dsp for reading: $!"; ioctl DSP, SNDCTL_DSP_SETFRAGMENT, pack "i", 0x7fff000e or die "SNDCTL_DSP_SETFRAGMENT: $!"; ioctl DSP, SNDCTL_DSP_SETFMT, pack "i", $aformat or die "SNDCTL_DSP_SETFMT: $!"; ioctl DSP, SNDCTL_DSP_CHANNELS, pack "i", $channels or die "SNDCTL_DSP_CHANNELS: $!"; ioctl DSP, SNDCTL_DSP_SPEED, pack "i", $rate or die "SNDCTL_DSP_SPEED: $!"; open AUDIO, ">$outprefix.a" or die; # video setup $grab = new Video::Capture::V4l or die "Unable to open Videodevice: $!"; my $channel = $grab->channel (0); my $tuner = $grab->tuner (0); $tuner->mode(MODE_PAL); $channel->norm(MODE_PAL); $tuner->mode(8); $tuner->set; $channel->set; #$CHANNEL69 = 855250; #print $grab->freq ($CHANNEL69),"\n"; sub new_vencoder { my $number = @enc; my $encp = do { local *ENCODER_WRITER }; my $encc = do { local *ENCODER_READER }; socketpair $encc, $encp, AF_UNIX, SOCK_STREAM, PF_UNSPEC; $select->add($encp); $cb[fileno $encp] = sub { my $buf; 4 == sysread $encp, $buf, 4 or die "unable to read bufferid"; $buf = unpack "N", $buf; push @buffers, $buf; }; if (0 == fork) { open DATA, ">$outprefix.v$number" or die "$!"; my $tables = Video::RTjpeg::init_compress($cw,$ch,$Q); Video::RTjpeg::init_mcompress(); syswrite DATA, pack "N", length($tables); syswrite DATA, $tables; my $buf; my $count = $number * 313; for(;;) { 8 == read $encc, $buf, 8 or die "incomplete frame time read: $!"; my ($buffer, $time) = unpack "NN", $buf; last if $buffer >= $buffers; shmread $shm, $buf, $buffer*$fsize, $fsize; syswrite $encc, (pack "N", $buffer); my $fr = Video::RTjpeg::mcompress($buf,$M,$M>>1, $x, $y, $w, $h); #my $fr = Video::RTjpeg::compress($buf); syswrite DATA, pack "NN", $time, length $fr; syswrite DATA, $fr; #Video::RTjpeg::fdatasync fileno DATA if ($count++ & 63) == 0; print "+"; } print "X"; exit; } push @enc, $encp; } new_vencoder for 1..$vencoders; @venc = map (($_)x$consec, @enc); my $frame = 0; my $frames = 0; my $dropped = 0; sub put_vframe { my $buffer = pop @buffers; my $enc = pop @venc; unshift @venc, $enc; if (defined $buffer) { print "-"; shmwrite $shm, ${$_[1]}, $buffer*$fsize, $fsize; syswrite $enc, (pack "NN", $buffer, $_[0]); } else { print "o"; $dropped++; } } my $syncpid = fork; if ($syncpid==0) { for(;;) { select undef, undef, undef, $syncfreq; print "S"; sync; } Video::RTjpeg::_exit; }; system "rtprio -p $$"; my $fr = \$grab->capture ($frame, $cw, $ch, $vformat); for(1..$initial_fadein) { my $nfr = \$grab->capture (1-$frame, $cw, $ch, $vformat); $grab->sync($frame) or die "unable to sync"; $frame = 1-$frame; $fr = $nfr; } my $start = time; my $nframe; $fr = \$grab->capture ($frame, $cw, $ch, $vformat); my $audpid = fork; if ($audpid==0) { my $count = 0; my $in = ""; vec($in, fileno DSP, 1) = 1; fcntl DSP, F_SETFL, O_NONBLOCK; ioctl DSP, SNDCTL_DSP_SETTRIGGER, pack "i", 0; ioctl DSP, SNDCTL_DSP_SETTRIGGER, pack "i", PCM_ENABLE_INPUT; for(;;) { my $buf; select my $xin = $in, undef, undef, $spf*2; print "."; if (0 < sysread DSP, $buf, 128*1024) { #print length $buf; syswrite AUDIO, $buf; } } Video::RTjpeg::_exit; }; my $do_capture = 1; $select->add(*STDIN); $cb[fileno STDIN] = sub { $do_capture = 0; }; while($do_capture) { printf "\n%02d:%02d +%2d %2ds %dd > ", int($nframe*$spf/60), int($nframe*$spf)%60, scalar@buffers, $frames-$nframe, $dropped if $frames % $fps == 0; my $nfr = \$grab->capture (1-$frame, $cw, $ch, $vformat); $grab->sync($frame) or die "unable to sync"; my $now = time; $nframe = int (($now-$start) / $spf + 0.5); $start = $now - $nframe * $spf; put_vframe($nframe, $fr); for ($select->can_read(0)) { $cb[fileno $_]->(); } $frame = 1-$frame; $frames++; $fr = $nfr; } open CTRL, ">$outprefix" or die; print CTRL <remove(*STDIN); close DSP; close AUDIO; kill 'TERM', $syncpid; kill 'TERM', $audpid; for (@enc) { syswrite $_, (pack "NN", -1, -1); $select->remove($_); } for(;;) { for ($select->can_read(0)) { $cb[fileno $_]->(); } }