| 
 
高级会员 
 
	积分857金钱857 注册时间2018-12-19在线时间174 小时 | 
 
 
 楼主|
发表于 2024-4-23 10:59:52
|
显示全部楼层 
| 本帖最后由 854278507 于 2024-4-25 10:00 编辑 
 NES游戏模拟器输出的声音数据是wav信号,用VS1053播放wav数据需要一个wav头,wav头就是wav声音数据文件的描述,用来告诉VS1053这些wav声音的属性。
 下面是正点原子例程提供的wav头
 
 这些数据一看就头大,后来发现里面有两个0x11,0x2B,由于是小端模式,还原成正常的数据就是0x2B11,也就是11025,说明这里是采样率,我就在sms模拟器里面把这个采样率换了好几种,单声道,双声道,8位采样,16位采样,无论怎么组合声音效果始终是不好,很大杂音。复制代码
/* 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,
};
昨天准备把SMS模拟器输出的声音数据保存到sd卡上,然后用别的播放器播放一下,看这个SMS模拟器输出的到底是什么格式的wav文件。
 就在添加写文件功能的时候发现了问题所在。
 下面是正点原子的打开声音输出的函数
 
 在打开声音的函数里面定义了一个指针p,并给这个指针p申请了内存,然后把wav头的数组内容复制到了指针p所指向的地址,然后又根据采样率修改了指针p所指针地址的数据,难怪我无论怎么修改那个wav头都不行,原来并不是直接wav头直接送到VS1053的,在发送到VS1053之前做了修改。复制代码/**
 * @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;
}
于是我就把打开声音的函数做了修改,下面是我修改后的代码
 复制代码/**
 * @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头数组的注释,虽然现在不需要这个数组了,但还是要了解一下这个数组的内容是什么意思。
 
 下面是NES模拟器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模拟器声音从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 */
};
 
 
 
 
 
 | 
 |