Archive for the ‘Expression Encoder’ Category.

Using Expression Encoder 4 in a Windows Azure Worker Role

One of the most common workloads I hear about from customers and developers is video encoding in Windows Azure.  There are certainly many ways to perform video encoding in Windows Azure – in fact, at PDC 2009 I was joined by Mark Richards of Origin Digital who discussed their video transcoding solution on Windows Azure – but until the release of Windows Azure 1.3 SDK, and in particular Startup Tasks and elevated privileges, it was complicated.

As I looked into this workload, I decided that I wanted to prove out a few things in a small spike:

  1. It is not necessary to use the VM Role.  The VM Role was introduced to make the process of migrating existing Windows Server applications to Windows Azure easier and faster, especially when this involves long, non-scriptable or fragile installation steps.  In this particular case, I was pretty sure I could script the setup process using Startup Tasks.
  2. It is possible to use the freely available Expression Encoder 4.
  3. It is possible to use the Web Platform Installer to install Expression Encoder 4.

Before I go any farther, make sure you review two great blog posts by Steve Marx – be sure to read these as they are a great starting point for Startup Tasks:

Okay, now that everyone’s familiar with Startup Tasks, let’s jump into what it takes to get the video encoding solution working.

I decided to put the encoding service in a Worker Role – it makes the most sense, as all I want the role to do is encode my videos.  Consequently, I added a Startup Task to the Service Definition that points to a script:

Code Snippet
  1. <Startup>
  2.   <Task commandLine="Startup\InstallEncoder.cmd" executionContext="elevated" taskType="background" />
  3. </Startup>

Note that I have the task type defined as “background”.  As Steve explains in his post, this makes it easy during development to debug; however, when you’re ready to push this to production, be sure to change this to “simple” so that the script runs to completion before any other code runs.

The next thing to do is add the tools to our project that are needed to configure the instance and install Encoder.  Here’s what you need:

  • WebPICmdLine – the executable and it’s assemblies
  • InstallEncoder.cmd – this is our script that’s used to set everything up
  • PsExec.exe (optional) – I like to include this for debugging purposes (e.g. psexec –s -i cmd)

As I mentioned in my post on Web Deploy with Windows Azure, make sure to mark these files as “Copy to Output Directory” in Visual Studio.  I like to organize these files like this:

image

Now, let’s take a look at the InstallEncoder.cmd script, as that’s where the real work is done.  Here’s the script:

Code Snippet
  1. REM : Install the Desktop Experience
  2. ServerManagerCMD.exe -install Desktop-Experience -restart -resultPath results.xml
  3. REM : Make a folder for the AppData
  4. md "%~dp0appdata"
  5. REM : Change the location of the Local AppData
  6. reg add "hku.defaultsoftwaremicrosoftwindowscurrentversionexploreruser shell folders" /v "Local AppData" /t REG_EXPAND_SZ /d "%~dp0appdata" /f
  7. REM : Install Encoder
  8. "%~dp0webpicmdWebPICmdLine.exe" /accepteula /Products: ExpressionEncoder4 /log:encoder.txt
  9. REM : Change the location of the Local AppData back to default
  10. reg add "hku.defaultsoftwaremicrosoftwindowscurrentversionexploreruser shell folders" /v "Local AppData" /t REG_EXPAND_SZ /d %%USERPROFILE%%AppDataLocal /f
  11. REM : Exit gracefully
  12. exit /b 0

Let’s break it down:

  • Line #2: Turns out that you need to install the Desktop Experience in the instance (it’s Windows Server 2008, after all) before you can run Expression Encoder 4.  You can do this easily with ServerManagerCMD.exe, but you’ll have to restart the machine for it to take affect.
  • Line #4: Create a new folder called “appdata”. (Incidentally, %~dp0 refers the directory where the batch file lives.)
  • Line #6: Update the “Local AppData” folder in the registry to temporarily use our “appdata” folder.
  • Line #8: Install Expression Encoder 4 using the WebPICmdLine. Note that with the latest drop you’ll need to add the “/accepteula” flag to avoid user interaction.
  • Line #10: Revert the “Local AppData” back to the default.
  • Line #12: Exit gracefully, so that Windows Azure doesn’t think there was an error.

If you need to restart your machine before you’ve installed everything in your startup task script, as I did, make sure the script is idempotent.  Specifically in this case, since we restart the machine after installing the Desktop Experience, line #2 will get run again when the machine starts up.  Fortunately ServerManagerCMD.exe has no problem running when the Desktop Experience is installed.

This is all that’s required to setup the Worker Role.  At this point, it has all the software required in order to start encoding videos.  Pretty slick, huh?

Unfortunately, there’s one other thing to be aware of – the Expression Encoder assemblies are all 32-bit, and consequently they will not run when targeting x64 or Any CPU.  This means that you will need to start a new process to host the Encoder assemblies and our encoding process.  Not a big deal.  Here’s what you can do.

Create a new Console project – I called mine “DllHostx86” based on some guidance I gleamed from Hani Tech’s post – and add the Expression Encoder assemblies:

image

Update the platform target so that it’s explicitly targeting x86 – this is required with these assemblies, as I’ve noted elsewhere in my blog (see Using the Expression Encoder SDK to encode lots of videos).

image

Now you can write the code in the Program.cs file to encode the videos using the Encoder APIs.  This itself is pretty easy and straightforward, and I invite you to review the post I mention above to see how.  Also, if you want to do something kind of fun you can add a text overlay on the video, just to show you can.

The next step is to make sure that the DllHostx86 executable is available to include as part of our Worker Role package – we’ll want to run it as a separate process outside of the Worker Role.  Simply update the output path so that it points to a folder that exists in your Worker Role project.

image

This way the any build will create an executable that exists within the Worker Role project folder, and consequently can be included within the Visual Studio project (be sure to set them to copy to the output directory).  The end result will look like this:

image

Now, all we have to do is write some code that will create a new process and execute our DllHostx86.exe file.

Code Snippet
  1. private void ExecuteEncoderHost()
  2. {
  3.     string dllHostPath = @"Redist\DllHostx86.exe";
  4.  
  5.     ProcessStartInfo psi = new ProcessStartInfo(dllHostPath);
  6.     Trace.WriteLine("Starting DllHostx86.exe process", "Information");
  7.  
  8.     using (Process dllHost = new Process())
  9.     {
  10.         dllHost.StartInfo = psi;
  11.         dllHost.Start();
  12.         dllHost.WaitForExit();
  13.     }
  14. }

While I think this is pretty straightforward, a few comments:

  • Line #5: If you want to pass parameters to the applications (which I ultimately do in my solution) you can use an overload of ProcessStartInfo to pass them in.
  • Line #12: Since my simple spike isn’t meant to handle multiple a lot of these processes at once, I use WaitForExit() to ensure that the encoding process completes before continuing.

And there you have it!  That’s all you need to do to setup a Windows Azure Worker Role to use Expression Encoder 4 to encode videos!

If you want to see an example of this running, you can try out my initial spike: http://encoder.cloudapp.net/.

This is just a simple, but hopefully effect, sample that illustrates how simple it is to setup the Worker Role using Startup Tasks.  With this solution in place, you can scale out as many Worker Roles as you deem necessary to process video – in fact, at one point I scaled up to 10 instances and watched them all encoding my videos.  In fact, I bet you could use some of the tips I illustrate in Using the Expression Encoder SDK to Encode Lots of Videos to run multiple encoding session at a time per instance.  Pretty nifty.

I hope this helps!

Overlay Text On Video Using Expression Encoder 4

I recently built an application where I wanted to write some arbitrary text onto a video.  I was using Expression Encoder 4 and had a difficult time finding some examples and documentation, aside from some basic MSDN documentation on the MediaItem class.  I was eventually able to figure it out, but it took a little time.  Even though the end result is not particularly glamorous, I thought I’d save you some time and share what I learned.

The first thing to recognize is that you have to create a Bitmap of the text you want to overlay.  This Bitmap is then overlayed on to the MediaItem.  So, first things first, create your Bitmap.  Here’s an approach:

Code Snippet
  1. /// <summary>
  2. /// This method will create a bitmap based
  3. /// </summary>
  4. /// <param name="overlayText"></param>
  5. /// <param name="rootPath"></param>
  6. /// <param name="width"></param>
  7. /// <param name="height"></param>
  8. /// <returns></returns>
  9. private static string createOverlayImage(string overlayText, string rootPath, int width, int height)
  10. {
  11.     // full path for a temporary bitmap
  12.     string overlayFileName = rootPath + "\\" + Guid.NewGuid().ToString() + ".bmp";
  13.  
  14.     // create a bitmap
  15.     Bitmap bitmap = new Bitmap(width, height);
  16.     Graphics g = Graphics.FromImage(bitmap);
  17.  
  18.     // define the font
  19.     Font font = new Font("Arial", (float)14.0);
  20.  
  21.     // define the area to draw on
  22.     Rectangle area = new Rectangle(new Point(0, 0), new Size(width, height));
  23.  
  24.     // draw the new image
  25.     g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
  26.     g.DrawString(overlayText, font, Brushes.Red, area);
  27.  
  28.     // save the picture with the text overlay
  29.     bitmap.Save(overlayFileName);
  30.  
  31.     // return the path to the overlay image
  32.     return overlayFileName;
  33. }

Nothing too surprising here:

  • Line #9: Pass in the text you want to overlay, the path for where you’ll store the created bitmap, and the width/height.
  • Line #12: Create a random file name for the Bitmap.
  • Line #26: You can change the font, color, and locations here if you’d like.

Now, with this method, you can set the overlay properties on the MediaItem like this:

Code Snippet
  1. // sets file name to media item
  2. mediaItem = new MediaItem("test.wmv");
  3.  
  4. // create the overlay image and return the path
  5. string overlayFileName = createOverlayImage("Thank you for encoding this video!", Environment.CurrentDirectory, mediaItem.VideoSize.Width, mediaItem.VideoSize.Height);
  6.                 
  7. // create the overlay on the media item
  8. mediaItem.OverlayFileName = overlayFileName;
  9. mediaItem.OverlayLayoutMode = OverlayLayoutMode.WholeSequence;
  10. mediaItem.OverlayRect = new Rectangle(new Point(30, 10), new Size((mediaItem.VideoSize.Width – 30), (mediaItem.VideoSize.Height – 10)));

Breaking it down:

  • Line #5: Grab the full path to the newly created Bitmap over your text overlay.
  • Line #8: Set the OverlayFileName to your Bitmap.
  • Line #9: Choose the layout mode.  There are a few options here that you can explore; details are on MSDN.
  • Line #10: The OverlayRect defines where your Bitmap lives on the video.  I indented it a bit, but it’s up to you.

image

I’ve modified the Simple template provided by the Expression Encoder 4 SDK with the code.  You can find it below.  Hope this helps.

Using the Expression Encoder SDK to encode lots of videos

I spent a good deal of time this weekend importing hours and hours family videos off our Mini DV cassettes.  Lots of fun, and LOTS of video!  Based on the size of these files, I was quite close to running out of room on my Windows Media Center.  So, I decided to encode the files as WMVs.  Huge size reduction with very little quality loss.

I decided to use Microsoft Expression Encoder 3 – a great tool.  The best part is that there’s an SDK and set of assemblies that you can use in your own applications.

Note: if you are using a 64-bit machine, be sure to set the platform target of your application to x86, or else you will get compilation errors from the Encoder assemblies.

Below you’ll find the application I wrote.  Let me explain my goals:

  • Multi-thread the application to encode more than one video at a time.
  • Leverage the multitude of cores in my machine.
  • Limit the number of threads (I chose the core count as a baseline).
  • Use source video and audio source to reduce quality lose.

In order to do this, I had to do two things: 1) find a way to pass in the file name into thread, and 2) keep track of the number of threads and limit them to the number of cores in the machine.

I spent a bit of time looking for a good approach.  In the end, I chose to use the delegate ParameterizedThreadStart, which takes a parameter of type object.  This way, I can create a thread using an instance of this delegate instead of just ThreadStart, and the overload to Thread.Start allows me to specify a value that is passed to this new thread.  (Be careful, though, as it only accepts a single parameter (although it can be a collection) and isn’t type-safe.)  Additionally, with this approach I was able to leverage a counter, and sleep whenever the counter is going to exceed the number of cores in my machine.

Here’s all the code.  For this to function, I imported the following assemblies (yes, you need Expression Encoder 3):

  • Microsoft.Expression.Encoder
  • Microsoft.Expression.Encoder.Types
  • Microsoft.Expression.Encoder.Utilities
  • WindowsBase
// This method is used to look-up the core the thread is using
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern int GetCurrentProcessorNumber();
 
static string inputFolder = @"C:tempVideos";
static string outputFolder = @"C:tempOutputVideo";
static int count;
static int maxNum;
 
static void Main(string[] args)
{
    // Start the counter at zero
    count = 0;
    // Grab the processor count
    maxNum = Environment.ProcessorCount;
    // Iterate through the AVI files
    foreach (var fileName in System.IO.Directory.GetFiles(inputFolder, "*.avi"))
    {
        // Sleep/wait for a core to free up
        while (count > (maxNum - 1))
        {
            Thread.Sleep(500);
        }
        // Increment the counter
        count++;
        // Create the thread with the delegate
        Thread t = new Thread(new ParameterizedThreadStart(EncodeFile));
        // Start the thread, passing in the file name
        t.Start(fileName);
    }
}
 
public static void EncodeFile(object ofileName)
{
    string fileName = (string)ofileName;
    MediaItem mediaItem = new MediaItem(fileName);
    mediaItem.OutputFormat = new WindowsMediaOutputFormat();
    // Use source video profile if available
    if (mediaItem.SourceVideoProfile != null)
    {
        mediaItem.OutputFormat.VideoProfile = mediaItem.SourceVideoProfile;
    }
    else
    {
        mediaItem.OutputFormat.VideoProfile = new AdvancedVC1VideoProfile()
        {
            Size = mediaItem.MainMediaFile.VideoStreams[0].VideoSize,
            Bitrate = new ConstantBitrate(1000)
        };
    }
    // Use source audio profile if available
    if (mediaItem.SourceAudioProfile != null)
    {
        mediaItem.OutputFormat.AudioProfile = mediaItem.SourceAudioProfile;
    }
    else
    {
        mediaItem.OutputFormat.AudioProfile = new WmaAudioProfile();
    }
    // Create a job and the media item for the video we wish to encode.
    Job job = new Job();
    job.MediaItems.Add(mediaItem);
    // Set up the progress callback function
    job.EncodeProgress
        += new EventHandler<EncodeProgressEventArgs>(OnProgress);
    // Set up the completed callback function
    job.EncodeCompleted
        += new EventHandler<EncodeCompletedEventArgs>(job_EncodeCompleted);
    // Set the output directory and encode
    job.OutputDirectory = outputFolder;
    // Do not create a job subfolder
    job.CreateSubfolder = false;
    // Encode
    job.Encode();
}
 
static void job_EncodeCompleted(object sender, EncodeCompletedEventArgs e)
{
    // Decrement the counter
    count--;
}
 
static void OnProgress(object sender, EncodeProgressEventArgs e)
{
    // Write out information
    Console.WriteLine(
        count.ToString() + " : " +
        GetCurrentProcessorNumber().ToString() + " : " +
        System.Threading.Thread.CurrentThread.ManagedThreadId.ToString() + " : " +
        e.Progress + " : " +
        e.CurrentItem.ActualOutputFileName);
}

 

Good stuff.

I’m running it from the console, so you will have to make some modifications if you want it to run in a more sophisticated application.  Works for me, though – I just start it up at the end of the day.  Here you can see the information that’s written out to the console (note the variety of cores leveraged):

Console Output

It’s fun to see my machine working this hard.  Every core is pegged.

Pegged Cores

Hope someone finds this useful.  Anyone see a better way to approach this?