手工制作Wav文件以及生成播放数据

上一个帖子写了如何播放wave文件, 除了用已有资源之外, 还可以自己生成wave文件来播放, 因为wave文件很简单, 就是一个文件头, 剩下的都是PCM数据. 
这个帖子写写怎么生成wave文件, 或者干脆不要文件头, 直接生成播放内容.
生成wave文件很多种语言都有library与API, 这里为求简单, 自己定义一个文件头格式, 整个生成就是一个单cpp文件.
这里是一个wave文件头的参考:  http://www.topherlee.com/software/pcm-tut-wavformat.html
根据此文件设计文件头结构体如下:
  1. struct SimpleWavHdr
  2. {
  3.         FOURCC RiffHdr;        //"RIFF"
  4.                 
  5.         uint32_t ChunkSize;        //file size - 8

  6.         FOURCC WavHdr;//"WAVE"
  7.         FOURCC FmtHdr; //"fmt "
  8.     uint32_t HdrLen; //16, length of above

  9.         uint16_t DataType; //1->PCM
  10.         uint16_t ChanNo; //1 Channel
  11.                 
  12.         uint32_t SampleRate;//8000 Hz
  13.         uint32_t SamplePerSec; //8000 sample per second
  14.                 
  15.     uint16_t BytePerSample;//Bytes per sample
  16.         uint16_t BitsPerSample;//Bits per sample
  17.                 
  18.         FOURCC dataHdr;//"data"
  19.                 
  20.         uint32_t RawSize;//data size from this point
  21. };
复制代码
根据要生成的音频的采样率,声道,位长填写此结构体, 并放在wave文件的前面, 后面跟上PCM数据, 那么这个wave文件就做成了. 可以在任何设备的播放器播放以验证效果, 也可以按照上一个帖子的方法下载到板子上去播放.
先来生成一个600Hz的纯正弦波信号看看. 整个cpp代码也就这么点:
  1. #include <process.h>
  2. #include <windows.h>
  3. #include <mmsystem.h>

  4. #include <cstdint>
  5. #include <iostream>
  6. #include <vector>

  7. #define _USE_MATH_DEFINES
  8. #ifdef _MATH_DEFINES_DEFINED
  9. #undef _MATH_DEFINES_DEFINED
  10. #endif

  11. #include <math.h>

  12. #pragma comment(lib, "Winmm.lib")
  13. #pragma comment(lib, "user32.lib")

  14. using namespace std;

  15. #ifndef M_PI
  16. #define M_PI                3.14159265358979323846
  17. #endif

  18. struct SimpleWavHdr
  19. {
  20.         FOURCC RiffHdr;        //"RIFF"
  21.                 
  22.         uint32_t ChunkSize;        //file size - 8

  23.         FOURCC WavHdr;//"WAVE"
  24.         FOURCC FmtHdr; //"fmt "
  25.     uint32_t HdrLen; //16, length of above

  26.         uint16_t DataType; //1->PCM
  27.         uint16_t ChanNo; //1 Channel
  28.                 
  29.         uint32_t SampleRate;//8000 Hz
  30.         uint32_t SamplePerSec; //8000 sample per second
  31.                 
  32.     uint16_t BytePerSample;//Bytes per sample
  33.         uint16_t BitsPerSample;//Bits per sample
  34.                 
  35.         FOURCC dataHdr;//"data"
  36.                 
  37.         uint32_t RawSize;//data size from this point
  38. };

  39. void WinWavPlay(void);

  40. #define TEST_SAMPLE_RATE    22050
  41. #define TEST_SAMPLE_LEN_SEC    2

  42. #define TEST_SAMPLE_NUM (TEST_SAMPLE_RATE*TEST_SAMPLE_LEN_SEC)

  43. #define CHAN_NO 2
  44. #define SAMPLE_SIZE_IN_BYTE_CH_MONO    2
  45. #define SAMPLE_SIZE_IN_BYTE_ALL_CH (SAMPLE_SIZE_IN_BYTE_CH_MONO * CHAN_NO)
  46. #define BITS_PER_BYTE   8

  47. #define SAMPLE_SIZE_IN_BITS_CH_MONO    (SAMPLE_SIZE_IN_BYTE_CH_MONO * BITS_PER_BYTE)
  48. #define SAMPLE_SIZE_IN_BITS_ALL_CH (SAMPLE_SIZE_IN_BITS_CH_MONO * CHAN_NO)

  49. #define AUDIO_HZ  600
  50. #define AUDIO_CYCLE (TEST_SAMPLE_RATE/AUDIO_HZ)

  51. #define UINT8_HIGH  (UINT8_MAX/2)
  52. #define UINT8_LOW   0

  53. #define UINT16_HIGH  (UINT16_MAX/2)
  54. #define UINT16_LOW   0

  55. #define INT16_HIGH  (INT16_MAX/2)
  56. #define INT16_LOW   (0-INT16_HIGH)

  57. const char TEST_WAV_NAME[]= "TestWav16bit_2ch_22K5_Sine600.wav";

  58. HANDLE hData  = NULL;  // handle of waveform data memory 
  59. HPSTR  lpData = NULL;  // pointer to waveform data memory 

  60. SimpleWavHdr  wavHdr;

  61. int main(int argc, char** argv)
  62. {
  63.     FILE* fp = NULL;

  64.     ZeroMemory( &wavHdr, sizeof(wavHdr));
  65.       
  66.         //Fill the header of the wave file
  67.     wavHdr.RiffHdr = FOURCC_RIFF;

  68.     wavHdr.ChunkSize = sizeof(wavHdr)-8 +  SAMPLE_SIZE_IN_BYTE_ALL_CH * TEST_SAMPLE_NUM;

  69.     wavHdr.WavHdr = mmioFOURCC('W','A','V','E');
  70.     wavHdr.FmtHdr = mmioFOURCC('f','m','t',' ');
  71.     wavHdr.HdrLen = 16;

  72.     wavHdr.DataType = WAVE_FORMAT_PCM;
  73.     wavHdr.ChanNo = CHAN_NO;
  74.     wavHdr.SampleRate = TEST_SAMPLE_RATE;

  75.     wavHdr.SamplePerSec = TEST_SAMPLE_RATE  * SAMPLE_SIZE_IN_BYTE_ALL_CH;

  76.     wavHdr.BytePerSample = SAMPLE_SIZE_IN_BYTE_ALL_CH;

  77.     wavHdr.BitsPerSample = SAMPLE_SIZE_IN_BITS_CH_MONO;

  78.     wavHdr.dataHdr = mmioFOURCC('d','a','t','a');

  79.     wavHdr.RawSize =  SAMPLE_SIZE_IN_BYTE_ALL_CH * TEST_SAMPLE_NUM;

  80.     fp = fopen(TEST_WAV_NAME, "wb");
  81.     fwrite(&wavHdr, sizeof(wavHdr), 1, fp);

  82.     uint8_t testByte;

  83.     uint16_t test_16bit_2ch[2];

  84.         //Generate and save the content
  85.     for(auto i=0; i<TEST_SAMPLE_NUM; ++i)
  86.     {
  87.                 //Pure Sine Wave
  88.         test_16bit_2ch[0] = (uint16_t)((INT16_MAX)*(sin(M_PI*2*(i%AUDIO_CYCLE*1.0)/AUDIO_CYCLE)));
  89.         test_16bit_2ch[1] = test_16bit_2ch[0];
  90.                 
  91.                 //(uint16_t) ((i%AUDIO_CYCLE)>(AUDIO_CYCLE/2))?INT16_HIGH:INT16_LOW;;

  92.          //some decay effect
  93. //         test_16bit_2ch[0] = test_16bit_2ch[0] * (i+1)/TEST_SAMPLE_NUM;
  94.         //test_16bit_2ch[1] = test_16bit_2ch[1] * (i+1)/TEST_SAMPLE_NUM;

  95.         fwrite((const void*)&test_16bit_2ch, sizeof(test_16bit_2ch[0]), 2, fp);
  96.     }

  97.     fclose(fp);

  98.         //Call this function to play it with windows to verify the content
  99.     //WinWavPlay();

  100.     return 0;
  101. }
复制代码
使用VC的命令行工具编译成执行文件并运行,一切无误的话会在当前文件夹生成名为TestWav16bit_2ch_22K5_Sine600.wav的文件,用任何播放器均可播放,下面是它的形状:

将生成数据的代码那里改成这样就成生成同频率的方波:
  1.                 //Square
  2.         test_16bit_2ch[0] = (uint16_t) ((i%AUDIO_CYCLE)>(AUDIO_CYCLE/2))?INT16_HIGH:INT16_LOW;;
  3.         test_16bit_2ch[1] = test_16bit_2ch[0];        
复制代码
同样编译运行,注意改生成文件名字. 这是生成文件的形状:

如果你试着比较听一下, 600Hz的正弦波明显比同频率方波听起来柔和一点.
这两个文件都可以直接下载到板子上进行播放, 注意改文件后缀名与文件大小定义即可.详情请参阅第一篇帖子.
这里把生成的两个wave文件附上来以作参考:

当然修改算法, 可以生成各种各样的波形, 如果感兴趣, 可以自己试试改一改, 听一听.
比如把生成数据的代码改成:
  1.                 //Random noise
  2.         test_16bit_2ch[0] = (uint16_t) rand();
  3.         test_16bit_2ch[1] = test_16bit_2ch[0];
复制代码
那么生成的就是伪随机数据, 听起来就是噪音.


接下来用板子直接生成波形播放,直接生成跟上面的上位机程序基本上一样, 只是把前面header丢掉直接播放内容即可, 但是官方例程里面buffer操作写的有些晦涩, 看起来不容易明白.
他buffer操作这样的:
首先填写完一个整burfer,开始DMA播放
DMA完成一半的时候,这时候要填写第一半已经播放完的buffer,
DMA完成后填写后面一半这时候因为DMA设置成Cicular模式,所以播放没有停顿
最后数据不足以填写一半buffer的时候又从头开始取数据
把上面生成数据的代码复制在第一次填写buffer与每次半填写buffer的地方就可以了:
  1.   /* Initialize the data buffer */
  2.   for(PlaybackPosition=PLAY_HEADER; 
  3.                         PlaybackPosition < (PLAY_HEADER+PLAY_BUFF_SIZE);
  4.                         PlaybackPosition+=2)
  5.   {
  6.                 test_x_index = (PlaybackPosition-PLAY_HEADER)/2;
  7.    // PlayBuff[PlaybackPosition]=*((__IO uint16_t *)(AUDIO_FILE_ADDRESS + PlaybackPosition));      
  8.                 if(0==(PlaybackPosition%2))
  9.                 {
  10.                         //First Chanel        
  11.                         #ifdef TEST_NOISE
  12.                         //Noise
  13.                         test_16bit_data = (uint16_t) rand();
  14.                         #endif
  15.                         
  16.                         #ifdef TEST_SINE
  17.                         //Sine 600Hz
  18.                         test_16bit_data = (uint16_t)((INT16_MAX)*(sin(M_PI*2*(test_x_index%AUDIO_CYCLE*1.0)/AUDIO_CYCLE)));                
  19.                         #endif
  20.                         
  21.                         #ifdef TEST_SQUARE
  22.                         //Square 600Hz
  23.                         test_16bit_data = (uint16_t) ((test_x_index%AUDIO_CYCLE)>(AUDIO_CYCLE/2))?INT16_HIGH:INT16_LOW;;                        
  24.                         #endif                
  25.                         
  26.                         PlayBuff[PlaybackPosition] = test_16bit_data;
  27.                 }
  28.                 else
  29.                 {
  30.                         //Second Chanel
  31.                         PlayBuff[PlaybackPosition] = test_16bit_data;                        
  32.                 }
  33.   }
复制代码
  1. /* Upate the first or the second part of the buffer */
  2.     for(int i = 0; i < PLAY_BUFF_SIZE/2; i++)
  3.     {
  4. //      PlayBuff[i+position] = *(uint16_t *)(AUDIO_FILE_ADDRESS + PlaybackPosition);
  5.             test_x_index = (PlaybackPosition-PLAY_HEADER)/2;
  6.          // PlayBuff[PlaybackPosition]=*((__IO uint16_t *)(AUDIO_FILE_ADDRESS + PlaybackPosition));      
  7.             if(0==(PlaybackPosition%2))
  8.             {
  9.                 //First Chanel            
  10.                 #ifdef TEST_NOISE
  11.                 //Noise
  12.                 test_16bit_data = (uint16_t) rand();
  13.                 #endif
  14.                 
  15.                 #ifdef TEST_SINE
  16.                 //Sine 600Hz
  17.                 test_16bit_data = (uint16_t)((INT16_MAX)*(sin(M_PI*2*(test_x_index%AUDIO_CYCLE*1.0)/AUDIO_CYCLE)));        
  18.                 #endif
  19.                 
  20.                 #ifdef TEST_SQUARE
  21.                 //Square 600Hz
  22.                 test_16bit_data = (uint16_t) ((test_x_index%AUDIO_CYCLE)>(AUDIO_CYCLE/2))?INT16_HIGH:INT16_LOW;;            
  23.                 #endif
  24.                 
  25.                 PlayBuff[i+position] = test_16bit_data;
  26.             }
  27.             else
  28.             {
  29.                 //Second Chanel
  30.                 PlayBuff[i+position] = test_16bit_data;            
  31.             }            
  32.             
  33.       PlaybackPosition+=2; 
  34.     }
复制代码
感兴趣的可以对比一下,声音效果跟电脑上生成的wave文件烧写进去应该是一样的.
后续有心得再来发帖.

猜你喜欢

转载自blog.csdn.net/zouli415/article/details/80177280