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.
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.
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
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, NULL);
if(hr== NS_E_NO_MORE_SAMPLES)
{
}
if(SUCCEEDED(hr))
{
if(dwFlags ==WM_SF_CLEANPOINT) {
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:
IWMMetadataEditor *pEditor;
hr= WMCreateEditor(&pEditor);
if(hr==S_OK)
{
pEditor->Open(m_filename.AllocSysString());
IWMHeaderInfo3* pHdrInfo;
pHdrInfo = NULL;
hr = pEditor->QueryInterface(IID_IWMHeaderInfo3,(void**)&pHdrInfo);
WORD wStream =0; WMT_ATTR_DATATYPE dType;
QWORD dwDuration;
WORD wSize =0;
hr = pHdrInfo->GetAttributeByName(
&wStream,L"Duration",&dType,(BYTE*)NULL,&wSize);
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