Tuesday, January 22, 2008
It took me 5 months to actually get it out the door, but I've just published a new white paper on the performance implications of P/Invoking in the COmpact Framework.  Check it out here.

1/22/2008 5:12:07 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  | 
 Monday, January 21, 2008
A customer asked us to integrate a new camera with Padarn recently (the D-Link DCS-900) to try to do a similar thing that I had done in my PTZ camera demo.  Well this camera was a little different and a whole lot easier to get "streaming" data from.  I ended up writing a small wrapper class that takes the MJPG data coming from the camera, parses it into in-memory Image classes, and then those are available for use.

Once I had that I figured it would be interesting to serve up a Padarn page that did nothing but return the latest frame data from that camera - after all the camera doesn't support PTZ and just a button that takes a picture didn't seem terribly new or informative. 

The problem was that the version of Padarn I was using (1.0.5020) didn't support writing a stream of bytes to the Response.  Well as of 1.0.5030 Padarn now supports WriteBytes (and a few other things like CacheBehavior) and I created a page that dynamically loads the images into an on-page IFrame and allows you to adjust the refresh rate dynamically without ever having to send a full page request in again.

I can see that this could be useful for dynamic updates to a page for something like a control system gauge or for animating building automation status items.

Check out the new sample here.

The code for the DCS-900 looks like this (notice that the ICamera interface changed a little, so the PTZ camera also had to be altered.  I simply stubbed out the new methods).

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Threading;
using System.IO;
using System.Net;
using System.Net.Sockets;
using OpenNETCF.Peripherals.Camera;

namespace OpenNETCF.Peripherals
{
  public class DCS900 : ICamera
  {
    private Image m_lastImage = null;
    private volatile bool m_stopThread = true;
    Thread m_grabberThread;
    private IPAddress m_ip;

    public DCS900()
    {
    }

    public void Initialize(IPAddress ipAddress, string userName, string password)
    {
        m_ip = ipAddress;
    }

    public bool SupportsPanTilt
    {
        get { return false; }
    }

    private int FindFrameBoundary(byte[] data, int offset, int maxLength)
    {
        byte[] frameDelimiter = Encoding.ASCII.GetBytes("--video boundary--");
        int n = 0;
        bool match;

        for (; offset < maxLength; offset++)
        {
            match = true;
            if (data[offset] == frameDelimiter[n])
            {
                match &= true;
                n++;
                if (n >= frameDelimiter.Length)
                {
                    // we've found the header
                    return offset + 1;
                }
            }
            else
            {
                match = false;
                n = 0;
            }
        }

        return -1;
    }

    private Image ImageFromFrameData(byte[] data, int frameLength)
    {
        int start = 50;

        // find the data start - it will be between 50 and 54 bytes in from the frame start
        while (data[start] != 0xFF)
        {
            start++;
            if (start > 54) return null;
            if (data[start + 1] == 0xD9) break;
        }

        try
        {
            using (MemoryStream stream = new MemoryStream(data, start, frameLength - start))
            {
                Image image = new Bitmap(stream);
                stream.Close();
                return image;
            }
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex.Message);
        }

        return null;
    }

    public void Start()
    {
        m_grabberThread = new Thread(GrabberThreadProc);
        m_grabberThread.IsBackground = true;
        m_grabberThread.Start();
    }

    public void Stop()
    {
        m_stopThread = true;
    }

    public bool IsRunning
    {
        get { return !m_stopThread; }
    }

    private void GrabberThreadProc()
    {
        lock (m_grabberThread)
        {
            m_stopThread = false;

            Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            s.Connect(new IPEndPoint(m_ip, 80));

            string header = "GET /VIDEO.CGI HTTP/1.0\r\nUser-Agent: user\r\nAuthorization: Basic YWRtaW46REVVU1Q=\r\n\r\n";
            byte[] data = Encoding.ASCII.GetBytes(header);
            byte[] image = new byte[32768]; // 32k buffer is more than adequate for this camera

            s.Send(data);

            data = new byte[2048];

            int offsetStart = 0;
            int offsetEnd = 0;
            int ptr = 0;

            int length = s.Receive(data);

            do
            {
                offsetStart = FindFrameBoundary(data, 0, length);
            } while (offsetStart < 0);

            while (!m_stopThread)
            {
                offsetEnd = FindFrameBoundary(data, offsetStart, length);

                while (offsetEnd < 0)
                {
                    int copyLength = length - offsetStart;
                    Buffer.BlockCopy(data, offsetStart, image, ptr, copyLength);
                    offsetStart = 0;
                    ptr += copyLength;

                    length = s.Receive(data);
                    offsetEnd = FindFrameBoundary(data, offsetStart, length);
                }

                if (ptr > 50)
                {
                    Buffer.BlockCopy(data, offsetStart, image, ptr, offsetEnd - 18);
                    ptr += offsetEnd - 18;

                    m_lastImage = ImageFromFrameData(image, ptr);

                }
                offsetStart = offsetEnd;
                ptr = 0;

                Thread.Sleep(200);
            }

            s.Close();
        }
    }

    public Image GetImage()
    {
        return m_lastImage;
    }

    public void Pan(PanDirection direction)
    {
        throw new NotSupportedException();
    }

    public void Tilt(TiltDirection direction)
    {
        throw new NotSupportedException();
    }
  }
}


1/21/2008 7:56:46 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  | 
 Thursday, January 17, 2008
We've had a little bit of a brand confusion problem for a while now.  Many people incorrectly refer to our Smart Device Framework library as "OpenNETCF" - so you hear things like "I'm using OpenNETCF version 2.1" which is a bit annoying.  OpenNETCF is the company name.  We have multiple products.  You're not using Microsoft 8.0 are you?  "Hey look at how smart I am!  I listen to music on my Apple 4.0."

But it seems to have gotten worse.  A friend just sent me a clip of a resume he received.  Of course it seems to have way more on it than a person probably would know having graduated probably 2 or 3 years ago (it's clipped, but I assume that the candidate was at UT for probably 2 years) but note the list of technologies.


1/17/2008 5:48:13 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  | 
 Thursday, January 03, 2008
As it seems is always the case, as soon as I published the WIA library, I found some problems.  The original library worked fine in the test application, but as soon as I moved it into a larger-scale application that does a lot of wacky multi-threading, multi-AppDomain and reflection calls I started to see bad behavior.  It all worked fine if you kept the original list of cameras, but if you added or removed them they started shifting around or existing cameras no longer worked.  So I've debugged and reworked it a bit and the new version appears to be much more stable and friendly in a production environment. 

Download the code at the original blog entry.

1/3/2008 5:35:38 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  | 
 Monday, December 31, 2007
Last Christmas (2006) I gave my father a USB turntable so he could start ripping his hundreds upon hundreds of vinyl albums to MP3.  Of course I assumed that all was going well for most of the year, as he was using it and seemed happy with it. 

Then about 2 months ago I actually got a copy of the Simon and Garfunkel song Cecilia from him (don’t tell the RIAA) and I noticed that it didn’t show up in my Zune software or in WMP.  A little further digging and I found it down in “unknown artist, unknown album, unknown song”.  How wonderfully useful.  So I asked him why the hell it didn’t have the track info.  He replied that the software he has basically ripped the vinyl to one giant wave file, then another piece of software would divide it into MP3 tracks, but it didn’t have a good way of tagging each track efficiently.  To give it track info, he’d have to pull it into some other software like WMP and alter it there, which is a very tedious process.

Well I decided to take a look into how media playback software actually got the info about the tracks.  I assumed it was something embedded in the software so I did a little searching for a specification, and found that the info is indeed stored in what is called an ID3 header tag.  Well since I enjoy writing software from nothing but a spec (really, I do – masochistic I know) I decided that maybe I’d put together some software that makes tagging albums just a bit easier using mostly drag and drop and inferring info based on how it’s organized in the file system.

The first step, however was to create a library that allowed me to read and write the ID3 tags.  That yielded the OpenNETCF.Media.MP3 library (this library does *not* offer MP3 playback capabilities, so don’t ask).

Once I had the library mostly done (meaning coded but not heavily tested) I then started on a desktop application I called AudioTagR.  The idea behind AudioTagR is that most people organize their music on their file system.  My dad and I both have a single folder that contains a folder for each artist.  Each artist folder contains one or more folders for each album.  Each album folder contains files that are the MP3 songs, and the song filenames are in the format “NN <song name>.mp3” where NN is the track number from the album.  AudioTagR assumes that you use this hierarchy to infer a lot of the information about unknown tracks (though it allows you to turn off inferring).

So the paradigm is that on the right is a folder view of the file system, with a root being the “root” of where your music is stored.  On the left is an “organizer” that is used for nothing but tagging tracks through inference.  You drag a song from the file tree into the organizer tree and it infers artist, album, song name and track if nothing exists.  If some info exists, or you drop it onto a node in the ordanizer that already exists, it will use node info instead of inferring.  The actual algorithm is a little more complex than I feel like explaining, but it all makes logical sense when you use it, so if you really want to know how it works, try it out.


So the day before Christmas I delivered v1 to him, and less than 2 minutes later he found the first bug.  After a couple versions of deploying via FTP, I decided there had to be a better mechanism.  Since this is a desktop app (yes, sometimes I begrudgingly work on non-CE stuff – but hey, the ID3 tag library is fully CF-compatible) I decided I’d see what the whole ClickOnce deployment and publishing stuff was about.  Sure enough, it turned out to be crazy simple, so I set up a publication that allows installation from a web page and now the software auto-updates on his machine every time I make a fix.

So now  that it’s done, I guess that there may be other people out there that received these nice USB turntables and are having a similar problem, so I say Merry Christmas to you all.  I’m giving away AudioTagR, along with the full source if you want it, to everyone as another OpenNETCF shared source project (MIT X11 license).  Since it is free, you get no support, and I take no responsibility for its use or consequences.  If you screw up the titles on all of your existing music I’m sorry but it’s not my fault.  Make a backup.  If you find a bug, by all means let me know and I’ll see what I can do to fix it.  If you add a feature that you like, send me the code and I can integrate it in.
 

If you simply want to install and use AudioTagR, click here.

Download the full source (C#) here
Get the OpenNETCF.Media.MP# library source here

12/31/2007 3:41:33 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  | 
Before I go any further, let me state for the record that this library does not provide any ability to play MP3 files, so if that's what you were thinking from the name, then don't get too excited.

The OpenNETCF.Media.MP3 library is a library that allows you to read and write ID3 tags to MP3 files.  It's both Compact Framework 2.0 and Full Framework 2.0 compliant.  For a usage sample, see the AudioTagR project.

The general object model looks like this:



It's licensed under our very open MIT X11 license.
Download the full source code (C#) here.

12/31/2007 3:25:40 PM (Eastern Standard Time, UTC-05:00)  #    Comments [1]  | 
 Friday, December 21, 2007
For the last couple weeks I've been struggling with getting camera capture inside a desktop application (yes - I lower myself to do desktop development occasionally).  The problem I'm finding is that API documentation and especially sample availability for things like DShow and Windows Image Acquisition (WIA) are pretty poor and there seem to be no reasonable, comprehensize samples on how to use any of it.  Occasionally you find something on sites like CodeProject, but IMO those projects tend to suck.  The authors typically "solve" a specific problem by writing code tightly coupled to the problem they're solving, so the code is very rarely reusable or extensible, and the quality usually is lacking as well.

Well today I finished up a general library and test harness for WIA.  It's too soon to tell if we'll actually use it in the application I'm working on, since it's missing a lot of capabilities that DShow will likely be better for, but to save a little headache in the developer community, I'm making it shared source (under our typical MIT x11 license).  It provides a simple object model for seeing what cameras you have, displaying live video from them and grabbing frames.  The test harness generates a TabControl with a page for each camera you have plugged in.  If you add or remove a camera while the app is running, tabs are added or removed.

Most importantly I designed the clas library for general use.  It can be dropped into any project and consumed for camera control without a bunch of rewrite, and while I'm not going to say it's bug free, I at least tested it though several scenarios and let it run for a fairly long time without it puking (which is more than I can say about pretty much anything else I found).

Below is the object model and a screen shot of the test app.





Download the source code here (Updated 1/3/08).
12/21/2007 4:14:58 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  |