用Direct Sound为MP3解码器libmad播放输出 收藏
WIN32平台下madplay默认采用WaveOut播放输出,本文实现为其增加Direct Sound输出。在Windows XP,VC++ 6.0测试通过。 (一)优秀的MP3解码器libmad简介
libmad是跨平台的基于命令行的MP3播放解码器,使用定点解码,可用于没有浮点运算的嵌入式系统。
(二)下载及测试
1、从官方网站ftp://ftp.mars.org/pub/mpeg/下载madplay-0.15.2b.tar.gz,libmad-0.15.1b.tar.gz,libid3tag-0.15.1b.tar.gz,还要从ftp://ftp.mars.org/pub/mpeg/extra/下载libz-1.1.4.tar.gz,共四个文件。将四个压缩包保存在同一目录,例如d:\madplay。
2、用WINRAR“解码压到当前目录”方法将四个压缩包解压,在d:\madplay下将有四个文件夹:madplay-0.15.2b,libmad-0.15.1b,libid3tag-0.15.1b,libz-1.1.4。
3、用VC++ 6.0打开D:\madplay\madplay-0.15.2b\msvc++下的madplay工程。修改VC++ 6.0的编译环境:Tools -> Options -> Directories标签 -> Directories框下增加如下两行:
D:\MADPLAY\LIBMAD-0.15.1B\MSVC++
D:\MADPLAY\LIBID3TAG-0.15.1B
点击OK保存设置退出。再为编译连接后的可执行文件指定运行参数:Project -> settings -> 点中左侧madplay -> 右侧Program arguments框内填入你硬盘上保存的一个MP3文件,例如:D:\MP3\test.mp3,单击OK保存设置退出。
按Ctrl+F5编译连接运行,就可以听到播放你选择的MP3歌曲了。
(三)为madplay增加Direct Sound输出
madplay采用WaveOut作为播放输出,用DS输出的优点不再我说了吧。
1、在audio.h中查找到“audio_ctlfunc_t audio_win32;”这一行,在下面增加一行:
audio_ctlfunc_t audio_dsound;
2、在config.h中找到“#define AUDIO_DEFAULT audio_win32”这一行,将这一行注释掉,然后在下面增加一行:
#define AUDIO_DEFAULT audio_dsound
3、在工作区中选择FileView标签,点击madplay files下的Source Files,添加文件aduio_dsound.c
我写的aduio_dsound.c的内容如下,添加了比较详细的注释。
/*
aduio_dsound.c 功能: 用 Direct Sound 为MP3解码器libmad播放输出.兼容 VC++ 6.0 的directx 7.0 接口函数: audio_dsound() email: ly2697@sina.com (请注明主题 mp3 decoder) 2008.08 */ /*
libmad解码器头文件 */ #include "audio.h" /*
Direct Sound 的头文件和库 */ #include<dsound.h> #pragma comment( lib, "dsound.lib" ) /*
高精定时器的头文件和库 */ #include<mmsystem.h> #pragma comment( lib, "winmm.lib") /*
DS_N: 缓冲区块数,设为8块. DS_ONEBUF: 一块的长度.设解码16帧的PCM数据长度,4608*16=73728(72KB). DS_BUFSIZE: Direct sound缓冲区长度,DS_N*DS_ONEBUF=589824(576KB). 最小4,最大0xfffffff,在dsound.h中由DSBSIZE_MIN和DSBSIZE_MAX定义. 每一帧的时长26ms,一块的时间长度为26*16=416ms;缓冲区总时间长度8*416=3328ms. */ #define DS_N 8 #define DS_ONEBUF 73728 #define DS_BUFSIZE 589824 /*
pcm_data[] -- 暂存写入到时DSound Buffer的数据,该数据是由audio_pcm()解码MP3帧得 到的PCM数据,不同的解码器(如mpg123,libmad等)解码一帧的函数都采用向外部提供的接收 缓冲区写入解码得到的PCM数据,将本模块略作改写可用于不同的MP3解码器播放输出. */ static unsigned char pcm_data[DS_ONEBUF+4608]; static int pcm_length=0; static audio_pcmfunc_t *audio_pcm; static LPDIRECTSOUND ds_lpDS=0; static LPDIRECTSOUNDBUFFER ds_lpDSB=0; static HWND ds_hWndMain=0; static HANDLE ds_hSemaphoreNotify=0; static DWORD ds_dwWriteCursor=0; static DWORD ds_dwPlayCursorOld=0; static MMRESULT ds_timerID=0; static int ds_iWriteTimes=0; //#define DBGOUTPUT
#ifdef DBGOUTPUT
#include <stdio.h> #endif /*
DirectSoundBuffer 通知机制引发的问题: 若采用DirectSoundBuffer通知机制,发现运行其它使用相同通知机制的播放程序时,会造成 本程序的通知事件误触发,如何识别是本程序还是其它程序引发的通知事件? 如何解决? 本程序采用定时器回调函数定时判断播放位置、发信号(设置通知事件).
ds_dwWriteCursor: 写入光标,指向播放一块后释放的一个空闲块首址,跟随于播放光标后. ds_dwPlayCursor: 播放光标,取值(0..n)*DS_ONEBUF ds_dwPlayCursorOld: 暂存播放光标先前值,取值(0..n-1)*DS_ONEBUF, 若使用写入光标, 由于本函数中“读”与audio_play()中“写”不同步而出错. */ /*
MyPlayPositionNotify() -- 定时器回调函数,播放完一块发一次信号. */ static void CALLBACK MyPlayPositionNotify(UINT uTimerID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2) { DWORD dwPlayCursor=0; ds_lpDSB->lpVtbl->GetCurrentPosition(ds_lpDSB,&dwPlayCursor, NULL); if(dwPlayCursor>ds_dwPlayCursorOld+DS_ONEBUF || (dwPlayCursor<DS_ONEBUF && ds_dwPlayCursorOld==DS_BUFSIZE-DS_ONEBUF)) { #ifdef DBGOUTPUT printf("\nWriteCursor = %6d, PlayCursor = %6d, PlayCursorOld = %6d", ds_dwWriteCursor, dwPlayCursor, ds_dwPlayCursorOld); #endif dwPlayCursor /= DS_ONEBUF; ds_dwPlayCursorOld = dwPlayCursor*DS_ONEBUF; /* 发信号,使audio_play()内的等待函数返回. */
ReleaseSemaphore(ds_hSemaphoreNotify,1,NULL); } } static int audio_open(int nChannels,long nSamplesPerSec,unsigned int depth)
{ DSBUFFERDESC dsbd; WAVEFORMATEX wvOut; ds_dwWriteCursor=0;
ds_dwPlayCursorOld=0; ds_iWriteTimes=0; if(nSamplesPerSec == -1)
return -1; /* Create DirectSound */ if ( DirectSoundCreate(NULL, &ds_lpDS, NULL) != DS_OK ) { return -1; } if( ds_hWndMain == 0 )
ds_hWndMain=GetDesktopWindow(); /* Set Cooperative Level */
if ( ds_lpDS->lpVtbl->SetCooperativeLevel(ds_lpDS,ds_hWndMain, DSSCL_PRIORITY) != DS_OK ) { return -1; } wvOut.wFormatTag = WAVE_FORMAT_PCM;
wvOut.wBitsPerSample = depth; wvOut.nChannels = nChannels; wvOut.nSamplesPerSec = nSamplesPerSec; wvOut.nAvgBytesPerSec = wvOut.nSamplesPerSec * wvOut.nChannels * wvOut.wBitsPerSample/8; wvOut.nBlockAlign = wvOut.nChannels * wvOut.wBitsPerSample/8; /* Create Secondary Sound Buffer */
ZeroMemory(&dsbd, sizeof(DSBUFFERDESC)); dsbd.dwSize = sizeof(DSBUFFERDESC); dsbd.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLVOLUME | DSBCAPS_GETCURRENTPOSITION2; dsbd.dwBufferBytes = DS_BUFSIZE; dsbd.lpwfxFormat = &wvOut; if ( ds_lpDS->lpVtbl->CreateSoundBuffer(ds_lpDS,&dsbd, &ds_lpDSB, NULL) != DS_OK ) { return -1; } ds_hSemaphoreNotify = CreateSemaphore(NULL,DS_N,DS_N,NULL);
if( ds_hSemaphoreNotify == NULL ) return -1; return 0;
} /*
audio_play() -- 播放一块,每解码一帧被调用一次 返回值: 写入到 SoundBuffer 的字节数,失败返回0 */ static int audio_play(struct audio_play *play) { LPVOID lpDSBuf; DWORD dwDSLockSize; UINT len=0; HRESULT hr; /* 1. 调用audio_pcm()解码一帧,得到的PCM写入到暂存区audio_pcm[] */
len = audio_pcm(pcm_data+pcm_length, play->nsamples,play->samples[0],
play->samples[1], play->mode, play->stats); pcm_length += len; if (pcm_length < DS_ONEBUF) return 0; /* 2. 若audio_pcm[]已写满DS_ONEBUF字节,等待播放完一块后释放一个空闲块 */
WaitForSingleObject(ds_hSemaphoreNotify,INFINITE);
/* 3. 将audio_pcm[]写入到 SoundBuffer,使完成播放一块 */
hr = ds_lpDSB->lpVtbl->Lock(ds_lpDSB,ds_dwWriteCursor,pcm_length,&lpDSBuf,&dwDSLockSize,NULL,0,0);
if(hr == DSERR_BUFFERLOST) { ds_lpDSB->lpVtbl->Restore(ds_lpDSB); ds_lpDSB->lpVtbl->Lock(ds_lpDSB,ds_dwWriteCursor,pcm_length,&lpDSBuf,&dwDSLockSize,NULL,0,0); } if(FAILED(hr)) return 0; CopyMemory(lpDSBuf,pcm_data,dwDSLockSize); ds_lpDSB->lpVtbl->Unlock(ds_lpDSB,lpDSBuf,dwDSLockSize,NULL,0); /* 4. 更新相关变量! */
pcm_length=0;
ds_dwWriteCursor += dwDSLockSize; if( ds_dwWriteCursor >= DS_BUFSIZE) ds_dwWriteCursor -= DS_BUFSIZE; /* 5. 首次写满Direct SoundBuffer后,启动定时器,开始播放 */
if(ds_iWriteTimes < DS_N) {
ds_iWriteTimes++; if(ds_iWriteTimes == DS_N) { UINT uRs = DS_ONEBUF / 4608 / 2 * 26; ds_timerID = timeSetEvent( uRs/2, uRs, (LPTIMECALLBACK)MyPlayPositionNotify, 0, TIME_PERIODIC | TIME_CALLBACK_FUNCTION ); ds_lpDSB->lpVtbl->SetCurrentPosition(ds_lpDSB,0); ds_lpDSB->lpVtbl->Play(ds_lpDSB,0, 0, DSBPLAY_LOOPING); } #ifdef DBGOUTPUT printf("\nWriteTimes= %2d, WriteBytes= %d",ds_iWriteTimes,dwDSLockSize); #endif } return dwDSLockSize;
} static int audio_init(struct audio_init *init)
{ SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST); return 0;
} static int audio_config(struct audio_config *config)
{ unsigned int bitdepth; bitdepth = config->precision & ~7;
if (bitdepth == 0) bitdepth = 16; else if (bitdepth > 32) bitdepth = 32; audio_close();
if (audio_open(config->channels,config->speed, bitdepth) == -1)
return -1; switch (config->precision = bitdepth) {
case 8: audio_pcm = audio_pcm_u8; break; case 16:
audio_pcm = audio_pcm_s16le; break; case 24:
audio_pcm = audio_pcm_s24le; break; case 32:
audio_pcm = audio_pcm_s32le; break; } return 0;
} static int audio_pause(int bPause)
{ static int paused; if(ds_lpDSB == 0) return -1; if (bPause && !paused) { ds_lpDSB->lpVtbl->Stop(ds_lpDSB); return 0; } else if (!bPause && paused) { ds_lpDSB->lpVtbl->Play(ds_lpDSB,0, 0, DSBPLAY_LOOPING); return 0; } paused = bPause;
return 0; } static int audio_stop()
{ if(ds_lpDSB == 0) return -1; ds_lpDSB->lpVtbl->Stop(ds_lpDSB); return 0; } /*
audio_flush() -- 播放完Direct Sound缓冲区ds_lpDSB中的数据,DS_N-1块 */ static void audio_flush() { int i; for( i=1; i<DS_N; i++ ) WaitForSingleObject(ds_hSemaphoreNotify,INFINITE); } static int audio_close()
{ SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS); if (ds_lpDSB != 0) {
ds_lpDSB->lpVtbl->Stop(ds_lpDSB); ds_lpDSB->lpVtbl->Release(ds_lpDSB); ds_lpDSB = 0; } if (ds_timerID != 0) { timeKillEvent(ds_timerID); ds_timerID = 0; } if (ds_hSemaphoreNotify != 0) { CloseHandle(ds_hSemaphoreNotify); ds_hSemaphoreNotify = 0; } if(ds_lpDS != 0) { ds_lpDS->lpVtbl->Release(ds_lpDS); ds_lpDS = 0; } ds_dwWriteCursor = 0; ds_dwPlayCursorOld = 0; ds_iWriteTimes = 0; return 0;
} int audio_dsound(union audio_control *control)
{ audio_error = 0; switch (control->command) {
case AUDIO_COMMAND_INIT: return audio_init(&control->init); case AUDIO_COMMAND_CONFIG:
return audio_config(&control->config); case AUDIO_COMMAND_PLAY:
return audio_play(&control->play); case AUDIO_COMMAND_STOP:
if (control->stop.flush) return audio_stop(); return audio_pause(1); case AUDIO_COMMAND_FINISH:
audio_flush(); return audio_close(); } return 0;
} |
|