注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

gmd20的个人空间

// 编程和生活

 
 
 

日志

 
 

(转) directshow实现wav和mp3格式转换  

2008-09-12 17:25:12|  分类: 程序设计 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

完整原文地址http://juhara.com/article-17-DirectShow-Tips-Developing-WAV-MP3-Converter.html

DirectShow Tips : Developing WAV - MP3 Converter

Zamrony P Juhara
06 November 2006 14:57:00
(4747 views)
Article on how to convert WAV to MP3 or vice versa using DirectShow and Delphi.

Introduction.

In 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.

Prerequisite.

You 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 needed.

We are going to need access to these following softwares:

  • Delphi, our compiler.
  • DirectX 8 or up. Download here.
  • DirectX header conversion. You can download here (DirectX 9 header) or here (DirectX 8.1 header).
  • DirectX SDK (optional). Download here. DirectX SDK includes GraphEdit utility which is useful for constructing and testing filter graph.
  • MP3 codec. Usually, it has been installed along with your operating system installation. To make sure MP3 codec is already installed, clickControl Panel -> Sounds and Audio Devices -> Choose Hardware tab -> Choose Audio Codecs -> Click Properties button. On Audio Codecs Properties window, choose Properties tab. Find for item Franhoufer IIS MPEG Layer-3 in Audio Compression Codecs list. If it is not there, you can download it here.

Short info about filters.

DirectShow 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:

Filter graph WAV playback

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:

  • File Source (Async).
  • Wave Parser.
  • MPEG Layer-3 Decoder.
  • MPEG Layer-3
  • WAV Dest
  • File Writer.

Filter graph WAV ke MP3

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 convinience, 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:

  • File Source (Async)
  • MPEG-I Stream Splitter.
  • MPEG Layer-3 Decoder.
  • WAV Dest.
  • File Writer.

Filter graph MP3 ke WAV

Fig.3 Filter graph for MP3 toWAV conversion.

Get filter instance.

To get filter instance, we can use CoCreateInstance()

de<  CoCreateInstance(clsid,                   nil,                   CLSCTX_INPROC_SERVER,                   IID_IBaseFilter,                   afilter);de<

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.

de<function FindFilterByFriendlyName(const classid:TGUID;  const friendlyname: string): IBaseFilter;var sysEnum:ICreateDevEnum;    enumMoniker:IEnumMoniker;    moniker:IMoniker;    propBag:IPropertyBag;    hr:HRESULT;    afriendlyName:OleVariant;    propName,aname:widestring;begin  result:=nil;  CoCreateInstance(CLSID_SystemDeviceEnum,                   nil,                   CLSCTX_INPROC_SERVER,                   IID_ICreateDevEnum,                   sysenum);  if sysEnum<>nil then  begin    aname:=FriendlyName;    propName:='FriendlyName';    hr:=sysEnum.CreateClassEnumerator(classid,                       enumMoniker,0);    if hr=S_OK then    begin      while enumMoniker.Next(1,moniker,nil)=S_OK do      begin        hr:=moniker.BindToStorage(nil,nil,IPropertyBag,propBag);        if succeeded(hr) then        begin          propBag.Read(PWideChar(propName),                       afriendlyName,nil);          if afriendlyName=aname then          begin            moniker.BindToObject(nil,nil,                                 IID_IBaseFilter,                                 result);            exit;          end;        end;      end;    end else      RaiseDirectshowException(hr,'Create Class Enumerator failed');  end;end;de<

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.

de<  FFilterGraph.AddFilter(afilter,PWideChar(FilterName));de<

afilter is instance of IBaseFilter that will be added. Second parameter is name of filter, whic is type of PWidechar.

Getting pin of a filter

We 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.

de<function GetPin(aFilter: IBaseFilter;  const PinDir: TPin_Direction; const indx: integer): IPin;var pPins:IEnumPins;    ctr:integer;    aPin:IPin;    CurPinDir:TPin_Direction;begin  result:=nil;  //start pin enumeration  aFilter.EnumPins(pPins);  if pPins<>nil then  begin    //pin enumeration ok    ctr:=0;    while pPins.Next(1,aPin,nil)=S_OK do    begin      aPin.QueryDirection(curPinDir);      if (ctr=indx) and (curPinDir=PinDir) then      begin        result:=aPin;        exit;      end;    end;  end;end;de<

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 filters

To 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.

de<function ConnectFilter(OutFilter,  InFilter: IBaseFilter): HResult;var outpin,inPin:IPin;begin  outPin:=GetPin(OutFilter,PINDIR_OUTPUT,0);  inPin:=GetPin(InFilter,PINDIR_INPUT,0);  result:=FFilterGraph.Connect(outPin,inPin);end;de<

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.

UML diagram untuk konversi WAV - MP3

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.

de<     TBasicConverter=class(TBasicPlayer)     private       FSrcFilename: string;       FDstFilename: string;       procedure SetDstFilename(const Value: string);       procedure SetSrcFilename(const Value: string);     protected     public       procedure Convert;     published       property SrcFilename:string read FSrcFilename write SetSrcFilename;       property DstFilename:string read FDstFilename write SetDstFilename;     end;     TWAVToMP3Converter=class(TBasicConverter)     public       procedure BuildFilterGraph;override;     end;     TMP3ToWAVConverter=class(TBasicConverter)     public       procedure BuildFilterGraph;override;     end;de<

TBasicPlayer looks like following code:

de<     TBasicPlayer=class(TObject)     private       ...     protected       ...       function GetPin(aFilter:IBaseFilter;                       const PinDir:TPin_Direction;                       const indx:integer):IPin;       function ConnectFilter(OutFilter,InFilter:IBaseFilter):HResult;       function AddFilterByCLSID(clsid:TGUID;const name:string):IBaseFilter;       function FindFilterByFriendlyName(const friendlyname:string):IBaseFilter;       ...     end;de<

WAV - MP3 Converter Application Implementation

TBasicConverter Implementation

de<{ TBasicConverter }procedure TBasicConverter.Convert;begin  BuildFilterGraph;  Run;end;procedure TBasicConverter.SetDstFilename(const Value: string);begin  FDstFilename := Value;end;procedure TBasicConverter.SetSrcFilename(const Value: string);begin  FSrcFilename := Value;end;de<

Convert() is only wrapper for two method call i.e BuildFilterGraph and Run.

BuildFilterGraph's Implementation for WAV to MP3 conversion.

I 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().

de<{ TWAVToMP3Converter }procedure TWAVToMP3Converter.BuildFilterGraph;var pFileSink:IFileSinkFilter;    pFileSource:IFileSourceFilter;    afileReader,awaveParser,aWaveDest,    aMPEGLayer3,    aFileWriter:IBaseFilter;    aSrcFilename,aDestFilename:widestring;    hr:HResult;begin  if (FFilterGraph<>nil) then  begin    //add file reader filter    afileReader:=AddFilterByCLSID(CLSID_AsyncReader,'File Reader');    //add Wave parser filter    aWaveParser:=AddFilterByCLSID(CLSID_WaveParser,'Wave Parser');    //Add MPEG Layer 3 Encoder    aMPEGLayer3:=FindFilterByFriendlyName(CLSID_AudioCompressorCategory,                                         'MPEG Layer-3');    FFilterGraph.AddFilter(aMPEGLayer3,'MPEG Layer-3');    //add Wave Dest filter    awaveDest:=AddFilterByCLSID(CLSID_WaveDest,'Wave Dest');    //add file writer filter    afileWriter:=AddFilterByCLSID(CLSID_FileWriter,'File Writer');    if (afileReader<>nil) and       (awaveParser<>nil) and       (aMPEGLayer3<>nil) and       (aWaveDest<>nil) and       (aFileWriter<>nil) then    begin      //ambil instance IFileSourceFilter      afileReader.QueryInterface(IID_IFileSourceFilter,pFileSource);      if pFileSource<>nil then      begin        aSrcFilename:=FSrcFilename;        pFileSource.Load(PWideChar(aSrcFilename),nil);      end;      //connect output pin file reader ke      //input pin Wave Parser      hr:=ConnectFilter(aFileReader,aWaveParser);      if hr<>S_OK then        RaiseDirectShowException(hr,'Koneksi File Reader ke MPEG-1 Splitter gagal. ');      //connect output Wave Parser ke      //input pin MPEG Layer 3      hr:=ConnectFilter(aWaveParser,aMPEGLayer3);      if hr<>S_OK then        RaiseDirectShowException(hr,'Koneksi Wave Parser ke MPEG Layer 3. ');      //connect output MPEG Layer 3 ke      //input pin Wave Dest      hr:=ConnectFilter(aMPEGLayer3,aWaveDest);      if hr<>S_OK then        RaiseDirectShowException(hr,'Koneksi MPEG Layer 3 ke Wave Dest gagal. ');      //ambil instance IFileSinkFilter      afileWriter.QueryInterface(IID_IFileSinkFilter,pFileSink);      if pFileSink<>nil then      begin        aDestFilename:=FDstFilename;        pFileSink.SetFileName(PWideChar(aDestFilename),nil);      end;      //connect output wave Dest ke      //input pin FileWriter      hr:=ConnectFilter(aWaveDest,aFileWriter);      if hr<>S_OK then        RaiseDirectShowException(hr,'Koneksi Wave Dest ke File Writer gagal. ');    end;  end;end;de<

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.

下面还有很多内容,参考原文

ownload application source code here.

Summary

We 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.

  评论这张
 
阅读(1167)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017