前言

为了在海思平台上使用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

海思hi3531d音频外接codec (tlv320aic32x4)相关推荐

  1. 强大的海思Hi3531D 4K编码器评测(ENC2,ENC5多路推流编解码一体机)

    强大的海思Hi3531D 4K编码器评测-ENC2,ENC5多路推流编解码一体机 1. 为啥要做这个评测? 2. 编码性能 2.1 12路1080p@30编码能力 2.2 2路4K@30(3840x2 ...

  2. 海思(Hi3531d)uboot启动分析

    最近在学习和研究海思Hi3531d芯片的uboot启动过程,看到"海思(Hi3521a)uboot详细分析"和3531有相通之处,谨以此记录分析过程,方便日后查看,具体请查看htt ...

  3. 海思hi3516dv300音频调节总结

    1.硬件 Hi3516支持内置AudioCodec/外置音频解码芯片: 由于这里硬件上外接FM1288,故走的是外置,通过I2S接入, 通过MIC单端/差分输入音频. 由于之前一直调试内置Codec, ...

  4. 使用sil9233a芯片控制海思hi3531d的hdmi输入

    前言 准备用hi3531d做hdmi输入,但是只有IT6801FN的驱动ko,担心调不好,于是决定先使用原来一直用的sii9233a,这款芯片的资料比较难找,而且都是英文的,所以我先放个链接好了 链接 ...

  5. php海思hi3531d,海思hi3531DV200 h.265编解码AI处理器

    主要特点 处理器内核  ARM Cortex A53 四核@1.15GHz − 32KB L1 I-Cache, 32KB L1 D-Cache − 512KB L2 Cache − 支持NEON/ ...

  6. 海思HI3536 audio 双声道播放音频

    一.说明: 海思对音频的支持并不像视频的支持那么全面,这里也有版权受限的原因.所以咋们可以移植一些其他的开源解码编码库来完善海思平台这一方面的不足. 问题描述:海思提供了File -> deco ...

  7. 海思芯片硬件java加速_海思芯片直播延迟测试结果(小于100毫秒)

    背景 最近接触了许多客户,许多是做安全方面产品的客户,有些还涉及到jun队后勤的等等,他们普遍对采集延迟,编码延迟,传输延迟等都有很大关注.例如有个客户是做反狙击探测的,那可是与生命相关的,容不得试错 ...

  8. 海思HI35xx语音识别方案

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

  9. 音视频数据采集及单向传输的实现(海思3516EV200平台)

    目录 2.硬件选型 2.1远端无线装置 2.1.1音视频采集模块 2.1.2光电转换模块 2.2本地接受设备 2.2.1光电转换模块 2.2.2音视频还原模块 3.串口配置 4.音视频采集 5.音视频 ...

最新文章

  1. python wxpy_wxpy
  2. HyperLedger Fabric 错误记录
  3. python2中的unicode_python2中的unicode()函数在python3中会报错:
  4. 【云炬大学生创业基础笔记】第1章第4节 为什么需要创业讨论
  5. nginx的日志配置
  6. Zimbra高级应用之-双向证书认证(一)
  7. 虚拟同步发电机_一种光储型虚拟同步机介绍
  8. CKeditor自定义上传图片功能
  9. db2 联合数据源 mysql_myeclipse连接db2的数据源怎么配
  10. 深入理解C++11pdf
  11. React Native Keyboard使用详解
  12. HTML 标签学习总结第一天
  13. C# 实现复杂对象的序列化与反序列化
  14. ElasticHD Windows环境下安装(转载)
  15. 专业营销手段汇编及研究
  16. px和毫米的换算_px和厘米怎么换算?
  17. 步进电机控制(PLC)
  18. 前沿重器[26] | 预训练模型的领域适配问题
  19. 导弹气动估算matlab,基于Fluent的导弹气动特性计算
  20. 周记--听平凡人说故事

热门文章

  1. CorelDRAW的快捷键在图书馆收集的看很全的吧
  2. 任务11:“数码管”的静态显示和动态显示
  3. 标准c语言程序设计,C语言程序设计标准.doc
  4. puzzle(0916)智行营救、勇往直前
  5. Oracle实验一 熟悉ORACLE环境并练习SQL操作
  6. 关于小米无人机的未来
  7. .NET中Invoke和BeginInvoke
  8. 异步编程中的BeginInvoke和EndInvoke
  9. 使用 Trace32 对 FLASH 编程摘要及Trace32-ICD和Trace32-ICE的区别
  10. 集群服务器配置anaconda步骤