当前位置: 首页 > 编程日记 > 正文

【VS开发】【智能语音处理】Windows下麦克风语音采集

简介

这是我很早以前的大学毕业设计,忽然间找到贴出来以纪念自己的纯真年代...但是因为CSDN不给面子所以导致短短的一篇文章贴了足足7次..他老提时说文章超过了64K,老大,拜托,那是算上了里面的图片大小吧...:-(

本文简单介绍了声卡的工作原理 , 录音的原理以及数字音频的基本知识并且利用 Windows 提供的 Waveform Aduio APIs 以及 Multimedia File I/O APIs 实现一个 Windows 环境下的麦克风录音以及将录音文件保存成 .wav 文件的简单系统 .

关键字

Waveform Aduio APIs, Multimedia File I/O APIs,waveInXXX,

mmioXXX, 麦克风 , 录音 , 波形文件 ,VC6++

要深入的了解麦克风录音的实现我们必须了解声卡的工作原理 ,麦克风录音的原理以及了解相关的编程接口,下面我们将慢慢道来…

1. 声卡的工作原理

声卡的工作原理其实很简单 ,我们知道,麦克风和喇叭所用的都是模拟信号,而电脑所能处理的都是数字信号,两者不能混用,声卡的作用就是实现两者的转换。从结构上分,声卡可分为模数转换电路和数模转换电路两部分,模数转换电路负责将麦克风等声音输入设备采到的模拟声音信号转换为电脑能处理的数字信号,而数模转换电路负责将电脑使用的数字声音信号转换为喇叭等设备能使用的模拟信号,就这么简单 。
CSDN_Dev_Image_2004-3-31505260.gif 
上图就是一块典型的声卡 , Mic 插口 用于连接麦克风 , 通过它可以录制外界的声音

2. 数字音频基础知识

麦克风录音的过程其实就是将模拟信号转化成数字信号的过程 , 其中涉及的一些概念如下 :

 1. 采样率 (Sampling Rate)

采样率指声卡在一秒之中对声音 ( 波形 ) 作记录的次数 , 根据研究声音播出时的质量常常只能达到采样率的一半 , 因此必须采取双倍的采样率才能将声音标准重现 . 也就是只要采样率大于原始信号频率的两倍以上即可减低错误 , 达到和原始声音差不多的质量 . 人的听力大概是 20KHZ, 所以高品质的采样率应为其两倍以上 .

当声音来源为音乐时 , 因为它所横跨的频率变化极为宽广 , 通常以 44.1KHZ 的频率为 CD 音乐采样率的标准 . 但是若以语言为主由于人说话的语音大概是 10KHZ, 因此加倍采样 , 只取 22KHZ 即可 , 采样率越高所记录下来的音质就越清晰 , 当然 , 越高的采样所记录下的文件就越大 .

2. 采样位

解析度决定了采样的音波是否能保持原来的形状 , 越接近原型则需解析度越高 , 若以 8 位来采样的话其能表达的组合种类是 2 的 8 次方 , 即 256, 表示用 8 位的采样大小能分辨出 256 个层次的声音 , 若用 16 位来采样 , 则能分辨的差异将高达 2 的 16 次方 , 为 65536, 其精度自然大为提高 .16 位 ,8 位采样的差别在于动态范围的宽窄 , 动态范围宽广 , 音量起伏的大小变化就能够更精细的被记录下来 , 如此一来不论是细微的声音或是强烈的动感震撼 , 都可以表现的淋漓尽致 , 而 CD 音质的采样规格正式 16 位采样的规格 .

3. 量化误差 (Quantization error)

在采样的过程中 , 不断连续变化的模拟信号要用数字化的数值来表示 , 这样的过程就会发生所谓的量化误差 (Quantization error). 所谓的量化误差指的是实际的信号的振幅 (smplitude) 和数字化之后所的数字之间的差异 . 如果用将数字信号还原成模拟信号的角度看 , 量化误差就是失真 (Distortion). 我们可以用增加采样大小的方式来降低量化误差 , 也就是更多的位 (bits) 来表示一个采样信号 , 这样可以提高精度 .

 4. 量化 (Quantization), 线性量化法 (Linear quantization) 和非线性量化法 (Nonlinear quantization)

所谓的量化 (Quantization) 就是将模拟信号所代表的连续范围分成一段一段的区间 (Interval), 每一段区间我们定义一个数字化的值 . 区间的数目是跟采样大小有关 , 举例来说 , 有一种最简单的量化法称为 ” 线性量化法 ”(Linear quantization), 这种量化法采用等距离的间隔空间 , 架设一个讯号它的最大值是 5.0, 采样大小为 3 位 , 则每个量化区间就时 5.0/2^3, 也就是 0.625 单位 . 另外一种相反的量化方法就是 ” 非线性量化法 ”(Nonlinear quantization), 这种量化法采用不同的间隔空间 . 以 ” 对数量化法 ”(Logarithm quantization) 为例 . 低振幅范围的量化区间就比高振幅的范围的区间较为接近 , 用这种量化的法产生的结果就是在低振幅时我们会得到佳好的效果 . 通常如果使用同样的采样大小 , 非线性量化法会比线性量化法得到更好的声音品质 . 但是如果是要对声音做滤波 (filtered) 或一些运算的时候 , 使用线性量化法会比较容易处理 .

 5. 声音强度

波形振幅的平方.两个声音强度上的差常以分贝 (db) 为单位来度量 ,计算公式如下:

20*log(A1/A2) 分贝 , A1,A2 为两个声音的振幅 .

a. 如果采样大小为 8 位 ,则采样的动态范围为 20*log(256) 分贝 =48db;

b. 如果样本大小为 16 位 ,则采样动态范围为 20*log(65536) 大约是 96 分贝 ,接近了人听觉极限和痛苦极限,是再线音乐的理想范围, windows 同时支持8 位和 16 位的采样大小 .

 6. 音频编码方法

目前已经发展了许多音频编码的方法用以减少存储量或是传输的时间 , 以下所列为两种较普遍的编码方法 :

a.PCM(Pulse code modulation);

脉冲编码调制 ,即对波形按照固定周期频率采样。为了保证采样后数据质量,采样频率必须是样本声音最高频率的两倍,这就是 Nyquist 频率 .

b.ADPCM(Adaptive delta pulse modulation).

3. RIFF 文件结构和 WAVE 文件格式 

   Windows 支持两种 RIFF(Resource Interchange File Format," 资源交互文件格式 ") 格式的音频文件: MIDI 的 RMID 文件和波形音频文件格式WAVE 文件,其中在计算机领域最常用的数字化声音文件格式是后者,它是微软专门为 Windows 系统定义的波形文件格式( Waveform Audio ),由于其扩展名为 "*.wav" ,因而该类文件也被称为 WAVE 文件。 为了突出重点,有的放矢,本文涉及到的声音文件所指的就是 WAVE 文件。常见的 WAVE 语音文件主要有两种,分别对应于单声道( 11.025KHz 采样率、 8Bit 的采样值)和双声道( 44.1KHz 采样率、 16Bit 的采样值)。这里的采样率是指声音信号在进行 " 模→数 " 转换过程中单位时间内采样的次数。采样值是指每一次采样周期内声音模拟信号的积分值。对于单声道声音文件,采样数据为八位的短整数(short int 00H-FFH );而对于双声道立体声声音文件,每次采样数据为一个 16 位的整数( int ),高八位和低八位分别代表左右两个声道。 WAVE 文件数据块包含以脉冲编码调制( PCM )格式表示的样本。在进行声音编程处理以前,首先让我们来了解一下 RIFF 文件和 WAVE 文件格式。 

   RIFF 文件结构可以看作是树状结构,其基本构成是称为 " 块 " ( Chunk )的单元,每个块有 " 标志符 " 、 " 数据大小 " 及 " 数据 " 所组成,块的结构如图 2 所示:

块的标志符( 4BYTES )

数据大小 ( 4BYTES )

数据

图 2 

  从上图可以看出,其中 " 标志符 " 为 4 个字符所组成的代码,如 "RIFF" , "LIST" 等,指定块的标志 ID ;数据大小用来指定块的数据域大小,它的尺寸也为 4 个字符;数据用来描述具体的声音信号,它可以由若干个子块构成,一般情况下块与块是平行的,不能相互嵌套,但是有两种类型的块可以嵌套子块,他们是 "RIFF" 或 "LIST" 标志的块,其中 RIFF 块的级别最高,它可以包括 LIST 块。另外, RIFF 块和 LIST 块与其他块不同, RIFF 块的数据总是以一个指定文件中数据存储格式的四个字符码(称为格式类型)开始,如 WAVE 文件有一个 "WAVE" 的格式类型。 LIST 块的数据总是以一个指定列表内容的 4 个字符码(称为列表类型)开始,例如扩展名为 ".AVI" 的视频文件就有一个 "strl" 的列表类型。 RIFF 和 LIST 的块结构如下:

RIFF/LIST 标志符

数据 1 大小

数据 1

格式 / 列表类型

数据

图 3 

   WAVE 文件是非常简单的一种 RIFF 文件,它的格式类型为 "WAVE" 。 RIFF 块包含两个子块,这两个子块的 ID 分别是 "fmt" 和 "data", 其中 "fmt" 子块由结构 PCMWAVEFORMAT 所组成,其子块的大小就是 sizeofof(PCMWAVEFORMAT), 数据组成就是 PCMWAVEFORMAT 结构中的数据。 WAVE 文件的结构如下图 4 所示:

标志符( RIFF )

数据大小

格式类型( "WAVE" )

"fmt"

Sizeof(PCMWAVEFORMAT)

PCMWAVEFORMAT

"data"

声音数据大小

声音数据

图 4 

   PCMWAVEFORMAT 结构定义如下:

typedef struct
{
     WAVEFORMAT wf;         // 波形格式 , 前面已经提过了 ; 
     WORD wBitsPerSample;// WAVE 文件的采样大小; 
}PCMWAVEFORMAT
 "data"子块包含WAVE文件的数字化波形声音数据,其存放格式依赖于"fmt"子块中wFormatTag成员指定的格式种类,在多声道WAVE文件中,样本是交替出现的。如16bit的单声道WAVE文件和双声道WAVE文件的数据采样格式分别如图5所示:

  16位单声道:

采样一

采样二

……

低字节

高字节

低字节

高字节

……


   16 位双声道:

采样一 ……

左声道

右声道

……

低字节

高字节

低字节

高字节

……


                             图 5

4. 硬件抽象层 ( HAL, Hardware Abstraction Layer )

HAL 是一个可加载的核心模块 (HAL.dll) ,它为运行在 Windows NT 架构 ( 包括 WindowsNT4.0,Windows2000,WindowsXP) 上 的硬件平台提供低级接口 , HAL 隐藏各种与硬件有关的细节 ,例如: I/O 接口 ,中断控制器,声卡…这样的话如果用户需要访问声卡硬件的话只能通过该声卡的驱动程序来实现,声卡驱动程序再调用 HAL 中的相应例程来实现 ,下图显示了 HAL ,声卡驱动程序, Waveform Audio APIs ,我们的麦克录音程序之间的关系:

5. Waveform Audio

Waveform Audio APIs 是 Microsoft 提供给广大 Win32 程序员用来给自己的应用程序添加声音支持的一套强大的 API, 它提供的功能如下:

1. 打开 / 关闭 / 查询声音设备 ;

2. 播放波形文件 ;

3. 设置播放速度 ;

4. 播放进度控制 ;

5. 录音 ;

6. 得到当前的播放位置 ;

7. 调节音量 .

下面简单介绍一下这套 API 提供的主要函数 :

  • 打开录音设备函数

MMRESULT waveInOpen(

LPHWAVEIN phwi,                 // 输入设备句柄

UINT uDeviceID,                   // 输入设备 ID

LPWAVEFORMATEX pwfx,       // 录音格式指针

DWORD dwCallback,              // 处理 MM_WIM_*** 消息的回调函数或窗

// 口句柄,线程 ID

DWORD dwCallbackInstance,

DWORD fdwOpen                  // 处理消息方式的符号位

);

  • 为录音设备准备缓存函数

MMRESULT waveInPrepareHeader(  HWAVEIN hwi,

LPWAVEHDR pwh,

UINT bwh );

  • 给输入设备增加一个缓存

MMRESULT waveInAddBuffer(  HWAVEIN hwi,

LPWAVEHDR pwh,

UINT cbwh );

  • 开始录音

MMRESULT waveInStart(  HWAVEIN hwi  );

  • 清除缓存

MMRESULT waveInUnprepareHeader( HWAVEIN hwi,

LPWAVEHDR pwh,

UINT cbwh);

  • 停止录音

MMRESULT waveInReset( HWAVEIN hwi );

  • 关闭录音设备

MMRESULT waveInClose( HWAVEIN hwi );

  • Wave_audio 数据格式

typedef struct {

WORD  wFormatTag; // 数据格式,一般为 WAVE_FORMAT_PCM 即

// 脉冲编码

WORD  nChannels;     // 声道

DWORD nSamplesPerSec;   // 采样频率

DWORD nAvgBytesPerSec;  // 每秒数据量

WORD  nBlockAlign;

WORD  wBitsPerSample;     // 样本大小

WORD  cbSize;

} WAVEFORMATEX;

  • waveform-audio 缓存格式

typedef struct {

LPSTR  lpData;                    // 内存指针

DWORD  dwBufferLength;    // 长度

DWORD  dwBytesRecorded; // 已录音的字节长度

DWORD  dwUser;

DWORD  dwFlags;

DWORD  dwLoops;              // 循环次数

struct wavehdr_tag * lpNext;

DWORD  reserved;

} WAVEHDR;

  • 相关消息

MM_WIM_OPEN: 打开设备时消息,在此期间我们可以进行一些初始化工作

MM_WIM_DATA: 当缓存已满或者停止录音时的消息,处理这个消息可以对

缓存进行重新分配,实现不限长度录音

MM_WIM_CLOSE :关闭录音设备时的消息。

5. Multimedia File I/O

Multimedia File I/O APIs 是 Microsoft 提供的另外一套强大的针对媒体文件 I/O 的 API, 我们知道许多像 MediaPlay,RealOne 这样的多媒体程序对媒体文件的读写性能要求很高 , 它们几乎要求实时的将磁盘上的媒体文件以流的形式读入 , 但是对于一般的文件 I/O 形式如图 1:
CSDN_Dev_Image_2004-3-31514310.jpg
1.文件从磁盘上被读入操作系统的File I/O的buffer;

2. 然后拷贝到应用程序自己的 buffer 中 ;

3. 应用程序这时候才能读取文件内容 .

上述的过程对于多媒体应用程序来说是低效的而且浪费宝贵的内存资源,如果文件和大的话势必还要采取分段读取等机制, Multimedia File I/O 采取了一种直接存取机制 ( 如图 2), 使得应用程序可以直接读取操作系统的 File I/O buffer, 大大提高了效率 . 后面我们会利用此套 API实现录音文件的存储 .
CSDN_Dev_Image_2004-3-31514312.jpg 

6. 麦克录音系统简介

本文实现的麦克录音系统将具备以下功能 :

1. 录制用户通过麦克风发出的声音 ;

这将利用到 Waveform APIs, 流程如下 :

a. 打开录音设备 waveInOpen;

b. 准备 wave 数据头 waveInPrepareHeader;

c. 准备数据块 waveInAddBuffer;

d. 开始录音 waveInStart;

e. 停止录音 (waveInReset);

f. 关闭录音设备 (waveInClose);

g. 当开始录音后当 buffer 已满时 , 将收到 MM_WIM_DATA 消息 , 处理该

消息可以保存已录好数据 .

2. 根据用户的声音强弱动态显示声音波形 ;

这主要通过 GDI 函数来实现 .

3. 将用户通过麦克风发出的声音录制成 wav 文件保存 .

这将利用到 Multimedia file I/O APIs.

a .调用 mminoOpen 函数来打开 WAVE 文件 , 获取 HMMIO 类型的文件句柄 ; 
              b .根据 WAVE 文件的结构 , 调用 mmioRead 、 mmioWrite 和 mmioSeek 函数实现文件的读、写和定位操作 ; 
               c .调用 mmioClose 函数来关闭 WAVE 文件 .

7. 麦克录音系统的实现 (MicDemo)

下面是该系统的界面 :
CSDN_Dev_Image_2004-3-31517320.jpg 

 对于录音来说最重要的就是CSoundIn类,下面就是该类的定义:

 namespace  perdubug  {   //  prevent the name-space pollution  
 

 class  CSoundIn  

 {

 public :    

                BOOL     __initMic();  //  get the best wave format supported by your sound card

                                                    
 //  and then i will use the format to capture sound. 
 

 

                 void      __closeMic();

 

                

                BOOL     __openMic();  //  open device and begin to capture with the best format(when 

                                                       
 //  invoke __initMic function then you will get the best format

                                                       
 //  supported by host's sound card

                

                
 // 
 
                
 //  if your want to capture sound and export into a wav file please invoke this function

                
 //  to tell me the full path then i will create the wav file.

                
 //
 
                 void      __createOutputWaveFile( const  TCHAR  *  lpszFileName);

                

                 //  if you invoke any member function return error/false please

                
 //  use this function to get the result 
 

                DWORD    __getLastError();

 

                 // 
 
                
 //  when the capture buffer is filled please invoke this function to 'add buffer'(Actually

                
 //  you should create two-circular buffers,when 1st buffer is filled then switch to 2st,1st

                
 //  buffer will be wrote into wav file.

                
 //
 
                 void  AddBuffer();

                

                 virtual   ~ CSoundIn();

                

                friend CSoundIn  &  theSoundCapture();

 private :

 

                BOOL  GetBestWaveFormat(WAVEFORMATEX  &  waveFormatEx);

                

                 //  because sound card is one and only so i must limit the number of CSoundIn object,

                
 //  but how to limit the class object nums?maybe put constructor into private scope is

                
 //  a good idea,:-) 
 

                CSoundIn();          

 

 private :

                WAVEINCAPS                    m_WaveInDevCaps;

                HWAVEIN                            m_WaveIn;

                WAVEHDR                           m_WaveHeader;

                WAVEFORMATEX            m_WaveFormat;  

                

                UINT    m_WaveInSampleRate;

                 int          m_NbMaxSamples;

                UINT    m_SizeRecord;

 

                DWORD   m_dwLastError;

 

                 enum   { MAX_SIZE_INPUT_BUFFER  =   1   *   2   *   1024  } ;  //  samples * voie * size_samples 
 

 

 public :

                SHORT  InputBuffer[MAX_SIZE_INPUT_BUFFER];    //  used for int FFT,many GUI application 

                                                                                                          
 //  want to display sound peak so.. 
 

                BOOL   m_bTerminateThread;                    //   to 'kill' waveCallback function 
 

                BOOL   m_bImportToWaveFile;

                

                CWaveFile       m_waveFile;

;

 

  //  end namespace perdubug 
 

 

对于将录音保存在WAV文件的工作主要是由CwaveFile类来完成.下面是该类的定义:

 // 
 
 
//  Encapsulates reading or writing sound data to or from a wave file

 // ----------------------------------------------------------------------------- 
 

 class  CWaveFile

 {

 public :

    WAVEFORMATEX *  m_pwfx;         //  Pointer to WAVEFORMATEX structure 
 

    HMMIO              m_hmmio;               //  MM I/O handle for the WAVE 
 

    MMCKINFO      m_ck;                       //  Multimedia RIFF chunk 
 

    MMCKINFO      m_ckRiff;                //  Use in opening a WAVE file 
 

    DWORD             m_dwSize;              //  The size of the wave file 
 

    MMIOINFO       m_mmioinfoOut;

    DWORD             m_dwFlags;

    BOOL                 m_bIsReadingFromMemory;

    BYTE *                m_pbData;

    BYTE *                m_pbDataCur;

    ULONG              m_ulDataSize;

    CHAR *               m_pResourceBuffer;    

 protected :

    HRESULT ReadMMIO();

    HRESULT WriteMMIO( WAVEFORMATEX  * pwfxDest );

 

 public :

    CWaveFile();

     ~ CWaveFile();

 

    HRESULT Open( LPCTSTR strFileName, WAVEFORMATEX *  pwfx, DWORD dwFlags );

    HRESULT OpenFromMemory( BYTE *  pbData, ULONG ulDataSize, 

                                                        WAVEFORMATEX *  pwfx, DWORD dwFlags );

    HRESULT Close();

 

    HRESULT Read( BYTE *  pBuffer, DWORD dwSizeToRead, DWORD *  pdwSizeRead );

    HRESULT Write( UINT nSizeToWrite, BYTE *  pbData, UINT *  pnSizeWrote );

 

    DWORD   GetSize();

    HRESULT ResetFile();

    WAVEFORMATEX *  GetFormat()  {  return  m_pwfx; } ;

;

 
 我们有了这两个强有力的类的支持就可以开始我们的编程工作了….

    1 .用VC6 ++ 建立一个MFC基于对话框的工程:MicDemo;

    2 .添加我们的两个类CSoundIn,CwaveFile;        

    3 .当我们点击开始(Start)按钮的时候我们就要开始录音了…

 

          void  CMicDemoDlg::OnStart() 

         {

                m_btnStart.EnableWindow(FALSE);                

                 if (theSoundCapture().__initMic())

                 {

                      m_filePath.SetWindowText(_T( " yangchen.wav. " ));         

                      theSoundCapture().__createOutputWaveFile(_T( " yangchen.wav " ));                     

                      if ( ! theSoundCapture().__openMic())

                       {

                        ::MessageBox( this -> m_hWnd,_T( " Can not open microphone! " ), _T( " Error " ),MB_OK | MB_ICONERROR);

                          return ;

                      } 

 
                } 

 
                

                m_btnStop.EnableWindow(TRUE);

              

               //  设置定时器是为了画波形用的       
 

                 SetTimer( 1 ,  200   /* start slow */ , NULL);

           } 

 
  

     4 .在定时器的回调函数中画波形.

            void  CMicDemoDlg::OnTimer(UINT nIDEvent) 

             {

                     if (nIDEvent  ==   1 )

                    {           

                                 static   const   int  xCon  =   13 ;

                                 static   const   int  yCon  =   13 ;

                                 static   const   int  wCon  =   623 ;

                                 static   const   int  hCon  =   80 ;

                                

                                CClientDC dc( this );

                                

                                CBitmap   Bitmap;

                                CBitmap  *  pbmOld  =  NULL;                              

                                CDC         dcMem;

                                

                                dcMem.CreateCompatibleDC( & dc);                 

                                Bitmap.CreateCompatibleBitmap( & dc,wCon,hCon);

                                

                                pbmOld  =  dcMem.SelectObject( & Bitmap);

                                

                                dcMem.PatBlt( 0 , 0 ,wCon,hCon, WHITENESS);                              

                                dcMem.MoveTo( 0 ,hCon / 2 );

                                

                                 // 
 
                                
 //  display incomming signal--key idea!

                                
 //
 
                                 for ( int  x  = 0  ; x  <  wCon; x ++ )   //  display Input 
 

                                  {

                                          dcMem.LineTo(x,(hCon  >>   1 )  -  (theSoundCapture().InputBuffer[x]  >>   7 ));

                                } 

 
                                

                                dc.BitBlt(xCon,yCon,wCon,hCon, & dcMem,  0 ,  0 , SRCCOPY);

                                

                                dcMem.SelectObject(pbmOld);

                                dcMem.DeleteDC();             

                  } 

 
                   else 
 
                         CDialog::OnTimer(nIDEvent);

            } 

 
       

       5 .点击停止(Stop)按钮的时候停止录音和写WAV文件

                   void  CMicDemoDlg::OnStop() 

                  {

                        m_btnStop.EnableWindow(FALSE); 

                        theSoundCapture().__closeMic(); 

                        m_btnStart.EnableWindow(TRUE);

                 } 

 
看完整段代码你可能会很奇怪怎么在CmicDemoDlg中居然都没有定义一个CSoundIn对象 ?? 呵呵,原因很简单,因为设备的独占性所以在一个时刻只能有一个CSoundIn对象存在(因为CSoundIn对象需要占据录音设备),所以我们必须限制程序员生成CSoundIn对象的数量,怎么限制呢 ? 那就是把CSoundIn的构造函数放在private区域里面:

   

      private :

                BOOL  GetBestWaveFormat(WAVEFORMATEX  &  waveFormatEx);

                

                 //  because sound card is one and only so i must limit the number of CSoundIn object,

                
 //  but how to limit the class object nums?maybe put constructor into private scope is

                
 //  a good idea,:-) 
 

        CSoundIn();          

 

这样的话就根本无法声明一个CSoundIn对象,不信你试一下在你的代码中写上:

     CSoundIn  soundInObj;

 能编译通过吗 ? 肯定是不能,那如何调用CSoundIn的成员函数呢 ? 答案是通过一个全局函数:

 

               //  global function,:-(

              
 //  client can only through this function to use CSoundIn object 
 

              CSoundIn  &  theSoundCapture()

               {

                      static  CSoundIn p;

                      return  p;

               } 

 
 

  这时候你应该明白了为什么上面的代码中调用CSoundIn的成员函数的时候都是用theSoundCapture来做的原因了吧.

转载于:https://www.cnblogs.com/huty/p/8518458.html

相关文章:

iOS音频——AudioToolbox

一、前言 二、音频文件Audio File Services 三、音频文件转换Extended Audio File Services 四、音频流Audio File Stream Services 五、音频队列Audio Queue Services 一、前言 AudioToolbox提供的API主要是C 使用起来相对晦涩&#xff0c;针对本文提供了简单的代码示例减小学…

【WA】九度OJ题目1435:迷瘴

题目描述&#xff1a; 通过悬崖的yifenfei&#xff0c;又面临着幽谷的考验——幽谷周围瘴气弥漫&#xff0c;静的可怕&#xff0c;隐约可见地上堆满了骷髅。由于此处长年不见天日&#xff0c;导致空气中布满了毒素&#xff0c;一旦吸入体内&#xff0c;便会全身溃烂而死。幸好y…

云端应用SQL注入攻击

实验目的及要求&#xff1a; 完成VMware Workstations14平台安装&#xff0c;会应用相关操作&#xff1b;完成Windows server2008R2操作系统及Kali Linux操作系统的安装&#xff1b;掌握SQLmap攻击工具的使用&#xff1b;使用SQLmap对目标站点进行渗透攻击&#xff1b; 实验环…

SPOJ375(树链剖分)

题目&#xff1a;Query on a tree 题意&#xff1a;给定一棵树&#xff0c;告诉了每条边的权值&#xff0c;然后给出两种操作&#xff1a; (1)把第i条边的权值改为val (2)询问a&#xff0c;b路径上权值最大的边 分析&#xff1a;本题与HDU3966差不多&#xff0c;区别就是&#…

简单的python服务器程序

一个接受telnet输入的服务器端小程序 #!/usr/local/bin/python3.5 #coding:utf-8 import sockethost port 51423s socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind((host, port)) s.listen(1)print(S…

iOS:一句代码实现文本输入的限制

前言 实际开发中&#xff0c;往往需要处理UITextView、UITextField输入的限制。比如输入必须是价格格式&#xff08;一个小数点、小数点后面最多两位&#xff09;&#xff1b;输入最大长度限制&#xff1b;对输入内容的实时回调。处理这些的时候&#xff0c;我们通常需要做一些…

Linux操作系统(一:基本操作)

1、创建一个以自己姓名拼音简写一致的用户名&#xff08; useradd -d /home/abc abc&#xff09; 2、在linux中使用打印命令&#xff0c;在命令行中输入“当前用户名Hello world !” 3、显示现在天年月日&#xff0c;显示后一天的日期&#xff0c;显示上一月日期&#xff0c;显…

Core Text 学习笔记-基础

前言 最近在学习YYKit框架&#xff0c;看到关于CoreText相关的知识的时候感到非常吃力&#xff0c;于是乎就恶补了一下Core Text相关的基础知识。 Glyphs&#xff08;字形&#xff09; 字符的图形形式&#xff0c; 则是文字中字母 (character) 的视觉表现。&#xff08;字形&am…

虚拟机下CentOS 6.5配置IP地址的三种方法

实验软件环境&#xff1a;虚拟机Vmware Workstation10.0 、CentOS 6.5 32位 1、自动获取IP地址 虚拟机使用桥接模式&#xff0c;相当于连接到物理机的网络里&#xff0c;物理机网络有DHCP服务器自动分配IP地址。 dhclient 自动获取ip地址命令 ifconfig 查询系统里网卡信息&…

GIS 相关知识扫盲

1、什么是GIS GIS:地理信息系统&#xff0c;它是一种特定的十分重要的空间信息系统。它是在计算机硬、软件系统支持下&#xff0c;对整个或部分地球表层&#xff08;包括大气层&#xff09;空间中的有关地理分布数据进行采集、储存、管理、运算、分析、显示和描述的技术系统。2…

Linux操作系统(二:shell脚本)

练习一&#xff1a;编写shell脚本&#xff0c;计算1-100的和&#xff1b; 练习二&#xff1a;将一目录下所有的文件的扩展名改为bak 练习三&#xff1a;写一个脚本&#xff0c;统计。/etc/ 目录下共有多少个目录文件 练习四&#xff1a;写一个脚本&#xff0c;依次向/etc/passw…

用OpenGLES实现yuv420p视频播放界面

背景 例子TFLive这个项目里&#xff0c;是我按着ijkPlayer写的直播播放器&#xff0c;要运行需要编译ffmpeg的库,网盘里存了一份, 提取码&#xff1a;vjce。OpenGL ES播放相关的在在OpenGLES的文件夹里。 learnOpenGL学到会使用纹理就可以了。 播放视频&#xff0c;就是把画面一…

C++类的静态成员详细讲解

在C中&#xff0c;静态成员是属于整个类的而不是某个对象&#xff0c;静态成员变量只存储一份供所有对象共用。所以在所有对象中都可以共享它。使用静态成员变量实现多个对象之间的数据共享不会破坏隐藏的原则&#xff0c;保证了安全性还可以节省内存。 静态成员的定义或声明要…

jenkins2 multibranch

通过multibranch类型的pipeline job使得对于多个branch的支持更加简单。只需要创建一个multibranch job&#xff0c;jenkins将自动地为所有的branch创建job。 文章来自&#xff1a;http://www.ciandcd.com文中的代码来自可以从github下载&#xff1a; https://github.com/ciand…

Nagios的安装和基本配置(一:知识点总结及环境准备)

实验目的及要求 掌握Nagios监控的基本使用&#xff1b;掌握Nagios监控服务的搭建和配置&#xff1b; 实验环境&#xff1a; 1、满足实验要求的PC端&#xff1b; Host-name OS IP sofaware Nagios-server Centos7 192.168.1.119 Apache,php,Nagios,Nagios-plguins Nag…

浅谈Android四大组件之Service

一&#xff1a;Service简介 Android开发中&#xff0c;当需要创建在后台运行的程序的时候&#xff0c;就要使用到Service。 1:Service&#xff08;服务&#xff09;是一个没有用户界面的在后台运行执行耗时操作的应用组件。其他应用组件能够启动Service&#xff0c;并且当用户切…

使用 fastlane 实现 iOS 持续集成(二)

本文接上篇文章主要说下怎样使用 fastlane 上传到fir和蒲公英&#xff0c;下面先介绍下 plugin 命令。 plugin命令介绍: 列出所有可用插件 fastlane search_plugins 搜索指定名称的插件: fastlane search_plugins [query] 添加插件: fastlane add_plugin [name] 安装插件: fast…

Nagios的安装和基本配置(二:Nagios-Server的安装)

任务二、Nagios-server的安装 2.1、创建Nagios用户和组 注&#xff1a; #useradd Nagios -s /bin/nologin #groundadd nagcmd #usermod -a -G nagcmd Nagios #usermod -a G nagcmd apache 2.2、安装Nagios 2.2.1、上传软件包至操作系统中&#xff1b; 2.2.2、解压软件并…

shell编程-正则表达式

1.正则表达式是什么 它主要用于字符串的模式分割&#xff0c;匹配&#xff0c;查找及替换操作。 2、正则表达式与通配符 正则表达式用来在文件中匹配符合条件的字符串&#xff0c;正则包含匹配。grep,awk,sed等命令可以支持正则表达式。 通配符用来匹配符合条件的文件名&#x…

使用 CocoaPods 给微信集成 SDK 打印收发消息

推荐序 本文介绍的是一套逆向工具&#xff0c;可以在非越狱手机上给任意应用增加插件。在文末的示例中&#xff0c;作者拿微信举例&#xff0c;展示出在微信中打印收发消息的功能。 这套工具可以加快逆向开发的速度&#xff0c;其重签名思想也可以用于二次分发别人的应用。 其实…

数据库之子查询四(多重,表复制)

一、多重子查询 select teaID,teaName,age,sex,dept,professionfrom tteacherwhere dept(select dept from teaIDt103265)and profession(select professionfrom tteacherwhere teaIDt103265)这里的子查询就是为了从表中提取出有效信息参与外部查询二、create table 语句中子查…

Nagios的安装和基本配置(三:Nagios-Client的安装)

任务三、Nagios-Client的安装 3.1、关闭防火墙和selinux 注&#xff1a; #systemctl stop firewalld.service #systemctl disable firewalld.service #vi /etc/selinux/config 3.2、配置环境 #yum install gcc glibc-common -y #yum install gd gd-devel openssl openssl…

事件(待完成)

内容窗口 事件绑定​ 给整个浏览器 内容窗口区的事件绑定。 ​通过 document.documentElement或者document.body&#xff1f;似乎都可以。但最好是直接通过document document.addEventListener(mousemove,function () { });// 整个浏览器内容范围都将触发。拖动实现必用​ 转载…

iOS 模仿支付宝支付到账推送,播报钱数

最近申请了支付宝的二维码收钱码&#xff0c;其中支付宝有这么一个功能&#xff0c;就是&#xff0c;别人扫描你的二维码给你转账之后&#xff0c;收到钱会有一条语音推送&#xff0c;”支付宝到账 1000万“之类的推送消息&#xff0c;不管你的支付宝app有没有被杀死。 只要你的…

hdu - 4707 - Pet

题意&#xff1a;一棵N个结点(编号从0开始)的树&#xff0c;根结点为0&#xff0c;求到根结点的距离大于D的结点个数&#xff08;0 < 测试组数T < 10, 0<N<100000, 0<D<N&#xff09;。 题目链接&#xff1a;http://acm.hdu.edu.cn/showproblem.php?pid4707…

Nagios的安装和基本配置(四:调试验证 错误总结)

任务四、调试验证 4.1、验证连通性 在/usr/local/Nagios/etc/nrpe.cfg文件中server的ip地址 #vi /usr/local/Nagios/etc/nrpe.cfg #重启nrpe #pkill nrpe #netstat -Intp #/usr/local/Nagios/bin/nrpe -d -c /usr/local/Nagios/etc/nrpe.cfg #在server主机做验证 #cd /…

hitTest和pointInside方法

hittest方法 就是用来寻找最合适的view当一个事件传递给一个控件&#xff0c;就会调用这个控件的hitTest方法点击了白色的view&#xff1a; 触摸事件 -> UIApplication -> UIWindow 调用 [UIWindow hitTest] -> 白色view [WhteView hitTest] 实验1: 定义 BaseView&…

Github上的PHP资源汇总大全

依赖管理 ——用于依赖管理的包和框架 Composer/Packagist : 一个包和依赖管理器 Composer Installers: 一个多框架Composer库安装器 Pickle: 可以在任意平台上安装PHP扩展包 依赖管理的附加部分 ——其它依赖管理的相关工具 Satis : 静态的Composer库生成器 Composition: 一个…

UIButton长按事件

添加长按事件1 - (void)viewDidLoad2 {3 [super viewDidLoad];4 //Do any additional setup after loading the view, typically from a nib.5 6 UIButton *aBtn[UIButton buttonWithType:UIButtonTypeRoundedRect];7 [aBtn setFrame:CGRectMake(0, 10, 60, 60…

Hadoop集群搭建(一:集群安装及网络环境配置)

实验目的及要求 完成VMware workstations安装&#xff0c;会应用相关操作&#xff1b;完成虚拟机中Linux CentOS 7操作系统安装&#xff1b;完成静态网络地址的配置&#xff0c;所有主机的网络能够正常使用&#xff0c;相互之间能够正常连接&#xff1b;完成主机名配置&#x…