nes 红白机模拟器 第6篇 声音支持
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 。 可以在 置顶博文中找地址。