前言
为了在海思平台上使用tlv320aic3254,花了大概2个星期研究海思音频部分的手册、3254的用户手册,最终参考tlv320aic31的代码,实现了3254的驱动,同时在mpp的sample中,增加了3254对应的宏,以及相应的初始化代码,这篇文章主要是做一个阶段性的梳理
参考资料
资料 | 说明 |
---|---|
ReleaseDoc\zh\00.hardware\chip\Hi3531DV100 H.265编解码处理器用户指南.pdf | 寄存器说明中文版 |
ReleaseDoc\en\00.hardware\chip\Hi3531D V100 H.265 CODEC Processor Data Sheet.pdf | 寄存器说明英文版 |
ReleaseDoc\zh\00.hardware\chip\Hi3531DV100_PINOUT_CN.xlsx | 引脚说明 |
ReleaseDoc\zh\01.software\board\HiMPP V3.0 媒体处理软件开发参考.pdf | mpp API说明 |
TLV320AIC3254 Application Reference Guide (Rev. A).pdf | codec用户手册 |
海思音频相关知识
海思的mpp(Media Process Platform)媒体处理平台针对音频提供了4种类型的API子模块,分别是AI(音频输入)、AO(音频输出)、ADEC(音频编码)、AENC(音频编码),本文的重点是AI和AO,编解码不做介绍
音频接口和 AI、AO 设备
-
音频输入输出接口简称为 AIO(Audio Input/Output)接口,用于和 Audio Codec 对接,完成声音的录制和播放。AIO 接口分为两种类型:当为输入类型时,称为 AIP,当为输出类型时,称为 AOP
-
Hi3531DV100 内部集成 1 个 AIAO,包含 3 个 AIP(Audio Input Port)和 3 个 AOP(Audio Output Port)
-
AIAO 接口支持 I2S 和 PCM 两种模式,采用 DMA 方式存取数据,本文只针对I2S讲解
-
软件中负责抽象音频接口输入功能的单元,称之为 AI 设备;负责抽象音频接口输出功能的单元,称之为 AO 设备
录音和播放原理
原始音频信号以模拟信号的形式给出后,通过 Audio Codec,按一定采样率和采样精度转换为数字信号。Audio Codec 以 I2S 时序或 PCM 时序的方式,将数字信号传输给 AI设备。芯片利用 DMA 将 AI 设备中的音频数据搬移到内存中,完成录音操作。
播放和录音是基于同样的原理。芯片利用 DMA 将内存中的数据传输到 AO 设备。AO设备通过 I2S 时序或 PCM 时序向 Audio Codec 发送数据。Audio Codec 完成数字信号到模拟信号的转换过程,并输出模拟信号
AIAO I2S连接示意图
从这张图可以得到一个信息,内部的AIP和AOP和外部的I2S引脚有一个映射关系,tlv320aic32x4作为一个即能录制,又能播放的codec,应该选用上面的I2S2,内部对应的是AIP2和AOP0
打开《Hi3531DV100_PINOUT_CN.xlsx》,在3.管脚复用寄存器中搜I2S2,如图
-
MCLK:主时钟,hi3531d会根据采样率的不同,生成一个主时钟,频率8M~15M不等
-
BLCK:位时钟,每发送一个位的数据,都需要靠位时钟实现同步,计算公式为采样率 x 采样精度 x 声道数
-
WS:声道选择,0和1表示传输不同的声道,频率等于采样频率
-
SD_TX:海思发送引脚
-
SD_RX:海思接收引脚
上面的5个引脚,默认功能就是I2S,可以用himm工具确认一下I2S引脚的复用情况
I2S主从模式
海思和3254均支持主模式或者从模式,当海思作为master时,将向外提供BCLK和WS,海思作为slave时,BLCK和WS由外部输入,codec也是一样。
两者的区别在于,海思不管是做master还是slave,一定会向外提供主时钟MCLK,因此codec不再需要单独的外部晶振,也不需要向外输出MCLK
硬件连接
海思 | codec |
---|---|
I2S2_BCLK_RX | BCLK |
I2S2_WS_RX | WCLK |
I2S2_SD_RX | DOUT |
I2S2_SD_TX | DIN |
I2S2_MCLK | MCLK |
tlv320aic3254驱动
驱动就三个文件:tlv320aic32x4.c、tlv320aic32x4.h、Makefile,下载链接
框架部分参考了tlv320aic31,内容部分做了比较大的改动
准备工作
在mpp/extdrv目录下,创建tlv320aic32x4目录,并将三个文件拷贝到其中,然后退回上层,在extdrv目录下,执行make,这时会生成对应的驱动程序
驱动代码的说明
为了防止文档较长,代码仅作节选
c文件
init函数主要是注册了一个misc设备,并进行了codec设备的初始化
static int __init tlv320aic32x4_init(void)
{misc_register(&tlv320aic32x4_dev);i2c_client_init();tlv320aic32x4_device_init();return 0;
}static void __exit tlv320aic32x4_exit(void)
{tlv320aic32x4_device_exit();misc_deregister(&tlv320aic32x4_dev);i2c_client_exit();
}
在注册misc设备的时候,给了一个结构体,这个驱动的主要内容,都在ioctl函数里面,open和close是什么都不做的
static struct file_operations tlv320aic32x4_fops =
{.owner = THIS_MODULE,.unlocked_ioctl = tlv320aic32x4_ioctl,.open = tlv320aic32x4_open,.release = tlv320aic32x4_close
};
static struct miscdevice tlv320aic32x4_dev =
{MISC_DYNAMIC_MINOR,I2C_DEV_NAME,&tlv320aic32x4_fops,
};
在ioctl内部,将应用层传下来的参数copy_from_user到内核空间,然后根据cmd的类型,分别进行不同的操作
在文件的开头,也定义了一部分内容
/* define MICRO */
#define I2C_DEV (1) // tlv_aic32x4 use i2c-1
#define I2C_DEV_NAME "tlv320aic32x4" // I2C dev info
#define I2C_DEV_ADDR (0x30) // i2c dev address
hi3531d只有i2c-0和i2c-1,因此使用的是哪个,I2C_DEV就定义哪个
I2C_DEV_NAME是指定insmod驱动后,在dev目录下生成的节点名字
I2C_DEV_ADDR指定了codec的设备地址,7位地址原本是0x18,0x30是经过移位的,在海思平台下,无论是应用层还是驱动层,调用i2c都必须用移位后的地址,读写不区分,都是这个地址
#define DEBUG_LEVEL 3
#define DPRINTK(level,fmt,args...) do{ if(level < DEBUG_LEVEL)\printk(KERN_INFO "%s [%s, line-%d]: " fmt "\n",I2C_DEV_NAME,__FUNCTION__,__LINE__,##args);\}while(0)
这个宏定义主要是方便信息的打印,比如此处等级设成3,那么等级为0,1,2的,就一定会打印出来,如果不希望打印过多的调试信息,level最好改为1,只打印严重错误的信息
/* global variable */
static struct i2c_board_info hi_info =
{I2C_BOARD_INFO(I2C_DEV_NAME, I2C_DEV_ADDR),
};
static struct i2c_client* tlv_client;
static unsigned int cur_page = 0;static const struct aic32x4_rate_divs aic32x4_divs[] = {/* hi3531d as master, aic32x4 as slave, it will be more easy to set parameters*//* channels less than 20 *///mclk rate nadc madc aosr ndac mdac dosr {12288000, AIC32x4_SAMPLE_RATE_48K, 1, 2, 128, 1, 2, 128 },{12288000, AIC32x4_SAMPLE_RATE_24K, 1, 4, 128, 1, 2, 256 },{12288000, AIC32x4_SAMPLE_RATE_12K, 1, 8, 128, 1, 2, 512 },{8192000, AIC32x4_SAMPLE_RATE_32K, 1, 2, 128, 1, 2, 128 },{8192000, AIC32x4_SAMPLE_RATE_16K, 1, 4, 128, 1, 2, 256 },{8192000, AIC32x4_SAMPLE_RATE_8K, 1, 8, 128, 1, 2, 512 },{11289600, AIC32x4_SAMPLE_RATE_44K, 1, 2, 128, 1, 2, 128 },{11289600, AIC32x4_SAMPLE_RATE_22K, 1, 4, 128, 1, 2, 256 },{11289600, AIC32x4_SAMPLE_RATE_11K, 1, 8, 128, 1, 2, 512 },//mclk rate 0x12 0x13 0x14 0x0b 0x0c 0x0d-0x0e /* channels equal 20 *///mclk rate nadc madc aosr ndac mdac dosr // {15360000, 48000, 1, 2, 128, 1, 2, 128 },// {15360000, 24000, 1, 4, 128, 1, 2, 256 },// {15360000, 12000, 1, 8, 128, 1, 2, 512 },// {10240000, 32000, 1, 2, 128, 1, 2, 128 },// {10240000, 16000, 1, 4, 128, 1, 2, 256 },// {10240000, 8000, 1, 8, 128, 1, 2, 512 },// {14112000, 44100, 1, 2, 128, 1, 2, 128 },// {14112000, 22050, 1, 4, 128, 1, 2, 256 },// {14112000, 11025, 1, 8, 128, 1, 2, 512 },};
i2c_client* tlv_client
实际上就是一个句柄,每次i2c读或写都要用到它。
cur_page主要是用来标记当前的读写的寄存器是多少页的,因为3254这个codec号称有128页,每一页最多有128个寄存器。
aic32x4_divs这个结构体,目的是为了给3254内部的dac、adc等设备时钟提供一个分频系数,不管是主还是从模式,这一步都是必须做的 。
海思应用层的通道数定义为20时的情况,目前没有做过测试,所以屏蔽掉了
static int i2c_client_init(void);
static void i2c_client_exit(void);
int tlv320aic32x4_read(unsigned char chip_addr, unsigned char reg_addr, unsigned char *reg_data);
int tlv320aic32x4_read_test(unsigned char chip_addr, unsigned char reg_addr, unsigned char *reg_data);
int tlv320aic32x4_write(unsigned char chip_addr, unsigned char reg_addr, unsigned char value);
以上这几个函数看名字就知道是干啥了,read读出来的值,放到形参指针中,read_test是read的一个简化,主要是调试阶段做测试用的
static int tlv320aic32x4_reg_list(void);
static int tlv320aic32x4_set_divs(int num);
static int tlv320aic32x4_get_divs(int rate);
static int tlv320aic32x4_soft_reset(void);
reg_list会打印页0和页1的所有寄存器,get_divs和set_divs是设置codec内部的分频系数,用的是上面的结构体aic32x4_divs[]
tlv320aic32x4_soft_reset会重置,并给所有的寄存器一个初始值,codec会设成i2s从模式,48k采样率,16bit位深,总之一般情况下,reset之后,是一个正常的配置,不是寄存器还原为默认值
static int tlv320aic32x4_device_init(void)
{tlv320aic32x4_soft_reset();tlv320aic32x4_set_divs(0);return 0;
}
static int tlv320aic32x4_device_exit(void)
{ret = tlv320aic32x4_write(I2C_DEV_ADDR, AIC32X4_PAGE_SEL, 0x0); ret = tlv320aic32x4_write(I2C_DEV_ADDR, AIC32X4_SOFT_RST, 0x01); msleep(10); return 0;
}
init会按默认配置,做好所有的设置,codec能正常工作,exit会写reset寄存器,让codec不再工作
头文件
驱动的c文件从结构上来看,还是比较好懂的,没用用到很复杂的东西,下面看头文件
struct aic32x4_rate_divs {u32 mclk;u32 rate;u8 nadc;u8 madc;u8 aosr;u8 ndac;u8 mdac;u16 dosr;
};
与set_divs和get_divs函数有关,c文件中定义了结构体变量aic32x4_divs[]
typedef union
{struct audio_interface_t interface;struct other_config_t other;struct adc_dac_config_t adc;struct adc_dac_config_t dac;struct agc_config1_t agc_conf1;struct agc_config2_t agc_conf2;struct agc_config3_t agc_conf3;struct agc_config4_t agc_conf4;struct audio_power_up_t powerup;struct hp_route_t hp;struct lo_route_t lo;struct output_volume_t output_volume;struct output_mute_t output_mute;struct micpga_left_t micpga_left;struct micpga_right_t micpga_right;struct micpga_volum_t micpga_volume;struct float_input_t float_input;
}Audio_Ctrl;
由于有众多的选项需要配置,为了方便应用层调用ioctl,同时也方便ioctl 的cmd定义,做了这样一个联合体,需要配置哪个部分,就对哪个部分进行赋值
在c文件中,可以看到ioctl中有这么一段,其实就是接收应用层的cmd参数
Audio_Ctrl temp;
Audio_Ctrl* audio_ctrl;if (argp != NULL)
{if (copy_from_user(&temp, argp, sizeof(Audio_Ctrl))){return -EFAULT;}
}
audio_ctrl = (Audio_Ctrl*)(&temp);
结构体说明
接下来详细解释一下各个结构体,为了理解这些结构体的含义,需要看3254的手册,那个路由的图,以及各个寄存器说明
struct audio_interface_t
{unsigned int sample_rate;unsigned char transfer_mode;unsigned char master_slave_mode;unsigned char chn_num;unsigned char bit_width;unsigned char data_offset;
};
sample_rate是音频的采样率,目前常用的是48k,44.1k和8k
transfer_mode传输模式,只做了i2s的调试,pcm理论上是可以,但是没有调过
master_slave_mode规定了3254的主从模式,注意是3254,不是海思的
chn_num通道数,可以分时复用传输多个声道的数据,但是一般是两个声道的立体声
bit_width位深,也叫采样精度,目前固定为16bit
data_offset数据在bclk时钟的偏移,这个参数给0就行,实际上从波形看,海思和codec都是偏移了一个blck周期
这个结构体针对dac和adc,联合体Audio_Ctrl中,既定义了adc,也定义了dac
struct adc_dac_config_t
{unsigned char power_up;unsigned char left_power_up;unsigned char right_power_up;unsigned char mute;unsigned char left_mute;unsigned char right_mute;unsigned char volume;unsigned char left_volume;unsigned char right_volume;
};
power_up左右都使能
left_power_up左边使能
right_power_up右边使能
3254这款codec的配置做的很细,为了减少代码量,可以直接用power_up,实现left_power_up加right_power_up的功能,如果想要分开设置,也可以单独进行left_power_up或者right_power_up,ioctl提供了三个命令(ADC_POWER_UP 、ADC_LEFT_POWER_UP 、ADC_RIGHT_POWER_UP)来实现这种功能。在其它的结构体中也可以看到大量这种形式的处理,下文不再单独描述
mute静音设置
volume音量调节
struct other_config_t
{unsigned char loop_data_in_to_data_out;unsigned char loop_adc_to_dac;unsigned char micbias_power_up;unsigned char micbias_output_voltage;unsigned char micbias_voltage_source;unsigned char adc_signal_block;unsigned char dac_signal_block;
};
loop_data_in_to_data_out 数据从data in进入后,直接环出到data out,不经过任何通道、adc、dac之类的,这个一般是不用的,测试可以看有没用声音
loop_adc_to_dac 数据从adc进入,然后环出到dac,中间不经过其它处理,同上,一般不用,仅做测试
micbias_xxx 针对数字麦克风的,用不上的话就不用管,需要使用就参考3254的文档说明
adc_signal_block和dac_signal_block,是内置的处理算法,一般用默认的就行
struct agc_config1_t
struct agc_config2_t
struct agc_config3_t
struct agc_config4_t
agc这些参数我也不怎么懂,按照手册做了一下
struct audio_power_up_t {unsigned char hp_power_up;unsigned char hpl_power_up;unsigned char hpr_power_up;unsigned char lo_power_up;unsigned char lol_power_up;unsigned char lor_power_up;unsigned char ma_power_up;unsigned char mal_power_up;unsigned char mar_power_up;
};
headphone,Mixer Amplifier、line out的使能
struct hp_route_t {unsigned char ldac_hpl;unsigned char in1l_hpl;unsigned char mal_hpl;unsigned char mar_hpl;unsigned char ldac_hpr;unsigned char rdac_hpr;unsigned char in1r_hpr;unsigned char mar_hpr;unsigned char hpl_hpr;
};
headphone左右通道数据的来源,可以来自dac,in1,或者ma
struct lo_route_t {unsigned char ldac_lol;unsigned char rdac_lol;unsigned char mal_lol;unsigned char lor_lol;unsigned char rdac_lor;unsigned char mar_lor;
};
line out的左右通道数据来源
struct output_volume_t {unsigned char hp_volume;unsigned char hpl_volume;unsigned char hpr_volume;unsigned char lo_volume;unsigned char lol_volume;unsigned char lor_volume;unsigned char in1l_hpl_volume;unsigned char in1r_hpr_volume;unsigned char ma_volume;unsigned char mal_volume;unsigned char mar_volume;
};
输出音频,主要是hp、lo、ma的音量,另外in1可以不经过其它处理,直接路由到hp,这个音量也是可以调节的
struct output_mute_t {unsigned char hp_mute;unsigned char hpl_mute;unsigned char hpr_mute;unsigned char lo_mute;unsigned char lol_mute;unsigned char lor_mute;
};
输出静音,没什么好说的
struct micpga_left_t {unsigned char in1l_micpga;unsigned char in2l_micpga;unsigned char in3l_micpga;unsigned char in1r_micpga;unsigned char cm_micpga_via_cm1l;unsigned char in2r_micpga;unsigned char in3r_micpga;unsigned char cm_micpga_via_cm2l;
};struct micpga_right_t {unsigned char in1r_micpga;unsigned char in2r_micpga;unsigned char in3r_micpga;unsigned char in1l_micpga;unsigned char cm_micpga_via_cm1r;unsigned char in2l_micpga;unsigned char in3l_micpga;unsigned char cm_micpga_via_cm2r;
};struct micpga_volum_t {unsigned char power_up;unsigned char left_power_up;unsigned char right_power_up;unsigned char volume;unsigned char left_volume;unsigned char right_volume;
};
左右micpga的路由设置,图上画的很清楚
struct float_input_t {unsigned char in1l_cm;unsigned char in1r_cm;unsigned char in2l_cm;unsigned char in2r_cm;unsigned char in3l_cm;unsigned char in3r_cm;
};
这个是什么我没搞懂,反正暂时也不用
寄存器宏定义
也只节选一部分,这部分将所有使用的寄存器地址定义成了宏,方便调用,第二页就加上128,在调用read或write时,寄存器地址与上0x7f,buf[0] = (*reg_addr* & 0x7f)
,将这个128去掉即可
// page define
#define AIC32X4_PAGE0 0
#define AIC32X4_PAGE1 128// page 0 registers
#define AIC32X4_PAGE_SEL 0x00 //页选择寄存器
#define AIC32X4_SOFT_RST 0x01 //写该位为1,重置所有寄存器,之后需要10ms延时
#define AIC32X4_CLK_SEL 0x04 //PLL和CODEC_CLKIN的时钟源选择
#define AIC32X4_PLLPR 0x05 //PLL使能、P和R值,如果CODEC_CLKIN来源于PLL,则 CODEC_CLKIN = (PLL*R*J.D)/P
#define AIC32X4_PLLJ 0x06 //J值
...
// page 1 registers
#define AIC32X4_POWER_CTL (AIC32X4_PAGE1 + 0x01) //是否断开AVDD与DVDD的弱连接
#define AIC32X4_LDO_CTL (AIC32X4_PAGE1 + 0x02) //LDO电压控制,由于不是手持设备,目前不需要使用LDO给内部提供电压
#define AIC32X4_PLAYBACK_CTL1 (AIC32X4_PAGE1 + 0x03) //与PowerTune功率控制有关,默认是最大功率(最高性能)
#define AIC32X4_PLAYBACK_CTL2 (AIC32X4_PAGE1 + 0x04) //与PowerTune功率控制有关,默认是最大功率(最高性能)
#define AIC32X4_OUTPUT_PWR (AIC32X4_PAGE1 + 0x09) //音频输出使能
ioctl cmd宏定义
节选了一部分,这个cmd是给应用层调用的,之前说过,比如设置adc的mute,可以使用ADC_MUTE命令,将左右一起设置,也可以使用ADC_LEFT_MUTE和ADC_RIGHT_MUTE进行分开设置
// ioctl cmd
#define SOFT_RESET _IOW('Z', 0x01, Audio_Ctrl) //复位并进行默认的初始化设置
#define LIST_REG_VALUE _IOW('Z', 0x02, Audio_Ctrl) //列出页0和1的所有寄存器值
#define AUDIO_INTERFACE _IOW('Z', 0x03, Audio_Ctrl) //音频接口设置(只支持I2S)
#define BIT_WIDTH _IOW('Z', 0x04, Audio_Ctrl) //位深(只支持16bit)
#define MASTER_SLAVE_MODE _IOW('Z', 0x05, Audio_Ctrl) //主从模式
其它宏定义
其它一些宏也没什么好说的,都比较好理解
// micpga route
#define AIC32x4_NOT_ROUTED 0
#define AIC32x4_ROUTED_WITH_10K_RES 1
#define AIC32x4_ROUTED_WITH_20K_RES 2
#define AIC32x4_ROUTED_WITH_40K_RES 3
micpga进行混音的时候,可以决定权重,如果某个通道设为AIC32x4_ROUTED_WITH_10K_RES,则声音最大,设为AIC32x4_ROUTED_WITH_20K_RES,声音就会减半,设为AIC32x4_NOT_ROUTED声音就没了
//agc para limit
#define AGC_2BITS_MAX 3
#define AGC_3BITS_MAX 7
#define AGC_4BITS_MAX 15
#define AGC_5BITS_MAX 31
#define AGC_6BITS_MAX 63
#define AGC_7BITS_MAX 127
#define AGC_8BITS_MAX 255
agc的很多参数,都是好几位,为了不让应用层传递错误的参数破坏寄存器的其他位,需要限定一下
测试程序
3254的配置比默认的aic31多了好几倍,而且参数都不一样,因此需要单独为它写配置程序
修改Makefile.param
测试程序需要用到驱动这边头文件定义的cmd、联合体、结构体,因此要想办法添加进来,打开mpp\sample\Makefile.param文件
################ select audio codec type for your sample ################
#external acodec
#ACODEC_TYPE ?= ACODEC_TYPE_TP2823
#ACODEC_TYPE ?= ACODEC_TYPE_NVP6134
#ACODEC_TYPE ?= ACODEC_TYPE_TLV320AIC31
ACODEC_TYPE ?= ACODEC_TYPE_TLV320AIC32x4
注意到有这样一段话,用来定义codec类型的,原本是没有320aic31和320aic32x4,现在加上31和32x4,并且只打开32x4
同时这段话要挪一个位置,放在下面这段话之后
ifeq ($(PARAM_FILE), )PARAM_FILE:=../../Makefile.paraminclude $(PARAM_FILE)
endif#放在这里
################ select audio codec type for your sample ################
#external acodec
#ACODEC_TYPE ?= ACODEC_TYPE_TP2823
#ACODEC_TYPE ?= ACODEC_TYPE_NVP6134
#ACODEC_TYPE ?= ACODEC_TYPE_TLV320AIC31
ACODEC_TYPE ?= ACODEC_TYPE_TLV320AIC32x4
原来的sample,会默认将aic31的头文件加进去
INC_FLAGS += -I$(SDK_PATH)/mpp/$(EXTDRV)/tlv320aic31
但是用32x4时,不能有aic31的头文件,因为一些名称我没有做改动,所以会重名,因此做了以下改动,tlv320aic32x4这个目录在之前讲过了,放了3个文件,其中就有32x4的头文件
ifeq ($(ACODEC_TYPE), ACODEC_TYPE_TLV320AIC)INC_FLAGS += -I$(SDK_PATH)/mpp/$(EXTDRV)/tlv320aic31
endififeq ($(ACODEC_TYPE), ACODEC_TYPE_TLV320AIC32x4)INC_FLAGS += -I$(SDK_PATH)/mpp/$(EXTDRV)/tlv320aic32x4
endif
另外,sample中,是靠宏来决定对哪个codec进行配置,因此也需要按照它的结构,定义相应的宏,这个宏HI_ACODEC_TYPE_TLV320AIC32x4将在函数中起作用
ifeq ($(ACODEC_TYPE), ACODEC_TYPE_TLV320AIC)CFLAGS += -DHI_ACODEC_TYPE_TLV320AIC31
endififeq ($(ACODEC_TYPE), ACODEC_TYPE_TLV320AIC32x4)CFLAGS += -DHI_ACODEC_TYPE_TLV320AIC32x4
endif
修改sample_audio.c
mpp\sample\audio\sample_audio.c,这个文件就是音频测试的主要文件,它上来就#include "tlv320aic31.h"
,这样肯定是不行的,改为条件编译
注意这里是多个条件,不要用ifdef
#if (defined HI_ACODEC_TYPE_TLV320AIC31)
#include "tlv320aic31.h"
#elif (defined HI_ACODEC_TYPE_TLV320AIC32x4)
#include "tlv320aic32x4.h"
#endif
它这个程序,个人感觉做的并不好,特别是通道参数或者模式改了,可能就编译不过,或者程序运行不了,因此我的做法是自己实现一个采集到播放的过程,并保存为pcm格式
首先在main函数中的switch中,添加5,它这个程序运行后,会让我们输入数字,来执行对应的程序,这个程序就是AIP2采集后,播放到AOP0中
case '5':
{SAMPLE_AUDIO_Aip2_Aop0();break;
}
然后修改一下使用提示usage
HI_VOID SAMPLE_AUDIO_Usage(HI_VOID)
{
...printf("\t5: start AI to AO(api2->apo0) loop\n");
...
}
最后就是重头戏,被调用的函数
/******************************************************************************
* function : Ai -> Ao
******************************************************************************/
extern SAMPLE_AI_S gs_stSampleAi[AI_DEV_MAX_NUM*AIO_MAX_CHN_NUM];
extern void *SAMPLE_COMM_AUDIO_AiProc_modby_glx(void *parg);
HI_S32 SAMPLE_AUDIO_Aip2_Aop0(HI_VOID)
{HI_S32 s32Ret, i;HI_S32 s32AiChnCnt;HI_S32 s32AoChnCnt;AUDIO_DEV AiDev = SAMPLE_AUDIO_TLV320_AI_DEV;AUDIO_DEV AoDev = SAMPLE_AUDIO_TLV320_AO_DEV;AI_CHN AiChn = 0;AO_CHN AoChn = 0;AIO_ATTR_S stAioAttr;stAioAttr.enSamplerate = AUDIO_SAMPLE_RATE_48000;stAioAttr.enBitwidth = AUDIO_BIT_WIDTH_16;stAioAttr.enWorkmode = AIO_MODE_I2S_MASTER;// stAioAttr.enWorkmode = AIO_MODE_I2S_SLAVE;stAioAttr.enSoundmode = AUDIO_SOUND_MODE_MONO;// stAioAttr.enSoundmode = AUDIO_SOUND_MODE_STEREO;stAioAttr.u32EXFlag = 1;stAioAttr.u32FrmNum = 30;stAioAttr.u32PtNumPerFrm = 320;stAioAttr.u32ChnCnt = 2;stAioAttr.u32ClkChnCnt = 2;stAioAttr.u32ClkSel = 1;/* config audio codec */s32Ret = SAMPLE_COMM_AUDIO_CfgTlv320(&stAioAttr);if (HI_SUCCESS != s32Ret){SAMPLE_DBG(s32Ret);return HI_FAILURE;}/* enable AI channle */s32AiChnCnt = stAioAttr.u32ChnCnt >> stAioAttr.enSoundmode;g_u32AiCnt = s32AiChnCnt;g_u32AiDev = AiDev;s32Ret = HI_MPI_AI_SetPubAttr(AiDev, &stAioAttr);if (s32Ret){printf("%s: HI_MPI_AI_SetPubAttr(%d) failed with %#x\n", __FUNCTION__, AiDev, s32Ret);return s32Ret;}s32Ret = HI_MPI_AI_Enable(AiDev);if (s32Ret){printf("%s: HI_MPI_AI_Enable(%d) failed with %#x\n", __FUNCTION__, AiDev, s32Ret);return s32Ret;}for (i = 0; i < s32AiChnCnt; i++){s32Ret = HI_MPI_AI_EnableChn(AiDev, i);if (s32Ret){printf("%s: HI_MPI_AI_EnableChn(%d,%d) failed with %#x\n", __FUNCTION__, AiDev, i, s32Ret);return s32Ret;}}/* enable AO channle */stAioAttr.u32ChnCnt = stAioAttr.u32ChnCnt>2 ? 2: stAioAttr.u32ChnCnt;s32AoChnCnt = stAioAttr.u32ChnCnt >> stAioAttr.enSoundmode;g_u32AoCnt = s32AoChnCnt;g_u32AoDev = AoDev;s32Ret = HI_MPI_AO_SetPubAttr(AoDev, &stAioAttr);if (HI_SUCCESS != s32Ret){printf("%s: HI_MPI_AO_SetPubAttr(%d) failed with %#x!\n", __FUNCTION__, \AoDev, s32Ret);return HI_FAILURE;}s32Ret = HI_MPI_AO_Enable(AoDev);if (HI_SUCCESS != s32Ret){printf("%s: HI_MPI_AO_Enable(%d) failed with %#x!\n", __FUNCTION__, AoDev, s32Ret);return HI_FAILURE;}for (i = 0; i < s32AoChnCnt; i++){s32Ret = HI_MPI_AO_EnableChn(AoDev, i);if (HI_SUCCESS != s32Ret){printf("%s: HI_MPI_AO_EnableChn(%d) failed with %#x!\n", __FUNCTION__, i, s32Ret);return HI_FAILURE;}}/* create file for save frames*/FILE *pfd[s32AoChnCnt];for (i = 0; i < s32AoChnCnt; i++){HI_CHAR aszFileName[128];sprintf(aszFileName, "audio_chn%d.%s", i, SAMPLE_AUDIO_Pt2Str(PT_LPCM));pfd[i] = fopen(aszFileName, "w+");if (NULL == pfd){printf("%s: open file %s failed\n", __FUNCTION__, aszFileName);return -1;}printf("open stream file:\"%s\" ok\n", aszFileName);}/* AI to AO channel */for (i=0; i<g_u32AoCnt; i++){AiChn = i;AoChn = i;SAMPLE_AI_S *pstAi = NULL;pstAi = &gs_stSampleAi[AiDev*AIO_MAX_CHN_NUM + AiChn];pstAi->bSendAenc = HI_FALSE;pstAi->bSendAo = HI_TRUE;pstAi->bStart= HI_TRUE;pstAi->AiDev = AiDev;pstAi->AiChn = AiChn;pstAi->AoDev = AoDev;pstAi->AoChn = AoChn;pstAi->pfd = pfd[i];pthread_create(&pstAi->stAiPid, 0, SAMPLE_COMM_AUDIO_AiProc_modby_glx, pstAi);if (s32Ret != HI_SUCCESS){SAMPLE_DBG(s32Ret);return HI_FAILURE;}}printf("\nplease press twice ENTER to exit this sample\n");getchar();getchar();for (i=0; i<g_u32AoCnt; i++){AiChn = i;s32Ret = SAMPLE_COMM_AUDIO_DestoryTrdAi(AiDev, AiChn);if (s32Ret != HI_SUCCESS){SAMPLE_DBG(s32Ret);return HI_FAILURE;}}s32Ret = SAMPLE_COMM_AUDIO_StopAi(AiDev, s32AiChnCnt, HI_FALSE, HI_FALSE);if (s32Ret != HI_SUCCESS){SAMPLE_DBG(s32Ret);return HI_FAILURE;}s32Ret = SAMPLE_COMM_AUDIO_StopAo(AoDev, s32AoChnCnt, HI_TRUE, HI_FALSE);if (s32Ret != HI_SUCCESS){SAMPLE_DBG(s32Ret);return HI_FAILURE;}return HI_SUCCESS;
}
首先两个extern是因为要用的变量在其它文件,不加它就报错
关于是单声道mono,还是立体声STEREO,这里其实藏了一个坑,注意到这个函数里面有这样一句话
s32AoChnCnt = stAioAttr.u32ChnCnt >> stAioAttr.enSoundmode;
当设置为单声道mono时,它移位0,不起作用,当设置为立体声时,右移一位,不管是AI的通道enable,还是AO的通道enable,都有这样一个for循环,次数就是s32AoChnCnt或者s32AiChnCnt,这个值来源于u32ChnCnt以及enSoundmode
当我们设置单声道时,u32ChnCnt可以设置为1、2、3等等,程序不会报错,但是设置为立体声STEREO时,如果u32ChnCnt还是设置为1,HI_MPI_AI_SetPubAttr这个函数就会报错,也就是立体声STEREO必须大于1,且为偶数。假设此时为立体声,u32ChnCnt设为2,HI_MPI_AI_EnableChn的时候,for循环只会执行一次,如果执行2次,想要初始化两个通道,HI_MPI_AI_EnableChn报错
因此此处一般情况下,是使用stereo,并设置u32ChnCnt为2,然后enable一个AI通道,enable一个aenc通道,enable一个adec通道,enable两个AO通道,并将这两个AO通道都绑定到adec通道上,这样采集是立体声,输出也是立体声,如果AO通道也只初始化一个,那采集编码解码都是立体声,输出是单声道
但是此处并没有enable编解码通道,因此想要实现立体声的采集和播放,需要设置为mono,u32ChnCnt设置为2,这样会初始化2个AI和2和AO通道,而且在上述程序中可以看到,打开了两个文件流,并创建了两个对应的线程,在线程中,完成数据从AI到AO的过程,并将数据写入文件
修改sample_comm.h
mpp\sample\common\sample_comm.h
这个文件和之前一样,改条件编译
#if (defined HI_ACODEC_TYPE_TLV320AIC31)
#include "tlv320aic31.h"
#elif (defined HI_ACODEC_TYPE_TLV320AIC32x4)
#include "tlv320aic32x4.h"
#endif#if (defined HI_ACODEC_TYPE_TLV320AIC31)
#define TLV320_FILE "/dev/tlv320aic31"
#elif (defined HI_ACODEC_TYPE_TLV320AIC32x4)
#define TLV320_FILE "/dev/tlv320aic32x4"
#endif
找一个地方,添加结构体声明,这个结构体也是因为其它文件要使用,放在这里不需要extern即可使用,它是从sample_comm_audio.c文件中剪切过来的,在此基础上,添加了FILE *pfd;成员
typedef struct tagSAMPLE_AI_S
{HI_BOOL bStart;HI_S32 AiDev;HI_S32 AiChn;HI_S32 AencChn;HI_S32 AoDev;HI_S32 AoChn;HI_BOOL bSendAenc;HI_BOOL bSendAo;FILE *pfd;pthread_t stAiPid;
} SAMPLE_AI_S;
修改sample_comm_audio.c
mpp\sample\common\sample_comm_audio.c
首先这几个全局变量,需要在其它位置使用,原来是static的,因此去掉static
SAMPLE_AI_S gs_stSampleAi[AI_DEV_MAX_NUM*AIO_MAX_CHN_NUM];
SAMPLE_AENC_S gs_stSampleAenc[AENC_MAX_CHN_NUM];
SAMPLE_ADEC_S gs_stSampleAdec[ADEC_MAX_CHN_NUM];
SAMPLE_AO_S gs_stSampleAo[AO_DEV_MAX_NUM];
之前讲到创建了线程,这个线程函数也是要单独定义的,就定义在此处,它完成了三件工作,从ai获取数据,将输入发送到ao,将数据存入文件
/******************************************************************************
* function : get frame from Ai, send it to Ao, and save as lpcm
******************************************************************************/
void *SAMPLE_COMM_AUDIO_AiProc_modby_glx(void *parg)
{HI_S32 s32Ret;HI_S32 AiFd;SAMPLE_AI_S *pstAiCtl = (SAMPLE_AI_S *)parg;AUDIO_FRAME_S stFrame; fd_set read_fds;struct timeval TimeoutVal;AI_CHN_PARAM_S stAiChnPara;prctl(PR_SET_NAME, "hi_SAMPLE_AiProc", 0, 0, 0); s32Ret = HI_MPI_AI_GetChnParam(pstAiCtl->AiDev, pstAiCtl->AiChn, &stAiChnPara);if (HI_SUCCESS != s32Ret){printf("%s: Get ai chn param failed\n", __FUNCTION__);return NULL;}stAiChnPara.u32UsrFrmDepth = 30;s32Ret = HI_MPI_AI_SetChnParam(pstAiCtl->AiDev, pstAiCtl->AiChn, &stAiChnPara);if (HI_SUCCESS != s32Ret){printf("%s: set ai chn param failed\n", __FUNCTION__);return NULL;}FD_ZERO(&read_fds);AiFd = HI_MPI_AI_GetFd(pstAiCtl->AiDev, pstAiCtl->AiChn);FD_SET(AiFd,&read_fds);while (pstAiCtl->bStart){ TimeoutVal.tv_sec = 1;TimeoutVal.tv_usec = 0;FD_ZERO(&read_fds);FD_SET(AiFd,&read_fds);s32Ret = select(AiFd+1, &read_fds, NULL, NULL, &TimeoutVal);if (s32Ret < 0) {break;}else if (0 == s32Ret) {printf("%s: get ai frame select time out\n", __FUNCTION__);break;}if (FD_ISSET(AiFd, &read_fds)){/* get frame from ai chn */s32Ret = HI_MPI_AI_GetFrame(pstAiCtl->AiDev, pstAiCtl->AiChn, &stFrame, NULL, HI_FALSE);if (HI_SUCCESS != s32Ret ){printf("%s: HI_MPI_AI_GetFrame(%d, %d), failed with %#x!\n",\__FUNCTION__, pstAiCtl->AiDev, pstAiCtl->AiChn, s32Ret);pstAiCtl->bStart = HI_FALSE;return NULL;}/* send frame to ao */if (HI_TRUE == pstAiCtl->bSendAo){s32Ret = HI_MPI_AO_SendFrame(pstAiCtl->AoDev, pstAiCtl->AoChn, &stFrame, 1000);if (HI_SUCCESS != s32Ret ){printf("%s: HI_MPI_AO_SendFrame(%d, %d), failed with %#x!\n",\__FUNCTION__, pstAiCtl->AoDev, pstAiCtl->AoChn, s32Ret);pstAiCtl->bStart = HI_FALSE;return NULL;}}/* save frame to file */fwrite(stFrame.pVirAddr[0],1,stFrame.u32Len, pstAiCtl->pfd);/* finally you must release the stream */s32Ret = HI_MPI_AI_ReleaseFrame(pstAiCtl->AiDev, pstAiCtl->AiChn, &stFrame, NULL);if (HI_SUCCESS != s32Ret ){printf("%s: HI_MPI_AI_ReleaseFrame(%d, %d), failed with %#x!\n",\__FUNCTION__, pstAiCtl->AiDev, pstAiCtl->AiChn, s32Ret);pstAiCtl->bStart = HI_FALSE;return NULL;}}}pstAiCtl->bStart = HI_FALSE;return NULL;
}
然后是codec的配置函数
HI_S32 SAMPLE_Tlv320_CfgAudio(AIO_MODE_E enWorkmode,AUDIO_SAMPLE_RATE_E enSample)
它传下去的参数,太少了,如果32x4做主,需要通道数来计算BCLK的分频,因此修改为
HI_S32 SAMPLE_Tlv320_CfgAudio(AIO_ATTR_S *pstAioAttr)
凡是调用到它的地方,都要进行修改
HI_S32 SAMPLE_COMM_AUDIO_CfgTlv320(AIO_ATTR_S *pstAioAttr)
{...s32Ret = SAMPLE_Tlv320_CfgAudio(pstAioAttr);...
}HI_S32 SAMPLE_COMM_AUDIO_CfgAcodec(AIO_ATTR_S *pstAioAttr)
{...
#if (defined HI_ACODEC_TYPE_TLV320AIC31) || (defined HI_ACODEC_TYPE_TLV320AIC31)s32Ret = SAMPLE_Tlv320_CfgAudio(pstAioAttr);...
#endif
}
SAMPLE_Tlv320_Disable这个函数,32x4并不需要调用它,因此加上条件编译,防止编译报错,主要是没有audio_ctrl.chip_num这个成员,因此编译的话一定会报错
HI_S32 SAMPLE_Tlv320_Disable()
{Audio_Ctrl audio_ctrl;int s_fdTlv = -1;HI_S32 s32Ret;
#if (defined HI_ACODEC_TYPE_TLV320AIC31)s_fdTlv = open(TLV320_FILE,O_RDWR);if (s_fdTlv < 0){printf("[Func]:%s [Line]:%d [Info]:%s\n", __FUNCTION__, __LINE__, "can't open /dev/tlv320aic31");return HI_FAILURE; } /* reset transfer mode 0:I2S 1:PCM */audio_ctrl.chip_num = 0;s32Ret = ioctl(s_fdTlv, SOFT_RESET, &audio_ctrl);if (HI_SUCCESS != s32Ret){printf("[Func]:%s [Line]:%d [Info]:%s\n", __FUNCTION__, __LINE__, "tlv320aic31 reset failed");}close(s_fdTlv);#endifreturn s32Ret;
}
接下来修改SAMPLE_Tlv320_CfgAudio函数的内容
HI_S32 SAMPLE_Tlv320_CfgAudio(AIO_MODE_E enWorkmode,AUDIO_SAMPLE_RATE_E enSample)
{
#if (defined HI_ACODEC_TYPE_TLV320AIC31)原来的定义
#elif (defined HI_ACODEC_TYPE_TLV320AIC32x4)int s_fdTlv = -1;Audio_Ctrl audio_ctrl;AIO_MODE_E enWorkmode = pstAioAttr->enWorkmode; AUDIO_SAMPLE_RATE_E enSample = pstAioAttr->enSamplerate;if (AUDIO_SAMPLE_RATE_8000 == enSample){audio_ctrl.interface.sample_rate = AIC32x4_SAMPLE_RATE_8K;}else if (AUDIO_SAMPLE_RATE_12000 == enSample){audio_ctrl.interface.sample_rate = AIC32x4_SAMPLE_RATE_12K;}else if (AUDIO_SAMPLE_RATE_11025 == enSample){audio_ctrl.interface.sample_rate = AIC32x4_SAMPLE_RATE_11K;}else if (AUDIO_SAMPLE_RATE_16000 == enSample){audio_ctrl.interface.sample_rate = AIC32x4_SAMPLE_RATE_16K;}else if (AUDIO_SAMPLE_RATE_22050 == enSample){audio_ctrl.interface.sample_rate = AIC32x4_SAMPLE_RATE_22K;}else if (AUDIO_SAMPLE_RATE_24000 == enSample){audio_ctrl.interface.sample_rate = AIC32x4_SAMPLE_RATE_24K;}else if (AUDIO_SAMPLE_RATE_32000 == enSample){audio_ctrl.interface.sample_rate = AIC32x4_SAMPLE_RATE_32K;}else if (AUDIO_SAMPLE_RATE_44100 == enSample){audio_ctrl.interface.sample_rate = AIC32x4_SAMPLE_RATE_44K;}else if (AUDIO_SAMPLE_RATE_48000 == enSample){audio_ctrl.interface.sample_rate = AIC32x4_SAMPLE_RATE_48K;}else {printf("SAMPLE_Tlv320aix32x4_CfgAudio(), not support enSample:%d\n",enSample);return -1;}if(AIO_MODE_I2S_MASTER == enWorkmode) {audio_ctrl.interface.transfer_mode = AIC32x4_TRANSFER_MODE_I2S;audio_ctrl.interface.master_slave_mode = AIC32x4_SLAVE_MODE;}else if(AIO_MODE_I2S_SLAVE == enWorkmode){audio_ctrl.interface.transfer_mode = AIC32x4_TRANSFER_MODE_I2S;audio_ctrl.interface.master_slave_mode = AIC32x4_MASTER_MODE;}else{printf("SAMPLE_Tlv320aic32x4_CfgAudio(), not support workmode:%d\n\n",enWorkmode);return -1;}s_fdTlv = open(TLV320_FILE,O_RDWR);if (s_fdTlv < 0){printf("can't open tlv320aic32x4,%s\n", TLV320_FILE);return -1; } if (ioctl(s_fdTlv,SOFT_RESET,&audio_ctrl)){printf("[Func]:%s [Line]:%d [Info]:%s\n", __FUNCTION__, __LINE__, "tlv320aic32x4 reset failed");}/* set master/slave mode*/if (ioctl(s_fdTlv,MASTER_SLAVE_MODE, &audio_ctrl)){printf("[Func]:%s [Line]:%d [Info]:%s\n", __FUNCTION__, __LINE__, "tlv320aic32x4 set master/slave mode failed");}/* set data transfer mode*/if (ioctl(s_fdTlv,AUDIO_INTERFACE, &audio_ctrl)){printf("[Func]:%s [Line]:%d [Info]:%s\n", __FUNCTION__, __LINE__, "tlv320aic32x4 set data transfer mode failed");}/* set bitwidth*/if(pstAioAttr->enBitwidth == AUDIO_BIT_WIDTH_16){audio_ctrl.interface.bit_width = AIC32x4_BIT_WIDTH_16B;audio_ctrl.interface.chn_num = pstAioAttr->u32ChnCnt;if (ioctl(s_fdTlv,BIT_WIDTH, &audio_ctrl)){printf("[Func]:%s [Line]:%d [Info]:%s\n", __FUNCTION__, __LINE__, "tlv320aic32x4 set bitwidth failed");}}else{printf("SAMPLE_Tlv320aic32x4_CfgAudio(), just support bitwidth 16bits\n");return -1;}
#endifreturn 0;
由于之前在头文件中定义了结构体struct tagSAMPLE_AI_S,因此此处c文件的定义需要删除
编译
在audio目录下,执行make即可完成音频测试程序的编译
文档下载链接
https://download.csdn.net/download/whitefish520/12733595