Screen Captures

From Nearline Storage
Jump to: navigation, search

My goal was high quality audio and video captured in a format that can be recoded into various formats.

I found that ffmpeg used in conjunction with arecord for the audio recording gave the best results.

Note: All of what follows is based on ffmpeg version SVN-r13061. Given the rapid evolution of ffmpeg, option names and command syntax may have changed since I wrote this.

ffmpeg version

You must have a recent version of ffmpeg that supports X11 screen capture. To discover if you have this do:

$ ffmpeg -formats 2>/cev/null | grep x11grab

If nothing comes back, this support is not available in your version of ffmpeg.

Fetching and Compiling ffmpeg

If you don't have a recent version of ffmpeg then you get get and build it using these commands:

$ svn checkout svn://svn.mplayerhq.hu/ffmpeg/trunk ffmpeg
$ ./configure --prefix=/usr --incdir=/usr/include/ffmpeg --libdir=/usr/lib --shlibdir=/usr/lib --mandir=/usr/share/man --arch=i686 --extra-cflags="-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector" --enable-libmp3lame --enable-libtheora --enable-libvorbis --enable-libfaad --enable-libfaac --enable-libgsm --enable-libxvid --enable-libx264 --enable-liba52 --enable-liba52bin --enable-pthreads --disable-static --enable-shared --enable-gpl --disable-debug --enable-x11grab
$ make
$ sudo make install

If you get errors about missing components when running the configure command then install the approriate runtime and *-devel development packages to make those formats and codecs available.

Capture

The hardest part of this for me was to get my microphone working. I had to play with alsamixer to get my mic going through to the alsa capture device. Getting that working is beyond my scope here, particularly since my experience has been that the settings are significantly different from system to system. The script attached below contains an alsactl file with all of the settings that I need to make this work for me on my system. That may or may not be of any use to you. Ultimately you want to get to the point where the following commands successfully record and then play back your voice:

$ arecord -f dat test.wav
$ aplay test.wav

Video Capture Specifications

The ffmpeg utility captures video from the section of the X11 display window that you specify. You need to tell it the size of that space and the offset of that space, if it's something other than 0,0. Use the command

$ xwininfo -frame

to get these values. With the "-frame" option xwininfo gives you the dimensions and offset of an application's window and frame. Otherwise you just get the app window inside the frame.

The dimensions are used with the ffmpeg's "-s" option. The Xwindows display number and the offset are used with its "-i" option:

$ ffmpeg -f x11grab -s 1024x768 -r 25 -i :0.0+5,10 -sameq video.mpg

That captures video from an area that's 1024 pixels wide and 768 pixels high, measuring from a point that's 5 pixels right of (x axis) and 10 pixels below (y axis) the upper left corner of the display.

If you have a huge display like I do (5040x1024) and you need to capture a whole, reasonably-sized desktop, then use Xvnc to set up a virtual desktop of the size you need. On my Fedora 7 system this involves:

  • Set up /etc/sysconfig/vncservers
 VNCSERVERS="2:dlk"
 VNCSERVERARGS[2]="-geometry 1024x768 -name \"Fedora 7 Linux\""
  • Set /home/dlk/.vnc/startup to start a normal desktop - comment everything there except:
 unset SESSION_MANAGER
 exec /etc/X11/xinit/xinitrc
  • $ sudo /sbin/service vncserver start
  • $ vncviewer :2
  • Tell ffmpeg to capture all of display :2
$ ffmpeg -f x11grab -s 1024x768 -r 25 -i :2 -sameq video.mpg

Audio Capture Options

It is possible to have ffmpeg capture both audio and video simultaneously. This command will do it:

$ ffmpeg -f oss -i /dev/audio -f x11grab -s 1024x768 -r 25 -i :0.0+5,10 -sameq combined.mpg

The problem was that when I tried this the video had tons of missing frames and the audio had occasional drop outs too.

It turned out to be better to record sound and video simultaneously with separate programs and then recombine them when you finished. This script does that:

#!/bin/sh

# Specify the display and the area to capture
DISPLAY=":0.0"
OFFSET="+0,0"
SIZE="1024x768"

# Temporary files
AUDIO="/tmp/audio.wav"
VIDEO="/tmp/video.m2v"

# The final, combined capture
MPG="/tmp/camtasia.mpg"

rm -f "$AUDIO"
rm -f "$VIDEO"
arecord -f dat "$AUDIO" &
ffmpeg -f x11grab -r 25 -s $SIZE -i ${DISPLAY}${OFFSET} -sameq "$VIDEO"
killall arecord
ffmpeg -i "$AUDIO" -i "$VIDEO" -sameq "$MPG"
rm -f "$AUDIO"
rm -f "$VIDEO"

This script automates the process of capturing the audio, capturing the video and merging them into a single file.

Editing

The avidemux program seems to do a pretty good job of editing the video. I haven't figured out how to save the output in the format I need it in yet. So far I'm just saving it as an AVI with both Video and Audio set to COPY. The AVI that comes out doesn't work in mplayer as is but

$ ffmpeg -i output.avi -sameq output.mpg

seems to fix it.

Conversions

Convert to AVI, downsampling the audio to save a little space and use a video codec that'll work on most computers:

$ ffmpeg -i combined.mpg -ac 1 -ab 56k -ar 22050 -vcodec msmpeg4 combined.avi

My Actual Capture Script

Finally, I extended the script shown above so that it saves my current alsamixer settings to a file, changes the settings for recording by microphone, does the capture, and then restores the audio to its previous state:

#!/bin/sh

DISPLAY=":0.0"
OFFSET="+0,0"
SIZE="1024x768"

# Use "xwininfo -frame" to get the size and offset values for the window you
# want to record.

AUDIO="/tmp/audio.wav"
VIDEO="/tmp/video.m2v"
MPG="/tmp/camtasia.mpg"

cat >/tmp/camtasia.alsactl <<EOF
state.NVidia {
	control.1 {
		comment.access 'read write'
		comment.type INTEGER
		comment.count 2
		comment.range '0 - 39'
		iface MIXER
		name 'Front Playback Volume'
		value.0 29
		value.1 29
	}
	control.2 {
		comment.access 'read write'
		comment.type INTEGER
		comment.count 2
		comment.range '0 - 39'
		iface MIXER
		name 'Surround Playback Volume'
		value.0 0
		value.1 0
	}
	control.3 {
		comment.access 'read write'
		comment.type INTEGER
		comment.count 1
		comment.range '0 - 39'
		iface MIXER
		name 'Center Playback Volume'
		value 0
	}
	control.4 {
		comment.access 'read write'
		comment.type INTEGER
		comment.count 1
		comment.range '0 - 39'
		iface MIXER
		name 'LFE Playback Volume'
		value 0
	}
	control.5 {
		comment.access 'read write'
		comment.type INTEGER
		comment.count 2
		comment.range '0 - 39'
		iface MIXER
		name 'Side Playback Volume'
		value.0 0
		value.1 0
	}
	control.6 {
		comment.access 'read write'
		comment.type BOOLEAN
		comment.count 2
		iface MIXER
		name 'Front Playback Switch'
		value.0 true
		value.1 true
	}
	control.7 {
		comment.access 'read write'
		comment.type BOOLEAN
		comment.count 2
		iface MIXER
		name 'Surround Playback Switch'
		value.0 true
		value.1 true
	}
	control.8 {
		comment.access 'read write'
		comment.type BOOLEAN
		comment.count 1
		iface MIXER
		name 'Center Playback Switch'
		value true
	}
	control.9 {
		comment.access 'read write'
		comment.type BOOLEAN
		comment.count 1
		iface MIXER
		name 'LFE Playback Switch'
		value true
	}
	control.10 {
		comment.access 'read write'
		comment.type BOOLEAN
		comment.count 2
		iface MIXER
		name 'Side Playback Switch'
		value.0 true
		value.1 true
	}
	control.11 {
		comment.access 'read write'
		comment.type BOOLEAN
		comment.count 2
		iface MIXER
		name 'Headphone Playback Switch'
		value.0 true
		value.1 true
	}
	control.12 {
		comment.access 'read write'
		comment.type BOOLEAN
		comment.count 2
		iface MIXER
		name 'Mono Playback Switch'
		value.0 false
		value.1 false
	}
	control.13 {
		comment.access 'read write'
		comment.type INTEGER
		comment.count 2
		comment.range '0 - 31'
		iface MIXER
		name 'CD Playback Volume'
		value.0 24
		value.1 24
	}
	control.14 {
		comment.access 'read write'
		comment.type BOOLEAN
		comment.count 2
		iface MIXER
		name 'CD Playback Switch'
		value.0 true
		value.1 true
	}
	control.15 {
		comment.access 'read write'
		comment.type INTEGER
		comment.count 2
		comment.range '0 - 31'
		iface MIXER
		name 'Front Mic Playback Volume'
		value.0 0
		value.1 0
	}
	control.16 {
		comment.access 'read write'
		comment.type BOOLEAN
		comment.count 2
		iface MIXER
		name 'Front Mic Playback Switch'
		value.0 false
		value.1 false
	}
	control.17 {
		comment.access 'read write'
		comment.type INTEGER
		comment.count 2
		comment.range '0 - 31'
		iface MIXER
		name 'Line Playback Volume'
		value.0 23
		value.1 23
	}
	control.18 {
		comment.access 'read write'
		comment.type BOOLEAN
		comment.count 2
		iface MIXER
		name 'Line Playback Switch'
		value.0 false
		value.1 false
	}
	control.19 {
		comment.access 'read write'
		comment.type INTEGER
		comment.count 2
		comment.range '0 - 31'
		iface MIXER
		name 'Mic Playback Volume'
		value.0 31
		value.1 31
	}
	control.20 {
		comment.access 'read write'
		comment.type BOOLEAN
		comment.count 2
		iface MIXER
		name 'Mic Playback Switch'
		value.0 false
		value.1 false
	}
	control.21 {
		comment.access 'read write'
		comment.type INTEGER
		comment.count 2
		comment.range '0 - 15'
		iface MIXER
		name 'Beep Playback Volume'
		value.0 0
		value.1 0
	}
	control.22 {
		comment.access 'read write'
		comment.type BOOLEAN
		comment.count 2
		iface MIXER
		name 'Beep Playback Switch'
		value.0 true
		value.1 true
	}
	control.23 {
		comment.access 'read write'
		comment.type INTEGER
		comment.count 2
		comment.range '0 - 31'
		iface MIXER
		name 'Analog Mix Playback Volume'
		value.0 21
		value.1 21
	}
	control.24 {
		comment.access 'read write'
		comment.type BOOLEAN
		comment.count 2
		iface MIXER
		name 'Analog Mix Playback Switch'
		value.0 true
		value.1 true
	}
	control.25 {
		comment.access 'read write'
		comment.type INTEGER
		comment.count 2
		comment.range '0 - 3'
		iface MIXER
		name 'Front Mic Boost'
		value.0 0
		value.1 0
	}
	control.26 {
		comment.access 'read write'
		comment.type INTEGER
		comment.count 2
		comment.range '0 - 3'
		iface MIXER
		name 'Mic Boost'
		value.0 3
		value.1 3
	}
	control.27 {
		comment.access 'read write'
		comment.type INTEGER
		comment.count 2
		comment.range '0 - 54'
		iface MIXER
		name 'Capture Volume'
		value.0 41
		value.1 41
	}
	control.28 {
		comment.access 'read write'
		comment.type BOOLEAN
		comment.count 2
		iface MIXER
		name 'Capture Switch'
		value.0 true
		value.1 true
	}
	control.29 {
		comment.access 'read write'
		comment.type INTEGER
		comment.count 2
		comment.range '0 - 54'
		iface MIXER
		name 'Capture Volume'
		index 1
		value.0 0
		value.1 0
	}
	control.30 {
		comment.access 'read write'
		comment.type BOOLEAN
		comment.count 2
		iface MIXER
		name 'Capture Switch'
		index 1
		value.0 false
		value.1 false
	}
	control.31 {
		comment.access 'read write'
		comment.type INTEGER
		comment.count 2
		comment.range '0 - 54'
		iface MIXER
		name 'Capture Volume'
		index 2
		value.0 0
		value.1 0
	}
	control.32 {
		comment.access 'read write'
		comment.type BOOLEAN
		comment.count 2
		iface MIXER
		name 'Capture Switch'
		index 2
		value.0 false
		value.1 false
	}
	control.33 {
		comment.access 'read write'
		comment.type ENUMERATED
		comment.count 1
		comment.item.0 'Front Mic'
		comment.item.1 Line
		comment.item.2 Mic
		comment.item.3 CD
		comment.item.4 Mix
		iface MIXER
		name 'Input Source'
		value Mic
	}
	control.34 {
		comment.access 'read write'
		comment.type ENUMERATED
		comment.count 1
		comment.item.0 'Front Mic'
		comment.item.1 Line
		comment.item.2 Mic
		comment.item.3 CD
		comment.item.4 Mix
		iface MIXER
		name 'Input Source'
		index 1
		value 'Front Mic'
	}
	control.35 {
		comment.access 'read write'
		comment.type ENUMERATED
		comment.count 1
		comment.item.0 'Front Mic'
		comment.item.1 Line
		comment.item.2 Mic
		comment.item.3 CD
		comment.item.4 Mix
		iface MIXER
		name 'Input Source'
		index 2
		value 'Front Mic'
	}
	control.36 {
		comment.access 'read write'
		comment.type INTEGER
		comment.count 2
		comment.range '0 - 39'
		iface MIXER
		name 'IEC958 Playback Volume'
		value.0 0
		value.1 0
	}
	control.37 {
		comment.access 'read write'
		comment.type ENUMERATED
		comment.count 1
		comment.item.0 PCM
		comment.item.1 ADC1
		comment.item.2 ADC2
		comment.item.3 ADC3
		iface MIXER
		name 'IEC958 Playback Source'
		value PCM
	}
	control.38 {
		comment.access 'read write'
		comment.type INTEGER
		comment.count 2
		comment.range '0 - 31'
		iface MIXER
		name 'IEC958 Capture Volume'
		value.0 0
		value.1 0
	}
	control.39 {
		comment.access read
		comment.type IEC958
		comment.count 1
		iface MIXER
		name 'IEC958 Playback Con Mask'
		value '0fff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
	}
	control.40 {
		comment.access read
		comment.type IEC958
		comment.count 1
		iface MIXER
		name 'IEC958 Playback Pro Mask'
		value '0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
	}
	control.41 {
		comment.access 'read write'
		comment.type IEC958
		comment.count 1
		iface MIXER
		name 'IEC958 Playback Default'
		value '0400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
	}
	control.42 {
		comment.access 'read write'
		comment.type BOOLEAN
		comment.count 1
		iface MIXER
		name 'IEC958 Playback Switch'
		value false
	}
	control.43 {
		comment.access 'read write'
		comment.type BOOLEAN
		comment.count 1
		iface MIXER
		name 'IEC958 Capture Switch'
		value false
	}
	control.44 {
		comment.access read
		comment.type IEC958
		comment.count 1
		iface MIXER
		name 'IEC958 Capture Default'
		value '0400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
	}
	control.45 {
		comment.access 'read write user'
		comment.type INTEGER
		comment.count 2
		comment.range '0 - 255'
		comment.tlv '0000000100000008ffffec1400000014'
		iface MIXER
		name 'PCM Playback Volume'
		value.0 195
		value.1 195
	}
	control.46 {
		comment.access 'read write user'
		comment.type INTEGER
		comment.count 2
		comment.range '0 - 120'
		comment.tlv '0000000100000008fffff44800000032'
		iface MIXER
		name 'Digital Capture Volume'
		value.0 96
		value.1 96
	}
}
state.Bt878 {
	control.1 {
		comment.access 'read write'
		comment.type INTEGER
		comment.count 1
		comment.range '0 - 15'
		iface MIXER
		name 'Capture Volume'
		value 8
	}
	control.2 {
		comment.access 'read write'
		comment.type BOOLEAN
		comment.count 1
		iface MIXER
		name 'Capture Boost'
		value false
	}
	control.3 {
		comment.access 'read write'
		comment.type ENUMERATED
		comment.count 1
		comment.item.0 'TV Tuner'
		comment.item.1 FM
		comment.item.2 Mic/Line
		iface MIXER
		name 'Capture Source'
		value 'TV Tuner'
	}
}
EOF

/sbin/alsactl -f /tmp/current.alsactl store
/sbin/alsactl -f /tmp/camtasia.alsactl restore
rm -f /tmp/camtasia.alsactl

rm -f "$AUDIO"
rm -f "$VIDEO"
rm -f "${MPG}.idx"
arecord -f dat "$AUDIO" &
ffmpeg -f x11grab -r 25 -s $SIZE -i ${DISPLAY}${OFFSET} -sameq "$VIDEO"
killall arecord
ffmpeg -i "$AUDIO" -i "$VIDEO" -sameq "$MPG"
rm -f "$AUDIO"
rm -f "$VIDEO"

/sbin/alsactl -f /tmp/current.alsactl restore
rm -f /tmp/current.alsactl