当前位置: 首页 > wzjs >正文

网站后台的功能口碑营销的前提及好处有哪些

网站后台的功能,口碑营销的前提及好处有哪些,网站开发 js,在线网页客服1. 前言 本文,我们将以回放(Playback,播放音频)为例,讲解PCM Data是如何从用户空间到内核空间,最后传递到Codec硬件。 在 ASoC音频框架简介中,我们给出了回放(Playback)…

1. 前言

本文,我们将以回放(Playback,播放音频)为例,讲解PCM Data是如何从用户空间到内核空间,最后传递到Codec硬件。
在 ASoC音频框架简介中,我们给出了回放(Playback)PCM数据流示意图。:

在这里插入图片描述

对于Linux来说,由于分为 user space 和kernel space,而且两者之间数据不能随便互相访问。因此用户如果播放音频,则需要调用copy_from_user()将用户数据从user space拷贝到kernel space (DMA Buffer)。
DMA 负责将DMA Buffer中的音频数据搬运到I2S TX FIFO。(soc内存到设备内存)
通过I2S总线,将音频数据传送到Codec。
Codec内部经过DAC转换,将模拟信号传到扬声器SPK(头戴式耳机、耳塞式耳机)。如果我们跳出所谓的ASOC框架。任何一个框架想要将音频数据从应用播出,都需要经历上面的步骤。
用户态>>内核态>>设备内存>>codec。
下面基于源码看看ASOC的Data Flow是如何实现上面的流程。

2. PCM Data Flow

Tinyalsa源码文件:
./external/tinyalsa/pcm.c
./external/kernel-headers/original/uapi/sound/asound.h

User Space

用户空间应用程序使用的是 tinyalsa提供的接口write PCM Data,即播放音频文件。
Write PCM逻辑设备是通过 ioctl() 函数完成的,即应用程序将需要播放的音频数据通过pcm_write() --> ioctl() 传递到内核。

// ./external/kernel-headers/original/uapi/sound/asound.h, line 448
struct snd_xferi {snd_pcm_sframes_t result;void __user *buf;snd_pcm_uframes_t frames;
};
// ./external/tinyalsa/pcm.c, line 483
int pcm_write(struct pcm *pcm, const void *data, unsigned int count)
{struct snd_xferi x;......x.buf = (void*)data;x.frames = count / (pcm->config.channels *pcm_format_to_bits(pcm->config.format) / 8);......ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x);......
}

音频数据中的几个重要概念:
Format:样本长度(采样精度 or 采样深度),常见的有 8 位和 16 位;
Channel:声道数,常见的有单声道 mono 和立体声stereo;
Frame:帧,构成一个完整的声音单元,Frame = Format * Channel;
Rate:又称 sample rate:采样率,即每秒的采样次数,针对帧而言;
Period size:周期大小,每次硬件中断处理音频数据的帧数,对于音频设备的数据读写,推荐以此为每次传输的数据量大小;
Buffer size:数据缓冲区大小,这里指runtime 的 buffer size,而不是结构体 snd_pcm_hardware 中定义的buffer_bytes_max;一般来说 buffer_size = period_size * period_count,period_count 相当于处理完一个 buffer 数据所需的硬件中断次数。

为了通过系统调用ioctl()传递音频数据,定义了struct snd_xferi x,x.buf指向本次要播放的音频数据,x.frames表示本次音频数据的帧数(frame count)。

Kernel Space

通过系统调用ioctl()传递数据到内核,在内核空间会调用PCM逻辑设备对应的snd_pcm_f_ops[0].unlocked_ioctl()。

// ./kernel-3.10/sound/core/pcm_native.c, line 3481
const struct file_operations snd_pcm_f_ops[2] = {{.owner =		THIS_MODULE,.write =		snd_pcm_write,.......unlocked_ioctl =	snd_pcm_playback_ioctl,......},{.owner =		THIS_MODULE,.read =			snd_pcm_read,.......unlocked_ioctl =	snd_pcm_capture_ioctl,......}
};

snd_pcm_playback_ioctl() 调用snd_pcm_playback_ioctl1()。

// ./kernel-3.10/sound/core/pcm_native.c, line 2784
static long snd_pcm_playback_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{struct snd_pcm_file *pcm_file;pcm_file = file->private_data;......return snd_pcm_playback_ioctl1(file, pcm_file->substream, cmd, (void __user *)arg);
}

snd_pcm_playback_ioctl1()函数:
a. 定义struct snd_xferi xferi,获取用户空间传来的arg;
b. 调用put_user() 清除snd_xferi.result状态;
c. 调用copy_from_user()将用户空间的arg拷贝到内核空间,即拷贝音频数据在用户空间的指针buf和音频数据帧数frames;
d. 调用snd_pcm_lib_write();
e. 调用put_user() 回填write结果_xferi->result。

// ./kernel-3.10/sound/core/pcm_native.c, line 2624
static int snd_pcm_playback_ioctl1(struct file *file, struct snd_pcm_substream *substream, unsigned int cmd, void __user *arg)
{......switch (cmd) {case SNDRV_PCM_IOCTL_WRITEI_FRAMES:{struct snd_xferi xferi;struct snd_xferi __user *_xferi = arg;struct snd_pcm_runtime *runtime = substream->runtime;snd_pcm_sframes_t result;if (runtime->status->state == SNDRV_PCM_STATE_OPEN)return -EBADFD;if (put_user(0, &_xferi->result))return -EFAULT;if (copy_from_user(&xferi, _xferi, sizeof(xferi)))return -EFAULT;result = snd_pcm_lib_write(substream, xferi.buf, xferi.frames);__put_user(result, &_xferi->result);return result < 0 ? result : 0;}......
}

snd_pcm_lib_write()做一些参数检查后调用snd_pcm_lib_write1()。注意调用snd_pcm_lib_write1()时传入的最后一个参数snd_pcm_lib_write_transfer,该函数完成音频数据从 kernel space 到 DMA Buffer 的传输。

// ./kernel-3.10/sound/core/pcm_lib.c, line 2101
snd_pcm_sframes_t snd_pcm_lib_write(struct snd_pcm_substream *substream, const void __user *buf, snd_pcm_uframes_t size)
{struct snd_pcm_runtime *runtime;int nonblock;......nonblock = !!(substream->f_flags & O_NONBLOCK);......return snd_pcm_lib_write1(substream, (unsigned long)buf, size, nonblock, snd_pcm_lib_write_transfer);
}

如下snd_pcm_lib_write1()代码段中,第40行调用transfer(),即调用snd_pcm_lib_write_transfer(),将音频数据从 kernel space 拷贝到 DMA Buffer。然后,在第49行,调用snd_pcm_start()启动DMA传输,将音频数据从 DMA Buffer 拷贝到 I2S TX FIFO。

// ./kernel-3.10/sound/core/pcm_lib.c, line 1985
static snd_pcm_sframes_t snd_pcm_lib_write1(struct snd_pcm_substream *substream, unsigned long data,snd_pcm_uframes_t size, int nonblock, transfer_f transfer)
{struct snd_pcm_runtime *runtime = substream->runtime;snd_pcm_uframes_t xfer = 0;snd_pcm_uframes_t offset = 0;snd_pcm_uframes_t avail;int err = 0;if (size == 0)return 0;......runtime->twake = runtime->control->avail_min ? : 1;if (runtime->status->state == SNDRV_PCM_STATE_RUNNING)snd_pcm_update_hw_ptr(substream);avail = snd_pcm_playback_avail(runtime);while (size > 0) {snd_pcm_uframes_t frames, appl_ptr, appl_ofs;snd_pcm_uframes_t cont;if (!avail) {if (nonblock) {err = -EAGAIN;goto _end_unlock;}runtime->twake = min_t(snd_pcm_uframes_t, size,runtime->control->avail_min ? : 1);err = wait_for_avail(substream, &avail);if (err < 0)goto _end_unlock;}frames = size > avail ? avail : size;cont = runtime->buffer_size - runtime->control->appl_ptr % runtime->buffer_size;if (frames > cont)frames = cont;......appl_ptr = runtime->control->appl_ptr;appl_ofs = appl_ptr % runtime->buffer_size;snd_pcm_stream_unlock_irq(substream);err = transfer(substream, appl_ofs, data, offset, frames);  //将音频数据 拷贝到 DMA Buffersnd_pcm_stream_lock_irq(substream);......offset += frames;size -= frames;xfer += frames;avail -= frames;if (runtime->status->state == SNDRV_PCM_STATE_PREPARED &&snd_pcm_playback_hw_avail(runtime) >= (snd_pcm_sframes_t)runtime->start_threshold) {err = snd_pcm_start(substream);  //启动DMA传输,将音频数据从 DMA Buffer 拷贝到 I2S TX FIFOif (err < 0)goto _end_unlock;}}......
}

snd_pcm_lib_write_transfer()函数实现:
a. 如果有设定过substream->ops->copy回调函数,则执行substream->ops->copy()将音频数据从 kernel space 拷贝到 DMA Buffer。芯片工司可以通过设定这个回调函数实现定制化的数据传输。将用户态的数据送完自己需要的空间。
b. 如果没有设定substream->ops->copy回调函数,则直接调用copy_from_user()将音频数据从 kernel space 拷贝到 DMA Buffer。

// ./kernel-3.10/sound/core/pcm_lib.c, line 1962
static int snd_pcm_lib_write_transfer(struct snd_pcm_substream *substream, unsigned int hwoff,unsigned long data, unsigned int off, snd_pcm_uframes_t frames)
{struct snd_pcm_runtime *runtime = substream->runtime;int err;char __user *buf = (char __user *) data + frames_to_bytes(runtime, off);if (substream->ops->copy) {if ((err = substream->ops->copy(substream, -1, hwoff, buf, frames)) < 0)return err;} else {char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, hwoff);if (copy_from_user(hwbuf, buf, frames_to_bytes(runtime, frames)))return -EFAULT;}return 0;
}

注:本例中,substream->ops->copy回调函数是在soc_new_pcm()中设置的。在soc_new_pcm()中,如果有设定platform->driver->ops (即PCM DMA驱动操作函数集),则PCM逻辑设备的某些操作函数将会被platform->driver->ops中覆盖掉,如下代码段第26行。

/ ./kernel-3.10/sound/soc/soc-pcm.c, line 2005
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{....../* ASoC PCM operations */if (rtd->dai_link->dynamic) {rtd->ops.open		= dpcm_fe_dai_open;rtd->ops.hw_params	= dpcm_fe_dai_hw_params;rtd->ops.prepare	= dpcm_fe_dai_prepare;rtd->ops.trigger	= dpcm_fe_dai_trigger;rtd->ops.hw_free	= dpcm_fe_dai_hw_free;rtd->ops.close		= dpcm_fe_dai_close;rtd->ops.pointer	= soc_pcm_pointer;rtd->ops.ioctl		= soc_pcm_ioctl;} else {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;}if (platform->driver->ops) {rtd->ops.ack		= platform->driver->ops->ack;rtd->ops.copy		= platform->driver->ops->copy;rtd->ops.silence	= platform->driver->ops->silence;rtd->ops.page		= platform->driver->ops->page;rtd->ops.mmap		= platform->driver->ops->mmap;}......
}

现在看一下snd_pcm_start()如何启动DMA传输的。
snd_pcm_start()直接调用了snd_pcm_action()。此处要注意snd_pcm_action()的第一个参数snd_pcm_action_start。snd_pcm_action()在多个地方会被调用,要注意其被调用时的第一个参数是什么。snd_pcm_action()函数的功能就是执行入参里面的函数指针。

// ./kernel-3.10/sound/core/pcm_native.c, line 891
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
};
// ./kernel-3.10/sound/core/pcm_native.c, line 904
int snd_pcm_start(struct snd_pcm_substream *substream)
{return snd_pcm_action(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING);
}

snd_pcm_action()中会调用snd_pcm_action_group()或snd_pcm_action_single()。为简化讲解,我们关注snd_pcm_action_single()。snd_pcm_action_single()函数非常简单,就是执行struct action_ops *ops指向的回调函数集,本例中为上文提到的snd_pcm_action_start。

// ./kernel-3.10/sound/core/pcm_native.c, line 785
static int snd_pcm_action(struct action_ops *ops, struct snd_pcm_substream *substream, int state)
{int res;if (snd_pcm_stream_linked(substream)) {if (!spin_trylock(&substream->group->lock)) {spin_unlock(&substream->self_group.lock);spin_lock(&substream->group->lock);spin_lock(&substream->self_group.lock);}res = snd_pcm_action_group(ops, substream, state, 1);spin_unlock(&substream->group->lock);} else {res = snd_pcm_action_single(ops, substream, state);}return res;
}
// ./kernel-3.10/sound/core/pcm_native.c, line 765
static int snd_pcm_action_single(struct action_ops *ops,struct snd_pcm_substream *substream,int state)
{int res;res = ops->pre_action(substream, state);if (res < 0)return res;res = ops->do_action(substream, state);if (res == 0)ops->post_action(substream, state);else if (ops->undo_action)ops->undo_action(substream, state);return res;
}

我们重点看一下ops->do_action(),即snd_pcm_do_start()。在前面soc_new_pcm()代码段中,我们看到substream->ops->trigger()被设定为soc_pcm_trigger(),该函数依次调用codec_dai driver的trigger函数、pcm_dma的trigger函数、cpu_dai driver的trigger函数。

// ./kernel-3.10/sound/core/pcm_native.c, line 862
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);
}
// ./kernel-3.10/sound/core/soc-pcm.c, line 609
static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{struct snd_soc_pcm_runtime *rtd = substream->private_data;struct snd_soc_platform *platform = rtd->platform;struct snd_soc_dai *cpu_dai = rtd->cpu_dai;struct snd_soc_dai *codec_dai = rtd->codec_dai;int ret;if (codec_dai->driver->ops->trigger) {ret = codec_dai->driver->ops->trigger(substream, cmd, codec_dai);  //调用codec_dai driver的trigger函数 (可选的)if (ret < 0)return ret;}if (platform->driver->ops && platform->driver->ops->trigger) {ret = platform->driver->ops->trigger(substream, cmd);  //调用pcm_dma的trigger函数if (ret < 0)return ret;}if (cpu_dai->driver->ops->trigger) {ret = cpu_dai->driver->ops->trigger(substream, cmd, cpu_dai);  //调用cpu_dai driver的trigger函数 (可选的)if (ret < 0)return ret;}return 0;
}

下面介绍trigger函数的内容不同的SoC平台会有差异,本例基于MTK平台的源码分析。
本例中,codec_dai driver的trigger函数、pcm_dma的trigger函数、cpu_dai driver的trigger函数分别如下:

codec_dai driver的trigger函数:mt6323_codec_trigger(),虽然该函数有定义,但是该函数没有做任何事情。

// ./kernel-3.10/sound/soc/mediatek/mt_soc_audio_v3/mt_soc_codec_63xx.c, line 1070
static struct snd_soc_dai_driver mtk_6331_dai_codecs[] =
{{.name = MT_SOC_CODEC_TXDAI_NAME,.ops = &mt6323_aif1_dai_ops,......},......
}
// ./kernel-3.10/sound/soc/mediatek/mt_soc_audio_v3/mt_soc_codec_63xx.c, line 1063
static const struct snd_soc_dai_ops mt6323_aif1_dai_ops =
{.startup    = mt63xx_codec_startup,.prepare   = mt63xx_codec_prepare,.trigger     = mt6323_codec_trigger,
};
// ./kernel-3.10/sound/soc/mediatek/mt_soc_audio_v3/mt_soc_codec_63xx.c, line 1048
static int mt6323_codec_trigger(struct snd_pcm_substream *substream , int command , struct snd_soc_dai *Daiport)
{switch (command){case SNDRV_PCM_TRIGGER_START:case SNDRV_PCM_TRIGGER_RESUME:case SNDRV_PCM_TRIGGER_STOP:case SNDRV_PCM_TRIGGER_SUSPEND:break;}return 0;
}

pcm_dma的trigger函数:mtk_pcm_I2S0dl1_trigger()

// ./kernel-3.10/sound/soc/mediatek/mt_soc_audio_v3/mt_soc_pcm_dl1_i2s0Dl1.c, line 730
static struct snd_soc_platform_driver mtk_I2S0dl1_soc_platform =
{.ops        = &mtk_I2S0dl1_ops,.pcm_new    = mtk_asoc_pcm_I2S0dl1_new,.probe      = mtk_afe_I2S0dl1_probe,
};
// ./kernel-3.10/sound/soc/mediatek/mt_soc_audio_v3/mt_soc_pcm_dl1_i2s0Dl1.c, line 715
static struct snd_pcm_ops mtk_I2S0dl1_ops =
{.......trigger =  mtk_pcm_I2S0dl1_trigger,.pointer =  mtk_pcm_I2S0dl1_pointer,.copy =     mtk_pcm_I2S0dl1_copy,......
};
// ./kernel-3.10/sound/soc/mediatek/mt_soc_audio_v3/mt_soc_pcm_dl1_i2s0Dl1.c, line 529
static int mtk_pcm_I2S0dl1_trigger(struct snd_pcm_substream *substream, int cmd)
{//printk("mtk_pcm_I2S0dl1_trigger cmd = %d\n", cmd);switch (cmd){case SNDRV_PCM_TRIGGER_START:case SNDRV_PCM_TRIGGER_RESUME:return mtk_pcm_I2S0dl1_start(substream);  // 启动 dma 传输case SNDRV_PCM_TRIGGER_STOP:case SNDRV_PCM_TRIGGER_SUSPEND:return mtk_pcm_I2S0dl1_stop(substream);  // 停止 dma 传输}return -EINVAL;
}
// ./kernel-3.10/sound/soc/mediatek/mt_soc_audio_v3/mt_soc_pcm_dl1_i2s0Dl1.c, line 493
static int mtk_pcm_I2S0dl1_start(struct snd_pcm_substream *substream)
{struct snd_pcm_runtime *runtime = substream->runtime;printk("%s\n", __func__);// here start digital partSetConnection(Soc_Aud_InterCon_Connection, Soc_Aud_InterConnectionInput_I05, Soc_Aud_InterConnectionOutput_O00);SetConnection(Soc_Aud_InterCon_Connection, Soc_Aud_InterConnectionInput_I06, Soc_Aud_InterConnectionOutput_O01);SetConnection(Soc_Aud_InterCon_Connection, Soc_Aud_InterConnectionInput_I05, Soc_Aud_InterConnectionOutput_O03);SetConnection(Soc_Aud_InterCon_Connection, Soc_Aud_InterConnectionInput_I06, Soc_Aud_InterConnectionOutput_O04);SetIrqEnable(Soc_Aud_IRQ_MCU_MODE_IRQ1_MCU_MODE, true);SetSampleRate(Soc_Aud_Digital_Block_MEM_DL1, runtime->rate);SetChannels(Soc_Aud_Digital_Block_MEM_DL1, runtime->channels);SetMemoryPathEnable(Soc_Aud_Digital_Block_MEM_DL1, true);EnableAfe(true);......return 0;
}

cpu_dai driver的trigger函数:本例中没有实现该函数

// ./kernel-3.10/sound/soc/mediatek/mt_soc_audio_v3/mt_soc_dai_stub.c, line 98
static struct snd_soc_dai_driver mtk_dai_stub_dai[] =
{{.......name = MT_SOC_DL1DAI_NAME,.ops = &mtk_dai_stub_ops,},......
}
// ./kernel-3.10/sound/soc/mediatek/mt_soc_audio_v3/mt_soc_dai_stub.c, line 93
static struct snd_soc_dai_ops mtk_dai_stub_ops =
{.startup    = multimedia_startup,
};

3. 总结

回放(Playback)PCM数据流示意图:

在这里插入图片描述

最后简单总结一下PCM write时的数据传递:
i. 应用程序调用tinyalsa提供的接口pcm_write()–>ioctl()将需要回放的音频数据指针和帧数传递给内核。
ii. 内核在snd_pcm_lib_write_transfer()函数中使用copy_from_user()将音频数据从user space拷贝到kernel space,即从应用程序的buffer拷贝到DMA buffer。
iii. 内核在snd_pcm_start()中启动DMA传输,将音频数据从DMA buffer拷贝到I2S TX FIFO。(实质上是通过pcm_dma的trigger函数来做的。)

ASOC框架的写数据流程核心点总结一下:

1.DMAbuffer的分配和指针更新
2.triger后按照频率对DMAbuffer里面的数据进行搬移
3.这实际上就是设计了个异步数据传输的步骤。PCM负责将数据拷贝到DMAbuffer就可以返回。由DMA负责将数据送到I2StxFIFO或者其他硬件内存。

数据如果在一个进程里面就只需要进行指针的传递,如果跨越进程就需要拷贝动作,先拷贝到进程间共享内存区,再从公共区拷贝到进程独立内存区。
ASOC-core定义好PCM逻辑设备的流程。platform()驱动可以根据自己的需要去重载不同的函数。
我理解substream就是一路实际运行的数据流。
pcm_runtime。包含了PCM的运行时的各类信息。而substream代表了具体的一路数据的运行时实体。

http://www.dtcms.com/wzjs/387041.html

相关文章:

  • wordpress无缝截图上海企业网站seo
  • 幼儿网站源代码小说网站排名前十
  • 在阿里云备案网站通过网站没有友情链接
  • 泾川建设路网站怎么在网上做广告宣传
  • 深圳网站建设开发哪家好无经验能做sem专员
  • 淘宝这种网站怎么做的b站推广2023
  • 网站建设风险管理计划书网络营销成功的案例分析
  • 印度喜欢用什么框架做外贸网站推广代理平台登录
  • 机关网站制度建设嘉兴网站建设方案优化
  • 云空间的网站如何做济南做网站推广哪家好
  • 外国人爱做视频网站手游推广加盟
  • 包年seo和整站优化网页设计学生作业模板
  • 教育主管部门建设的专题资源网站是网站制作流程图
  • 手机视频网站建设广东今天新闻最新消息
  • 南山网站公司定韩国热搜榜
  • 网页游戏大全首选优化网站制作方法大全
  • flashfxp上传到 wwwroot后网站还是打不开上海牛巨微seo
  • 党政机关网站建设整站优化快速排名
  • 0797 网站制作百度招聘官网
  • 河北农业建设信息网站线上平台推广方式
  • 外贸网站的域名今日热搜头条
  • 哪个网站做课件能赚钱成人培训班有哪些课程
  • 网站互点都是怎么做的排名前十的小说
  • 中山技术支持中山网站建设今日国内新闻头条大事
  • 专业软件网站建设自己做seo网站推广
  • wordpress 微语工具站seo
  • 云南省火电建设公司网站qq营销软件
  • 有什么网站可以做简历seo管理软件
  • 南京 网站建设模板价格网址提交
  • 百度做网站联系电话广州seo团队