|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionThis article is a follow-up of my previous article Audio File Saving for the DirectX.Capture Class. This article describes the saving of video in Windows Media file format (WMV). Saving video is more complex than saving audio. This is because saving video can mean saving video with and without audio. Because of that, there is a bigger chance that video saving fails due to a conflict with the selected save format. Furthermore, there are many more predefined video formats than audio formats, so it would be nice to have a user friendly solution of selecting a specific video format that suits your needs the best. The DirectX.Capture class example was a big help for me in finding out how video file saving in the Windows Media format could be done. Since the Using Profiles...Why use profiles? Well, each Windows Media format is represented by a profile. There is a long list of system profiles that can be used for Windows Media. What's interesting is that it is also possible to make a profile or modify an existing profile. The system profiles can be found in the file WMSysPrx.prx in the Windows directory. A PRX file contains the description of the profile in XML format. Maybe you already knew, but Microsoft provides in the Windows Media Encoder software package with a Windows Media profile editor named WMProEdt.exe. It's worth looking into that. Here's part of a Windows Media Encoder profile: <profile version="589824"
storageformat="1"
name="Higher quality video (VBR 97)"
description="">
<streamconfig majortype="{73646976-0000-0010-8000-00AA00389B71}"
streamnumber="1"
streamname="Video Stream"
inputname="Video409"
bitrate="100000"
bufferwindow="-1"
reliabletransport="0"
decodercomplexity=""
rfc1766langid="en-us"
vbrenabled="1"
vbrquality="97"
bitratemax="0"
bufferwindowmax="0">
<videomediaprops maxkeyframespacing="80000000"
quality="0"/>
<wmmediatype subtype="{33564D57-0000-0010-8000-00AA00389B71}"
bfixedsizesamples="0"
btemporalcompression="1"
lsamplesize="0">
<videoinfoheader dwbitrate="100000"
dwbiterrorrate="0"
avgtimeperframe="333667">
...
</profile>
It is an XML file containing the name, the description, the Windows structure names, the attribute names and their values of the profile. So, a profile provides all details about the supported audio and video streams. A profile is used to configure the ASF file writer. The ASF file writer is the filter that will handle the actual multiplexing, the encoding of the video and/or audio and the actual file saving! By configuring the ASF file writer, the video (and/or audio) will be saved in the chosen format. Much more importantly for me, however, the profile can also be used as an information source for a user friendly solution of selecting a Windows Media file format. A good start for learning more about Windows Media and profiles is to read about the Windows Media Format SDK at MSDN. Just search for this article, the link is updated quite often. This SDK contains samples, header files, programs and documentation. The good news is that the Windows Media Format SDK can also be downloaded. Problems with Using the ASF File WriterIn my previous article about audio saving, Audio File Saving for the DirectX.Capture Class, I mentioned that the ASF file writer gives conflicts when it is added to the graph, but is not connected. Because of that, I could not use the default property window for selecting a Windows Media format for file saving. A new form is needed to provide similar functionality as the default property window. That is not too bad because now more information can be shown about the profile itself than the default property window shows. For example, information about supporting audio and video, the bit rate and the description of a profile can be shown. It is even possible to show the frame size, but that is some more work because the frame size is a little bit harder to retrieve. To have the specific profile information is handy because you will know in advance if audio and/or video can be saved. Another good reason is that the program can use this information also. If a selected format does not match the rendered audio and/or video streams to the file writer, the program can detect that and can give a warning. IWMProfileTo get a profile, the If the profile is found, then the name can be retrieved via IWMWriterThe If a video input property is found, then via Another interesting feature might be the use of own-made profiles. Using the Using the CodeFor saving video in a Windows Media format, four new classes are introduced: I wrote the WMProfileDataThe class public class WMProfileData
{
/// <summary> Name of the profile </summary>
protected string name;
/// <summary> Guid of the profile </summary>
protected Guid guid;
/// <summary> Description of the profile </summary>
protected string description;
/// <summary> Audio bit rate </summary>
protected int audioBitrate;
/// <summary> Video bit rate </summary>
protected int videoBitrate;
/// <summary> Indicates whether this profile supports an audio
/// stream
/// </summary>
protected bool audio;
/// <summary> Indicates whether this profile supports an video
/// stream
/// <summary>
protected bool video;
/// <summary> Indicates whether this profile is the one currently in
/// use </summary>
protected bool enabled;
}
AsfFormatThis class uses the hr = profile.GetStreamCount(out streamCount);
if((hr >= 0)&&((streamCount > 0))
{
IWMStreamConfig streamConfig = null;
Guid streamGuid = Guid.Empty;
audio = false;
video = false;
audioBitrate = 0;
videoBitrate = 0;
for(short i = 1;(i <= streamCount)&&(hr >= 0); i++)
{
hr = profile.GetStreamByNumber(i, out streamConfig);
if((hr >= 0)&&(streamConfig != null))
{
hr = streamConfig.GetStreamType(out streamGuid);
if(hr >= 0)
{
if(streamGuid == MediaType.Video)
{
video = true;
hr = streamConfig.GetBitrate(out videoBitrate);
if(hr < 0)
{
videoBitrate = 0;
}
}
else
if(streamGuid == MediaType.Audio)
{
audio = true;
hr = streamConfig.GetBitrate(out audioBitrate);
if(hr < 0)
{
audioBitrate = 0;
}
}
hr = 0; // Allow possible unreadable bitrates
}
}
} // for i
}
The AsfFormThe Code Changes in Capture.csThe following code shows possible use of the switch(recFileMode)
{
case RecFileModeType.Wmv:
if(asfFormat == null)
{
asfFormat = new AsfFormat(AsfFormat.AsfFormatSelection.Video);
}
else
{
asfFormat.GetProfileFormatInfo(AsfFormat.AsfFormatSelection.Video);
}
break;
case RecFileModeType.Avi:
break;
default:
// Unsupported file format
return;
}
In addition, there is a one-liner that changes the extension of the file name. // Change filename extension
this.filename = Path.ChangeExtension(this.filename,
RecFileMode.ToString().ToLower());
In the function // Render the file writer portion of graph (mux -> file)
// Record captured audio/video in Avi, Wmv or Wma format
Guid mediaSubType; // Media sub type
bool captureAudio = true;
bool captureVideo = true;
IBaseFilter videoCompressorfilter = null;
// Set media sub type and video compressor filter if needed
if(RecFileMode == RecFileModeType.Avi)
{
mediaSubType = MediaSubType.Avi;
// For Avi file saving a video compressor must be used
// If one is selected, that one will be used.
videoCompressorfilter = videoCompressorFilter;
}
else
{
mediaSubType = MediaSubType.Asf;
}
// Initialize the Avi or Asf file writer
hr = captureGraphBuilder.SetOutputFileName( ref mediaSubType, Filename,
out muxFilter, out fileWriterFilter );
if( hr < 0 ) Marshal.ThrowExceptionForHR( hr );
// For Wma (and Wmv) a suitable profile must be selected. This
// can be done via a property window, however the muxFilter is
// just created. if needed, the property windows should show up
// right now!
// Another solution is to configure the Asf file writer, the
// use interface must ensure the proper format has been selected.
if((RecFileMode == RecFileModeType.Wma)||
(RecFileMode == RecFileModeType.Wmv))
{
if(this.AsfFormat != null)
{
this.AsfFormat.UpdateAsfAVFormat(this.muxFilter);
this.AsfFormat.GetCurrentAsfAVInfo(out captureAudio,
out captureVideo);
}
}
For saving video, the video stream check is performed. This check will prevent video file saving if an audio-only file save was requested. For AVI only, the video compressor value will be used. // Render video (video -> mux) if needed or possible
if((VideoDevice != null)&&(captureVideo))
{
// Try interleaved first, because if the device supports it,
// it's the only way to get audio as well as video
cat = PinCategory.Capture;
med = MediaType.Interleaved;
hr = captureGraphBuilder.RenderStream( ref cat, ref med,
videoDeviceFilter, videoCompressorfilter, muxFilter );
if( hr < 0 )
{
med = MediaType.Video;
hr = captureGraphBuilder.RenderStream( ref cat, ref med,
videoDeviceFilter, videoCompressorfilter, muxFilter );
if ( hr == -2147220969 )
throw new DeviceInUseException( "Video device", hr );
if( hr < 0 ) Marshal.ThrowExceptionForHR( hr );
}
}
For saving audio, the audio stream check is performed. This check will prevent audio file saving if a video-only file save was requested. // Render audio (audio -> mux) if possible
if((AudioDevice != null)&&(captureAudio))
{
// If this Asf file format than please keep in mind that
// certain Wmv formats do not have an audio stream, so
// when using this code, please ensure you use a format
// which supports audio!
cat = PinCategory.Capture;
med = MediaType.Audio;
hr = captureGraphBuilder.RenderStream( ref cat, ref med,
audioDeviceFilter, audioCompressorFilter, muxFilter );
if( hr < 0 )
Marshal.ThrowExceptionForHR( hr );
}
isCaptureRendered = true;
didSomething = true;
Code Changes in CaptureTest.csIn CaptureTest.cs, the menuAVRecFileModes.MenuItems.Clear();
// Fill in all file modes, use enumerations also as string (and file
// extension)
for(int i = 0; i < 3; i++)
{
m = new MenuItem(((DirectX.Capture.Capture.RecFileModeType)i).ToString(),
new EventHandler(menuAVRecFileModes_Click));
m.Checked = (i == (int)capture.RecFileMode);
menuAVRecFileModes.MenuItems.Add(m);
}
menuAVRecFileModes.Enabled = true;
Points of InterestThis example has additional support to get audible sound when using a TV card. It provides audio via the PCI bus, so no wired audio connection is involved. During testing, I noticed that there were often no audio sources listed. If the sources were available, I noticed that the sources were invalid, so a source could not be selected. For the last problem, I found two solutions. The first solution was to reload I also did some testing with saving files in Mpeg2 format. There are already some interesting articles on this subject, Preview and Record with MPEG2 Capture Device and Working with Stream Buffer Engine - TIME SHIFT on Windows XP Service Pack 1 written by BeCapture. The second article gives a very good explanation of how the Mpeg2 demultiplexer should be configured. Still, I could not get the last example working. Looking at some of the problems I got, I am wondering if this is a working example. Nevertheless, these examples helped me to get a working C# solution. The concern I have is that the solution depends very much on the Mpeg2 encoder and decoder filters that are available. Graphedt is a big help in checking whether a combination may work, but still it is tough to get a good result. Sometimes a design seems to work, but the file stays zero bytes. Sometimes the property page information of a filter needs to be modified to get a good result. The easiest one to use for file saving that I found was a Nero audio/video encoder. Using a hardware Mpeg2 encoder is also not too difficult. More difficult is finding a proper dump filter. For the moment, I do not think an article on this subject will give new information. However, I can imagine that for pulling the pieces together, some help will be very useful. So, if you have questions on this subject, please let me know. During testing, I did some investigation of the good old I noticed that the cat = PinCategory.Preview;
med = MediaType.Video;
hr = captureGraphBuilder.RenderStream( ref cat, ref med, videoDeviceFilter,
null, null );
if( hr < 0 )
Marshal.ThrowExceptionForHR( hr );
Unfortunately, the file save functionality needs the capture pin. Using Video Mixing Renderer 9 for video rendering or using a lower video preview resolution will quite often also give a good picture. Here is an example for using Video Mixing Renderer 9 as the video renderer. cat = PinCategory.Preview;
med = MediaType.Video;
IBaseFilter VMRfilter = (IBaseFilter) new VideoMixingRenderer9();
hr = graphBuilder.AddFilter(VMRfilter, "Video mixing renderer 9");
if( hr < 0 )
{
Marshal.ThrowExceptionForHR( hr );
}
hr = captureGraphBuilder.RenderStream(ref cat, ref med, videoDeviceFilter,
null, VMRfilter);
if( hr < 0 )
{
Marshal.ThrowExceptionForHR( hr );
}
I did not find a solution that worked on all my capture cards, i.e. Hauppauge PVR150, Pinnacle PCTV, MX460 Vivo, Radeon 8500 Vivo. The Hauppauge cards in particular caused me problems. On one system it works great, but on another system I usually get a black picture. The code example has been tested with Visual Studio 2003 as well as Visual Studio 2005. Conflicts between these two compiler versions might occur. I added the conditional Feedback and ImprovementsI hope this code helps you in understanding the structure of the History
| ||||||||||||||||||||