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篇 声音支持的更多相关文章

  1. nes 红白机模拟器 第7篇 编译使用方法

    模拟器,基于 InfoNES ,作者添加修改以下功能: 1, joypad 真实手柄驱动程序(字符型设备驱动) 2,原始图像只有256*240 ,添加 图像放大算法,这里实现了2种,a, 最近邻插值 ...

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

    对比了很多种,开源的 NES 模拟器 VirtuaNES , nestopia , FakeNES , FCEUX , InfoNES , LiteNES 最后决定使用 LiteNES 进行移值,它是 ...

  3. nes 红白机模拟器 第1篇

    对比了很多种,开源的 NES 模拟器 VirtuaNES , nestopia , FakeNES , FCEUX , InfoNES , LiteNES 最后决定使用 LiteNES 进行移值,它是 ...

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

    小霸王学习机的真实手柄,实测CPU 占用 80% 接线图: 手柄读时序: joypad.c 驱动: 普通的字符设备驱动. #include <linux/module.h> #includ ...

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

    InfoNES 支持 map ,声音,代码比较少,方便 移值. 在上个 LiteNES  的基础上,其实不到半小时就移值好了这个,但问题是,一直是黑屏.InfoNES_LoadFrame ()  Wo ...

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

    先看一下效果图 放大的原理是使用最初级的算法,直接取对应像素法. /*================================================================= ...

  7. nes 红白机模拟器 第4篇 linux 手柄驱动支持

    小霸王学习机的真实手柄,实测CPU 占用 80% 接线图: 手柄读时序: joypad.c 驱动: 普通的字符设备驱动. #include <linux/module.h> #includ ...

  8. nes 红白机模拟器 第2篇 InfoNES

    InfoNES 支持 map ,声音,代码比较少,方便 移值. 在上个 LiteNES  的基础上,其实不到半小时就移值好了这个,但问题是,一直是黑屏.InfoNES_LoadFrame ()  Wo ...

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

    手柄使用的是 CD4021 ,datasheet 上说支持 3V - 15V . 因为手柄是 5V 供电,2440 开发板上是GPIO 3.3V 电平,STM32 GPIO 也是 3.3V (也兼容5 ...

随机推荐

  1. SLIQ/SPRINT

    SLIQ/SPRINT */--> SLIQ/SPRINT Before SLIQ, most classification alogrithms have the problem that t ...

  2. linux下如何查看服务器的硬件配置信息

    性能测试时一定要确定测试环境和的硬件配置.软件版本配置,保证和线上一致,才更接近真实环境. 那么linux下如何查看服务器的硬件配置信息?? 一.查看cpu信息 1.所有信息 lscpu [root@ ...

  3. 统计学方法(PCA、ICA、RCA、LCA)

    ---------------------------------------------------------------------------------------------------- ...

  4. 第十六届“二十一世纪的计算”学术研讨会 牛津大学肿瘤成像学教授Michael Brady主题演讲

    Computing and Healthcare 牛津大学肿瘤成像学教授Michael Brady主题演讲" title="第十六届"二十一世纪的计算"学术研讨 ...

  5. Linux启动初始化配置文件浅析

    转自:http://blog.51cto.com/19055/1144600 1)/etc/profile   登录时,会执行. 全局(公有)配置,不管是哪个用户,登录时都会读取该文件. (2)/ec ...

  6. firefox上网慢

    由于Chromium浏览器,打字的时候经常会跳字母,所以就换了firefox浏览器,但是FF上网的时候特别慢,而且大部分时间是花费在解析域名上.因此到网上找了许多资料,最终解决方法如下. 1.安装dn ...

  7. C#获取代码执行时间(精确到毫秒)

    private void Time(int i) { Stopwatch sw = new Stopwatch(); sw.Start(); Thread.Sleep(i); sw.Stop(); C ...

  8. Nginx笔记总结一:基本安装和配置

    1. Nginx安装 下载地址和安装依赖包 http://nginx.org/download/nginx-1.9.14.tar.gz yum -y install pcre pcre-devel z ...

  9. List、Set、数组之间的转换

    数组转Collection 使用Apache Jakarta Commons Collections: import org.apache.commons.collections.Collection ...

  10. RocketMQ 零拷贝

    一.零拷贝原理:Consumer 消费消息过程,使用了零拷贝,零拷贝包含以下两种方式: 1.使用 mmap + write 方式  (RocketMQ选择的方式:因为有小块数据传输的需求,效果会比 s ...