使用DirectShow技术切换双声道音频声道的方法

  我们在编写多媒体播放器程序时,经常会遇到不知怎么让双声道切换到左声道或右声道音频的问题,而使用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有一点了解)

//

//

//  FileName    :   MyFilter.cpp

//  Author      :   damo

//  Date        :   2004.06.01

//  Comment     :   

//

//

#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 };    // clsMinorType

 

const 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;

} // CreateInstance

 

 

HRESULT 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_



//****************************************以下为IZQAutio.h文件的源代码**************************************************

//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_



//*************************以下为ZQAudio.def文件的内容****************************

LIBRARY         ZQAudio.Ax
EXPORTS
               DllGetClassObject PRIVATE
               DllCanUnloadNow PRIVATE
               DllRegisterServer PRIVATE
               DllUnregisterServer PRIVATE



将以上文件加入到VC工程里,并安装DirectX8.1 SDK,设置好相关的文件搜索路径(具体可参考<DirectShow开发指南>)编译后将得到一个Filter,现在你可在程序中用它来切换声道了,如果你想在VB中使用它,须写一个DLL将各种播放操作封装,然后供VB进行调用.
另外,用它对某些VCD格式的歌曲会无效,原因在于Mpeg Audio Decoder解码器的设置问题,你可在程序中包含MpegType.h文件来切换声道.
以上均是我摸索实验的结果,如有错误还请指出!

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要实现音频双声道变单声道,我们需要读取音频文件,将左右两个声道音频数据合并为一个声道,并输出单声道音频文件。以下是实现音频双声道变单声道的Java代码示例: ``` import javax.sound.sampled.AudioFileFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import java.io.File; import java.io.IOException; public class StereoToMono { public static void main(String[] args) throws IOException { // 读取音频文件 File audioFile = new File("audio_stereo.wav"); AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(audioFile); // 获取音频格式 AudioFileFormat fileFormat = AudioSystem.getAudioFileFormat(audioFile); // 双声道转单声道 if (fileFormat.getChannels() == 2) { byte[] buffer = new byte[1024]; int bytesRead = 0; int totalBytesRead = 0; while ((bytesRead = audioInputStream.read(buffer, 0, buffer.length)) != -1) { totalBytesRead += bytesRead; for (int i = 0; i < bytesRead; i += fileFormat.getFrameSize() * 2) { buffer[i / 2] = buffer[i]; buffer[i / 2 + 1] = buffer[i + 1]; } if (totalBytesRead == bytesRead) { audioInputStream = new AudioInputStream(audioInputStream, fileFormat, totalBytesRead / fileFormat.getFrameSize() / 2); } else { audioInputStream = new AudioInputStream(audioInputStream, fileFormat, (totalBytesRead - bytesRead) / fileFormat.getFrameSize() / 2); } } } // 输出单声道音频文件 File monoFile = new File("audio_mono.wav"); AudioSystem.write(audioInputStream, fileFormat.getType(), monoFile); } } ``` 这个代码示例通过AudioSystem类和AudioInputStream类读取音频文件,将左右两个声道音频数据合并为一个声道,然后输出单声道音频文件。其中,我们通过操作字节数组实现双声道转单声道的功能,具体实现可参考代码注释。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值