本帖最后由 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文件烧写进去应该是一样的.
后续有心得再来发帖.
|
|