高级会员
- 积分
- 791
- 金钱
- 791
- 注册时间
- 2018-12-19
- 在线时间
- 163 小时
|
楼主 |
发表于 2024-4-23 10:59:52
|
显示全部楼层
本帖最后由 854278507 于 2024-4-25 10:00 编辑
NES游戏模拟器输出的声音数据是wav信号,用VS1053播放wav数据需要一个wav头,wav头就是wav声音数据文件的描述,用来告诉VS1053这些wav声音的属性。
下面是正点原子例程提供的wav头
- /* NES模拟器声音从VS1053输出,模拟WAV解码的wav头数据 */
- const uint8_t nes_wav_head[] =
- {
- 0X52, 0X49, 0X46, 0X46, 0XFF, 0XFF, 0XFF, 0XFF, 0X57, 0X41, 0X56, 0X45, 0X66, 0X6D, 0X74, 0X20,
- 0X10, 0X00, 0X00, 0X00, 0X01, 0X00, 0X01, 0X00, 0X11, 0X2B, 0X00, 0X00, 0X11, 0X2B, 0X00, 0X00,
- 0X01, 0X00, 0X08, 0X00, 0X64, 0X61, 0X74, 0X61, 0XFF, 0XFF, 0XFF, 0XFF, 0X00, 0X00, 0X00, 0X00,
- 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
- };
复制代码 这些数据一看就头大,后来发现里面有两个0x11,0x2B,由于是小端模式,还原成正常的数据就是0x2B11,也就是11025,说明这里是采样率,我就在sms模拟器里面把这个采样率换了好几种,单声道,双声道,8位采样,16位采样,无论怎么组合声音效果始终是不好,很大杂音。
昨天准备把SMS模拟器输出的声音数据保存到sd卡上,然后用别的播放器播放一下,看这个SMS模拟器输出的到底是什么格式的wav文件。
就在添加写文件功能的时候发现了问题所在。
下面是正点原子的打开声音输出的函数
- /**
- * @brief NES打开音频输出
- * [url=home.php?mod=space&uid=271674]@param[/url] samples_per_sync: 样本同步(未用到)
- * @param sample_rate : 音频采样率
- * @retval 0, 成功;
- * 其他, 失败;
- */
- int nes_sound_open(int samples_per_sync, int sample_rate)
- {
- uint8_t *p;
- uint8_t i;
- p = mymalloc(SRAMIN, 100); /* 申请100字节内存 */
- if (p == NULL)return 1; /* 内存申请失败,直接退出 */
- printf("sound open:%d\r\n", sample_rate);
- for (i = 0; i < sizeof(nes_wav_head); i++) /* 复制nes_wav_head内容 */
- {
- p[i] = nes_wav_head[i];
- }
- if (lcddev.width == 480) /* 是480*480屏幕 */
- {
- sample_rate = 8000; /* 设置8Khz,约原来速度的0.75倍 */
- }
-
- p[24] = sample_rate & 0XFF; /* 设置采样率 */
- p[25] = (sample_rate >> 8) & 0XFF;
- p[28] = sample_rate & 0XFF; /* 设置字节速率(8位模式,等于采样率) */
- p[29] = (sample_rate >> 8) & 0XFF;
- nesplaybuf = 0;
- nessavebuf = 0;
- vs10xx_reset(); /* 硬复位 */
- vs10xx_soft_reset(); /* 软复位 */
- vs10xx_set_all(); /* 设置音量等参数 */
- vs10xx_reset_decode_time(); /* 复位解码时间 */
- while (vs10xx_send_music_data(p)); /* 发送wav head */
- while (vs10xx_send_music_data(p + 32)); /* 发送wav head */
- tim6_int_init(100 - 1, 1280 - 1); /* 1ms中断一次 */
- myfree(SRAMIN, p); /* 释放内存 */
- return 0;
- }
复制代码 在打开声音的函数里面定义了一个指针p,并给这个指针p申请了内存,然后把wav头的数组内容复制到了指针p所指向的地址,然后又根据采样率修改了指针p所指针地址的数据,难怪我无论怎么修改那个wav头都不行,原来并不是直接wav头直接送到VS1053的,在发送到VS1053之前做了修改。
于是我就把打开声音的函数做了修改,下面是我修改后的代码- /**
- * @brief SMS打开音频输出
- * @param sample_rate : 音频采样率
- * @retval 无
- */
- void sms_sound_open(int sample_rate)
- {
- uint8_t *p;
- uint8_t i;
- p = mymalloc(SRAMIN, 64); // 申请100字节内存
- if (p == NULL) // 内存申请失败,直接退出
- {
- return;
- }
- printf("\r\n[%s][%s][%04d]sound open:%d", __FILE__, __func__, __LINE__, sample_rate);
- memset(p, 0x00, 64);
- if (lcddev.width == 480) // 是480*480屏幕
- {
- sample_rate = 8000; // 设置8Khz,约原来速度的0.75倍
- }
- /* 下面是wav头 */
- p[0] = 'R'; /* "RIFF" */
- p[1] = 'I';
- p[2] = 'F';
- p[3] = 'F';
-
- *(uint32_t *)&p[4] = 0xFFFFFFFF; /* 文件大小 */
-
- p[8] = 'W'; /* "WAVE" */
- p[9] = 'A';
- p[10] = 'V';
- p[11] = 'E';
-
- p[12] = 'f'; /* "fmt " */
- p[13] = 'm';
- p[14] = 't';
- p[15] = ' ';
-
- *(uint32_t *)&p[16] = 16; /* 后面还有16个字节的数据 */
- *(uint32_t *)&p[20] = 1; /* 文件格式:wav为1 */
- *(uint16_t *)&p[22] = 2; /* 双声道 */
- *(uint16_t *)&p[34] = 16; /* 采样深度:16位 */
- *(uint32_t *)&p[24] = sample_rate; /* 采样率 */
- *(uint16_t *)&p[32] = *(uint16_t *)&p[22] * (*(uint16_t *)&p[34] / 8); /* 字节对齐 */
- *(uint32_t *)&p[28] = sample_rate * *(uint16_t *)&p[32]; /* 比特率 */
-
- p[36] = 'd'; /* "data" */
- p[37] = 'a';
- p[38] = 't';
- p[39] = 'a';
-
- *(uint32_t *)&p[40] = 0xFFFFFFFF; /* 数据大小 */
- printf("\r\n\r\n");
- printf("\r\n[%s][%s][%04u]WAV文件头", __FILE__, __func__, __LINE__);
- printf("\r\n");
- uint8_t k;
- k = 0;
- for (i = 0; i < 64; i++)
- {
- printf("0x%02X ", p[i]);
- k++;
- if (k >= 4)
- {
- k = 0;
- printf("\r\n");
- }
- }
- printf("\r\n\r\n");
- smsbufpos = 0;
- smsplaybuf = 0; // 即将播放的音频帧缓冲编号
- smssavebuf = 0; // 当前保存到的音频缓冲编号
- #ifdef __VS10xX_H__
- VS_HD_Reset(); // 硬复位
- VS_Soft_Reset(); // 软复位
- VS_Set_All(); // 设置音量等参数
- VS_Reset_DecodeTime(); // 复位解码时间
- while (VS_Send_MusicData(&p[0]))
- ; // 发送wav head
- while (VS_Send_MusicData(&p[32]))
- ; // 发送wav head
- TIM6_Int_Init(250 - 1, 128 - 1); // 0.25ms中断一次
- #endif /* __VS10xX_H__ */
- myfree(SRAMIN, p); // 释放内存
- }
复制代码
从上面的函数可以看出不再需要wav头那个数组了,我定义了一个指针p,然后给这个指针p申请内存,然后直接修改指针p指向地址的内容,并写了注释。
上面的函数里面修改指针p所指向的数据最好是用结构体的方式,会比较直观一些,由于偷懒和为了让大家看明白这个wav头内容的修改就没有写成结构体的方式。
经过测试SMS模拟器输出的是双声道,16位采样,NES模拟器是单声道,8位采样。
SMS模拟器输出的声音数据量是NES模拟器输出声音数据量的4位。
准确来说SMS模拟器输出的wav字节率是NES模拟器输出的wav字节率的4倍。
wav字节率 = 声音通道数 * 采样率 * (采率深度 / 8)
SMS模拟器的wav字节率 = 2 * 11025 * (16 / 8)
NES模拟器的wav字节率 = 1 * 11025 * (8 / 8)
NES模拟器是用定时器6每1ms给VS1053发一次声音数据,SMS模拟器的声音数据量比较大,就要把定时的时间改小,字节率是4倍,定时时间就要是1/4了,就把定时器6的定时时间改成0.25ms,这样SMS模拟器输出的声音就正常了。
最后再补充一下SMS模拟器wav头数组的注释,虽然现在不需要这个数组了,但还是要了解一下这个数组的内容是什么意思。
- // SMS模拟器声音从VS1053输出,模拟WAV解码的wav头数据.
- const uint8_t sms_wav_head[] =
- {
- 0x52, 0x49, 0x46, 0x46, /* 0 "RIFF" */
- 0xFF, 0xFF, 0xFF, 0xFF, /* 4 文件大小 */
- 0x57, 0x41, 0x56, 0x45, /* 8 "WAVE" */
- 0x66, 0x6D, 0x74, 0x20, /* 12 "fmt " */
- 0x10, 0x00, 0x00, 0x00, /* 16 后面的数据长度:0x10,后面还有16个字节的数据 */
- 0x01, 0x00, /* 20 声音格式:wav为0x01 */
- 0x02, 0x00, /* 22 声音通道数:
- * 单声道:0x01
- * 双声道:0x02
- */
- 0x11, 0x2B, 0x00, 0x00, /* 24 采样率:每秒中的采样次数,可选以下三种
- * 低质量:11025(0x2B11)
- * 中质量:22050(0x5622)
- * 高质量:44100(0xAC44)
- */
- 0x44, 0xAC, 0x00, 0x00, /* 28 比特率:每秒中的数据字节数,采样率 * 单次采样所占用的字节数
- * 单声道 8位采样时为采样率的1倍
- * 单声道16位采样时为采样率的2倍
- * 双声道 8位采样时为采样率的2倍
- * 双声道16位采样时为采样率的4倍
- */
- 0x04, 0x00, /* 32 对齐方式
- * 单声道 8位采样时:1字节对齐
- * 单声道16位采样时:2字节对齐
- * 双声道 8位采样时:2字节对齐
- * 双声道16位采样时:4字节对齐
- */
- 0x10, 0x00, /* 34 采样深度:
- * 8位采样:0x08
- * 16位采样:0x10
- */
- 0x64, 0x61, 0x74, 0x61, /* 36 "data" */
- 0xFF, 0xFF, 0xFF, 0xFF, /* 40 数据大小 */
- 0x00, 0x00, 0x00, 0x00, /* 44 */
- 0x00, 0x00, 0x00, 0x00, /* 48 */
- 0x00, 0x00, 0x00, 0x00, /* 52 */
- 0x00, 0x00, 0x00, 0x00, /* 56 */
- 0x00, 0x00, 0x00, 0x00, /* 60 */
- };
复制代码 下面是NES模拟器wav头数组的注释
- // NES模拟器声音从VS1053输出,模拟WAV解码的wav头数据.
- const uint8_t nes_wav_head[] =
- {
- 0x52, 0x49, 0x46, 0x46, /* 0 "RIFF" */
- 0xFF, 0xFF, 0xFF, 0xFF, /* 4 文件大小 */
- 0x57, 0x41, 0x56, 0x45, /* 8 "WAVE" */
- 0x66, 0x6D, 0x74, 0x20, /* 12 "fmt " */
- 0x10, 0x00, 0x00, 0x00, /* 16 后面的数据长度:0x10,后面还有16个字节的数据 */
- 0x01, 0x00, /* 20 声音格式:wav为0x01 */
- 0x01, 0x00, /* 22 声音通道数:
- * 单声道:0x01
- * 双声道:0x02
- */
- 0x11, 0x2B, 0x00, 0x00, /* 24 采样率:每秒中的采样次数,可选以下三种
- * 低质量:11025(0x2B11)
- * 中质量:22050(0x5622)
- * 高质量:44100(0xAC44)
- */
- 0x11, 0x2B, 0x00, 0x00, /* 28 比特率:每秒中的数据字节数,采样率 * 单次采样所占用的字节数
- * 单声道 8位采样时为采样率的1倍
- * 单声道16位采样时为采样率的2倍
- * 双声道 8位采样时为采样率的2倍
- * 双声道16位采样时为采样率的4倍
- */
- 0x01, 0x00, /* 32 对齐方式
- * 单声道 8位采样时:1字节对齐
- * 单声道16位采样时:2字节对齐
- * 双声道 8位采样时:2字节对齐
- * 双声道16位采样时:4字节对齐
- */
- 0x08, 0x00, /* 34 采样深度:
- * 8位采样:0x08
- * 16位采样:0x10
- */
- 0x64, 0x61, 0x74, 0x61, /* 36 "data" */
- 0xFF, 0xFF, 0xFF, 0xFF, /* 40 数据大小 */
- 0x00, 0x00, 0x00, 0x00, /* 44 */
- 0x00, 0x00, 0x00, 0x00, /* 48 */
- 0x00, 0x00, 0x00, 0x00, /* 52 */
- 0x00, 0x00, 0x00, 0x00, /* 56 */
- 0x00, 0x00, 0x00, 0x00, /* 60 */
- };
复制代码
|
|