海思hi3531d音频外接codec (tlv320aic32x4)

前言

为了在海思平台上使用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 媒体处理软件开发参考.pdfmpp API说明
TLV320AIC3254 Application Reference Guide (Rev. A).pdfcodec用户手册

海思音频相关知识

海思的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连接示意图

1

从这张图可以得到一个信息,内部的AIP和AOP和外部的I2S引脚有一个映射关系,tlv320aic32x4作为一个即能录制,又能播放的codec,应该选用上面的I2S2,内部对应的是AIP2和AOP0

打开《Hi3531DV100_PINOUT_CN.xlsx》,在3.管脚复用寄存器中搜I2S2,如图

2

  • 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_RXBCLK
I2S2_WS_RXWCLK
I2S2_SD_RXDOUT
I2S2_SD_TXDIN
I2S2_MCLKMCLK

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的路由设置,图上画的很清楚

3

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/39208.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python监控股价并通过微信提醒

01 Python监控股价并通过微信提醒 炒股盯盘太累&#xff0c;能在设置的买卖点进行智能提醒吗&#xff1f; 没错&#xff0c;python可以帮你实现&#xff1a;通过python编程实现股价实时监控&#xff0c;并在买卖点通过微信发送信息自动提醒&#xff01; 今天&#xff0c;梅朵…

海思烧录工具Hitool使用方法

海思有自己一套独立的烧写工具具Hitool.exe&#xff0c;这是优于国内其他芯片厂家的地方&#xff0c;不会在烧录问题上&#xff0c;耽误您太多时间。 海思的SDK文档放置的很规整&#xff0c;目录清晰&#xff0c;但是对于新手&#xff0c;感觉文档放置路径过深&#xff0c;不好…

海思HI35xx语音识别方案

前言 语音识别是智能化应用的一个重要分支&#xff0c;也是语音交互功能的基础。语音识别基于神经网络算法&#xff0c;借助大数据进行模型训练&#xff0c;据科大讯飞相关数据报道&#xff0c;它们已经能够实现98%以上的准确识别率&#xff0c;同时支持多种外语及国内的一些方…

海思Hi3798MV200机顶盒芯片处理器简介

Hi3798MV200是用于IPTV/OTT机顶盒市场的支持4KP60解码的全4K高性能SOC芯片。集成4核64位高性能Cortex A53处理器和多核高性能2D/3D加速引擎&#xff1b;支持H.265 4Kx2KP60 10bit超高清视频解码&#xff0c;高性能的H.265高清视频编码&#xff0c;HDR视频解码及显示&#xff0c…

TTS语音播报模块UNV6288的使用方法

TTS即text to sound&#xff0c; 输入字符它可以帮你转化为语音输出。 UNV6288可以实现中文、英文、数字的语音合成。用户也可以定义语音大小速度等参数。 这个模块的控制方式很简单&#xff0c;只要通过 TTL 串口向模块发送GBK编码&#xff0c;串口发送什么就播放什么。 另…

基于STM32 + SYN6288语音播报

完整代码下载 https://download.csdn.net/download/zhouml_msn/85592868 一、接线示例&#xff1a; 二、模块重点&#xff1a; 1&#xff1a;5V供电 &#xff0c;功耗约120mA (带图中小喇叭)&#xff1b; 2&#xff1a;最少接3根线&#xff1a;5V, GND, RXD&#xff0c;TXD…

5.32 综合案例2.0 - TTS语音云播报(支持M320开发板)

HaaS506 - TTS语音云播报 简介准备硬件接口代码流程功能实现1、物联网平台开发2、设备端开发代码调试 3.应用平台开发3.1新建‘普通项目’3.2关联产品和设备3.3新建移动应用 简介 用python开发板写的云喇叭&#xff0c;文字转语音&#xff0c;附教程源码 手机端发送文字&#x…

深圳知名语音ic品牌,语音芯片方案公司,多功能语音芯片,WTV890

随着人工智能技术的不断发展&#xff0c;语音交互已成为智能家居、智能医疗、消费类电子等领域的主流趋势。WTV890作为深圳知名的语音IC品牌&#xff0c;凭借着卓越的性能、丰富的扩展功能和灵活的应用&#xff0c;已成为众多需要语音交互的智能设备的首选方案。 WTV890-32N 有…

chatgpt赋能python:用Python生成动画:带你深入了解Python动画生成技术

用Python生成动画&#xff1a;带你深入了解Python动画生成技术 Python作为一门高效的编程语言&#xff0c;已经被广泛应用于各个领域&#xff0c;包括数据分析、机器学习、自动化测试等。除此之外&#xff0c;Python还可以用于生成动画&#xff0c;帮助我们更直观地理解一些复…

银联AID

应用标识 application identifier&#xff1b;AID 由注册的应用提供商标识&#xff08;RID&#xff09;以及专用应用标识符扩展&#xff08;PIX&#xff09;组成 AID:即唯一标识一个应用&#xff0c;分为两部分&#xff0c;RID(5字节)PIX&#xff08;最多11字节&#xff09; …

信用卡的支付清算

一、分享背景 Hi 大家好&#xff0c;我今天给大家分享是信用卡清算方面的内容&#xff0c;今天的分享主要分四块来讲&#xff1a; 定价模式 业务流程 清算流程 常见问题 二、分享内容 1、定价模式 首先给大家分享一下关于信用卡定价方面的内容&#xff0c;信用卡的定价…

金融支付-银联卡支付系统产品介绍

一、系统概述 银联卡支付业务是中国现代化支付方式中最重要的一种&#xff0c;村镇银行作为金融支付机构&#xff0c;银联卡支付业务在其日常经营活动中&#xff0c;占有很大的比重。金电银联卡支付系统正是着眼于村镇银行业务需求&#xff0c;结合村镇银行业务特点&#xff0c…

SpringBoot使用银联支付

目录 前言 一、银联支付java sdk 二、官方DEMO 三、springboot项目使用银联支付 3.1、新建项目 3.2、配置 3.3、封装客户端 写在后面 前言 项目里使用了微信支付&#xff0c;支付宝支付。但是还不满足&#xff01;我们还需要银联支付&#xff01;&#xff01;那就去看…

银联银行卡卡号java_编写Java程序,使用单例模式,创建可以生成银联借记卡号的工具类...

编写Java程序,使用单例模式,创建可以生成银联借记卡号的工具类 编写Java程序,使用单例模式,创建可以生成银联借记卡号的工具类,银联借记卡号是一个 19 位的数字,卡号以“62”开头,如图所示。 使用单例模式,创建可以生成银联借记卡号的工具类,银联借记卡号是一个 19 位…

苹果手机充值显示服务器繁忙,iTunes充值常见错误和解决方法

iTunes充值常见错误和解决方法 小编归纳总结了常见的iTunes充值异常和解决方法&#xff0c;包括充值未到账、语音操作异常、订单状态异常、系统验证信息错误、支付密码异常、账户金额限制、银行卡状态异常及系统异常8大常见iTunes充值错误&#xff0c;以方便果粉顺利充值iTunes…

雅思阅读笔记

一、一个星期七天 1.Monday 2.Tuesday 3.Wednesday 4.Thursday5. Friday 6.Saturday 7.Sunday 二、一年十二个月 1.January 2.February 3.March 4.April 5.May 6.June 7.July 8.August 9.September 10. October 11.November 12.December 三、一年四季 1.spring 2.summer 3.autu…

雅思复习总结

文章目录 1. 考前复习阶段1.1 阅读1.2 听力1.3 写作1.4 口语 2. 考试过程3. 最后成绩 刚刚考完雅思&#xff0c;这是我第一次考雅思&#xff0c;总结一下这次考试的经验。 1. 考前复习阶段 对于中国考生来说&#xff0c;阅读和听力是强项&#xff0c;只需做大量的练习即可。重…

计算雅思成绩C语言,雅思成绩到底如何计算的?

很多烤鸭在漫漫屠鸭路上时常会感到困惑&#xff1a;明明自己发挥不错&#xff0c;为什么成绩却和想象的不同&#xff1f;总分达标了&#xff0c;小分却不够该怎么办&#xff1f; 其实雅思成绩单隐藏了很多的信息&#xff0c;今天我们来分析两份典型的成绩单&#xff0c;帮助烤鸭…