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

32 Comments

  • DewayneJuicy said

    Hi. I see that you don't update your page too often. I know that writing
    posts is boring and time consuming. But did you know
    that there is a tool that allows you to create new articles using
    existing content (from article directories or other blogs from your
    niche)? And it does it very well. The new posts are high quality and pass the copyscape test.

    Search in google and try: miftolo's tools

  • 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

  • seks anonse said

    It's actually a great and useful piece of info. I'm glad that you just
    shared this useful information with us. Please stay us informed like this.
    Thanks for sharing.

  • BestScott said

    I have noticed you don't monetize your site, don't waste your traffic, you can earn additional bucks every month.
    You can use the best adsense alternative for any type of website (they
    approve all websites), for more info simply search in gooogle:
    boorfe's tips monetize your website

  • paysafecard to bitcoin said

    Link exchange is nothing else except it is just placing the other
    person's website link on your page at proper place and other person will
    also do similar in support of you.

  • big boobs said

    Its not my first time to go to see this site, i am
    browsing this website dailly and take fastidious
    facts from here every day.

  • NigelBig said

    Hi. I have checked your devmash.com and i see you've got some duplicate content so
    probably it is the reason that you don't rank hi in google.

    But you can fix this issue fast. There is a
    tool that generates content like human, just
    search in google: miftolo's tools

  • Clarice said

    We're a group of volunteers and starting a brand new scheme in our
    community. Your website offered us with valuable information to work on. You have done an impressive activity and our entire group shall be
    thankful to you.

  • matka result said

    Aw, this was a very nice post. Finding the time and actual effort to make
    a very good article… but what can I say… I put things off a lot and don't
    seem to get nearly anything done.

  • Kalyan Panel Chart said

    Remarkable issues here. I am very happy to see your post. Thanks so much and I am taking a look forward to
    touch you. Will you please drop me a e-mail?

  • video games vs violence said

    Nice blog here! Also your website loads up very fast!
    What host are you using? Can I get your affiliate link
    to your host? I wish my site loaded up as quickly as yours lol

  • Roman said

    Installing devices like insulation, windows and doors are almost necessary,
    so if you are searching in a home you would like to ensure the
    house already has it, or anticipate carrying this out the moment possible.
    Realty - Trac disclosed that 59 percent of mortgage holders carry a loan-to-property
    value ratio of 120 percent or greater. The debt and interest is piling for the city and the investors are simply not prepared to be one of several victims with the worst ever property crises of Middle
    East.

  • Hung said

    My brother recommended I would possibly like this web site.
    He was once entirely right. This publish truly made my day.
    You cann't imagine just how much time I had spent for
    this info! Thank you!

  • air jordan 12 said

    I simply desired to say thanks once again. I'm not certain what I might have handled in the absence of these ways documented by you on such a area. It became a very traumatic setting in my position, but discovering the very expert avenue you managed it forced me to jump for happiness. I'm just thankful for your guidance and even wish you find out what an amazing job you were getting into teaching most people through your web blog. I'm certain you've never met any of us.

  • jordan retro said

    I intended to create you this very small note to be able to thank you once again for your nice tactics you have shared in this case. It is simply tremendously open-handed with you to offer freely precisely what a number of people would have offered for an electronic book to help make some bucks on their own, certainly now that you could have done it in the event you decided. Those tips also served to become a fantastic way to understand that other individuals have similar dreams like mine to figure out very much more in regard to this condition. I believe there are thousands of more pleasurable occasions ahead for people who check out your blog post.

  • nike react said

    I wanted to send you that very little word to give thanks yet again over the pleasant tips you have featured above. It is remarkably generous with people like you to offer unhampered exactly what some people would have offered for sale as an electronic book to help with making some bucks on their own, notably now that you could have tried it in the event you wanted. The concepts in addition served like a great way to fully grasp most people have a similar zeal really like my own to understand way more regarding this condition. I am sure there are thousands of more enjoyable situations in the future for people who read carefully your site.

  • nike epic react flyknit said

    I'm also writing to let you understand what a helpful experience our child went through going through yuor web blog. She realized several details, most notably what it's like to possess an awesome coaching spirit to have other individuals with ease comprehend several tortuous matters. You really exceeded visitors' desires. Many thanks for displaying these effective, trusted, informative and in addition cool thoughts on the topic to Sandra.

  • jordan 12 said

    Thanks a lot for providing individuals with a very brilliant chance to read articles and blog posts from this blog. It is often very enjoyable and also packed with a lot of fun for me and my office fellow workers to visit your website nearly 3 times in one week to read through the newest items you have. And indeed, I'm just certainly fulfilled with the spectacular knowledge served by you. Selected 3 facts on this page are absolutely the most efficient I've had.

  • nike huarache said

    Thanks for all of your effort on this blog. Kate enjoys engaging in investigations and it's really easy to understand why. My spouse and i know all about the compelling method you create simple items via the website and improve response from some other people on the issue plus our child is without question understanding a lot. Have fun with the rest of the year. Your doing a brilliant job.

  • aquiamoxicilina.icu said

    Great post. I was checking continuously this blog and I am
    impressed! Very helpful information specially the last part :) I
    care for such info a lot. I was seeking this particular
    info for a long time. Thank you and best of luck.

  • discount azithromycin said

    I know this if off topic but I'm looking into starting my own blog and was wondering what all is
    needed to get setup? I'm assuming having a blog like yours would cost a pretty penny?
    I'm not very internet savvy so I'm not 100% certain.
    Any tips or advice would be greatly appreciated.
    Thank you

  • buy keflex online said

    You could definitely see your enthusiasm within the article you
    write. The arena hopes for even more passionate writers such
    as you who aren't afraid to say how they believe.
    All the time go after your heart.

  • antabuse low price said

    It is not my first time to pay a visit this site, i am browsing this
    website dailly and get nice information from here daily.

  • http://hereerythromycin.gdn/ said

    I like what you guys tend to be up too. This sort
    of clever work and reporting! Keep up the terrific works guys I've added you guys to my own blogroll.

  • http://basprixfurosemide.fun said

    Asking questions are genuinely nice thing if you are
    not understanding something entirely, but this paragraph
    provides good understanding yet.

  • buy discount ibuprofen said

    Hi my family member! I wish to say that this article is awesome, nice written and come with approximately all important infos.
    I'd like to look extra posts like this .

  • comprare buspar said

    Hey there! Quick question that's completely off topic.

    Do you know how to make your site mobile friendly? My web site looks
    weird when browsing from my iphone4. I'm trying to find a
    template or plugin that might be able to correct this
    problem. If you have any suggestions, please share.

    Thanks!

Add a Comment