08.音频系统:第008课_项目实战2_多APP同时录音:第002节_录音框架及代码流程

在上小节中,简单的使用C++编写了一个录音程序,并且尝试去运行两个程序去录音,然后发现第二个程序不能录音。不能运行。如果我们想去解决这个问题,我们需要深入的去了解一下android系统中录音的框架,看一下这个框架是否合理,能否进行改进。

先来回顾一下,播放声音时的音频框架:
在这里插入图片描述
假设系统中有两个声卡,每个声卡有输入,输出功能,对于他的输出功能,我们称为输出通道output,AudioFlinger中有一个PlaybackThread与他对应。应用程序想要播放声音的时候,创建一个AudioTrack,同时指定声音的类型,AudioFlinger会根据这个声音的类型,找到对应的声卡,再找到对应的PlaybackThread播放线程,然后在PlaybackThread中创建一个Track,这个Track与AudioTrack对应,他们之间通过共享内存传递数据,PlaybackThread播放声音的时候,会查询内部的一个或者多个Track,取出里面的数据混合在一起发送给声卡。
对个APP对应AudioFlinger中的一个PlaybackThread,APP需要播放声音时,传递给PlaybackThread,PlaybackThread混合之后进行播放。这样就不存在多个APP竞争一个输出通道的问题。以上讲解的是android系统中播放声音的框架。

那么我们能不能模仿播放的框架,完成一个录音的理想矿建,图示如下:
在这里插入图片描述
对于每个声卡都有录制声卡的inpout通道,如果我们在AudioFlinger对每个通道创建一个RecordThread。当APP需要录制声音的时候,其创建AduioRecord指定录音的来源,然后找到指定的声卡,这样就找了RecordThread线程,在RecordThread中创建track,其与APP中的AduioRecord一一对应。即RecordThread从硬件获取数据发送给每个track,track同步到AduioRecord(通过共享内存)。这样就能实现多APP同时录音的功能。这里也是多个APP对应一个线程,这样就不存在多个APP进程一个线程,或者说一个output的问题。

下面我们在来看看现实的框架,如下图:
在这里插入图片描述
如上所示,当应用程序录音的时候,会创建一个AduioRecord,其会指定声音来源,根据声音来源找到声卡,然后创建一个RecordThread,如果第二个APP想要录制声音,其也会在AudioFlinger中创建一个RecordThread,从图中我们可以知道一个AduioRecord对应一个RecordThread,这样就就会导致一个声卡对应多个RecordThread,这些线程会竞争使用声卡的问题,两个线程之间需要互斥的访问声卡,进行录音。

为了解决这个问题,android系统做了一个简单粗暴的方法,禁止多个APP同时录音,同一时间,只能存在一个APP进行录音,下面是文字性的一些总结


a. APP创建、设置AudioRecord, 
   指定了声音来源: inputSource, 比如: AUDIO_SOURCE_MIC
   还指定了采样率、通道数、格式等参数
b. AudioPolicyManager根据inputSource等参数确定录音设备: device
c. AudioFlinger创建一个RecordThread, 以后该线程将从上述device读取声音数据
d. 在RecordThread内部为APP的AudioRecord创建一个对应的RecordTrack
   APP的AudioRecord 与 RecordThread内部的RecordTrack 通过共享内存传递数据
e. RecordThread从HAL中得到数据, 再通过内部的RecordTrack把数据传给APP的AudioRecord

注意: 
在原生代码中,
APP的一个AudioRecord会导致创建一个RecordThread,
在一个device上有可能存在多个RecordThread,
任意时刻只能有一个RecordThread在运行,
所以只能有一个APP在录音,不能多个APP同时录音

下面是录音框架的流程图:
在这里插入图片描述
注意看流程中的红色字体标记的函数,其为重要函数,如下:
在这里插入图片描述
通过以上的流程,我们直接以知道,只要应用程序创建了AudioRecord,并且制定了来源,那么就会有一个对应的RecordThread线程被创建。
在这里插入图片描述
结合源码,我们可以知道图上的AudioFlinger中,存在一个AudioIputDescriptor数组,以及一个mInputs,其每个mInput与一个线程意义对应,mInput中存在一个索引值,根据索引值,可以找到对应的AudioIputDescriptor。当然,也能根据这个索引值找到RecordThread。

在上小节中,错误是发生在第二个程序的if(pAudioRecord->start()!= android::NO_ERROR)处出现了问题,pAudioRecord->start()的调用流程,在时序图中已经给出,其最终会调用到:
在这里插入图片描述
其上流程,就是导致错误的原因,如果我们只是简单的吧错误的函数去掉,这样可以吗?
是不行的,其会到导致多个线程,同时对声卡进行访问,假设有两个应用程序同时录音,则APP1录制部分声音,APP2录制部分声音。他们录音时得到的声音都不完全。每个APP只能得到部分声音数据,则并不是我们想要的结果,下小节我们讲解如何修改代码。

我们继续分析一下框架,假设上面的pAudioRecord->start()执行成功,应用程序开始读取数据,其会调用read函数:
在这里插入图片描述
没有数据的时候,其会进入休眠等待数据,提供数据的为thraadLoop线程:
在这里插入图片描述
其会从硬件上读取数据。在Thread.cpp中,找到函数:

bool AudioFlinger::RecordThread::threadLoop()
      // otherwise use the HAL / AudioStreamIn directly
     } else {
     	/*使用HAL层读取数据*/
     	ssize_t bytesRead = mInput->stream->read(mInput->stream,(uint8_t*)mRsmpInBuffer + rear * mFrameSize, mBufferSize);
     		/*保存数据*/
     		framesRead = bytesRead;
      // loop over each active track,得到每个active track
      for (size_t i = 0; i < size; i++) {

在读取带数据之后,会分发给每一个活跃的Track,即发送给一个或者多个APP。可以知道AudioFlinger层是支持多APP录音的,只是在AudioPolicyManager在禁止了。下小节我们修改AudioPolicyManager的代码。

下面是该小节的总结:
录音框架及代码流程

a. APP创建、设置AudioRecord, 
   指定了声音来源: inputSource, 比如: AUDIO_SOURCE_MIC
   还指定了采样率、通道数、格式等参数
b. AudioPolicyManager根据inputSource等参数确定录音设备: device
c. AudioFlinger创建一个RecordThread, 以后该线程将从上述device读取声音数据
d. 在RecordThread内部为APP的AudioRecord创建一个对应的RecordTrack
   APP的AudioRecord 与 RecordThread内部的RecordTrack 通过共享内存传递数据
e. RecordThread从HAL中得到数据, 再通过内部的RecordTrack把数据传给APP的AudioRecord

注意: 
在原生代码中,
APP的一个AudioRecord会导致创建一个RecordThread,
在一个device上有可能存在多个RecordThread,
任意时刻只能有一个RecordThread在运行,
所以只能有一个APP在录音,不能多个APP同时录音

猜你喜欢

转载自blog.csdn.net/weixin_43013761/article/details/89811367