Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / MFC

Reading WMV ASF,WMA (windows media) Files

4.52/5 (13 votes)
8 May 20075 min read 1   3.8K  
This article describes the basics of using the Windows Media SDK for reading WMV,WMA,ASF files

Screenshot - Article.gif

Introduction

I was working on a project that involved Reading video frames out of WMV files. First I searched for an article on CodeProject and other sites but could not find anything. So I started learning from the Windows MediaSDK help files, and also used Google discussion Groups to solve the problems that I have encountered.

This article describes the basics of reading from a Windows Media file. I am adding a class that can be used for reading WMV video frames, out of a file.

Although my focus is on the reading of video frames, the steps described in this article are almost the same for reading audio buffers.

Note: Windows Media SDK requires a basic understanding of COM Interfaces.

Basic Preparation

You should download the Windows Media SDK files from Microsoft's site. I am using version 9.5.

Very important: Follow the instructions provided for setting up the environment to run WM SDK.

As in any program that uses COM, you need to remember to call CoIntialize(NULL);. this Instantiates the basic interface IUnknown, and allows the use of COM Objects.

I will now describe the steps taken for opening a WMV file.

Opening the file:

Step 1: Create a Reader Interface

There are two Reader Objects, AsyncronusReader and the SyncronusReader. The AsyncronusReader uses a callback function that receives the next frame's information. I prefer to use the SyncReader, which is easier to understand and implement.

C++
//Step 1: Create the Reader Object 
    IWMSyncReader* m_ISyncReader;                
    hr  = WMCreateSyncReader(NULL,0,&m_ISyncReader);

Step 2: Opening a file

Now that we have a Reader Object we can call the Open function and open a file for reading. The file name should be in w_char so you can use a CString Object and call the AllocSysString() method.

C++
//Step 2: Open the file
    hr = m_ISyncReader->Open(m_filename.AllocSysString());

Step 3: Receiving the Outputs and Stream Numbers

Every WMV file has a number of streams (a stream is the compressed audio or video). Output is uncompressed data read from the file. Output numbers start from 0 and stream numbers start from 1. If we have an output number, we can get its stream number. So basically, we read output (which refer to Compressed Streams stored in the file). Note: output can also be called an output stream.

This part has more steps, so I will break it down in separate parts.

3.1 Getting the number of Outputs in the file

C++
//3.1 get the number of outputs
    DWORD m_theOutputCount; 
    m_ISyncReader->GetOutputCount(&m_theOutputsCount);

3.2 Identify the Audio stream and the Video Stream

We use the IWMOutputMediaProps Interface to receive the output stream properties. Also we use the WM_MEDIA_TYPE structure.

First we get the output properties by calling the m_ISyncReader.GetOutputProps(outputNum,&IVideoOutputProps) function which gives us for an output number of IVideoOutputProps. Then we need to get the details. This is done by two calls to the m_IVideoOutputProps->GetMediaType() function, first to get the size needed to allocate for the WM_MEDIA_TYPE, then for actually getting the information.

C++
DWORD theSize;
    m_ISyncReader->GetOutputProps(i,&m_IVideoOutputProps); 
    m_IVideoOutputProps->GetMediaType(NULL,&theSize);
    m_theMediaType = ( WM_MEDIA_TYPE* ) new BYTE[theSize ];
    m_IVideoOutputProps->GetMediaType(m_theMediaType,&theSize);

Now we can check if this is an Audio or Video Stream.

For Audio Stream check:

C++
if( WMMEDIATYPE_Audio == m_theMediaType->majortype)

For Video Stream check:

C++
if( WMMEDIATYPE_Video == m_theMediaType->majortype)
    if(m_theMediaType->formattype == WMFORMAT_VideoInfo)

Now we can receive the Stream number of the video or Audio output.

C++
m_ISyncReader->GetStreamNumberForOutput(OutputNumber,(WORD*)&StreamNumber);

Also, for the video stream we should read the Video Header:

C++
WMVIDEOINFOHEADER m_theVideoInfoHeader;
memcpy(&m_theVideoInfoHeader,m_theMediaType->pbFormat,
    sizeof(WMVIDEOINFOHEADER));
m_BitmapInfoHdr= m_theVideoInfoHeader.bmiHeader;

We can receive more information on the movie, such as: its duration, the movie name, etc. We leave this for later, however.

Step 4: Setting the Reader to receive correct sample durations

Set to receive correct sample durations. To ensure that the synchronous reader delivers correct sample durations for video streams, you must first configure the stream output. Call the IWMSyncReader::SetOutputSetting method to set the g_wszVideoSampleDurations, setting it to TRUE. If true, the reader will deliver accurate sample durations.

C++
BYTE* pValue = new BYTE[5];
    strcpy((char*)pValue,"TRUE");
    hr = m_ISyncReader->SetOutputSetting(m_iVideoOutputNumber,
        g_wszVideoSampleDurations,WMT_TYPE_BOOL,pValue,sizeof(pValue));

Step 5: Set To receive Uncompressed Samples

The SetReadStreamSamples method specifies whether samples from a stream will be delivered, compressed, or uncompressed. This is how you get set to receive uncompressed samples:

C++
m_ISyncReader->SetReadStreamSamples(m_iVideoStreamNumber,FALSE);

Reading Samples From the File

Reading the samples with the Synchronous Reader is quite simple.

We call the IWMSyncReader::GetNextSample() function. This function fills a INSSBuffer Interface pointer, you choose what stream to read from, and you get the next sample. You also get its duration and position time in the movie.

When receiving a sample you should check if it's a CLEANPOINT sample. This sample is a picture you would want to read.

When you get to the end of the movie you will receive NS_E_NO_MORE_SAMPLES as the HRESULT.

See this next code snippet for details:

C++
QWORD cnsSampleTime = 0;
QWORD cnsSampleDuration = 0;
DWORD dwFlags = 0;
DWORD dwOutputNumber;
        
HRESULT hr = m_ISyncReader->GetNextSample(m_iVideoStreamNumber,
                &m_pINSSBuffer,
                &cnsSampleTime,
                &cnsSampleDuration,
                &dwFlags,
                NULL,//&dwOutputNumber,
                NULL);
if(hr== NS_E_NO_MORE_SAMPLES)
{
    //finished reading the file
}

if(SUCCEEDED(hr))
{
    if(dwFlags ==WM_SF_CLEANPOINT) //this a clean point frame, a picture to 
                                       //take read sdk for explantion 
    {
        //1. Get the Bitmap from the frame
       m_pINSSBuffer->GetBufferAndLength(&m_bitmapBuffer,
               &m_dwrdBitmapBufferLength);
    }
    m_pINSSBuffer->Release();
    m_pINSSBuffer = NULL;
}

The buffer we receive here is only the DIB, the bitmap data without the header, which we received in early stages.

Now you can save pictures to the disc, display the frames on your window, or analyze the frames for other purposes.

Reading Other file properties

As I mentioned earlier, you can get more information from the file, such as its duration. This is done in a few steps.

For receiving extra information on the file such as the file Duration, Title, number of frames, we do the following.

Note: this information is there only if when creating the file the information was included. So even if you try to get some information (such as the numberOfFrames) you cannot.

To receive the information you need to create two objects:

C++
IWMMetadataEditor *pEditor; IWMHeaderInfo3* pHdrInfo;

First you create the Editor, then you can receive the HeaderInfo.

Here is the code that receives the Duration of the file:

C++
//step 6: Get wmv Duration (total time)
//6.1 create a MetaData Editor
IWMMetadataEditor *pEditor;

hr= WMCreateEditor(&pEditor);
if(hr==S_OK)
{
    pEditor->Open(m_filename.AllocSysString());
    //6.2 create a HeaderInfo interface. 
    IWMHeaderInfo3* pHdrInfo;
    pHdrInfo    = NULL;
    hr = pEditor->QueryInterface(IID_IWMHeaderInfo3,(void**)&pHdrInfo);
    WORD wStream =0;// for any stream;
    WMT_ATTR_DATATYPE dType;
    QWORD dwDuration;
    WORD wSize =0;
    //first Call for receiving the buffer size
    hr = pHdrInfo->GetAttributeByName(
        &wStream,L"Duration",&dType,(BYTE*)NULL,&wSize);
    //know that we have the size we allocate the memory and read the attribute
    BYTE* pValue;
    if(wSize>0) 
        pValue = new BYTE[wSize];
    hr = pHdrInfo->GetAttributeByName(&wStream,L"Duration",
        &dType,pValue,&wSize);
    dwDuration =*((QWORD*)pValue);
    m_qwTotalTimeInSeconds = (dwDuration*100)/1000000000;
    SAFE_ARRAYDELETE(pValue);
    SAFE_RELEASE(pHdrInfo);
    SAFE_RELEASE(pEditor);            
}

Investigate Windows Media SDK for further information about what attributes you can receive, and what names to use in the GetAttributeByName() function.

Well, this is it. I hope you find this article useful.

History

May 8 - Initial posting

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here