Reading WMV ASF,WMA (windows media) Files






4.52/5 (12 votes)
May 8, 2007
5 min read

116239

3753
This article describes the basics of using the Windows Media SDK for reading WMV,WMA,ASF files
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.
//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.
//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
//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.
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:
if( WMMEDIATYPE_Audio == m_theMediaType->majortype)
For Video Stream check:
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.
m_ISyncReader->GetStreamNumberForOutput(OutputNumber,(WORD*)&StreamNumber);
Also, for the video stream we should read the Video Header:
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.
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:
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:
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:
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:
//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