Thursday, November 11, 2010

Embedding Windows 7 Video in Java Swing

Last year I was tasked with adding video playback to a well known open source P2P application written in Swing. Since some video formats were no longer supported by DirectShow in Windows 7, part of this involved embedding the (at the time) new Windows 7 MFPlay video player. Unfortunately, since then, MFPlay was deprecated by Microsoft and the application is no longer being distributed. Still, it's interesting how simple it can be to get native video playing in a Canvas.

About MFPlay:

The MFPlay media API is part of Microsoft Media Foundation which was intended as a replacement for DirectShow. It plays most files playable by DirectShow as well as .MP4s and some .MOVs. Unlike DirectShow, MFPlay does not support third party codecs and for some mysterious reason does not play .MPEGs. It is included in Windows 7 but may not be supported in future Windows versions.


About JNA:

JNA, or Java Native Access, provides a simpler way of accessing native libraries (DLLs on Windows) than JNI.  A JNA tutorial is beyond the scope of this post but one can be found here.  Wikipedia also has some good, simple examples of how it is used.


Embedding the Beast:

Essentially all that is necessary to embed a video is to pass the window handle of a Canvas and the URL of the video file from Java to native code that calls MFPlay's MFPCreateMediaPlayer function.  In this example, we are using JNA to communicate with our native DLL.  The window handle can easily be retrieved using JNA's Native.getComponentID() method.

The only catch is that MFPlay normally requires the thread that instantiates it to provide a Windows event loop. This isn't really practical for a Java program but fortunately there is a magic flag, MFP_OPTION_FREE_THREADED_CALLBACK, that eliminates the need for an event loop and saves the day.


The first working version looked something like this (abridged):

Java:

public class MFPlayer {

    private Canvas renderer;

    private JMediaFoundation mediaFoundation;

    public MFPlayer(Canvas renderer) {
        this.renderer = renderer;
    }

    public void play(String url) {
        if (mediaFoundation == null) {
            mediaFoundation = (JMediaFoundation)Native.loadLibrary("JMediaFoundation", JMediaFoundation.class);
        }
        int result = mediaFoundation.play(new WString(url), Native.getComponentID(renderer));
        if (!isSuccess(result)) {
            // Playback failed. Do something about it here.
            JOptionPane.showMessageDialog(renderer, "MediaFoundation can't play " + url + ".", "Playback Failed", JOptionPane.WARNING_MESSAGE, null);
        }
    }

    private boolean isSuccess(int hresult) {
        return hresult >= 0;
    }

    /**Interface corresponding to JMediaFoundation.dll*/
    private interface JMediaFoundation extends Library {
        /**
        * Asynchronously starts playback of the video
        * file in the component corresponding to hwnd.
        *
        * @param url the video file to be played
        * @param hwnd the window handle of the Canvas the video
        *         will be rendered in
        * @return a value less than 0 if MediaFoudation thinks 
        *         it is unable to play the file. (Since playback 
        *         is asynchronous it's actually a bit more 
        *         complicated and this will not catch all 
        *         unplayable files.)
        */
        public int play(WString url, long hwnd);
    }
}
 

C:
extern "C" __declspec( dllexport ) int play(LPCWSTR url, long hwnd); 

C++:
IMFPMediaPlayer *pPlayer = NULL;

int play(LPCWSTR url, long hwnd){
    //initialize COM
    HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);

    if(hr < 0){
        //COM initialization failed
        return hr;
    }

    hr = MFPCreateMediaPlayer(
        url, //the video file
        TRUE, //start playback
        MFP_OPTION_FREE_THREADED_CALLBACK, //embedding only works with this flag (no windows event loop here)
        NULL, //no callback for this simple example
        (HWND)hwnd, //handle to the canvas used as a renderer
        &pPlayer //pointer to the new player
        );

    return hr;   
}
 

The C/C++ code is compiled to JMediaFoundation.dll which is accessed through JNA by means of our JMediaFoundation interface.  With a little code providing a JFrame and a menu, we have video working in Swing.

Well, not quite everything is working. The video plays but files with Dolby Surround Sound (like Big Buck Bunny) are silent due to licensing issues. But that is another story for another day.


And here is MFPlay with full controls as shipped in that famous P2P app.


The Eclipse project containing the source code and compiled DLL for this simple example can be found here.

For information on Microsoft's MFPlay see http://msdn.microsoft.com/en-us/library/dd389294%28VS.85%29.aspx
For JNA see https://jna.dev.java.net/
For Big Buck Bunny see http://www.bigbuckbunny.org/

Screen captures from the Creative Commons movie Big Buck Bunny (c) copyright Blender Foundation | www.bigbuckbunny.org

1 comment:

  1. please help me
    i have played a video file using java swing in windows xp. But video file do not support in windows 7.
    Please explain me why it is. And how can i run in windows7.

    ReplyDelete