Tuesday, May 6. 2008Transparent Data Encryption with Hibernate and PostgreSQLA new feature that I've been developing for Altos Research will require that we store the credit card information of our subscribers. This makes me nervous as hell. I am scared to death of the impact a data security breach would have on the customers whose data is compromised as well as on the financial viability of the company. My first engineering task in all of this was to come up with a reliable method of encrypting the billing information fields that we store in PostgreSQL. The first part of the solution was fairly straightforward - implementing a utility class that would encrypt and descrupt strings using the AES encryption algorithm. I used various examples from around the net, and put together a basic encrypt/decrypt utility that looks something like this - it takes a BASE64-encoded encrypted string value and returns the unencrypted value using the encryption key provided (and yes, these implementations are not even close to fully optimized):
public static String encryptString ( String pUnencryptedString, String pKey ) {
Security.addProvider(new SunJCE());
byte[] keybBytes = Base64.decodeBase64(pKey.getBytes());
SecretKeySpec sks = new SecretKeySpec(keybBytes, "AES");
Cipher c = Cipher.getInstance("AES");
c.init(Cipher.ENCRYPT_MODE, sks);
byte[] inputBytes = pUnencryptedString.getBytes("UTF8");
byte[] outputBytes = cipher.doFinal(inputBytes);
String base64 = new String (Base64.encodeBase64(outputBytes));
return base64;
}
public static String decryptString ( String pEncryptedString, String pKey ) {
Security.addProvider(new SunJCE());
byte[] keybBytes = Base64.decodeBase64(pKey.getBytes());
SecretKeySpec sks = new SecretKeySpec(keybBytes, "AES");
Cipher c = Cipher.getInstance("AES");
c.init(Cipher.DECRYPT_MODE, sks);
byte[] inputBytes = Base64.decodeBase64(pEncryptedString.getBytes());
byte[] outputBytes = cipher.doFinal(inputBytes);
return new String(outputBytes);
}
With the basic encrypt/decrypt utility in place, I then created a Hibernate UserType named EncryptedValueUserType to perform the needed transformations at the time the values are read from (decryption) and written to (encryption) the PostgreSQL database. That UserType class looks like this - the interesting methods being 'nullSafeGet' in which the encryption is done and 'nullSafeSet' which performs the encryption:
public class EncryptedValueUserType
implements Serializable, UserType {
private static final int[] SQL_TYPES = {Types.VARCHAR};
public int[] sqlTypes() {
return SQL_TYPES;
}
public Class returnedClass() {
return java.lang.String.class;
}
public boolean equals(Object pObject1, Object pObject2) throws HibernateException {
if (pObject1 == null && pObject2 == null) return true;
if (pObject1 == null || pObject2 == null) return false;
return (( String ) pObject1).equals(( String ) pObject2);
}
public int hashCode(Object o) throws HibernateException {
return ((String) o).hashCode();
}
public Object nullSafeGet(ResultSet pResultSet, String[] pStrings, Object o)
throws HibernateException, SQLException {
String encryptedValue = pResultSet.getString(pStrings[0]);
return pResultSet.wasNull() ? null : decryptString (encryptedValue, "MY_AES_ENCRYPTION_KEY");
}
public void nullSafeSet(PreparedStatement pPreparedStatement, Object o, int i)
throws HibernateException, SQLException {
if ( o == null ) {
pPreparedStatement.setNull(i, Types.VARCHAR);
} else {
pPreparedStatement.setString(i, encryptString( (String) o ), "MY_AES_ENCRYPTION_KEY");
}
}
public Object deepCopy(Object o) throws HibernateException {
return o;
}
public boolean isMutable() {
return false;
}
public Serializable disassemble(Object o) throws HibernateException {
return (Serializable) o;
}
public Object assemble(Serializable pSerializable, Object o) throws HibernateException {
return pSerializable;
}
public Object replace(Object o, Object o1, Object o2) throws HibernateException {
return o;
}
}
Of course you should not embed the actual encryption key directly in the Java source file as I show above, but you get the idea. Now all you need to do to use this is assign the 'EncryptedValueUserType' to any database field you would like to encrypt in your Hibernate mapping file (hbm.xml):
<property name="accountNumber" type="com.altosresearch.utils.EncryptedValueUserType" column="account_number" />
And now all your business logic nees to do is call the normal getter/setter methods for this property. All of the security work is done completely transparently. This makes it VERY simple to encrypt lots of different fields besides credit card numbers, such as passwords and even address information.
Wednesday, April 9. 2008Pascal's wager as an argument for environmental responsibilityI came across a comment from a digg.com user today that sounded remarkably like Pascal's Wager applied to environmentalism. The comment was in response to the new TED.org talk by Al Gore in which he updates his now famous Inconvenient Truth slide deck with more recent data. While the talk itself is definitely worth spending 30 minutes to watch, this commenter on digg.com is, in my opinion, doing a disservice to the scientific community and scientific evidence accumulated to date.
Wikipedia does a good job of explaining what Pascal's Wager is and when it was originally made, so I'll not revisit that. Here is what I think the Environmentalist's Wager might look like:
What bothers me about such a formulation is that it cheapens the scientific evidence. The findings are quire clear and unambiguous - the earth is getting warmer. In the most recent IPCC report this assertion was assigned a confidence rating of 'Greater than 99%'. So to argue in favor of being environmentally responsible 'on the off chance that there is a problem' seems to be doing a disservice to the movement. An alternative formulation of this could be based on the more conservative (given a 'Greater than 90%' confidence rating in the IPCC report) and controversial claim that humans are the cause of the warming. A Wager version of that might look like:
So what is the cause of the lack of political motivation among American's to do something significant about this? My guess is that for many people, their version of the wager looks like this instead:
Seems like wishful thinking to me. Voice chat in World of Warcaft with a USB microphone and WineI just happen to be a big fan of the PC game World of Warcraft, and I play the game regularly on my Linux (Kubuntu 7.10) desktop PC using the wonderful CrossOver Games product from Codeweavers.com. One World of Warcraft gameplay feature that I had not been able to get working was voice chat. I was able to listen to the voice chat traffic coming from other players, but never managed to get WoW to recognize any audio input device on my machine despite the fact that there are actually two audio capture devices available. The hardware configuration that I am working with includes an onboard audio device with sound out and sound in capabilities as well as a Logitech USB Webcam Pro for Notebooks. Please refer to my two previous posts for more information on how I managed to get the Logitech webcam working as an audio and video input source using the ALSA sound system.
Before proceeding, make sure that you can successfully record audio from your USB microphone/webcam using the command-line 'arecord' utility. As long as that works, the rest of the work needed to enable WoW voice chat input should be easy. As it turns out, the default Wine configuration that comes with Crossover Games will get you most of the way there. The settings that it applies should work without modification, but just in case here are screenshots from the Wine configuration application related to sound drivers and audio devices for reference. I have highlighted the important values in crudely drawn red paint: With the configuration as-is, WoW would not record any audio from the USB microphone on the 'Voice Options' tab when using the 'Test Microphone' feature. What was needed was to configured the ALSA sound system to consider the USB microphone as the 'System Default' or 'dsnoop:0' audio capture device. To do this required the creation of a simple ALSA configuration file (located at ~/.asoundrc): pcm.!default {
type asym
playback.pcm {
type plug
slave.pcm "hw:0,0"
}
capture.pcm {
type plug
slave.pcm "hw:1,0"
}
}
With that configuration in place, I was able to successfully record a test audio sample and use the voice chat feature by selecting 'System Default' as the microphone device on the WoW Voice Options control panel. If that doesn't work for you, I would suggest otaining/installing/using the 'gamix' ALSA mixer configuration utility to make sure that your microphone device is set to 'record' and that the level is set to a value > 0. For additional information on setting up an ALSA recording device, I suggest you visit this wiki entry from the Audacity Audio website. Friday, March 21. 2008The Squeezebox really is that goodSadly, I purchased my Logitech Sqeezebox back in November but had procrastinated in doing the installation until today (March!). That was a big mistake, because this little box and the service that goes with it is amazing. I purchased the Squeezebox for one very specific purpose - to enable me to access the Rhapsody.com music subscription service. I have had a Rhapsody.com account for a few years now, and have been extremely happy with the selection of music and overall performance. I am a huge fan of the 'rent my music' school of media consumption (vs. the 'own my music' school), which the Rhapsody service model directly supports. The only drawback is that you have to use your computer to access the service. I was partly handicapped due to the fact that I use Ubuntu Linux at home, limiting me to using Rhapsody.com's web-based interface. It works, but tends to cause Firefox to die periodically. What I really wanted was to play Rhapsody music using my existing (more and more infrequently used) home stereo. Although I think the Squeezebox's original intent was to allow you to play music stored on your local/home PC over your stereo, the device also supports multiple on-line music services (Rhapsody, Pandora, Last.fm and others). The setup was dead simple: Turned it on, and used the remote control to quickly configure the Squeezebox to connect to my home WiFi network. At the end of the device setup sequence, the LCD displayed a device PIN that I could use to access and then control the device over the net. I then pointed my browser at www.squeezenetwork.com and registered the device using the PIN. Once registered, I could then configure the Squeeze Network to access my Rhapsody.com account. Again, no problems encountered. And now, nirvana. Sitting at my desk in the home office accessing my squeezenetwork.com account in Firefox I can browse, search and play any of the 3 million or so tracks available within Rhapsody.com over my home stereo. All of the device capabilities can be controlled by the browser, and the changes are near instant. I click 'Next track' in the browser and within 1 second the next track starts streaming via the Squeezebox. Totally amazing. It solved my problem completely. Saturday, March 15. 200807-1101 : Chicago Lawyers' v. Craigslist IncNotch a victory for sanity and reasoned judgment coming from the US 7th District Court of Appeals. In case 07-1101: Chicago Lawyers' vs. Craigslist the court ruled strongly in favor of the defendant, Craigslist. In the lawsuit, the Chicago Lawyers' are making the argument that Craigslist is at least partially liable for the content of the advertisements placed using their website. Specifically, the case revolves around a classified advertisement in which the statement "NO MINORITIES" appeared, which is in violation of federal Equal Housing Opportunity rules. Craigslist argued - correctly - that they are not the originator of the content so they cannot be held liable under any federal laws for the words used in the classified ad. In the opinion written by Judge Easterbrook, Easterbrook makes several good points: Web sites are not common carriers, but screening, though lawful, is hard. Simple filters along the lines of “postings may not contain the words ‘white’ ” can’t work. Statements such as “red brick house with white trim” do not violate any law, and prospective buyers and renters would be worse off if craigslist blocked descriptive statements.Further, the judge points out that it would be completely impractical for a company like Craigslist to hire humans to manually review each ad for potentially illegal speech prior to posting: An online service could hire a staff to vet the postings, but that would be expensive and may well be futile: if postings had to be reviewed before being put online, long delay could make the service much less useful, and if the vetting came only after the material was online the buyers and sellers might already have made their deals. Every month more than 30 million notices are posted to the craigslist system. Fewer than 30 people, all based in California, operate the system, which offers classifieds and forums for 450 cities. It would be necessary to increase that staff (and the expense that users must bear) substantially to conduct the sort of editorial review that the Lawyers’ Committee demands—and even then errors would be frequent. And yes, all of this has been covered extensively elsewhere, but something funny occurred to me as I was reading about the case and the ruling. Although I do not know for certain in which city this particular classified advertisement was placed, it seemed that the population trends throughout most of the USA would make such an advertisement fairly counterproductive. For example, assume that by "NO MINORITIES" the apartment owner meant "No people who are not of white European descent". Next, assume the ad was posted in a city like San Francisco, the home of Craigslist. Based on 2006 US Census data, that meant that they would only consider 57% of the city population as eligible to rent their apartment. Can you imagine any sensible business person declaring nearly half of all possible customers were unwelcome because of the color of their skin? I suppose you can, but they'd probably be out of business fairly quickly. Friday, March 7. 2008But wait, there's more to using a webcam on Linux!Just when you thought it was safe to go back in the webcam waters, I've got an update to my earlier entry on how to record high quality video footage from a USB webcam (the Logitech QuickCam Pro for Notebooks) on Ubuntu Linux. While I was reasonably happy with the outcome of my earlier results, I was bothered by the fact that I had not been able to record video at the highest claimed frame rate (30 frames per second, 30fps) and resolution (800 px wide by 600 px high). The best I'd been able to achieve earlier was 640x480@15fps. After a few email messages exchanged with members of the (very helpful) gstreamer community, I was able to dig to the bottom of the problem. This understanding then allowed me to construct a gstreamer pipeline to capture 800x600@30fps video. Edgard Lima, the maintainer of the v4l2 gstreamer source driver taught me a nice troubleshooting trick: set the environment variable 'GST_DEBUG' to the value "*v4l2*:5" before executing your gstreamer command. In the Fish shell, this looks something like this: set -x GST_DEBUG "*v4l2*:5" gst-launch-0.10 -v v4l2src num-buffers=1 ! fakesinkThis command will produce a LARGE amount of debug output, but it will tell you everything that the v4l2src driver was able to learn about your video camera. The first important part is that the v4l2src driver acknowledges that the QuickCam can provide video in one of two pixel formats - MJPEG (or 'motion jpeg') and YUYV (also called 'YUV2'): v4l2src0> getting src format enumerations v4l2src0> index: 0 v4l2src0> type: 1 v4l2src0> flags: 00000001 v4l2src0> description: 'MJPEG' v4l2src0> pixelformat: MJPG v4l2src0> index: 1 v4l2src0> type: 1 v4l2src0> flags: 00000000 v4l2src0> description: 'YUV 4:2:2 (YUYV)' v4l2src0> pixelformat: YUYV v4l2src0> got 2 format(s)Further down in the output you will see the driver enumerate every possible combination of pixel format, height, width and framereate that the device is capable of (abbreviated). The important thing to notice is that when in the YUV2 pixel format and 800x600 pixels the camera will only deliver a maximum of 25fps. In order to enable 30pfs capture, you must use the MJPEG pixel format: [snip] v4l2src0> get frame interval for 800x600, MJPG v4l2src0> adding discrete framerate: 30/1 v4l2src0> adding discrete framerate: 25/1 v4l2src0> adding discrete framerate: 20/1 v4l2src0> adding discrete framerate: 15/1 v4l2src0> adding discrete framerate: 10/1 v4l2src0> adding discrete framerate: 5/1 [snip] v4l2src0> get frame interval for 800x600, YUYV v4l2src0> adding discrete framerate: 25/1 v4l2src0> adding discrete framerate: 20/1 v4l2src0> adding discrete framerate: 15/1 v4l2src0> adding discrete framerate: 10/1 v4l2src0> adding discrete framerate: 5/1 [snip]which is then immediately followed by the combination that the driver has selected for use in recording - the process known as 'fixating':
v4l2src0> fixating caps image/jpeg, width=(int)160, height=(int)120,
framerate=(fraction){ 30/1, 25/1, 20/1, 15/1, 10/1, 5/1 }
v4l2src0> trying to set_capture 160x120 at 30/1 fps, format MJPEGNow that I knew what the device was capable of, it was only a matter of time before I was able to figure out how to tell gstreamer to use the specific settings (800x600@30fps) that I wanted. I then turned down the debug level and added the '-v' switch to gstreamer to get more verbose information on the stream:
> set -x GST_DEBUG "*v4l2*:1"
> gst-launch-0.10 -v v4l2src num-buffers=1 !
image/jpeg,width=800,framerate=30/1,rate=30 ! fakesinkThe output confirmed that the stream was exactly as I wanted it. This output tells you that the video originates as 800x600@30fps MJPEG and stays that way as it is sent to its destination (in this case, the 'fake' destination named 'fakesink'):
/pipeline0/capsfilter0.src: caps = image/jpeg,
width=(int)800, height=(int)600, framerate=(fraction)30/1, rate=(int)30
/pipeline0/capsfilter0.sink: caps = image/jpeg,
width=(int)800, height=(int)600, framerate=(fraction)30/1, rate=(int)30
/pipeline0/fakesink0.sink: caps = image/jpeg,
width=(int)800, height=(int)600, framerate=(fraction)30/1, rate=(int)30
With this confirmed, it was then just a matter of using this video pipeline in an overall processing pipeline to combine audio and generate a file that can still be easily played back and uploaded to YouTube. That command is:
gst-launch-0.10 v4l2src queue-size=16 ! stamp sync-margin=1 sync-interval=1
! image/jpeg,width=800,framerate=30/1,rate=30 ! ffdec_mjpeg
! queue2 max-size-buffers=10000 max-size-bytes=0 max-size-time=0
! ffmpegcolorspace ! theoraenc quality=60 name=venc alsasrc device="hw:1,0"
! audio/x-raw-int,rate=16000,channels=1,depth=16 ! audioconvert
! queue2 max-size-buffers=10000 max-size-bytes=0 max-size-time=0
! vorbisenc quality=0.9 name=aenc oggmux name=mux
! filesink location=out.ogg aenc. ! mux. venc. ! mux.
An example of a file encoded using this command can be from here.
Wednesday, March 5. 2008Using a USB Webcam on Ubuntu Linux to produce good quality video captures that can be uploaded to YouTube directlyPlease excuse the lengthy title, but after spending the better part of the last two days getting this entire process nailed down I want to make sure that this write-up is indexed by the search engines as comprehensively as possible. The whole thing started when I was asked the seemingly simple question of "Is it possible to use a webcam on Linux?" My initial answer was "Yeah, sure, I think." The goal was clear - configure a low-cost webcam on Ubuntu Linux to capture video and audio in a form that could be used for both voice/video chat (Skype) as well as for upload to video sharing sites like YouTube. A bit of digging later, I had put together enough information to convince myself that it could be done. To start, I needed the hardware - and I opted for the Logitech QuickCam for Noteooks Pro. It is a USB device with both camera and microphone. I selected this model because it supported relatively high resolutions and was fully supported by the Linux USB Video Class driver which are compatible with the Video For Linux, version 2 (v4l2) framework and kernel drivers. I wanted v4l2 support because the Skype Beta client for Linux uses that framework for voice and video calling. As it turns out, all of the drivers to make this work are included in a default installation of Ubuntu 7.10, so nothing additional needs to be installed as far as voice calling with Skype is concerned. To first test things out, I downloaded and installed the Skype beta client for linux. I plugged in the USB webcam, launched Skype, and select Options -> Audio Devices. I selected the USB device from the list of possible sound input sources. I then selected Options -> Video Devices and did the same. Pressing the 'Test' button displayed a small video feed from the camera. To confirm, I placed a call to a friend, and everything just worked. The video quality was good and the audio quality was fine. To get a closer look at how the kernel treats the device, I used the 'lsusb' command to get a list of the USB devices: # lsusb Bus 002 Device 002: ID 045e:00db Microsoft Corp. us 002 Device 003: ID 047d:1020 Kensington Bus 002 Device 001: ID 0000:0000 Bus 001 Device 009: ID 046d:0991 Logitech, Inc. Bus 001 Device 006: ID 0424:223a Standard Microsystems Corp. 8-in-1 Card Reader Bus 001 Device 005: ID 0424:2504 Standard Microsystems Corp. Bus 001 Device 002: ID 0424:2502 Standard Microsystems Corp. Bus 001 Device 001: ID 0000:0000 There in the middle of the list is 'Logitech, Inc.' the maker of the webcam. For information on the list of sound devices that had been detected by ALSA, the audio framework: # arecord -l card 0: CK804 [NVidia CK804], device 0: Intel ICH [NVidia CK804] Subdevices: 1/1 Subdevice #0: subdevice #0 card 0: CK804 [NVidia CK804], device 1: Intel ICH - MIC ADC [NVidia CK804 - MIC ADC] Subdevices: 1/1 Subdevice #0: subdevice #0 card 1: U0x46d0x991 [USB Device 0x46d:0x991], device 0: USB Audio [USB Audio] Subdevices: 1/1 Subdevice #0: subdevice #0 This told me that the microphone on the webcam was registered as 'card 1, subdevice 0'. This information will be used later when we attempt to record audio/video. This can also be determined from the output of /proc/asound/pcm: # cat /proc/asound/pcm 00-02: Intel ICH - IEC958 : NVidia CK804 - IEC958 : playback 1 00-01: Intel ICH - MIC ADC : NVidia CK804 - MIC ADC : capture 1 00-00: Intel ICH : NVidia CK804 : playback 1 : capture 1 01-00: USB Audio : USB Audio : capture 1Again, the USB Audio is registered as card 1, subdevice 0. With the basics working, the next thing I wanted to do was make a high-quality video recording that would be suitable for upload to YouTube. After another round of digging it looked like two video capture frameworks had direct support for v4l2 - mplayer/mencoder and gstreamer. I first attempted to use mencoder, but after several hours was not able to get things working. I was able to capture video only, and as soon as I tried to mix in the audio input, mencoder would die with a 'Floating point exception' error. A message to the mplayer-devel mailing list about the problem went unanswered. So next I turned my attention to gstreamer. Although I will spare you details, after about two days of hacking at this I was able to achieve my goal: High resolution video, good audio quality, and perfect synchronization between the two. First, make sure you have the full set of gstreamer-plugins (good, bad, and ugly) installed. This should include: # dpkg --list '*gst*' | grep ii ii gstreamer-tools 0.10.14-1ubuntu3 Tools for use with GStreamer ii gstreamer0.10-alsa 0.10.14-1ubuntu3 GStreamer plugin for ALSA ii gstreamer0.10-esd 0.10.6-0ubuntu4 GStreamer plugin for ESD ii gstreamer0.10-ffmpeg 0.10.2-2ubuntu1 FFmpeg plugin for GStreamer ii gstreamer0.10-gnomevfs 0.10.14-1ubuntu3 GStreamer plugin for GnomeVFS ii gstreamer0.10-gnonlin 0.10.9-1 non-linear editing module for GStreamer ii gstreamer0.10-plugins-bad 0.10.5-4ubuntu1 GStreamer plugins from the "bad" set ii gstreamer0.10-plugins-bad-multiverse 0.10.5-1 GStreamer plugins from the "bad" set (Multiv ii gstreamer0.10-plugins-base 0.10.14-1ubuntu3 GStreamer plugins from the "base" set ii gstreamer0.10-plugins-base-apps 0.10.14-1ubuntu3 GStreamer helper programs from the "base" se ii gstreamer0.10-plugins-good 0.10.6-0ubuntu4 GStreamer plugins from the "good" set ii gstreamer0.10-plugins-ugly 0.10.6-0ubuntu2 GStreamer plugins from the "ugly" set ii gstreamer0.10-plugins-ugly-multiverse 0.10.6-0ubuntu1 GStreamer plugins from the "ugly" set (Multi ii gstreamer0.10-tools 0.10.14-1ubuntu3 Tools for use with GStreamer ii gstreamer0.10-x 0.10.14-1ubuntu3 GStreamer plugins for X11 and Pango ii libgstreamer-plugins-base0.10-0 0.10.14-1ubuntu3 GStreamer libraries from the "base" set ii libgstreamer-plugins-base0.10-dev 0.10.14-1ubuntu3 GStreamer development files for libraries fr ii libgstreamer0.10-0 0.10.14-1ubuntu3 Core GStreamer libraries and elements ii libgstreamer0.10-dev 0.10.14-1ubuntu3 GStreamer core development files ii python-gst0.10 0.10.8-1ubuntu1 generic media-playing framework (Python bind Next, we'll need to install one non-standard package called GEntrans which is maintained by Mark Nauwalaerts. I discovered this package via an email sent to the GStreamer-devel mailing list asking for help with my initial video recording problems: I was completely unable to get good synchonized audio and video recordinds. The audio always lagged or was shifted or garbled in some way. The GEntrans package is a gstreamer plugin that provides a gstreamer element named 'stamp' that can apply timestamps to incoming video streams in a way that they can later by perfectly matched to the incoming audio streams to produce perfectly sync'd output. You can download the source from the sf.net site, and install it with: ./configure --prefix=/usr sudo make install I suggest you use the --prefix=/usr so that the compiled plugins will go into the standard gstreamer plugins directory (/usr/lib/gstreamer-0.10). That makes them easier to use in the next step. After many, many rounds of experimentation and help from Mark, I put together the following gstreamer recording command: # gst-launch-0.10 v4l2src queue-size=16 ! stamp sync-margin=1 sync-interval=1
! video/x-raw-yuv,width=800,height=600,framerate=15/1
! queue2 max-size-buffers=1000 max-size-bytes=0 max-size-time=0 ! ffmpegcolorspace
! theoraenc quality=60 name=venc alsasrc device="hw:1,0"
! audio/x-raw-int,rate=16000,channels=1,depth=16
! audioconvert ! queue2 max-size-buffers=1000 max-size-bytes=0 max-size-time=0
! vorbisenc quality=0.9 name=aenc oggmux name=mux ! filesink location=test.ogg aenc. ! mux. venc. ! mux
Yes, that is actually one single command-line command. The basic interpetation is this: Create a gstreamer processing pipeline with the following characteristics:
What I did not know, but was thrilled to discover, is that YouTube accepts files formatted like this - Theora encoded video, Vorbis encoded audio, wrapped into an Ogg formatted file - directly for upload! You can see the results of me wishing my nephew a happy birthday here on YouTube. Keep in mind that the YouTube processed version is not as high quality as the original Ogg file that was produced during the recording locally. The fact is that it can be done and that it works! Side note: If you just want to record audio to an mp3 file using the microphone on your webcam, use the following command: gst-launch-0.10 alsasrc device="hw:1,0"
! audio/x-raw-int,rate=16000,channels=1,depth=16
! audioconvert ! lame ! filesink location=test.mp3
To do the same but using Vorbis encoded Ogg-formatted output:
gst-launch-0.10 alsasrc device="hw:1,0"
! audio/x-raw-int,rate=16000,channels=1,depth=16
! audioconvert ! vorbisenc ! oggmux
! filesink location=test.ogg
And if you just want to play the audio back to yourself:
gst-launch-0.10 alsasrc device="hw:1,0"
! audio/x-raw-int,rate=16000,channels=1,depth=16
! audioconvert
! alsasink
If all you want is to show the video being captured by the camera in a window on your desktop:
gst-launch-0.10 v4l2src
! video/x-raw-yuv,width=800,height=600
! ffmpegcolorspace
! xvimagesink
For those of you who need to adjust the color/contrast/brightness settings of their webcam, I suggest luvcview.
Thursday, February 28. 2008HOWTO: Configure apache to correctly serve Zip files to Internet ExplorerOne of the more irritating problems to affect my Altos Research users recently was the inability to successfully download a Zip archive file from our servers using Internet Explorer. It didn't matter what version of IE they were using (IEv6 or IEv7) or which version of Windows (XP or Vista). Every time they downloaded the Zip and attempted to open it they would see an error message informing them that the file was corrupt or invalid. The problem did not occur when using any other web browser - Firefox, Safari or Opera. Only Internet Explorer seemed to be corrupting the Zip files that were downloaded from our servers. Of course, the fact that I have only Linux workstations here in my home office prevented me from replicating the problem, there were enough reports from reliable sources to indicate that this wasn't just a matter of a few misconfigured PCs or network proxies. In the case of the Altos Research website, the Zip files we were serving are dynamically generated by our application. They are not just static files being served up by the web server. Each Zip file contains the customized reports for each of our customers. Because of that, my first guess was that we were doing something wrong with the generation of the Zip data or the HTTP headers that were being sent when the file was downloaded. To test this theory, I performed the following experiment: I logged into the AR application as one of our customers using Firefox. I then downloaded the report Zip file to my local workstation. I verified that the file was not corrupt - I was able to open the Zip archive and extract the PDF files it contained. Next, I manually uploaded that file to the AR web server document root where it could be downloaded directly from Apache. I then asked several Windows/IE users to try and download/open that uploaded file. In every case, the IE users were still unable to open the Zip file, even when it was not dynamically generated and served by our application. This told me that the problem was not being caused by the application code we use to generate the Zip archive. The problem must be due to something in our Apache server configuration. A note about the Apache configuration: From the very start, I had configured Apache to use the mod_deflate plug-in for HTTP-level compression. This is almost always a good idea, as it decreases the bandwidth used and generally speeds up content delivery to the end-users. I knew from past problems that it was smart to restrict mod_deflate to only apply compression to certain content types (text/html, text/xml, for example) and exclude certain file types that already contain compressed data (image/jpeg, image/gif, application/pdf). There is no sense in having Apache compress already-compressed files, after all. So my initial mod_deflate configuration looked like this: SetOutputFilter DEFLATE DeflateFilterNote ratio AddOutputFilterByType DEFLATE text/html text/plain text/xml text/javascript text/css text /plainI had assumed, but did not verify, that this configuration would result in only files of those mime-types being compressed using HTTP GZIP compression. I was wrong. I discovered just how wrong I was by performing the most basic test: Downloading a Zip file from Apache while monitoring the traffic using Wireshark. Much to my surprise, the packet captures of the Zip file downloading traffic indicated quite clearly that Apache was still using GZIP compression to transfer the data (abbreviated a bit): GET /test.zip HTTP/1.1 Host: www.altosresearch.com Accept-Encoding: gzip,deflate HTTP/1.1 200 OK Date: Thu, 28 Feb 2008 20:29:01 GMT Server: Apache/2.0.48 (Fedora) Content-Encoding: gzip Content-Type: application/zipWhat?!?! I thought that my mod_deflate configuration would force the plugin to ONLY compress output that was one of the mime-types listed in the 'AddOutputFilterByType' parameter. Apparently, that is not the case. So I started to dig a bit more and discovered that the mod_deflate plugin will make use of an environment variable named 'no-gzip'. If this variable is set when the HTTP request is made, the mod_deflate will NOT compress the output data. There are some basic configuration examples of how to use this for static files served directly by Apache: SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary SetEnvIfNoCase Request_URI \.pdf$ no-gzip dont-vary SetEnvIfNoCase Request_URI \.zip$ no-gzip dont-varyAfter implementing these configuration changes and reloading our Apache configuration, I was able to verify that files ending with a '.zip' suffix were not being GZIP compressed in transit: GET /test.zip HTTP/1.1 Host: www.altosresearch.com Accept-Encoding: gzip,deflate HTTP/1.1 200 OK Date: Thu, 28 Feb 2008 20:45:56 GMT Server: Apache/2.0.48 (Fedora) Last-Modified: Thu, 28 Feb 2008 00:58:06 GMT Content-Type: application/zipNotice that there is no 'Content-Encoding: gzip' header - that is the important part. By setting the 'no-gzip' variable for requests ending in '.zip', mod_deflate was prevented from compressing the output. I once again asked a few Internet Explorer users to download that same file and verify that it was not being corrupted. Success! Multiple testers, using combinations of Windows XP and Vista with IE6 and IE7 were all able to successfully download the Zip file and extract the contents. In most cases, that would be the end of the story, but the Altos Research application makes things a bit trickier. Trickier because our application serves dynamically generated Zip archives, and the URL that our users click to download that archive looks like this instead: http://www.altosresearch.com/altos/app?service=pdfzipBecause the 'Request_URI' portion of this URL is only '/altos/app', it cannot be used with the SetEnvIf directives to set the 'no-gzip' environment variable. Instead, I had to resort to a much more obfuscated mod_rewrite solution: RewriteCond %{QUERY_STRING} ^service=pdfzip$
RewriteRule ^(.*)$ $1 [QSA,E=no-gzip:1,PT,L]
In the first line, I am using RewriteCond to ensure that the rule on line two will only be applied to requests where the query string (the stuff that comes after the '?' character) is exactly equal to 'service=pdfzip'. In line 2 I am preserving the request string (not actually doing any rewriting) while specifying that the query string be appended (QSA) and that the environment variable 'no-gzip' be set with a value of '1'. In other words, I am telling Apache to set 'no-gzip=1' for any request with 'service=pdfzip' in the query string and to leave everything else as-is.
The actual final rewrite rule is a bit more complicated because we need to prevent the GZIP compression of both dynamically generated Zip files as well as dynamically generated PDF files. The final version is: RewriteCond %{QUERY_STRING} ^.*service=pdf$ [or]
RewriteCond %{QUERY_STRING} ^service=pdfzip$
RewriteRule ^(.*)$ $1 [QSA,E=no-gzip:1,PT,L]
With these rules in place and our Apache configuration reloaded, all of our Internet Explorer users are now able to download uncorrupted Zip and PDF files.
Monday, February 25. 2008Affordable health care vs. affordable health insuranceUsing health insurance to pay for your yearly physical, eye exam or dentist visit is like using car insurance to pay for gas and tune-ups. Can you imagine the response you'd get from your State Farm Insurance rep if you asked "Does this auto insurance plan cover my gasoline and oil-changes too?" Nobody on earth would even consider such a thing. The very idea is just silly. But if you apply the same reasoning to health insurance, you have to conclude that at some point, the American consumer has come to equate health insurance with health care. I don't know anyone who would confuse their car insurance plan for a car maintenance plan, but I know plenty of well-informed people who no longer see that distinction when it comes to their personal health expenditures. Interesting, but so what? I recently read through the Obama and McCain issue statements on health care, hoping to see for myself what the important differences are (and there are many). After reading a few paragraphs into each, the following theme emerged: The emphasis of both of their plans is to reduce the cost and increase the availability of health insurance. Although the costs of health care are discussed, it is not the primary focus of either plan. That is when it struck me: Americans do not want affordable health insurance. They want affordable health maintenance. People do not buy home or auto insurance to cover routine maintenance of their homes and cars. We buy those types of insurance to protect ourselves against rare catastrophic events. In terms of health, the desire is for the average family to be able to afford the routine costs of health maintenance. In a less insane world, these families would purchase health insurance like they did auto insurance - to protect themselves financially from infrequent catastrophic events. Tying these two ideas together: It seems to me that driving force behind both candidates health care plans is the desire to make insurance more affordable. At the same time, it looks like we Americans are using health insurance for all the wrong reasons (regularly scheduled maintenance vs. only catastrophic events). In the end, all we really want is for the typical family to be able to afford the costs of basic health maintenance. Unfortunately, neither candidate seems to be addressing that fundamental issue. By casting the argument in terms of health insurance and not health maintenance, it only reinforces the confusion. Thursday, February 21. 2008527 PACs: The groups everyone loves to hate
In my inbox this morning was a campaign email from the Barack Obama for President 2008 campaign manager David Plouffe. The intent of the message was to warn Obama supporters of the formation of a pro-Clinton/anti-Obama 527 Group named the "American Leadership Project". In that message, the funding structure for this new group was portrayed as:
Seems innocuous enough until you consider the fact that a different 527 Group, which is subject to the same funding legalities as the Swift Boat Veterans for Truth, is the Obama-supporting MoveOn.org 527 Group. My question back to the Obama campaign would be this: If you are going to oppose 527 Groups (American Leadership Project, Swift Boat Veterans for Truth) because of their funding structure - why would you not also opppose MoveOn.org and their fundraising efforts on behalf of pro-Obama advocacy? Seems a bit hypocritical to me. Monday, January 28. 2008Rubtting a DMCA take-down order with TOR: The Onion RouterMy first email check this morning showed a message from the hosting provider of www.buberel.org, The Planet (http://www.theplanet.com/) with the following message subject: DMCA Complaint from the ESA for 69.57.148.142 Upon further reading, it appears as though the Entertainment Sofware Association (ESA) believed that my server was responsible for hosting a bittorrent of the newly released PC game 'Prey'. The first thought to enter my head was "Oh crap, someone has hacked my server and is using it to distribute pirated software!". After searching the disks and checking the security logs, I was convinced that no such copyright infringing materials were being distributed from my server. Then it suddenly dawned on me that it must be my hosting/operating of a TOR (The Onion Router) anonymous proxy server that had convinced them that my server was the actual source of the pirated materials.
Although I do not condone the illegal distribution of copyrighted software - I make my own living writing software after all - I was thrilled with the response from The Planet's legal team. It was clear that they understand the issue and that people like myself run TOR proxies to ensure anonymity and freedom of communications on the internet. From time to time this capability will be used to protect illegal activity, but there are most definitely "substantial non-infringing uses" of the technology (protecting political dissidents, whistle blowers, etc.). Notch a small victory for freedom! Wednesday, January 23. 2008HOWTO: Configure apache to correctly serve debian package filesJust recently a new version of my favorite command shell (Fish, the friendly interactive shell, version 1.23.0) was released. In order to keep my Ubuntu 7.10 desktop system updated, I went ahead and created a debian package file (.deb) to install locally. After successfully installing the file myself, I decided that it might be useful to other Unbuntu users who use the older version. After uploading it to www.buberel.org and testing the download, I noticed that the file was shipped with an unknown mime-type, causing Firefox to open the contents of the compressed file in the browser directly. After a bit of digging around, I added the following line to my Apache configuration file and reloaded the server: AddType application/x-deb .deb The package can now be correctly downloaded from here. Sunday, January 6. 2008John McCain misrepresenting the Presidential Oath of OfficeAs a result of McCain's strong showing in the Iowa Republican nomination vote, I decided to read up a bit about John McCain. I was skimming his website's 'Issues' section when I came accross his statement on National Security:
For some reason, that wording reminded me of the US Presidential Oath of Office:
The emphasis, in both cases, is mine. If you are going to put 'preserve and protect' in quotation marks, you might as well get the latter half of the quote right. The president is sworn to uphold and protect the constitution, not the American citizens. Just thought that was worth pointing out. Thursday, January 3. 2008My rules of life in Silicon Valley
Here are the rules I have formulated to help myself and others understand what it is like to live and work as an entrepreneur here in Silicon Valley. I am often asked by recent arrivals, family members and friends from the midwest what life here is like. Although this does not capture everything about the culture here, it helps me keep everything in perspective.
When you find yourself in the company of more than your immediate friends and close acquaintances, you should make the following assumptions: 1. You are not the wealthiest person in the room. 2. You are not the smartest person in the room. The reasons for these assumptions is fairly straighforward. Every time I've been in a room of 10 or more people, both (1) and (2) were completely accurate. Like my neighbors across the street and their new yellow Ferrari or the vast quantities of Stanford graduates around me, I can safely assume that I am not at the top of any pecking order. |