CH585 ADPCM 音频文件解码播放

复制代码
测试一下 ADPCM 解码播放    ...... 矜辰所致

前言

之前我们我们已经掌握了一种低成本的 CH585 音频播放方案,我们也测试过了 SBC 解码播放 以及 原始的 PCM 播放,作为嵌入式中另外一种常用的语音编解码的 ADPCM,博主早就有计划也要试一下。

所以本文我们主要就来测试一下 ADPCM 解码以及播放 。

相关博文:

嵌入式语音开发应用基础说明

CH585 SBC 音频解码播放测试说明

CH585 PCM 播放测试以及双 PWM 原理

.

我是矜辰所致,全网同名,尽量用心写好每一系列文章,不浮夸,不将就,认真对待学知识的我们,矜辰所致,金石为开!

目录

  • 前言
  • [一、 基础说明](#一、 基础说明)
    • [1.1 IMA ADPCM](#1.1 IMA ADPCM)
    • [1.2 测试环境](#1.2 测试环境)
    • [1.3 ADPCM 解码源码](#1.3 ADPCM 解码源码)
  • [二、 应用层处理](#二、 应用层处理)
    • [2.1 与 SBC 解码的区别](#2.1 与 SBC 解码的区别)
    • [2.2 ADPCM 文件处理方式](#2.2 ADPCM 文件处理方式)
    • [2.2 APP 代码修改](#2.2 APP 代码修改)
  • 三、音频文件生成
    • [3.1 音频文件转成 wav](#3.1 音频文件转成 wav)
    • [3.2 wav 转成 adpcm](#3.2 wav 转成 adpcm)
    • [3.3 adpcm 转成 Hex](#3.3 adpcm 转成 Hex)
    • [3.4 指令流程小结](#3.4 指令流程小结)
  • 结语

一、 基础说明

1.1 IMA ADPCM

ADPCM (自适应差分脉冲编码调制)是一种音频有损压缩算法。

ADPCM 也分很多种: IMA ADPCM(adpcm_ima_wav) 、G.726(电信 ADPCM)、MS ADPCM(微软 ADPCM)等。

而本文的我们测试的对象是 IMA ADPCM ,也是嵌入式行业存 Flash 提示音、报警语音 的统一标准 :

  • 4bit / 采样,1 字节 = 2 个采样;低 4bit 在前、高 4bit 在后;步长表 89 级,输入输出 16bit 线性 PCM;
  • 无专利、开源代码遍地;FFmpeg 原生编码;
  • 封装:带 44 字节 WAV 头,去掉头部就是纯裸流,可直接烧录单片机;

本文测试的为存裸流的 ADPCM ,不带有任何包头,块头。

1.2 测试环境

使用的测试代码还是之前 SBC 解码播放的那套架构,播放的形式与之前一样, 硬件连接双 PWM 接 H 桥播放。

我们需要把之前的 SBC 解码的库换成 ADPCM 的解码库。

1.3 ADPCM 解码源码

ADPCM 的解码是开源的,这里咱也不墨迹,直接放一下本次测试使用的源码。

adpcm.c

c 复制代码
#include "adpcm.h"

/* IMA ADPCM Step Size Table */
static const int16_t ima_step_table[89] = {
    7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
    19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
    50, 55, 60, 66, 73, 80, 88, 97, 107, 118,
    130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
    337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
    876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
    2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
    5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
    15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
};

/* IMA ADPCM Index Table 
 * 索引调整表
*/
static const int8_t ima_index_table[16] = {
    -1, -1, -1, -1, 2, 4, 6, 8,
    -1, -1, -1, -1, 2, 4, 6, 8
};

// 初始化
void ADPCM_DecodeInit(ima_adpcm_state_t *state)
{
    state->predictor  = 0;
    state->stepIndex  = 0;
}

/* Decode one 4-bit nibble → 16-bit PCM sample (supplier algorithm) 
 * 解码一个 nibble
*/
static int16_t ima_adpcm_decode_nibble(uint8_t nibble, ima_adpcm_state_t *st)
{
    int step = ima_step_table[st->stepIndex];
    int diff = step >> 3;

    if(nibble & 1) diff += step >> 2;   /* bit0: step/4 */
    if(nibble & 2) diff += step >> 1;   /* bit1: step/2 */
    if(nibble & 4) diff += step;        /* bit2: step   */
    if(nibble & 8) diff = -diff;        /* bit3: sign   */

    st->predictor += diff;
    if(st->predictor > 32767)  st->predictor = 32767;
    if(st->predictor < -32768) st->predictor = -32768;

    st->stepIndex += ima_index_table[nibble & 0x0F];
    if(st->stepIndex < 0)  st->stepIndex = 0;
    if(st->stepIndex > 88) st->stepIndex = 88;

    return (int16_t)st->predictor;
}

/* Decode 120-byte ADPCM block → 240 PCM samples
 * Low nibble first per byte (matches supplier) 
 * 解码一段 ADPCM 数据
 */
void ima_adpcm_decode(const uint8_t *adpcmData, uint32_t  adpcmLen,
                           int16_t *pcmData, ima_adpcm_state_t  *state)
{
    uint16_t n = 0;
    for(uint16_t i = 0; i < adpcmLen; i++)
    {
        uint8_t b = adpcmData[i];
        pcmData[n++] = ima_adpcm_decode_nibble(b & 0x0F, state);       /* low nibble first */
        pcmData[n++] = ima_adpcm_decode_nibble((b >> 4) & 0x0F, state); /* high nibble second */
    }
}

adpcm.h

c 复制代码
#ifndef ADPCM_DECODER_H
#define ADPCM_DECODER_H

#include <stdint.h>


typedef struct {
    int32_t predictor;
    int32_t stepIndex;
} ima_adpcm_state_t;


void ADPCM_DecodeInit(ima_adpcm_state_t *state);
void ima_adpcm_decode(const uint8_t *adpcmData, uint32_t  adpcmLen,
                           int16_t *pcmData, ima_adpcm_state_t  *state);



#ifdef __cplusplus
}
#endif

#endif

这个应该也是标准的 ADPCM 的解码源码(我反正是查了好些不同的代码示例,基本都一样)可以直接复制过去用。

二、 应用层处理

接下来就是修改一下我们的应用层面了。

2.1 与 SBC 解码的区别

SBC 压缩方式有帧结构可以计算出长度,而 ADPCM 没有,所以我们单从应用层去读取 ADPCM 的原始数据,我们是无法判断 ADPCM 是否结束了的。

我们测试示例的处理方式是在 Flash 里面分配了多段区域,每段区域用来存放不同的音频,Flash 分配的大小要根据自己的音频大小来划分的,分配的 Flash 大小肯定不能小于需要存放的音频大小的:

c 复制代码
typedef struct {
    uint32_t addr;
    uint32_t length;
} audio_record_t;

audio_record_t music_record[] = {
    { 0x000000, 51200 },  
    { 0x00c800, 102400 },//0x14800
    { 0x025800, 153600 }, //offset=0x2D800
};

一般来说,我们在分配 Flash 的时候会多预留一点余量。但是如果是 ADPCM 压缩的音频文件,因为我们程序只知道 Flash 分配的大小,却无法知道音频文件的大小,当分配的 Flash 空间比音频文件大的时候, 后面不需要的 Flash 的数据都会被当成音频数据播放,就会产生不需要的杂音。

所以我们需要保证 ADPCM 音频文件的大小与 分配的 Flash 大小正好一致,才能避免出现杂音。但是这样无法保证对齐,而且每次音频改变,都得改一下代码,在实际应用上,这样的处理方式明显繁琐且不常规,我们需要更加通用一点的方式。

2.2 ADPCM 文件处理方式

再次声明一下,本文测试针对纯裸流的 ADPCM ,不带有任何包头,块头。

为了解决上面的问题,我们可以进行一些处理,博主了解到的方式有两种:

  1. 在生成 ADPCM 音频文件的时候,给 ADPCM 文件补全到自己分配的音频 FLASH 大小 (更加推荐的方式是不改变原始 ADPCM ,转成 Hex 的时候补齐到分配大小)。
  2. 在生成 ADPCM 音频文件的时候,给 ADPCM 音频文件头部追加长度。

两种方式各有优缺点,这个看应用需求,博主本文测试使用第一种方式,具体的生成补全方式下文会有说明,我们先来看一下应用层的处理代码(上面两种不同的处理方式,应用层代码也略有不同,一个是要先获取追加的长度数据,再根据长度处理音频数据,一个只处理 ADPCM 音频数据)。

2.2 APP 代码修改

博主根据 ADPCM 解码播放修改了一下应用层,最关键的修改就是在 audio_handler 函数种的数据处理部分,其他的一些关键点请查看之前文章 《CH585 SBC 音频解码播放测试说明》 的内容。

这里就直接放一下修改后的测试代码(这面的解析是基于上面 ADPCM 音频文件第一种处理方式,把整段分配的 Flash 区域都当成音频数据来处理的,这就要求我们生成音频文件的时候要特殊处理):

c 复制代码
#include "CH58x_common.h"
#include <stdbool.h>
#include "ch5xx_audio_dma.h"
#include "adpcm.h"


#define ADPCM_FLASH_ADDR    0x32000

typedef struct {
    uint32_t addr;
    uint32_t length;
} audio_record_t;

// 分配空间大小,ADPCM 数据必须填满!
audio_record_t music_record[] = {
    { 0x000000, 0x0A000  }, //40751
    // { 0x3C000, 37199  },
    // { 0x003D00, 8192 },
};

#define MUSIC_COUNT     (sizeof(music_record) / sizeof(music_record[0]))

volatile uint32_t read_index;
volatile uint8_t  play_on;
volatile uint8_t  read_data_sta;

ima_adpcm_state_t adpcm_state;

uint8_t  adpcm_buf[64];
int16_t  pcm_buf[128];

typedef struct {
    uint32_t start_addr;
    uint32_t length;
} sample_t;

sample_t sample;

void DebugInit(void)
{
    GPIOA_SetBits(GPIO_Pin_9);
    GPIOA_ModeCfg(GPIO_Pin_8, GPIO_ModeIN_PU);
    GPIOA_ModeCfg(GPIO_Pin_9, GPIO_ModeOut_PP_5mA);
    UART1_DefInit();
    UART1_BaudRateCfg(460800);
}

__HIGH_CODE
static void audio_handler(ch5xx_audio_event_t * p_event)
{
    switch(p_event->type) {
        case CH5XX_PWM_DMA_GET: {
            uint32_t *p_data  = p_event->p_data;
            uint32_t *p_data2 = p_event->p_data2;
            uint32_t length   = p_event->len;

            switch(read_data_sta) {
                case 0: {
                    uint32_t filled = 0;

                    while(filled < length) {
                        if(read_index >= sample.length) {
                            read_data_sta = 1;
                            break;
                        }

                        uint32_t remain = sample.length - read_index;
                        uint32_t need_samples = length - filled;
                        uint32_t need_bytes = (need_samples + 1) / 2;

                        uint32_t copy_bytes = (remain < need_bytes) ? remain : need_bytes;
                        uint32_t decode_samples = copy_bytes * 2;
                        if(decode_samples > need_samples) {
                            decode_samples = need_samples;
                        }

                        __wrap_memcpy(adpcm_buf, (uint8_t *)(sample.start_addr + read_index), copy_bytes);

                        ima_adpcm_decode(adpcm_buf, copy_bytes, &pcm_buf[filled], &adpcm_state);

                        for(uint32_t i = 0; i < decode_samples; i++) {
                            int16_t pcm_sample = pcm_buf[filled + i];
                            if(pcm_sample > 0) {
                                *p_data = pcm_sample / 8;
                                *p_data2 = 0;
                            } else {
                                *p_data2 = (0 - pcm_sample) / 8;
                                *p_data = 0;
                            }
                            p_data++;
                            p_data2++;
                        }

                        filled += decode_samples;
                        read_index += copy_bytes;
                    }

                    for(; filled < length; filled++) {
                        *p_data++ = 0;
                        *p_data2++ = 0;
                    }

                    if(read_data_sta == 1) {
                        PRINT("ADPCM end\r\n");
                        ch5xx_audio_stop();
                    }
                    break;
                }

                case 1:
                    ch5xx_audio_stop();
                    break;

                default:
                    break;
            }
            break;
        }

        case CH5XX_PWM_ALL_END:
            PRINT("ALL END\r\n");
            play_on = 0;
            read_data_sta = 0;
            break;

        default:
            break;
    }

}

__HIGH_CODE
void main_loop(void)
{
    uint8_t index = 0;

    while(1) {
        read_index = 0;
        play_on = 1;
        read_data_sta = 0;

        ADPCM_DecodeInit(&adpcm_state);

        sample.length = music_record[index].length;
        sample.start_addr = ADPCM_FLASH_ADDR + music_record[index].addr;

        PRINT("Play:%d addr:%08x len:%u\r\n", index, sample.start_addr, sample.length);

        ch5xx_audio_start();
        while(play_on);
        ch5xx_audio_stop();
        index++;
        if(index >= MUSIC_COUNT) {
            index = 0;
        }

        DelayMs(1000);
    }
}

int main(void)
{
    SetSysClock(CLK_SOURCE_HSE_PLL_78MHz);

    DebugInit();

    PRINT("\r\nADPCM Player\r\n");

    sample.start_addr = ADPCM_FLASH_ADDR;
    sample.length = 0xFFFFFFFF;

    ch5xx_audio_init(CH5XX_AUDIO_SAMPLE_RATE_16K, audio_handler);

    main_loop();
}

三、音频文件生成

我们知道 ffmpeg 可以把音频文件生成 SBC 和 PCM 文件:

bash 复制代码
SBC 文件生成
ffmpeg -i input.mp3 -ar 16000 -ac 1 -f sbc voice.sbc 
PCM 文件生成
ffmpeg -i 004.mp3 -ar 16000 -ac 1 -f s16le 004p.pcm

在最开始测试的时候博主以为也是和之前测试一样, ffmpeg 一条命令就生成,然后直接使用了 :

bash 复制代码
ffmpeg -i 004.mp3 -ar 16000 -ac 1  -c:a adpcm_ima_wav  -f data -map 0:a  004adp.adpcm

也确实生成了 adpcm, 但是上面这条指令生成的 adpcm 是分块编码的,每块开头 4 字节状态头 。

每块开头有 4 字节头,记录该块开始时的 predictor 和 stepIndex。这样的好处是可以从任意块开始解码,支持随机跳转。

我们本文修改的代码是针对 纯裸流 IMA ADPCM ,绝大多数消费类 MCU 设备也是用的纯裸流的结构。所以我们还得想个办法,生成纯裸流的 ADPCM 。

上面这种方式生成的 ADPCM 用本文的代码直接也能播放出来,但是因为没有处理块头,都当成音频数据解析,所以能明显听出来杂音。

我自己把流程记录下来(这里只是博主测试成功的方式,并不一定是最优解,仅供参考)。

3.1 音频文件转成 wav

先通过 ffmpeg 工具把音乐文件转成 wav 文件:

bash 复制代码
ffmpeg -i 004.mp3 -ar 16000 -ac 1 004_16k.wav

3.2 wav 转成 adpcm

然后再通过 Python (Python 安装之前的博文有,网上也很多,自行查找)脚本压缩成我们需要的 adpcm 音频文件(和上面解码源码相对应的压缩流程),新建一个 .py 文件 ,内容如下:

python 复制代码
# adpcm_encode.py - 纯标准库
import wave
import struct
import sys

class IMAADPCMEncoder:
    def __init__(self):
        self.predictor = 0
        self.step_index = 0
        self.step_table = [
            7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
            19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
            50, 55, 60, 66, 73, 80, 88, 97, 107, 118,
            130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
            337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
            876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
            2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
            5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
            15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
        ]
        self.index_table = [
            -1, -1, -1, -1, 2, 4, 6, 8,
            -1, -1, -1, -1, 2, 4, 6, 8
        ]
    
    def encode_nibble(self, sample):
        diff = sample - self.predictor
        step = self.step_table[self.step_index]
        nibble = 0
        
        if diff < 0:
            nibble = 8
            diff = -diff
        
        if diff >= step:
            nibble |= 4
            diff -= step
        if diff >= step >> 1:
            nibble |= 2
            diff -= step >> 1
        if diff >= step >> 2:
            nibble |= 1
        
        # 更新 predictor(和解码端一致)
        diff = step >> 3
        if nibble & 4: diff += step
        if nibble & 2: diff += step >> 1
        if nibble & 1: diff += step >> 2
        if nibble & 8: diff = -diff
        
        self.predictor += diff
        if self.predictor > 32767:
            self.predictor = 32767
        elif self.predictor < -32768:
            self.predictor = -32768
        
        self.step_index += self.index_table[nibble & 0x0F]
        if self.step_index < 0:
            self.step_index = 0
        elif self.step_index > 88:
            self.step_index = 88
        
        return nibble


def encode_file(input_wav, output_adpcm):
    with wave.open(input_wav, 'rb') as wf:
        nchannels = wf.getnchannels()
        sampwidth = wf.getsampwidth()
        framerate = wf.getframerate()
        nframes = wf.getnframes()
        
        # 读取所有 PCM 数据
        raw_data = wf.readframes(nframes)
    
    # 解析 16bit 采样
    # struct.unpack 解析所有采样
    fmt = '<' + 'h' * (len(raw_data) // 2)
    pcm_samples = list(struct.unpack(fmt, raw_data))
    
    # 立体声转单声道
    if nchannels == 2:
        mono_samples = []
        for i in range(0, len(pcm_samples), 2):
            mono_samples.append((pcm_samples[i] + pcm_samples[i+1]) // 2)
        pcm_samples = mono_samples
    
    # 重采样到 16KHz(简单线性)
    if framerate != 16000:
        old_len = len(pcm_samples)
        new_len = int(old_len * 16000 / framerate)
        
        resampled = []
        for i in range(new_len):
            idx = i * (old_len - 1) / (new_len - 1) if new_len > 1 else 0
            idx_low = int(idx)
            idx_high = min(idx_low + 1, old_len - 1)
            frac = idx - idx_low
            
            val = int(pcm_samples[idx_low] * (1 - frac) + pcm_samples[idx_high] * frac)
            resampled.append(val)
        
        pcm_samples = resampled
    
    # IMA ADPCM 编码
    enc = IMAADPCMEncoder()
    adpcm_bytes = bytearray()
    
    for i in range(0, len(pcm_samples), 2):
        s0 = pcm_samples[i]
        s1 = pcm_samples[i + 1] if i + 1 < len(pcm_samples) else 0
        
        n0 = enc.encode_nibble(s0)
        n1 = enc.encode_nibble(s1)
        
        adpcm_bytes.append((n1 << 4) | n0)
    
    with open(output_adpcm, 'wb') as f:
        f.write(adpcm_bytes)
    
    print(f"PCM samples: {len(pcm_samples)}")
    print(f"PCM bytes: {len(pcm_samples) * 2}")
    print(f"ADPCM bytes: {len(adpcm_bytes)}")
    print(f"Ratio: {len(pcm_samples) * 2 / len(adpcm_bytes):.2f}:1")


if __name__ == "__main__":
    if len(sys.argv) < 3:
        print("Usage: python adpcm_encode.py input.wav output.adpcm")
        sys.exit(1)
    
    encode_file(sys.argv[1], sys.argv[2])

然后使用此 python 脚本把我们生成的 wav 文件生成 adpcm 文件:

bash 复制代码
python adpcm_encode.py 004w_16k.wav 004adp.adpcm

效果如下:
C:\Users\OWNER\Desktop\Demo抽烟报警>python adpcm_encode.py 004w_16k.wav 004adp.adpcm
PCM samples: 81502
PCM bytes: 163004
ADPCM bytes: 40751
Ratio: 4.00:1

转化完成,脚本文件还给我们输出了文件大小ADPCM bytes: 40751

这里得到这个文件,实际上我们都可以先测试一下了,我们直接在程序中定义:

c 复制代码
// 分配空间大小,ADPCM 数据必须填满!
audio_record_t music_record[] = {
    { 0x000000, 40751  }, 
};

然后根据之前文章的学习把刚才生成的 004adp.adpcm 转成 hex :

bash 复制代码
srec_cat.exe 004adp.adpcm -binary -offset 0x32000 -o 004adp.hex -intel
或者
bin2hex.exe --offset=0x32000 004adp.adpcm 004adpcm.hex   

先烧录测试一下看看能否播放:

OK! 测试过,解码播放没问题。

3.3 adpcm 转成 Hex

上面生成了 ADPCM 我们只能播放一段,我们要多放几段,那占用空间就不能像一个那样随意了,至少要保证对齐。

所以我们会多分配一点 flash 空间,为了避免杂音,我们需要补齐分配的大小,这里我们补齐就直接在生成 Hex 的时候进行补齐 0 ,这样可以不破坏音频源文件。

根据我们的 app 中分配的 Flash 区域进行 Hex 生成(第三段故意放长测试填充效果,不填充直接放会有刺耳的声音):

c 复制代码
define ADPCM_FLASH_ADDR    0x32000
audio_record_t music_record[] = {
	{ 0x000000, 51200  },  //ADPCM_FLASH_ADDR 0x32000
	{ 0x00C800, 51200  },  //0x3E800
	{ 0x019000, 102400 },  //0x4B000
};

这里直接上一下指令:

bash 复制代码
srec_cat 004adp.adpcm -binary -offset 0x32000 -fill 0x00 0x32000 0x3E800 -o 004adp_h.hex -intel
srec_cat 005adp.adpcm -binary -offset 0x3E800 -fill 0x00 0x3E800 0x4B000 -o 005adp_h.hex -intel
srec_cat 006adp.adpcm -binary -offset 0x4B000 -fill 0x00 0x4B000 0x64000 -o 006adp_h.hex -intel

使用 srec_info xxx.hex -intel 可以查看 hex 文件位置:

bash 复制代码
C:\Users\OWNER\Desktop\Demo抽烟报警>srec_info 004adp_h.hex -intel
Format: Intel Hexadecimal (MCS-86)
Data:   032000 - 03E7FF

C:\Users\OWNER\Desktop\Demo抽烟报警>srec_info 005adp_h.hex -intel
Format: Intel Hexadecimal (MCS-86)
Data:   03E800 - 04AFFF

C:\Users\OWNER\Desktop\Demo抽烟报警>srec_info 006adp_h.hex -intel
Format: Intel Hexadecimal (MCS-86)
Data:   04B000 - 063FFF

本来以为这样就好了,但是我烧录的时候一直提示 HEX 文件重叠,这个地方我查了一会儿,也使用了上面的指令查大小,按理来说确实不会重叠。

后来我直接把上面转换填充结尾地址减少了一个地址,使用了下面的指令就成功了:

bash 复制代码
srec_cat 004adp.adpcm -binary -offset 0x32000 -fill 0x00 0x32000 0x3E799 -o 004adp_h.hex -intel
srec_cat 005adp.adpcm -binary -offset 0x3E800 -fill 0x00 0x3E800 0x04AFFF -o 005adp_h.hex -intel

最后就是下载测试播放就好了:

疑问补充说明

上面说到,开始以正常地址设置,我在 ISP 工具里面怎么都显示重合 :

bash 复制代码
0x32000 0x3E800 -o 004adp_h.hex -intel
0x3E800 0x4B000 -o 005adp_h.hex -intel
0x4B000 0x64000 -o 006adp_h.hex -intel

但是上文也说了,使用工具读出来确实是不重合的,上文使用了减小一个地址的方式处理,我尝试了把 这个 3 个 Hex 合并,也是可以解决上面问题

bash 复制代码
srec_cat 004adp_h.hex -intel 005adp_h.hex -intel 006adp_h.hex -intel -o all.hex -intel -address-length=4

-address-length=4 参数:

CH585 Flash 地址超过 64K(0x10000),普通 2 字节 Intel HEX 只能表达 0~0xFFFF,大于 0x10000 的高位地址会解析错乱;

该参数输出32 位 4 字节扩展地址 Hex,烧录器能正确识别 0x32000、0x64000 这类高位地址。

烧录的时候选择合并后的 Hex 文件即可:

3.4 指令流程小结

bash 复制代码
步骤 1:
ffmpeg -i 004.mp3 -ar 16000 -ac 1 004_16k.wav

步骤 2:
python adpcm_encode.py 004w_16k.wav 004adp.adpcm
...
...

步骤 3:
srec_cat 004adp.adpcm -binary -offset 0x32000 -fill 0x00 0x32000 0x3E800 -o 004adp_h.hex -intel
srec_cat 005adp.adpcm -binary -offset 0x3E800 -fill 0x00 0x3E800 0x4B000 -o 005adp_h.hex -intel
srec_cat 006adp.adpcm -binary -offset 0x4B000 -fill 0x00 0x4B000 0x64000 -o 006adp_h.hex -intel

步骤 4:合并(可选的,但是个人建议)
srec_cat 004adp_h.hex -intel 005adp_h.hex -intel 006adp_h.hex -intel -o all.hex -intel -address-length=4

结语

本文在之前播放示例的基础上,添加了 ADPCM 解码, 也成功的测试了解码播放效果。

那之前测试过 SBC 解码播放,原始 PCM 播放,加上 ADPCM 解码播放,基本上能够满足嵌入式一般应用的需求了,当然还有些嵌入式设备使用的 是 Speex 编码,这个后期有实际应用再来说明把(主要是博主的应用不需要 = =!可没时间搞了(●'◡'●) )。

好了,本文就到这里。谢谢大家!

相关推荐
矜辰所致23 天前
嵌入式语音开发应用基础说明
ffmpeg·ai 语音·嵌入式语音·语音播放·语音采样
π同学2 个月前
ESP-IDF+vscode开发ESP32第十讲——I2S工程2
vscode·esp32·sd·音频播放
俊基科技2 个月前
HX-01 USB 音频编码模块:全场景语音交互核心,赋能多领域音频应用
语音模组·嵌入式语音·hx‑01
俊基科技2 个月前
基于 FM1188 的 F-18 语音处理模块设计与应用研究
嵌入式硬件·ai降噪·回音消除·语音模组·嵌入式语音
矜辰所致3 个月前
BLE 蓝牙 MAC 地址相关说明
ble·ch585·ble 蓝牙·ble mac地址·irk
矜辰所致6 个月前
CH592 与 CH585 TMOS 中休眠函数的区别
低功耗·ch585·ch592·tmos·休眠唤醒
小邓   ༽6 个月前
左右声道音频,测试左右声道音频
左右声道·声道音频·音频测试·音频解码
矜辰所致6 个月前
CH585 的 LED 屏控制器(串行输出接口)
ch585·串行时钟总线·串行输出·led屏控制器
矜辰所致8 个月前
沁恒 RISC-V 蓝牙芯片 Flash 分区管理及操作
risc-v·flash·flash读写·ch585·蓝牙 ble