Login
 
Archive
Links
Search
Blog List
There are no categories in this blog.
 
Aug 30

Written by: Jeremiah Morrill
8/30/2008 11:48 AM 

In my last post about the WPF MediaKit library, I mentioned a little about the design goals and architecture.  I have two distinct, but complimentary class hierarchies.  The "MediaPlayerBase" class tree wraps DirectShow COM functionality into a DispatcherObject, running in it's own Dispatcher.  The "MediaElementBase" wraps the "MediaPlayerBase" into a nice XAML friendly, data-bindable FrameworkElement.

Today the class inheritance looks like this:

On the DirectShow centric side

DispatcherObject->MediaPlayerBase->MediaSeekingPlayer

On the WPF centric side

FrameworkElement->D3DRenderer->MediaElementBase->MediaSeekingElement

If you notice, I have a "MediaSeekingPlayer".  I decided to put the basic functionality of the DirectShow graph (Play, Pause, Stop) into the base class and the media position functionality into it's own sub class.  I did this because it doesn't make sense to have seeking functionality on a live or unseekable source, such as a web cam.

So with these classes I wanted to write a MediaElement clone (aka a MediaUriElement).  Well, not a total clone, but the same concept of playing any Uri, with maybe some extra features, like being able to set the out output device or seeking by frames, time, bytes, samples, etc (great for super-duper smooth rewind).  I wanted to show how much source code it will really take to implement your own custom video.  Here source code to make this screenshot happen.

image 

<DShow:MediaUriElement 
    x:Name="mediaPlayer" 
    Source="c:\users\jmoney\desktop\sensor.wmv" 
    LoadedBehavior="Pause" Stretch="Uniform" 
    PreferedPositionFormat="Frames"/>

 

 

 

On the DirectShow Side

public class MediaUriPlayer : MediaSeekingPlayer
    {

#if DEBUG
        /// 
        /// Used to view the graph in graphedit
        /// 
        private DsROTEntry m_dsRotEntry;
#endif

        /// 
        /// The DirectShow graph interface.  In this example
        /// We keep reference to this so we can dispose 
        /// of it later.
        /// 
        private IGraphBuilder m_graph;

        /// 
        /// The VMR9 video renderer
        /// 
        private IBaseFilter m_renderer;

        /// 
        /// The media Uri
        /// 
        private Uri m_sourceUri;

        /// 
        /// Gets or sets the Uri source of the media
        /// 
        public Uri Source
        {
            get
            {
                VerifyAccess();
                return m_sourceUri;
            }
            set
            {
                VerifyAccess();
                m_sourceUri = value;
                OpenSource();
            }
        }

        /// 
        /// Opens the media by initializing the DirectShow graph
        /// 
        private void OpenSource()
        {
            string fileSource = m_sourceUri.OriginalString;

            /* Make sure we clean up any remaining mess */
            FreeResources();

#if DEBUG
            /* Remove me - just testing stuff */
            GC.Collect();
            GC.WaitForPendingFinalizers();
#endif
           
            try
            {
                /* Creates the GraphBuilder COM object */
                m_graph = new FilterGraph() as IGraphBuilder;

                /* Create and initialize a video renderer with a
                 * custom allocator.  Right now we only support VMR9
                 * but the EVR will be supported in the future */
                m_renderer = CreateVideoRenderer(RendererType.VideoMixingRenderer9);

                /* We add the renderer to the graph first
                 * and the GB will connect it automagically 
                 * when we render the file */
                int hr = m_graph.AddFilter(m_renderer, "VMR9 Renderer");
                DsError.ThrowExceptionForHR(hr);

                /* DirectShow now automatically assembles the
                 * graph and filters based on the media type */
                hr = m_graph.RenderFile(fileSource, null);
                DsError.ThrowExceptionForHR(hr);

#if DEBUG
                /* Adds the GB to the ROT so we can view
                 * it in graphedit */
                m_dsRotEntry = new DsROTEntry(m_graph);
#endif
                /* Configure the graph in the base class */
                SetupFilterGraph(m_graph);

                /* Remove and dispose of renderer if we do not
                 * have a video stream */
                if(!HasVideo)
                {
                    m_graph.RemoveFilter(m_renderer);

                    /* Tells the base class to unregister and
                     * free the custom allocator */
                    FreeCustomAllocator();

                    Marshal.FinalReleaseComObject(m_renderer);
                    m_renderer = null;
                }
            }
            catch (Exception ex)
            {
                /* This exection will happen usually if the media does
                 * not exist or could not open due to not having the
                 * proper CODECs installed */
                FreeResources();

                /* Fire our failed event */
                InvokeMediaFailed(new MediaFailedEventArgs(ex.Message, ex));
            }

            InvokeMediaOpened();
        }

        /// 
        /// Frees all unmanaged memory and resets the object back
        /// to its initial state
        /// 
        protected override void FreeResources()
        {
            /* Let's clean up the base class's stuff first */
            base.FreeResources();

#if DEBUG
            /* Remove us from the ROT */
            if (m_dsRotEntry != null)
            {
                m_dsRotEntry.Dispose();
                m_dsRotEntry = null;
            }
#endif
            if (m_renderer != null)
            {
                Marshal.FinalReleaseComObject(m_renderer);
                m_renderer = null;
            }
            if (m_graph != null)
            {
                Marshal.FinalReleaseComObject(m_graph);
                m_graph = null;

                InvokeMediaClosed();
            }
        }

        /// 
        /// Creates a new MediaUriPlayer, 
        /// running on it's own Dispatcher
        /// 
        public static MediaUriPlayer CreateMediaUriPlayer()
        {
            MediaUriPlayer engine = null;

            var reset = new ManualResetEvent(false);

            var t = new Thread((ThreadStart)delegate
            {
                engine = new MediaUriPlayer();

                /* We queue up a method to execute
                 * when the Dispatcher is ran. 
                 * This will wake up the calling thread. */
                engine.Dispatcher.Invoke((Action)(() => reset.Set()));

                Dispatcher.Run();
            }) { 
                Name = "MediaUriPlayer Graph",
                IsBackground = true
            };

            /* We can run in MTA also */
            t.SetApartmentState(ApartmentState.MTA);

            /* Starts the thread and creates the object */
            t.Start();

            /* We wait until our object is created and
             * the new Dispatcher is running */
            reset.WaitOne();

            return engine;
        }
    }

And on the WPF Side

public class MediaUriElement : MediaSeekingElement
{
    public MediaUriElement() : base()
    {}

    protected MediaUriPlayer MediaUriPlayer { get; set; }

    #region Source

    public static readonly DependencyProperty SourceProperty =
        DependencyProperty.Register("Source", typeof(Uri), typeof(MediaUriElement),
            new FrameworkPropertyMetadata(null,
                new PropertyChangedCallback(OnSourceChanged)));

    public Uri Source
    {
        get { return (Uri)GetValue(SourceProperty); }
        set { SetValue(SourceProperty, value); }
    }

    private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((MediaUriElement)d).OnSourceChanged(e);
    }

    protected void OnSourceChanged(DependencyPropertyChangedEventArgs e)
    {
        MediaPlayer.Dispatcher.BeginInvoke((Action)delegate
        {
            MediaUriPlayer.Source = e.NewValue as Uri;

            bool isLoaded = false;

            Dispatcher.Invoke((Action)(() => isLoaded = IsLoaded));
            if (isLoaded)
            {
                MediaState state = MediaState.Manual;
                Dispatcher.Invoke((Action)(() => state = LoadedBehavior));
                ExecuteMediaState(state);
            }
        });
    }

    #endregion

    /// 
    /// Gets the instance of the media player to initialize
    /// our base classes with
    /// 
    protected override MediaPlayerBase OnRequestMediaPlayer()
    {
        MediaUriPlayer = MediaUriPlayer.CreateMediaUriPlayer();
        return MediaUriPlayer;
    }
}

So hopefully I'll have this pretty much finished by this weekend.  We'll see.  I'll keep ya posted.

-Jer

Tags:

4 comment(s) so far...

Re: Refactoring until you can't abstract no mo'

Sweet... Can't wait to try it out.

Jer, I see yuou use MTA, does this mean that I can run several HD video's (renderd in the same Viewbox so I can do transitions) with out playback "chops" when loading a new video?

This I have problem with original MediaElement. (I run Quadcore and 8800GT, so I have the muscles...)

/Ken

By Ken on   8/31/2008 11:48 PM

Re: Refactoring until you can't abstract no mo'

Funny you should mention the choppy loading of media element. It crossed my mind while making this. It actually doesn't matter if we use mta or sta in this scenario. What matters is this runs on a thread other than the ui thread. This way no dshow operations will block the ui and media loads very smooth. Even doing things like seeking are Proxied via the dispatcher begininvoke.

By Jeremiah morrill on   9/1/2008 12:19 AM

Re: Refactoring until you can't abstract no mo'

Thats fantastic if Dshow don't block WPF UI. I have eperimented with MediaElement in separate threads and visualtargets, VisualTargetPresentationSource and more, but never got any good results.

BTW, here is some 720 & 1080 HD videos to try with: http://www.microsoft.com/windows/windowsmedia/musicandvideo/hdvideo/contentshowcase.aspx

/Ken

By Ken on   9/1/2008 4:22 AM

Re: Refactoring until you can't abstract no mo'

Is the MediaKit available for experimentation purposes yet? (Have a ETA for a release? Beta or otherwise)

-Eric

By Eric Meyer on   9/3/2008 6:04 AM

Your name:
Your email:
(Optional) Email used only to show Gravatar.
Your website:
Title:
Comment:
Security Code
Enter the code shown above in the box below
Add Comment   Cancel 
  Minimize
Text/HTML Minimize
Copyright 2007
Downloaded from DNNSkins.com