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 ...
随机推荐
- Part-Linux-2
1.cgi #1.创建cgi-bin目录#2.创建hi.json -> {"hi":"hello"}#3.python2 -m CGIHTTPServer ...
- 转载-Apache和Nginx运行原理解析
本文只作为了解Apache和Nginx知识的一个梳理,想详细了解的请阅读文末参考链接中的博文. Web服务器 Web服务器也称为WWW(WORLD WIDE WEB)服务器,主要功能是提供网上信息浏览 ...
- android高仿抖音、点餐界面、天气项目、自定义view指示、爬取美女图片等源码
Android精选源码 一个爬取美女图片的app Android高仿抖音 android一个可以上拉下滑的Ui效果 android用shape方式实现样式源码 一款Android上的新浪微博第三方轻量 ...
- Hell World:)
第一次弄博客是在2017年春节,自己弄了个域名,租了个小小的VPS,装好了wordpress,挑了一套模板,就这样上线了,可惜wordpress实在不是一个适合写字的地方,插件.主题令人眼花缭乱,慢慢 ...
- php--小数点问题
1.用round去小数点后两位时,有时候会出现很长的小数解决方法 sprintf("%.2f",round($total_fee,2)); 使用sprintf再截取一遍.出现变态小 ...
- spring 任务调度quartz
简单记录一下spring任务调度quartz的例子 首先添加包 quartz-2.2.3.jar 然后写个简单的TestJob类 package com.job; import java.util.D ...
- SpringMVC之@SessionAttribute和@ModelAttribute
1.Controller package com.tz.controller; import java.util.Map; import org.springframework.stereotype. ...
- 改变生活的移动计算——感受 MobiSys 2015
MobiSys 2015" title="改变生活的移动计算--感受 MobiSys 2015"> 作者:微软亚洲研究院研究员 张健松 今年的MobiSys会议地点 ...
- 如何进行Web服务的性能测试
涉及到web服务的功能在不断的增加,对于我们测试来说,我们不仅要保证服务端功能的正确性,也要验证服务端程序的性能是否符合要求.那么性能测试都要做些什么呢?我们该怎样进行性能测试呢? 性能测试 ...
- React Native拆包及热更新方案 · Solartisan
作者:solart 版权声明:本文图文为博主原创,转载请注明出处. 随着 React Native 的不断发展完善,越来越多的公司选择使用 React Native 替代 iOS/Android 进行 ...