我们在编写多媒体播放器程序时,经常会遇到不知怎么让双声道切换到左声道或右声道音频的问题,而使用MCI接口或媒体播放器控件往往只能使用调节声道左右均衡的方法达到切换声道的目的,但这样只会有一只喇叭发出声音,且某些VCD格式的歌曲甚至不能用这种方法切换声道.
但我们在使用媒体播放器播放VCD格式的文件时,如果你仔细观察,会发现可以在播放时通过 属性->高级->选中Mpeg Audio Decoder过滤器->属性->频道 的方法分别切换到左右声道或立体声状态,媒体播放器是怎样实现这个功能但我们为什么又不能直接在VB中实现它呢?,其实这都是因为媒体播放器是基于DirectShow技术的原因.
同理,我们也可以直接调用DirectShow达到播放多媒体文件的目的,但要实现切换声道还得首先弄懂音频数据的格式,下面以16位音频数据为例(8位音频只是少了一个字节的数据)简单说一下解压后的音频数据在即将播放时的声道分布格式.
当音频数据通过各种解码器解码后,在将要播放时都会先送到声卡数据缓冲区里,其数据为Wave格式,例如:aa bb cc dd,其中aa-左声道,bb-右声道,cc-左声道,dd-右声道,很简单的是吗:)
如果要切换到左声道就要用左声道的数据填充到右声道里,即使上面的音频数据变为:aa aa cc cc这种形式,知道它的原理,下一步的工作就是利用DirectShow技术写一个Filter插入到声卡reanderer的前面,这样只要是双声道格式的音频数据,我们都可以对它进行声道切换了.下面列出我写的一个用于16音频声道切换的Filter源代码(其实很简单的,但你得对COM有一点了解)
//ZQAudio.cpp
//用于切换声道的一个directshow fileter
// Copyright (c) 2004, 程序设计:张强. All rights reserved.
//
hndamofy@163.com
// 如果您要传播此源码,请保留该部分
#include <streams.h> // DirectShow (includes windows.h)
#include <initguid.h> // declares DEFINE_GUID to declare an EXTERN_C const.
#include "IZQAudio.h" // 自定义接口
// {CFBE95E1-5DB4-11d8-929C-92B98A07327D}
DEFINE_GUID(CLSID_ZQAudio,
0xcfbe95e1, 0x5db4, 0x11d8, 0x92, 0x9c, 0x92, 0xb9, 0x8a, 0x7, 0x32, 0x7d);const AMOVIESETUP_MEDIATYPE sudPinTypes =
{ &MEDIATYPE_Audio // clsMajorType
, &MEDIASUBTYPE_PCM }; // clsMinorTypeconst AMOVIESETUP_PIN psudPins[] =
{ { L"Input" // strName
, FALSE // bRendered
, FALSE // bOutput
, FALSE // bZero
, FALSE // bMany
, &CLSID_NULL // clsConnectsToFilter
, L"" // strConnectsToPin
, 1 // nTypes
, &sudPinTypes // lpTypes
}
, { L"Output" // strName
, FALSE // bRendered
, TRUE // bOutput
, FALSE // bZero
, FALSE // bMany
, &CLSID_NULL // clsConnectsToFilter
, L"" // strConnectsToPin
, 1 // nTypes
, &sudPinTypes // lpTypes
}
};const AMOVIESETUP_FILTER sudNullNull =
{ &CLSID_ZQAudio // clsID
, L"zhang qiang Filter" // strName
, MERIT_DO_NOT_USE // dwMerit
, 2 // nPins
, psudPins }; // lpPin// CZQAudio
//
class CZQAudio:
public CTransInPlaceFilter,
public IZQAudioInterface
{public:
//COM 函数声明
static CUnknown *WINAPI CreateInstance(LPUNKNOWN punk, HRESULT *phr);
STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);
DECLARE_IUNKNOWN; STDMETHODIMP put_AudioMode(int inAudio_Channel_Mode);
STDMETHODIMP get_AudioMode(int *outAudio_Channel_Mode);
private: int Audio_Channel_Mode; //声道模式设置
//int s;
CCritSec m_Mylock; //锁定对象 //构造函数,同时调用基类的构造函数
CZQAudio(TCHAR *tszName, LPUNKNOWN punk, HRESULT *phr)
: CTransInPlaceFilter (tszName, punk, CLSID_ZQAudio, phr)
{ Audio_Channel_Mode=0; } //主要执行函数
HRESULT Transform(IMediaSample *pSample);
//检查输入类型
HRESULT CheckInputType(const CMediaType *mtIn);
//检查输入与输出类型是否一致
//HRESULT CheckTransform(const CMediaType *mtIn,const CMediaType *mtOut){ return S_OK; };
//取得类型
HRESULT GetMediaType(int iPosition, CMediaType *pMediaType); };
// Needed for the CreateInstance mechanism
CFactoryTemplate g_Templates[]=
{ { L"zhang qiang Filter"
, &CLSID_ZQAudio
, CZQAudio::CreateInstance
, NULL
, &sudNullNull }
};
int g_cTemplates = sizeof(g_Templates)/sizeof(g_Templates[0]);//
// CreateInstance
//
// 创建一个对象实例
CUnknown * WINAPI CZQAudio::CreateInstance(LPUNKNOWN punk, HRESULT *phr) { CZQAudio *pNewObject = new CZQAudio(NAME("Zhang qiang audio filter"), punk, phr );
if (pNewObject == NULL) {
*phr = E_OUTOFMEMORY;
} return pNewObject;
} // CreateInstanceHRESULT CZQAudio::Transform(IMediaSample *pSample)
{
BYTE *pOutData;
int i=0,w_pos=0,r_pos=0,SampleSize=0,n=0;
//进行声道选择
switch(Audio_Channel_Mode)
{
case 1:r_pos=0;
w_pos=2;
break; //左声道 case 2:r_pos=2;
w_pos=0;
break; //右声道 default:return NOERROR; //不处理(双声道模式)
} pSample->GetPointer(&pOutData); //取得缓冲区指针
SampleSize=pSample->GetActualDataLength(); //取得有效数据大小
n=SampleSize/4; //循环次数 //执行声道切换操作
for(i=0;i<n;i++){
memcpy(pOutData+w_pos,pOutData+r_pos,2);
w_pos+=4;
r_pos+=4;
}
return NOERROR;
}HRESULT CZQAudio::GetMediaType(int iPosition,CMediaType *pMediaType)
{
return NOERROR;
}HRESULT CZQAudio::CheckInputType(const CMediaType *mtIn)
{
//检查是否在停止状态,且是否是音频数据类型
if(IsStopped() && *mtIn->Type()==MEDIATYPE_Audio)
{
//检查是否是PCM格式的数据
if(*mtIn->Subtype()==MEDIASUBTYPE_PCM ||
*mtIn->Subtype()==MEDIASUBTYPE_WAVE)
{
return S_OK; //成功
}
}
return E_INVALIDARG; //无效类型
}//设置声道
STDMETHODIMP CZQAudio::put_AudioMode(int inAudio_Channel_Mode)
{
//判断参数是否合法
if(inAudio_Channel_Mode>=0 && inAudio_Channel_Mode<=2){
Audio_Channel_Mode=inAudio_Channel_Mode;
return S_OK;
}
return E_INVALIDARG; //参数无效
}//取得声道模式设置
STDMETHODIMP CZQAudio::get_AudioMode(int *outAudio_Channel_Mode)
{
//取得声道模式设置
*outAudio_Channel_Mode=Audio_Channel_Mode;
return S_OK;
}//暴露接口
STDMETHODIMP CZQAudio::NonDelegatingQueryInterface(REFIID riid,void **ppv)
{
CheckPointer(ppv,E_POINTER);
if(riid==IID_IZQAudioInterface){
return GetInterface((IZQAudioInterface *) this,ppv);
}else{
return CTransInPlaceFilter::NonDelegatingQueryInterface(riid,ppv);
}
}
/******************************全局函数******************************/
* 注册及反注册函数
/**************************************************************************/
STDAPI DllRegisterServer()
{
return AMovieDllRegisterServer2( TRUE );
}STDAPI DllUnregisterServer()
{
return AMovieDllRegisterServer2( FALSE );
}
//屏蔽 4514 警告
#pragma warning( disable:4514)
//IZQAudio.h
//声道切换的自定义接口#ifndef _H_IZQAudioInterface_
#define _H_IZQAudioInterface_#ifdef _cplusplus
extern "C" {
#endif//IZQAudioChannel's GUID
// {83BA1141-6135-11d8-929C-A23780A5EB7C}
DEFINE_GUID(IID_IZQAudioInterface,
0x83ba1141, 0x6135, 0x11d8, 0x92, 0x9c, 0xa2, 0x37, 0x80, 0xa5, 0xeb, 0x7c);// 双声道模式参数声明
#define AM_AUDIO_DUAL_MERGE 0 //双声道
#define AM_AUDIO_DUAL_LEFT 1 //左声道
#define AM_AUDIO_DUAL_RIGHT 2 //右声道//IZQAudioInterface 接口定义
DECLARE_INTERFACE_(IZQAudioInterface,IUnknown)
{
//设置声道:0-不处理声道,1-左声道,2-右声道
STDMETHOD (put_AudioMode) (THIS_
int inAudio_Channel_Mode
) PURE;
//返回声道的设置
STDMETHOD (get_AudioMode) (THIS_
int *outAudio_Channel_Mode
) PURE;
};#ifdef _cplusplus
}
#endif#endif //_H_IZQAudio_
将以上文件加入到VC工程里,并安装DirectX8.1 SDK,设置好相关的文件搜索路径(具体可参考<DirectShow开发指南>)编译后将得到一个Filter,现在你可在程序中用它来切换声道了,如果你想在VB中使用它,须写一个DLL将各种播放操作封装,然后供VB进行调用.
另外,用它对某些VCD格式的歌曲会无效,原因在于Mpeg Audio Decoder解码器的设置问题,你可在程序中包含MpegType.h文件来切换声道.
以上均是我摸索实验的结果,如有错误还请指出!