InfoNES 源碼中並沒有包含 linux 的聲音支持。
但提供 wince 和 win 的工程,文件,通過分析,win 的 DirectSound 發聲,在使用 linux ALSA 實現。
先使用 DirectSound 模仿寫一個 播放 wav 的程序。
為了簡單,我這里使用 vc++ 6.0 (vs2015 實在太大了,電腦裝上太卡)。
新建一個 mfc exe 項目,基於對話框。放一個按鈕,雙擊添加事件。
添加頭文件引用
#include <mmsystem.h>
#pragma comment(lib,"Winmm.lib")
點擊 開始播放 事件
void CWavDlg::OnButtonPlay()
{
// TODO: Add your control notification handler code here
PlaySound(_T("1.wav"), NULL, SND_NOWAIT);
}
在 debug 目錄,放一個 1.wav 生成可執行文件,點 開始播放, 果然可以播放出來。(win 的東西就是這么簡單實用)。
分析 InfoNES_Sound_Win.cpp
類初始化
1 DIRSOUND::DIRSOUND(HWND hwnd) 2 { 3 DWORD ret; 4 WORD x; 5 6 // init variables 7 iCnt = Loops * 3 / 4; // loops:20 iCnt:20*3/4 = 15 8 9 for ( x = 0;x < ds_NUMCHANNELS; x++ ) // ds_NUMCHANNELS = 8 10 { 11 lpdsb[x] = NULL; // DirectSoundBuffer lpdsb[ds_NUMCHANNELS]; 8個 初始化為 NULL 12 } 13 14 // init DirectSound 創建一個 DirectSound 里面有 8個 DirectSoundBuffer 15 ret = DirectSoundCreate(NULL, &lpdirsnd, NULL); 16 17 if (ret != DS_OK) 18 { 19 InfoNES_MessageBox( "Sound Card is needed to execute this application." ); 20 exit(-1); 21 } 22 23 // set cooperative level 24 #if 1 25 //設置屬性不重要 26 ret = lpdirsnd->SetCooperativeLevel(hwnd, DSSCL_PRIORITY); 27 #else 28 ret = lpdirsnd->SetCooperativeLevel( hwnd, DSSCL_NORMAL ); 29 #endif 30 31 if ( ret != DS_OK ) 32 { 33 InfoNES_MessageBox( "SetCooperativeLevel() Failed." ); 34 exit(-1); 35 } 36 }
SoundOpen
1 WORD DIRSOUND::AllocChannel(void) 2 { 3 WORD x; 4 5 //判斷 lpdsb 找到一個 為空的 這里應該返回0 6 for (x=0;x<ds_NUMCHANNELS;x++) 7 { 8 if (lpdsb[x] == NULL) 9 { 10 break; 11 } 12 } 13 14 if ( x == ds_NUMCHANNELS ) 15 { 16 /* No available channel */ 17 InfoNES_MessageBox( "AllocChannel() Failed." ); 18 exit(-1); 19 } 20 21 return (x); 22 } 23 24 void DIRSOUND::CreateBuffer(WORD channel) 25 { 26 DSBUFFERDESC dsbdesc; //SoundBuffer 描述 27 PCMWAVEFORMAT pcmwf; //wav fmt 格式描述 28 HRESULT hr; 29 30 //清0 31 memset(&pcmwf, 0, sizeof(PCMWAVEFORMAT)); 32 //pcm 格式 33 pcmwf.wf.wFormatTag = WAVE_FORMAT_PCM; 34 //1個聲道 35 pcmwf.wf.nChannels = ds_CHANSPERSAMPLE; 36 //采樣率 44100 37 pcmwf.wf.nSamplesPerSec = ds_SAMPLERATE; 38 //對齊 采樣率 / 8 * 聲道數 = 44100 / 8 * 1 = 5512.5 39 pcmwf.wf.nBlockAlign = ds_CHANSPERSAMPLE * ds_BITSPERSAMPLE / 8; 40 //緩存區大小 44100*5512.5 = 243101250 41 pcmwf.wf.nAvgBytesPerSec = pcmwf.wf.nSamplesPerSec * pcmwf.wf.nBlockAlign; 42 //8位 聲音 43 pcmwf.wBitsPerSample = ds_BITSPERSAMPLE; 44 45 //清0 46 memset(&dsbdesc, 0, sizeof(DSBUFFERDESC)); 47 dsbdesc.dwSize = sizeof(DSBUFFERDESC); 48 dsbdesc.dwFlags = 0; 49 //緩存大小 735 * 15 = 11025 50 dsbdesc.dwBufferBytes = len[channel]*Loops; 51 dsbdesc.lpwfxFormat = (LPWAVEFORMATEX)&pcmwf; 52 53 hr = lpdirsnd->CreateSoundBuffer(&dsbdesc, &lpdsb[channel], NULL); 54 55 if (hr != DS_OK) 56 { 57 InfoNES_MessageBox( "CreateSoundBuffer() Failed." ); 58 exit(-1); 59 } 60 } 61 62 //samples_per_sync = 735 sample_rate = 44100 63 BOOL DIRSOUND::SoundOpen(int samples_per_sync, int sample_rate) 64 { 65 //ch 1 WORD unsigned short 類型 , 創建一個 通道 , 返回 第0 個 SoundBuffer 66 ch1 = AllocChannel(); 67 68 /** 69 * 參數定義 70 * BYTE *sound[ds_NUMCHANNELS]; 71 * DWORD len[ds_NUMCHANNELS]; 72 */ 73 //申請了一個 735 大小的 Byte 74 sound[ch1] = new BYTE[ samples_per_sync ]; 75 //記錄了 大小 735 76 len[ch1] = samples_per_sync; 77 78 if ( sound[ch1] == NULL ) 79 { 80 InfoNES_MessageBox( "new BYTE[] Failed." ); 81 exit(-1); 82 } 83 84 //創建緩存區 85 CreateBuffer( ch1 ); 86 87 /* Clear buffer */ 88 FillMemory( sound[ch1], len[ch1], 0 ); 89 //執行15次 90 for ( int i = 0; i < Loops; i++ ) 91 SoundOutput( len[ch1], sound[ch1] ); 92 93 /* Begin to play sound */ 94 Start( ch1, TRUE ); 95 96 return TRUE; 97 }
SoundOutput
1 //初始化時 執行 samples:735 wave:NULL 2 BOOL DIRSOUND::SoundOutput(int samples, BYTE *wave) 3 { 4 /* Buffering sound data */ 5 //將 wave 復制到 sound 6 CopyMemory( sound[ ch1 ], wave, samples ); 7 8 /* Copying to sound data buffer */ 9 FillBuffer( ch1 ); 10 11 /* Play if Counter reaches buffer edge */ 12 //初始化時 iCnt:15 Loops:20 13 if ( Loops == ++iCnt ) 14 { 15 iCnt = 0; 16 } 17 //這里 iCnt = 16 18 return TRUE; 19 } 20 void DIRSOUND::FillBuffer( WORD channel ) 21 { 22 LPVOID write1; 23 DWORD length1; 24 LPVOID write2; 25 DWORD length2; 26 HRESULT hr; 27 28 //得到要寫入的地址 29 hr = lpdsb[channel]->Lock( iCnt * len[channel], len[channel], &write1, &length1, &write2, &length2, 0 ); 30 31 //如果返回DSERR_BUFFERLOST,釋放並重試鎖定 32 if (hr == DSERR_BUFFERLOST) 33 { 34 lpdsb[channel]->Restore(); 35 36 hr = lpdsb[channel]->Lock( iCnt * len[channel], len[channel], &write1, &length1, &write2, &length2, 0 ); 37 } 38 39 if (hr != DS_OK) 40 { 41 InfoNES_MessageBox( "Lock() Failed." ); 42 exit(-1); 43 } 44 45 //寫入數據 46 CopyMemory( write1, sound[channel], length1 ); 47 48 if (write2 != NULL) 49 { 50 CopyMemory(write2, sound[channel] + length1, length2); 51 } 52 //解鎖 53 hr = lpdsb[channel]->Unlock(write1, length1, write2, length2); 54 55 if (hr != DS_OK) 56 { 57 InfoNES_MessageBox( "Unlock() Failed." ); 58 exit(-1); 59 } 60 }
Play
1 //初始化時 ch1 重復播放 2 void DIRSOUND::Start(WORD channel, BOOL looping) 3 { 4 HRESULT hr; 5 6 hr = lpdsb[channel]->Play( 0, 0, looping == TRUE ? DSBPLAY_LOOPING : 0 ); 7 8 if ( hr != DS_OK ) 9 { 10 InfoNES_MessageBox( "Play() Failed." ); 11 exit(-1); 12 } 13 }
播放調用
1 void InfoNES_SoundOutput( int samples, BYTE *wave1, BYTE *wave2, BYTE *wave3, BYTE *wave4, BYTE *wave5 ) 2 { 3 //rec_freq = 735 4 BYTE wave[ rec_freq ]; 5 //取了 wave1~5 的平均值 6 for ( int i = 0; i < rec_freq; i++) 7 { 8 wave[i] = ( wave1[i] + wave2[i] + wave3[i] + wave4[i] + wave5[i] ) / 5; 9 } 10 #if 1 11 if (!lpSndDevice->SoundOutput( samples, wave ) ) 12 #else 13 if (!lpSndDevice->SoundOutput( samples, wave3 ) ) 14 #endif 15 { 16 InfoNES_MessageBox( "SoundOutput() Failed." ); 17 exit(0); 18 } 19 }
最后總結得到幾個有用的參數:
聲道數 1
采樣率 44100
采樣位數 8
每次播放塊大小(NES APU 每次生成一塊)735
更新 2018-11-04
已移值到 alsa-lib 支持,播放正常,已更新至 github 。 可以在 置頂博文中找地址。