linux alsa asoc总结

2021/9/29 7:14:09

本文主要是介绍linux alsa asoc总结,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

1. 引入

在没有计算机的年代,比如小时候听的收音机,它接收电磁波,将其中调制的声音信号解调出,这个声音信号是经转换后的一种模拟电信号,在经过适当的放大电路放大后,便可以直接送到耳机或者扬声器进行播放。

如下是一段声音的波形图,是对声音信号转换成的模拟电信号的形象化展示。其中,横轴表示时间,纵轴表示声音的响度(正比例于电流)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mm7j9Q5V-1632820540464)(audio.assets/image-20210913113425764.png)]

后来进入数字时代,我们对声音的概念变成了一个个音频文件。

那么,之前我们用收音机听相声和现在用开发板直接播放音频文件听相声,实现原理有什么区别呢?

用收音机听,是模拟信号给到音频播放设备

用开发板直接播放音频文件,是数字信号转成模拟信号,最终再给到音频播放设备

多了数字信号与模拟信号之间的转换过程。

这里所谓的数字信号,即是对模拟信号的数字化表示,将模拟信号转换成计算机能存储的二进制数据。

那么,具体如何转换呢?

2. 声音信号的数字化

如下图所示,用一个比源声音频率高的采样信号去量化源声音,记录每个采样点的值,最后如果把所有采样点数值连接起来,其与源声音曲线是互相吻合的,只是不是连续的。

如下图所示:

  1. 两条蓝色虚线距离就是采样信号的周期,即对应一个采样频率(FS),可以想象得到采样频率越高最后得到的结果就与源声音越吻合。

  2. 每条蓝色虚线长度决定着该时刻源声音的量化值,该量化值有另外一个概念与之挂钩,就是量化位数。量化位数表示每个采样点用多少位表示数据范围,常用有 16bit、 24bit 或 32bit,位数越高最后还原得到的音质越好,数据量也会越大。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XOu4GIdP-1632820540469)(audio.assets/45416a2ead90c50a803ef6ab8e89c8b1.png)]

由此可以得到,

采样频率:每秒钟抽取声波幅度样本的次数。采样频率越高,声音质量越好,数据量也越大。常用的采样频率有11.025KHz,22.05KHz,44.1KHz,48KHz,96KHz等。

量化位数:每个采样点用多少二进制位表示数据范围。量化位数也叫采样位数。量化位数越多,音质越好,数据量也越大。常用的采样位数有8位,16位,24位,32位等。

此外,还有一个声道的概念,

声道数:使用声道的个数。立体声比单声道的表现力丰富,但是数据量翻倍。常用的声道数有单声道,立体声(左声道和右声道)。

将上述数字化后的二进制数据存储起来,便是原始的声音数据。存储声音数据的文件格式很多,比如wav、mp3等,wav一般存储的是原始的声音数据,可由硬件直接播放,而mp3存储的是经过编码压缩后的声音数据,需要解码后才能播放。

接下来,介绍一下wav文件格式。

3. 音频文件存储格式WAV

WAV文件遵循RIFF规则,其内容以区块(chunk)为最小单位进行存储。WAV文件一般由3个区块组成:RIFF chunk、Format chunk和Data chunk。

由上可以知道,音频文件实际是对声音信号转换后的模拟电信号的数字化的二进制数据的一种存储形式。

假设有一个声音文件存储在nand上,当播放的时候,主控芯片在上层应用的控制下将声音数据从nand读取到ddr,在驱动程序的控制下将声音数据从ddr读取到aic(Audio Interface Controller)的内部fifo中,最后经外部接线送给codec。

这样,声音数据便传送到了外部的编解码芯片,经由数模转换后转为模拟电信号,最终送到音频播放设备播放。

这里介绍一下codec

codec,也即编解码器,这里我们所指的是音频编解码芯片,一般包含一个DAC(数字->模拟信号转换)和一个ADC(模拟->数字信号的转换),可以在模拟信号与数字信号之间进行转换。

主控和codec传输数据的协议一般为i2s协议。

image-20210914093557280

4. I2S总线协议

4.1 I2S总线概述

I2S(Inter-IC Sound)总线, 又称集成电路内置音频总线,是飞利浦半导体公司(现为恩智浦半导体公司)针对数字音频设备之间的音频数据传输而制定的一种总线标准。

该总线专门用于音频设备之间的数据传输,广泛应用于各种多媒体系统。

它采用了独立的导线传输时钟与数据信号的设计,通过将数据和时钟信号分离,避免了因时差诱发的失真,为用户节省了购买抵抗音频抖动的专业设备的费用。

4.2 I2S信号线

I2S总线主要有3个信号线:

  • 串行时钟SCK

    串行时钟SCK,也叫位时钟BCLK,对应数字音频的每一位数据,SCK都有1个脉冲。SCK的频率 = 声道数 * 采样频率 * 采样位数。

  • 字段选择信号WS

    字段选择信号WS,也叫LRCLK,用于切换左右声道的数据。WS的频率 = 采样频率。

    字段选择信号WS表明了正在被传输的声道。

    I2S Philips标准WS信号的电平含义如下:

    ​ WS为0,表示正在传输的是左声道的数据;

    ​ WS为1,表示正在传输的是右声道的数据。

  • 串行数据DATA

    串行数据DATA,就是用二进制表示的音频数据。I2S串行数据在传输的时候,由高位(MSB)到低位(LSB)依次进行传输。

  • 主时钟MCLK

    一般还有MCLK,也即主时钟,提供时钟给codec。

4.3 几种常见的I2S数据格式

随着技术的发展,在统一的I2S硬件接口下,出现了多种不同的I2S数据格式,可分为左对齐(MSB)标准、右对齐(LSB)标准、I2S Philips 标准。

对于所有数据格式和通信标准而言,始终会先发送最高有效位(MSB 优先)。

发送端和接收端必须使用相同的数据格式,确保发送和接收的数据一致。

4.3.1 I2S Philips标准

使用LRCLK信号来指示当前正在发送的数据所属的声道,为0时表示左声道数据。LRCLK信号从当前声道数据的第一个位(MSB)之前的一个时钟开始有效。LRCLK信号在BCLK的下降沿变化。发送方在时钟信号BCLK的下降沿改变数据,接收方在时钟信号BCLK的上升沿读取数据。正如上文所说,LRCLK频率等于采样频率Fs,一个LRCLK周期(1/Fs)包括发送左声道和右声道数据。

对于这种标准I2S格式的信号,无论有多少位有效数据,数据的最高位总是出现在LRCLK变化(也就是一帧开始)后的第2个BCLK脉冲处。这就使得接收端与发送端的有效位数可以不同。如果接收端能处理的有效位数少于发送端,可以放弃数据帧中多余的低位数据;如果接收端能处理的有效位数多于发送端,可以自行补足剩余的位。这种同步机制使得数字音频设备的互连更加方便,而且不会造成数据错位。

I2S Philips 标准时序图如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jql8Lggo-1632820540473)(audio.assets/a2408454b51d1434455d5d3cafa8acfb.png)]

4.3.2 左对齐(MSB)标准

在LRCLK发生翻转的同时开始传输数据。该标准较少使用。注意此时LRCLK为1时,传输的是左声道数据,这刚好与I2S Philips标准相反。

左对齐(MSB)标准时序图如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YFa5l1yq-1632820540478)(audio.assets/3a1385d382db5d075333c3d75091070e.png)]

4.3.3 右对齐(LSB)标准

声音数据LSB传输完成的同时,LRCLK完成第二次翻转(刚好是LSB和LRCLK是右对齐的,所以称为右对齐标准)。注意此时LRCLK为1时,传输的是左声道数据,这刚好与I2S Philips标准相反。

右对齐(LSB)标准时序图如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AEKT0cqS-1632820540480)(audio.assets/52f6c8606e7f9fa109dfaf0f72728f70.png)]

5. ASoC驱动框架

5.1 ASoC的由来

ASoC–ALSA System on Chip ,是建立在标准ALSA驱动层上,为了更好地支持嵌入式处理器和移动设备中的音频Codec的一套软件体系。

在ASoc出现之前,内核对于SoC中的音频已经有部分的支持,不过会有一些局限性:

  1. Codec驱动与SoC CPU的底层耦合过于紧密,这种不理想会导致代码的重复,例如,仅是wm8731的驱动,当时Linux中有分别针对4个平台的驱动代码。

  2. 音频事件没有标准的方法来通知用户,例如耳机、麦克风的插拔和检测,这些事件在移动设备中是非常普通的,而且通常都需要特定于机器的代码进行重新对音频路劲进行配置。

  3. 当进行播放或录音时,驱动会让整个codec处于上电状态,这对于PC没问题,但对于移动设备来说,这意味着浪费大量的电量。同时也不支持通过改变过取样频率和偏置电流来达到省电的目的。

ASoC正是为了解决上述种种问题而提出的,目前已经被整合至内核的代码树中:sound/soc。ASoC不能单独存在,其建立在标准ALSA驱动之上,必须和标准的

ALSA驱动框架相结合才能工作。

ASoC将嵌入式设备的音频系统抽象为三大部分:

  1. Machine

  2. Platform

  3. Codec

5.2 machine

Machine是指某一款机器,可以是某款设备,某款开发板,又或者是某款智能手机,由此可以看出Machine几乎是不可重用的,每个Machine上的硬件实现可能都

不一样,CPU不一样,Codec不一样,音频的输入、输出设备也不一样,Machine为CPU、Codec、输入输出设备提供了一个载体。

单独的Platform和Codec驱动是不能工作的,它必须由Machine驱动把它们结合在一起才能完成整个设备的音频处理工作。

5.2.1 注册函数

/**
 * snd_soc_register_card - Register a card with the ASoC core
 *
 * @card: Card to register
 *
 */
int snd_soc_register_card(struct snd_soc_card *card)

5.2.2 核心结构体

声卡的抽象

/* SoC card */
struct snd_soc_card {
	......
	/* CPU <--> Codec DAI links  */
	struct snd_soc_dai_link *dai_link;
	int num_links;
	......
};

描述板级中使用何种platform以及codec

指定了Platform、Codec、codec_dai、cpu_dai的名字,稍后Machine驱动将会利用这些名字去匹配已经在系统中注册的platform,

codec,dai,注册的位置在相应的Platform驱动和Codec驱动中。

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;

    ......
    
	/* machine stream operations */
	const struct snd_soc_ops *ops;

    ......
};

在panda板中只实现了hw_params函数,主要是设置协议格式、时钟频率、时钟分频等。

/* SoC audio ops */
struct snd_soc_ops {
	int (*startup)(struct snd_pcm_substream *);
	void (*shutdown)(struct snd_pcm_substream *);
	int (*hw_params)(struct snd_pcm_substream *, struct snd_pcm_hw_params *);
	int (*hw_free)(struct snd_pcm_substream *);
	int (*prepare)(struct snd_pcm_substream *);
	int (*trigger)(struct snd_pcm_substream *, int);
};

5.3 platform

Platform一般是指某一个SoC平台,通常包含该SoC中与音频相关的时钟、DMA、I2S、PCM等等,只要指定了SoC,那么我们可以认为它会有一个对应的

Platform,它只与SoC相关,与Machine无关,这样我们就可以把Platform抽象出来,使得同一款SoC不用做任何的改动,就可以用在不同的Machine中。

实际上,把Platform认为是某个SoC更好理解。

Platform驱动中包含了该SoC平台的音频DMA通路和音频接口(i2s、pcm、spdif等)的配置和控制,不能包含任何与板子或机器相关的代码。

5.3.1 I2S

1. 注册函数

int snd_soc_register_component(struct device *dev,
			       const struct snd_soc_component_driver *cmpnt_drv,
			       struct snd_soc_dai_driver *dai_drv,
			       int num_dai)

2. 核心结构体

描述数字音频接口,需要实现的主要是初始化函数,电源管理函数,以及操作函数集

/*
 * Digital Audio Interface Driver.
 *
 * Describes the Digital Audio Interface in terms of its ALSA, DAI and AC97
 * operations and capabilities. Codec and platform drivers will register this
 * structure for every DAI they have.
 *
 * This structure covers the clocking, formating and ALSA operations for each
 * interface.
 */
struct snd_soc_dai_driver {
	/* DAI description */
	const char *name;
	unsigned int id;
	unsigned int base;

	/* 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);

	/* ops */
	const struct snd_soc_dai_ops *ops;
	......
};

操作函数集,包括对该接口的时钟配置、格式配置、数据流操作配置等,主要表现为根据回调函数的功能,对aic控制器的各种寄存器进行配置

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);

    /*
	 * DAI format configuration
	 * Called by soc_card drivers, normally in their hw_params.
	 */
	int (*set_fmt)(struct snd_soc_dai *dai, unsigned int fmt);

    ......

	/*
	 * 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 *);
	......
};

5.3.2 DMA

1. 注册函数

/**
 * snd_soc_add_platform - Add a platform to the ASoC core
 * @dev: The parent device for the platform
 * @platform: The platform to add
 * @platform_drv: The driver for the platform
 */
int snd_soc_add_platform(struct device *dev, struct snd_soc_platform *platform,
		const struct snd_soc_platform_driver *platform_drv)

2. 核心结构体

需要实现如下回调函数

pcm_new中分配dma空间

pcm_free中进行释放

/* SoC platform interface */
struct snd_soc_platform_driver {
	......
        
	/* pcm creation and destruction */
	int (*pcm_new)(struct snd_soc_pcm_runtime *);
	void (*pcm_free)(struct snd_pcm *);

	......

	/* platform stream pcm ops */
	const struct snd_pcm_ops *ops;

	......
};

dma操作函数集,根据功能在必要的回调函数中初始化dma、配置dma、启动dma、释放dma等,在x1600中主要是使用kernel的DMA engine框架操作pdma控制器。

struct snd_pcm_ops {
	int (*open)(struct snd_pcm_substream *substream);
	int (*close)(struct snd_pcm_substream *substream);
	int (*ioctl)(struct snd_pcm_substream * substream,
		     unsigned int cmd, void *arg);
	int (*hw_params)(struct snd_pcm_substream *substream,
			 struct snd_pcm_hw_params *params);
	int (*hw_free)(struct snd_pcm_substream *substream);
	int (*prepare)(struct snd_pcm_substream *substream);
	int (*trigger)(struct snd_pcm_substream *substream, int cmd);
	snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);

    ......
};

5.4 codec

Codec即编解码器,Codec里面包含了I2S接口、D/A、A/D、Mixer、PA(功放),通常包含多种输入(Mic、Line-in、I2S、PCM)和多个输出(耳机、喇叭、听

筒,Line-out),Codec和Platform一样,是可重用的部件,同一个Codec可以被不同的Machine使用。嵌入式Codec通常通过I2C对内部的寄存器进行控制。

ASoC中的一个重要设计原则就是要求Codec驱动是平台无关的,它包含了一些音频的控件(Controls),音频接口,DAMP(动态音频电源管理)的

定义和某些Codec IO功能。为了保证硬件无关性,任何特定于平台和机器的代码都要移到Platform和Machine驱动中。

5.4.1 注册函数

/**
 * snd_soc_register_codec - Register a codec with the ASoC core
 *
 * @dev: The parent device for this codec
 * @codec_drv: Codec driver
 * @dai_drv: The associated DAI driver
 * @num_dai: Number of DAIs
 */
int snd_soc_register_codec(struct device *dev,
			   const struct snd_soc_codec_driver *codec_drv,
			   struct snd_soc_dai_driver *dai_drv,
			   int num_dai)

5.4.2 核心结构体

主要包括初始化函数,电源管理函数,音频控件描述结构体,电源管理控件,时钟操作函数,寄存器读写函数等。

/* codec driver */
struct snd_soc_codec_driver {

	/* driver ops */
	int (*probe)(struct snd_soc_codec *);
	int (*remove)(struct snd_soc_codec *);
	int (*suspend)(struct snd_soc_codec *);
	int (*resume)(struct snd_soc_codec *);
	struct snd_soc_component_driver component_driver;

	/* Default control and setup, added after probe() is run */
	const struct snd_kcontrol_new *controls;
	int num_controls;
	const struct snd_soc_dapm_widget *dapm_widgets;
	int num_dapm_widgets;
	const struct snd_soc_dapm_route *dapm_routes;
	int num_dapm_routes;

	/* codec wide operations */
	int (*set_sysclk)(struct snd_soc_codec *codec,
			  int clk_id, int source, unsigned int freq, int dir);
	int (*set_pll)(struct snd_soc_codec *codec, int pll_id, int source,
		unsigned int freq_in, unsigned int freq_out);

	/* codec IO */
	struct regmap *(*get_regmap)(struct device *);
	unsigned int (*read)(struct snd_soc_codec *, unsigned int);
	int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);
	unsigned int reg_cache_size;
	short reg_cache_step;
	short reg_word_size;
	const void *reg_cache_default;

	/* codec bias level */
	int (*set_bias_level)(struct snd_soc_codec *,
			      enum snd_soc_bias_level level);
	bool idle_bias_off;
	bool suspend_bias_off;

	void (*seq_notifier)(struct snd_soc_dapm_context *,
			     enum snd_soc_dapm_type, int);

	bool ignore_pmdown_time;  /* Doesn't benefit from pmdown delay */
};

描述数字音频接口,与i2s相同

/*
 * Digital Audio Interface Driver.
 *
 * Describes the Digital Audio Interface in terms of its ALSA, DAI and AC97
 * operations and capabilities. Codec and platform drivers will register this
 * structure for every DAI they have.
 *
 * This structure covers the clocking, formating and ALSA operations for each
 * interface.
 */
struct snd_soc_dai_driver {
	/* DAI description */
	const char *name;
	unsigned int id;
	unsigned int base;

	/* 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);

    ......

	/* ops */
	const struct snd_soc_dai_ops *ops;

	/* DAI capabilities */
	struct snd_soc_pcm_stream capture;
	struct snd_soc_pcm_stream playback;

    ......
};

操作函数集,包括对该接口的时钟配置、格式配置、数据流操作配置等,主要表现为根据回调函数的功能,对codec内部的各种寄存器进行配置

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);

    /*
	 * DAI format configuration
	 * Called by soc_card drivers, normally in their hw_params.
	 */
	int (*set_fmt)(struct snd_soc_dai *dai, unsigned int fmt);

    ......

	/*
	 * 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 *);

    ......
};

5.5 总结

ASoC是基于ALSA驱动框架下的拓展,ALSA驱动本质属于字符设备驱动,只要是字符设备驱动就会有open、close、ioctl等接口与上层应用通信。

其中最主要是ioctl,其根据不同的参数调用不同的ops回调函数。

6. 上层应用

使用strace命令追踪执行aplay Windows.wav命令时的系统调用,部分如下:

execve("/usr/bin/aplay", ["aplay", "Windows.wav"], [/* 7 vars */]) = 0
brk(0)                                  = 0x20000
uname({sys="Linux", node="192.168.1.17", ...}) = 0
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = -1 ENOENT (No such file or directory)
open("/lib/tls/v4l/half/librt.so.1", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/tls/v4l/half", 0xbef92460) = -1 ENOENT (No such file or directory)
open("/lib/tls/v4l/librt.so.1", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/tls/v4l", 0xbef92460)      = -1 ENOENT (No such file or directory)
open("/lib/tls/half/librt.so.1", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/tls/half", 0xbef92460)     = -1 ENOENT (No such file or directory)
open("/lib/tls/librt.so.1", O_RDONLY)   = -1 ENOENT (No such file or directory)
stat64("/lib/tls", 0xbef92460)          = -1 ENOENT (No such file or directory)
open("/lib/v4l/half/librt.so.1", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/v4l/half", 0xbef92460)     = -1 ENOENT (No such file or directory)
open("/lib/v4l/librt.so.1", O_RDONLY)   = -1 ENOENT (No such file or directory)
stat64("/lib/v4l", 0xbef92460)          = -1 ENOENT (No such file or directory)
open("/lib/half/librt.so.1", O_RDONLY)  = -1 ENOENT (No such file or directory)
stat64("/lib/half", 0xbef92460)         = -1 ENOENT (No such file or directory)
open("/lib/librt.so.1", O_RDONLY)       = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0\240\26\0\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=40807, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb6f31000
mmap2(NULL, 57872, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb6eff000
mprotect(0xb6f05000, 28672, PROT_NONE)  = 0
mmap2(0xb6f0c000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x5000) = 0xb6f0c000
close(3)                                = 0

......
    
ioctl(4, SNDRV_PCM_IOCTL_HW_REFINE, 0xbef92490) = 0
ioctl(4, SNDRV_PCM_IOCTL_HW_REFINE, 0xbef92860) = 0
ioctl(4, SNDRV_PCM_IOCTL_HW_PARAMS, 0xbef92860) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x2e858) = 0
ioctl(4, SNDRV_PCM_IOCTL_SW_PARAMS, 0xbef92404) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x2e858) = 0
ioctl(4, SNDRV_PCM_IOCTL_PREPARE, 0xc54f8) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x2e858) = 0
ioctl(4, SNDRV_PCM_IOCTL_SW_PARAMS, 0xbef927f0) = 0
read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 8192) = 8192
ioctl(4, SNDRV_PCM_IOCTL_WRITEI_FRAMES, 0xbef92ab8) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x2e858) = 0
read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 8192) = 8192
ioctl(4, SNDRV_PCM_IOCTL_WRITEI_FRAMES, 0xbef92ab8) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x2e858) = 0
read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 8192) = 8192
ioctl(4, SNDRV_PCM_IOCTL_WRITEI_FRAMES, 0xbef92ab8) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x2e858) = 0
read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 8192) = 8192
ioctl(4, SNDRV_PCM_IOCTL_WRITEI_FRAMES, 0xbef92ab8) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x2e858) = 0
read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 8192) = 8192
ioctl(4, SNDRV_PCM_IOCTL_WRITEI_FRAMES, 0xbef92ab8) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x2e858) = 0
read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 8192) = 8192
ioctl(4, SNDRV_PCM_IOCTL_WRITEI_FRAMES, 0xbef92ab8) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x2e858) = 0
read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 8192) = 8192
ioctl(4, SNDRV_PCM_IOCTL_WRITEI_FRAMES, 0xbef92ab8) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x2e858) = 0
read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 8192) = 8192
ioctl(4, SNDRV_PCM_IOCTL_WRITEI_FRAMES, 0xbef92ab8) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x2e858) = 0
read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 8192) = 8192
ioctl(4, SNDRV_PCM_IOCTL_WRITEI_FRAMES, 0xbef92ab8) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x2e858) = 0
read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 8192) = 8192
ioctl(4, SNDRV_PCM_IOCTL_WRITEI_FRAMES, 0xbef92ab8) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x2e858) = 0
    
......    
    
fcntl64(4, F_GETFL)                     = 0x80002 (flags O_RDWR|O_CLOEXEC)
fcntl64(4, F_SETFL, O_RDWR|O_CLOEXEC)   = 0
ioctl(4, SNDRV_PCM_IOCTL_DRAIN, 0xc50a4) = 0
fcntl64(4, F_GETFL)                     = 0x80002 (flags O_RDWR|O_CLOEXEC)
fcntl64(4, F_SETFL, O_RDWR|O_CLOEXEC)   = 0
close(3)                                = 0
ioctl(4, SNDRV_PCM_IOCTL_DROP, 0xc51ac) = 0
ioctl(4, SNDRV_PCM_IOCTL_HW_FREE, 0)    = 0
close(4)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++

可以看到,单是播放一段声音,便需要如此之多的系统调用确实不人性化。

为此,内核在应用层为开发者提供了alsa-lib工具库,屏蔽各种open、ioctl、read、write等操作,集成出各种功能的接口供上层调用,部分接口如下所示:

int snd_pcm_open(snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, int mode)
int snd_pcm_close (snd_pcm_t *pcm)
int snd_pcm_set_params (snd_pcm_t *pcm, snd_pcm_format_t format, snd_pcm_access_t access, unsigned int channels, 
               unsigned int rate, int soft_resample, unsigned int latency)
int snd_pcm_get_params (snd_pcm_t *pcm, snd_pcm_uframes_t *buffer_size, snd_pcm_uframes_t *period_size) 
snd_pcm_sframes_t snd_pcm_writei (snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size)
snd_pcm_sframes_t 	snd_pcm_readi (snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size)
......    

我们熟悉的aplay、arecord、amixer等命令,均是基于上述alsa-lib提供的接口实现的,包含于alsa-utils工具包中。

7. 一些调试经验

7.1 噪声问题

在dma传送的时候,当某个描述符的搬移次数(需要搬移的总数据/每次搬移的大小)出现除不尽的情况时,会出现数据丢失的情况,进而导致后面的数据全部错位,进而产生噪声。

7.2 时钟与采样率的关系

如果播放没有声音或者声音有问题,可以先检查时钟

  1. 先看看有没有mclk,因为该时钟提供的是主时钟
  2. 然后看下lrclk,该时钟与播放音频文件的采样率是相同的
  3. 最后看下bclk,一般该时钟与lrclk成倍数关系,在x1600的aic中,bclk=64lrclk

7.3 播放速度变快或者变慢的问题

  1. 当lrclk的时钟频率 > 播放音频文件的采样率时,会出现声音变快的现象
  2. 当lrclk的时钟频率 < 播放音频文件的采样率时,会出现声音变慢的现象

7.4 执行aplay/arecord出现的一些问题

7.4.1 underrun/overrun问题

underrun:一般出现在放音时,当上层的数据没有及时的送到驱动的buffer中时,导致驱动出现无数据可读的情况,即会出现underrun错误

overrun:一般出现在录音时,当上层没有及时取走驱动buffer中的数据时,导致驱动buffer出现无空间可写的情况,即会出现overrun错误

因为使用的是aplay/arecord,如果出现此问题一般是驱动的问题,此时表明录放音通路已经打通,但是和上层应用匹配出现问题

7.4.2 pcm input/ouput error问题

出现该问题说明录放音通路没有打通,此时可先注意下时钟,看看有没有时钟,或者时钟是否正确,之后再着重检查dma是否配置正确,dma描述符链是否转起来。



这篇关于linux alsa asoc总结的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程