android soundrecorder之三 录音流程及数据流向

本文深入探讨Android SoundRecorder的录音过程,从应用层到硬件交互,详细解析数据如何从mic经过codec路由,通过DMA传输并最终保存。文章还提及了在录音开始时的constraint检查,以及在snd_pcm_open后的交互流程,并提供了关键的日志打印,帮助理解录音机制。
摘要由CSDN通过智能技术生成

转载请标注原文地址:http://blog.csdn.net/uranus_wm/article/details/12851111

 

前两篇文章分别介绍了linux alsa结构和android soundrecorder的应用层实现

除了板级的初始过程,其他都是关于类层次关系的一些静态说明

这章主要介绍下录音的动态过程,以及数据是如何一步步获取和保存的

还是先上图:


这个图大致介绍了soundrecord的流程,alsa到kernel部分还有个control模块这里没有画出来

下面一张图里面会提到,实际上control模块还是影响我们代码量主要的模块,主要就是mixer和muxer的配置

HAL层以上前面已经说过了,这里我们关注HAL以下部分:

  1. 应用层点击录音按钮
  2. tinyalsa打开设备文件节点/dev/snd/pcmC0D0c,给出相应的采样通道,采样率,一帧数据大小,总buffer大小等
  3. tinyalsa打开设备文件节点/dev/snd/controlC0,设置codec内部相关寄存器,选择输入输出device,配置内部route,即所谓dapm
  4. tinyalsa通过pcmC0D0c触发录音开始,dai_link上时钟开启,dapm建立后,从main_mic接收的pcm信号就会通过codec内部route发送到AIFADCDAT线上cpu exynos4412这边AIFDACDAT接收pcm信号数据后,通过移位器将数据保存到I2S的RX_FIFO
  5. RX_FIFO收到数据,触发DMA控制器开始拷贝数据,DMA(pl330)将RX_FIFO中的数据拷贝到DMA专有的内存缓冲,本例中snd_dma_buffer起始物理地址addr=0x6036_0000
  6. tinyalsa通过ioctl向设备节点/dev/snd/pcmC0D0c发起读数据请求,snd_pcm会将dma缓冲buffer中的数据拷贝到tinyalsa传入的用户buffer中

下面这张顺序图详细记录了soundrecorder开启录音之后alsa的交互流程:

 

这里我要提到的是在snd_pcm_open之后,有很大一段代码是做constraint检查

最开始我在调试的时候这个constraint检查一直不过,也就是上面的1.1节点,然后后面的过程没法进行下去

snd_pcm_open()时增加了一大推rule;这些rule的作用其实就是检查tinyalsa指定的config对于当前dai_link是否支持

int snd_pcm_hw_constraints_init(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints;
	int k, err;

	for (k = SNDRV_PCM_HW_PARAM_FIRST_MASK; k <= SNDRV_PCM_HW_PARAM_LAST_MASK; k++) {
		snd_mask_any(constrs_mask(constrs, k));
	}

	for (k = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++) {
		snd_interval_any(constrs_interval(constrs, k));
	}

	snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_CHANNELS));
	snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_BUFFER_SIZE));
	snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_BUFFER_BYTES));
	snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_SAMPLE_BITS));
	snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_FRAME_BITS));

	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT,
				   snd_pcm_hw_rule_format, NULL,
				   SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1);
	if (err < 0)
		return err;
	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, 
				  snd_pcm_hw_rule_sample_bits, NULL,
				  SNDRV_PCM_HW_PARAM_FORMAT, 
				  SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1);
	if (err < 0)
		return err;
	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, 
				  snd_pcm_hw_rule_div, NULL,
				  SNDRV_PCM_HW_PARAM_FRAME_BITS, SNDRV_PCM_HW_PARAM_CHANNELS, -1);
	if (err < 0)
		return err;
	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FRAME_BITS, 
				  snd_pcm_hw_rule_mul, NULL,
				  SNDRV_PCM_HW_PARAM_SAMPLE_BITS, SNDRV_PCM_HW_PARAM_CHANNELS, -1);
.......
}

其过程是这样的:

首先snd_pcm_hardware会声明自己支持的通道数,格式等,buffer_bytes_max是buffer总大小,其他值我也没有详细研究

基本按照linux原生拷贝,没有修改

static const struct snd_pcm_hardware dma_hardware = {
	.info			= SNDRV_PCM_INFO_INTERLEAVED |
				    SNDRV_PCM_INFO_BLOCK_TRANSFER |
				    SNDRV_PCM_INFO_MMAP |
				    SNDRV_PCM_INFO_MMAP_VALID |
				    SNDRV_PCM_INFO_PAUSE |
				    SNDRV_PCM_INFO_RESUME,
	.formats		= SNDRV_PCM_FMTBIT_S16_LE |
				    SNDRV_PCM_FMTBIT_U16_LE |
				    SNDRV_PCM_FMTBIT_U8 |
				    SNDRV_PCM_FMTBIT_S8,
	.channels_min		= 1,
	.channels_max		= 2,
	.buffer_bytes_max	= 128*1024,
	.period_bytes_min	= PAGE_SIZE,
	.period_bytes_max	= PAGE_SIZE*2,
	.periods_min		= 2,
	.periods_max		= 128,
	.fifo_size		= 32,
};

codec_dai这边声明如下:

static struct snd_soc_dai_driver wm8994_dai[] = {
	{
		.name = "wm8994-aif1",
		.id = 1,
		.playback = {
			.stream_name = "AIF1 Playback",
			.channels_min = 1,
			.channels_max = 2,
			.rates = WM8994_RATES,
			.formats = WM8994_FORMATS,
		},
		.capture = {
			.stream_name = "AIF1 Capture",
			.channels_min = 1,
			.channels_max = 2,
			.rates = WM8994_RATES,
			.formats = WM8994_FORMATS,
		 },
		.ops = &wm8994_aif1_dai_ops,
	},
	};

cpu_dai这边声明如下:


                
  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,下面是一个简单的录音APP实现步骤: 1. 在 Android Studio 中创建一个空白项目,并在布局文件中添加一个按钮和一个文本框用于显示录音时长。 2. 添加权限 在 AndroidManifest.xml 文件中添加以下权限: ``` <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> ``` 3. 创建录音机类 创建一个名为 `SoundRecorder` 的 Java 类,该类用于管理录音的开始、停止、暂停和继续等操作。 ```java public class SoundRecorder { private MediaRecorder mRecorder; private String mFileName; private long mStartTime; public SoundRecorder(String fileName) { mFileName = fileName; mRecorder = new MediaRecorder(); mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); mRecorder.setOutputFile(mFileName); } public void start() { try { mRecorder.prepare(); mRecorder.start(); mStartTime = System.currentTimeMillis(); } catch (IOException e) { Log.e("SoundRecorder", "prepare() failed"); } } public void pause() { mRecorder.pause(); } public void resume() { mRecorder.resume(); } public void stop() { mRecorder.stop(); mRecorder.release(); mRecorder = null; } public int getDuration() { return (int) (System.currentTimeMillis() - mStartTime) / 1000; } } ``` 4. 在 Activity 中调用录音机类 在 Activity 中创建一个 `SoundRecorder` 实例,并在按钮的点击事件中调用录音机的开始、停止、暂停和继续等方法,并在文本框中显示录音时长。 ```java public class MainActivity extends AppCompatActivity { private Button mRecordButton; private TextView mDurationTextView; private SoundRecorder mRecorder; private boolean mIsRecording = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mRecordButton = findViewById(R.id.record_button); mDurationTextView = findViewById(R.id.duration_text_view); mRecordButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mIsRecording) { mRecorder.stop(); mIsRecording = false; mRecordButton.setText("Record"); } else { mRecorder = new SoundRecorder(getExternalCacheDir().getAbsolutePath() + "/recording.3gp"); mRecorder.start(); mIsRecording = true; mRecordButton.setText("Stop"); new CountDownTimer(Long.MAX_VALUE, 1000) { @Override public void onTick(long millisUntilFinished) { mDurationTextView.setText(String.format("%02d:%02d", mRecorder.getDuration() / 60, mRecorder.getDuration() % 60)); } @Override public void onFinish() { } }.start(); } } }); } } ``` 这样你就可以在 Android Studio 中创建一个简单的录音APP了。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值