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

智能推荐

centOS 快速安装和配置 NVIDIA docker Container Toolkit_nvidia container toolkit-程序员宅基地

文章浏览阅读2.3k次。CentOS快速安装配置NVIDIA Container Toolkit_nvidia container toolkit

Android 中调用线程thread.stop 方法后报错,Deprecated Thread methods are not supported._timethread().stop();报错-程序员宅基地

文章浏览阅读3.9k次。03-16 15:39:03.082 16179-16179/tech.androidstudio.handlerdemotimer E/global: Deprecated Thread methods are not supported.03-16 15:39:03.082 16179-16179/tech.androidstudio.handlerdemotimer E/global: _timethread().stop();报错

%e5 转换汉字 php,汉字转Unicode编码,Unicode编码转汉字-程序员宅基地

文章浏览阅读2.3k次。/*** 汉字转Unicode编码* @param string $str 原始汉字的字符串* @param string $encoding 原始汉字的编码* @param boot $ishex 是否为十六进制表示(支持十六进制和十进制)* @param string $prefix 编码后的前缀* @param string $postfix 编码后的后缀*/function unicode..._%e5

东汉十三州以及各个郡的说明_三国地图十三州精确到城池-程序员宅基地

文章浏览阅读6.4w次,点赞7次,收藏10次。东汉十三州以及州下辖的郡县。_三国地图十三州精确到城池

响应式编程实现异步RPC,提升xxl-job调度吞吐量-程序员宅基地

文章浏览阅读1.1k次。在xxl-job中,RPC即用于调度中心请求执行器执行job、kill job,也用于执行器请求调度中心主动注册、执行结果上报。xxl-job实现的RPC类似Feign框架,是基于http..._xxljob 用的什么协议

C++ Json到对象的自动序列化和反序列化工作_c++ json序列化和反序列化-程序员宅基地

文章浏览阅读555次,点赞17次,收藏22次。JSERIALIZE_DEF_OBJECTLIST(Person,Object,objectList) //接受json中的objectList对象数组,对象数组使用此宏定义。JSERIALIZE_DEF_OBJECTTYPE(Person,Son,son) //接受json中的son对象,对象成员使用此宏定义。//输出反序列化结果。

随便推点

ios砸壳_ios砸壳需要 闪退怎么砸-程序员宅基地

文章浏览阅读6.2k次。frida-ios-dump源码地址:​​​​​​GitHub - AloneMonkey/frida-ios-dump: pull decrypted ipa from jailbreak devicefrida-ios-dump是基于frida开发的一键砸壳工具,需要配置frida环境手机配置1)越狱状态2)安装openssh3)安装fridaMac配置1)安装frida,命令行:sudo pip install frida-tools (没有安装pip的话需要先安装pip)_ios砸壳需要 闪退怎么砸

IOS-----越狱开发_depends libundirect.depends firmware-程序员宅基地

文章浏览阅读2.6k次,点赞3次,收藏2次。1.制作系统应用程序。 ios的程序分为mobile和root权限模式,我们一般用xcode开发的app取得的是mobile权限,但是ios越狱后安装的app如:Cydia、91助手、PP助手等均为系统级应用程序。系统级app的好处是:用不无法手动删除、取得完全的root权限、可设置开机启动项等等功能。通过xcode打包的ipa是无法安装成为系统app的,所以我们需要另外一种打包方式:_depends libundirect.depends firmware

C++--继承基本概念、对象赋值转换、作用域_什么是赋值转换-程序员宅基地

文章浏览阅读254次,点赞5次,收藏2次。继承1. 继承的基本概念1.1 继承的定义1.2 继承基类成员访问方式的变化2. 基类和派生类对象赋值转换3. 继承中的作用域1. 继承的基本概念继承是面向对象程序设计使代码复用的最重要的手段,允许在保持原有类特性的基础上进行扩展,增加功能,产生新的类,称为派生类/子类。继承是类设计层次的复用。1.1 继承的定义派生类 : 继承方式 基类class Student : public Person1.2 继承基类成员访问方式的变化父类成员在子类中的访问权限(除过父类中的私有成员):_什么是赋值转换

模式识别(2)KNN分类_usps数据集是在哪里提出的-程序员宅基地

文章浏览阅读2.3k次,点赞10次,收藏36次。基于USPS和UCI数据集的近邻法分类一、问题描述 使用近邻算法进行分类问题的研究,并在USPS手写体数据集和UCI数据集上的iris和sonar数据上验证算法的有效性,并分别对近邻法中k近邻算法、最近邻算法和Fisher线性判别进行对比分析。二、数据集说明2.1 USPS手写体 USPS,美国邮政署,是美国联邦政府的独立机构,其中的手_usps数据集是在哪里提出的

Access根据出生日期计算年龄_Excel表格中怎么用出生日期计算年龄?这些方法好用哟...-程序员宅基地

文章浏览阅读1.9k次。  平时工作中用到Excel表格的几率特别大,也积累了一些小技巧,今天就给大家分享一下计算年龄的方法。  在Excel表格中利用“系统时间”和“出生年月”来计算“周岁年龄”、“虚岁年龄”和“实际年龄”是非常方便的,特别是人事管理和工资的统计中遇到的可能性比较大,一起来看一下计算年龄的方法吧。  方法一  第一步,如下图所示,先把需要计算年龄的出生日期输入到表格中。   第二步,然后在B2单元格中输..._access计算年龄

【EJB】异步方法调用-程序员宅基地

文章浏览阅读641次,点赞23次,收藏18次。虽然我个人也经常自嘲,十年之后要去成为外卖专员,但实际上依靠自身的努力,是能够减少三十五岁之后的焦虑的,毕竟好的架构师并不多。架构师,是我们大部分技术人的职业目标,一名好的架构师来源于机遇(公司)、个人努力(吃得苦、肯钻研)、天分(真的热爱)的三者协作的结果,实践+机遇+努力才能助你成为优秀的架构师。如果你也想成为一名好的架构师,那或许这份Java成长笔记你需要阅读阅读,希望能够对你的职业发展有所帮助。《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!

推荐文章

热门文章

相关标签