pcm设备,通过阅读tinyalsa的代码和查看Android下的音频设备节点,可知在Android中一个pcm设备最多可有一个mixer设备"/dev/snd/controlC%u"(一般是controlC0)和32个/dev/snd/pcmC%uD%uc(一般是pcmC0D0c)、/dev/snd/pcmC%uD%u%p
tinyalsa的对外提供的头文件就我上图提到的一个"
asoundlib.h",提供最基础的pcm和mixer操作。实现文件为
pcm.c(实现pcm api)和mixer.c(实现mixer api)。根据asoundlib.h编写了四个小工具
tinypcminfo tinyplay tinycap tinymix,这四个小工具作为系统命令存放在系统中,可以很方便的使用。tinyasla作为精简版的alsa-lib可能会有人想把它移植到Linux使用,
tinyasla
依赖的库有libcutils && libutils,如果能把依赖的这两个库的一些方法使用Linux接口实现那么剩下的问题应该不大了吧,这个仅仅是我的猜想。
tinypcminfo的实现文件
tinypcminfo.c (查看pcm设备能力)
tinyplay的实现文件
tinyplay.c(使用pcm设备播放wav格式的音频文件)
tinycap的实现文件
tinycap.c(使用pcm设备采集pcm格式的码流,并保存为wav格式的文件)
tinymix的实现文件
tinymix.c(对pcm设备的控制,包括音量调节、设备切换)
这几个命令使用时可以先使用tinypcminfo查看pcm设备的能力,要不当我们使用其他三个命令时使用了不合理的配置会出现parameter invalid的错误。
PCM API
/* 对pcm设备节点的操作 */
struct
pcm
*
pcm_open
(
unsigned
int
card
,
unsigned
int
device
,
unsigned
int
flags
,
struct
pcm_config
*
config
);
int
pcm_close
(
struct
pcm
*
pcm
);
int
pcm_is_ready
(
struct
pcm
*
pcm
);
/* 获取pcm设备的能力 */
struct
pcm_params
*
pcm_params_get
(
unsigned
int
card
,
unsigned
int
device
,
unsigned
int
flags
);
void
pcm_params_free
(
struct
pcm_params
*
pcm_params
);
unsigned
int
pcm_params_get_min
(
struct
pcm_params
*
pcm_params
,
enum
pcm_param param
);
unsigned
int
pcm_params_get_max
(
struct
pcm_params
*
pcm_params
,
enum
pcm_param param
);
/* 配置pcm设备capture和playback的规格 */
int
pcm_get_config
(
struct
pcm
*
pcm
,
struct
pcm_config
*
config
);
int
pcm_set_config
(
struct
pcm
*
pcm
,
struct
pcm_config
*
config
);
/* 返回调用tinyalsa最后的错误信息 */
const
char
*
pcm_get_error
(
struct
pcm
*
pcm
);
/* 设置pcm设备采集和播放的位数,位数越高越接近真实声音 */
unsigned
int
pcm_format_to_bits
(
enum
pcm_format format
);
/* pcm设备的内置缓冲之间大小、帧数、时间的转换 */
unsigned
int
pcm_get_buffer_size
(
struct
pcm
*
pcm
);
unsigned
int
pcm_frames_to_bytes
(
struct
pcm
*
pcm
,
unsigned
int
frames
);
unsigned
int
pcm_bytes_to_frames
(
struct
pcm
*
pcm
,
unsigned
int
bytes
);
unsigned
int
pcm_get_latency
(
struct
pcm
*
pcm
);
// in ms
/* 返回下一帧的有效帧指针和该帧时间戳,时间戳有CLOCK_MONOTONIC和CLOCK_REALTIME可选,里面使用的是CLOCK_REALTIME */
/* 我认为应该需要两个时间戳,一个用于播放的时间戳(CLOCK_MONOTONIC)不受系统时间的影响,一个用于记录当前音频采集的时间戳(CLOCK_REALTIME) */
int
pcm_get_htimestamp
(
struct
pcm
*
pcm
,
unsigned
int
*
avail
,
struct
timespec
*
tstamp
);
/* 通过FIFO把数据写入硬件用于playback或者从硬件中读取capture数据 */
int
pcm_write
(
struct
pcm
*
pcm
,
const
void
*
data
,
unsigned
int
count
);
int
pcm_read
(
struct
pcm
*
pcm
,
void
*
data
,
unsigned
int
count
);
/* 这是一个可选的和hardware通信的方式。 */
int
pcm_mmap_write
(
struct
pcm
*
pcm
,
const
void
*
data
,
unsigned
int
count
);
int
pcm_mmap_read
(
struct
pcm
*
pcm
,
void
*
data
,
unsigned
int
count
);
int
pcm_mmap_begin
(
struct
pcm
*
pcm
,
void
**
areas
,
unsigned
int
*
offset
,
unsigned
int
*
frames
);
int
pcm_mmap_commit
(
struct
pcm
*
pcm
,
unsigned
int
offset
,
unsigned
int
frames
);
int
pcm_start
(
struct
pcm
*
pcm
);
int
pcm_stop
(
struct
pcm
*
pcm
);
int
pcm_wait
(
struct
pcm
*
pcm
,
int
timeout
);
int
pcm_set_avail_min
(
struct
pcm
*
pcm
,
int
avail_min
);
对于pcm设备的操作只需要注意所操作的设备是否存在,以及设备的能力问题不要设置设备所不能及的能力设备就可以正常工作了,其实就是需要注意channels、rate、format、period_size和period_count。
MIXER API
/* 对mixer设备的操作 */
struct
mixer
*
mixer_open
(
unsigned
int
card
);
void
mixer_close
(
struct
mixer
*
mixer
);
/* 获取mixer设备信息name */
const
char
*
mixer_get_name
(
struct
mixer
*
mixer
);
/* 获取mixer设备的控制句柄 struct mixer_ctl */
unsigned
int
mixer_get_num_ctls
(
struct
mixer
*
mixer
);
struct
mixer_ctl
*
mixer_get_ctl
(
struct
mixer
*
mixer
,
unsigned
int
id
);
struct
mixer_ctl
*
mixer_get_ctl_by_name
(
struct
mixer
*
mixer
,
const
char
*
name
);
/* 取mixer设备的控制信息 */
const
char
*
mixer_ctl_get_name
(
struct
mixer_ctl
*
ctl
);
enum
mixer_ctl_type mixer_ctl_get_type
(
struct
mixer_ctl
*
ctl
);
const
char
*
mixer_ctl_get_type_string
(
struct
mixer_ctl
*
ctl
);
unsigned
int
mixer_ctl_get_num_values
(
struct
mixer_ctl
*
ctl
);
unsigned
int
mixer_ctl_get_num_enums
(
struct
mixer_ctl
*
ctl
);
const
char
*
mixer_ctl_get_enum_string
(
struct
mixer_ctl
*
ctl
,
unsigned
int
enum_id
);
/* Some sound cards update their controls due to external events,
* such as HDMI EDID byte data changing when an HDMI cable is
* connected. This API allows the count of elements to be updated.
void
mixer_ctl_update
(
struct
mixer_ctl
*
ctl
);
/* 设置和获取可控制的信息,方式有比例、数组、范围、固定值 */
/* id通过mixer_get_num_ctls获得 */
int
mixer_ctl_get_percent
(
struct
mixer_ctl
*
ctl
,
unsigned
int
id
);
int
mixer_ctl_set_percent
(
struct
mixer_ctl
*
ctl
,
unsigned
int
id
,
int
percent
);
int
mixer_ctl_get_value
(
struct
mixer_ctl
*
ctl
,
unsigned
int
id
);
int
mixer_ctl_get_array
(
struct
mixer_ctl
*
ctl
,
void
*
array
,
size_t
count
);
int
mixer_ctl_set_value
(
struct
mixer_ctl
*
ctl
,
unsigned
int
id
,
int
value
);
int
mixer_ctl_set_array
(
struct
mixer_ctl
*
ctl
,
const
void
*
array
,
size_t
count
);
int
mixer_ctl_set_enum_by_string
(
struct
mixer_ctl
*
ctl
,
const
char
*
string
);
int
mixer_ctl_get_range_min
(
struct
mixer_ctl
*
ctl
);
int
mixer_ctl_get_range_max
(
struct
mixer_ctl
*
ctl
);
MIXER使用,有哪些属性,干什么用的,看名字就已经很清楚了。
root@pone
:/
data
/
local
/
tmp
# tinymix -D 1
Mixer
name
:
'mixer_name_xx'
Number
of controls
:
2
ctl type num name value
0
BOOL
1
Mic
Capture
Switch
Off
1
INT
1
Mic
Capture
Volume
256
// 详细信息
root@pone
:/
data
/
local
/
tmp
# tinymix -D 1 0
Mic
Capture
Switch
:
Off
root@pone
:/
data
/
local
/
tmp
# tinymix -D 1 1
Mic
Capture
Volume
:
256
(
range
0
->
256
)
// 设置
root@pone
:/
data
/
local
/
tmp
# tinymix -D 1 0 1
root@pone
:/
data
/
local
/
tmp
# tinymix -D 1 1 250
// 修改后的状态
root@pone
:/
data
/
local
/
tmp
# tinymix -D 1
Mixer
name
:
'mixer_name_xx'
Number
of controls
:
2
ctl type num name value
0
BOOL
1
Mic
Capture
Switch
On
1
INT
1
Mic
Capture
Volume
250
2. 音频采样
目前
Linux
中主流的音频体系结构是ALSA(Advanced Linux Sound Architecture),ALSA在内核驱动层提供了alsa-driver,在应用层提供了alsa-lib,应用程序只需要调用alsa-lib提供的API就可以完成对底层硬件的操作。说的这么好,但是
Android
中没有使用标准的ALSA,而是一个ALSA的简化版叫做tinyalsa。Android中使用tinyalsa控制管理所有模式的音频通路,我们也可以使用tinyalsa提供的工具进行查看、调试。
编译tinyalsa后生成四个小工具:
[objc]
view plain
copy
tinymix
tinyplay
tinycap
tinypcminfo
编译命令:
[objc]
view plain
copy
-
mmm external/tinyalsa/
下面依次演示一下四个小工具的使用:(以下使用联芯科技的LC1860平台配合LC1160电源+音频芯片,截图及演示效果均来自M7301P5
测试
机)
1, tinypcminfo
tinypcminfo用于查看pcm通道的相关信息
[objc]
view plain
copy
-
tinypcminfo -D /proc/asound/cards
结果如下:
从上面获得的信息中可以知道PCM的采样率,通道个数,采样点数等信息。
其中 –D 后边跟的参数为声卡文件,一般位于/proc/asound/cards。可以使用
[objc]
view plain
copy
-
cat /proc/asound/cards
查看当前声卡
2, tinymix
如下图所示,直接输入tinymix可以得到音频通路相关的各项配置参数。也可以通过添加参数修改其中的配置,如希望提高ADC1 Gain值到110,输入tinymix 12 110即可。
单独查看上述信息比较难以梳理,配合音频通路图会更加清晰。
上图中红色字体标明了LC1160与麦克风、耳机等硬件设备的连接关系。(注:M73xx项目由于内部ClassD不满足要求,喇叭连在了AUX通路上)
各个通路上的增益调节部分使用绿色字体标出了与tinymix中选项的对应关系。
图中加号与Mux是通路选择开关,对应tinymix中的其它的选项,用于在各种模式下切换音频通道。此部分比较多没有在图中一一标出,但根据已知的通路名是比较容易对应的。
图中黄色箭头标出的是通话时下行音频数据流,从PCM接口进入到LC1160,然后经过MonoDAC进行数模转化后送到听筒。
图中紫色箭头标出的是通话时上行音频数据流,从主MIC采集声音后,经过ADC1进行模数转换后由PMC的DO线送出
3, tinyplay
tinyplay是一个简易的音乐播放器,一般用于播放测试。tinyplay只能播放wav原始格式的音乐,不能进行Mp3等格式的解码,支持44.1kHz,48kHz采样率的wav音乐。
在调用tinyplay播放音乐之前需要先使用tinymix切换好音频通路:
[objc]
view plain
copy
tinymix
0
I
2
SR
tinymix
1
I
2
SL
tinymix
2
0
0
tinymix
2
4
1
tinymix
4
0
1
tinymix
4
1
1
tinyplay z
.wav
4, tinycap
tinycap是一个简易的录音软件,一般用于录音测试。
在调用tinycap录音之前需要先调整好音频通路:
[objc]
view plain
copy
tinymix
1
4
3
0
tinymix
1
9
1
tinymix
2
6
1
tinymix
5
0
ADC
1
tinymix
5
1
ADC
1
echo
"0xfb 0x01"
> /sys/devices/platform/comip_codec/lc
1
1
6
0
_reg
echo
"0xad 0x08"
> /sys/devices/platform/comip_codec/lc
1
1
6
0
_reg
echo
"0xac 0x01"
> /sys/devices/platform/comip_codec/lc
1
1
6
0
_reg
echo
"0x3b 0xcc"
> /sys/devices/platform/comip_codec/lc
1
1
6
0
_reg
echo
2
> /sys/bus/i
2
c/drivers/fm
2
0
1
8
/
0
-
0
0
6
0
/mode
tinycap /sdcard/Music/l
.wav
录音结束通过ctrl+C强行退出即可,之后在/sdcard/Music/路径下查看到l.wav音频文件
使用adb pull到本地电脑中,使用goldwave播放、查看。
[objc]
view plain
copy
-
adb pull /sdcard/Music/l
.wav
d
:\
LC1160的寄存器是分页的,即同一个地址上存在两个不同含义的寄存器,通过控制0xFC寄存器中的值来切换到第二功能页
[objc]
view plain
copy
echo
"0xfc 0x01"
> /sys/devices/platform/comip_codec/lc
1
1
6
0
_reg
cat /sys/devices/platform/comip_codec/lc
1
1
6
0
_reg
echo
"0xfc 0x00"
> /sys/devices/platform/comip_codec/lc
1
1
6
0
_reg
Android音频架构
我们上面介绍的四个层面的音频API实现均在Framework层,其他各层音频相关有哪些功能?当我们调用某一API时最终是怎么驱动硬件工作的呢?下面我们先看看系统各层音频相关模块及功能。
Android音频API
MediaRecorder与MediaPlayer并不能算完整意义的音频API,它们只是系统音频API的封装,除了采集/播放,他们集成了编码/解码、复用/解复用等能力。它们在最底层还是调用了AudioRecorder、AudioTrack。下面主要介绍它们的几个主要的配置项。
【Android 高性能音频】Oboe 音频流打开后 耳机 / 音箱 插拔事件处理 ( 动态注册广播接收者监听耳机插拔事件 | 重新打开 Oboe 音频流 )
【Android 高性能音频】Oboe 音频流打开后 耳机 / 音箱 插拔事件处理 ( 动态注册广播接收者监听耳机插拔事件 | 重新打开 Oboe 音频流 )
【Android 高性能音频】Oboe 音频流打开后 耳机 / 音箱 插拔事件处理 ( 设置 Oboe 音频设备 ID | setDeviceId 函数原型 | AudioStream 音频流 )
【Android 高性能音频】Oboe 音频流打开后 耳机 / 音箱 插拔事件处理 ( 设置 Oboe 音频设备 ID | setDeviceId 函数原型 | AudioStream 音频流 )
【Android 高性能音频】Oboe 开发流程 ( Oboe 音频帧简介 | AudioStreamCallback 中的数据帧说明 )
【Android 高性能音频】Oboe 开发流程 ( Oboe 音频帧简介 | AudioStreamCallback 中的数据帧说明 )