ALSA声卡驱动:

1.Linux ALSA声卡驱动之一:ALSA架构简介和ASOC架构简介

2.Linux ALSA声卡驱动之二:Platform

3. Linux ALSA声卡驱动之三:Platform之Cpu_dai

4. Linux ALSA声卡驱动之四:Codec 以及Codec_dai

5.Linux ALSA声卡驱动之五:Machine 以及ALSA声卡的注册

6.Linux ALSA声卡驱动之六:PCM的注册流程

7.Linux ALSA声卡驱动之七:录音(Capture) 调用流程

一、录音(Capture)调用时间时序图

从上面的时序图能看出,tingyalsa的调用是通过open的方式来调用底层驱动函数。上面图片不够清晰可以看这个pdf文档(//download.csdn.net/download/Bill_xiao/12260596).

  • 1.1 tinyalsa调用

tinyalsa 录音:tinycap /sdcard/record.wav -D 0 -d 13 -c 2 -r 32000 -b 16 -T 5 这些参数是什么意思。

tinycap :调用的c程序,一个可执行程序,在system/bin下面。

/sdcard/record.wav:录音数据保存的文件路径。

-D 0:声卡号 0的驱动,一般我们手机只有一个声卡。

-d 13:设备号13,也就pcm号13,就是驱动配置录音设备(codec)

-c 2  :c 是channels(声道)的首字母 2代码两个声道,左右声道。

-r 32000 : r是rate(采样率)的首字母  32000是采用频率,以hz为单位

-b 16  :b是bate(量化度),16位宽大小,常用的有8  16   24   32   ,量化度越高,音频信号越可能接近原生信号。

-T 5:T time  录音的时间大小,单位是s。

  • 1.2 tinyalsa   main函数

int main(int argc, char **argv)
{FILE *file;struct wav_header header;unsigned int card = 0;unsigned int device = 0;unsigned int channels = 2;unsigned int rate = 44100;unsigned int bits = 16;unsigned int frames;unsigned int period_size = 1024;unsigned int period_count = 4;unsigned int cap_time = 0;enum pcm_format format;for(int i=0;i<argc;i++){printf("tinycap argv[%d] ::%s \n",i,argv[i]);   } if (argc < 2) {fprintf(stderr, "Usage: %s file.wav [-D card] [-d device]"" [-c channels] [-r rate] [-b bits] [-p period_size]"" [-n n_periods] [-T capture time]\n", argv[0]);return 1;}file = fopen(argv[1], "wb");if (!file) {fprintf(stderr, "Unable to create file '%s'\n", argv[1]);return 1;}/* parse command line arguments */argv += 2;while (*argv) {if (strcmp(*argv, "-d") == 0) {argv++;if (*argv)device = atoi(*argv);} else if (strcmp(*argv, "-c") == 0) {argv++;if (*argv)channels = atoi(*argv);} else if (strcmp(*argv, "-r") == 0) {argv++;if (*argv)rate = atoi(*argv);} else if (strcmp(*argv, "-b") == 0) {argv++;if (*argv)bits = atoi(*argv);} else if (strcmp(*argv, "-D") == 0) {argv++;if (*argv)card = atoi(*argv);} else if (strcmp(*argv, "-p") == 0) {argv++;if (*argv)period_size = atoi(*argv);} else if (strcmp(*argv, "-n") == 0) {argv++;if (*argv)period_count = atoi(*argv);} else if (strcmp(*argv, "-T") == 0) {argv++;if (*argv)cap_time = atoi(*argv);}if (*argv)argv++;}header.riff_id = ID_RIFF;header.riff_sz = 0;header.riff_fmt = ID_WAVE;header.fmt_id = ID_FMT;header.fmt_sz = 16;header.audio_format = FORMAT_PCM;header.num_channels = channels;header.sample_rate = rate;switch (bits) {case 32:format = PCM_FORMAT_S32_LE;break;case 24:format = PCM_FORMAT_S24_LE;break;case 16:format = PCM_FORMAT_S16_LE;break;default:fprintf(stderr, "%d bits is not supported.\n", bits);return 1;}header.bits_per_sample = pcm_format_to_bits(format);//bit  32  16 24  采样精度header.byte_rate = (header.bits_per_sample / 8) * channels * rate;  //采样大小 byteheader.block_align = channels * (header.bits_per_sample / 8);//每帧的大小,单位byteheader.data_id = ID_DATA;/* leave enough room for header */fseek(file, sizeof(struct wav_header), SEEK_SET);/* install signal handler and begin capturing */signal(SIGINT, sigint_handler);signal(SIGHUP, sigint_handler);signal(SIGTERM, sigint_handler);frames = capture_sample(file, card, device, header.num_channels,header.sample_rate, format,period_size, period_count, cap_time);printf("tinycap frames= %u byte\n", frames);/* write header now all information is known */header.data_sz = frames * header.block_align;//帧的大小,相当于period_sizeheader.riff_sz = header.data_sz + sizeof(header) - 8; //pcm或者wav文件的大小printf("tinycap header.data_sz= %u byte\n", header.data_sz);//XIAOfseek(file, 0, SEEK_SET);fwrite(&header, sizeof(struct wav_header), 1, file);fseek(file, 0, SEEK_END);//XIAOprintf("tinycap file size= %ld byte\n", ftell(file));//XIAOfclose(file);return 0;
}

上述代码  file = fopen(argv[1], "wb"),虽然不知道argv[1]是什么,但是fopen函数是知道,打开一个文件。argv[1]是我们执行

tinycap /sdcard/record.wav -D 0 -d 13 -c 2 -r 32000 -b 16 -T 5  第二个参数: /sdcard/record.wav   

 while (*argv) {
        if (strcmp(*argv, "-d") == 0) {
            argv++;
            if (*argv)
                device = atoi(*argv);
        } else if (strcmp(*argv, "-c") == 0) {
            argv++;
            if (*argv)
                channels = atoi(*argv);
        } else if (strcmp(*argv, "-r") == 0) {

上面代码的-d -c  -r 不就是我们输入的参数么?不错,其实就是对我们输入的参数进行校正和赋值,如果我们输入的参数跟程序里面的字符串匹配就对初始化值重新赋值,否则就不做处理。上述代码中有很多关于音频相关的名词如果不懂的可以看上章(Linux ALSA声卡驱动之六:PCM的注册流程).

  • 1.3 capture_sample,

上述的代码只是初始化赋值而已,对于音频的数据的获取以及底层驱动的交互一点都没有。不要急在main函数里面有个capture_sample,这个里面应该是有关音频的实际操作。点击进去果然,找到open_pcm,那open_pcm有做了什么事呢?

unsigned int capture_sample(FILE *file, unsigned int card, unsigned int device,unsigned int channels, unsigned int rate,enum pcm_format format, unsigned int period_size,unsigned int period_count, unsigned int cap_time)
{struct pcm_config config;struct pcm *pcm;char *buffer;unsigned int size;unsigned int bytes_read = 0;struct timespec end;struct timespec now;memset(&config, 0, sizeof(config));config.channels = channels;config.rate = rate;config.period_size = period_size;config.period_count = period_count;config.format = format;config.start_threshold = 0;config.stop_threshold = 0;config.silence_threshold = 0;pcm = pcm_open(card, device, PCM_IN, &config);if (!pcm || !pcm_is_ready(pcm)) {fprintf(stderr, "Unable to open PCM device (%s)\n",pcm_get_error(pcm));return 0;}printf("tinycap capture_sample buffer: %u \n", pcm_get_buffer_size(pcm));size = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm));printf("tinycap capture_sample  period_size= %u byte ,period_count= %u byte ,size= %u byte\n", config.period_size,config.period_count,size);//xiaobuffer = malloc(size);if (!buffer) {fprintf(stderr, "Unable to allocate %d bytes\n", size);free(buffer);pcm_close(pcm);return 0;}// printf("tinycap Capturing sample: %u ch, %u hz, %u bit\n", channels, rate,//     pcm_format_to_bits(format));clock_gettime(CLOCK_MONOTONIC, &now);end.tv_sec = now.tv_sec + cap_time;end.tv_nsec = now.tv_nsec;while (capturing && !pcm_read(pcm, buffer, size)) {printf("tinycap capture_sample pcm_read size: %u \n", size);if (fwrite(buffer, 1, size, file) != size) {fprintf(stderr,"Error capturing sample\n");break;}bytes_read += size;if (cap_time) {clock_gettime(CLOCK_MONOTONIC, &now);if (now.tv_sec > end.tv_sec ||(now.tv_sec == end.tv_sec && now.tv_nsec >= end.tv_nsec))break;}}free(buffer);pcm_close(pcm);return pcm_bytes_to_frames(pcm, bytes_read);
}
  • 1.4 open_pcm  做的工作:

1.open(fn, O_RDWR|O_NONBLOCK)打开底层驱动函数,到了现在我们已经与底层驱动联系上了。其实这个open首先打开的是sound.c  snd_open 函数,从snd_minors取出我们指定的PCM设备。

2. ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, &params) 通过ioctl把一些硬件参数设置到底层。

struct pcm *pcm_open(unsigned int card, unsigned int device,unsigned int flags, struct pcm_config *config)
{struct pcm *pcm;struct snd_pcm_info info;struct snd_pcm_hw_params params;struct snd_pcm_sw_params sparams;char fn[256];int rc;pcm = calloc(1, sizeof(struct pcm));if (!pcm || !config)return &bad_pcm; /* TODO: could support default config here */pcm->config = *config;snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device,flags & PCM_IN ? 'c' : 'p');pcm->flags = flags;pcm->fd = open(fn, O_RDWR|O_NONBLOCK);if (pcm->fd < 0) {oops(pcm, errno, "cannot open device '%s'", fn);return pcm;}if (fcntl(pcm->fd, F_SETFL, fcntl(pcm->fd, F_GETFL) &~O_NONBLOCK) < 0) {oops(pcm, errno, "failed to reset blocking mode '%s'", fn);goto fail_close;}//由底层file->private_data->substream 来赋值if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info)) {printf("tinycap pcm_open  ioctl SNDRV_PCM_IOCTL_INFO  \n");oops(pcm, errno, "cannot get info");goto fail_close;}printf("tinycap pcm_open  ioctl  info.subdevice=%u ,info.device=%u,info.name=%s \n",info.subdevice,info.device,info.name);pcm->subdevice = info.subdevice;//下面所有的params操作都是对snd_mask masks 和snd_interval intervals这两个结构体数组的填充赋值。param_init(&params);param_set_mask(&params, SNDRV_PCM_HW_PARAM_FORMAT,pcm_format_to_alsa(config->format));param_set_mask(&params, SNDRV_PCM_HW_PARAM_SUBFORMAT,SNDRV_PCM_SUBFORMAT_STD);param_set_min(&params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, config->period_size);param_set_int(&params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,pcm_format_to_bits(config->format));param_set_int(&params, SNDRV_PCM_HW_PARAM_FRAME_BITS,pcm_format_to_bits(config->format) * config->channels);param_set_int(&params, SNDRV_PCM_HW_PARAM_CHANNELS,config->channels);param_set_int(&params, SNDRV_PCM_HW_PARAM_PERIODS, config->period_count);param_set_int(&params, SNDRV_PCM_HW_PARAM_RATE, config->rate);if (flags & PCM_NOIRQ) {if (!(flags & PCM_MMAP)) {oops(pcm, EINVAL, "noirq only currently supported with mmap().");goto fail_close;}params.flags |= SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP;pcm->noirq_frames_per_msec = config->rate / 1000;}if (flags & PCM_MMAP)param_set_mask(&params, SNDRV_PCM_HW_PARAM_ACCESS,SNDRV_PCM_ACCESS_MMAP_INTERLEAVED);elseparam_set_mask(&params, SNDRV_PCM_HW_PARAM_ACCESS,SNDRV_PCM_ACCESS_RW_INTERLEAVED);//硬件信息的匹配,而且还初始化了DMA内存if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, &params)) {oops(pcm, errno, "cannot set hw params");goto fail_close;}/* get our refined hw_params */config->period_size = param_get_int(&params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);config->period_count = param_get_int(&params, SNDRV_PCM_HW_PARAM_PERIODS);printf("tinycap pcm_open  config->period_size=%u  config->period_count=%u   \n",config->period_size,config->period_count);pcm->buffer_size = config->period_count * config->period_size;
  • 1.5  pcm_read 做了如下工作

1.pcm_start 录音准备,搭起录音通道,为准备录音做准备。

2.ioctl(pcm->fd, SNDRV_PCM_IOCTL_READI_FRAMES, &x),读取录音数据。并写到sdcard/record.wav

int pcm_read(struct pcm *pcm, void *data, unsigned int count)
{struct snd_xferi x;if (!(pcm->flags & PCM_IN))return -EINVAL;x.buf = data;//读的数据都存储在这里,而且data的大小是countx.frames = count / (pcm->config.channels *pcm_format_to_bits(pcm->config.format) / 8);printf("tinycap pcm_read  x.frames=%lu \n",x.frames);for (;;) {printf("tinycap pcm_read  pcm->running=%d \n",pcm->running);if (!pcm->running) {if (pcm_start(pcm) < 0) {fprintf(stderr, "start error");return -errno;}}if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_READI_FRAMES, &x)) {printf("tinycap pcm_read  ioctl SNDRV_PCM_IOCTL_READI_FRAMES\n");pcm->prepared = 0;pcm->running = 0;if (errno == EPIPE) {/* we failed to make our window -- try to restart */pcm->underruns++;continue;}return oops(pcm, errno, "cannot read stream data");}return 0;}
}

二、PCM设备底层的调用流程

2.1 open

前面已经讲过上层open之后会调用 snd_open,最终会调用snd_pcm_f_ops里面的对应的open函数,ioctl也是一样的执行流程。

函数调用流程:snd_pcm_capture_open->snd_pcm_open->snd_pcm_open_file ->snd_pcm_open_substream,最后会如下面的代码调用substream->ops->open(substream)

substream->ops->open是什么呢?如下定义。这些定义在soc_new_pcm

rtd->ops.open        = soc_pcm_open;
        rtd->ops.hw_params    = soc_pcm_hw_params;
        rtd->ops.prepare    = soc_pcm_prepare;
        rtd->ops.trigger    = soc_pcm_trigger;
        rtd->ops.hw_free    = soc_pcm_hw_free;
        rtd->ops.close        = soc_pcm_close;
        rtd->ops.pointer    = soc_pcm_pointer;
        rtd->ops.ioctl        = soc_pcm_ioctl;

int snd_pcm_open_substream(struct snd_pcm *pcm, int stream,struct file *file,struct snd_pcm_substream **rsubstream)
{struct snd_pcm_substream *substream;int err;err = snd_pcm_attach_substream(pcm, stream, file, &substream);if (err < 0)return err;if (substream->ref_count > 1) {*rsubstream = substream;return 0;}err = snd_pcm_hw_constraints_init(substream);if (err < 0) {pcm_dbg(pcm, "snd_pcm_hw_constraints_init failed\n");goto error;}if ((err = substream->ops->open(substream)) < 0)goto error;substream->hw_opened = 1;err = snd_pcm_hw_constraints_complete(substream);if (err < 0) {pcm_dbg(pcm, "snd_pcm_hw_constraints_complete failed\n");goto error;}*rsubstream = substream;return 0;error:snd_pcm_release_substream(substream);return err;
}
}
static int soc_pcm_open(struct snd_pcm_substream *substream)
{struct snd_soc_pcm_runtime *rtd = substream->private_data;struct snd_pcm_runtime *runtime = substream->runtime;struct snd_soc_platform *platform = rtd->platform;struct snd_soc_dai *cpu_dai = rtd->cpu_dai;struct snd_soc_dai *codec_dai;const char *codec_dai_name = "multicodec";int i, ret = 0;DEBUG_I("soc_pcm_open substream->name=%s \n", substream->name);//xiaopinctrl_pm_select_default_state(cpu_dai->dev);for (i = 0; i < rtd->num_codecs; i++)pinctrl_pm_select_default_state(rtd->codec_dais[i]->dev);pm_runtime_get_sync(cpu_dai->dev);for (i = 0; i < rtd->num_codecs; i++)pm_runtime_get_sync(rtd->codec_dais[i]->dev);pm_runtime_get_sync(platform->dev);mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass);/* startup the audio subsystem */if (cpu_dai->driver->ops && cpu_dai->driver->ops->startup) {ret = cpu_dai->driver->ops->startup(substream, cpu_dai);if (ret < 0) {dev_err(cpu_dai->dev, "ASoC: can't open interface"" %s: %d\n", cpu_dai->name, ret);goto out;}}if (platform->driver->ops && platform->driver->ops->open) {ret = platform->driver->ops->open(substream);DEBUG_I("soc_pcm_open platform->driver->ops->open substream->name=%s \n", substream->name);//xiaoif (ret < 0) {dev_err(platform->dev, "ASoC: can't open platform"" %s: %d\n", platform->component.name, ret);goto platform_err;}}for (i = 0; i < rtd->num_codecs; i++) {codec_dai = rtd->codec_dais[i];if (codec_dai->driver->ops && codec_dai->driver->ops->startup) {ret = codec_dai->driver->ops->startup(substream,codec_dai);DEBUG_I("soc_pcm_open codec_dai->driver->ops->startup substream->name=%s \n", substream->name);//xiaoif (ret < 0) {dev_err(codec_dai->dev,"ASoC: can't open codec %s: %d\n",codec_dai->name, ret);goto codec_dai_err;}}if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)codec_dai->tx_mask = 0;elsecodec_dai->rx_mask = 0;}if (rtd->dai_link->ops && rtd->dai_link->ops->startup) {ret = rtd->dai_link->ops->startup(substream);if (ret < 0) {pr_err("ASoC: %s startup failed: %d\n",rtd->dai_link->name, ret);goto machine_err;}}

经过上面的代码调用会到之前注册的platform  codec  cpu_dai  codec_dai的驱动程序代码。

我们以mtk-soc-tdm-capture.c程序驱动来说明

static struct snd_pcm_ops mtk_afe_capture_ops = {.open =     mtk_capture_pcm_open,.close =    mtk_capture_pcm_close,.ioctl =    snd_pcm_lib_ioctl,.hw_params =    mtk_capture_pcm_hw_params,.hw_free =  mtk_capture_pcm_hw_free,.prepare =  mtk_capture_pcm_prepare,.trigger =  mtk_capture_pcm_trigger,.pointer =  mtk_capture_pcm_pointer,.copy =     mtk_capture_pcm_copy,.silence =  mtk_capture_pcm_silence,.page =     mtk_capture_pcm_page,
};
static int mtk_capture_pcm_open(struct snd_pcm_substream *substream)
{struct snd_pcm_runtime *runtime = substream->runtime;int ret = 0;DEBUG_I("mtk-soc-tdm-capture mtk_capture_pcm_open  substream->name=%s \n", substream->name);//xiaoAudDrv_Clk_On();pr_debug("%s\n", __func__);TDM_VUL_Control_context = Get_Mem_ControlT(Soc_Aud_Digital_Block_MEM_VUL);runtime->hw = mtk_capture_hardware;memcpy((void *)(&(runtime->hw)), (void *)&mtk_capture_hardware,sizeof(struct snd_pcm_hardware));pr_debug("runtime->hw->rates = 0x%x\n ", runtime->hw.rates);ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,&constraints_sample_rates);ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);if (ret < 0)pr_err("snd_pcm_hw_constraint_integer failed\n");pr_debug("mtk_capture_pcm_open runtime rate = %d channels = %d\n", runtime->rate,runtime->channels);runtime->hw.info |= SNDRV_PCM_INFO_INTERLEAVED;runtime->hw.info |= SNDRV_PCM_INFO_NONINTERLEAVED;runtime->hw.info |= SNDRV_PCM_INFO_MMAP_VALID;if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)pr_debug("SNDRV_PCM_STREAM_CAPTURE mtkalsa_capture_constraints\n");if (ret < 0) {pr_err("mtk_capture_pcm_close\n");mtk_capture_pcm_close(substream);return ret;}pr_debug("mtk_capture_pcm_open return\n");return 0;
}

其实上述代码没做什么就是对pcm硬件参数的初始化,打开audio 时钟。至此open的流程讲完了。

2.2 ioctl   SNDRV_PCM_IOCTL_HW_PARAMS

函数调用流程:snd_pcm_capture_ioctl->snd_pcm_capture_ioctl1->snd_pcm_common_ioctl1->snd_pcm_hw_params_user->snd_pcm_hw_params->substream->ops->hw_params->mtk_capture_pcm_hw_params

经过这一系列的函数调用最终就做两件事:1.确定pcm硬件参数。2.开辟DMA内存,DMA内存是MTK事先已经划分好一块区域,而我们只是向系统申请DMA内存的大小。

DMA MTK有做类型区分,主要是不同类型的DMA IO地址不同

DMA类型定义在mtk-soc-digital-type.h

Soc_Aud_Digital_Block_MEM_DL1 = 0,
    Soc_Aud_Digital_Block_MEM_DL1_DATA2,
    Soc_Aud_Digital_Block_MEM_DL2,
    Soc_Aud_Digital_Block_MEM_VUL,
    Soc_Aud_Digital_Block_MEM_VUL2,
    Soc_Aud_Digital_Block_MEM_DAI,
    Soc_Aud_Digital_Block_MEM_DL3,
    Soc_Aud_Digital_Block_MEM_AWB,
    Soc_Aud_Digital_Block_MEM_MOD_DAI,

对应定义的内存地址定义在mtk-soc-afe-control.h

#define AUDIO_TOP_CON0             (AFE_BASE + 0x0000)
#define AUDIO_TOP_CON1             (AFE_BASE + 0x0004)
#define AUDIO_TOP_CON3             (AFE_BASE + 0x000c)
#define AFE_DAC_CON0               (AFE_BASE + 0x0010)
#define AFE_DAC_CON1               (AFE_BASE + 0x0014)
#define AFE_I2S_CON                (AFE_BASE + 0x0018)
#define AFE_DAIBT_CON0             (AFE_BASE + 0x001c)
#define AFE_CONN0                  (AFE_BASE + 0x0020)
#define AFE_CONN1                  (AFE_BASE + 0x0024)
#define AFE_CONN2                  (AFE_BASE + 0x0028)
#define AFE_CONN3                  (AFE_BASE + 0x002c)
#define AFE_CONN4                  (AFE_BASE + 0x0030)
#define AFE_I2S_CON1               (AFE_BASE + 0x0034)
#define AFE_I2S_CON2               (AFE_BASE + 0x0038)

static int mtk_capture_pcm_hw_params(struct snd_pcm_substream *substream,struct snd_pcm_hw_params *hw_params)
{struct snd_pcm_runtime *runtime = substream->runtime;struct snd_dma_buffer *dma_buf = &substream->dma_buffer;int ret = 0;dma_buf->dev.type = SNDRV_DMA_TYPE_DEV;dma_buf->dev.dev = substream->pcm->card->dev;dma_buf->private_data = NULL;runtime->dma_bytes = params_buffer_bytes(hw_params);if (AllocateAudioSram(&substream->runtime->dma_addr,&substream->runtime->dma_area,substream->runtime->dma_bytes, substream,params_format(hw_params), false) == 0) {DEBUG_I("mtk_capture_pcm_hw_params  AllocateAudioSram success\n");pr_aud("AllocateAudioSram success\n");SetHighAddr(Soc_Aud_Digital_Block_MEM_VUL, false, substream->runtime->dma_addr);} else if (Capture_dma_buf->area) {pr_aud("Capture_dma_buf = %p Capture_dma_buf->area = %p apture_dma_buf->addr = 0x%lx\n",Capture_dma_buf, Capture_dma_buf->area, (long) Capture_dma_buf->addr);DEBUG_I("Capture_dma_buf = %p Capture_dma_buf->area = %p apture_dma_buf->addr = 0x%lx\n",Capture_dma_buf, Capture_dma_buf->area, (long) Capture_dma_buf->addr);runtime->dma_area = Capture_dma_buf->area;runtime->dma_addr = Capture_dma_buf->addr;SetHighAddr(Soc_Aud_Digital_Block_MEM_VUL, true, runtime->dma_addr);mCaptureUseSram = true;AudDrv_Emi_Clk_On();} else {pr_warn("mtk_capture_pcm_hw_params snd_pcm_lib_malloc_pages\n");ret =  snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));}set_mem_block(substream, hw_params, TDM_VUL_Control_context, Soc_Aud_Digital_Block_MEM_VUL);pr_aud("mtk_capture_pcm_hw_params dma_bytes = %zu dma_area = %p dma_addr = 0x%lx\n",substream->runtime->dma_bytes, substream->runtime->dma_area, (long)substream->runtime->dma_addr);DEBUG_I("mtk_capture_pcm_hw_params dma_bytes = %zu dma_area = %p dma_addr = 0x%lx\n",substream->runtime->dma_bytes, substream->runtime->dma_area, (long)substream->runtime->dma_addr);return ret;}

2.3 ioctl   SNDRV_PCM_IOCTL_START

函数调用流程:snd_pcm_capture_ioctl->snd_pcm_capture_ioctl1->snd_pcm_common_ioctl1->snd_pcm_action_lock_irq->snd_pcm_action_start->snd_pcm_do_start->substream->ops->trigge->mtk_capture_pcm_trigger->StartAudioCaptureHardware

static struct action_ops snd_pcm_action_start = {.pre_action = snd_pcm_pre_start,.do_action = snd_pcm_do_start,.undo_action = snd_pcm_undo_start,.post_action = snd_pcm_post_start
};
static int snd_pcm_do_start(struct snd_pcm_substream *substream, int state)
{if (substream->runtime->trigger_master != substream)return 0;return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START);
}
static void StartAudioCaptureHardware(struct snd_pcm_substream *substream)
{struct audio_digital_i2s m2ndI2SInAttribute;struct snd_pcm_runtime *runtime = substream->runtime;//xucepr_debug("StartAudioCaptureHardware\n");DEBUG_I(" mtk-soc-tdm-capture StartAudioCaptureHardwaresubstream->name = %s\n", substream->name);memset_io((void *)&m2ndI2SInAttribute, 0, sizeof(m2ndI2SInAttribute));m2ndI2SInAttribute.mLR_SWAP = Soc_Aud_LR_SWAP_NO_SWAP;m2ndI2SInAttribute.mI2S_IN_PAD_SEL = true; /* I2S_IN_FROM_IO_MUX */m2ndI2SInAttribute.mI2S_SLAVE = Soc_Aud_I2S_SRC_MASTER_MODE;//Soc_Aud_I2S_SRC_SLAVE_MODE m2ndI2SInAttribute.mI2S_SAMPLERATE = substream->runtime->rate;m2ndI2SInAttribute.mINV_LRCK = Soc_Aud_INV_LRCK_NO_INVERSE;m2ndI2SInAttribute.mI2S_FMT = Soc_Aud_I2S_FORMAT_I2S;if (substream->runtime->format == SNDRV_PCM_FORMAT_S32_LE ||substream->runtime->format == SNDRV_PCM_FORMAT_U32_LE)m2ndI2SInAttribute.mI2S_WLEN = Soc_Aud_I2S_WLEN_WLEN_32BITS;elsem2ndI2SInAttribute.mI2S_WLEN = Soc_Aud_I2S_WLEN_WLEN_16BITS;Set2ndI2SIn(&m2ndI2SInAttribute);if (substream->runtime->format == SNDRV_PCM_FORMAT_S32_LE ||substream->runtime->format == SNDRV_PCM_FORMAT_U32_LE) {SetMemIfFetchFormatPerSample(Soc_Aud_Digital_Block_MEM_VUL, AFE_WLEN_32_BIT_ALIGN_8BIT_0_24BIT_DATA);SetConnectionFormat(OUTPUT_DATA_FORMAT_24BIT, Soc_Aud_AFE_IO_Block_MEM_VUL);} else {SetMemIfFetchFormatPerSample(Soc_Aud_Digital_Block_MEM_VUL, AFE_WLEN_16_BIT);SetConnectionFormat(OUTPUT_DATA_FORMAT_16BIT, Soc_Aud_AFE_IO_Block_MEM_VUL);}if (GetMemoryPathEnable(Soc_Aud_Digital_Block_I2S_IN_2) == false) {SetMemoryPathEnable(Soc_Aud_Digital_Block_I2S_IN_2, true);Set2ndI2SInEnable(true);} elseSetMemoryPathEnable(Soc_Aud_Digital_Block_I2S_IN_2, true);/* here to set interrupt */irq_add_user(substream,irq_request_number(Soc_Aud_Digital_Block_MEM_VUL),substream->runtime->rate,substream->runtime->period_size);SetSampleRate(Soc_Aud_Digital_Block_MEM_VUL, substream->runtime->rate);//采样率SetMemoryPathEnable(Soc_Aud_Digital_Block_MEM_VUL, true);
//配置输入输出的i2s地址 mConnectionLink     d      mtk-soc-afe-connection.c  SetConnectionState(ConnectionState, Soc_Aud_InterConnectionInput_I05,//Soc_Aud_InterConnectionOutput_O00);SetIntfConnection(Soc_Aud_InterCon_Connection,Soc_Aud_AFE_IO_Block_I2S0, Soc_Aud_AFE_IO_Block_MEM_VUL);if (!mtk_soc_always_hd) {pr_warn("%s mtk_soc_always_hd == %d\n", __func__,mtk_soc_always_hd);EnableALLbySampleRate(runtime->rate);EnableAPLLTunerbySampleRate(runtime->rate);}   SetCLkMclk(Soc_Aud_I2S0, substream->runtime->rate);//设置采样频率EnableI2SCLKDiv(Soc_Aud_I2S0_MCKDIV, true);//给设备供电EnableAfe(true);//启动AFE}

StartAudioCaptureHardware会根据创建的值在下面的数组选择匹配关系,找到了就执行类似SetDl1ToI2s0的函数

static const struct connection_link_t mConnectionLink[] = {{Soc_Aud_AFE_IO_Block_MEM_DL1, Soc_Aud_AFE_IO_Block_I2S3, SetDl1ToI2s0},{Soc_Aud_AFE_IO_Block_MEM_DL1, Soc_Aud_AFE_IO_Block_I2S1_DAC, SetDl1ToI2s1Dac},{Soc_Aud_AFE_IO_Block_ADDA_UL2, Soc_Aud_AFE_IO_Block_MEM_VUL_DATA2, SetAdc2ToVulData2},{Soc_Aud_AFE_IO_Block_ADDA_UL, Soc_Aud_AFE_IO_Block_MEM_VUL, SetAdcToVul},{Soc_Aud_AFE_IO_Block_ADDA_UL, Soc_Aud_AFE_IO_Block_MEM_VUL_DATA2, SetAdcToVulData2},{Soc_Aud_AFE_IO_Block_MEM_DL1, Soc_Aud_AFE_IO_Block_I2S1_DAC_2, SetDl1ToI2s1Dac2},{Soc_Aud_AFE_IO_Block_MEM_DL1, Soc_Aud_AFE_IO_Block_MEM_AWB, SetDl1ToAwb},{Soc_Aud_AFE_IO_Block_MEM_DL2, Soc_Aud_AFE_IO_Block_MEM_AWB, SetDl2ToAwb},{Soc_Aud_AFE_IO_Block_MEM_DL2, Soc_Aud_AFE_IO_Block_MODEM_PCM_1_O, SetDl2ToModem1Out},{Soc_Aud_AFE_IO_Block_MEM_DL2, Soc_Aud_AFE_IO_Block_MODEM_PCM_2_O, SetDl2ToModem2Out},{Soc_Aud_AFE_IO_Block_MEM_DL1, Soc_Aud_AFE_IO_Block_DAI_BT_OUT, SetDl1ToDaiBtOut},{Soc_Aud_AFE_IO_Block_MODEM_PCM_1_I_CH1, Soc_Aud_AFE_IO_Block_I2S3, SetModem1InCh1ToI2s3},{Soc_Aud_AFE_IO_Block_MODEM_PCM_2_I_CH1, Soc_Aud_AFE_IO_Block_I2S3, SetModem2InCh1ToI2s3},{Soc_Aud_AFE_IO_Block_I2S0_CH2, Soc_Aud_AFE_IO_Block_MODEM_PCM_1_O_CH4, SetI2s0Ch2ToModem1OutCh4},{Soc_Aud_AFE_IO_Block_I2S0_CH2, Soc_Aud_AFE_IO_Block_MODEM_PCM_2_O_CH4, SetI2s0Ch2ToModem2OutCh4},{Soc_Aud_AFE_IO_Block_MEM_DL2, Soc_Aud_AFE_IO_Block_I2S1_DAC, SetDl2ToI2s1Dac},
bool SetDl1ToI2s0(unsigned int ConnectionState)
{SetConnectionState(ConnectionState, Soc_Aud_InterConnectionInput_I05,Soc_Aud_InterConnectionOutput_O00);SetConnectionState(ConnectionState, Soc_Aud_InterConnectionInput_I06,Soc_Aud_InterConnectionOutput_O01);return true;
}

Soc_Aud_InterConnectionInput_I05  Soc_Aud_InterConnectionOutput_O00   Soc_Aud_InterConnectionOutput_I06   Soc_Aud_InterConnectionOutput_O01   这几个值是不是跟下图afe图相似。看到这里应该不难理解了,如果我们需要把platform(afe)里面的接口联通就需要向SetConnectionState填写对应的通道值,假如我想O3 O4 连接I5 I6 需要这样写

SetConnectionState(ConnectionState, Soc_Aud_InterConnectionInput_I05,
            Soc_Aud_InterConnectionOutput_O03);
    SetConnectionState(ConnectionState, Soc_Aud_InterConnectionInput_I06,
            Soc_Aud_InterConnectionOutput_O04);

那SetConnectionState具体是做什么呢?其实就是调用regmap_update_bits函数对寄存器地址操作,这样达到通道的联通还断开。对上传来说就是录音有没有数据,播放音乐有没有声音

2.3 ioctl   SNDRV_PCM_IOCTL_READI_FRAMES

函数调用流程:snd_pcm_capture_ioctl->snd_pcm_capture_ioctl1->snd_pcm_lib_read->snd_pcm_lib_read_transfer->substream->ops->copy->mtk_capture_pcm_copy->mtk_memblk_copy

依据传递的标识符不同,mtk_mem_dlblk_copy还是mtk_mem_ulblk_copy 。这两个函数的功能无非就是:把pcm数据从DMA拷贝到文件中或者把pcm数据拷贝到DMA内存中,供硬件使用。

int mtk_memblk_copy(struct snd_pcm_substream *substream,int channel, snd_pcm_uframes_t pos, void __user *dst, snd_pcm_uframes_t count,struct afe_mem_control_t *pMemControl, enum soc_aud_digital_block mem_blk)
{if (pMemControl == NULL)return 0;switch (mem_blk) {case Soc_Aud_Digital_Block_MEM_DL1:case Soc_Aud_Digital_Block_MEM_DL2:case Soc_Aud_Digital_Block_MEM_DL3:mtk_mem_dlblk_copy(substream, channel, pos, dst, count, pMemControl, mem_blk);break;case Soc_Aud_Digital_Block_MEM_VUL:case Soc_Aud_Digital_Block_MEM_DAI:case Soc_Aud_Digital_Block_MEM_AWB:case Soc_Aud_Digital_Block_MEM_MOD_DAI:case Soc_Aud_Digital_Block_MEM_VUL_DATA2:case Soc_Aud_Digital_Block_MEM_VUL2:mtk_mem_ulblk_copy(substream, channel, pos, dst, count, pMemControl, mem_blk);break;default:pr_err("%s not support", __func__);}return 0;
}

2.4 PCM数据  DMA  硬件 它们的数据传递关系

Linux ALSA声卡驱动之七:录音(Capture) 调用流程相关推荐

  1. Linux ALSA声卡驱动之七:ASoC架构中的Codec

    1.  Codec简介 在移动设备中,Codec的作用可以归结为4种,分别是: 对PCM等信号进行D/A转换,把数字的音频信号转换为模拟信号 对Mic.Linein或者其他输入源的模拟信号进行A/D转 ...

  2. Linux ALSA声卡驱动之八:ASoC架构中的Platform

    1.  Platform驱动在ASoC中的作用 前面几章内容已经说过,ASoC被分为Machine,Platform和Codec三大部件,Platform驱动的主要作用是完成音频数据的管理,最终通过C ...

  3. Linux ALSA声卡驱动之四:Codec 以及Codec_dai

    ALSA声卡驱动: 1.Linux ALSA声卡驱动之一:ALSA架构简介和ASOC架构简介 2.Linux ALSA声卡驱动之二:Platform 3. Linux ALSA声卡驱动之三:Platf ...

  4. Linux ALSA声卡驱动之二:Platform

    ALSA声卡驱动: 1.Linux ALSA声卡驱动之一:ALSA架构简介和ASOC架构简介 2.Linux ALSA声卡驱动之二:Platform 3. Linux ALSA声卡驱动之三:Platf ...

  5. Linux ALSA声卡驱动之五:Machine 以及ALSA声卡的注册

    ALSA声卡驱动: 1.Linux ALSA声卡驱动之一:ALSA架构简介和ASOC架构简介 2.Linux ALSA声卡驱动之二:Platform 3. Linux ALSA声卡驱动之三:Platf ...

  6. Linux ALSA声卡驱动之三:Platform之Cpu_dai

    ALSA声卡驱动: 1.Linux ALSA声卡驱动之一:ALSA架构简介和ASOC架构简介 2.Linux ALSA声卡驱动之二:Platform 3. Linux ALSA声卡驱动之三:Platf ...

  7. Linux ALSA声卡驱动之一:ALSA架构简介和ASOC架构简介

    ALSA声卡驱动: 1.Linux ALSA声卡驱动之一:ALSA架构简介和ASOC架构简介 2.Linux ALSA声卡驱动之二:Platform 3. Linux ALSA声卡驱动之三:Platf ...

  8. linux alsa声卡驱动原理分析- 设备打开过程和数据流程,linux alsa声卡驱动原理分析解析- 设备打开过程跟数据流程资料.ppt...

    linux alsa声卡驱动原理分析解析- 设备打开过程跟数据流程资料 Linux ALSA声卡驱动原理分析 -设备打开过程和数据流程;目 录;目 录;一.导 读;目 录;二.ALSA架构简介;二. ...

  9. Linux ALSA声卡驱动之三:PCM设备的创建

    1. PCM是什么 PCM 是英文Pulse-code modulation的缩写,中文译名是脉冲编码调制.我们知道在现实生活中,人耳听到的声音是模拟信号,PCM就是要把声音从模拟转换成数字信号的一种 ...

最新文章

  1. Happy New Year
  2. [导入]C#好书盘点【月儿原创】
  3. 局域网内Windows允许其他电脑指定IP访问本地mysql-8.0.23数据库
  4. api-ms-win-crt-runtime-l1-1-0.dll和api-ms-win-downlevel-shlwapi-l1-1-0.dll免费下载
  5. Android 中activity中传递数据的方式
  6. DOM全屏Api requestFullscreen
  7. 阿里云释放数据能力 开启大数据元年
  8. css sgc加密,ASP+SGC实现柱状图
  9. oracle的jde系统,国内Oracle JDE用户的福音,首创AWS JDE Dynamic Adapter集成中间件
  10. 校园食堂订餐管理系统企业点餐软件
  11. Spotfire简介
  12. Python+PyQt5构建电影天堂电影搜索工具
  13. 计算机网络技术ui设计,UI设计小白到大神的进阶之路—入门基础篇
  14. 第3章 Kafka API
  15. 向量运算(点积,叉积)
  16. 用C语言将搜狗输入法词库转换成QQ拼音输入法词库
  17. 如何将visio画的图转为eps格式?
  18. Linux攻关之基础模块三 文件命令
  19. 全志H616方案香橙派orangepi zero2的26pin接口 SPI测试
  20. 仿牛客论坛项目全面大总结

热门文章

  1. html5 自定义表格样式,自定义excel表格样式
  2. Python简单实现邂逅在迷宫无限十连自动抽卡并根据金卡数量停止程序
  3. opa847方波放大电路_一种脉宽可调的高压方波发生器及高压方波发生方法
  4. 跳槽一次能涨多少?今天带你见识跳槽的天花板
  5. 常用英语合成词大全,感谢高人收集!(转自iCIBA)
  6. 某程序员工作一年感慨:看到代码就恶心,想转行销售,网友:你觉得人民币恶心吗?
  7. MVC与三层架构之间的关系
  8. 蓝牙开发之 IOS AMS
  9. 前端图片压缩上传(压缩篇)
  10. 搭建简单的淘宝优惠劵网站