20130609

Making kvm/qemu/libvirt Play Nice with PulseAudio on a Headless Ubuntu 12.04 Server

I've been running over a dozen virtual machines on my headless server for almost two years now, and for all that time I've always missed being able to hear the audio from those machines. I would occasionally try to figure out how to make audio work over VNC, but never could find a solution on the Internet. Finally last week I decided to at least get part-way there by getting the audio to play on the server's speaker port. The first step was pretty easy – installing PulseAudio on the server:

sudo apt-get install pulseaudio

Now from what I could gather on the Internet, it seems like I needed to run PulseAudio in system mode, despite all the warnings that it should probably not be run that way. I figured that since I don't usually have any logged in users on the system, it would just be better to have it running all the time. In order to do that, I edited the /etc/default/pulseaudio file, and changed the following settings to:

PULSEAUDIO_SYSTEM_START=1
DISALLOW_MODULE_LOADING=1

Then I added my user and the libvirt-qemu user to the pulse-access group:

sudo adduser myuser pulse-access
sudo adduser libvirt-qemu pulse-access

You'll need to log out and back in again for the new group to be picked up on your shell. Finally, I started the PulseAudio service:

sudo service pulseaudio start

Now a quick test to make sure the sound subsystem was working:

paplay test-sound.wav

In my case, I could barely hear the sound playing, so I did a pactl list sinks to figure out which sink was being used, then issued
pactl set-sink-volume 1 100%
pactl set-sink-mute 1 0
to set the volume level of sink 1 to the maximum and unmute it. Now I could hear the sound just fine!

The next hurdle was to get the sound from the virtual machines to play through PulseAudio. It turns out there are quite a few obstacles to achieving that goal. First off, libvirt automatically disables audio if you are using a VNC client! It turns out to be fairly simple to fix that though, simply edit /etc/libvirt/qemu.conf and change the following setting to:

vnc_allow_host_audio = 1

After restarting the libvirt daemon using sudo service libvirt-bin restart I could see in the syslog file that libvirt/kvm was trying to use the PulseAudio subsytem, but apparmor was blocking access to several key files/directories. I never did find a working answer by Googling, but I worked out the following settings for the /etc/apparmor.d/abstractions/libvirt-qemu file. I changed /{dev,run}/shm r, to /{dev,run}/shm rw, then added /{dev,run}/shm/pulse* rw, right after that line. Finally I added /var/lib/libvirt/.pulse-cookie rwk, (note the trailing commas on those lines!) then told apparmor to reload the configuration:

sudo invoke-rc.d apparmor reload

I fired off a Windows XP x32 guest, and was able to hear sound, but it was very distorted and choppy. The solution to that was to change the sound hardware in the virtual machine's configuration file from <sound model='ac97'> to <sound model='es1370'>. After that, I was getting perfect sound from my virtual machine!

Now for a few caveats – it seems that changing any of the PulseAudio configuration or restarting the service while the virtual machine is running can cause problems like the sound no longer working, all the way to the virtual machine's OS hanging up trying to play sounds. So once you started your virtual machine, leave things alone! I have also been working on trying to forward the sound over the network to my workstation, but so far I am having mixed results with that. Hopefully I'll have another post soon describing how to make that work.

And here is the usual warning that goes with tweaking your system like this: These instructions worked for me, but your mileage may vary. Also, I won't be responsible if any of this causes your machine to stop working or catch on fire – but this stuff should be pretty straight-forward and not cause any serious issues that can't be reversed. Hopefully my adventure will help you to enjoy hearing from your virtual machines. If you have any questions or corrections, please feel free to post them in the comments.

Read More......

20130527

Time-Lapse Video Capture From Network Cameras (Linux)

I have several network cameras watching the outside of my home, monitored by ZoneMinder. I have it set up so that when there is motion detected, it will record for several seconds and send me an email with stills of the incident. While this is nice and gives me a little peace-of-mind, I've always thought about having it record continuously. While it is easy enough to do in ZoneMinder, I didn't really want to use up that much storage recording video and then have to scroll through it to find anything interesting.

The other day I saw a blog post where someone was using a Raspberry Pi and a webcam to do some time-lapse photography, and that sparked an idea that seemed easy enough to do in an afternoon – I could come up with a Python script to grab images from the network cameras at fixed intervals, and write them to a video file in order to generate a time-lapse video!

The first step was to figure out how to build a video file a frame at a time using Python. I had played with the motion-jpeg (mjpeg) format in the past, which pretty much consists of jpeg images streamed one after the other in a file (sometimes with a boundary record between them). I discovered that I could simply capture and append jpeg images to a file and get a video file that could be read by a few video players and converters. Best of all, I could use a simple avconv (formerly ffmpeg) command to convert the mjpeg files to mp4, which is smaller and viewable by almost any player.

Next, I wanted to be able to time-stamp each image so that I could tell when the video was created. For this I stumbled across the Python Imaging Library (PIL) which supports several image formats, including jpeg. Using it, I was able to select a font and write a time-stamp on each image as it was captured before adding it to the mjpeg video file. If it isn't already installed on your system, you can install it using

sudo apt-get install python-imaging
for Debian-based systems or by using the appropriate package manager for your distro.

With all the pieces in place, I developed a little Python script that periodically grabs images from several network cameras and builds a separate mjpeg file for each of them:

talicam.py:
#!/usr/bin/python

# Number of seconds between frames:
LAPSE_TIME = 30

# Name of truetype font file to use for timestamps (should be a monospace font!)
FONT_FILENAME = "UbuntuMono-B.ttf"

# Format of timestamp on each frame
TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S"

# Command to batch convert mjpeg to mp4 files:
#  for f in *.mjpeg; do echo $f ; avconv -r 30000/1001 -i "$f" "${f%mjpeg}mp4" 2>/dev/null ; done

import urllib
import sys, time, datetime
import StringIO
import Image, ImageDraw, ImageFont

class Camera:
    def __init__(self, name, url, filename):
        self.name = name
        self.url = url
        self.filename = filename
        
    def CaptureImage(self):
        camera = urllib.urlopen(self.url)
        image_buffer = StringIO.StringIO()
        image_buffer.write(camera.read())
        image_buffer.seek(0)
        image = Image.open(image_buffer)
        camera.close()
        return image
        
    def TimestampImage(self, image):
        draw_buffer = ImageDraw.Draw(image)
        font = ImageFont.truetype(FONT_FILENAME, 16)
        timestamp = datetime.datetime.now()
        stamptext = "{0} - {1}".format(timestamp.strftime(TIMESTAMP_FORMAT), self.name)
        draw_buffer.text((5, 5), stamptext, font=font)

    def SaveImage(self, image):
        with open(self.filename, "a+b") as video_file:
            image.save(video_file, "JPEG")
            video_file.flush()

    def Update(self):
        image = self.CaptureImage()
        self.TimestampImage(image)
        self.SaveImage(image)
        print("Captured image from {0} camera to {1}".format(self.name, self.filename))


if __name__ == "__main__":
    cameras = []
    cameras.append(Camera("porch", "http://username:password@10.17.42.172/SnapshotJPEG?Resolution=640x480&Quality=Clarity", "cam1.mjpeg"))
    cameras.append(Camera("driveway", "http://username:password@10.17.42.174/SnapshotJPEG?Resolution=640x480&Quality=Clarity", "cam2.mjpeg"))
    cameras.append(Camera("backyard", "http://username:password@10.17.42.173/SnapshotJPEG?Resolution=640x480&Quality=Clarity", "cam3.mjpeg"))
    cameras.append(Camera("sideyard", "http://10.17.42.176/image/jpeg.cgi", "cam4.mjpeg"))
    cameras.append(Camera("stairway", "http://10.17.42.175/image/jpeg.cgi", "cam5.mjpeg"))
    
    print("Capturing images from {0} cameras every {1} seconds...".format(len(cameras), LAPSE_TIME))
    
    try:
        while (True):
            for camera in cameras:
                camera.Update()
                
            time.sleep(LAPSE_TIME)
            
    except KeyboardInterrupt:
        print("\nExit requested, terminating normally")
        sys.exit(0)

Notice the URLs supplied in the Camera constructors. These are specific to each brand of camera, but you can usually find the format with a little Googling. In my program above, the first three cameras are Panasonic BL-C101A network cameras, the last two are a D-Link DCS-930L and a TrendNet TV-IP551W which both have very similar software and URLs.

The font file referenced above needs to be located in the same directory as the Python script, and for best results should be a mono-space font. I just grabbed the Ubuntu Monospace Bold TrueType font file for use here, but you could use anything you like.

You will probably want to launch this as a background task so that it can run for extended periods of time. I have it running on the same server that runs my ZoneMinder setup, so it can run 24-7 collecting time-lapse video. I also wrote a quick little script file that iterates the mjpeg files it finds and converts them to mp4 for easier viewing and archiving:

mjpeg2mp4:
#!/bin/bash

echo "Removing old files..."
rm -fv *.mp4

echo "Converting files to mp4..."
for f in *.mjpeg ; do
    t=${f%mjpeg}mp4
    echo "  Converting $f to $t"
    avconv -r 30000/1001 -i "$f" -q 5 "$t" 2>/dev/null
done

echo "Done!"

I had a lot of fun learning a few new tricks while working on this, and hopefully you can use it as a starting point for your own time-lapse adventure. If you find this post useful, or have questions about how it works, please leave a comment below.

Read More......
 
Template design by Amanda @ Blogger Buster