Android AudioRecord 流程分析_android record_audio-程序员宅基地

技术标签: android  

Android 4.4KitKat AudioRecord 流程分析

  Android是架构分为三层:

  • 底层      Linux Kernel
  • 中间层  主要由C++实现 (Android 60%源码都是C++实现)
  • 应用层  主要由JAVA开发的应用程序

  应用程序执行过程大致如下: JAVA应用程序产生操作(播放音乐或停止),然后通过JNI调用进入中间层执行C++代码,中间层处理后可能需要硬件产生动作的,会继续将操作传到Linux Kernel,Kernel ,不需要硬件产生操作的可能在中间层做一些处理就直接返回。需要硬件产生操作的动作则需通过Kernel调用相关的驱动执行动作或一些处理。

  在这里大家需要明白一点:Android仅使用了Linux的Kernel ,即便是一些常用的库例如pthread等,都是Android自已用C/C++/汇编重写实现的。

  因为在音频通路建立过程中,涉及Android IPC通信及系统服务管理,所以下面就这两点先做个简述:

  ①Android IPC通信采用的是Client/Server结构,Client 客户端 (AudioRecord)通过接口(IAudioRecord)调用Server 服务器对象(AudioFlinger及AudioFlinger::RecordThread等)的方法,并获取执行结果。AudioRecord.cpp 主要是对类AudioRecord的实现,AudioFlinger.cpp主要是对类AudioFlinger的实现。在底层音频通信中,可以将AudioRecord作为Android IPC通信的客户端,而将AudioFlinger作为服务器端。AudioRecord获取服务器端接口(mAudioRecord)后就可以像执行自已的方法一样调用服务器端方法(AudioFlinger)。

  ②Android 启动时会创建一个服务管理进程。Android系统中所有的服务都必需注册添加到该进程中,可以通过sp<IServiceManager> sm=defaultServiceManager()获取管理进程接口,然后可以通过它的AddService方法将服务注册添加:sm->addService(String16("media.audio_flinger"), new AudioFlinger());只有将服务添加到管理进程中才能被其它的进程使用:

sp<IServiceManager> sm = defaultServiceManager();
sp<IBinder> binder = sm->getService(String16("media.audio_flinger"));

Android的音频系统在启动的时候会创建两个服务:一个是上面的示例 AudioFlingerService,一个是AudioPolicyService,并添加到管理进程中,之后其它进程可以使用它们提供的方法。

以下简称AudioFlingerService为AudioFlinger, AudioPolicyService为AudioPolicy

 

核心流程:

AudioSystem:getinput(…)->aps->getinput(..)->AudioPolicyService::getInput(…)->mpPolicyManager->getInput(…)->

<AudioPolicyService>mpClientInterface->openInput(…)->AudioFlinger::openInput(…)

录音流程分析

应用层录音

  AndioRecord类的主要功能是让各种JAVA应用能够管理音频资源,以便它们通过此类能够录制平台的声音输入硬件所收集的声音。此功能的实现就是通过”pulling同步”(reading读取)AudioRecord对象的声音数据来完成的。在录音过程中,应用所需要做的就是通过read方法去及时地获取AudioRecord对象的录音数据. AudioRecord类提供的三个获取声音数据的方法分别是read(byte[], int, int), read(short[], int, int), read(ByteBuffer, int). 无论选择使用那一个方法都必须事先设定方便用户的声音数据的存储格式。

  开始录音的时候,一个AudioRecord需要初始化一个相关联的声音buffer, 这个buffer主要是用来保存新的声音数据。这个buffer的大小,我们可以在对象构造期间去指定。它表明一个AudioRecord对象还没有被读取(同步)声音数据前能录多长的音(即一次可以录制的声音容量)。声音数据从音频硬件中被读出,数据大小不超过整个录音数据的大小(可以分多次读出),即每次读取初始化buffer容量的数据。一般情况下录音实现的简单流程如下:

  1. 创建一个数据流。
  2. 构造一个AudioRecord对象,其中需要的最小录音缓存buffer大小可以通过getMinBufferSize方法得到。如果buffer容量过小,将导致对象构造的失败。
  3. 初始化一个buffer,该buffer大于等于AudioRecord对象用于写声音数据的buffer大小。
  4. 开始录音。
  5. 从AudioRecord中读取声音数据到初始化buffer,将buffer中数据导入数据流。
  6. 停止录音。
  7. 关闭数据流。

程序示例 :

复制代码
// Create a DataOuputStream to write the audio data into the saved file.
OutputStream os = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(os);
DataOutputStream dos = new DataOutputStream(bos);
// Create a new AudioRecord object to record the audio.
int bufferSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration,  audioEncoding);
AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
              11025, AudioFormat.CHANNEL_IN_MONO,
              AudioFormat.ENCODING_PCM_16BIT, bufferSize); short[] buffer = new short[bufferSize]; audioRecord.startRecording(); isRecording = true ; while (isRecording) { int bufferReadResult = audioRecord.read(buffer, 0, bufferSize); for (int i = 0; i < bufferReadResult; i++) dos.writeShort(buffer[i]); } audioRecord.stop(); dos.close();
复制代码
1. getMinBufferSize

   getMinBufferSize函数前文已做介绍,不再细说,查看源码可知函数实现中通过调用native_get_min_buff_size这个JNI函数进入framework/base/core/jni/android_media_AudioRecord.cpp函数中的android_media_AudioRecord_get_min_buff_size.

  native_get_min_buff_size函数到android_media_AudioRecord_get_min_buff_size的关联是通过android_media_AudioRecord.cpp中的函数数组来查看的:

复制代码
static JNINativeMethod gMethods[] = {
    // name,               signature,  funcPtr
    {
      
       "native_start",         "(II)I",    (void *)android_media_AudioRecord_start},
    {
      
       "native_stop",          "()V",    (void *)android_media_AudioRecord_stop},
    {
      
       "native_setup",         "(Ljava/lang/Object;IIIII[I)I", (void *)android_media_AudioRecord_setup},
    {
      
       "native_finalize",      "()V",    (void *)android_media_AudioRecord_finalize},
    {
      
       "native_release",       "()V",    (void *)android_media_AudioRecord_release},
    {
      
       "native_read_in_byte_array", "([BII)I", (void *)android_media_AudioRecord_readInByteArray},
    {
      
       "native_read_in_short_array",  "([SII)I", (void *)android_media_AudioRecord_readInShortArray},
    {
      
       "native_read_in_direct_buffer","(Ljava/lang/Object;I)I", (void *)android_media_AudioRecord_readInDirectBuffer},
    {
      
       "native_set_marker_pos","(I)I",   (void *)android_media_AudioRecord_set_marker_pos},
    {
      
       "native_get_marker_pos","()I",    (void *)android_media_AudioRecord_get_marker_pos},
    {
      
       "native_set_pos_update_period", "(I)I",   (void *)android_media_AudioRecord_set_pos_update_period},
    {
      
       "native_get_pos_update_period", "()I",    (void *)android_media_AudioRecord_get_pos_update_period},
    {
      
       "native_get_min_buff_size", "(III)I",   (void *)android_media_AudioRecord_get_min_buff_size},
};
复制代码

  android_media_AudioRecord_get_min_buff_size代码如下:

复制代码
// ----------------------------------------------------------------------------
// returns the minimum required size for the successful creation of an AudioRecord instance.
// returns 0 if the parameter combination is not supported.
// return -1 if there was an error querying the buffer size.
static jint android_media_AudioRecord_get_min_buff_size(JNIEnv *env,  jobject thiz,
    jint sampleRateInHertz, jint nbChannels, jint audioFormat) {
    ALOGV(">> android_media_AudioRecord_get_min_buff_size(%d, %d, %d)",sampleRateInHertz, nbChannels, audioFormat);
    size_t frameCount = 0;
  //以地址的方式获取frameCount的值。 status_t result = AudioRecord::getMinFrameCount(&frameCount,sampleRateInHertz, (audioFormat == ENCODING_PCM_16BIT ?AUDIO_FORMAT_PCM_16_BIT : AUDIO_FORMAT_PCM_8_BIT), audio_channel_in_mask_from_count(nbChannels)); if (result == BAD_VALUE) { return 0; } if (result != NO_ERROR) { return -1; } return frameCount * nbChannels * (audioFormat == ENCODING_PCM_16BIT ? 2 : 1); }
复制代码

  根据最小的framecount计算最小的buffersize。音频中最常见的是frame这个单位,一个frame就是1个采样点的字节数*声道。为啥搞个frame出来?因为对于多//声道的话,用1个采样点的字节数表示不全,因为播放的时候肯定是多个声道的数据都要播出来//才行。所以为了方便,就说1秒钟有多少个frame,这样就能抛开声道数,把意思表示全了。getMinBufSize函数完了后,我们得到一个满足最小要求的缓冲区大小。这样用户分配缓冲区就有了依据。

 2. new AudioRecord
复制代码
  public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat,
            int bufferSizeInBytes) throws IllegalArgumentException {
        mRecordingState = RECORDSTATE_STOPPED;
    </span><span style="color: #008000;">//</span><span style="color: #008000;"> remember which looper is associated with the AudioRecord instanciation<br data-filtered="filtered">     // 获得主线程的Looper,关于Looper的介绍见其他专题。<br data-filtered="filtered"></span><span style="color: #0000ff;">     if</span><span style="line-height: 1.5;"> ((mInitializationLooper = Looper.myLooper()) == </span><span style="color: #0000ff;">null</span><span style="color: #000000;">) {</span></pre>
            mInitializationLooper = Looper.getMainLooper();
        }
        audioParamCheck(audioSource, sampleRateInHz, channelConfig, audioFormat);
        audioBuffSizeCheck(bufferSizeInBytes);

        // native initialization
        int[] session = new int[1];
        session[0] = 0;
        //TODO: update native initialization when information about hardware init failure
        //      due to capture device already open is available.
     //调用native层的native_setup,把自己的WeakReference传进去 int initResult = native_setup( new WeakReference<AudioRecord>(this), mRecordSource, mSampleRate, mChannelMask, mAudioFormat, mNativeBufferSizeInBytes, session); if (initResult != SUCCESS) { loge("Error code "+initResult+" when initializing native AudioRecord object."); return; // with mState == STATE_UNINITIALIZED } mSessionId = session[0]; mState = STATE_INITIALIZED; }
复制代码

   函数实现通过调用native_setup函数进入了framework/base/core/jni/android_media_AudioRecord.cpp中的android_media_AudioRecord_setup:

复制代码
static int android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject weak_this,
        jint source, jint sampleRateInHertz, jint channelMask,
                // Java channel masks map directly to the native definition
        jint audioFormat, jint buffSizeInBytes, jintArray jSession)
{
    //ALOGV(">> Entering android_media_AudioRecord_setup");
    //ALOGV("sampleRate=%d, audioFormat=%d, channel mask=%x, buffSizeInBytes=%d",
    //     sampleRateInHertz, audioFormat, channelMask, buffSizeInBytes);
<span style="color: #0000ff;">if</span> (!<span style="color: #000000;">audio_is_input_channel(channelMask)) {
    ALOGE(</span><span style="color: #800000;">"</span><span style="color: #800000;">Error creating AudioRecord: channel mask %#x is not valid.</span><span style="color: #800000;">"</span><span style="color: #000000;">, channelMask);
    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> AUDIORECORD_ERROR_SETUP_INVALIDCHANNELMASK;
}<br data-filtered="filtered">    //popCount是统计一个整数中有多少位为1的算法
uint32_t nbChannels </span>=<span style="color: #000000;"> popcount(channelMask
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zhengdongtao110/article/details/89332192

智能推荐

稀疏编码的数学基础与理论分析-程序员宅基地

文章浏览阅读290次,点赞8次,收藏10次。1.背景介绍稀疏编码是一种用于处理稀疏数据的编码技术,其主要应用于信息传输、存储和处理等领域。稀疏数据是指数据中大部分元素为零或近似于零的数据,例如文本、图像、音频、视频等。稀疏编码的核心思想是将稀疏数据表示为非零元素和它们对应的位置信息,从而减少存储空间和计算复杂度。稀疏编码的研究起源于1990年代,随着大数据时代的到来,稀疏编码技术的应用范围和影响力不断扩大。目前,稀疏编码已经成为计算...

EasyGBS国标流媒体服务器GB28181国标方案安装使用文档-程序员宅基地

文章浏览阅读217次。EasyGBS - GB28181 国标方案安装使用文档下载安装包下载,正式使用需商业授权, 功能一致在线演示在线API架构图EasySIPCMSSIP 中心信令服务, 单节点, 自带一个 Redis Server, 随 EasySIPCMS 自启动, 不需要手动运行EasySIPSMSSIP 流媒体服务, 根..._easygbs-windows-2.6.0-23042316使用文档

【Web】记录巅峰极客2023 BabyURL题目复现——Jackson原生链_原生jackson 反序列化链子-程序员宅基地

文章浏览阅读1.2k次,点赞27次,收藏7次。2023巅峰极客 BabyURL之前AliyunCTF Bypassit I这题考查了这样一条链子:其实就是Jackson的原生反序列化利用今天复现的这题也是大同小异,一起来整一下。_原生jackson 反序列化链子

一文搞懂SpringCloud,详解干货,做好笔记_spring cloud-程序员宅基地

文章浏览阅读734次,点赞9次,收藏7次。微服务架构简单的说就是将单体应用进一步拆分,拆分成更小的服务,每个服务都是一个可以独立运行的项目。这么多小服务,如何管理他们?(服务治理 注册中心[服务注册 发现 剔除])这么多小服务,他们之间如何通讯?这么多小服务,客户端怎么访问他们?(网关)这么多小服务,一旦出现问题了,应该如何自处理?(容错)这么多小服务,一旦出现问题了,应该如何排错?(链路追踪)对于上面的问题,是任何一个微服务设计者都不能绕过去的,因此大部分的微服务产品都针对每一个问题提供了相应的组件来解决它们。_spring cloud

Js实现图片点击切换与轮播-程序员宅基地

文章浏览阅读5.9k次,点赞6次,收藏20次。Js实现图片点击切换与轮播图片点击切换<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title></title> <script type="text/ja..._点击图片进行轮播图切换

tensorflow-gpu版本安装教程(过程详细)_tensorflow gpu版本安装-程序员宅基地

文章浏览阅读10w+次,点赞245次,收藏1.5k次。在开始安装前,如果你的电脑装过tensorflow,请先把他们卸载干净,包括依赖的包(tensorflow-estimator、tensorboard、tensorflow、keras-applications、keras-preprocessing),不然后续安装了tensorflow-gpu可能会出现找不到cuda的问题。cuda、cudnn。..._tensorflow gpu版本安装

随便推点

物联网时代 权限滥用漏洞的攻击及防御-程序员宅基地

文章浏览阅读243次。0x00 简介权限滥用漏洞一般归类于逻辑问题,是指服务端功能开放过多或权限限制不严格,导致攻击者可以通过直接或间接调用的方式达到攻击效果。随着物联网时代的到来,这种漏洞已经屡见不鲜,各种漏洞组合利用也是千奇百怪、五花八门,这里总结漏洞是为了更好地应对和预防,如有不妥之处还请业内人士多多指教。0x01 背景2014年4月,在比特币飞涨的时代某网站曾经..._使用物联网漏洞的使用者

Visual Odometry and Depth Calculation--Epipolar Geometry--Direct Method--PnP_normalized plane coordinates-程序员宅基地

文章浏览阅读786次。A. Epipolar geometry and triangulationThe epipolar geometry mainly adopts the feature point method, such as SIFT, SURF and ORB, etc. to obtain the feature points corresponding to two frames of images. As shown in Figure 1, let the first image be ​ and th_normalized plane coordinates

开放信息抽取(OIE)系统(三)-- 第二代开放信息抽取系统(人工规则, rule-based, 先抽取关系)_语义角色增强的关系抽取-程序员宅基地

文章浏览阅读708次,点赞2次,收藏3次。开放信息抽取(OIE)系统(三)-- 第二代开放信息抽取系统(人工规则, rule-based, 先关系再实体)一.第二代开放信息抽取系统背景​ 第一代开放信息抽取系统(Open Information Extraction, OIE, learning-based, 自学习, 先抽取实体)通常抽取大量冗余信息,为了消除这些冗余信息,诞生了第二代开放信息抽取系统。二.第二代开放信息抽取系统历史第二代开放信息抽取系统着眼于解决第一代系统的三大问题: 大量非信息性提取(即省略关键信息的提取)、_语义角色增强的关系抽取

10个顶尖响应式HTML5网页_html欢迎页面-程序员宅基地

文章浏览阅读1.1w次,点赞6次,收藏51次。快速完成网页设计,10个顶尖响应式HTML5网页模板助你一臂之力为了寻找一个优质的网页模板,网页设计师和开发者往往可能会花上大半天的时间。不过幸运的是,现在的网页设计师和开发人员已经开始共享HTML5,Bootstrap和CSS3中的免费网页模板资源。鉴于网站模板的灵活性和强大的功能,现在广大设计师和开发者对html5网站的实际需求日益增长。为了造福大众,Mockplus的小伙伴整理了2018年最..._html欢迎页面

计算机二级 考试科目,2018全国计算机等级考试调整,一、二级都增加了考试科目...-程序员宅基地

文章浏览阅读282次。原标题:2018全国计算机等级考试调整,一、二级都增加了考试科目全国计算机等级考试将于9月15-17日举行。在备考的最后冲刺阶段,小编为大家整理了今年新公布的全国计算机等级考试调整方案,希望对备考的小伙伴有所帮助,快随小编往下看吧!从2018年3月开始,全国计算机等级考试实施2018版考试大纲,并按新体系开考各个考试级别。具体调整内容如下:一、考试级别及科目1.一级新增“网络安全素质教育”科目(代..._计算机二级增报科目什么意思

conan简单使用_apt install conan-程序员宅基地

文章浏览阅读240次。conan简单使用。_apt install conan