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
类初始化
DIRSOUND::DIRSOUND(HWND hwnd)
{
DWORD ret;
WORD x; // init variables
iCnt = Loops * / ; // loops:20 iCnt:20*3/4 = 15 for ( x = ;x < ds_NUMCHANNELS; x++ ) // ds_NUMCHANNELS = 8
{
lpdsb[x] = NULL; // DirectSoundBuffer lpdsb[ds_NUMCHANNELS]; 8个 初始化为 NULL
} // init DirectSound 创建一个 DirectSound 里面有 8个 DirectSoundBuffer
ret = DirectSoundCreate(NULL, &lpdirsnd, NULL); if (ret != DS_OK)
{
InfoNES_MessageBox( "Sound Card is needed to execute this application." );
exit(-);
} // set cooperative level
#if 1
//设置属性不重要
ret = lpdirsnd->SetCooperativeLevel(hwnd, DSSCL_PRIORITY);
#else
ret = lpdirsnd->SetCooperativeLevel( hwnd, DSSCL_NORMAL );
#endif if ( ret != DS_OK )
{
InfoNES_MessageBox( "SetCooperativeLevel() Failed." );
exit(-);
}
}
SoundOpen
WORD DIRSOUND::AllocChannel(void)
{
WORD x; //判断 lpdsb 找到一个 为空的 这里应该返回0
for (x=;x<ds_NUMCHANNELS;x++)
{
if (lpdsb[x] == NULL)
{
break;
}
} if ( x == ds_NUMCHANNELS )
{
/* No available channel */
InfoNES_MessageBox( "AllocChannel() Failed." );
exit(-);
} return (x);
} void DIRSOUND::CreateBuffer(WORD channel)
{
DSBUFFERDESC dsbdesc; //SoundBuffer 描述
PCMWAVEFORMAT pcmwf; //wav fmt 格式描述
HRESULT hr; //清0
memset(&pcmwf, , sizeof(PCMWAVEFORMAT));
//pcm 格式
pcmwf.wf.wFormatTag = WAVE_FORMAT_PCM;
//1个声道
pcmwf.wf.nChannels = ds_CHANSPERSAMPLE;
//采样率 44100
pcmwf.wf.nSamplesPerSec = ds_SAMPLERATE;
//对齐 采样率 / 8 * 声道数 = 44100 / 8 * 1 = 5512.5
pcmwf.wf.nBlockAlign = ds_CHANSPERSAMPLE * ds_BITSPERSAMPLE / ;
//缓存区大小 44100*5512.5 = 243101250
pcmwf.wf.nAvgBytesPerSec = pcmwf.wf.nSamplesPerSec * pcmwf.wf.nBlockAlign;
//8位 声音
pcmwf.wBitsPerSample = ds_BITSPERSAMPLE; //清0
memset(&dsbdesc, , sizeof(DSBUFFERDESC));
dsbdesc.dwSize = sizeof(DSBUFFERDESC);
dsbdesc.dwFlags = ;
//缓存大小 735 * 15 = 11025
dsbdesc.dwBufferBytes = len[channel]*Loops;
dsbdesc.lpwfxFormat = (LPWAVEFORMATEX)&pcmwf; hr = lpdirsnd->CreateSoundBuffer(&dsbdesc, &lpdsb[channel], NULL); if (hr != DS_OK)
{
InfoNES_MessageBox( "CreateSoundBuffer() Failed." );
exit(-);
}
} //samples_per_sync = 735 sample_rate = 44100
BOOL DIRSOUND::SoundOpen(int samples_per_sync, int sample_rate)
{
//ch 1 WORD unsigned short 类型 , 创建一个 通道 , 返回 第0 个 SoundBuffer
ch1 = AllocChannel(); /**
* 参数定义
* BYTE *sound[ds_NUMCHANNELS];
* DWORD len[ds_NUMCHANNELS];
*/
//申请了一个 735 大小的 Byte
sound[ch1] = new BYTE[ samples_per_sync ];
//记录了 大小 735
len[ch1] = samples_per_sync; if ( sound[ch1] == NULL )
{
InfoNES_MessageBox( "new BYTE[] Failed." );
exit(-);
} //创建缓存区
CreateBuffer( ch1 ); /* Clear buffer */
FillMemory( sound[ch1], len[ch1], );
//执行15次
for ( int i = ; i < Loops; i++ )
SoundOutput( len[ch1], sound[ch1] ); /* Begin to play sound */
Start( ch1, TRUE ); return TRUE;
}
SoundOutput
//初始化时 执行 samples:735 wave:NULL
BOOL DIRSOUND::SoundOutput(int samples, BYTE *wave)
{
/* Buffering sound data */
//将 wave 复制到 sound
CopyMemory( sound[ ch1 ], wave, samples ); /* Copying to sound data buffer */
FillBuffer( ch1 ); /* Play if Counter reaches buffer edge */
//初始化时 iCnt:15 Loops:20
if ( Loops == ++iCnt )
{
iCnt = ;
}
//这里 iCnt = 16
return TRUE;
}
void DIRSOUND::FillBuffer( WORD channel )
{
LPVOID write1;
DWORD length1;
LPVOID write2;
DWORD length2;
HRESULT hr; //得到要写入的地址
hr = lpdsb[channel]->Lock( iCnt * len[channel], len[channel], &write1, &length1, &write2, &length2, ); //如果返回DSERR_BUFFERLOST,释放并重试锁定
if (hr == DSERR_BUFFERLOST)
{
lpdsb[channel]->Restore(); hr = lpdsb[channel]->Lock( iCnt * len[channel], len[channel], &write1, &length1, &write2, &length2, );
} if (hr != DS_OK)
{
InfoNES_MessageBox( "Lock() Failed." );
exit(-);
} //写入数据
CopyMemory( write1, sound[channel], length1 ); if (write2 != NULL)
{
CopyMemory(write2, sound[channel] + length1, length2);
}
//解锁
hr = lpdsb[channel]->Unlock(write1, length1, write2, length2); if (hr != DS_OK)
{
InfoNES_MessageBox( "Unlock() Failed." );
exit(-);
}
}
Play
//初始化时 ch1 重复播放
void DIRSOUND::Start(WORD channel, BOOL looping)
{
HRESULT hr; hr = lpdsb[channel]->Play( , , looping == TRUE ? DSBPLAY_LOOPING : ); if ( hr != DS_OK )
{
InfoNES_MessageBox( "Play() Failed." );
exit(-);
}
}
播放调用
void InfoNES_SoundOutput( int samples, BYTE *wave1, BYTE *wave2, BYTE *wave3, BYTE *wave4, BYTE *wave5 )
{
//rec_freq = 735
BYTE wave[ rec_freq ];
//取了 wave1~5 的平均值
for ( int i = ; i < rec_freq; i++)
{
wave[i] = ( wave1[i] + wave2[i] + wave3[i] + wave4[i] + wave5[i] ) / ;
}
#if 1
if (!lpSndDevice->SoundOutput( samples, wave ) )
#else
if (!lpSndDevice->SoundOutput( samples, wave3 ) )
#endif
{
InfoNES_MessageBox( "SoundOutput() Failed." );
exit();
}
}
最后总结得到几个有用的参数:
声道数 1
采样率 44100
采样位数 8
每次播放块大小(NES APU 每次生成一块)735
更新 2018-11-04
已移值到 alsa-lib 支持,播放正常,已更新至 github 。 可以在 置顶博文中找地址。
nes 红白机模拟器 第6篇 声音支持的更多相关文章
- nes 红白机模拟器 第7篇 编译使用方法
模拟器,基于 InfoNES ,作者添加修改以下功能: 1, joypad 真实手柄驱动程序(字符型设备驱动) 2,原始图像只有256*240 ,添加 图像放大算法,这里实现了2种,a, 最近邻插值 ...
- arm 2440 linux 应用程序 nes 红白机模拟器 第1篇
对比了很多种,开源的 NES 模拟器 VirtuaNES , nestopia , FakeNES , FCEUX , InfoNES , LiteNES 最后决定使用 LiteNES 进行移值,它是 ...
- nes 红白机模拟器 第1篇
对比了很多种,开源的 NES 模拟器 VirtuaNES , nestopia , FakeNES , FCEUX , InfoNES , LiteNES 最后决定使用 LiteNES 进行移值,它是 ...
- arm 2440 linux 应用程序 nes 红白机模拟器 第4篇 linux 手柄驱动支持
小霸王学习机的真实手柄,实测CPU 占用 80% 接线图: 手柄读时序: joypad.c 驱动: 普通的字符设备驱动. #include <linux/module.h> #includ ...
- arm 2440 linux 应用程序 nes 红白机模拟器 第2篇 InfoNES
InfoNES 支持 map ,声音,代码比较少,方便 移值. 在上个 LiteNES 的基础上,其实不到半小时就移值好了这个,但问题是,一直是黑屏.InfoNES_LoadFrame () Wo ...
- nes 红白机模拟器 第5篇 全屏显示
先看一下效果图 放大的原理是使用最初级的算法,直接取对应像素法. /*================================================================= ...
- nes 红白机模拟器 第4篇 linux 手柄驱动支持
小霸王学习机的真实手柄,实测CPU 占用 80% 接线图: 手柄读时序: joypad.c 驱动: 普通的字符设备驱动. #include <linux/module.h> #includ ...
- nes 红白机模拟器 第2篇 InfoNES
InfoNES 支持 map ,声音,代码比较少,方便 移值. 在上个 LiteNES 的基础上,其实不到半小时就移值好了这个,但问题是,一直是黑屏.InfoNES_LoadFrame () Wo ...
- nes 红白机模拟器 第3篇 游戏手柄测试 51 STM32
手柄使用的是 CD4021 ,datasheet 上说支持 3V - 15V . 因为手柄是 5V 供电,2440 开发板上是GPIO 3.3V 电平,STM32 GPIO 也是 3.3V (也兼容5 ...
随机推荐
- 吴裕雄--天生自然python学习笔记:pandas模块删除 DataFrame 数据
Pandas 通过 drop 函数删除 DataFrarne 数据,语法为: 例如,删除陈聪明(行标题)的成绩: import pandas as pd datas = [[65,92,78,83,7 ...
- PyCharm4.5 中文破解版破解步骤
1.在下载之家下载PyCharm4.5中文版软件包,然后右击软件安装包选择解压到“pycharm4.5.3”. 2.在解压文件夹中找到pycharm-professional-4.5.3,右击打开. ...
- python后端面试第六部分:git版本控制--长期维护
################## git版本控制 ####################### 1,git常见命令作用 2,某个文件夹中的内容进行版本管理:进入文件夹,右键git bash 3, ...
- be accustomed to doing|actual |acute|adapt |
Sometimes you've got to play a position that you're not accustomedto for 90 minutes, " he said. ...
- Spring常见的两种增强方式
一.编程式增强 不借助spring的配置,通过自己实例化对象来实现的增强方式 创建增强类,需要实现你需要的增强接口,(只有实现了该接口,这个类就是一个通知)) /** * 增强类 */ public ...
- java中的锁——列队同步器
队列同步器 队列同步器(AbstractQueuedSynchronizer)为实现依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量.事件,等等)提供一个框架.此类的设计目标是成为依 ...
- B-Tree索引
翻译自http://dev.mysql.com/doc/refman/5.6/en/index-btree-hash.html 理解B-Tree和Hash的数据结构能够帮助我们预测不同存储引擎下的查询 ...
- Eclipse中项目过大引起的 IDE 加载缓慢,JVM 内存不足的情况解决
如果 IDE 加载项目非常缓慢,甚至常常出现卡死的情况,有可能是开发工具设置的 JVM 内存不够引起的.解决办法:找到 Eclipse 的安装目录,修改 Eclipse.ini 配置文件.修改此配置文 ...
- Windows10下Linux系统的安装和使用
WSL 以往我都是直接安装VirtualBox,然后再下载Linux系统的ISO镜像,装到VirtualBox里运行. 改用Win10系统后,了解到了WSL(Windows Subsystem for ...
- Redis学习之热点key重建
在Redis的生产环境中,大量客户端连接请求某一个key,但都需要从DB中获取数据,来回写数据库,如下图: <ignore_js_op> 造成的问题: 大量的线程请求数据库,造成数据库压力 ...