android audio/linux alsa音频-应用层基础
Posted xgbing
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android audio/linux alsa音频-应用层基础相关的知识,希望对你有一定的参考价值。
-
snd_pcm_open
1. 参数和返回值
int snd_pcm_open(snd_pcm_t **pcmp, const char *name, snd_pcm_stream_t stream, int mode)
pcmp 打开的pcm句柄
name 要打开的pcm设备名字,默认default,或者从asound.conf或者asoundrc里面选择所要打开的设备
stream SND_PCM_STREAM_PLAYBACK 或 SND_PCM_STREAM_CAPTURE,分别表示播放和录音的PCM流
mode 打开pcm句柄时的一些附加参数 SND_PCM_NONBLOCK 非阻塞打开(默认阻塞打开), SND_PCM_ASYNC 异步模式打开
返回值 0 表示打开成功,负数表示失败,对应错误码
2. 调用流程
snd_pcm_open 函数位于alsa-lib库中,编译生成的动态链接库为libasound.so,进入alsa-lib中进行分析,snd_pcm_open代码如下:
int snd_pcm_open(snd_pcm_t **pcmp, const char *name,
snd_pcm_stream_t stream, int mode)
snd_config_t *top;
int err;
err = snd_config_update_ref(&top);
err = snd_pcm_open_noupdate(pcmp, top, name, stream, mode, 0);
snd_config_unref(top);
return err;
主要调用两个函数:
snd_config_update_ref
这个函数作用是检查alsa配置文件是否发生了变化,包括文件名字和各个配置文件的内容是否修改,如果有修改,就重新加载配置文件树,刷新全局变量snd_config并增加一个snd_config的引用计数,调用snd_config_update_ref传入的top就是指向snd_config的指针,这个top当作参数传入snd_pcm_open_noupdate函数中。snd_config_unref接下来会减去snd_config的引用计数。
首先创建顶层配置节点,然后打开/usr/share/alsa/alsa.conf,加载文件内容到顶层配置节点上,然后遍历所有的hooks,调用snd_config_hooks加载所有hooks,并调用相关的hooks函数打开对应的plugin动态库。
下一篇将分析 snd_pcm_open_noupdate 函数
-
格式说明
下面的程序打印出各种格式:
#include <stdio.h>
#include <alsa/asoundlib.h>
int main()
int val;
printf("ALSA library version: %s\\n", SND_LIB_VERSION_STR); //ALSA版本
printf("\\nPCM stream types:\\n");
for (val = 0; val <= SND_PCM_STREAM_LAST; val++)
printf(" %s\\n", snd_pcm_stream_name((snd_pcm_stream_t)val));
printf("\\nPCM access types:\\n");
for (val = 0; val <= SND_PCM_ACCESS_LAST; val++)
printf(" %s\\n", snd_pcm_access_name((snd_pcm_access_t)val));
printf("\\nPCM formats:\\n");
for (val = 0; val <= SND_PCM_FORMAT_LAST; val++)
if (snd_pcm_format_name((snd_pcm_format_t)val)!= NULL)
printf(" %s (%s)\\n",
snd_pcm_format_name((snd_pcm_format_t)val),
snd_pcm_format_description(
(snd_pcm_format_t)val));
printf("\\nPCM subformats:\\n");
for (val = 0; val <= SND_PCM_SUBFORMAT_LAST;val++)
printf(" %s (%s)\\n",
snd_pcm_subformat_name((
snd_pcm_subformat_t)val),
snd_pcm_subformat_description((
snd_pcm_subformat_t)val));
printf("\\nPCM states:\\n");
for (val = 0; val <= SND_PCM_STATE_LAST; val++)
printf(" %s\\n",
snd_pcm_state_name((snd_pcm_state_t)val));
return 0;
程序运行结果打印:
ALSA library version: 1.1.0
PCM stream types:
PLAYBACK
CAPTURE
PCM access types:
MMAP_INTERLEAVED
MMAP_NONINTERLEAVED
MMAP_COMPLEX
RW_INTERLEAVED
RW_NONINTERLEAVED
PCM formats:
S8 (Signed 8 bit)
U8 (Unsigned 8 bit)
S16_LE (Signed 16 bit Little Endian)
S16_BE (Signed 16 bit Big Endian)
U16_LE (Unsigned 16 bit Little Endian)
U16_BE (Unsigned 16 bit Big Endian)
S24_LE (Signed 24 bit Little Endian)
S24_BE (Signed 24 bit Big Endian)
U24_LE (Unsigned 24 bit Little Endian)
U24_BE (Unsigned 24 bit Big Endian)
S32_LE (Signed 32 bit Little Endian)
S32_BE (Signed 32 bit Big Endian)
U32_LE (Unsigned 32 bit Little Endian)
U32_BE (Unsigned 32 bit Big Endian)
FLOAT_LE (Float 32 bit Little Endian)
FLOAT_BE (Float 32 bit Big Endian)
FLOAT64_LE (Float 64 bit Little Endian)
FLOAT64_BE (Float 64 bit Big Endian)
IEC958_SUBFRAME_LE (IEC-958 Little Endian)
IEC958_SUBFRAME_BE (IEC-958 Big Endian)
MU_LAW (Mu-Law)
A_LAW (A-Law)
IMA_ADPCM (Ima-ADPCM)
MPEG (MPEG)
GSM (GSM)
SPECIAL (Special)
S24_3LE (Signed 24 bit Little Endian in 3bytes)
S24_3BE (Signed 24 bit Big Endian in 3bytes)
U24_3LE (Unsigned 24 bit Little Endian in 3bytes)
U24_3BE (Unsigned 24 bit Big Endian in 3bytes)
S20_3LE (Signed 20 bit Little Endian in 3bytes)
S20_3BE (Signed 20 bit Big Endian in 3bytes)
U20_3LE (Unsigned 20 bit Little Endian in 3bytes)
U20_3BE (Unsigned 20 bit Big Endian in 3bytes)
S18_3LE (Signed 18 bit Little Endian in 3bytes)
S18_3BE (Signed 18 bit Big Endian in 3bytes)
U18_3LE (Unsigned 18 bit Little Endian in 3bytes)
U18_3BE (Unsigned 18 bit Big Endian in 3bytes)
G723_24 (G.723 (ADPCM) 24 kbit/s, 8 samples in 3 bytes)
G723_24_1B (G.723 (ADPCM) 24 kbit/s, 1 sample in 1 byte)
G723_40 (G.723 (ADPCM) 40 kbit/s, 8 samples in 3 bytes)
G723_40_1B (G.723 (ADPCM) 40 kbit/s, 1 sample in 1 byte)
DSD_U8 (Direct Stream Digital, 1-byte (x8), oldest bit in MSB)
DSD_U16_LE (Direct Stream Digital, 2-byte (x16), little endian, oldest bits in MSB)
DSD_U32_LE (Direct Stream Digital, 4-byte (x32), little endian, oldest bits in MSB)
DSD_U16_BE (Direct Stream Digital, 2-byte (x16), big endian, oldest bits in MSB)
DSD_U32_BE (Direct Stream Digital, 4-byte (x32), big endian, oldest bits in MSB)
PCM subformats:
STD (Standard)
PCM states:
OPEN
SETUP
PREPARED
RUNNING
XRUN
DRAINING
PAUSED
SUSPENDED
DISCONNECTED
-
读写操作和XRUN
当调用snd_pcm_writei来发送数据时,可能会发生underrun;
当调用snd_pcm_readi来读取数据时,可能发生overrun。
两者都返回值-EPIPE。返回值为EPIPE表明发生了underrun/overrun,使得PCM音频流进入到XRUN状态并停止处理数据。从该状态中恢复过来的标准方法是调用snd_pcm_prepare()函数,把PCM流置于PREPARED状态,这样下次我们向该PCM流中数据时,它就能重新开始处理数据。
ssize_t CKVM_PCM_Record_Handle::pcm_read(u_char *data, size_t rcount)
ssize_t r;
size_t result = 0;
size_t count = rcount;
if (count != chunk_size)
count = chunk_size;
while (count > 0 && !in_aborting)
r = readi_func(handle, data, count);
if (r == -EAGAIN || (r >= 0 && (size_t)r < count))
snd_pcm_wait(handle, 100);
else if (r == -EPIPE)
xrun();
else if (r == -ESTRPIPE)
suspend();
else if (r < 0)
error(_("read error: %s"), snd_strerror(r));
prg_exit(EXIT_FAILURE);
if (r > 0)
result += r;
count -= r;
data += r * bits_per_frame / 8;
return rcount;
ssize_t CKVM_PCM_Play_Handle::pcm_write(u_char *data, size_t count)
ssize_t r;
ssize_t result = 0;
if (count < chunk_size)
snd_pcm_format_set_silence(hwparams.format, data + count * bits_per_frame / 8, (chunk_size - count) * hwparams.channels);
count = chunk_size;
//data = remap_data(data, count);
while (count > 0 && !in_aborting)
r = writei_func(handle, data, count);
if (r == -EAGAIN || (r >= 0 && (size_t)r < count))
snd_pcm_wait(handle, 100);
else if (r == -EPIPE)
xrun();
else if (r == -ESTRPIPE)
suspend();
else if (r < 0)
error(_("write error: %s"), snd_strerror(r));
prg_exit(EXIT_FAILURE);
if (r > 0)
// if (vumeter)
// compute_max_peak(data, r * hwparams.channels);
result += r;
count -= r;
data += r * bits_per_frame / 8;
return result;
/* I/O error handler */
void CKVM_PCM_Handle::xrun(void)
snd_pcm_status_t *status;
int res;
snd_pcm_status_alloca(&status);
if ((res = snd_pcm_status(handle, status))<0)
error(_("status error: %s"), snd_strerror(res));
prg_exit(EXIT_FAILURE);
if (snd_pcm_status_get_state(status) == SND_PCM_STATE_XRUN)
if (fatal_errors)
error(_("fatal %s: %s"),
stream == SND_PCM_STREAM_PLAYBACK ? _("underrun") : _("overrun"),
snd_strerror(res));
prg_exit(EXIT_FAILURE);
if (monotonic)
#ifdef HAVE_CLOCK_GETTIME
struct timespec now, diff, tstamp;
clock_gettime(CLOCK_MONOTONIC, &now);
snd_pcm_status_get_trigger_htstamp(status, &tstamp);
timermsub(&now, &tstamp, &diff);
fprintf(stderr, _("%s!!! (at least %.3f ms long)\\n"),
stream == SND_PCM_STREAM_PLAYBACK ? _("underrun") : _("overrun"),
diff.tv_sec * 1000 + diff.tv_nsec / 1000000.0);
#else
fprintf(stderr, "%s !!!\\n", _("underrun"));
#endif
else
struct timeval now, diff, tstamp;
gettimeofday(&now, 0);
snd_pcm_status_get_trigger_tstamp(status, &tstamp);
timersub(&now, &tstamp, &diff);
fprintf(stderr, _("%s!!! (at least %.3f ms long)\\n"),
stream == SND_PCM_STREAM_PLAYBACK ? _("underrun") : _("overrun"),
diff.tv_sec * 1000 + diff.tv_usec / 1000.0);
if (verbose)
fprintf(stderr, _("Status:\\n"));
snd_pcm_status_dump(status, log);
if ((res = snd_pcm_prepare(handle))<0)
error(_("xrun: prepare error: %s"), snd_strerror(res));
prg_exit(EXIT_FAILURE);
return; /* ok, data should be accepted again */
if (snd_pcm_status_get_state(status) == SND_PCM_STATE_DRAINING)
if (verbose)
fprintf(stderr, _("Status(DRAINING):\\n"));
snd_pcm_status_dump(status, log);
if (stream == SND_PCM_STREAM_CAPTURE)
fprintf(stderr, _("capture stream format change? attempting recover...\\n"));
if ((res = snd_pcm_prepare(handle))<0)
error(_("xrun(DRAINING): prepare error: %s"), snd_strerror(res));
prg_exit(EXIT_FAILURE);
return;
if (verbose)
fprintf(stderr, _("Status(R/W):\\n"));
snd_pcm_status_dump(status, log);
error(_("read/write error, state = %s"), snd_pcm_state_name(snd_pcm_status_get_state(status)));
prg_exit(EXIT_FAILURE);
/* I/O suspend handler */
void CKVM_PCM_Handle::suspend(void)
int res;
if (!quiet_mode)
fprintf(stderr, _("Suspended. Trying resume. ")); fflush(stderr);
while ((res = snd_pcm_resume(handle)) == -EAGAIN)
sleep(1); /* wait until suspend flag is released */
if (res < 0)
if (!quiet_mode)
fprintf(stderr, _("Failed. Restarting stream. ")); fflush(stderr);
if ((res = snd_pcm_prepare(handle)) < 0)
error(_("suspend: prepare error: %s"), snd_strerror(res));
prg_exit(EXIT_FAILURE);
if (!quiet_mode)
fprintf(stderr, _("Done.\\n"));
录音代码示例
/*
This example reads from the default PCM device
and writes to standard output for 5 seconds of data.
*/
/* Use the newer ALSA API */
#define ALSA_PCM_NEW_HW_PARAMS_API
#include <alsa/asoundlib.h>
int main()
long loops;
int rc;
int size;
snd_pcm_t *handle;
snd_pcm_hw_params_t *params;
unsigned int val;
int dir;
snd_pcm_uframes_t frames;
char *buffer;
/* Open PCM device for recording (capture). */
rc = snd_pcm_open(&handle, "default",
SND_PCM_STREAM_CAPTURE, 0);
if (rc < 0)
fprintf(stderr,
"unable to open pcm device: %s\\n",
snd_strerror(rc));
exit(1);
/* Allocate a hardware parameters object. */
snd_pcm_hw_params_alloca(¶ms);
/* Fill it in with default values. */
snd_pcm_hw_params_any(handle, params);
/* Set the desired hardware parameters. */
/* Interleaved mode */
snd_pcm_hw_params_set_access(handle, params,
SND_PCM_ACCESS_RW_INTERLEAVED);
/* Signed 16-bit little-endian format */
snd_pcm_hw_params_set_format(handle, params,
SND_PCM_FORMAT_S16_LE);
/* Two channels (stereo) */
snd_pcm_hw_params_set_channels(handle, params, 2);
/* 44100 bits/second sampling rate (CD quality) */
val = 44100;
snd_pcm_hw_params_set_rate_near(handle, params,
&val, &dir);
/* Set period size to 32 frames. */
frames = 32;
snd_pcm_hw_params_set_period_size_near(handle,
params, &frames, &dir);
/* Write the parameters to the driver */
rc = snd_pcm_hw_params(handle, params);
if (rc < 0)
fprintf(stderr,
"unable to set hw parameters: %s\\n",
snd_strerror(rc));
exit(1);
/* Use a buffer large enough to hold one period */
snd_pcm_hw_params_get_period_size(params,
&frames, &dir);
size = frames * 4; /* 2 bytes/sample, 2 channels 每帧大小是4字节*/
buffer = (char *) malloc(size);
/* We want to loop for 5 seconds val的值即为录制32帧数据需要的时间*/
snd_pcm_hw_params_get_period_time(params,
&val, &dir);
loops = 5000000 / val; //录制5秒
while (loops > 0)
loops--;
rc = snd_pcm_readi(handle, buffer, frames);
if (rc == -EPIPE)
/* EPIPE means overrun */
fprintf(stderr, "overrun occurred\\n");
snd_pcm_prepare(handle);
else if (rc < 0)
fprintf(stderr,
"error from read: %s\\n",
snd_strerror(rc));
else if (rc != (int)frames)
fprintf(stderr, "short read, read %d frames\\n", rc);
rc = write(1, buffer, size);
if (rc != size)
fprintf(stderr,
"short write: wrote %d bytes\\n", rc);
snd_pcm_drain(handle);
snd_pcm_close(handle);
free(buffer);
return 0;
测试:./rec > rec.raw //录音保存到文件rec.raw
播放rec.raw: aplay -f cd rec.raw
播放代码示例
/*
This example reads standard from input and writes
to the default PCM device for 5 seconds of data.
*/
/* Use the newer ALSA API */
#define ALSA_PCM_NEW_HW_PARAMS_API
#include <alsa/asoundlib.h>
int main()
long loops;
int rc;
int size;
snd_pcm_t *handle;
snd_pcm_hw_params_t *params;
unsigned int val;
int dir;
snd_pcm_uframes_t frames;
char *buffer;
/* Open PCM device for playback. */
rc = snd_pcm_open(&handle, "default",
SND_PCM_STREAM_PLAYBACK, 0);
if (rc < 0)
fprintf(stderr,"unable to open pcm device: %s\\n",snd_strerror(rc));
exit(1);
/* Allocate a hardware parameters object. */
snd_pcm_hw_params_alloca(¶ms);
/* Fill it in with default values. */
snd_pcm_hw_params_any(handle, params);
/* Set the desired hardware parameters. */
/* Interleaved mode */
snd_pcm_hw_params_set_access(handle, params,
SND_PCM_ACCESS_RW_INTERLEAVED);
/* Signed 16-bit little-endian format */
snd_pcm_hw_params_set_format(handle, params,
SND_PCM_FORMAT_S16_LE);
/* Two channels (stereo) */
snd_pcm_hw_params_set_channels(handle, params, 2);
/* 44100 bits/second sampling rate (CD quality) */
val = 44100;
snd_pcm_hw_params_set_rate_near(handle, params,
&val, &dir);
/* Set period size to 32 frames. */
frames = 32;
snd_pcm_hw_params_set_period_size_near(handle,
params, &frames, &dir);
/* Write the parameters to the driver */
rc = snd_pcm_hw_params(handle, params);
if (rc < 0)
fprintf(stderr,
"unable to set hw parameters: %s\\n",
snd_strerror(rc));
exit(1);
/* Use a buffer large enough to hold one period */
snd_pcm_hw_params_get_period_size(params, &frames,
&dir);
size = frames * 4; /* 2 bytes/sample, 2 channels */
buffer = (char *) malloc(size);
/* We want to loop for 5 seconds */
snd_pcm_hw_params_get_period_time(params,&val, &dir);
/* 5 seconds in microseconds divided by
* period time */
loops = 5000000 / val;
while (loops > 0) //循环录音 5 s
loops--;
rc = read(0, buffer, size);
if (rc == 0) //没有读取到数据
fprintf(stderr, "end of file on input\\n");
break;
else if (rc != size)//实际读取 的数据 小于 要读取的数据
fprintf(stderr,"short read: read %d bytes\\n", rc);
rc = snd_pcm_writei(handle, buffer, frames);//写入声卡 (放音)
if (rc == -EPIPE)
/* EPIPE means underrun */
fprintf(stderr, "underrun occurred\\n");
snd_pcm_prepare(handle);
else if (rc < 0)
fprintf(stderr,"error from writei: %s\\n",snd_strerror(rc));
else if (rc != (int)frames)
fprintf(stderr,"short write, write %d frames\\n", rc);
snd_pcm_drain(handle); //snd_pcm_drain把所有挂起没有传输完的声音样本传输完全,最后关闭该音频流,释放之前动态分配的缓冲区,退出。
snd_pcm_close(handle);
free(buffer);
return 0;
-
重定向log
static snd_output_t *log;
int err = snd_output_stdio_attach(&log, stderr, 0); //把stderr的信息重定向到 log中去。
//之后通过 snd_pcm_hw_params_dump(params, log); snd_pcm_dump(handle, log); snd_pcm_status_dump(status, log); //等把sound的配置和运行信息导出到log中去。
最后关闭 snd_output_close(log);
-
获取声卡信息
Alsa/pcm.h中:
/**
* \\defgroup PCM_Info Stream Information
* \\ingroup PCM
* See the \\ref pcm page for more details.
* \\
*/
size_t snd_pcm_info_sizeof(void);
/** \\hideinitializer
* \\brief allocate an invalid #snd_pcm_info_t using standard alloca
* \\param ptr returned pointer
*/
#define snd_pcm_info_alloca(ptr) __snd_alloca(ptr, snd_pcm_info)
int snd_pcm_info_malloc(snd_pcm_info_t **ptr);
void snd_pcm_info_free(snd_pcm_info_t *obj);
void snd_pcm_info_copy(snd_pcm_info_t *dst, const snd_pcm_info_t *src);
unsigned int snd_pcm_info_get_device(const snd_pcm_info_t *obj);
unsigned int snd_pcm_info_get_subdevice(const snd_pcm_info_t *obj);
snd_pcm_stream_t snd_pcm_info_get_stream(const snd_pcm_info_t *obj);
int snd_pcm_info_get_card(const snd_pcm_info_t *obj);
const char *snd_pcm_info_get_id(const snd_pcm_info_t *obj);
const char *snd_pcm_info_get_name(const snd_pcm_info_t *obj);
const char *snd_pcm_info_get_subdevice_name(const snd_pcm_info_t *obj);
snd_pcm_class_t snd_pcm_info_get_class(const snd_pcm_info_t *obj);
snd_pcm_subclass_t snd_pcm_info_get_subclass(const snd_pcm_info_t *obj);
unsigned int snd_pcm_info_get_subdevices_count(const snd_pcm_info_t *obj);
unsigned int snd_pcm_info_get_subdevices_avail(const snd_pcm_info_t *obj);
snd_pcm_sync_id_t snd_pcm_info_get_sync(const snd_pcm_info_t *obj);
void snd_pcm_info_set_device(snd_pcm_info_t *obj, unsigned int val);
void snd_pcm_info_set_subdevice(snd_pcm_info_t *obj, unsigned int val);
void snd_pcm_info_set_stream(snd_pcm_info_t *obj, snd_pcm_stream_t val);
以上是关于android audio/linux alsa音频-应用层基础的主要内容,如果未能解决你的问题,请参考以下文章
嵌入式linux/android alsa_aplay alsa_amixer命令行用法