本文共 16523 字,大约阅读时间需要 55 分钟。
-
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#include 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.0PCM stream types: PLAYBACK CAPTUREPCM access types: MMAP_INTERLEAVED MMAP_NONINTERLEAVED MMAP_COMPLEX RW_INTERLEAVED RW_NONINTERLEAVEDPCM 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 deviceand writes to standard output for 5 seconds of data.*//* Use the newer ALSA API */#define ALSA_PCM_NEW_HW_PARAMS_API#includeint 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 writesto the default PCM device for 5 seconds of data.*//* Use the newer ALSA API */#define ALSA_PCM_NEW_HW_PARAMS_API#includeint 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);
转载地址:https://blog.csdn.net/xgbing/article/details/102812160 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!