打印
[STM32F7]

STM32F769 Disc音频播放之二:手工制作Wav文件以及生成播放数据

[复制链接]
2646|18
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
zhanzr21|  楼主 | 2016-12-16 01:31 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 zhanzr21 于 2016-12-16 01:33 编辑

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

        FOURCC WavHdr;//"WAVE"
        FOURCC FmtHdr; //"fmt "
    uint32_t HdrLen; //16, length of above

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

#include <cstdint>
#include <iostream>
#include <vector>

#define _USE_MATH_DEFINES
#ifdef _MATH_DEFINES_DEFINED
#undef _MATH_DEFINES_DEFINED
#endif

#include <math.h>

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

using namespace std;

#ifndef M_PI
#define M_PI                3.14159265358979323846
#endif

struct SimpleWavHdr
{
        FOURCC RiffHdr;        //"RIFF"
               
        uint32_t ChunkSize;        //file size - 8

        FOURCC WavHdr;//"WAVE"
        FOURCC FmtHdr; //"fmt "
    uint32_t HdrLen; //16, length of above

        uint16_t DataType; //1->PCM
        uint16_t ChanNo; //1 Channel
               
        uint32_t SampleRate;//8000 Hz
        uint32_t SamplePerSec; //8000 sample per second
               
    uint16_t BytePerSample;//Bytes per sample
        uint16_t BitsPerSample;//Bits per sample
               
        FOURCC dataHdr;//"data"
               
        uint32_t RawSize;//data size from this point
};

void WinWavPlay(void);

#define TEST_SAMPLE_RATE    22050
#define TEST_SAMPLE_LEN_SEC    2

#define TEST_SAMPLE_NUM (TEST_SAMPLE_RATE*TEST_SAMPLE_LEN_SEC)

#define CHAN_NO 2
#define SAMPLE_SIZE_IN_BYTE_CH_MONO    2
#define SAMPLE_SIZE_IN_BYTE_ALL_CH (SAMPLE_SIZE_IN_BYTE_CH_MONO * CHAN_NO)
#define BITS_PER_BYTE   8

#define SAMPLE_SIZE_IN_BITS_CH_MONO    (SAMPLE_SIZE_IN_BYTE_CH_MONO * BITS_PER_BYTE)
#define SAMPLE_SIZE_IN_BITS_ALL_CH (SAMPLE_SIZE_IN_BITS_CH_MONO * CHAN_NO)

#define AUDIO_HZ  600
#define AUDIO_CYCLE (TEST_SAMPLE_RATE/AUDIO_HZ)

#define UINT8_HIGH  (UINT8_MAX/2)
#define UINT8_LOW   0

#define UINT16_HIGH  (UINT16_MAX/2)
#define UINT16_LOW   0

#define INT16_HIGH  (INT16_MAX/2)
#define INT16_LOW   (0-INT16_HIGH)

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

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

SimpleWavHdr  wavHdr;

int main(int argc, char** argv)
{
    FILE* fp = NULL;

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

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

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

    wavHdr.DataType = WAVE_FORMAT_PCM;
    wavHdr.ChanNo = CHAN_NO;
    wavHdr.SampleRate = TEST_SAMPLE_RATE;

    wavHdr.SamplePerSec = TEST_SAMPLE_RATE  * SAMPLE_SIZE_IN_BYTE_ALL_CH;

    wavHdr.BytePerSample = SAMPLE_SIZE_IN_BYTE_ALL_CH;

    wavHdr.BitsPerSample = SAMPLE_SIZE_IN_BITS_CH_MONO;

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

    wavHdr.RawSize =  SAMPLE_SIZE_IN_BYTE_ALL_CH * TEST_SAMPLE_NUM;

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

    uint8_t testByte;

    uint16_t test_16bit_2ch[2];

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

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

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

    fclose(fp);

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

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

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

如果你试着比较听一下, 600Hz的正弦波明显比同频率方波听起来柔和一点.
这两个文件都可以直接下载到板子上进行播放, 注意改文件后缀名与文件大小定义即可.详情请参阅第一篇帖子.
这里把生成的两个wave文件附上来以作参考:
sine_square_reference_result_file.zip (2.02 KB)
当然修改算法, 可以生成各种各样的波形, 如果感兴趣, 可以自己试试改一改, 听一听.
比如把生成数据的代码改成:
                //Random noise
        test_16bit_2ch[0] = (uint16_t) rand();
        test_16bit_2ch[1] = test_16bit_2ch[0];
那么生成的就是伪随机数据, 听起来就是噪音.


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





评分
参与人数 1威望 +1 收起 理由
工业经济 + 1 很给力!
沙发
阿基米东| | 2017-1-16 09:28 | 只看该作者
请问 FOURCC 是怎么样的?

使用特权

评论回复
板凳
zhanzr21|  楼主 | 2017-1-16 16:57 | 只看该作者
从微软的 MMSystem.h中抠出来的
/* standard four character codes */
#define FOURCC_RIFF     mmioFOURCC('R', 'I', 'F', 'F')
#define FOURCC_LIST     mmioFOURCC('L', 'I', 'S', 'T')

使用特权

评论回复
地板
51xlf| | 2017-1-28 18:48 | 只看该作者
这个噪声是怎么处理?

使用特权

评论回复
5
51xlf| | 2017-1-28 18:49 | 只看该作者
信号仿真处理的这么快呢。

使用特权

评论回复
6
zhanzr21|  楼主 | 2017-1-28 22:52 | 只看该作者
51xlf 发表于 2017-1-28 18:48
这个噪声是怎么处理?

噪音是我故意使用rand函数生成的, 测试效果

使用特权

评论回复
7
pmp| | 2017-1-29 23:17 | 只看该作者
有matlab的仿真吗

使用特权

评论回复
8
zhanzr21|  楼主 | 2017-1-30 20:07 | 只看该作者
没有matlab的源代码, 原理一样的, C++转matlab非常简单的

使用特权

评论回复
9
wangdezhi| | 2017-2-1 23:22 | 只看该作者
这个是linux的执行代码吗?

使用特权

评论回复
10
wangdezhi| | 2017-2-1 23:25 | 只看该作者
这些lib库是从哪里获取的?

使用特权

评论回复
11
zhanzr21|  楼主 | 2017-2-3 09:42 | 只看该作者
wangdezhi 发表于 2017-2-1 23:22
这个是linux的执行代码吗?

这个是Windows下面的, 不过要改成Linux也不难

使用特权

评论回复
12
zhanzr21|  楼主 | 2017-2-3 09:46 | 只看该作者
wangdezhi 发表于 2017-2-1 23:25
这些lib库是从哪里获取的?

Windows SDK里面的,
装了VC也会有的, 只不过VC自带的比较旧,

使用特权

评论回复
13
i1mcu| | 2017-2-4 23:49 | 只看该作者

使用特权

评论回复
14
232321122| | 2017-2-9 22:59 | 只看该作者
不是VS1003简单一些吗?

使用特权

评论回复
15
pmp| | 2017-2-15 11:09 | 只看该作者
这个不是可以搞嵌入式开发的么

使用特权

评论回复
16
pmp| | 2017-2-15 11:09 | 只看该作者
后端的音频处理方法是怎么实现的?

使用特权

评论回复
17
zhanzr21|  楼主 | 2017-2-15 16:26 | 只看该作者
我这个是系列**, 这是其中一篇

使用特权

评论回复
18
typeof| | 2017-2-17 22:44 | 只看该作者
做一个mp4播放电路吗?

使用特权

评论回复
19
typeof| | 2017-2-17 22:45 | 只看该作者
现在做文件解析还是使用操作系统简单一些。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:每天都進步

91

主题

1013

帖子

34

粉丝