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. Linux安装swoole拓展 (一键安装lnmp后安装可用完美)

    一键安装lnmp后安装可用完美 swoole(一键安装完lnmp重启下,之前出现502一直解决不了,不清楚啥情况) 找到对应php版本,在lnmp文件夹的src 1.安装swoole cd /usr/ ...

  2. mysql 数据库 创建用户和授权

    创建用户和密码 CREATE USER '用户名'@'%' IDENTIFIED BY '密码'; 创建几个数据库 例如 db1,db2 用户授权访问指定的数据库 grant all privileg ...

  3. GCC与静态库、动态库

    GCC 常用指令 1 man gcc gcc工作流程例如: gcc hello.c 1234567891011121314 //***第一步***gcc -E hello.c >hello.i ...

  4. mybatis的通用mapper小结

    import tk.mybatis.mapper.entity.Example; //此包是tk下的1.定义一个dao层接口不需要任何方法 需要继承Mapper<类型> 2.在servic ...

  5. Docker系列之实战:3.安装MariaDB

    环境 [root@centos181001 ~]# cat /etc/centos-release CentOS Linux release 7.6.1810 (Core) [root@centos1 ...

  6. 不装逼地说,在 Google 到底能学到啥?

    不装逼地说,在 Google 到底能学到啥? 2017-03-17 PHP开发者 (点击上方蓝字,快速关注我们) 本文转自公众号「半轻人」(ID:ban-qing-ren),伯乐在线/PHP开发者已获 ...

  7. jQuery学习笔记三

    使用fadeIn()js解释器会将所选元素的CSS opacity属性从0改为100,fadeTo()会动画显示所选元素,将它为改为某个特定的透明度百分比,使用fadeOut()js解释器会将所选元素 ...

  8. 设计模式详解及PHP实现:代理模式

    [目录] 代理模式(Proxy pattern) 代理模式是一种结构型模式,它可以为其他对象提供一种代理以控制对这个对象的访问. 主要角色 抽象主题角色(Subject):它的作用是统一接口.此角色定 ...

  9. JS实现总价随数量变化而变化(顾客购买商品表单)

    */ * Copyright (c) 2016,烟台大学计算机与控制工程学院 * All rights reserved. * 文件名:test.html * 作者:常轩 * 微信公众号:Worldh ...

  10. 使用EventBus + Redis发布订阅模式提升业务执行性能

    前言 最近一直奔波于面试,面了几家公司的研发.有让我受益颇多的面试经验,也有让我感觉浪费时间的面试经历~因为疫情原因,最近宅在家里也没事,就想着使用Redis配合事件总线去实现下具体的业务. 需求 一 ...