I was sidetracked today by a rather interesting problem; How to get duration using Media Foundation and C#?  I've worked on several projects over the years where I needed a simple method to get the duration of a multimedia file.  In the past I turned to DirectShow but this time I wanted to get the duration using Media Foundation and I wanted the code to be written in C#. After wandering around the Media Foundation documentation for a while, I discovered an article titled Using the Source Reader to Process Media Data.  The interesting bit was the section called Getting the File Duration which served as a starting point for my implementation.

The Method

Enough talk.  Lets jump straight in.  Here is the finished method:

public double GetDuration(string filename)
{

    double duration = double.NaN;

    const int S_OK = 0;
    const uint MEDIA_SOURCE = 0xFFFFFFFF;

    int hr;

    //Startup media foundation 
    hr = MFInterop.MFStartup(MFStartupFlags.Full);
    if (hr == S_OK)
    {

        //Create source reader
        IMFSourceReader sourceReader;
        if (S_OK == MFInterop.MFCreateSourceReaderFromURL(filename, null, out sourceReader))
        {

            Guid MF_PD_DURATION = new Guid("6C990D33-BB8E-477A-8598-0D5D96FCD88A");

            //The duration timebase is 100-nanosecond units
            double timebase = 10 * 1000 * 1000;

            //Get the multimedia duration 
            PropVariant propVariant;
            sourceReader.GetPresentationAttribute(MEDIA_SOURCE, MF_PD_DURATION, out propVariant);
            if (propVariant.Type == System.Runtime.InteropServices.VarEnum.VT_UI8)
            {
                duration = ((ulong)propVariant.Value) / timebase;
            }
            propVariant.Clear();

        }

        //Shutdown media foundation
        MFInterop.MFShutdown();

    }
    return duration;
}

The method itself is relatively simple, but gathering the supported enumerations, interfaces, and structures takes a bit of research and know-how.  I am going to discuss how I found each of these required elements in due course, but if you are on a deadline and want to push the easy button you can find a working example solution for Visual Studio 2013 at the bottom of this article.

Startup and Shutdown

You can see in the GetDuration method that we are required to startup and shutdown Media Foundation.  If we neglect this step our method will fail to work properly.  I like to create a static utility class named MFInterop to house my external function calls.  I find this a handy way to group all my external calls together and I can easily add more declarations when needed.  It looks like this:

using System.Security;

public static class MFInterop
{

    [DllImport("mfplat.dll", ExactSpelling = true), SuppressUnmanagedCodeSecurity]
    public static extern int MFShutdown();

    [DllImport("mfplat.dll", ExactSpelling = true), SuppressUnmanagedCodeSecurity]
    private static extern int MFStartup
    (
        int Version,
        MFStartupFlags dwFlags
    );

    public static int MFStartup(MFStartupFlags objFlags)
    {
        int MF_VERSION = 0x20070;
        return MFInterop.MFStartup(MF_VERSION, objFlags);
    }

}

and the MFStartupFlags enumeration:

public enum MFStartupFlags
{
    Full = 0,
    Lite = 1,
    NoSocket = 1
}

I had to scrounge around the header files to figure out the values for MF_VERSION and MFStartupFlags.  Instead of making MFStartup public, I instead chose to write a small wapper method that encapsulated the version information.  You can find the documentation for MFStartup and MFShutdown in the Microsoft Media Foundation Programming Reference. It is worth pointing out that you would probably want to initialize Media Foundation one time in your startup logic, and correspondingly shutdown when your application exits.  Putting the startup and shutdown logic directly into our method isn't terribly efficient.

IMFSourceReader

To get the media duration we are going to need an IMFSourceReader interface and according to the documentation, we can use MFCreateSourceReaderFromURL to get one.  Let's add another static external declaration to our MFInterop class, like this:

[DllImport("mfreadwrite.dll", ExactSpelling = true), SuppressUnmanagedCodeSecurity]
public static extern int MFCreateSourceReaderFromURL
(
    [In, MarshalAs(UnmanagedType.LPWStr)] string pwszURL,
    IMFAttributes pAttributes,
    out IMFSourceReader ppSourceReader
);

This is where things start to get interesting.  You will discover very quickly that Media Foundation methods expect to work with Media Foundation interfaces, and Media Foundation interfaces will reference various enumerations, structures, and further interfaces.  Each time you declare one you create a need to declare three more.  It feels almost viral trying to get everything declared properly.  Fortunately for the purposes of this article, we only have to declare a few things.

Jiggery Pokery

But where do we look to find a C# declaration for IMFAttributes or IMFSourceReader? It took me a while to figure this part out.  The answer lies in understanding IDL files, type libraries and interop assemblies.  I don't pretend to be a C++ guru, and when people start throwing around terms like interop, IDL, and type libraries my eyes start to go a little out of focus.  But after a bit of jiggery pokery, I discovered it's actually not that difficult to get the IDE to do the heavy lifting for us.

The first thing we need to locate is a file called Mfobjects.idl.  On my machine, this file was located at

c:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include

If you open the Mfobjects.idl file in your favorite text editor and snoop around, you will find the interface declaration for IMFAttributes.  Everything you need to know is right there; but it is unfortunately written in the wrong language.  What we need is an easy way to convert the IDL version of the interface into a C# version we can use in our project.  After a few hits and misses I discovered that we can use some of the command line tools included with Visual Studio to generate a type library (TLB) from the IDL and then generate an interop assembly from the type library.  Finally, we can use our favorite reflection tool to examine the interop assembly and look at the C#. Still with me?  Excellent.  Everyone get out your command lines...

MIDL

Start by opening the Visual Studio Native Tools Command Prompt.  If you use a standard Command Prompt you will have to add the path for the Microsoft tools manually or change directory to the location of the tools on your hard drive.  Try typing MIDL at the command prompt and you should get some output like this:

C:\>midl
Microsoft (R) 32b/64b MIDL Compiler Version 8.00.0603
Copyright (c) Microsoft Corporation. All rights reserved.
midl : command line error MIDL1000 : missing source-file name

As I understand it, IDL is an Interface Definition Language developed by Microsoft to define interfaces for COM objects.  It has always been one of those technologies that dances on the periphery of my skill set.  I won't pretend to explain or even understand IDL but I will tell you that with a bit of tweaking to the Mfobjects.idl file we can easily create our type library. My approach was to first make a copy of the Mfobjects.idl so I could safely screw things up without permanent damage.

c:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include>mkdir c:\temp
c:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include>copy mfobjects.idl c:\temp
     1 file(s) copied.
c:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include>cd c:\temp
c:\temp>

For some reason, the MIDL tool can only generate a type library for IDL files that conform to a specific format.  Of course, all the IDL files we are going to play with don't seem to have the magic bits that the MIDL tool requires, so we are going to add them manually.   It's dead simple, here is all we need to do:

[
uuid(4A3AE19D-C636-44B2-B554-10B87BF7E6EE),
lcid(0x00),
version(1.0),
helpstring("devMash mfobjects type library")
]
library dm_mfobjects
{
    INSERT ORIGINAL FILE CONTENTS HERE 
}

As you can see, we simply place the original file contents in between the curly braces.  One thing you will want to be cautious about is the UUID.  Use the Create Guid tool in Visual Studio to create a unique Guid for each type library you decide to generate.  It is also a good idea to give your type library a unique name and help string so you can easily identify it later. Save the changes to your copy of Mfobjects.idl (we don't want any accidents!) and let's generate our type library:

c:\temp>midl Mfobjects.idl /tlb Mfobjects.tlb
Microsoft (R) 32b/64b MIDL Compiler Version 8.00.0603
Copyright (c) Microsoft Corporation. All rights reserved.
    MIDL OUTPUT REMOVED FOR BREVITY
c:\temp>dir
08/25/2015  04:42 AM           153,410 Mfobjects.idl
08/25/2015  04:46 AM            81,260 Mfobjects.tlb

After entering the MIDL command above you will see some output scroll by.  You can safely ignore it as it appears to be nothing more than a list of external IDL files and headers that were referenced by Mfobjects.idl.  Do a directory and if all is well you will see we have created a new file called Mfobjects.tlb.

TLBIMP

Now that we have a type library we need to generate an interop assembly.  This step is pretty easy:

c:\temp>tlbimp Mfobjects.tlb /out:Mfobjects.dll
Microsoft (R) .NET Framework Type Library to Assembly Converter 4.0.30319.33440
Copyright (C) Microsoft Corporation.  All rights reserved.
    TLBIMP OUTPUT REMOVED FOR BREVITY
c:\temp>dir
08/25/2015  04:42 AM           153,410 Mfobjects.idl
08/25/2015  04:46 AM            81,260 Mfobjects.tlb
08/25/2015  05:09 AM            42,496 Mfobjects.dll

You will probably see a lot of warnings scroll by when generating the interop assembly.  I happily ignored this and pressed on into the fog bank with absolute confidence.  You can too.

Reflection

For our next step you are going to need a reflection tool like Red Gate's .NET Reflector or Telerik's JustDecompile.  I am partial to .NET Reflector because I have used it for years and it is conveniently bundled with other Red Gate products that I own.  If you don't already have a favorite reflection tool, JustDecompile is free and is a quality product but you have to create an account and sign up to do the install.  I am sure there are other tools out there that would work equally well. Once you have settled on a tool you are happy with, simply open the interop assembly we generated earlier.  In .NET Reflector this is done via the usual File->Open metaphor.  If all has gone well, you should be able to see your assembly in a treeview navigation panel, and if you expand a few nodes you will eventually come across the IMFAttributes interface. In most reflection tools, selecting the node in the tree (clicking on IMFAttributes) will cause the associated code to be displayed in the main window.  Some tools offer options about what language (VB, C#, etc.) and what version of .NET you would like to target. You should be able to see something similar to this: [caption id="attachment_84" align="aligncenter" width="1243"]Using .NET Reflector to display the IMFMediaAttributes interface Using .NET Reflector to display the IMFMediaAttributes interface[/caption] Simply select the decompiled code for the IMFAttributes interface and paste it into your Visual Studio project.  Viola!  Well, almost Viola. Don't forget to add the following using clauses:

using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;

Stragglers

If this code is ever going to compile, we are going to have to cleanup the remaining stragglers.  The IMFAttributes interface contains methods which in turn make references to PROPVARIANT, MF_ATTRIBUTE_TYPE and MF_ATTRIBUTES_MATCH_TYPE.  The last two are simply enumerations and are easy enough to lookup but we are going to have to ignore PROPVARIANT for a little while. When I migrate enumerations from C++ to C# I like to do a little refactoring.  Generally, I will camel-case the naming and remove any redundant prefixing that made sense in C++ but is out of place in C#.  Also, I have a tendency to look up actual values from the headers and incorporate them directly.  For example, here is the original enum for MF_ATTRIBUTE_TYPE:

typedef enum _MF_ATTRIBUTE_TYPE { 
  MF_ATTRIBUTE_UINT32    = VT_UI4,
  MF_ATTRIBUTE_UINT64    = VT_UI8,
  MF_ATTRIBUTE_DOUBLE    = VT_R8,
  MF_ATTRIBUTE_GUID      = VT_CLSID,
  MF_ATTRIBUTE_STRING    = VT_LPWSTR,
  MF_ATTRIBUTE_BLOB      = VT_VECTOR | VT_UI1,
  MF_ATTRIBUTE_IUNKNOWN  = VT_UNKNOWN
} MF_ATTRIBUTE_TYPE;

and here is my version in C#

public enum MFAttributeType
{
    UINT32 = 0x13,  // VT_UI4
    UINT64 = 0x15,  // VT_UI8
    DOUBLE = 5,     // VT_R8
    GUID = 0x48,    // VT_CLSID
    STRING = 0x1f,  // VT_LPWSTR
    BLOB = 0x1011,  // VT_VECTOR | VT_UI1
    IUNKNOWN = 13   // VT_UNKNOWN
}

This is obviously a matter of style, but you get the general idea. That takes care of the most of the stragglers, but we still have those pesky PROPVARIANT references.  Again, let's defer addressing that particular problem just now.

Rinse and Repeat

If you remember, we were trying to simply declare interfaces for IMFAttributes and IMFSourceReader so we could execute the Media Foundation call MFCreateSourceReaderFromURL.  It's been a long road and we only have a partial IMFAttributes interface to show for all of our hard work.  But this kind of work is like rolling a snowball down a hill.  Once you get started it goes faster and faster. At this point you might be tempted to think copying the IMFSourceReader interface will be a simple matter.  Unfortunately you will quickly discover that the IMFSourceReader interface does not appear to be defined in Mfobjects.idl.  Instead it is found in Mfreadwrite.idl.  So we will need to generate a type library and interop assembly for Mfreadwrite.idl in exactly the same way we did for Mfobjects.idl. After generating an interop assembly for Mfreadwrite.idl, you can open it in your favorite reflection tool and copy/paste the interface declaration for IMFSourceReader into your C# project. Notice that the interface declaration for IMFSourceReader includes several methods which reference to two more interfaces, IMFMediaType and IMFSample, as well as our hanging PROPVARIANT problem. A quick look in our reflector reveals IMFMediaType was part of Mfreadwrite.idl, and so we can easily copy the interface declaration without have to resort to further conversions.  Don't forget to clean up MF_ATTRIBUTE_TYPE and MF_ATTRIBUTES_MATCH_TYPE after copying, and please continue to patiently ignore those darn PROPVARIANT references. The same holds true for IMFSample, simply copy and cleanup.  Notice IMFSample makes a reference to IMFMediaBuffer so we are going to need to get that interface also, which is also conveniently found in our interop assembly. (If you notice the IMFAttributes interface can also be found in our interop assembly for Mfreadwrite.idl, but we had no way of knowing that when we started.) You should now have the following interfaces:

and the following enumerations:

with the only remaining errors related to...

PROPVARIANT

The PROPVARIANT structure is deserving of it's own article.  It's a verbose and ugly looking structure with a C++ Union occupying half the declaration.  I was confident that given some time and experimentation the PROPVARIANT could be ported to C#.  It would make an interesting topic to write about and much to my delight I discovered an excellent article has already been written titled Interop with PROPVARIANTs in .NET by Adam Root. After reading through Adam's article a few times to get the gist of it, we can copy his PropVariant structure without modification directly into out project.  All that should remain is refactoring the PROPVARIANT references found in our copied interfaces to use PropVariant. With some luck, your code should now compile but we aren't done yet.

Getting the Duration, Finally!

We had to do a lot of work to create an instance of a new IMFSourceReader but it's about to pay off. The IMFSourceReader exposes a method called GetPresentationAttribute which according to the Media Foundation documentation, "gets an attribute from the underlying media source".  To call GetPresentationAttribute we will need to pass two parameters; the stream index and a presentation descriptor attribute. For our purposes the stream index is quite simple and according to the documentation it is easily declared:

const uint MEDIA_SOURCE = 0xFFFFFFFF;

For the second parameter we will need to consult the documentation and examine the list of possible Presentation Descriptor Attributes that we can use with GetPresentationAttribute.   A quick look will reveal MF_PD_DURATION can be used to get the duration in 100-nanosecond units. Presentation Descriptor Attributes are declared as Guids so we have to look in mfidl.h to find the actual definition:

EXTERN_GUID( MF_PD_DURATION, 0x6c990d33, 0xbb8e, 0x477a, 0x85, 0x98, 0xd, 0x5d, 0x96, 0xfc, 0xd8, 0x8a );

converting this to C# gives us:

Guid MF_PD_DURATION = new Guid("6C990D33-BB8E-477A-8598-0D5D96FCD88A");

The output from GetPresentationAttribute will be a PropVariant containing the duration in 100-nanosecond units.  A nanosecond is a billionth of a second or 10−9 seconds.  Therefore a 100-nanosecond unit is actually one ten-millionth of a second or 10−7 seconds.  We can express the timebase like this:

double timebase = 10 * 1000 * 1000;

and finally invoke GetPresentationAttribute like this:

PropVariant propVariant;
sourceReader.GetPresentationAttribute(MEDIA_SOURCE, MF_PD_DURATION, out propVariant);
if (propVariant.Type == System.Runtime.InteropServices.VarEnum.VT_UI8)
{
    duration = ((ulong)propVariant.Value) / timebase;
}
propVariant.Clear();

If all has gone according to plan GetPresentationAttribute should return a valid PropVariant structure with a Type of VT_UI8, which is really just a 64-bit unsigned long.  Dividing the returned value by our timebase will yield the duration in seconds.  Lastly we should release our PropVariant by calling the Clear method. You now have the duration in seconds! Huzzah!

Source Code

I've created an example project in Visual Studio 2013 targeting the .NET framework 4.5 called GetDuration.zip.  This example contains all the source code discussed in this article; including the PropVariant structure written by Adam Root.  The project is released under the MIT License so you can use it as a starting point for your own Media Foundation projects in C#.

Conclusion

Hopefully this article has helped you to learn how you can get duration using Media Foundation and C#.  We have covered how to reference external Media Foundation methods, how to look up and translate Media Foundation enumerations and how generate Media Foundation interface definitions using various Visual Studio Native Command Line tools and reflection.  You should now have a good foundation to build on while developing your own Media Foundation applications using C#.  

devMash.com Pinwheelarticle copyright © 2015 devMash.com example project released under the MIT License

2 Comments

  • Nair said

    The GetDuration.zip is not found. I am getting the error "System.Security.SecurityException: 'ECall methods must be packaged into a system module". So wanted to confirm if i did the right thing. Please check.

  • Geo said

    The source code in this article is available on github at https://github.com/devMashHub/GetDuration

Comments have been disabled for this content.