3. 解码接口

所有解码被统一管理。解码时系统会根据文件采样率与DAC采样率进行变采样。

只有一路解码时,可不进行变采样,使DAC随着音频文件的采样率调整输出的采样率。

目前SDK默认支持的多个解码通路:

1、2*f1a/b/c+a
2、ump3+a
3、midi+a
3、标准MP3+a

解码管理的接口函数:

void decoder_init(void);
dec_obj *decoder_io(void *pfile, u32 dec_ctl, dp_buff * dbuff,u8 loop);
dec_obj *decoder_list(dec_data_stream *p_strm, u32 dec_ctl, dp_buff *dbuff, u8 loop, u32 output_sr, u16 *p_max_input);
void irq_decoder_ret(dec_obj *obj,u32 ret);

解码功能控制函数:

void decoder_stop(dec_obj *obj, DEC_STOP_WAIT wait);
void decoder_pause(dec_obj *obj);
bool decoder_ff(dec_obj *obj, u8 step);
bool decoder_fr(dec_obj *obj, u8 step);
u32 decoder_get_flen(void *priv);
int decoder_time(dec_obj *p_dec);

各个格式的解码接口函数:

u32 midi_decode_api(void \*strm, void **ppdec, void \*p_dp_buf);
u32 mp3_st_decode_api(void \*strm, void **p_dec, void \*p_dp_buf);
u32 ump3_decode_api(void \*strm, void **p_dec, void \*p_dp_buf);
u32 wav_decode_api(void \*strm, void **p_dec, void \*p_dp_buf);
u32 a_decode_api(void \*strm, void **p_dec, void \*p_dp_buf);
u32 f1a_decode_index(void \*strm, u32 index, dec_obj **p_dec, void \*p_dp_buf, f1x_data \*p_f1x_data);
u32 opus_decode_api(void \*strm, void **p_dec, void \*p_dp_buf);
u32 ima_decode_api(void \*strm, void \**p_dec, void \*p_dp_buf);
u32 sbc_decode_api(void \*strm, void \**p_dec, void \*p_dp_buf);
u32 speex_decode_api(void \*strm, void \**p_dec, void \*p_dp_buf);
u32 jla_lw_decode_api(void \*strm, void \**p_dec, void \*p_dp_buf);

3.1. 解码使用一般流程

在开机时需要调用函数 void decoder_init(void),此函数实现对解码管理器进行初始化,会初始化解码控制器里面的一些变量。

3.1.1. 在解码之前的设备以及文件的初始化

在解码之前要确保已经完成了需要解码的设备,以及设备中的文件进行了初始化。获取到了文件句柄,初始化DAC之后便可以开始正常的解码流程:

1、调用decoder_io实现对解码器的控制,此函数的传参可以控制:

a.对文件在指定的几种解码格式中解析,自动选取正确的格式进行解码;
b.控制解码输出的PCM数据结果是否经过音效处理,目前阶段主要是EQ、SRC;
c.输入一断点buff,有了此buff才能实现续播功能;
d.控制续播的次数;

2、经过上面的一步已经完成了对解码的启动,此时可以调用decoder_pause、decoder_stop实现暂停解码与停止解码的功能;

"图1.1 解码初始化流程"

图1.1 解码初始化流程

3.1.2. 数据流获取

所有解码格式初始化时,会注册数据输入、输出接口给解码器进行回调。用户可根据需求自行修改数据输入、输出接口。

涉及文件apps/app/bsp/common/decoder/mp_io.c,涉及的结构体如下:

struct if_decoder_io {
        void *priv ;
        int (*input)(void *priv, u32 addr, void *buf, int len, u8 type);
        int (*check_buf)(void *priv, u32 addr, void *buf);
        u32(*output)(void *priv, void *data, int len);
        u32(*get_lslen)(void *priv);
        u32(*store_rev_data)(void *priv, u32 addr, int len);
};

结构体中注册的函数原型:

int mp_input(void *priv, u32 addr, void *buf, int len, u8 type)
{
    dec_obj *obj = priv;
    int rlen = 0;
    fs_seek(obj->p_file, addr, SEEK_SET);        //addr为相对文件起始位置的偏移,len为多少个byte
    rlen = fs_read(obj->p_file, buf, len);
    return rlen;
}

/* 解码开始时会调用input接口读取音频文件数据,其中参数addr为文件内的偏移地址,len为读取长度,返回值为读到的字节数; */

int mp_output(void *priv, void *data, int len)
{
         dec_obj *obj = priv;
         return sound_output(&obj->sound, data, len);
}
/* 数据解码完成后解码器会调用output接口将解码后的pcm数据写入dac的buff,其中参数data为数据输出的起始地址,len为输出长度,返回值为输出的字节数; */

3.1.3. 解码流程

1、主循环与解码、AUDIO中断
如图4.1,系统初始化DAC后,AUDIO中断会定时启动并检测是否有解码通道;未注册解码器时,DAC模块空转无声。
当解码器启动时,解码后的pcm数据会写入缓存buff中,AUDIO中断则会不断地消耗缓存buff中的pcm数据,处理后DAC模块出声。
当解码器停止时,AUDIO中断检测到没有解码通道,DAC模块空转无声,重复上述流程。
"图1.3 解码启动与结束的中断行为"

图1.3 解码启动与结束的中断行为

2、解码中断运行具体流程

如图1.4,解码器启动后,首先会启动一次解码中断;

1)解码中断会通过input接口读取音源数据进行解码运算;
2)运算完成后调用output接口将解码后的pcm数据写入到缓存buff中;
3)AUDIO中断会检测已注册的解码通道中是否有数据,有则将数据写入DAC物理buff并融合通道数据、调整音量;
4)DAC消耗pcm数据,并启动下一轮解码中断,循环上述过程直到解码停止或结束。
"图1.4 解码运行具体流程"

图1.4 解码运行具体流程

3.1.4. DAC数据处理说明

DAC中断会调用函数fill_dac_fill_phy()对解码后的pcm数据进行相应处理并填充到DAC的物理buff,该函数处理数据过程如下:

1、解码通道数据轮询

查询各个解码通道是否有被注册、是否有有效数据,若通道已注册且有有效数据,则获取该通道pcm数据的起始地址与长度。

"图1.5 解码通道数据轮询"

图1.5 解码通道数据轮询

2、多通道数据融合

获取每个通道的样点数据,并进行叠加融合。

"图1.6 多通道数据融合"

图1.6 多通道数据融合

3、音量限幅

通道融合后的数据需要进行限幅,并将处理后的数据更新到DAC的物理buff。

"图1.7 音量调节"

图1.7 音量调节

4、启动下一次解码中断

更新缓存buff的信息,减去本次消耗的pcm数据长度,并启动下一次解码中断。

"图1.8 启动下一次解码中断"

图1.8 启动下一次解码中断


3.2. 解码管理接口函数

3.2.1. 函数dec_obj *decoder_io(void *pfile, u32 dec_ctl, dp_buff * dbuff,u8 loop)

此函数实现对以及获取到的文件进行解码,其中参数:

1、void *pfile,已经获取到的文件句柄;
2、u32 dec_ctl,解码控制参数,通过对不BIT的置一操作实现对相应功能的控制:
    ① BIT_MP3_ST:置上此位为可以将MP3解码加入到解码轮询中;
    ② BIT_WAV:置上此位为可以将WAV解码加入到解码轮询中;
3、dp_buff * dbuff,断点缓存,用于续播功能:
    ① NULL,不能实现续播功能,
    ② 非NULL,会使用给定的buff实现续播功能
4、u8 loop,续播次数控制(0~255):
    ① 0,播放完成后不续播;
    ② 1~244,续播次数;
    ③ 255,一直持续续播;
5、返回值,解码句柄,利用解码句柄可以实现对当前解码通道进行控制(主要是暂停和播放)。
"图1.8 启动下一次解码中断"

图1.8 启动下一次解码中断

"图2.1 解码控制参数"

图2.1 解码控制参数

3.2.2. dec_obj *decoder_list(dec_data_stream *p_strm, u32 dec_ctl, dp_buff *dbuff, u8 loop, u32 output_sr)

此函数实现对获取到的数据源进行解码初始化,其中参数:

1、dec_data_stream \*pfile,已经获取到的文件句柄;
2、u32 dec_ctl,解码控制参数,通过对BIT的置一操作实现对相应功能的控制:
    ① BIT_MP3_ST:置上此位为可以将MP3解码加入到解码轮询中;
    ② BIT_WAV:置上此位为可以将WAV解码加入到解码轮询中;
3、dp_buff * dbuff,断点缓存,用于续播功能:
    ① NULL,不能实现续播功能,
    ② 非NULL,会使用给定的buff实现续播功能
4、u8 loop,续播次数控制(0~255):
    ① 0,播放完成后不续播;
    ② 1~244,续播次数;
    ③ 255,一直持续续播;
5、u32 output_sr, 音频解码输出采样率;
6、返回值,解码句柄,利用解码句柄可以实现对当前解码通道进行控制(主要是暂停和播放)。
"图1.8 启动下一次解码中断"

图1.8 启动下一次解码中断

"图2.1 解码控制参数"

图2.1 解码控制参数

3.2.3. 函数void irq_decoder_ret(dec_obj *obj,u32 ret)

此函数是解码器在解码过程中的回调函数,不能再这里做长时间的操作,因为这样会卡慢解码的速度,其中参数:

1、dec_obj *obj,解码句柄
2、ret,解码运行的结果,0为成功,其他如表2.1和图2.2
3、返回值,无。

irq_decoder_ret返回值

含义

MAD_ERROR_FILE_END

解码文件结束

MAD_ERROR_FILESYSTEM_ERR

未使用

MAD_ERROR_DISK_ERR

未使用

MAD_ERROR_SYNC_LIMIT

文件错误

MAD_ERROR_FF_FR_FILE_END

快进到头

MAD_ERROR_FF_FR_END

未使用

MAD_ERROR_FF_FR_FILE_START

快退到头

MAD_ERROR_LIMIT

未使用

MAD_ERROR_NODATA

未使用

MAD_ERROR_STREAM_NODATA

本轮run读不到数据

MAD_CANNOT_SYNC_TSLOOP

本轮没有找到同步字

MAD_THISREAD_LT_NEEDSZ

本次读数据没读够

MAD_FRAMELEN_GT_BUFFSZ

压缩帧长超出范围

表2.1 irq_decoder_ret返回值

"图2.2 解码回调函数的相关处理"

图2.2 解码回调函数的相关处理


3.3. 解码功能控制函数

3.3.1. 函数void decoder_pause(dec_obj *obj)

此函数实现在解码过程中对当前解码进行暂停播放与继续播放的控制,其中参数:

1、dec_obj *obj,解码句柄,启动解码时从函数decoder_io获取的返回值;

3.3.2. 函数void decoder_stop(dec_obj *obj, DEC_STOP_WAIT wait)

此函数实现停止当前解码的功能,其中参数:

1、obj,解码句柄,启动解码时从函数decoder_io获取的返回值;
2、wait,是否等待DAC消耗完解码输出的PCM数据后再停止解码;
    ① NO_WAIT,直接停止解码
    ② NO_WAIT,等待DAC消耗完全部的解码输出数据后再停止解码
"图3.1 停止解码等待变量的DEC_STOP_WAIT"

图3.1 停止解码等待变量的DEC_STOP_WAIT

3.3.3. 函数bool decoder_ff(dec_obj *obj, u8 step)

此函数实现解码快进功能,仅播放标准MP3和WAV有效,其中参数:

1、obj:解码句柄,启动解码时从函数decoder_io获取的返回值;
2、step:快进步进,单位为秒;

3.3.4. 函数bool decoder_fr(dec_obj *obj, u8 step)

此函数实现解码快退功能,仅播放标准MP3和WAV有效,其中参数:

1、obj:解码句柄,启动解码时从函数decoder_io获取的返回值;
2、step:快退步进,单位为秒;

3.3.5. 函数int decoder_time(dec_obj *p_dec)

此函数实现获取当前播放时间位置功能,其中参数:

1、obj:解码句柄,启动解码时从函数decoder_io获取的返回值;
2、返回值:当前正在播放的时间位置,单位为秒;

3.4. 各个格式的解码函数

各种格式的解码启动函数统一被decoder_io所调用,其一般名称为:XXX_decode_api。XXX为其格式名称。

u32 mp3_decode_api(void *p_file, void **p_dec,void *p_dp_buf)
u32 wav_decode_api_1(void *p_file,void **p_pdec,void *t_dp_buf)

解码MP3音频时会进行MP3格式检查,格式检查可能需要读取多段音源文件数据,对于部分读取速度较慢的设备,可能会无法清狗而导致看门狗复位;故在mp3_standard_api.c文件中有FORMAT_CHECK_TIMEOUT_10MS宏定义,当格式检查超过一定时间时,强制退出格式检查并返回错误。

#define MP3_FORMAT_CHECK_TIMEOUT_10MS     400
//MP3格式检查超时时间,单位10ms,400即4秒,一般不超过看门狗复位时间的一半

3.4.1. 统一介绍decode_api

函数u32 XXX_decode_api(void *p_file, void **p_dec,void *p_dp_buf):

① void *p_file,文件句柄
② void **p_dec,存放解码句柄指针的指针
③ void *p_dp_buf,断点buff,提供给续播使用

3.5. 解码常见问题

3.5.1. 播歌有卡音或者杂音如何查找问题

播歌有卡音或者杂音通常有三种情况:

3.5.1.1. 函数fill_dac_fill()从缓存buff中取不到数据

这种情况可以通过查看调用函数cbuf_read_alloc()后打印的字符x、y和z来判断,通常卡音与字符打印会同时出现;该问题出现的原因可能是解决被高优先级中断打断或者速度不够解不过来,可以尝试提高系统时钟频率。

"图5.1 AUDIO_DAC取不到数据"

图5.1 AUDIO_DAC取不到数据

3.5.1.2. DAC中断被打断导致没有定时起中断

AW30N在使用APA输出时,可以通过在dac中断函数中调用到的fifo_dac_fill()函数中翻转I/O口,检查进入函数的频率是否固定;同时检查是否有更高优先级的中断挡住DAC中断或者关总中断时间过长的地方。

"图5.2 DAC中断"

图5.2 DAC中断

如果使用的是RDAC输出,可以通过以上方法翻转IO,但是一次dac中断可能会二次调用fifo_dac_fill()函数,所以需要看整体的时间是否固定。

3.5.1.3. 音源本身有杂音

这种情况可以通过将音源放在电脑端播放验证。

3.5.2. 解码开始后声音变调问题

这种情况通常是因为音频文件的采样率与DAC的采样率不一致,需要进行SRC变采样;而工程占用了太多ram资源,SRC变采样模块申请不到空间不起作用导致播放变调;可以通过查看打印中是否有“src init fail”来确定该问题。