




| DirectShow Tips : Developing WAV - MP3 Converter |
|
|
|
| Written by Zamrony P. Juhara |
| Saturday, 16 January 2010 14:41 |
|
MP3 is one of most popular format for storing audio data. The compression algorithm is able to decrease the size for storage without much audio quality loss while WAV format is very popular because it is default audio format in Windows operating system. However due to nature of WAV format, storing audio data may consume huge storage capacity. This article discuss how we can utilize DirectShow to convert WAV to MP3 or vice versa using DirectShow and Delphi. IntroductionIn this article, we are going to utilize DirectShow to do file format conversion, i.e from WAV file into MP3 and vice versa. You are also going to learn how to construct filter graph manually, including how to add filter into filter graph and connect a filter with another filter. PrerequisiteYou are expected to have some knowledge about DirectShow and how to use DirectShow with Delphi. You can read my articles Multimedia Player with DirectShow Part 1 and Multimedia Player with DirectShow Part 2 as a starting point. You are also expected to have knowledge on COM programming subject, because we are going to use it a lot in DirectX, at least you know how to create an instance of COM object. Why? Because DirectX (including its components) built on the top of COM. Software neededWe are going to need access to these following softwares:
Short info about filtersDirectShow is architecture for multimedia streaming on Windows. Building blocks of DirectShow are software component called filter. Filters are COM server which is operate on multimedia stream data and it's encapsulated in IBaseFilter interface. Filters are doing many things, some filters can do data loading from storage media, decompressing and/or compressing data, manipulating data, writing multimedia stream to storage media and still many more. Filters may need input or produce output. A filter at least, has one input or output, where number of input or output is not limited. A filter may need two inputs and produce one output. Input and output on filter is called pin. A filter in general, only doing specific task, for example, to load data from file. To be able to do something more complex, filters can be connected through their pins. For two pins of two filters can be connected, input pin of first filter must be connected to output pin of second filter and both pins must agree with media type for data transfer between both pins. How to connect pins will be discussed soon. Example is shown on following diagram:
Fig.1 Filter graph WAV playback To play WAV file on the speaker, first thing to do is to add and set File Source with name of file to load. Its output pin, which is in the form of berupa audio stream data, connected to Wave Parser filter which its job is to parse audio stream into audio data suitable for WaveOut Renderer. WaveOut Renderer filter sends audio data to the speaker. Note that File Source only has one output without any input. Wave Parser has one input and one output. WaveOut Renderer has one input pin. Typically, filters must be connected downstream, i.e from source to destination. So, for three filters above, File Source must connect to Wave Parser first, before Wave Parser can connect toWaveOut Renderer. But some filters don't have to be connected downstream. WAV to MP3 conversion.To convert WAV file into MP3, we are goin to use following filter construction:
Fig.2 Filter graph for WAV toMP3 conversion. For some WAV files with known waveformat, Wave Parser can connect directly to MPEG Layer-3 filter. For example, file that I used (thud.wav), its waveformat is not known, Wave Parser must be connected to intermediate filter capable to convert waveformat into suitable waveformat for MPEG Layer-3. WAV Dest transforms audio data into stream so it can send it to File Writer for saving stream to storage media. WAV Dest is not DirectShow standard filte. This filter is a sample filter included in DirectX SDK. For your convenience, this filter (wavdest.ax) is included in source code download. You must register it with Windows i.e run RegSvr32 wavdest.ax or excute batch file regwavedest.bat. MP3 to WAV conversion.For MP3 to WAV conversion, we need this filter graph construction:
Fig.3 Filter graph for MP3 toWAV conversion. Get filter instance.To get filter instance, we can use CoCreateInstance()
where clsid holds GUID filter identifier and aFilter is variabel of type IBaseFilter. Getting filter through enumeration.Some filters cannot be created directly with CoCreateInstance(), for example compressor category filters. From my experience, with CoCreateInstance, those filters will be successfully created, but do not doing any encoding as expected. For compressor filters, we are going to get its instance through enumeration. To enumerate filters available on our system, we must create system device enumerator with type of ICreateDevEnum using CoCreateInstance(). System device enumerator class identiifier is CLSID_SystemDeviceEnum. If we succeed, we create class enumerator by using CreateClassEnumerator() with GUID class in parameter classid. For audio compressors, we use kita bisa CLSID_AudioCompressorCategory. Second parameter is variable that will hold moniker from enumeration result. Third parameter is type of filter we are going to enumerate. If we set it to zero, we enumerate all kind of filter type.
With Next(), we take moniker for each filter. First parameter of Next(), is number of moniker that we are going to retrieve. We set with 1 to retrieve moniker one by one. Second parameter is variable that will hold moniker instance. Third parameter is number of moniker actually retrieved. We set it to nil bcuse we don't need it. Next() returns value of S_OK if moniker succesfully retrieved. If we succeed, we take FriendlyName filter and compare it with name passed via parameter. To get FriendlyName, we use BindToStorage(). First and second parameters respectively are context binding dan moniker to the left. Both parameters is not relevant and can be set to nil. Third parameter of BindToStorage is GUID of interface we requested. Fourth parameter is variable that will hold pointer to IPropertyBag instance. From propBag, we read its content to get its FriendlyName through Read() function. We need to make sure that filter name uses widestring type, because COM uses widestring. Result is compared. If it is same, then this moniker holds filter that we need. We create filter instance by using BindToObject() of moniker. Parameters of BindToObject() is equal to BindToStorage(). Adding filter.Filter is added to filter graph via AddFilter() function belong to IFilterGraph interface.
afilter is instance of IBaseFilter that will be added. Second parameter is name of filter, whic is type of PWidechar. Getting pin of a filterWe need pins to be able to connect filters. To get pin of a filter, we enumerate pins in a filter. EnumPins() function of IBaseFilter interface is for you. Number of parameters of this function is only one, i.e variable that will hold instance of IEnumPins.
If EnumPins succeed, pPins holds pointer IEnumPins instance. With Next(), we retrieve pin one by one.If a pin is found, we take its pin direction, and check whether pin is input pin or output pin. Then, we compare with pin direction that we are looking for. And also we compare if its index is same with index we need. Connecting two filtersTo connect two filters, we need to know their pins, by using GetPin above. Thera are two ways to connect two filters, i.e using Connect of IPin interface or Connect of IGraphBuilder. The first Connect is direct connection, if both pins agree with media type, connection can be created. Othewise, connection is failed. Connect of IGraphBuilder, is little bit different. If both pins do not have any media type matched, IGraphBuilder will try to connect first filter with other filter that is capable to transform media type of first filter into media type matched secod filter. If it cannot find matched intermediate filter, connection is failed. In our pplication demo we are going to use Connect IGraphBuilder.
We take output pin of first filter and input pin of second filter and then connect both pins with Connect(). WAV - MP3 Converter Application Design.Conversion process will be separated into two classes, TWAVToMP3Converter for WAV to MP3 conversion and TMP3ToWAVConverter for MP3 to WAV conversion. we are going to inherit both classes from TBasicConverter classes which is derived from TBasicPlayer class. TBasicPlayer class is base class for multimedia player that we have built in Multimedia Player with DirectShow Part 1 and Multimedia Player with DirectShow Part 2 articles. Following diagram is UML classes diagram for WAV - MP3 conversion.
Fig.4 UML Diagram for WAV - MP3 conversion. TBasicPlayer will be modified. We will add protected methods useful for filter connection process (ConnectFilter), getting pin of a filter (GetPin), getting filter through enumeration (FindFilterByName) and adding filter to filter graph by its class identifier (AddFilterByCLSID). For TBasicConverter, we add two properties, i.e SrcFilename and DstFilename. Those properties will hold source filename and destination filename. We also add Convert method which its purpose is for building filter graph and run conversion process. In TWAVToMP3Converter class, virtual abstract method, BuildFilterGraph, is overriden. In this method we construct filters we beed for WAV to MP3 conversion. In TMP3ToWAVConverter class, BuildFilterGraph is override to do filter graph contruction for MP3 to WAV conversion.
TBasicPlayer looks like following code:
WAV - MP3 Converter Application ImplementationTBasicConverter Implementation
Convert() is only wrapper for two method call i.e BuildFilterGraph and Run. BuildFilterGraph's Implementation for WAV to MP3 conversionI think all comments in code will explained flow of execution. Maybe what I need to explain is how to set File Source filter to load file to processed. File Source where its address is in aFileReader variable has type of IBaseFilter. To be able to set source filename, we need instance of IFileSourceFilter interface. With its Load() method, we load source file. First parameter is name of file with type of PWideChar and second parameter is playlist. We do not use it here, so we set it to nil. MPEG Layer-3 filter instance is retrieved with FindFilterByName().
We do the similar way for File Writer, except that we get instance of IFileSinkFilter instead of IFileSourceFilter. We set target filename using SetFilename(). First parameter is filename and second parameter is playlist. BuildFilterGraph's Implementation for MP3 to WAV conversionSimilar way we do for MP3 to WAV conversion. What's different is only filters contruction.
Application's ImplementationOk, now we discuss main application's implementation. Create new application and drag drop controls on form to make it looks like figure below:
Fig.5 Main form design. Following code is complete code of main application.
I will only explain about WM_MMNotify method. This method will be called when DirectShow needs to notify application. We must check for conversion process completion to get rid of all filters we constructed, because every time Convert is called, BuildFilterGraph will be called and adds filters to filter graph. If we don't remove filters, filters will be duplicated in filter graph. Download application source code here. SummaryWe have discussed how to do WAV to MP3 conversion and vice versa. We also discussed how to construct flter graph manually by connecting filters one by one. |
| Last Updated on Saturday, 16 January 2010 18:20 |