zoukankan      html  css  js  c++  java
  • 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

    类初始化

     1 DIRSOUND::DIRSOUND(HWND hwnd)
     2 {
     3     DWORD ret;
     4     WORD x;
     5 
     6     // init variables
     7     iCnt = Loops * 3 / 4; // loops:20 iCnt:20*3/4 = 15
     8 
     9     for ( x = 0;x < ds_NUMCHANNELS; x++ ) // ds_NUMCHANNELS = 8
    10     {
    11         lpdsb[x] = NULL; // DirectSoundBuffer lpdsb[ds_NUMCHANNELS]; 8个 初始化为 NULL
    12     }
    13 
    14     // init DirectSound 创建一个 DirectSound 里面有 8个 DirectSoundBuffer
    15     ret = DirectSoundCreate(NULL, &lpdirsnd, NULL);
    16 
    17     if (ret != DS_OK)
    18     {
    19         InfoNES_MessageBox( "Sound Card is needed to execute this application." );
    20         exit(-1);
    21     }
    22 
    23   // set cooperative level
    24 #if 1
    25     //设置属性不重要
    26     ret = lpdirsnd->SetCooperativeLevel(hwnd, DSSCL_PRIORITY);
    27 #else
    28     ret = lpdirsnd->SetCooperativeLevel( hwnd, DSSCL_NORMAL );
    29 #endif
    30 
    31     if ( ret != DS_OK )
    32     {
    33     InfoNES_MessageBox( "SetCooperativeLevel() Failed." );
    34         exit(-1);
    35     }
    36 }

     SoundOpen

     1 WORD DIRSOUND::AllocChannel(void)
     2 {
     3     WORD x;
     4     
     5     //判断 lpdsb 找到一个 为空的 这里应该返回0
     6     for (x=0;x<ds_NUMCHANNELS;x++)
     7     {
     8         if (lpdsb[x] == NULL)
     9         {
    10             break;
    11         }
    12     }
    13 
    14     if ( x == ds_NUMCHANNELS )
    15     {
    16     /* No available channel */
    17     InfoNES_MessageBox( "AllocChannel() Failed." );
    18         exit(-1);         
    19     }
    20 
    21     return (x);
    22 }
    23 
    24 void DIRSOUND::CreateBuffer(WORD channel)
    25 {
    26     DSBUFFERDESC dsbdesc; //SoundBuffer 描述
    27     PCMWAVEFORMAT pcmwf;  //wav fmt 格式描述
    28     HRESULT hr;
    29 
    30     //清0
    31     memset(&pcmwf, 0, sizeof(PCMWAVEFORMAT));
    32     //pcm 格式
    33     pcmwf.wf.wFormatTag          = WAVE_FORMAT_PCM;
    34     //1个声道
    35     pcmwf.wf.nChannels             = ds_CHANSPERSAMPLE;
    36     //采样率 44100
    37     pcmwf.wf.nSamplesPerSec  = ds_SAMPLERATE;
    38     //对齐 采样率 / 8 * 声道数 = 44100 / 8 * 1 = 5512.5
    39     pcmwf.wf.nBlockAlign         = ds_CHANSPERSAMPLE * ds_BITSPERSAMPLE / 8;
    40     //缓存区大小 44100*5512.5 = 243101250
    41     pcmwf.wf.nAvgBytesPerSec = pcmwf.wf.nSamplesPerSec * pcmwf.wf.nBlockAlign;
    42     //8位 声音
    43     pcmwf.wBitsPerSample         = ds_BITSPERSAMPLE;
    44 
    45     //清0
    46     memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
    47     dsbdesc.dwSize                = sizeof(DSBUFFERDESC);
    48     dsbdesc.dwFlags             = 0;
    49     //缓存大小 735 * 15 = 11025
    50     dsbdesc.dwBufferBytes = len[channel]*Loops;
    51     dsbdesc.lpwfxFormat     = (LPWAVEFORMATEX)&pcmwf;
    52 
    53     hr = lpdirsnd->CreateSoundBuffer(&dsbdesc, &lpdsb[channel], NULL);
    54 
    55     if (hr != DS_OK)
    56     {
    57         InfoNES_MessageBox( "CreateSoundBuffer() Failed." );
    58         exit(-1);
    59     }
    60 }
    61 
    62 //samples_per_sync = 735 sample_rate = 44100
    63 BOOL DIRSOUND::SoundOpen(int samples_per_sync, int sample_rate)
    64 {
    65     //ch 1 WORD unsigned short 类型 , 创建一个 通道 , 返回 第0 个 SoundBuffer
    66     ch1 = AllocChannel();
    67     
    68     /** 
    69     *   参数定义
    70     *    BYTE     *sound[ds_NUMCHANNELS];
    71     *    DWORD   len[ds_NUMCHANNELS];
    72     */
    73     //申请了一个 735 大小的 Byte
    74     sound[ch1] = new BYTE[ samples_per_sync ];
    75     //记录了 大小 735
    76     len[ch1]     = samples_per_sync;
    77 
    78     if ( sound[ch1] == NULL )
    79     {
    80         InfoNES_MessageBox( "new BYTE[] Failed." );
    81         exit(-1);
    82     }
    83     
    84     //创建缓存区
    85     CreateBuffer( ch1 );
    86 
    87     /* Clear buffer */
    88     FillMemory( sound[ch1], len[ch1], 0 ); 
    89     //执行15次
    90     for ( int i = 0; i < Loops; i++ )
    91         SoundOutput( len[ch1], sound[ch1] ); 
    92 
    93     /* Begin to play sound */
    94     Start( ch1, TRUE );
    95 
    96   return TRUE;
    97 }

    SoundOutput

     1 //初始化时 执行 samples:735 wave:NULL
     2 BOOL DIRSOUND::SoundOutput(int samples, BYTE *wave)
     3 {
     4     /* Buffering sound data */
     5     //将 wave 复制到 sound
     6     CopyMemory( sound[ ch1 ], wave, samples );  
     7 
     8     /* Copying to sound data buffer */
     9     FillBuffer( ch1 );  
    10 
    11     /* Play if Counter reaches buffer edge */
    12     //初始化时 iCnt:15 Loops:20
    13     if ( Loops == ++iCnt )
    14     {
    15         iCnt = 0;
    16     }
    17     //这里 iCnt = 16
    18     return TRUE;
    19 }
    20 void DIRSOUND::FillBuffer( WORD channel )
    21 {
    22     LPVOID write1;
    23     DWORD length1;
    24     LPVOID write2;
    25     DWORD length2;
    26     HRESULT hr;
    27 
    28     //得到要写入的地址
    29     hr = lpdsb[channel]->Lock( iCnt * len[channel], len[channel], &write1, &length1, &write2, &length2, 0 );
    30 
    31     //如果返回DSERR_BUFFERLOST,释放并重试锁定
    32     if (hr == DSERR_BUFFERLOST)
    33     {
    34         lpdsb[channel]->Restore();
    35 
    36         hr = lpdsb[channel]->Lock( iCnt * len[channel], len[channel], &write1, &length1, &write2, &length2, 0 );
    37     }
    38 
    39     if (hr != DS_OK)
    40     {
    41         InfoNES_MessageBox( "Lock() Failed." );
    42         exit(-1);
    43     }
    44 
    45     //写入数据
    46     CopyMemory( write1, sound[channel], length1 );
    47 
    48     if (write2 != NULL)
    49     {
    50         CopyMemory(write2, sound[channel] + length1, length2);
    51     }
    52     //解锁
    53     hr = lpdsb[channel]->Unlock(write1, length1, write2, length2);
    54 
    55     if (hr != DS_OK)
    56     {
    57         InfoNES_MessageBox( "Unlock() Failed." );
    58         exit(-1);
    59     }
    60 }

    Play

     1 //初始化时 ch1 重复播放
     2 void DIRSOUND::Start(WORD channel, BOOL looping)
     3 {
     4     HRESULT hr;
     5 
     6     hr = lpdsb[channel]->Play( 0, 0, looping == TRUE ? DSBPLAY_LOOPING : 0 );
     7 
     8     if ( hr != DS_OK )
     9     {
    10         InfoNES_MessageBox( "Play() Failed." );
    11         exit(-1);
    12     }
    13 }

     播放调用

     1 void InfoNES_SoundOutput( int samples, BYTE *wave1, BYTE *wave2, BYTE *wave3, BYTE *wave4, BYTE *wave5 ) 
     2 {
     3     //rec_freq = 735
     4     BYTE wave[ rec_freq ];
     5     //取了 wave1~5 的平均值
     6     for ( int i = 0; i < rec_freq; i++)
     7     {
     8         wave[i] = ( wave1[i] + wave2[i] + wave3[i] + wave4[i] + wave5[i] ) / 5;
     9     }
    10 #if 1
    11     if (!lpSndDevice->SoundOutput( samples, wave ) )
    12 #else
    13     if (!lpSndDevice->SoundOutput( samples, wave3 ) )
    14 #endif
    15     {
    16         InfoNES_MessageBox( "SoundOutput() Failed." );
    17         exit(0);
    18     }
    19 }

    最后总结得到几个有用的参数:

    声道数 1

    采样率 44100

    采样位数 8

    每次播放块大小(NES  APU 每次生成一块)735

    更新 2018-11-04 

    已移值到 alsa-lib 支持,播放正常,已更新至 github 。 可以在 置顶博文中找地址。

  • 相关阅读:
    ES5 创建构造函数的私有属性
    js 触发打印操作
    创建 React 项目
    处理因使用 BigInt 等最新语法时 ts 编译报错
    TS 查找第三方声明文件
    Git 撤销工作区中的变动
    Git 查看文件修改状态
    Git 查看用户名和 Email
    查看某个 npm 包的所有发行版版本号,比如 vue
    Git 查看文件修改详情
  • 原文地址:https://www.cnblogs.com/ningci/p/6863510.html
Copyright © 2011-2022 走看看