Linux驱动,之dai接口,I2S,ALSA音频驱动框架(三)

结合自己的专业知识,整合了其他人的文字。
参考学习 原文链接:https://blog.csdn.net/lihuan680680/article/details/122289214
原文链接:https://blog.csdn.net/QQ135102692/article/details/125348020

目录

1、常见音频知识
2、常见接口
3、stm32下,音频驱动,播放音乐方法
4、linux下,alsa架构分析
i2s,alsa驱动架构,其实和其他字符设备(i2c,input,lcd)非常像,我们需要关心的是codex设备驱动,注册过程,使用等。

音频驱动框架分为接口驱动(和soc相关,输出时序的),设备驱动(codex,具体的设备驱动),
alsa内核驱动(内核提供的一套注册接口,描述方法,将设备驱动,和接口驱动,联系起来)。
常见的音频接口有,I2S, PCM , AC97,dai。
常见的音频编码格式,aac,wav,pcm。
常见的音频文件格式,aac,wav,mp3。

1.PCM接口
最简单的音频接口是PCM(脉冲编码调制)接口,该接口由时钟脉冲(BCLK)、帧同步信号(FS)及接收数据(DR)和发送数据(DX)组成。在FS信号的上升沿数据传输从MSB开始,FS频率等于采样频率。FS信号之后开始数据字的传输,单个数据位按顺序进行传输,一个时钟周期传输一个数据字。PCM接口很容易实现,原则上能够支持任何数据方案和任何采样频率,但需要每个音频通道获得一个独立的数据队列。

2.IIS接口
IIS接口在20世纪80年代首先被PHILIPS用于消费音频产品,并在一个称为LRCLK(Left/Right CLOCK)的信号机制中经过转换,将两路音频变成单一的数据队列。当LRCLK为高时,左声道数据被传输;LRCLK为低时,右声道数据被传输。与CPM相比,IIS更适合于立体声系统,当然,IIS的变体也支持多通道的时分复用,因此可以支持多通道。

一、回想stm32下,音频驱动

播放wav格式音乐的过程。
在这里插入图片描述

stm32有一个音频dai(digital audio interface)接口,控制wm8960音频芯片的方法,硬件接口如下,需要关注的是dai,i2c接口。
在这里插入图片描述
单片机和linux的驱动方法。
在这里插入图片描述
单片机需要实现的三个驱动如上。

1、codex驱动
wm8978.c

#include "wm8978.h"
#include "myiic.h"
#include "delay.h"
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32F407开发板
//WM8978 驱动代码	   
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2014/5/24
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2014-2024
//All rights reserved									  
// 	

//WM8978寄存器值缓存区(总共58个寄存器,0~57),占用116字节内存
//因为WM8978的IIC操作不支持读操作,所以在本地保存所有寄存器值
//写WM8978寄存器时,同步更新到本地寄存器值,读寄存器时,直接返回本地保存的寄存器值.
//注意:WM8978的寄存器值是9位的,所以要用u16来存储. 
static u16 WM8978_REGVAL_TBL[58]=
{
	0X0000,0X0000,0X0000,0X0000,0X0050,0X0000,0X0140,0X0000,
	0X0000,0X0000,0X0000,0X00FF,0X00FF,0X0000,0X0100,0X00FF,
	0X00FF,0X0000,0X012C,0X002C,0X002C,0X002C,0X002C,0X0000,
	0X0032,0X0000,0X0000,0X0000,0X0000,0X0000,0X0000,0X0000,
	0X0038,0X000B,0X0032,0X0000,0X0008,0X000C,0X0093,0X00E9,
	0X0000,0X0000,0X0000,0X0000,0X0003,0X0010,0X0010,0X0100,
	0X0100,0X0002,0X0001,0X0001,0X0039,0X0039,0X0039,0X0039,
	0X0001,0X0001
}; 
//WM8978初始化
//返回值:0,初始化正常
//    其他,错误代码
u8 WM8978_Init(void)
{
	u8 res;
	GPIO_InitTypeDef  GPIO_InitStructure;
	
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB|RCC_AHB1Periph_GPIOC, ENABLE);			//使能外设GPIOB,GPIOC时钟
     
	//PB12/13 复用功能输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽 
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
  GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
	
	//PC2/PC3/PC6复用功能输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3|GPIO_Pin_6;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽 
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
  GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化
	
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource12,GPIO_AF_SPI2); //PB12,AF5  I2S_LRCK
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource13,GPIO_AF_SPI2);	//PB13,AF5  I2S_SCLK 
	GPIO_PinAFConfig(GPIOC,GPIO_PinSource3,GPIO_AF_SPI2);	//PC3 ,AF5  I2S_DACDATA 
	GPIO_PinAFConfig(GPIOC,GPIO_PinSource6,GPIO_AF_SPI2);	//PC6 ,AF5  I2S_MCK
	GPIO_PinAFConfig(GPIOC,GPIO_PinSource2,GPIO_AF6_SPI2);	//PC2 ,AF6  I2S_ADCDATA  I2S2ext_SD是AF6!!!
	

	
	IIC_Init();//初始化IIC接口
	res=WM8978_Write_Reg(0,0);	//软复位WM8978
	if(res)return 1;			//发送指令失败,WM8978异常
	//以下为通用设置
	WM8978_Write_Reg(1,0X1B);	//R1,MICEN设置为1(MIC使能),BIASEN设置为1(模拟器工作),VMIDSEL[1:0]设置为:11(5K)
	WM8978_Write_Reg(2,0X1B0);	//R2,ROUT1,LOUT1输出使能(耳机可以工作),BOOSTENR,BOOSTENL使能
	WM8978_Write_Reg(3,0X6C);	//R3,LOUT2,ROUT2输出使能(喇叭工作),RMIX,LMIX使能	
	WM8978_Write_Reg(6,0);		//R6,MCLK由外部提供
	WM8978_Write_Reg(43,1<<4);	//R43,INVROUT2反向,驱动喇叭
	WM8978_Write_Reg(47,1<<8);	//R47设置,PGABOOSTL,左通道MIC获得20倍增益
	WM8978_Write_Reg(48,1<<8);	//R48设置,PGABOOSTR,右通道MIC获得20倍增益
	WM8978_Write_Reg(49,1<<1);	//R49,TSDEN,开启过热保护 
	WM8978_Write_Reg(10,1<<3);	//R10,SOFTMUTE关闭,128x采样,最佳SNR 
	WM8978_Write_Reg(14,1<<3);	//R14,ADC 128x采样率
	return 0;
} 
//WM8978写寄存器
//reg:寄存器地址
//val:要写入寄存器的值 
//返回值:0,成功;
//    其他,错误代码
u8 WM8978_Write_Reg(u8 reg,u16 val)
{ 
	IIC_Start(); 
	IIC_Send_Byte((WM8978_ADDR<<1)|0);//发送器件地址+写命令	 
	if(IIC_Wait_Ack())return 1;	//等待应答(成功?/失败?) 
    IIC_Send_Byte((reg<<1)|((val>>8)&0X01));//写寄存器地址+数据的最高位
	if(IIC_Wait_Ack())return 2;	//等待应答(成功?/失败?) 
	IIC_Send_Byte(val&0XFF);	//发送数据
	if(IIC_Wait_Ack())return 3;	//等待应答(成功?/失败?) 
    IIC_Stop();
	WM8978_REGVAL_TBL[reg]=val;	//保存寄存器值到本地
	return 0;	
}  
//WM8978读寄存器
//就是读取本地寄存器值缓冲区内的对应值
//reg:寄存器地址 
//返回值:寄存器值
u16 WM8978_Read_Reg(u8 reg)
{  
	return WM8978_REGVAL_TBL[reg];	
} 
//WM8978 DAC/ADC配置
//adcen:adc使能(1)/关闭(0)
//dacen:dac使能(1)/关闭(0)
void WM8978_ADDA_Cfg(u8 dacen,u8 adcen)
{
	u16 regval;
	regval=WM8978_Read_Reg(3);	//读取R3
	if(dacen)regval|=3<<0;		//R3最低2个位设置为1,开启DACR&DACL
	else regval&=~(3<<0);		//R3最低2个位清零,关闭DACR&DACL.
	WM8978_Write_Reg(3,regval);	//设置R3
	regval=WM8978_Read_Reg(2);	//读取R2
	if(adcen)regval|=3<<0;		//R2最低2个位设置为1,开启ADCR&ADCL
	else regval&=~(3<<0);		//R2最低2个位清零,关闭ADCR&ADCL.
	WM8978_Write_Reg(2,regval);	//设置R2	
}
//WM8978 输入通道配置 
//micen:MIC开启(1)/关闭(0)
//lineinen:Line In开启(1)/关闭(0)
//auxen:aux开启(1)/关闭(0) 
void WM8978_Input_Cfg(u8 micen,u8 lineinen,u8 auxen)
{
	u16 regval;  
	regval=WM8978_Read_Reg(2);	//读取R2
	if(micen)regval|=3<<2;		//开启INPPGAENR,INPPGAENL(MIC的PGA放大)
	else regval&=~(3<<2);		//关闭INPPGAENR,INPPGAENL.
 	WM8978_Write_Reg(2,regval);	//设置R2 
	
	regval=WM8978_Read_Reg(44);	//读取R44
	if(micen)regval|=3<<4|3<<0;	//开启LIN2INPPGA,LIP2INPGA,RIN2INPPGA,RIP2INPGA.
	else regval&=~(3<<4|3<<0);	//关闭LIN2INPPGA,LIP2INPGA,RIN2INPPGA,RIP2INPGA.
	WM8978_Write_Reg(44,regval);//设置R44
	
	if(lineinen)WM8978_LINEIN_Gain(5);//LINE IN 0dB增益
	else WM8978_LINEIN_Gain(0);	//关闭LINE IN
	if(auxen)WM8978_AUX_Gain(7);//AUX 6dB增益
	else WM8978_AUX_Gain(0);	//关闭AUX输入  
}
//WM8978 输出配置 
//dacen:DAC输出(放音)开启(1)/关闭(0)
//bpsen:Bypass输出(录音,包括MIC,LINE IN,AUX等)开启(1)/关闭(0) 
void WM8978_Output_Cfg(u8 dacen,u8 bpsen)
{
	u16 regval=0;
	if(dacen)regval|=1<<0;	//DAC输出使能
	if(bpsen)
	{
		regval|=1<<1;		//BYPASS使能
		regval|=5<<2;		//0dB增益
	} 
	WM8978_Write_Reg(50,regval);//R50设置
	WM8978_Write_Reg(51,regval);//R51设置 
}
//WM8978 MIC增益设置(不包括BOOST的20dB,MIC-->ADC输入部分的增益)
//gain:0~63,对应-12dB~35.25dB,0.75dB/Step
void WM8978_MIC_Gain(u8 gain)
{
	gain&=0X3F;
	WM8978_Write_Reg(45,gain);		//R45,左通道PGA设置 
	WM8978_Write_Reg(46,gain|1<<8);	//R46,右通道PGA设置
}
//WM8978 L2/R2(也就是Line In)增益设置(L2/R2-->ADC输入部分的增益)
//gain:0~7,0表示通道禁止,1~7,对应-12dB~6dB,3dB/Step
void WM8978_LINEIN_Gain(u8 gain)
{
	u16 regval;
	gain&=0X07;
	regval=WM8978_Read_Reg(47);	//读取R47
	regval&=~(7<<4);			//清除原来的设置 
 	WM8978_Write_Reg(47,regval|gain<<4);//设置R47
	regval=WM8978_Read_Reg(48);	//读取R48
	regval&=~(7<<4);			//清除原来的设置 
 	WM8978_Write_Reg(48,regval|gain<<4);//设置R48
} 
//WM8978 AUXR,AUXL(PWM音频部分)增益设置(AUXR/L-->ADC输入部分的增益)
//gain:0~7,0表示通道禁止,1~7,对应-12dB~6dB,3dB/Step
void WM8978_AUX_Gain(u8 gain)
{
	u16 regval;
	gain&=0X07;
	regval=WM8978_Read_Reg(47);	//读取R47
	regval&=~(7<<0);			//清除原来的设置 
 	WM8978_Write_Reg(47,regval|gain<<0);//设置R47
	regval=WM8978_Read_Reg(48);	//读取R48
	regval&=~(7<<0);			//清除原来的设置 
 	WM8978_Write_Reg(48,regval|gain<<0);//设置R48
}  
//设置I2S工作模式
//fmt:0,LSB(右对齐);1,MSB(左对齐);2,飞利浦标准I2S;3,PCM/DSP;
//len:0,16位;1,20位;2,24位;3,32位;  
void WM8978_I2S_Cfg(u8 fmt,u8 len)
{
	fmt&=0X03;
	len&=0X03;//限定范围
	WM8978_Write_Reg(4,(fmt<<3)|(len<<5));	//R4,WM8978工作模式设置	
}	

//设置耳机左右声道音量
//voll:左声道音量(0~63)
//volr:右声道音量(0~63)
void WM8978_HPvol_Set(u8 voll,u8 volr)
{
	voll&=0X3F;
	volr&=0X3F;//限定范围
	if(voll==0)voll|=1<<6;//音量为0时,直接mute
	if(volr==0)volr|=1<<6;//音量为0时,直接mute 
	WM8978_Write_Reg(52,voll);			//R52,耳机左声道音量设置
	WM8978_Write_Reg(53,volr|(1<<8));	//R53,耳机右声道音量设置,同步更新(HPVU=1)
}
//设置喇叭音量
//voll:左声道音量(0~63) 
void WM8978_SPKvol_Set(u8 volx)
{ 
	volx&=0X3F;//限定范围
	if(volx==0)volx|=1<<6;//音量为0时,直接mute 
 	WM8978_Write_Reg(54,volx);			//R54,喇叭左声道音量设置
	WM8978_Write_Reg(55,volx|(1<<8));	//R55,喇叭右声道音量设置,同步更新(SPKVU=1)	
}
//设置3D环绕声
//depth:0~15(3D强度,0最弱,15最强)
void WM8978_3D_Set(u8 depth)
{ 
	depth&=0XF;//限定范围 
 	WM8978_Write_Reg(41,depth);	//R41,3D环绕设置 	
}
//设置EQ/3D作用方向
//dir:0,在ADC起作用
//    1,在DAC起作用(默认)
void WM8978_EQ_3D_Dir(u8 dir)
{
	u16 regval; 
	regval=WM8978_Read_Reg(0X12);
	if(dir)regval|=1<<8;
	else regval&=~(1<<8); 
 	WM8978_Write_Reg(18,regval);//R18,EQ1的第9位控制EQ/3D方向
}

//设置EQ1
//cfreq:截止频率,0~3,分别对应:80/105/135/175Hz
//gain:增益,0~24,对应-12~+12dB
void WM8978_EQ1_Set(u8 cfreq,u8 gain)
{ 
	u16 regval;
	cfreq&=0X3;//限定范围 
	if(gain>24)gain=24;
	gain=24-gain;
	regval=WM8978_Read_Reg(18);
	regval&=0X100;
	regval|=cfreq<<5;	//设置截止频率 
	regval|=gain;		//设置增益	
 	WM8978_Write_Reg(18,regval);//R18,EQ1设置 	
}
//设置EQ2
//cfreq:中心频率,0~3,分别对应:230/300/385/500Hz
//gain:增益,0~24,对应-12~+12dB
void WM8978_EQ2_Set(u8 cfreq,u8 gain)
{ 
	u16 regval=0;
	cfreq&=0X3;//限定范围 
	if(gain>24)gain=24;
	gain=24-gain; 
	regval|=cfreq<<5;	//设置截止频率 
	regval|=gain;		//设置增益	
 	WM8978_Write_Reg(19,regval);//R19,EQ2设置 	
}
//设置EQ3
//cfreq:中心频率,0~3,分别对应:650/850/1100/1400Hz
//gain:增益,0~24,对应-12~+12dB
void WM8978_EQ3_Set(u8 cfreq,u8 gain)
{ 
	u16 regval=0;
	cfreq&=0X3;//限定范围 
	if(gain>24)gain=24;
	gain=24-gain; 
	regval|=cfreq<<5;	//设置截止频率 
	regval|=gain;		//设置增益	
 	WM8978_Write_Reg(20,regval);//R20,EQ3设置 	
}
//设置EQ4
//cfreq:中心频率,0~3,分别对应:1800/2400/3200/4100Hz
//gain:增益,0~24,对应-12~+12dB
void WM8978_EQ4_Set(u8 cfreq,u8 gain)
{ 
	u16 regval=0;
	cfreq&=0X3;//限定范围 
	if(gain>24)gain=24;
	gain=24-gain; 
	regval|=cfreq<<5;	//设置截止频率 
	regval|=gain;		//设置增益	
 	WM8978_Write_Reg(21,regval);//R21,EQ4设置 	
}
//设置EQ5
//cfreq:中心频率,0~3,分别对应:5300/6900/9000/11700Hz
//gain:增益,0~24,对应-12~+12dB
void WM8978_EQ5_Set(u8 cfreq,u8 gain)
{ 
	u16 regval=0;
	cfreq&=0X3;//限定范围 
	if(gain>24)gain=24;
	gain=24-gain; 
	regval|=cfreq<<5;	//设置截止频率 
	regval|=gain;		//设置增益	
 	WM8978_Write_Reg(22,regval);//R22,EQ5设置 	
}

2、i2s接口驱动
i2s.c

#include "i2s.h"  
#include "usart.h"
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32F407开发板
//I2S 驱动代码	   
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2014/5/24
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2014-2024
//All rights reserved									  
// 	
 

//I2S2初始化

//参数I2S_Standard:  @ref SPI_I2S_Standard  I2S标准,
//I2S_Standard_Phillips,飞利浦标准;
//I2S_Standard_MSB,MSB对齐标准(右对齐);
//I2S_Standard_LSB,LSB对齐标准(左对齐);
//I2S_Standard_PCMShort,I2S_Standard_PCMLong:PCM标准
//参数I2S_Mode:  @ref SPI_I2S_Mode  I2S_Mode_SlaveTx:从机发送;I2S_Mode_SlaveRx:从机接收;I2S_Mode_MasterTx:主机发送;I2S_Mode_MasterRx:主机接收;
//参数I2S_Clock_Polarity   @ref SPI_I2S_Clock_Polarity:  I2S_CPOL_Low,时钟低电平有效;I2S_CPOL_High,时钟高电平有效
//参数I2S_DataFormat: @ref SPI_I2S_Data_Format :数据长度,I2S_DataFormat_16b,16位标准;I2S_DataFormat_16bextended,16位扩展(frame=32bit);I2S_DataFormat_24b,24位;I2S_DataFormat_32b,32位.
void I2S2_Init(u16 I2S_Standard,u16 I2S_Mode,u16 I2S_Clock_Polarity,u16 I2S_DataFormat)
{ 
  I2S_InitTypeDef I2S_InitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);//使能SPI2时钟
	
	RCC_APB1PeriphResetCmd(RCC_APB1Periph_SPI2,ENABLE); //复位SPI2
	RCC_APB1PeriphResetCmd(RCC_APB1Periph_SPI2,DISABLE);//结束复位
  
	I2S_InitStructure.I2S_Mode=I2S_Mode;//IIS模式
	I2S_InitStructure.I2S_Standard=I2S_Standard;//IIS标准
	I2S_InitStructure.I2S_DataFormat=I2S_DataFormat;//IIS数据长度
	I2S_InitStructure.I2S_MCLKOutput=I2S_MCLKOutput_Disable;//主时钟输出禁止
	I2S_InitStructure.I2S_AudioFreq=I2S_AudioFreq_Default;//IIS频率设置
	I2S_InitStructure.I2S_CPOL=I2S_Clock_Polarity;//空闲状态时钟电平
	I2S_Init(SPI2,&I2S_InitStructure);//初始化IIS

 
	SPI_I2S_DMACmd(SPI2,SPI_I2S_DMAReq_Tx,ENABLE);//SPI2 TX DMA请求使能.
  I2S_Cmd(SPI2,ENABLE);//SPI2 I2S EN使能.	
} 
//采样率计算公式:Fs=I2SxCLK/[256*(2*I2SDIV+ODD)]
//I2SxCLK=(HSE/pllm)*PLLI2SN/PLLI2SR
//一般HSE=8Mhz 
//pllm:在Sys_Clock_Set设置的时候确定,一般是8
//PLLI2SN:一般是192~432 
//PLLI2SR:2~7
//I2SDIV:2~255
//ODD:0/1
//I2S分频系数表@pllm=8,HSE=8Mhz,即vco输入频率为1Mhz
//表格式:采样率/10,PLLI2SN,PLLI2SR,I2SDIV,ODD
const u16 I2S_PSC_TBL[][5]=
{
	{800 ,256,5,12,1},		//8Khz采样率
	{1102,429,4,19,0},		//11.025Khz采样率 
	{1600,213,2,13,0},		//16Khz采样率
	{2205,429,4, 9,1},		//22.05Khz采样率
	{3200,213,2, 6,1},		//32Khz采样率
	{4410,271,2, 6,0},		//44.1Khz采样率
	{4800,258,3, 3,1},		//48Khz采样率
	{8820,316,2, 3,1},		//88.2Khz采样率
	{9600,344,2, 3,1},  	//96Khz采样率
	{17640,361,2,2,0},  	//176.4Khz采样率 
	{19200,393,2,2,0},  	//192Khz采样率
};  
//设置IIS的采样率(@MCKEN)
//samplerate:采样率,单位:Hz
//返回值:0,设置成功;1,无法设置.
u8 I2S2_SampleRate_Set(u32 samplerate)
{ 
	u8 i=0; 
	u32 tempreg=0;
	samplerate/=10;//缩小10倍   
	
	for(i=0;i<(sizeof(I2S_PSC_TBL)/10);i++)//看看改采样率是否可以支持
	{
		if(samplerate==I2S_PSC_TBL[i][0])break;
	}
 
	RCC_PLLI2SCmd(DISABLE);//先关闭PLLI2S
	if(i==(sizeof(I2S_PSC_TBL)/10))return 1;//搜遍了也找不到
	RCC_PLLI2SConfig((u32)I2S_PSC_TBL[i][1],(u32)I2S_PSC_TBL[i][2]);//设置I2SxCLK的频率(x=2)  设置PLLI2SN PLLI2SR
 
	RCC->CR|=1<<26;					//开启I2S时钟
	while((RCC->CR&1<<27)==0);		//等待I2S时钟开启成功. 
	tempreg=I2S_PSC_TBL[i][3]<<0;	//设置I2SDIV
	tempreg|=I2S_PSC_TBL[i][4]<<8;	//设置ODD位
	tempreg|=1<<9;					//使能MCKOE位,输出MCK
	SPI2->I2SPR=tempreg;			//设置I2SPR寄存器 
	return 0;
}  
//I2S2 TX DMA配置
//设置为双缓冲模式,并开启DMA传输完成中断
//buf0:M0AR地址.
//buf1:M1AR地址.
//num:每次传输数据量
void I2S2_TX_DMA_Init(u8* buf0,u8 *buf1,u16 num)
{  
	NVIC_InitTypeDef   NVIC_InitStructure;
	DMA_InitTypeDef  DMA_InitStructure;
	
 
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);//DMA1时钟使能 
	
	DMA_DeInit(DMA1_Stream4);
	while (DMA_GetCmdStatus(DMA1_Stream4) != DISABLE){}//等待DMA1_Stream1可配置 
		
  /* 配置 DMA Stream */

  DMA_InitStructure.DMA_Channel = DMA_Channel_0;  //通道0 SPI2_TX通道 
  DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&SPI2->DR;//外设地址为:(u32)&SPI2->DR
  DMA_InitStructure.DMA_Memory0BaseAddr = (u32)buf0;//DMA 存储器0地址
  DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;//存储器到外设模式
  DMA_InitStructure.DMA_BufferSize = num;//数据传输量 
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量模式
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器增量模式
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//外设数据长度:16位
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//存储器数据长度:16位 
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;// 使用循环模式 
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;//高优先级
  DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; //不使用FIFO模式        
  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;
  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//外设突发单次传输
  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//存储器突发单次传输
  DMA_Init(DMA1_Stream4, &DMA_InitStructure);//初始化DMA Stream
	
	DMA_DoubleBufferModeConfig(DMA1_Stream4,(u32)buf1,DMA_Memory_0);//双缓冲模式配置
 
  DMA_DoubleBufferModeCmd(DMA1_Stream4,ENABLE);//双缓冲模式开启
 
  DMA_ITConfig(DMA1_Stream4,DMA_IT_TC,ENABLE);//开启传输完成中断
	
	NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream4_IRQn; 
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00;//抢占优先级0
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;//子优先级0
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能外部中断通道
  NVIC_Init(&NVIC_InitStructure);//配置
  
}  
//I2S DMA回调函数指针
void (*i2s_tx_callback)(void);	//TX回调函数 
//DMA1_Stream4中断服务函数
void DMA1_Stream4_IRQHandler(void)
{      
	if(DMA_GetITStatus(DMA1_Stream4,DMA_IT_TCIF4)==SET)DMA1_Stream4,传输完成标志
	{ 
		DMA_ClearITPendingBit(DMA1_Stream4,DMA_IT_TCIF4);
      	i2s_tx_callback();	//执行回调函数,读取数据等操作在这里面处理  
	}   											 
}  
//I2S开始播放
void I2S_Play_Start(void)
{   	  
	DMA_Cmd(DMA1_Stream4,ENABLE);//开启DMA TX传输,开始播放 		
}
//关闭I2S播放
void I2S_Play_Stop(void)
{   
		DMA_Cmd(DMA1_Stream4,DISABLE);//关闭DMA,结束播放	 
 	 
} 

3、连结文件,将文件解码后,调用i2d提供的dma,读写数据,到i2s接口,驱动时序,模数转换
封装了一个wav.c文件,解码wav格式,然后dma,数据传输

#include "wavplay.h" 
#include "audioplay.h"
#include "usart.h" 
#include "delay.h" 
#include "malloc.h"
#include "ff.h"
#include "i2s.h"
#include "wm8978.h"
#include "key.h"
#include "led.h"
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32F407开发板
//WAV 解码代码	   
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2014/6/29
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2014-2024
//All rights reserved				
//********************************************************************************
//V1.0 说明
//1,支持16位/24位WAV文件播放
//2,最高可以支持到192K/24bit的WAV格式. 
// 	
 
__wavctrl wavctrl;		//WAV控制结构体
vu8 wavtransferend=0;	//i2s传输完成标志
vu8 wavwitchbuf=0;		//i2sbufx指示标志
 
//WAV解析初始化
//fname:文件路径+文件名
//wavx:wav 信息存放结构体指针
//返回值:0,成功;1,打开文件失败;2,非WAV文件;3,DATA区域未找到.
u8 wav_decode_init(u8* fname,__wavctrl* wavx)
{
	FIL*ftemp;
	u8 *buf; 
	u32 br=0;
	u8 res=0;
	
	ChunkRIFF *riff;
	ChunkFMT *fmt;
	ChunkFACT *fact;
	ChunkDATA *data;
	ftemp=(FIL*)mymalloc(SRAMIN,sizeof(FIL));
	buf=mymalloc(SRAMIN,512);
	if(ftemp&&buf)	//内存申请成功
	{
		res=f_open(ftemp,(TCHAR*)fname,FA_READ);//打开文件
		if(res==FR_OK)
		{
			f_read(ftemp,buf,512,&br);	//读取512字节在数据
			riff=(ChunkRIFF *)buf;		//获取RIFF块
			if(riff->Format==0X45564157)//是WAV文件
			{
				fmt=(ChunkFMT *)(buf+12);	//获取FMT块 
				fact=(ChunkFACT *)(buf+12+8+fmt->ChunkSize);//读取FACT块
				if(fact->ChunkID==0X74636166||fact->ChunkID==0X5453494C)wavx->datastart=12+8+fmt->ChunkSize+8+fact->ChunkSize;//具有fact/LIST块的时候(未测试)
				else wavx->datastart=12+8+fmt->ChunkSize;  
				data=(ChunkDATA *)(buf+wavx->datastart);	//读取DATA块
				if(data->ChunkID==0X61746164)//解析成功!
				{
					wavx->audioformat=fmt->AudioFormat;		//音频格式
					wavx->nchannels=fmt->NumOfChannels;		//通道数
					wavx->samplerate=fmt->SampleRate;		//采样率
					wavx->bitrate=fmt->ByteRate*8;			//得到位速
					wavx->blockalign=fmt->BlockAlign;		//块对齐
					wavx->bps=fmt->BitsPerSample;			//位数,16/24/32位
					
					wavx->datasize=data->ChunkSize;			//数据块大小
					wavx->datastart=wavx->datastart+8;		//数据流开始的地方. 
					 
					printf("wavx->audioformat:%d\r\n",wavx->audioformat);
					printf("wavx->nchannels:%d\r\n",wavx->nchannels);
					printf("wavx->samplerate:%d\r\n",wavx->samplerate);
					printf("wavx->bitrate:%d\r\n",wavx->bitrate);
					printf("wavx->blockalign:%d\r\n",wavx->blockalign);
					printf("wavx->bps:%d\r\n",wavx->bps);
					printf("wavx->datasize:%d\r\n",wavx->datasize);
					printf("wavx->datastart:%d\r\n",wavx->datastart);  
				}else res=3;//data区域未找到.
			}else res=2;//非wav文件
			
		}else res=1;//打开文件错误
	}
	f_close(ftemp);
	myfree(SRAMIN,ftemp);//释放内存
	myfree(SRAMIN,buf); 
	return 0;
}

//填充buf
//buf:数据区
//size:填充数据量
//bits:位数(16/24)
//返回值:读到的数据个数
u32 wav_buffill(u8 *buf,u16 size,u8 bits)
{
	u16 readlen=0;
	u32 bread;
	u16 i;
	u8 *p;
	if(bits==24)//24bit音频,需要处理一下
	{
		readlen=(size/4)*3;							//此次要读取的字节数
		f_read(audiodev.file,audiodev.tbuf,readlen,(UINT*)&bread);	//读取数据
		p=audiodev.tbuf;
		for(i=0;i<size;)
		{
			buf[i++]=p[1];
			buf[i]=p[2]; 
			i+=2;
			buf[i++]=p[0];
			p+=3;
		} 
		bread=(bread*4)/3;		//填充后的大小.
	}else 
	{
		f_read(audiodev.file,buf,size,(UINT*)&bread);//16bit音频,直接读取数据  
		if(bread<size)//不够数据了,补充0
		{
			for(i=bread;i<size-bread;i++)buf[i]=0; 
		}
	}
	return bread;
}  
//WAV播放时,I2S DMA传输回调函数
void wav_i2s_dma_tx_callback(void) 
{   
	u16 i;
	if(DMA1_Stream4->CR&(1<<19))
	{
		wavwitchbuf=0;
		if((audiodev.status&0X01)==0)
		{
			for(i=0;i<WAV_I2S_TX_DMA_BUFSIZE;i++)//暂停
			{
				audiodev.i2sbuf1[i]=0;//填充0
			}
		}
	}else 
	{
		wavwitchbuf=1;
		if((audiodev.status&0X01)==0)
		{
			for(i=0;i<WAV_I2S_TX_DMA_BUFSIZE;i++)//暂停
			{
				audiodev.i2sbuf2[i]=0;//填充0
			}
		}
	}
	wavtransferend=1;
} 
//得到当前播放时间
//fx:文件指针
//wavx:wav播放控制器
void wav_get_curtime(FIL*fx,__wavctrl *wavx)
{
	long long fpos;  	
 	wavx->totsec=wavx->datasize/(wavx->bitrate/8);	//歌曲总长度(单位:秒) 
	fpos=fx->fptr-wavx->datastart; 					//得到当前文件播放到的地方 
	wavx->cursec=fpos*wavx->totsec/wavx->datasize;	//当前播放到第多少秒了?	
}
//播放某个WAV文件
//fname:wav文件路径.
//返回值:
//KEY0_PRES:下一曲
//KEY1_PRES:上一曲
//其他:错误
u8 wav_play_song(u8* fname)
{
	u8 key;
	u8 t=0; 
	u8 res;  
	u32 fillnum; 
	audiodev.file=(FIL*)mymalloc(SRAMIN,sizeof(FIL));
	audiodev.i2sbuf1=mymalloc(SRAMIN,WAV_I2S_TX_DMA_BUFSIZE);
	audiodev.i2sbuf2=mymalloc(SRAMIN,WAV_I2S_TX_DMA_BUFSIZE);
	audiodev.tbuf=mymalloc(SRAMIN,WAV_I2S_TX_DMA_BUFSIZE);
	if(audiodev.file&&audiodev.i2sbuf1&&audiodev.i2sbuf2&&audiodev.tbuf)
	{ 
		res=wav_decode_init(fname,&wavctrl);//得到文件的信息
		if(res==0)//解析文件成功
		{
			if(wavctrl.bps==16)
			{
				WM8978_I2S_Cfg(2,0);	//飞利浦标准,16位数据长度
				I2S2_Init(I2S_Standard_Phillips,I2S_Mode_MasterTx,I2S_CPOL_Low,I2S_DataFormat_16bextended);		//飞利浦标准,主机发送,时钟低电平有效,16位扩展帧长度
			}else if(wavctrl.bps==24)
			{
				WM8978_I2S_Cfg(2,2);	//飞利浦标准,24位数据长度
				I2S2_Init(I2S_Standard_Phillips,I2S_Mode_MasterTx,I2S_CPOL_Low,I2S_DataFormat_24b);		//飞利浦标准,主机发送,时钟低电平有效,24位扩展帧长度
			}
			I2S2_SampleRate_Set(wavctrl.samplerate);//设置采样率
			I2S2_TX_DMA_Init(audiodev.i2sbuf1,audiodev.i2sbuf2,WAV_I2S_TX_DMA_BUFSIZE/2); //配置TX DMA
			i2s_tx_callback=wav_i2s_dma_tx_callback;			//回调函数指wav_i2s_dma_callback
			audio_stop();
			res=f_open(audiodev.file,(TCHAR*)fname,FA_READ);	//打开文件
			if(res==0)
			{
				f_lseek(audiodev.file, wavctrl.datastart);		//跳过文件头
				fillnum=wav_buffill(audiodev.i2sbuf1,WAV_I2S_TX_DMA_BUFSIZE,wavctrl.bps);
				fillnum=wav_buffill(audiodev.i2sbuf2,WAV_I2S_TX_DMA_BUFSIZE,wavctrl.bps);
				audio_start();  
				while(res==0)
				{ 
					while(wavtransferend==0);//等待wav传输完成; 
					wavtransferend=0;
					if(fillnum!=WAV_I2S_TX_DMA_BUFSIZE)//播放结束?
					{
						res=KEY0_PRES;
						break;
					} 
 					if(wavwitchbuf)fillnum=wav_buffill(audiodev.i2sbuf2,WAV_I2S_TX_DMA_BUFSIZE,wavctrl.bps);//填充buf2
					else fillnum=wav_buffill(audiodev.i2sbuf1,WAV_I2S_TX_DMA_BUFSIZE,wavctrl.bps);//填充buf1
					while(1)
					{
						key=KEY_Scan(0); 
						if(key==WKUP_PRES)//暂停
						{
							if(audiodev.status&0X01)audiodev.status&=~(1<<0);
							else audiodev.status|=0X01;  
						}
						if(key==KEY2_PRES||key==KEY0_PRES)//下一曲/上一曲
						{
							res=key;
							break; 
						}
						wav_get_curtime(audiodev.file,&wavctrl);//得到总时间和当前播放的时间 
						audio_msg_show(wavctrl.totsec,wavctrl.cursec,wavctrl.bitrate);
						t++;
						if(t==20)
						{
							t=0;
 							LED0=!LED0;
						}
						if((audiodev.status&0X01)==0)delay_ms(10);
						else break;
					}
				}
				audio_stop(); 
			}else res=0XFF; 
		}else res=0XFF;
	}else res=0XFF; 
	myfree(SRAMIN,audiodev.tbuf);	//释放内存
	myfree(SRAMIN,audiodev.i2sbuf1);//释放内存
	myfree(SRAMIN,audiodev.i2sbuf2);//释放内存 
	myfree(SRAMIN,audiodev.file);	//释放内存 
	return res;
} 

4、写了个app,应用层,调用wav接口,播放文件。

#include "audioplay.h"
#include "ff.h"
#include "malloc.h"
#include "usart.h"
#include "wm8978.h"
#include "i2s.h"
#include "led.h"
#include "lcd.h"
#include "delay.h"
#include "key.h"
#include "exfuns.h"  
#include "text.h"
#include "string.h"  
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32F407开发板
//音乐播放器 应用代码	   
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2014/5/24
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2014-2024
//All rights reserved									  
// 	
 

//音乐播放控制器
__audiodev audiodev;	  
 

//开始音频播放
void audio_start(void)
{
	audiodev.status=3<<0;//开始播放+非暂停
	I2S_Play_Start();
} 
//关闭音频播放
void audio_stop(void)
{
	audiodev.status=0;
	I2S_Play_Stop();
}  
//得到path路径下,目标文件的总个数
//path:路径		    
//返回值:总有效文件数
u16 audio_get_tnum(u8 *path)
{	  
	u8 res;
	u16 rval=0;
 	DIR tdir;	 		//临时目录
	FILINFO tfileinfo;	//临时文件信息		
	u8 *fn; 			 			   			     
    res=f_opendir(&tdir,(const TCHAR*)path); //打开目录
  	tfileinfo.lfsize=_MAX_LFN*2+1;						//长文件名最大长度
	tfileinfo.lfname=mymalloc(SRAMIN,tfileinfo.lfsize);	//为长文件缓存区分配内存
	if(res==FR_OK&&tfileinfo.lfname!=NULL)
	{
		while(1)//查询总的有效文件数
		{
	        res=f_readdir(&tdir,&tfileinfo);       		//读取目录下的一个文件
	        if(res!=FR_OK||tfileinfo.fname[0]==0)break;	//错误了/到末尾了,退出		  
     		fn=(u8*)(*tfileinfo.lfname?tfileinfo.lfname:tfileinfo.fname);			 
			res=f_typetell(fn);	
			if((res&0XF0)==0X40)//取高四位,看看是不是音乐文件	
			{
				rval++;//有效文件数增加1
			}	    
		}  
	} 
	myfree(SRAMIN,tfileinfo.lfname);
	return rval;
}
//显示曲目索引
//index:当前索引
//total:总文件数
void audio_index_show(u16 index,u16 total)
{
	//显示当前曲目的索引,及总曲目数
	LCD_ShowxNum(60+0,230,index,3,16,0X80);		//索引
	LCD_ShowChar(60+24,230,'/',16,0);
	LCD_ShowxNum(60+32,230,total,3,16,0X80); 	//总曲目				  	  
}
 
//显示播放时间,比特率 信息  
//totsec;音频文件总时间长度
//cursec:当前播放时间
//bitrate:比特率(位速)
void audio_msg_show(u32 totsec,u32 cursec,u32 bitrate)
{	
	static u16 playtime=0XFFFF;//播放时间标记	      
	if(playtime!=cursec)					//需要更新显示时间
	{
		playtime=cursec;
		//显示播放时间			 
		LCD_ShowxNum(60,210,playtime/60,2,16,0X80);		//分钟
		LCD_ShowChar(60+16,210,':',16,0);
		LCD_ShowxNum(60+24,210,playtime%60,2,16,0X80);	//秒钟		
 		LCD_ShowChar(60+40,210,'/',16,0); 	    	 
		//显示总时间    	   
 		LCD_ShowxNum(60+48,210,totsec/60,2,16,0X80);	//分钟
		LCD_ShowChar(60+64,210,':',16,0);
		LCD_ShowxNum(60+72,210,totsec%60,2,16,0X80);	//秒钟	  		    
		//显示位率			   
   		LCD_ShowxNum(60+110,210,bitrate/1000,4,16,0X80);//显示位率	 
		LCD_ShowString(60+110+32,210,200,16,16,"Kbps");	 
	} 		 
}
//播放音乐
void audio_play(void)
{
	u8 res;
 	DIR wavdir;	 		//目录
	FILINFO wavfileinfo;//文件信息
	u8 *fn;   			//长文件名
	u8 *pname;			//带路径的文件名
	u16 totwavnum; 		//音乐文件总数
	u16 curindex;		//图片当前索引
	u8 key;				//键值		  
 	u16 temp;
	u16 *wavindextbl;	//音乐索引表
	
	WM8978_ADDA_Cfg(1,0);	//开启DAC
	WM8978_Input_Cfg(0,0,0);//关闭输入通道
	WM8978_Output_Cfg(1,0);	//开启DAC输出   
	
 	while(f_opendir(&wavdir,"0:/MUSIC"))//打开音乐文件夹
 	{	    
		Show_Str(60,190,240,16,"MUSIC文件夹错误!",16,0);
		delay_ms(200);				  
		LCD_Fill(60,190,240,206,WHITE);//清除显示	     
		delay_ms(200);				  
	} 									  
	totwavnum=audio_get_tnum("0:/MUSIC"); //得到总有效文件数
  	while(totwavnum==NULL)//音乐文件总数为0		
 	{	    
		Show_Str(60,190,240,16,"没有音乐文件!",16,0);
		delay_ms(200);				  
		LCD_Fill(60,190,240,146,WHITE);//清除显示	     
		delay_ms(200);				  
	}										   
  	wavfileinfo.lfsize=_MAX_LFN*2+1;						//长文件名最大长度
	wavfileinfo.lfname=mymalloc(SRAMIN,wavfileinfo.lfsize);	//为长文件缓存区分配内存
 	pname=mymalloc(SRAMIN,wavfileinfo.lfsize);				//为带路径的文件名分配内存
 	wavindextbl=mymalloc(SRAMIN,2*totwavnum);				//申请2*totwavnum个字节的内存,用于存放音乐文件索引
 	while(wavfileinfo.lfname==NULL||pname==NULL||wavindextbl==NULL)//内存分配出错
 	{	    
		Show_Str(60,190,240,16,"内存分配失败!",16,0);
		delay_ms(200);				  
		LCD_Fill(60,190,240,146,WHITE);//清除显示	     
		delay_ms(200);				  
	}  	 
 	//记录索引
    res=f_opendir(&wavdir,"0:/MUSIC"); //打开目录
	if(res==FR_OK)
	{
		curindex=0;//当前索引为0
		while(1)//全部查询一遍
		{
			temp=wavdir.index;								//记录当前index
	        res=f_readdir(&wavdir,&wavfileinfo);       		//读取目录下的一个文件
	        if(res!=FR_OK||wavfileinfo.fname[0]==0)break;	//错误了/到末尾了,退出		  
     		fn=(u8*)(*wavfileinfo.lfname?wavfileinfo.lfname:wavfileinfo.fname);			 
			res=f_typetell(fn);	
			if((res&0XF0)==0X40)//取高四位,看看是不是音乐文件	
			{
				wavindextbl[curindex]=temp;//记录索引
				curindex++;
			}	    
		} 
	}   
   	curindex=0;											//从0开始显示
   	res=f_opendir(&wavdir,(const TCHAR*)"0:/MUSIC"); 	//打开目录
	while(res==FR_OK)//打开成功
	{	
		dir_sdi(&wavdir,wavindextbl[curindex]);			//改变当前目录索引	   
        res=f_readdir(&wavdir,&wavfileinfo);       		//读取目录下的一个文件
        if(res!=FR_OK||wavfileinfo.fname[0]==0)break;	//错误了/到末尾了,退出
     	fn=(u8*)(*wavfileinfo.lfname?wavfileinfo.lfname:wavfileinfo.fname);			 
		strcpy((char*)pname,"0:/MUSIC/");				//复制路径(目录)
		strcat((char*)pname,(const char*)fn);  			//将文件名接在后面
 		LCD_Fill(60,190,240,190+16,WHITE);				//清除之前的显示
		Show_Str(60,190,240-60,16,fn,16,0);				//显示歌曲名字 
		audio_index_show(curindex+1,totwavnum);
		key=audio_play_song(pname); 			 		//播放这个音频文件
		if(key==KEY2_PRES)		//上一曲
		{
			if(curindex)curindex--;
			else curindex=totwavnum-1;
 		}else if(key==KEY0_PRES)//下一曲
		{
			curindex++;		   	
			if(curindex>=totwavnum)curindex=0;//到末尾的时候,自动从头开始
 		}else break;	//产生了错误 	 
	} 											  
	myfree(SRAMIN,wavfileinfo.lfname);	//释放内存			    
	myfree(SRAMIN,pname);				//释放内存			    
	myfree(SRAMIN,wavindextbl);			//释放内存	 
} 
//播放某个音频文件
u8 audio_play_song(u8* fname)
{
	u8 res;  
	res=f_typetell(fname); 
	switch(res)
	{
		case T_WAV:
			res=wav_play_song(fname);
			break;
		default://其他文件,自动跳转到下一曲
			printf("can't play:%s\r\n",fname);
			res=KEY0_PRES;
			break;
	}
	return res;
}

ok有了上面的基础,回忆,再回到linux平台,看看有什么区别,发现,其实是非常像的。单片机和linux驱动开发,区别就在于,linux内核,提供了一套,专门管理文件的架构,一切皆是文件,所以,把驱动也看成一个文件,来给应用层操作,所以内核就,在这个基础上,提供一套注册到内核的接口,当做文件来管理了。

二、ALSA架构

ALSA(Advanced Linux Sound Architecture)即高级 Linux 声音架构,目前已经成为了linux的主流音频体系结构,ALSA在内核部分提供alsa-driver对音频驱动进行耦合和管理,在用户空间空间提供alsa-lib,应用开发人员可以使用alsa-lib接口控制声卡。

linux ASoC音频设备驱动
ASoC是ALSA在SoC方面的发展和演变,它的本质仍然属于ALSA,但是在ALSA架构基础上对CPU相关的代码和Codec相关的代码进行了分离,其原因是采用传统ALSA架构情况下,同一型号的Codec工作于不同的CPU时,需要不同的驱动,这是不符合代码重用的要求的。

ASoC主要由3部分组成:

1)Codec驱动,这一部分只关系Codec本身,与CPU相关的特性不由此部分操作
(2)平台驱动,这一部分只关心CPU本身,不关系Codec,它主要处理了两个问题:DMA引擎和SoC解除的PCM、IIS或AC’97数字接口控制。
(3)板驱动,这一部分将平台驱动和Codec驱动绑定在一起,描述了板一级的硬件特征,
    其中machine是连接codec driver及platform driver的桥梁

以上3部分中,1和2基本都可以仍然是通用的驱动了,即Codec驱动认为自己可以连接任意CPU,而CPU的IIS、PCM、或AC’97接口对应的平台驱动则认为自己可以连接符号其接口类型的Codec,只有3是不通用的,由特定的电路板上具体的CPU和Codec确定,比如codex设置48k的采样率,16位,那我的soc是不知道的,也是需要配置成48k采样率,16位,否则,采集的数据是不对。这样就减少了耦合度。就是设备驱动分离的思想。因此它很像一个插座,上面插着Codec和平台这两个插头。ASoC的用户空间编程方法与ALSA完全一致。

嵌入式移动设备的音频子系统目前主要是ALSA 驱动 asoc 框架,其中包括 codec driver、 soc的platform driver、 板载硬件machine driver 等。

1、codec driver,音频编解码,adc,dac 转换芯片驱动,只关心 codec 本身,只需调用字符设备的框架(i2c,i2s的接口),控制code设置参数,模数,数模的转换。
2、platform driver 主要是平台 soc相关的驱动,不同soc,cpu 提供dai( 如 i2s), dma 等部分,提供时序控制的驱动代码,设置寄存器。
3、machine driver 主要将 platform driver 和 codec driver 连接起来。 

在这里插入图片描述

platform 和 dai接口driver 一般修改较少,这些都是芯片商写好的,我们作为使用者,只需关心codex, 主要修改machine 和 codec driver,这也将是我们后续分析的重点。

ALSA架构图如下:
在这里插入图片描述
在这里插入图片描述
一个音频文件的播放流程如下图,由dma将音频文件的内容从内存搬运到IIS控制器,后传送到codec音频解码芯片,将数字信号转换为模拟信号输出给耳机或者喇叭,如下图
在这里插入图片描述

上面这些图,看完后,音频驱动框架就非常清楚了。关于i2s的介绍,本篇不介绍,在第一篇有介绍。

他们的文件在linux内核的目录如下。

include/sound/driver.h
sound/core/*.c

3、alsa设备文件介绍

linux把注册的codex当做card,声卡的意思。设备节点目录:/dev/snd

ALSA系统包括包括驱动包alsa-driver、开发包alsa-libs、开发包插件alsa-libplugins、设置管理工具包alsa-utils、其他声音相关处理小程序包alsa-tools、特殊音频固件支持包alsa-firmware、OSS接口兼容模拟层工具alsa-oss供7个子项目,其中只有驱动包是必需的。

目前ALSA内核提供给用户空间的接口有:

1)设备信息接口(/proc/asound)
(2)设备控制接口(/dev/snd/controlCX)
(3)混音器设备接口(/dev/snd/mixerCXDX)
(4)PCM设备接口(/dev/snd/pcmCXDX)
(5)原始MIDI(迷笛)设备接口(/dev/snd/midiCXDX)
(6)声音合成(synthesizer)设备接口(/dev/snd/seq)
(7)定时器接口(/dev/snd/timer)

这些接口被提供给alsa-lib使用,而不是给应用程序使用,应用程序最好使用alsa-lib,或者更高级的接口比如jack提供的接口。

controlC0:用于声卡的控制,例如通道选择,混音,麦克风的控制等;
midiC0D0:用于播放midi音频;
pcmC0D0c : 用于录音的pcm设备;
pcmC0D0p :用于播放的pcm设备;
seq :音序器;
timer :定时器;

在这里插入图片描述

card声卡和组件管理

card声卡和组件管理
/* 对于每个声卡,必须创建一个card实例,该函数用于创建card
    参数idx为索引号
    参数id为标识的字符串
    参数module一般指向THIS_MODULE
    参数extra_size是要分配的额外的数据的大小,分配的extra_size大小的内存将作为card->private_data*/
 struct snd_card *snd_card_new(int idx, const char *id,struct module *module, int extra_size)

  /* 注册声卡 */
int snd_card_register(struct snd_card *card)

/* 释放(注销)声卡 */
int snd_card_free(struct snd_card *card)

 /* 创建一个ALSA设备部件
     参数type为设备类型,查看宏SNDRV_DEV_XXX*/
int snd_device_new(struct snd_card *card, snd_device_type_t type,void *device_data, struct snd_device_ops *ops)

  /* 
   释放声卡的设备
   参数device_data指向要设备的私有数据
   */
int snd_device_free(struct snd_card *card, void *device_data)

PCM 设备接口

/* 创建PCM实例
     参数card指向声卡
     参数id是标识字符串
     参数device为PCM设备引索(0表示第1个PCM设备)
     参数playback_count为播放设备的子流数
     参数capture_count为录音设备的子流数
     参数指向构造的PCM实例*/
int snd_pcm_new(struct snd_card *card, const char *id, int device,int playback_count, int capture_count,struct snd_pcm ** rpcm)

 /* 设置PCM操作函数 
     参数direction,查看宏SNDRV_PCM_STREAM_XXX*/
void snd_pcm_set_ops(struct snd_pcm *pcm, int direction, struct snd_pcm_ops *ops)

 /* 分配DMA缓冲区,仅当DMA缓冲区已预分配的情况下才可调用该函数 */
int snd_pcm_lib_malloc_pages(struct snd_pcm_substream *substream, size_t size)

 /* 释放由snd_pcm_lib_malloc_pages函数分配的一致性DMA缓冲区 */
int snd_pcm_lib_free_pages(struct snd_pcm_substream *substream)

 /* 分配缓冲区的最简单的方法是调用该函数
     type的取值可查看宏SNDRV_DMA_TYPE_* */
int snd_pcm_lib_preallocate_pages_for_all(struct snd_pcm *pcm,int type, void *data,size_t size, size_t max)

控制接口说明


/* 创建一个control实例----struct snd_kcontrol结构体
     参数ncontrol为初始化记录
     private_data为设置的私有数据 */
struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol, void *private_data)

 /* 为声卡添加一个控制实例 */
int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol)

/* 驱动程序可中断服务程序中调用该函数来改变或更新一个control*/
void snd_ctl_notify(struct snd_card *card, unsigned int mask,struct snd_ctl_elem_id *id)

基于ALSA音频框架的驱动程序设计

1:struct snd_card *snd_card_new(int idx, const char *id,struct module *module, int extra_size);/* 创建一个声卡 */

2:static struct snd_device_ops ops = {
		.dev_free =     xxx_free,
	};

3:struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol,void *private_data); /*  创建control实例 */

4:int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol);    /* 为声卡添加控制实例 */

5:int snd_device_new(struct snd_card *card, SNDRV_DEV_CODEC,void *device_data, struct snd_device_ops *ops);  /* 创建一个ALSA设备部件-----编解码设备 */

6:int snd_pcm_new(struct snd_card *card, const char *id, int device,int playback_count, int capture_count,struct snd_pcm ** rpcm) /*  创建PCM实例 */

8:int snd_pcm_lib_preallocate_pages_for_all(struct snd_pcm *pcm,int type, void *data,size_t size, size_t max)  /* 分配缓冲区 */

9:void snd_pcm_set_ops(struct snd_pcm *pcm, SNDRV_PCM_STREAM_PLAYBACK, struct snd_pcm_ops *ops)  /* 设置PCM操作函数----播放  */

10:void snd_pcm_set_ops(struct snd_pcm *pcm, SNDRV_PCM_STREAM_CAPTURE, struct snd_pcm_ops *ops)  /* 设置PCM操作函数 ----录音 */

11:/*音频相关的初始化:音频流相关、引脚等相关的初始化*/

12:DMA相关的设置

13:int snd_card_register(struct snd_card *card);  /* 注册声卡 */IRQ_AC97

.ASoC音频驱动重要结构体

1:ASoC Codec驱动:

struct snd_soc_dai   /* 数字音频接口,描述了Codec DAI(数字音频接口---Digital Audio Interface)和PCM配置 */
struct snd_soc_codec     /* 音频编解码器---IO操作、动态音频电源管理以及时钟、PLL等控制 */
struct snd_soc_dai_ops  /* 数字音频接口DAI操作函数集,soc接口时序设相关 */
struct snd_soc_ops     /* SoC操作函数----Codec音频操作 */


*2:ASoC平台驱动:

*在ASoC平台驱动部分,同样存在着Codec驱动中的snd_soc_dai、snd_soc_dai_ops、snd_soc_ops这三个结构体的实例用于描述
*DAI和DAI的操作,不过不同的是,在平台驱动中,它们只描述CPU相关的部分而不描述Codec。除此之外,在ASoC
*平台驱动中,必须实现完整的DMA驱动,即传统ALSA的and_pcm_ops结构体成员函数trigger()pointer()等,因此ASoC平台
*驱动通常由DAI和DMA两部分组成。

struct snd_soc_device { /* SoC设备 */
struct snd_soc_dai_link  { /* 绑定ASoC Codec驱动和CPU端的平台驱动数据结构 */

二、CPU_DAI驱动分析

这个是关于soc,cpu的硬件接口dai,寄存器操作,控制时序的驱动。

1、CPU_DAI相关的重要数据

struct snd_soc_dai {
	const char *name;  //描述dai的名称
	int id; 
	struct device *dev;   
 
	/* driver ops */
	struct snd_soc_dai_driver *driver;    //指向snd_soc_dai_driver,这个结构里描述了cpu_dai的支持类型和各种对cpu_dai进行操作的api
 
	/* DAI runtime info */
	unsigned int capture_active:1;		/* stream is in use */
	unsigned int playback_active:1;		/* stream is in use */
	unsigned int symmetric_rates:1;
	unsigned int symmetric_channels:1;
	unsigned int symmetric_samplebits:1;
	unsigned int active;
	unsigned char probed:1;
 
	struct snd_soc_dapm_widget *playback_widget;
	struct snd_soc_dapm_widget *capture_widget;
 
	/* DAI DMA data */
	void *playback_dma_data;    //cpu_dai播放时使用的dma
	void *capture_dma_data;    //cpu_dai录制时使用的dma
 
	/* Symmetry data - only valid if symmetry is being enforced */
	unsigned int rate;
	unsigned int channels;
	unsigned int sample_bits;
 
	/* parent platform/codec */
	struct snd_soc_codec *codec;    //指向所关联的codec设备
	struct snd_soc_component *component;     //指向所关联的component
 
	/* CODEC TDM slot masks and params (for fixup) */
	unsigned int tx_mask;
	unsigned int rx_mask;
 
	struct list_head list;
};
struct snd_soc_dai_driver {
	/* DAI description */
	const char *name;    
	unsigned int id;
	unsigned int base;
	struct snd_soc_dobj dobj;
 
	/* DAI driver callbacks */
	int (*probe)(struct snd_soc_dai *dai);
	int (*remove)(struct snd_soc_dai *dai);
	int (*suspend)(struct snd_soc_dai *dai);    //休眠接口
	int (*resume)(struct snd_soc_dai *dai);    //唤醒接口
	/* compress dai */
	int (*compress_new)(struct snd_soc_pcm_runtime *rtd, int num);
	/* Optional Callback used at pcm creation*/
	int (*pcm_new)(struct snd_soc_pcm_runtime *rtd,
		       struct snd_soc_dai *dai);
	/* DAI is also used for the control bus */
	bool bus_control;
 
	/* ops */
	const struct snd_soc_dai_ops *ops;    //对cpu_dai进行操作的api接口集合
	const struct snd_soc_cdai_ops *cops;
 
	/* DAI capabilities */
	struct snd_soc_pcm_stream capture;    //描述播放模式dai支持的参数
	struct snd_soc_pcm_stream playback;    //描述录制模式dai支持的参数
	unsigned int symmetric_rates:1;
	unsigned int symmetric_channels:1;
	unsigned int symmetric_samplebits:1;
 
	/* probe ordering - for components with runtime dependencies */
	int probe_order;
	int remove_order;
};
struct snd_soc_dai_ops {
	/*
	 * DAI clocking configuration, all optional.
	 * Called by soc_card drivers, normally in their hw_params.
	 */
 
        //设置时钟
	int (*set_sysclk)(struct snd_soc_dai *dai,
		int clk_id, unsigned int freq, int dir);
        //设置锁相环
	int (*set_pll)(struct snd_soc_dai *dai, int pll_id, int source,
		unsigned int freq_in, unsigned int freq_out);
        //设置时钟分频系数
	int (*set_clkdiv)(struct snd_soc_dai *dai, int div_id, int div);
	int (*set_bclk_ratio)(struct snd_soc_dai *dai, unsigned int ratio);
 
	/*
	 * DAI format configuration
	 * Called by soc_card drivers, normally in their hw_params.
	 */
	int (*set_fmt)(struct snd_soc_dai *dai, unsigned int fmt);
	int (*xlate_tdm_slot_mask)(unsigned int slots,
		unsigned int *tx_mask, unsigned int *rx_mask);
	int (*set_tdm_slot)(struct snd_soc_dai *dai,
		unsigned int tx_mask, unsigned int rx_mask,
		int slots, int slot_width);
	int (*set_channel_map)(struct snd_soc_dai *dai,
		unsigned int tx_num, unsigned int *tx_slot,
		unsigned int rx_num, unsigned int *rx_slot);
	int (*get_channel_map)(struct snd_soc_dai *dai,
			unsigned int *tx_num, unsigned int *tx_slot,
			unsigned int *rx_num, unsigned int *rx_slot);
	int (*set_tristate)(struct snd_soc_dai *dai, int tristate);
 
	int (*set_sdw_stream)(struct snd_soc_dai *dai,
			void *stream, int direction);
	/*
	 * DAI digital mute - optional.
	 * Called by soc-core to minimise any pops.
	 */
	int (*digital_mute)(struct snd_soc_dai *dai, int mute);
	int (*mute_stream)(struct snd_soc_dai *dai, int mute, int stream);
 
	/*
	 * ALSA PCM audio operations - all optional.
	 * Called by soc-core during audio PCM operations.
	 */
	int (*startup)(struct snd_pcm_substream *,
		struct snd_soc_dai *);
	void (*shutdown)(struct snd_pcm_substream *,
		struct snd_soc_dai *);
	int (*hw_params)(struct snd_pcm_substream *,
		struct snd_pcm_hw_params *, struct snd_soc_dai *);
	int (*hw_free)(struct snd_pcm_substream *,
		struct snd_soc_dai *);
	int (*prepare)(struct snd_pcm_substream *,
		struct snd_soc_dai *);
	/*
	 * NOTE: Commands passed to the trigger function are not necessarily
	 * compatible with the current state of the dai. For example this
	 * sequence of commands is possible: START STOP STOP.
	 * So do not unconditionally use refcounting functions in the trigger
	 * function, e.g. clk_enable/disable.
	 */
	int (*trigger)(struct snd_pcm_substream *, int,
		struct snd_soc_dai *);
	int (*bespoke_trigger)(struct snd_pcm_substream *, int,
		struct snd_soc_dai *);
	/*
	 * For hardware based FIFO caused delay reporting.
	 * Optional.
	 */
	snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *,
		struct snd_soc_dai *);
};

看下关于soc接口部分,dts配置这些参数是怎么设置的,单片机是写死在i2s接口文件里,linux写在dts,方便修改适配

i2s1_8ch: i2s@fe410000 {
        compatible = "rockchip,rk3568-i2s-tdm";
        reg = <0x0 0xfe410000 0x0 0x1000>;
        interrupts = <GIC_SPI 53 IRQ_TYPE_LEVEL_HIGH>;
        clocks = <&cru MCLK_I2S1_8CH_TX>, <&cru MCLK_I2S1_8CH_RX>, <&cru HCLK_I2S1_8CH>;
        clock-names = "mclk_tx", "mclk_rx", "hclk";
        dmas = <&dmac1 2>, <&dmac1 3>;
        dma-names = "tx", "rx";
        resets = <&cru SRST_M_I2S1_8CH_TX>, <&cru SRST_M_I2S1_8CH_RX>;
        reset-names = "tx-m", "rx-m";
        rockchip,cru = <&cru>;
        rockchip,grf = <&grf>;
        #sound-dai-cells = <0>;
        pinctrl-names = "default";
        pinctrl-0 = <&i2s1m0_sclktx
                 &i2s1m0_sclkrx
                 &i2s1m0_lrcktx
                 &i2s1m0_lrckrx
                 &i2s1m0_sdi0
                 &i2s1m0_sdi1
                 &i2s1m0_sdi2
                 &i2s1m0_sdi3
                 &i2s1m0_sdo0
                 &i2s1m0_sdo1
                 &i2s1m0_sdo2
                 &i2s1m0_sdo3>;
        status = "disabled";
};

3、rockchip_i2s_tdm_probe分析
cpu_dai驱动从dts获取到cpu_dai硬件信息后,进入 rockchip_i2s_tdm_probe函数,进行一系列cpu_dai的硬件初始化工作

static int rockchip_i2s_tdm_probe(struct platform_device *pdev)
{
    ...
    //初始化snd_soc_dai_driver结构,dai的支持配置和各种用于控制dai的api接口
    rockchip_i2s_tdm_dai_prepare(pdev, &soc_dai);
    //从设备树获取bclk_fs值,没有默认64
    i2s_tdm->bclk_fs = 64;
    if (!of_property_read_u32(node, "rockchip,bclk-fs", &val)) {
	if ((val >= 32) && (val % 2 == 0))
		i2s_tdm->bclk_fs = val;
    }
    //从设备树中读取各种初始化cpu_dai相关的设置保存到i2s_tdm,例如I2S的时钟参数等
    of_property_read_u32(node, ..., &val)devm_reset_control_get(&pdev->dev, ...);
    devm_clk_get(&pdev->dev, "hclk")
    devm_clk_get(&pdev->dev, "mclk_tx");
    devm_clk_get(&pdev->dev, "mclk_rx")
    
    //获取cpu_dai的寄存器地址,并初始化cpu_dai
    platform_get_resource(pdev, IORESOURCE_MEM, 0);
    devm_ioremap_resource(&pdev->dev, res);
   
    //初始化regmap   
    devm_regmap_init_mmio(&pdev->dev, regs, &rockchip_i2s_tdm_regmap_config);
    
    //TX/RX的dma参数获取
    i2s_tdm->playback_dma_data.addr = res->start + I2S_TXDR;
    i2s_tdm->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
    i2s_tdm->playback_dma_data.maxburst = 8;
    i2s_tdm->capture_dma_data.addr = res->start + I2S_RXDR;
    i2s_tdm->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
    i2s_tdm->capture_dma_data.maxburst = 8;
    
    //初始化cpu_dai的tx和rx通路
    rockchip_i2s_tdm_tx_path_prepare(i2s_tdm, node);
    rockchip_i2s_tdm_rx_path_prepare(i2s_tdm, node);
    i2s_tdm->soc_data->init(&pdev->dev, res->start);
    
    //注册component组件
    devm_snd_soc_register_component(&pdev->dev,&rockchip_i2s_tdm_component,soc_dai, 1);
        - >snd_soc_register_component(dev, cmpnt_drv, dai_drv, num_dai);
    //获取dma    
    devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
        ->snd_soc_add_platform(dev, &pcm->platform,&dmaengine_pcm_platform);
    return 0}
snd_soc_register_component(dev, cmpnt_drv, dai_drv, num_dai)->
    snd_soc_component_initialize(cmpnt, cmpnt_drv, dev); //初始化component
            //注册了一个dai,并填充snd_soc_dai_driver数据
            snd_soc_register_dais(cmpnt, dai_drv, num_dai, true); 
            //将component加入component_list链表
            snd_soc_component_add(cmpnt);

该函数被传入两个关键参数struct snd_soc_component_driver cmpnt_drv、struct snd_soc_dai_driver dai_drv,定义如下

static const struct snd_soc_component_driver rockchip_i2s_component = {
	.name = DRV_NAME,
};
struct snd_soc_dai_driver rockchip_i2s_tdm_dai = {
		.probe = rockchip_i2s_tdm_dai_probe,    
		.playback = {
			.stream_name = "Playback",
			.channels_min = 2,
			.channels_max = 16,
			.rates = SNDRV_PCM_RATE_8000_192000,
			.formats = (SNDRV_PCM_FMTBIT_S8 |
				    SNDRV_PCM_FMTBIT_S16_LE |
				    SNDRV_PCM_FMTBIT_S20_3LE |
				    SNDRV_PCM_FMTBIT_S24_LE |
				    SNDRV_PCM_FMTBIT_S32_LE),
		},
		.capture = {
			.stream_name = "Capture",
			.channels_min = 2,
			.channels_max = 16,
			.rates = SNDRV_PCM_RATE_8000_192000,
			.formats = (SNDRV_PCM_FMTBIT_S8 |
				    SNDRV_PCM_FMTBIT_S16_LE |
				    SNDRV_PCM_FMTBIT_S20_3LE |
				    SNDRV_PCM_FMTBIT_S24_LE |
				    SNDRV_PCM_FMTBIT_S32_LE),
		},
        //对cpu_dai的api操作集合,如dai的时钟配置、格式配置等
		.ops = &rockchip_i2s_tdm_dai_ops, 
};
dmaengine_pcm_request_chan_of(pcm, dev, config);
     //根据name去dts中找资源,申请对应的DMA通道
     chan = dma_request_slave_channel_reason(dev, name);
         return of_dma_request_slave_channel(dev->of_node, name);
   
     //注册platform到ASoC Core
     ret = snd_soc_add_platform(dev, &pcm->platform,&dmaengine_pcm_platform);

platform中需要完成音频数据管理和音频数据的dma搬运,其中就涉及到了dma相关的操作,snd_soc_add_platform的目的就是完成dma通道的申请,并将pcm和dma关联起来

四、CODEC驱动分析

1、CODEC相关的重要数据

static const struct regmap_config rk817_codec_regmap_config = {
	.name = "rk817-codec",
	.reg_bits = 8,    //寄存器地址位数(必选项)
	.val_bits = 8,    //寄存器值位数(必选项)
	.reg_stride = 1,
	.max_register = 0x4f,    //最大寄存器值
	.cache_type = REGCACHE_FLAT,
	.volatile_reg = rk817_volatile_register,    
	.writeable_reg = rk817_codec_register,    //寄存器是否可写
	.readable_reg = rk817_codec_register,   //寄存器是否可写读
	.reg_defaults = rk817_reg_defaults,    //默认寄存器配置参数
	.num_reg_defaults = ARRAY_SIZE(rk817_reg_defaults),
};

2、配置codex的DTS

rk809_codec: codec {
            #sound-dai-cells = <0>;
            compatible = "rockchip,rk809-codec", "rockchip,rk817-codec";
            clocks = <&cru I2S1_MCLKOUT>;
            clock-names = "mclk";
            assigned-clocks = <&cru I2S1_MCLKOUT>, <&cru I2S1_MCLK_TX_IOE>;
            assigned-clock-rates = <12288000>;
            assigned-clock-parents = <&cru I2S1_MCLKOUT_TX>, <&cru I2S1_MCLKOUT_TX>;
            pinctrl-names = "default","spk_gpio";
            pinctrl-0 = <&i2s1m0_mclk>;
            pinctrl-1 = <&spk_ctl_gpio>;
            hp-volume = <20>;
            spk-volume = <3>;
            //mic-in-differential;
 
            capture-volume = <0>;
            io-channels = <&saradc 4>;
            hp-det-adc-value = <1000>;
            spk-ctl-gpios = <&gpio3 RK_PC5 GPIO_ACTIVE_HIGH>;
            status = "okay";
};

3、rk817_platform_probe分析

codec_dai驱动从dts获取到硬件信息后,通过platform平台框架注册的,然后进入rk817_platform_probe探测设备,;匹配成功后,进行一系列codec_dai的硬件初始化工作

static int rk817_platform_probe(struct platform_device *pdev)
{
    ...
    rk817_codec_data = devm_kzalloc(&pdev->dev,sizeof(struct rk817_codec_priv),
    GFP_KERNEL);    //给rk817_codec_data申请空间
    platform_set_drvdata(pdev, rk817_codec_data); //rk817_codec_data绑定pdev
    
    //从dts节点中获取adc通道、gpio节点、volume值等
    rk817_codec_parse_dt_property(&pdev->dev, rk817_codec_data); 
    //regmap初始化
    rk817_codec_data->regmap = devm_regmap_init_i2c(rk817->i2c,&rk817_codec_regmap_config);
    //mclk获取
    rk817_codec_data->mclk = devm_clk_get(&pdev->dev, "mclk");
    //componen注册
    devm_snd_soc_register_component(&pdev->dev, &soc_codec_dev_rk817,rk817_dai, ARRAY_SIZE(rk817_dai));
    ...    
}

devm_snd_soc_register_component后续分析:

初始化snd_soc_component的实例,然后注册dai,最终将注册的dai放入component->dai_list中,然后将分配的component放入到component_list链表中,遍历全局的component链表可以找到cpu_dai,codec_dai,起到作用就是将他们两绑定起来。

devm_snd_soc_register_component->
    snd_soc_register_component->
        snd_soc_add_component->
            snd_soc_component_initialize    //component初始化
            snd_soc_register_dais    //dai注册
            snd_soc_component_add    //component加入全局链表

五、MACHINE(simple-card.c)驱动分析

1、MACHINE相关的重要数据

struct snd_soc_dai_link {
	/* config - must be set by machine driver */
	const char *name;			/* Codec name */
	const char *stream_name;		/* Stream name */
	/*
	 * You MAY specify the link's CPU-side device, either by device name,
	 * or by DT/OF node, but not both. If this information is omitted,
	 * the CPU-side DAI is matched using .cpu_dai_name only, which hence
	 * must be globally unique. These fields are currently typically used
	 * only for codec to codec links, or systems using device tree.
	 */
	const char *cpu_name;
	struct device_node *cpu_of_node;
	/*
	 * You MAY specify the DAI name of the CPU DAI. If this information is
	 * omitted, the CPU-side DAI is matched using .cpu_name/.cpu_of_node
	 * only, which only works well when that device exposes a single DAI.
	 */
	const char *cpu_dai_name;
	/*
	 * You MUST specify the link's codec, either by device name, or by
	 * DT/OF node, but not both.
	 */
	const char *codec_name;
	struct device_node *codec_of_node;
	/* You MUST specify the DAI name within the codec */
	const char *codec_dai_name;
 
	struct snd_soc_dai_link_component *codecs;
	unsigned int num_codecs;
 
	/*
	 * You MAY specify the link's platform/PCM/DMA driver, either by
	 * device name, or by DT/OF node, but not both. Some forms of link
	 * do not need a platform.
	 */
	const char *platform_name;
	struct device_node *platform_of_node;
	int id;	/* optional ID for machine driver link identification */
 
	const struct snd_soc_pcm_stream *params;
	unsigned int num_params;
 
	unsigned int dai_fmt;           /* format to set on init */
 
	enum snd_soc_dpcm_trigger trigger[2]; /* trigger type for DPCM */
 
	/* codec/machine specific init - e.g. add machine controls */
	int (*init)(struct snd_soc_pcm_runtime *rtd);
 
	/* optional hw_params re-writing for BE and FE sync */
	int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd,
			struct snd_pcm_hw_params *params);
 
	/* machine stream operations */
	const struct snd_soc_ops *ops;
	const struct snd_soc_compr_ops *compr_ops;
 
	/* Mark this pcm with non atomic ops */
	bool nonatomic;
 
	/* For unidirectional dai links */
	unsigned int playback_only:1;
	unsigned int capture_only:1;
 
	/* Keep DAI active over suspend */
	unsigned int ignore_suspend:1;
 
	/* Symmetry requirements */
	unsigned int symmetric_rates:1;
	unsigned int symmetric_channels:1;
	unsigned int symmetric_samplebits:1;
 
	/* Do not create a PCM for this DAI link (Backend link) */
	unsigned int no_pcm:1;
 
	/* This DAI link can route to other DAI links at runtime (Frontend)*/
	unsigned int dynamic:1;
 
	/* This DAI link can be reconfigured at runtime (Backend) */
	unsigned int dynamic_be:1;
 
	/*
	 * This DAI can support no host IO (no pcm data is
	 * copied to from host)
	 */
	unsigned int no_host_mode:2;
 
	/* DPCM capture and Playback support */
	unsigned int dpcm_capture:1;
	unsigned int dpcm_playback:1;
 
	/* DPCM used FE & BE merged format */
	unsigned int dpcm_merged_format:1;
	/* DPCM used FE & BE merged channel */
	unsigned int dpcm_merged_chan:1;
	/* DPCM used FE & BE merged rate */
	unsigned int dpcm_merged_rate:1;
 
	/* pmdown_time is ignored at stop */
	unsigned int ignore_pmdown_time:1;
 
	/* Do not create a PCM for this DAI link (Backend link) */
	unsigned int ignore:1;
 
	struct list_head list; /* DAI link list of the soc card */
	struct snd_soc_dobj dobj; /* For topology */
 
	/* this value determines what all ops can be started asynchronously */
	enum snd_soc_async_ops async_ops;
};
struct snd_soc_card {
	const char *name;
	const char *long_name;
	const char *driver_name;
	char dmi_longname[80];
	char topology_shortname[32];
 
	struct device *dev;
	struct snd_card *snd_card;
	struct module *owner;
 
	struct mutex mutex;
	struct mutex dapm_mutex;
	struct mutex dapm_power_mutex;
 
	bool instantiated;
	bool topology_shortname_created;
 
	int (*probe)(struct snd_soc_card *card);
	int (*late_probe)(struct snd_soc_card *card);
	int (*remove)(struct snd_soc_card *card);
 
	/* the pre and post PM functions are used to do any PM work before and
	 * after the codec and DAI's do any PM work. */
	int (*suspend_pre)(struct snd_soc_card *card);
	int (*suspend_post)(struct snd_soc_card *card);
	int (*resume_pre)(struct snd_soc_card *card);
	int (*resume_post)(struct snd_soc_card *card);
 
	/* callbacks */
	int (*set_bias_level)(struct snd_soc_card *,struct snd_soc_dapm_context *dapm,
			      enum snd_soc_bias_level level);
	int (*set_bias_level_post)(struct snd_soc_card *,struct snd_soc_dapm_context *dapm,
				   enum snd_soc_bias_level level);
 
	int (*add_dai_link)(struct snd_soc_card *,
			    struct snd_soc_dai_link *link);
	void (*remove_dai_link)(struct snd_soc_card *,
			    struct snd_soc_dai_link *link);
 
	long pmdown_time;
 
	/* CPU <--> Codec DAI links  */
	struct snd_soc_dai_link *dai_link;  /* predefined links only */
	int num_links;  /* predefined links only */
	struct list_head dai_link_list; /* all links */
	int num_dai_links;
 
	struct list_head rtd_list;
	int num_rtd;
 
	/* optional codec specific configuration */
	struct snd_soc_codec_conf *codec_conf;
	int num_configs;
 
	/*
	 * optional auxiliary devices such as amplifiers or codecs with DAI
	 * link unused
	 */
	struct snd_soc_aux_dev *aux_dev;
	int num_aux_devs;
	struct list_head aux_comp_list;
 
	const struct snd_kcontrol_new *controls;
	int num_controls;
 
	/*
	 * Card-specific routes and widgets.
	 * Note: of_dapm_xxx for Device Tree; Otherwise for driver build-in.
	 */
	const struct snd_soc_dapm_widget *dapm_widgets;
	int num_dapm_widgets;
	const struct snd_soc_dapm_route *dapm_routes;
	int num_dapm_routes;
	const struct snd_soc_dapm_widget *of_dapm_widgets;
	int num_of_dapm_widgets;
	const struct snd_soc_dapm_route *of_dapm_routes;
	int num_of_dapm_routes;
	bool fully_routed;
 
	struct work_struct deferred_resume_work;
 
	/* lists of probed devices belonging to this card */
	struct list_head component_dev_list;
 
	struct list_head widgets;
	struct list_head paths;
	struct list_head dapm_list;
	struct list_head dapm_dirty;
 
	/* attached dynamic objects */
	struct list_head dobj_list;
 
	/* Generic DAPM context for the card */
	struct snd_soc_dapm_context dapm;
	struct snd_soc_dapm_stats dapm_stats;
	struct snd_soc_dapm_update *update;
 
#ifdef CONFIG_DEBUG_FS
	struct dentry *debugfs_card_root;
	struct dentry *debugfs_pop_time;
#endif
	u32 pop_time;
 
	void *drvdata;
};

DTS

rk809_sound: rk809-sound {
status = "okay";
compatible = "simple-audio-card";
simple-audio-card,format = "i2s";
simple-audio-card,name = "rockchip,rk809-codec";
simple-audio-card,mclk-fs = <256>;
simple-audio-card,widgets =
               "Microphone", "Mic Jack",
               "Headphone", "Headphone Jack";
simple-audio-card,routing =
               "Mic Jack", "MICBIAS1",
               "IN1P", "Mic Jack",
               "Headphone Jack", "HPOL",
               "Headphone Jack", "HPOR";
simple-audio-card,cpu {
   sound-dai = <&i2s1_8ch>;
};
simple-audio-card,codec {
   sound-dai = <&rk809_codec>;
};
};

3、asoc_simple_card_probe分析

codec_dai驱动从dts获取到cpu_dai和codec_dai的连接关系和节点后,进入asoc_simple_card_probe,进行声卡的注册和初始化工作

static int asoc_simple_card_probe(struct platform_device *pdev)
{
    struct simple_card_data *priv;
    struct snd_soc_dai_link *dai_link;
    struct simple_dai_props *dai_props;
    struct device *dev = &pdev->dev;
    struct device_node *np = dev->of_node;
    struct snd_soc_card *card;
    int num, ret;
    ...
    //获取节点"simple-audio-card,dai-link"
    //由于节点不存在 num = 1 
    if (np && of_get_child_by_name(np, PREFIX "dai-link"))
        num = of_get_child_count(np);
    }else{ num = 1; }
 
    //申请 priv\dai_props\dai_link的内存空间
    //priv->dai_props priv->dai_link
	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
	dai_props = devm_kcalloc(dev, num, sizeof(*dai_props), GFP_KERNEL);
	dai_link  = devm_kcalloc(dev, num, sizeof(*dai_link), GFP_KERNEL);
    
    //初始化snd_soc_card
    card = simple_priv_to_card(priv); //card=&(priv->snd_card)
    card->owner		= THIS_MODULE;
    card->dev		= dev;
    card->dai_link		= priv->dai_link;
    card->num_links		= num;
    card->probe		= asoc_simple_soc_card_probe;
    
    //如果simple-card 存在
    //对priv->dai_link priv->snd_card 部分填充和初始化
    if (np && of_device_is_available(np)){
      asoc_simple_card_parse_of(priv); //详细分析在下
    } else {...};      
    //实例化和填充card,绑定dailink,创建音频节点pcmC0D0P,并完成cpu_dai和codec的probe 
    ret=devm_snd_soc_register_card(dev, card)->
        return snd_soc_register_card(card); //详细分析在下
    return ret;
}

asoc_simple_card_parse_ of ( priv )分析如下:

static int asoc_simple_card_parse_of(struct simple_card_data *priv)
{
    //获取节点"simple-audio-card,dai-link"
    //不存在dai_link=NULL
    struct device_node *dai_link;
    dai_link = of_get_child_by_name(node, PREFIX "dai-link");
    
    /* 
    从设备树解析声卡的widgets
    card->of_dapm_widgets = widgets;
        -----card->of_dapm_widgets[0].id = snd_soc_dapm_mic;
        -----card->of_dapm_widgets[0].name = "Microphone";
        -----card->of_dapm_widgets[0].event = "Mic Jack"; 
    card->num_of_dapm_widgets = num_widgets;
        -----num_of_dapm_widgets = 2;
	*/
    ret = asoc_simple_card_of_parse_widgets(card, PREFIX);
    
    /*
    从设备树解析声卡的route
    card->num_of_dapm_routes = num_routes;
        -----num_of_dapm_routes = 4;
    card->of_dapm_routes = routes;
        -----card->of_dapm_routes[0].sink = "Mic Jack"; 
        -----card->of_dapm_routes[0].sourece = "MICBIAS1"
        -----card->of_dapm_routes[1].sink = "IN1P"; 
        -----card->of_dapm_routes[1].sourece = "Mic Jack"
    */
    ret = asoc_simple_card_of_parse_routing(card, PREFIX, 1);
    if (dai_link) {
        ...
    }else{
        /* 以下列出函数功能*/
        /*1 card->dai_link->dai_fmt = i2s 
             simple-audio-card,format = "i2s"
        */
        /*2 card->dai_link->cpu_of_node= "<&i2s1_8ch>" 
            single_cpu=1
        */
        /*3 card->dai_link->codec_of_node = "<&rk809_codec>"
        */
        /*4 require clk,codec_dai->clk = "clk of i2s"
            cpu_dai->systemclk = clk_get_rate(clk); 
            cpu_dai->clk_direction = SND_SOC_CLOCK_IN;
        */
        /*5 require clk,codec_dai->clk = "I2S_MCSL_OUT"
            codec_dai->systemclk = clk_get_rate(clk); 
            codec_dai->clk_direction = SND_SOC_CLOCK_IN;
        */
        /*6 dai_link->codec_dai_name = "tlv320aic31xx-hifi"
            static struct snd_soc_dai_driver 
                            aic31xx_dai_driver[] = {
            {
                .name = "tlv320aic31xx-hifi",
                .playback = {
                    ...
                },
                .capture = {
                    ...
                },
                .ops = &aic31xx_dai_ops,
                .symmetric_rates = 1,
                }
	        };
            cpu_dai_name 由于dai->driver的名字不存在
            所以 dai_link->codec_dai_name = "rockchip-i2s-tdm"
            struct snd_soc_component_driver        
                            rockchip_i2s_tdm_component = {
	             .name = DRV_NAME,
            };     
            
        */
        /*7 dai_link->name = 
                            "rockchip-i2s-tdm-tlv320aic31xx-hifi"
            dai_link->stream_name = 
                            "rockchip-i2s-tdm-tlv320aic31xx-hifi"
        */
        /*8 dai_link->cpu_name = NULL;
            dai_link->codec_name = NULL;
        */
        ret = asoc_simple_card_dai_link_of(node, priv, 0, true);
    }
    
    //card->name = "rockchip,tlv320aic31xx-codec";
    //simple-audio-card,name = "rockchip,tlv320aic31xx-codec";
    ret = asoc_simple_card_parse_card_name(card, PREFIX);
    ...
}

snd_soc_register_card的分析流程如下:

int snd_soc_register_card(struct snd_soc_card *card)
{
    struct snd_soc_pcm_runtime *rtd;
    //num_links == 1 
    for (i = 0; i < card->num_links; i++) {
        struct snd_soc_dai_link *link = &card->dai_link[i];
        //dai_link->codecs[0].name = dai_link->codec_name;
        //dai_link->codecs[0].of_node = dai_link->codec_of_node;
		 //dai_link->codecs[0].dai_name = dai_link->codec_dai_name;
        ret = soc_init_dai_link(card, link);
	}
    
    //card->dev->driver_data = card;
    dev_set_drvdata(card->dev, card);
    
    //card相关链表头的初始化
    INIT_LIST_HEAD(&card->widgets);
    INIT_LIST_HEAD(&card->paths);
    INIT_LIST_HEAD(&card->dapm_list);
    INIT_LIST_HEAD(&card->aux_comp_list);
    INIT_LIST_HEAD(&card->component_dev_list);
    INIT_LIST_HEAD(&card->dai_link_list);
    INIT_LIST_HEAD(&card->rtd_list);
    INIT_LIST_HEAD(&card->dapm_dirty);
    INIT_LIST_HEAD(&card->dobj_list);
    
    //card相关锁的初始化
    mutex_init(&card->mutex);
    mutex_init(&card->dapm_mutex);
    mutex_init(&card->dapm_power_mutex);
    
    //声卡的部分参数实例化填充以及声卡的注册
    ret = snd_soc_instantiate_card(card); //详细分析在下
    ...
}
snd_soc_instantiate_card的分析流程如下:
static int snd_soc_instantiate_card(struct snd_soc_card *card)
{
    struct snd_soc_pcm_runtime *rtd;
    struct snd_soc_dai_link *dai_link;
    
    //num_links==1
    for (i = 0; i < card->num_links; i++) {
        //详细分析在下
        //找到cpu/codec的snd_soc_dai
        ret = soc_bind_dai_link(card, &card->dai_link[i]);
    }
    
    //将card->list加入到card->dai_link_list
    snd_soc_add_dai_link(card, card->dai_link+i);
    
    //创建新的声卡
    //sprintf(str, "card%i", card->number);
    //entry = snd_info_create_subdir(card->module, str, NULL);
    ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, 
        SNDRV_DEFAULT_STR1,card->owner, 0, &card->snd_card);  
    
    //添加widgets
	if (card->of_dapm_widgets)
        snd_soc_dapm_new_controls(&card->dapm, 
            card->of_dapm_widgets,card->num_of_dapm_widgets);
    ...
    //执行probe函数asoc_simple_soc_card_probe
    card->probe(card);
    
    //执行cpu和codec的component->driver->probe函数
    //component->driver->probe(component)
    for(...)
        ret = soc_probe_link_components(card, rtd, order);
    //执行cpu和codec的dai->driver->probe函数 (一般未定义)
    //执行dai_link->init asoc_simple_card_dai_init
    //执行cpu和codec的dai->driver->ops->set_fmt
    //创建pcm soc_new_pcm(rtd, num) 初始化rtd->ops
    //rtd->pcm = pcm;
    for(...)
        ret = soc_probe_link_dais(card, rtd, order);
        
    //添加route
	if (card->of_dapm_routes)
        snd_soc_dapm_add_routes(&card->dapm, card->of_dapm_routes,card->num_of_dapm_routes);
 
    //声卡名称填充 cat /proc/asound/cards
    snprintf(card->snd_card->shortname, 
       sizeof(card->snd_card->shortname),"%s", card->name);
    snprintf(card->snd_card->driver,sizeof(card->snd_card->driver),
		 "%s", card->driver_name ? card->driver_name : card->name);
    ...
 
    //注册声卡,注册回调函数,如上面提到的snd_pcm_dev_register函数
    //该函数建立设备节点:/dev/snd/pcmCxxDxxp和 /dev/snd/pcmCxxDxxc
    snd_card_register(card->snd_card);   
}

static int soc_bind_dai_link的分析流程如下:

static int soc_bind_dai_link(struct snd_soc_card *card,struct snd_soc_dai_link *dai_link)
{
    struct snd_soc_pcm_runtime *rtd;
 
    //遍历card->rtd_list
    //如果rtd->dai_link == dai_link,表明当前的dai_link已经绑定
    if (soc_is_dai_link_bound(card, dai_link)) {
		return 0;
	}
    
    //rtd申请和填充
    //rtd->card = card;
    //rtd->dai_link = dai_link;
    /*rtd->codec_dais = kcalloc(dai_link->num_codecs,
				sizeof(struct snd_soc_dai *),GFP_KERNEL);
    */
    rtd = soc_new_pcm_runtime(card, dai_link);
    
    //从component链表中找到已经注册的cpu snd_soc_dai,并填充rtd
    //并将cpu dai的component链表加入rtd的component链表
    rtd->cpu_dai = snd_soc_find_dai(&cpu_dai_component);
    snd_soc_rtdcom_add(rtd, rtd->cpu_dai->component);
    
    //rtd->num_codecs = dai_link->num_codecs =1;
    //从component链表中找到已经注册的codec snd_soc_dai,并填充rtd
    //并将codec dai的component链表加入rtd的component链表
    rtd->num_codecs = dai_link->num_codecs
    for (i = 0; i < rtd->num_codecs; i++) {
        codec_dais[i] = snd_soc_find_dai(&codecs[i]);
        snd_soc_rtdcom_add(rtd, codec_dais[i]->component);
    }
    
    //将该rtd的链表加入到card->rtd_list中
    soc_add_pcm_runtime(card, rtd);
}

六、ALSA-Probe的流程

下图是板子内核启动到probe完成阶段的流程图,可以看到ALSA初始化音频的整个流程是怎么初始化声卡,和配置一些基本参数的
在这里插入图片描述

step 1:
cpu_dai_probe:获取设备树有关I2S的硬件描述信息,如时钟参数,dma通道参数,regmap_i2c初始化,然后 devm_snd_soc_register_component注册cpu_dai,调用 devm_snd_dmaengine_pcm_register注册dmaengine
codec_dai_probe:硬件资源的获取,regmap_i2c初始化,然后调用devm_snd_soc_register_component注册
asoc_simple_card_probe:dai_link获取,获取cpu_dai,codec_dai的信息,初始化函数接口,调用devm_snd_soc_register_card执行声卡的注册,注册过程中依次执行具体设备的probe,声卡的probe,cpu_dai probe ,codec_probe

step 2:

rk817_probe: snd_soc_add_component_controls,设置音频通路相关的寄存器
rockchip_i2s_tdm_dai_probe: snd_soc_add_dai_controls ,设置I2S相关的寄存器
asoc_simple_soc_card_probe: asoc_simple_card_init_hp ,asoc_simple_card_init_hp ?
asoc_simple_card_hw_params : rockchip_i2s_tdm_set_sysclk设置I2S的时钟参数,rk817_set_dai_sysclk设置codec一层具体的时钟参数
snd_soc_dai_set_fmt:rk817_set_dai_fmt 设置音频数据格式,是I2S还是PCM,CODEC是master还是slave

七、Tinyalsa pcm_open的调用流程
应用程序,调用pcm格式,操控声卡的一些操作,先看下pcm的fops,接口如下,定义在pcm_native.c

const struct file_operations snd_pcm_f_ops[2] = {
	{
		.owner =		THIS_MODULE,
		.write =		snd_pcm_write,
		.write_iter =		snd_pcm_writev,
		.open =			snd_pcm_playback_open,
		.release =		snd_pcm_release,
		.llseek =		no_llseek,
		.poll =			snd_pcm_playback_poll,
		.unlocked_ioctl =	snd_pcm_playback_ioctl,
		.compat_ioctl = 	snd_pcm_ioctl_compat,
		.mmap =			snd_pcm_mmap,
		.fasync =		snd_pcm_fasync,
		.get_unmapped_area =	snd_pcm_get_unmapped_area,
	},
	{
		.owner =		THIS_MODULE,
		.read =			snd_pcm_read,
		.read_iter =		snd_pcm_readv,
		.open =			snd_pcm_capture_open,
		.release =		snd_pcm_release,
		.llseek =		no_llseek,
		.poll =			snd_pcm_capture_poll,
		.unlocked_ioctl =	snd_pcm_capture_ioctl,
		.compat_ioctl = 	snd_pcm_ioctl_compat,
		.mmap =			snd_pcm_mmap,
		.fasync =		snd_pcm_fasync,
		.get_unmapped_area =	snd_pcm_get_unmapped_area,
	}
};

substream是pcm ops的下一层,绝大部分任务都是在substream中处理,substream的ops定义在soc-pcm.c,定义如下

int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
        ...
    	/* ASoC PCM operations */
	if (rtd->dai_link->dynamic) {
		rtd->ops.open		= dpcm_fe_dai_open;
		rtd->ops.hw_params	= dpcm_fe_dai_hw_params;
		rtd->ops.prepare	= dpcm_fe_dai_prepare;
		rtd->ops.trigger	= dpcm_fe_dai_trigger;
		rtd->ops.hw_free	= dpcm_fe_dai_hw_free;
		rtd->ops.close		= dpcm_fe_dai_close;
		rtd->ops.pointer	= soc_pcm_pointer;
		rtd->ops.ioctl		= soc_pcm_ioctl;
	} else {
		rtd->ops.open		= soc_pcm_open;
		rtd->ops.hw_params	= soc_pcm_hw_params;
		rtd->ops.prepare	= soc_pcm_prepare;
		rtd->ops.trigger	= soc_pcm_trigger;
		rtd->ops.hw_free	= soc_pcm_hw_free;
		rtd->ops.close		= soc_pcm_close;
		rtd->ops.pointer	= soc_pcm_pointer;
		rtd->ops.ioctl		= soc_pcm_ioctl;
	}
        ...
	if (playback)
		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops);
 
	if (capture)
		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops);
        ...   
}

在这里插入图片描述
step1:设置时钟参数
step2:设置音频参数,根据上层设置的采样率,数据格式来配置codec寄存器,并开启时钟

八、Tinyalsa pcm_write的调用流程

下图使用tinyalsa测试,可以看到alsa-driver在pcm_write阶段做了哪些工作,流程是什么
在这里插入图片描述

原文链接:https://blog.csdn.net/QQ135102692/article/details/125348020

到此,linux提供的一天音频管理架构就分析完了。

这篇文章太长了,移植一个codex,我们怎么修改dts的,怎么用alsa-lib来采集声音的,下篇将。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值