android audio/linux alsa音频-应用层基础

Posted xgbing

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android audio/linux alsa音频-应用层基础相关的知识,希望对你有一定的参考价值。

  1. 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 函数


 

  1. 格式说明

下面的程序打印出各种格式:

#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
  1. 读写操作和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(&params);

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

  /* 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;
  1. 重定向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);

  1. 获取声卡信息

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音频-应用层基础的主要内容,如果未能解决你的问题,请参考以下文章

解决Ubuntu SMPlayer播放视频无声音问题

嵌入式流媒体开发Linux ALSA 声卡数据采集与播放

使用 ALSA/CAF 进行 Android 通话录音

嵌入式linux/android alsa_aplay alsa_amixer命令行用法

linux中的alsa工具与Android中的tinyalsa工具

android2.3采用alsa作为音频系统,在asound.conf中的扬声器音量设置问题。