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 。 可以在 置顶博文中找地址。

posted @ 2017-05-18 08:19  宁次  阅读(2456)  评论(0编辑  收藏  举报