声卡混音器的编程,我也找了好久,一直没找到写得比较好的。
自己的理解能力也不够,英语就太差了。所以一直没弄太清楚。
自从在网上发现这篇后,有茅塞顿开的感觉,写得真不错。
这篇原文,发表在sina的blog上,但是源blog已经被删除了(奇怪)
我都是用baidu的快照看到的。
混音器的api编程资料
2006-08-16 00:45:45
大 中 小
有关混音器编程在百度上几乎找不到几个网页包含比较清楚的资料
查看msdn上面也是仅有的一点介绍,搞了一整天,找了些零碎的代码,看起来巨复杂
还好没有放弃,最好在google上找了一些资料
看过以后才明白了一些,其实结构也不算复杂,只是自己的理解力和资料都太少
下面是自己整理的,可能有不准确的地方
//----------------------------------------------------------------
基本概念:audio line
每个混音设备(mixer device)包含一系列的音频线路(audio lines),
一条音频线路可能是目的线路(destination line)或者源线路(source line),
一条目的线路可能和一或多条源线路绑定(attach)在一起,
每一条(目的或源)音频线路和一些控制(control)联系起来,
这些控制承载着所联系的线路的设置,
并且能够使用户通过与其交互从而达到设置音频系统参数的目的
注意以上所说的control不是windows的control,只是线路信息的一种载体,
这种control没有图形界面
理解destination:places that audio goes
理解source:The audio that goes to the destinations must come from somewhere
举个例子,一个喇叭线路代表一个目的线路,于是这条线路拥有相应的控制(喇叭本身的参数)与它联系在一起,比如音量控制,静音,低音,颤抖,等,同时一个喇叭(目的)线路会有一或多条源线路(输入喇叭的信号)同它绑定(attach),比如辅助,线路输入,合成器,CD音频,等等,这些源线路相应绑定一些控制来设置获取它们的行为。
//-------------------------------------------------------------------------------------------------
下面通过windows自带的混音器程序来理解音频线路的概念
双击windows任务栏位于桌面右下角的图标,或在系统目录找到sndvol32.exe,即可打开windows自带的混音器查看设置程序,点击菜单上的选项-属性,打开属性窗口
在属性窗口中我们可以选择在主窗口中显示并访问的为播放或者录音,在这里可以把这两个选项作为两条目的线路,它们相应的控制也可以下面的选择框中的多选框中选择是否在主设置窗口中显示
显然在这个属性窗口中我们无法设置线路的任何数据,但从这里可以观察到audio line的一种直观的表示
其它相关的参考:--------------------------------------------------------
一个混音设备总是有一个或多个目的线路,一般来说会有两个,分别用来输入和输出,
如果存在录象设备(Video capturing devices)会有例外,因为这些设备为了保证最佳的影像效果,通常会只暴露单一的音频目的线路(audio destination)来让用户控制录音的音量,这使得用户需要安装另一个混音设备来提供目的线路支持播放(回放)
混音器是系统共享的资源,就像剪切板一样,在一个应用程序中修改设置会影响到其它使用音频设备资源的程序
一些声卡制造商提供了相应的声卡控制程序来取代系统混音控制程序,这些程序可能会功能相对更加健全,更能发挥声卡的潜力,但是一般会更加复杂难以理解和使用
开始编程:--------------------------------------------------------------
数据结构:
MIXERLINE包含了由一条audio line的信息,如上所述,一条audio line 可能是destinaton,或者是source
因此MIXERLINE数据结构要照顾到两种线路,因此一个MIXERLINE结构所保存的信息要么是一条源线路,要么是一条目的线路,这一点从各个数据成员的名字也可以大致地看出来
typedef struct {
DWORD cbStruct;
DWORD dwDestination; //目的线路索引
DWORD dwSource;
//源线路索引
DWORD dwLineID;
//这条线路的ID
DWORD fdwLine;
DWORD dwUser;
DWORD dwComponentType; //组件类型,两种线路又分为若干个类型
DWORD cChannels;
//声道数
DWORD cConnections; //源线路所连接的目的线路数目
DWORD cControls; //线路所绑定的相应控制数目
CHAR szShortName[MIXER_SHORT_NAME_CHARS];
CHAR szName[MIXER_LONG_NAME_CHARS];
struct {
DWORD dwType;
DWORD dwDeviceID;
WORD wMid;
WORD wPid;
MMVERSION vDriverVersion;
CHAR szPname[MAXPNAMELEN];
} Target;
} MIXERLINE;
-------------------一般步骤
打开mixer设备(获得mixer句柄)
mixerOpen
获取音频线路的设备ID(获得ID)
mixerGetID
取得mixer设备指定音频线路(line)的信息
mixerGetLineInfo
得到一个或多个与某个音源线路(line)相关的控制(control)
mixerGetLineControls
取得指定控制的属性
mixerGetControlDetails
设置指定控制的属性
mixerSetControlDetails
关闭mixer设备
mixerClose
--------------------------------------------
最后在codeguru上找到了一个简单的mixer封装类,很实用的一段代码,用这个就可以控制系统声音了
在这里转载一下
// AlexfMixer.h: interface for the CAlexfMixer class.
// CAlexfMixer - simple mixer control wrapper
// Copyright (C) Alexander Fedorov 1999
#include<windows.h>
#include <mmsystem.h>
// Thanks to Langis Pitre
#define NO_SOURCE ((MIXERLINE_COMPONENTTYPE_SRC_LAST + 1))
class CAlexfMixer
{
protected:
HMIXER m_HMixer;
INT m_iMixerControlID;
MMRESULT mmr;
DWORD m_dwChannels;
BOOL m_bSuccess;
void ZeroAll();
public:
DWORD m_dwMixerCtlBound;
BOOL IsOk() {return m_bSuccess;};
BOOL On();
BOOL Off();
DWORD GetControlValue();
BOOL SetControlValue(DWORD dw);
CAlexfMixer(DWORD DstType, DWORD SrcType, DWORD ControlType,HWND hwnd=0);
//指定窗口句柄会使设备在系统控制改变时发送给窗口MM_MIXM_CONTROL_CHANGE
virtual ~CAlexfMixer();
};
//--------------------------------------------------------------------
//------AlexfMixer.cpp
#include"AlexfMixer.h"
void CAlexfMixer::ZeroAll()
{
m_HMixer = NULL;
m_iMixerControlID = 0;
mmr = MMSYSERR_NOERROR;
m_dwChannels = 0;
m_bSuccess = FALSE;
m_dwMixerCtlBound=0;
}
CAlexfMixer::CAlexfMixer( DWORD DstType, DWORD SrcType, DWORD ControlType,HWND hwnd)
{
ZeroAll();
if(mixerGetNumDevs()<1) return;
if(hwnd==0)
mmr = mixerOpen(&m_HMixer, 0, 0, 0L, MIXER_OBJECTF_MIXER);
else
mmr = mixerOpen(&m_HMixer, 0, (DWORD)hwnd, 0L, CALLBACK_WINDOW);
if (mmr != MMSYSERR_NOERROR) return;
// get dwLineID
MIXERLINE mxl;
mxl.cbStruct = sizeof(MIXERLINE);
// DstType
mxl.dwComponentType = DstType;
if (mixerGetLineInfo((HMIXEROBJ)m_HMixer,&mxl,MIXER_GETLINEINFOF_COMPONENTTYPE)!= MMSYSERR_NOERROR)
return;
// SrcType
if( SrcType != NO_SOURCE )
{
UINT nconn = mxl.cConnections;
DWORD DstIndex = mxl.dwDestination;
for( UINT j = 0; j < nconn; j++ )
{
mxl.cbStruct = sizeof( MIXERLINE );
mxl.dwSource = j;
mxl.dwDestination = DstIndex;
if(mixerGetLineInfo( ( HMIXEROBJ )m_HMixer,
&mxl, MIXER_GETLINEINFOF_SOURCE ) != MMSYSERR_NOERROR) return;
//检测出相应的线路即退出循环,从而在mxl中保留线路信息
if( mxl.dwComponentType == SrcType ) break;
}
}
// get dwControlID
MIXERCONTROL mxc;
MIXERLINECONTROLS mxlc;
mxlc.cbStruct = sizeof(MIXERLINECONTROLS);
mxlc.dwLineID = mxl.dwLineID;
mxlc.dwControlType = ControlType;
mxlc.cControls = 1;
mxlc.cbmxctrl = sizeof(MIXERCONTROL);
mxlc.pamxctrl = &mxc;
if (mixerGetLineControls((HMIXEROBJ)m_HMixer, &mxlc,MIXER_GETLINECONTROLSF_ONEBYTYPE)!=MMSYSERR_NOERROR)
return;
m_iMixerControlID = mxc.dwControlID;
m_dwMixerCtlBound=mxc.Bounds.dwMaximum-mxc.Bounds.dwMinimum;
m_dwChannels = mxl.cChannels;
if (m_dwChannels > 0) m_dwChannels--;
m_bSuccess = TRUE;
}
CAlexfMixer::~CAlexfMixer()
{
if (m_HMixer) mixerClose(m_HMixer);
}
DWORD CAlexfMixer::GetControlValue()
{
if (!m_bSuccess) return 0;
m_bSuccess = FALSE;
MIXERCONTROLDETAILS mxcd;
MIXERCONTROLDETAILS_UNSIGNED mxcd_u;
mxcd.cbStruct = sizeof(mxcd);
//根据controlID来获取control的详细信息
mxcd.dwControlID = m_iMixerControlID;
mxcd.cChannels = m_dwChannels;
mxcd.cMultipleItems = 0;
mxcd.cbDetails = sizeof(mxcd_u);
mxcd.paDetails = &mxcd_u;
mmr = mixerGetControlDetails((HMIXEROBJ)m_HMixer, &mxcd, 0L);
if (MMSYSERR_NOERROR != mmr) return 0;
m_bSuccess = TRUE;
return mxcd_u.dwValue;
}
BOOL CAlexfMixer::SetControlValue(DWORD dw)
{
if (!m_bSuccess) return m_bSuccess;
m_bSuccess = FALSE;
MIXERCONTROLDETAILS mxcd;
MIXERCONTROLDETAILS_UNSIGNED mxcd_u;
mxcd.cbStruct = sizeof(mxcd);
mxcd.dwControlID = m_iMixerControlID;
mxcd.cChannels = m_dwChannels;
mxcd.cMultipleItems = 0;
mxcd.cbDetails = sizeof(mxcd_u);
mxcd.paDetails = &mxcd_u;
mmr = mixerGetControlDetails((HMIXEROBJ)m_HMixer, &mxcd, 0L);
if (MMSYSERR_NOERROR != mmr) return m_bSuccess;
mxcd_u.dwValue = dw;
mmr = mixerSetControlDetails((HMIXEROBJ)m_HMixer, &mxcd, 0L);
if (MMSYSERR_NOERROR != mmr) return m_bSuccess;
m_bSuccess = TRUE;
return m_bSuccess;
}
BOOL CAlexfMixer::On()
{
return SetControlValue(0);
}
BOOL CAlexfMixer::Off()
{
return SetControlValue(1);
}
//---------------------------------------------------------------------------------
//如何使用
//main.cpp--------------
// cl /c /GX main.cpp AlexfMxier.cpp
//link main.obj AlexfMixer.obj winmm.lib
#include<iostream>
#include"alexfmixer.h"
using namespace std;
void main(){
//初始化为设置声音
CAlexfMixer mixer(MIXERLINE_COMPONENTTYPE_DST_SPEAKERS,NO_SOURCE,
MIXERCONTROL_CONTROLTYPE_VOLUME);
/**********初始化为设置波形
CAlexfMixer mixer(MIXERLINE_COMPONENTTYPE_DST_SPEAKERS,
MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT,//通过第一个参数得到源线路
MIXERCONTROL_CONTROLTYPE_VOLUME);
//*/
/****************初始化为设置静音
CAlexfMixer mixer(MIXERLINE_COMPONENTTYPE_DST_SPEAKERS,
NO_SOURCE, MIXERCONTROL_CONTROLTYPE_MUTE );
mixer.On();//用mixer.Off()关闭
//*/
//检查是否出错
if (!mixer.IsOk()){
MessageBox(NULL,TEXT("打开设备过程出错!"),NULL,MB_OK);
return;
}
cout<<"系统音量范围"<<mixer.m_dwMixerCtlBound<<endl;
cout<<"当前系统音量"<<mixer.GetControlValue()<<endl;
cout<<"将系统音量设置为40000"<<endl;
mixer.SetControlValue(40000);
}
这个类是在codeguru上找到的,在这里说一下自己的重要体会:
学做程序很重要的一点,就是学会寻找自己需要的资源拿来学习并加以复用,很高兴认识的codeguru和codeproject,上面真的有很多现成的好的例子,以前自己干了太多浪费时间的事情,很多时候都需要学会更好的交流方法,不能自己死脑筋recreate the wheel(中国有一句叫闭门造车一个意思)
//END