nes 红白机模拟器 第6篇 声音支持

Posted 宁次

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 。 可以在 置顶博文中找地址。

以上是关于nes 红白机模拟器 第6篇 声音支持的主要内容,如果未能解决你的问题,请参考以下文章

arm 2440 linux 应用程序 nes 红白机模拟器 第2篇 InfoNES

arm 2440 linux 应用程序 nes 红白机模拟器 第4篇 linux 手柄驱动支持

arm linux 应用程序 nes 红白机模拟器 第1篇

nes 红白机模拟器 第3篇 游戏手柄测试 51 STM32

nes 红白机模拟器 第5篇 全屏显示

NES 系统架构