Mijn pagina voor prettig werken op de PC www.pcpret.nl |
Homepage | Stuur bericht | Gastenboek | Website Blog |
I wish you much pleasure with learning and using the DirectX.Capture class library and I hope you will enjoy my article also.
Hans Vosman
DirectX.Capture
class example, I noticed that minor to many code modifications
seems to be needed.
I was in favor of the minor version using SetOutputFileName()
and RenderStream()
.
I noticed that ConfigureFilterUsingProfileGuid()
was used also.
Only it was not fully clear to me what to do with ConfigureFilterUsingProfileGuid()
.
It was used for changing the Windows Media audio/video format but the example showed me a video only guid and
I had the idea that there were much more possible choices.
The SetOutputFileName()
is very important.
It is not only setting the filename as the name suggest, no, it will also add filters to the graph.
For Asf this will be the Asf file writer, for Avi this will be Avi muxer and file writer (two filters!).
IWMProfile
interface.
Actually the selection is done by loading a profile representing the audio/video file format.
I noticed that MSDN also mentioned that the ConfigureFilterUsingProfileGuid()
without a note it should not be used anymore.
The amazes me a bit because in DirectShowLib
I found a note that the interface became obsolete.
I found an interesting example that showed me that it was possible to get the audio/video formats:
Windows Media Audio compressor By Idael Cardoso.
This article describes how IWMProfile
can be used, and the sample program showed me a nice listbox with a list of
Windows Media Audio formats.
It helped me in finding more information:
CSharp Windows Media Format SDK, Translation by Idael Cardoso.
Than I made up my mind and I thought, first get the program working, the rest
will come later.
mediaControl.Run()
.
What I did in general was adding the filter, having the property window for making specific settings, to the
graph in advance.
Upon the real capturing the added filter(s) were be connected to the graph (either using RenderStream()
or a direct connect).
That method I used for Wav, Mp3 and Avi file saving, but seems to cause problems for Asf (+ Wmv, Wmv).
Why the mediaControl.Run()
was called, well when selecting something in the menu, updateMenu()
is called ...
My impression is that it has something to do with the filter itself.
I noticed that all examples I saw, either configured the filter immediately after the SetOutputFileName()
or did not
configure at all. So I choosed for the first solution. For audio this is okay
because the default choice is good enough. For video this would not be
acceptable. renderGraph
in Capture.cs.
In the code the mediaSubtype
is set first, then SetOutputFileName()
is called to add the Avi
multiplexer and the file writer.
Furthermore the filename is stored. The next step is to initialize the video rendering.
A possible compressor filter is taken into account. After this the audio rendering is initialized.
Upon a call of mediaControl.Run()
in the function StartPreviewIfNeeded()
, the capture graph starts.
//Original code fragment renderGraph Capture.cs
Guid mediaSubType = MediaSubType.Avi;
hr = captureGraphBuilder.SetOutputFileName( ref mediaSubType, Filename, out
muxFilter, out fileWriterFilter );
if( hr < 0 ) Marshal.ThrowExceptionForHR( hr );
// Render video (video -> mux)
if ( VideoDevice != null )
{
// 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 );
}
}
// Render audio (audio -> mux)
if ( AudioDevice != null )
{
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;
ConfigureFilterUsingProfileGuid()
for selecting the proper audio/video format via the Guid that corresponds with a specific audio/video format and so
corresponds with a specific profile.
Another advantage is that way the coding will be easier to understand.
In a future topic the IWMProfile
interface could be explained in some more detail.
For now it would mean that this article would not be written because it would become a little bit too long.
So it this article makes you more curious, I think, that is very positive.
IWMProfile
interface can be used because IWMProfile
can provide information about the streams it uses.
recFileMode
contains the audio/video recording file mode, so this variable gets value RecFileModeType.Wma
.
The variable recFileMode
should be accessed via RecFileMode
.
There is a check added that prevents changing the file mode during file capturing.
This to prevent strange side effects.
It also shows that other functionality could be executed upon changing the value of recFileMode
.
A nice feature would be the change of the filename extension.
This code should be put in Capture.cs, preferably in the beginning because there you can find more declarations.
///
/// Recording file mode type enumerations
///
public enum RecFileModeType
{
/// Avi video (+ audio)
Avi,
/// Wmv video (+ audio)
Wmv,
/// Wma audio
Wma,
}
private RecFileModeType recFileMode = RecFileModeType.Wma;
///
/// Recording file modes
///
public RecFileModeType RecFileMode
{
get { return(recFileMode); }
set
{
if(this.graphState == GraphState.Capturing)
{
// Value may not be changed now
return;
}
recFileMode = value;
}
}
RenderStream()
, mentioned earlier.
The major difference is that the file recording mode is taken into account.
Still this example is kept simple, so for saving a specific audio and/or video file settings might be important.
Keep in mind that saving of Wma file is needed:
RecFileMode = RecFileModeType.Wma
Keep in mind that saving of Wmv file is needed:
RecFileMode = RecFileModeType.Wmv
Keep in mind that saving of Avi file is needed:
RecFileMode = RecFileModeType.Avi
The code explains itselfs (I hope).
There are checks added so depending on the file format specific actions can be performed.
The first action is the initialization of mediaSubtype
.
The next action is to configure the Asf file writer.
The configuration is a one-liner.
Interesting is the type casting of the file writer pointer for calling ConfigureFilterUsingProfileGuid()
.
This solution is specific for .Net, it gives an easy solution for changing the
preferred audio/video profile.
There is one question left: What does WMProfile_V80_64StereoAudio
means?
WMProfile_V80_64StereoAudio
is the audio recording format I choosed as default.
There are more choices possible (will be described later on).
For a different choice, a different value must be used.
Also, if you want to save video, just select a valid Windows media video format.
The audio and video rendering sections looks the same, with one major difference.
Upon entering the video rendering section there is a check on the file format, for audio capturing,
no video must be rendered so that section must be ignored!
// Record captured audio/video in Avi, Wmv or Wma format
Guid mediaSubType; // Media sub type
// Set media sub type
if(RecFileMode == RecFileModeType.Avi)
{
mediaSubType = MediaSubType.Avi;
}
else
{
mediaSubType = MediaSubType.Asf;
}
// Intialize 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, however DShowNet does not
// have the interface that is needed. Please ensure it is added.
if(RecFileMode == RecFileModeType.Wma)
{
IConfigAsfWriter lConfig = muxFilter as IConfigAsfWriter;
// Obsolete interface?
// According to MSDN no, according to DirectShowLib yes.
// For simplicity, it will be used ...
hr = lConfig.ConfigureFilterUsingProfileGuid(WMProfile_V80_64StereoAudio);
if(hr < 0)
{
// Problems with selecting video write format
// Release resources ... (not done yet)
Marshal.ThrowExceptionForHR( hr );
}
}
// Render video (video -> mux) if needed or possible
if((VideoDevice != null)&&(RecFileMode != RecFileModeType.Wma))
{
// 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 );
}
}
}
// Render audio (audio -> mux) if possible
if ( AudioDevice != null )
{
// Keep in mind that for 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;
ConfigureFilterUsingProfileGuid()
needs to be called for setting the proper audio format.
Which formats exists?
In this example there are seven specific Windows media audio formats provided (hard coded) by means of a Guid
.
Each Guid
stands for a special format and profile.
There are more Windows Media formats; however, those formats supports also video
(and audio).
The best way to get these formats is to use IWMProfile
functionality.
Than you will be sure that you can get formats that really exists. In a future code sample, the IWMProfile
method can be shown.
For now, this will be though enough.
Thanks to the IWMProfile
interface I was able to retrieve all names that belong to a specific file format and
profile.
The following code, with at least the audio format you are going to use, should be put somewhere in Capture.cs.
// Windows Media Audio 8 for Dial-up Modem (Mono, 28.8 Kbps)
private static readonly Guid WMProfile_V80_288MonoAudio = new Guid("7EA3126D-E1BA-4716-89AF-F65CEE0C0C67");
// Windows Media Audio 8 for Dial-up Modem (FM Radio Stereo, 28.8 Kbps)
private static readonly Guid WMProfile_V80_288StereoAudio = new Guid("7E4CAB5C-35DC-45bb-A7C0-19B28070D0CC");
// Windows Media Audio 8 for Dial-up Modem (32 Kbps)
private static readonly Guid WMProfile_V80_32StereoAudio = new Guid("60907F9F-B352-47e5-B210-0EF1F47E9F9D");
// Windows Media Audio 8 for Dial-up Modem (Near CD quality, 48 Kbps)
private static readonly Guid WMProfile_V80_48StereoAudio = new Guid("5EE06BE5-492B-480a-8A8F-12F373ECF9D4");
// Windows Media Audio 8 for Dial-up Modem (CD quality, 64 Kbps)
private static readonly Guid WMProfile_V80_64StereoAudio = new Guid("09BB5BC4-3176-457f-8DD6-3CD919123E2D");
// Windows Media Audio 8 for ISDN (Better than CD quality, 96 Kbps)
private static readonly Guid WMProfile_V80_96StereoAudio = new Guid("1FC81930-61F2-436f-9D33-349F2A1C0F10");
// Windows Media Audio 8 for ISDN (Better than CD quality, 128 Kbps)
private static readonly Guid WMProfile_V80_128StereoAudio = new Guid("407B9450-8BDC-4ee5-88B8-6F527BD941F2");
Using the names in the code, might look like this:
DShowNET
does not support the interface of IConfigAsfWriter
.
DirectShowLib
supports this interface; so if you use that library
then there is no extra work. This interface can be added to Capture.cs or another suitable place.
Keep in mind to change the naming accordingly.
[Guid("45086030-F7E4-486a-B504-826BB5792A3B"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IConfigAsfWriter
{
/// Obsolete?
[PreserveSig]
int ConfigureFilterUsingProfileId([In] int dwProfileId);
/// Obsolete?
[PreserveSig]
int GetCurrentProfileId([Out] out int pdwProfileId);
/// Obsolete?
[PreserveSig]
int ConfigureFilterUsingProfileGuid([In, MarshalAs(UnmanagedType.LPStruct)] Guid guidProfile);
[PreserveSig]
int GetCurrentProfileGuid([Out] out Guid pProfileGuid);
/// Obsolete?
[PreserveSig]
int ConfigureFilterUsingProfile([In] IntPtr pProfile);
/// Obsolete?
[PreserveSig]
int GetCurrentProfile([Out] out IntPtr ppProfile);
[PreserveSig]
int SetIndexMode([In, MarshalAs(UnmanagedType.Bool)] bool bIndexFile);
[PreserveSig]
int GetIndexMode([Out, MarshalAs(UnmanagedType.Bool)] out bool pbIndexFile);
}
DirectX.Capture
class example will work with TV-cards using a wired connection to a sound card.
So for those who have a special TV card, the modification might be interesting:
during preview: do audio rendering.
I added an option via a variable audioviapci
to tell the program to use the extra code or
do nothing.
false
must be used for wired audio connections (default choice).
true
must be used when the audio is coming via the PCI bus.
The variable audioviapci
should be put somewhere in Capture.cs.
There is one limitation: the TV-card driver must have a capture device for audio.
If the TV-card driver does not have such device, then the following code will not work either.
How to handle such case could be a new article.
For capturing audio, this modification is not needed.
It is just meant for listening.
// Option for selection audio rendering via the pci bus of the TV card
// For wired audio connections the value must be false!
// For TV-cards, like the Hauppauge PVR150, the value must be true!
// This TV-card does not have a wired audio connection. However, this
// option will work only if the TV-card driver has an audio device!
private bool audioviapci = false;
The actual code should be put in the function renderGraph()
in Capture.cs.
This code should be inserted after the check
if (wantPreviewRendered && !isPreviewRendered)
in renderGraph()
where the video rendering is started.
// Special option to enable rendering audio via PCI bus
if(audioviapci)
{
med = MediaType.Audio;
hr = captureGraphBuilder.RenderStream( ref cat, ref med, audioDeviceFilter, null, null );
if( hr < 0 )
{
Marshal.ThrowExceptionForHR( hr );
}
}
The last parameter in RenderStream()
is null
.
This means the audio is rendered to the default audio renderer.
It might be possible that there is still no audible sound.
In that case do the following:
Normally, I use conditions to have the original and the new code in the same file. This is handy if there is an error in the code. Then I compare that code with the previous version. I removed the conditions because this souce file should be easy to use. I did not remove the conditions completely, so you will be able to find the exact location of the code changes easily.
DirectX.Capture
class, and I hope I provided you
an enhancement that might be useful to you.
Feel free to post any comments and questions.
Back to the beginning.