Android音频(6)——音频系统分析

一、AudioPolicyService启动过程分析

1. 播放声音,声音从哪个设备播放出来是由audio policy决定的。

2.在每一个声卡,对应一个output,一个output对应系统中都有一个线程与其对应。

3.对硬件的访问操作是由AudioFlinger来完成的

4.AudioPolicyService在启动时会去读取解析配置文件/system/etc/audio_policy.conf 根据配置文件来操作AudioFlinger来打开output,创建线程。

5.tiny4412上的配置文件

# cat /system/etc/audio_policy.conf

global_configuration {
  attached_output_devices AUDIO_DEVICE_OUT_SPEAKER
  default_output_device AUDIO_DEVICE_OUT_SPEAKER
  attached_input_devices AUDIO_DEVICE_IN_BUILTIN_MIC
}

audio_hw_modules { //注意这里是modules,里面的每一项都表示一个module
  primary {    //这是一个module,一个module对一个厂家提供的一个.so文件
    outputs { //注意这是outputs,里面的每一项都是一个output
      primary { //这是一个output,里面的参数是对这个output的配置,格式“name value”
        sampling_rates 44100
        channel_masks AUDIO_CHANNEL_OUT_STEREO
        formats AUDIO_FORMAT_PCM_16_BIT
        devices AUDIO_DEVICE_OUT_SPEAKER|AUDIO_DEVICE_OUT_EARPIECE|AUDIO_DEVICE_OUT_WIRED_HEADSET|AUDIO_DEVICE_OUT_WIRED_HEADPHONE|AUDIO_DEVICE_OUT_ALL_SCO|AUDIO_DEVICE_OUT_AUX_DIGITAL
        flags AUDIO_OUTPUT_FLAG_PRIMARY
      }
    }
    inputs {
      primary {
        sampling_rates 8000|11025|12000|16000|22050|24000|32000|44100|48000
        channel_masks AUDIO_CHANNEL_IN_MONO|AUDIO_CHANNEL_IN_STEREO
        formats AUDIO_FORMAT_PCM_16_BIT
        devices AUDIO_DEVICE_IN_BUILTIN_MIC|AUDIO_DEVICE_IN_WIRED_HEADSET|AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET|AUDIO_DEVICE_IN_AUX_DIGITAL|AUDIO_DEVICE_IN_VOICE_CALL
      }
    }
  }
}

6.AudioPolicyService启动过程分析

a. 加载解析/vendor/etc/audio_policy.conf或/system/etc/audio_policy.conf
   对于配置文件里的每一个module项, new HwModule(name), 放入mHwModules数组
   对于module里的每一个output, new IOProfile, 放入module的mOutputProfiles
   对于module里的每一个input, new IOProfile, 放入module的mInputProfiles
b. 根据module的name加载厂家提供的so文件 (通过AudioFlinger来加载)    这里很重要!!!
c. 打开对应的output                     (通过AudioFlinger来open output)

7.HwModule是来自配置文件中的audio_hw_modules中的成员primary

8.instantiate()用于注册服务

main() //framework/main_mediaserver.cpp
    AudioPolicyService::instantiate(); //framework/main_mediaserver.cpp
这个instantiate()实现在BinderService类中,用于add Service.

二、AudioFlinger启动过程分析

参考002 UML

1.加载的.so文件在/system/lib/hw下

2 AudioFlinger启动过程分析

a. 注册AudioFlinger服务
b. 被AudioPolicyService调用以打开厂家提供的so文件
b.1 加载哪个so文件? 文件名是什么? 文件名从何而来?
    名字从/system/etc/audio_policy.conf得到module的名字为: primary,所以so文件就是 : audio.primary.XXX.so, eg. audio.primary.tiny4412.so(来自audio_hw_hal.cpp和AudioHardware.cpp)

b.2 该so文件由什么源文件组成? 查看Android.mk
    audio.primary.$(TARGET_DEVICE) : device/friendly-arm/common/libaudio/AudioHardware.cpp
                                     libhardware_legacy
    libhardware_legacy :     hardware/libhardware_legacy/audio/audio_hw_hal.cpp                                 

    结论:主要是由AudioHardware.cpp和audio_hw_hal.cpp构成。

b.3 对硬件的封装:
    AudioFlinger.cpp   : 把硬件封装成AudioHwDevice (放入mAudioHwDevs数组中)
    audio_hw_hal.cpp   : 把硬件封装成audio_hw_device
    厂家               : 把硬件封装成AudioHardware (派生自: AudioHardwareInterface)

    AudioHwDevice是对audio_hw_device的封装,
    audio_hw_device中函数的实现要通过AudioHardware类对象

c. 被AudioPolicyService调用来open output、创建playback thread 

三、AudioTrack创建过程

1.AudioTrack::AudioTrack()调用的set()中创建一个AudioTrack并与硬件挂钩

2.Android中使用一个output来描述声卡的输出通道。一个output对应一个playbackThread,这些信息由AudioFilnger决定

3.AudioTrack创建过程概述

a.1 C++实现测试程序:
    frameworks/base/media/tests/audiotests/shared_mem_test.cpp
    内容为自己构造一个正弦波音频,一直播放知道Ctrl+C结束。

a.2 java实现的测试程序:(目前还没测试)
    frameworks/base/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaAudioTrackTest.java

播放声音时都要创建AudioTrack对象(这个AudioTrack对象可以是C++实现的,也可以是Java实现的),
java的AudioTrack对象创建时会导致C++的AudioTrack对象被创建; 所以分析的核心是c++的AudioTrack类, 创建AudioTrack时涉及一个重要函数: set

b. 猜测创建过程的主要工作
b.1 set()使用AudioTrack的属性, 根据AudioPolicy找到对应的output、playbackThread(一个output就对应一个playbackThread)
b.2 在playbackThread中创建对应的track
b.3 APP的AudioTrack 和 playbackThread的mTracks中的track之间建立共享内存

c. 源码时序图 003 UML

四、AudioTrack创建过程_选择output

1. AudioTrack创建过程_选择output

a. APP构造AudioTrack时指定了 stream type
b. AudioTrack::setAttributesFromStreamType 根据stream type设置属性
c. AudioPolicyManager::getStrategyForAttr  根据属性获得声音的strategy(类别)
d. AudioPolicyManager::getDeviceForStrategy 根据类别选择声音从哪个device里面播放
e. AudioPolicyManager::getOutputForDevice    根据device获得output.
    //可能有多个output支持同一类别的一个device
       e.1 AudioPolicyManager::getOutputsForDevice //获取这个device的所有output
       e.2 output = selectOutput(outputs, flags, format);//从这个device的多个output中选择某一个output

2.AudioPolicyManager.mOutputs对应于已打开的output,每一项都含有mProfile,这个mProfile是来自/system/etc/audio_policy.conf,mProfile中指明该output支持哪些device。

DefaultKeyedVector<audio_io_handle_t, sp<AudioOutputDescriptor> > mOutputs;

3.有可能有多个output都支持某个device,怎么从多个output中取出最合适的output?

AudioPolicyManager::selectOutput中
首先匹配flag,找出最吻合的output:
a.APP创建AudioTrack时会传入flag
b.output对应的profile中也有flag(来自/system/etc/audio_policy.conf中指定的flag),
c.使用上述a,b的flag进行比较,取出吻合度最高的output.
如果吻合度相同:
如果primary output支持该设备,选它,否则取出第一个output。

五、AudioTrack创建过程_Track和共享内存

1.一个output对应一个播放设备(声卡),也对应一个播放线程。音频数据流向playbackThread ---> output ---> 声卡设备

2.应用程序中的AudioTrack和播放线程中的mTrack中的成员是一一对应的。它们之间通过共享内存来传递数据。

3. AudioTrack创建过程_Track和共享内存
回顾:
a. APP创建AudioTrack <-----------------> AudioFlinger中PlaybackThread创建对应的Track
b. APP给AudioTrack提供音频数据有2种方式: 一次性提供(MODE_STATIC)、边播放边提供(MODE_STREAM)

问:
a. 音频数据存在buffer中, 这个buffer由谁提供? APP 还是 PlaybackThread ?
(1)MODE_STATIC:若是App一次性提供音频数据,那么buffer是由App创建的,因为App更方便知道buffer的大小属性。
(2)MODE_STREAM:若App是边播放边提供音频数据,那么就由playbackThread创建,这样App实现起来比较方便。

b. APP提供数据, PlaybackThread消耗数据, 如何同步?
(1)MODE_STATIC:一次性提供,就不需要同步,这是一前一后的事情。
(2)MODE_STREAM:边播放边提供音频数据的时候,这是典型的生产者消费者问题,使用环形缓冲区来同步。

4.playbackThread中的mTracks是个链表,上面的每一个Track都对应应用程序中的一个AudioTrack. 也正是由于App创建了
AudioTrack才导致mTracks链表上的track被创建。

5.App和playbackThread是处于不同的进程中的,音频数据可以通过binder通信,但是效率不是很高,所以使用共享内存来传递数据。

6.测试程序shared_mem_test.cpp中使用的就是一次性提供:

AudioTrackTest::Test01()
    iMem = heap->allocate(BUF_SZ*sizeof(short));
    memcpy(p, smpBuf, BUF_SZ*sizeof(short));
     sp<AudioTrack> track = new AudioTrack(AUDIO_STREAM_MUSIC,// stream type
               rate,
               AUDIO_FORMAT_PCM_16_BIT,// word length, PCM
               AUDIO_CHANNEL_OUT_MONO,
               iMem); /*这里一次性把音频数据传下去了*/

  对于C++的实现,参数iMem若是不为NULL就表示这个共享内存是由App创建的,之后就会使用MODE_STATIC这个方式传输数据;若为NULL,表明App并没有去创建这个buffer,将由playbackThread来创建这个共享内存,之后将通过MODE_STREAM方式传输数据;

AudioTrack它并没有一个模式来分辨是使用MODE_STATIC还是MODE_STREAM,它通过参数iMem是NULL还是非NULL来分辨使用哪种方式。

C++的AudioTrack类不需要指定模式,但是Java的AudioTrack需要:

MediaAudioTrackTest.java
testSetPlaybackHeadPositionTooFar()
    int TEST_MODE = AudioTrack.MODE_STREAM;
    AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, 2*minBuffSize, TEST_MODE);

testSetPlaybackRateUninit()
    int TEST_MODE = AudioTrack.MODE_STATIC;
    AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, minBuffSize, TEST_MODE);

7.AudioTrack构造函数:

AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes, int mode, int sessionId) //AudioTrack.java
    //本地初始化
    native_setup(new WeakReference<AudioTrack>(this), mAttributes,
                mSampleRate, mChannels, mAudioFormat,
                mNativeBufferSizeInBytes, mDataLoadMode, session);
        //对应这个JNI函数
        android_media_AudioTrack_setup //android_media_AudioTrack.cpp
            //本地的AudioTrack类对象,没有传入任何参数
            sp<AudioTrack> lpTrack = new AudioTrack();
            case MODE_STREAM:
                //对于MODE_STREAM模式,直接调用set(),没有在App进程中分配内存
                lpTrack->set()
            case MODE_STATIC:
                //对于MODE_STATIC模式,会在App进程中分配buffer
                lpJniStorage->allocSharedMem(buffSizeInBytes)
                lpTrack->set()

六、音频数据的传递

1.App这边相关的代码在AudioTrack.cpp中,playbackTread的处理函数在Track.cpp中。

2.mProxy用于App端管理共享内存

// frameworks\av\media\libmedia\AudioTrack.cpp
AudioTrack::createTrack_l()
    // update proxy
    if (mSharedBuffer == 0) {
        mStaticProxy.clear();
        mProxy = new AudioTrackClientProxy(cblk, buffers, frameCount, mFrameSizeAF);
    } else {
        mStaticProxy = new StaticAudioTrackClientProxy(cblk, buffers, frameCount, mFrameSizeAF);
        mProxy = mStaticProxy;
    }

mServerProxy类是用于给播放线程管理内存的

// frameworks\av\services\audioflinger\Tracks.cpp
AudioFlinger::PlaybackThread::Track::Track

    if (sharedBuffer == 0) {
        mAudioTrackServerProxy = new AudioTrackServerProxy(mCblk, mBuffer, frameCount,
                mFrameSize, !isExternalTrack(), sampleRate);
    } else {
        mAudioTrackServerProxy = new StaticAudioTrackServerProxy(mCblk, mBuffer, frameCount,
                mFrameSize);
    }
    mServerProxy = mAudioTrackServerProxy;

3.音频数据的传递总结

a. APP创建AudioTrack, playbackThread创建对应的Track,它们之间通过共享内存传递音频数据
b. APP有2种使用共享内存的方式:
b.1 MODE_STATIC: APP创建共享内存, APP一次性填充数据
b.2 MODE_STREAM: APP使用obtainBuffer获得空白内存, 填充数据后使用releaseBuffer释放内存
c. playbackThread使用obtainBuffer获得含有数据的内存, 使用数据后使用releaseBuffer释放内存
d. App端的AudioTrack中含有mProxy,它被用来管理共享内存,里面含有obtainBuffer,releaseBuffer函数;
   播放线程端的Track中含有mServerProxy,它被用来管理共享内存,里面含有obtainBuffer, releaseBuffer函数
   对于不同的MODE, 这些Proxy指向不同的对象。
e. 对于MODE_STREAM, APP和playbackThread使用环型缓冲区的方式传递数据。

5.环形缓冲区讲解

a.初始R=0,W=0,buff的长度为LEN
b.写入一个数据
	w = W % LEN;
	buff[w] = data;
	W++;
c.读出一个数据
	r = R % LEN;
	data = buff[r];
	R++;
判断缓冲区满:R + LEN == W
判断缓冲区空:R == W

由上可以看出R和W是一直递增的。

判断溢出时,R和W同时都减少LEN的整数倍大小,将不影响结果

改进: 将LEN圆整到2的n次方,此时W%LEN这个可能比较慢的操作就可以转换为w=W&(LEN-1)

七、PlaybackThread处理流程

1.声卡往往只支持一种格式的音频数据(如2-Channel,44K-Sample, 16bit-Deep)。但是App可能会传递下来不同格式的音频数据,
这些数据在在playbackThread中进行重采样,采样到声卡支持的格式。mAudioMixer对象负责这一工作。重采样之后还要把各个App
的音频数据混合起来,称为混音,也是由mAudioMixer负责的。

2.hook成员指向不同的处理函数

//frameworks\av\services\audioflinger\AudioMixer.cpp
process__nop():手机静音了,不做任何处理
process__genericNoResampling(): 不需要重采样
process__genericResampling(): 应该就是进行重采样

3. PlaybackThread 处理流程

AudioFlinger::PlaybackThread::threadLoop()
frameworks\av\services\audioflinger\Threads.cpp
a. prepareTracks_l :
   确定enabled track, disabled track。对于enabled track, 设置mState.tracks[x]中的参数
b. threadLoop_mix : 处理数据(比如重采样)、混音
   确定hook: 逐个分析mState.tracks[x]的数据, 根据它的格式确定tracks[x].hook。再确定总的mState.hook

   调用hook: 调用总的mState.hook即可, 它会再去调用每一个mState.tracks[x].hook

   混音后的数据会放在mState.outputTemp临时BUFFER中然后转换格式后存入 thread.mMixerBuffer
c. memcpy_by_audio_format : 把数据从thread.mMixerBuffer或thread.mEffectBuffer复制到thread.mSinkBuffer
d. threadLoop_write: 把thread.mSinkBuffer写到声卡上
e. threadLoop_exit

4.总结
  原始数据会保存在共享内存中,经过重采样等处理后存放在一个临时的outputTemp buffer中,这个临时buffer中的数据保存的是重采样后的数据,数据来源是多个Track。最终的可播放的数据经过混音后存放在mMixerBuffer这个buffer中。如果需要进行音效处理(比如加强低音),还有一部分数据会被放在mEffectBuffer中。之后会把mMixerBuffer和mEffectBuffer中的音频数据都输出到mSinkBuffer中,然后从mSinkBuffer中将数据发给声卡硬件。

原文地址:https://www.cnblogs.com/hellokitty2/p/10932313.html

时间: 2024-10-11 00:11:35

Android音频(6)——音频系统分析的相关文章

Android自带音频均衡器MusicFx分析

Android自带音频均衡器MusicFx分析 种种原因,我要简单分析一个Android中built-in的音频均衡器MusicFx.重点是它的默认值的来历.网上很少有文章讲了这个的除了这篇<com.android.musicFx设置音效流程 -- 从app到AudioFlinger>.注:Android系统版本为4.2.2_r1. 从App到AudioFliger的终点是在android_media_AudioEffect.cpp(之前版本在是audio_media_AudioEffect.

android MediaRecorder录制音频

使用MediaRecorder录制音频步骤: 创建MediaRecorder对象 调用MediaRecorder的setAudioSource()方法设置声音来源,一般传入MediaRecorder.AudioSource.MIC参数指定录制来自麦克风的声音 调用MediaRecorder对象的setOutputFormat()设置所录制的音频 文件的格式 调用MediaRecorder对象的setAudioEncoder() ,setAudioEncodingBitRate(int bitRa

Android中的音频处理------SoundPool,MediaRecorder,MediaPlayer以及RingStone总结

用Soundpool可以播一些短的反应速度要求高的声音, 比如游戏中的爆破声, 而Mediaplayer适合播放长点的. MediaRecorder主要用来录音. SoundPool载入音乐文件使用了独立的线程,不会阻塞UI主线程的操作. 但是这里如果音效文件过大没有载入完成,我们调用play方法时可能产生严重的后果, 这里AndroidSDK提供了一个SoundPool.OnLoadCompleteListener类来帮助我们了解媒体文件是否载入完成, 我们重载onLoadComplete(S

Android游戏之音频类设计

Android游戏之音频类设计 1.基础知识: A. setVolumeControlStream(AudioManager.STREAM_MUSIC);http://developer.android.com/training/managing-audio/volume-playback.htmlhttp://developer.android.com/reference/android/app/Activity.html#setVolumeControlStream(int) public

android播放网络音频

android播放网络音频,很简单的技术,但是可以学习下 很简单的一个获取网络音频播放器,有进度条,播放,暂停,停止,重新播放,支持缓存,以下是源码,希望可以帮到大家 布局文件很简单,就几个按钮,TextView,和SeekBar. activity_audio_palyer.xml <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="

【Android】播放音频的几种方式介绍

接下来笔者介绍一下Android中播放音频的几种方式,android.media包下面包含了Android开发中媒体类,当然笔者不会依次去介绍,下面介绍几个音频播放中常用的类: 1.使用MediaPlayer播放音频 MediaPlayer的功能很强大,下面附上一张该类封装音频的生命周期图: MediaPlayer支持AAC.AMR.FLAC.MP3.MIDI.OGG.PCM等格式,MediaPlayer可以通过设置元数据和播放源来音频. 1.1播放Raw文件夹下面音频的元数据 //直接创建,不

【转】解决在Android设备播放音频与其他应用重音的问题,并监听耳机的控制按钮

概述 在安卓开发中免不了需要播放一点音乐了,音频了.但是这时候有别的应用正在播放,这时候就会出现重音的现象,完全影响用户体验,我们的项目就遇上了这样的尴尬,然后查找了一些文档,记录一下: 管理音频焦点 从谷歌开发了解到,这跟音频的焦点又关系,我们可以获取当前音频的焦点来解决问题: 有多个应用程序可能播放音频,重要的是要考虑他们应该如何交互.为了避免每个音乐应用程序同时播放,Android使用音频焦点来控制音频播放 - 只有拥有音频焦点的应用程序才能播放音频. 在您的应用程序开始播放音频之前,应该

Android MediaPlayer 播放音频

本文链接: Android MediaPlayer 播放音频 主要介绍使用MediaPlayer播放音频的方式.关于MediaPlayer的基础知识,比如状态,可以参考Android MediaPlayer 基础简介. 为了方便表达,定义变量名为mediaPlayer. MediaPlayer的使用方式 创建MediaPlayer 可以直接 new MediaPlayer,也可以用MediaPlayer提供的create方法创建. mediaPlayer = new MediaPlayer();

音频 PCM音频编码格式详解

[概念] PCM(Pulse Code Modulation)音频编码格式也被称为脉冲编码调制.就是将声音等模拟信号变成符号化的脉冲列,再进行记录存储. [原理] 把一个时间连续,取值连续的模拟信号变换成时间离散,取值离散的数字信号后在信道中传输.脉冲编码调制就是对模拟信号先抽样,再对样值幅度量化,编码的过程. 抽样,就是对模拟信号进行周期性扫描,把时间上连续的信号变成时间上离散的信号.该模拟信号经过抽样后还应当包含原信号中所有信息,也就是说能无失真的恢复原模拟信号.它的抽样速率的下限是由抽样定

(转)同步音频--同步音频到视频

ffmpeg文档6 (2008-08-27 18:44:22) 转载 标签: 杂谈 分类: 翻译文档 指导6:同步音频 同步音频 现在我们已经有了一个比较像样的播放器.所以让我们看一下还有哪些零碎的东西没处理.上次,我们掩饰了一点同步问题,也就是同步音频到视频而不是其它的同 步方式.我们将采用和视频一样的方式:做一个内部视频时钟来记录视频线程播放了多久,然后同步音频到上面去.后面我们也来看一下如何推而广之把音频和视频 都同步到外部时钟. 生成一个视频时钟 现在我们要生成一个类似于上次我们的声音时