For the proof of concept about a recording and encoding application I’m building, I had the need to show the preview of the actual data stream I can grab from a video source before the actual encoding job started. In the previous example you could view the result of the encoding operation and if you used that code, you could saw a delay between your actions in front of the webcam and the preview of the encoded stream.

To solve the issue a little more dig into Expression Encoder was needed, at the point of this writing the documentation won’t help you too much cause it’s quite poor compared to the information we’re used to have from MSDN.

So let’s introduce again our main actors:

  • LiveJob - this is the main class you use to perform the encoding from different sources, it has the ability to enumerate the devices, activate the selected sources, chose between preset encoding profiles or use your custom ones, do the dirty encoding job and show a preview of the encoded stream.
  • LiveDeviceSource - this class represent your actual source (or sources - it can mix video and audio) for the raw bits you will pass to the encoder, you can use this to gain access to the selected devices and use their configuration dialog; you can also specify a preview window to be used to see the uncompressed stream.
  • PreviewWindow - this is a wrapper around a plain old winform control or around a WPF window (something with an handle in short, see my previous post).

To fulfill our requirement we can modify the previous Encoding service in the following way:

  • Add two properties for the PreviewWindows: one for the raw stream preview coming from the webcam (we call this LivePreviewWindow) and one for the encoded stream (we call it EncodedPreviewWindow)
  • Add a private variable to store the LiveDeviceSource we get from mixing the selected devices.
  • To display the raw preview, during the device activation we need to set the PreviewWindow property of the active LiveDeviceSource and then we need to call the ActivateSource() function on the LiveJob class (this will be done in the ActivateDevices() function) passing the newly created LiveDeviceSource as parameter; this will activate the preview.
  • To display the encoded preview, we need to set the OutputPreviewWindow of the LiveJob class before calling the StartEncoding() function (you can see it in the StartCapture() function).

If you leave those properties empty the preview will be disabled; here’s the new code for the class:

public class EncodingService : IDisposable
{
/// <summary>
/// job used to perform the real encoding and mixing of video and audio sources
/// </summary>
readonly LiveJob _encodeJob = new LiveJob();

/// <summary>
/// the active device source to show the preview and perform the encoding job
/// </summary>
private LiveDeviceSource _activeSource;

/// <summary>
/// enumerating the devices is a breeze, the LiveJob class does it for you.
/// </summary>
public ReadOnlyCollection<LiveDevice> VideoDevices { get { return _encodeJob.VideoDevices; } }

/// <summary>
/// enumerating the devices is a breeze, the LiveJob class does it for you.
/// </summary>
public ReadOnlyCollection<LiveDevice> AudioDevices { get { return _encodeJob.AudioDevices; } }

/// <summary>
/// preview window used to show the live stream (not the encoded one)
/// </summary>
public PreviewWindow LivePreviewWindow { get; set; }

/// <summary>
/// preview window used to show the encoded stream
/// </summary>
public PreviewWindow EncodedPreviewWindow { get; set; }

public void ActivateDevices(LiveDevice video, LiveDevice audio)
{
    DeactivateDevices();
    // first off: add a device source specifying the video and audio device you'll be using
    _activeSource = _encodeJob.AddDeviceSource(video, audio);
    // specify a preview window (something with a Handle - any winform control or a WPF window)
    if (_activeSource.PreviewWindow == null && LivePreviewWindow != null)
        _activeSource.PreviewWindow = LivePreviewWindow;
    // activate the source
    _encodeJob.ActivateSource(_activeSource);
}

public void DeactivateDevices()
{
    if (_activeSource != null && _activeSource.IsActive)
        _encodeJob.RemoveDeviceSource(_activeSource);
}

public void StartCapture(LiveDevice video, LiveDevice audio, string outputFile)
{
    // assign an output filename
    if (!string.IsNullOrEmpty(outputFile))
        _encodeJob.OutputFileName = outputFile;
    else
        _encodeJob.OutputFormat = new WindowsMediaBroadcastOutputFormat();
    // specify a preview window (something with a Handle - any winform control or a WPF window)
    if (_encodeJob.OutputPreviewWindow == null && EncodedPreviewWindow != null)
        _encodeJob.OutputPreviewWindow = EncodedPreviewWindow;
    // start the job (this activates the preview too)
    _encodeJob.StartEncoding();
}

public void StopCapture()
{
    _encodeJob.StopEncoding();
}

public void Dispose()
{
    SafeDispose(_encodeJob);
}

private static void SafeDispose(LiveJob job)
{
    try
    {
        if (job != null)
        {
            job.StopEncoding();
            job.Dispose();
        }
    }
    catch (Exception ex)
    {
        // sometimes I get a COM exception when disposing the job...it needs further investigation
        System.Diagnostics.Debug.WriteLine(ex);
    }
}

/// <summary>
/// states if you can activate the devices
/// </summary>
public bool CanActivate
{
    get { return _activeSource == null || !_activeSource.IsActive; }
}

/// <summary>
/// states if we are streaming something to the preview window
/// </summary>
public bool IsActive
{
    get { return _activeSource != null && _activeSource.IsActive; }
}

/// <summary>
/// states if you can start the encoding job
/// </summary>
public bool CanCapture
{
    get { return IsActive && !_encodeJob.IsCapturing; }
}

/// <summary>
/// states if we are performing the actual encoding job
/// </summary>
public bool IsCapturing
{
    get { return _encodeJob.IsCapturing; }
}
}

To use this service class you must first call ActivateDevices() to create the live source and activate it, then you can start and stop the encoding job using the StartEncoding() and StopEncoding functions.

As a side note: pay very good attention to the size of the PreviewWindow: if you are building a WPF application and you use a WindowsFormsHost and a Panel controls to display the previews...the sizes of those controls will likely be (0,0) at the moment of creation due to the WPF rendering pipeline (I did this mistake too), so you will probably need to call PreviewWindow.SetSize() or delay the creation of those controls until you know the dimensions of the preview rendering boxes.

In the quick and dirty demo application I built, I ended up having an ugly fix for this issue:

private void PreviewRect_SizeChanged(object sender, SizeChangedEventArgs e)
{
  _vm.LivePreviewWindow.SetSize(new Size((int)PreviewRect.RenderSize.Width, (int)PreviewRect.RenderSize.Height));
}

Next time I’ll show you a trick on how to add some overlays over the WindowsFormsHost controls to add some cool UI effects to your previews.

Related Content