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 ...
随机推荐
- Linux挂载存储(供应商给的资料)
infofile iqn.1994-05.com.redhat:16a2b8b7d8 infodb iqn.1994-05.com.redhat:8518efa2fe72 在iscsi server上 ...
- 基于phathomjs token 不定时无响应问题排查
问题描述 基于phathomjs的token池项目,基本原理是,打开淘宝页,获取匿名cookie,再由cookie中提取有效token,调用方通过该token获取淘宝数据 为方便基他人应用,封装了部分 ...
- cs231n spring 2017 lecture3 Loss Functions and Optimization
1. Loss function是用来量化评估当前预测的好坏,loss function越小表明预测越好. 几种典型的loss function: 1)Multiclass SVM loss:一般的S ...
- pressure to compete|listen to sb do|have sb do|felt like|shouldn't have done|spring up|in honour of|not more than|much as|
The pressure to compete causes Americans to be energetic, but it also puts then under a constant emo ...
- crack|erosion|strip|
V-ERG (使)破裂;(使)裂开;(使)断裂 If something hard cracks, or if you crack it, it becomes slightly damaged, w ...
- Java集合 - 明的博客
"In this world there are only two tragedies. One is not getting what one wants, and the other i ...
- js组合式继承
组合式继承是比较经典的继承,但是也有比较严重的缺点就是连两次调用了父类的构造函数. <!DOCTYPE html> <html lang="en"> < ...
- Ceph块设备
Ceph块设备 来自这里. 块是一个字节序列(例如,一个512字节的数据块).基于块的存储接口是最常见的存储数据的方法,它通常基于旋转介质,像硬盘.CD.软盘,甚至传统的9道磁带. 基本的块设备命令 ...
- curator配置及使用
1.action.yml --- actions: 1: action: index_settings options: index_settings: index: routing.allocati ...
- windows应用程序单实例
前言 这才第几天博客就跟不上了,看来一天一篇博客的目标还是有点大,写博客还是挺费时间的,写了不满意删,删完再写...直到自己没了耐心.今天先写个前言,实质性的内容明天再补吧.今天一天的收获还是挺多的, ...