Android P的音频架构(一)

Android P的音频架构

音频架构图

谷歌官网:https://source.android.com/devices/audio
在谷歌官网介绍的音频架构中,我们可以看到下面的架构图:
在这里插入图片描述

android p音频处理流程

通过用AudioTrack来播放pcm格式音乐的流程,来讲解整个音频架构处理的流程。

android p音频播放从app开始,在framework层创建播放器,在audio library层做音频流和输出流控制,在Hal层将音频数据写入到输出设备进行声音输出。其中audio library层是音频处理的核心。

App --> Frameworks --> Audio Library --> HAL

一、App层

上层应用,包括各种音乐播放器,语音播报等声音输出软件。在app层,应用播放不同的音频文件。播放音频文件主要有两种方式,MediaPlayer和AudioTrack,这两者都提供了Java API供应用开发者使用,虽然都可以播放音频,但他们还是有区别:

1.MediaPlayer可以播放多种格式的声音文件,例如MP3,AAC,WAV,OGG,MIDI等。MediaPlayer会在framework层创建对应的音频解码器。

2.AudioTrack只能播放已经解码的PCM流,如果对比支持的文件格式的话则是AudioTrack只支持wav格式的音频文件,因为wav格式的音频文件大部分都是PCM流。AudioTrack不创建解码器,所以只能播放不需要解码的wav文件。

3.MediaPlayer在framework层还是会创建AudioTrack,把解码后的PCM数流传递给AudioTrack,AudioTrack再传递给AudioFlinger进行混音,然后才传递给硬件播放,所以是MediaPlayer包含了AudioTrack。

总结:MediaPlayer 更加适合在后台长时间播放本地音乐文件或者在线的流式资源;AudioTrack 则更接近底层,提供了非常强大的控制能力,支持低延迟播放,适合流媒体和VoIP语音电话等场景。

本文主要讲解使用AudioTrack播放pcm或者wav格式音频文件:

// 一个使用AudioTrack播放音频的简单例子
public class MainActivity extends Activity {
    private Button mPlayBtn;		//播放按钮
    private Button mPauseBtn;		//暂停按钮
    private byte[] mAudioData;		//音频数据
    private AudioTrack mAudioTrack;	//AudioTrack对象

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mPlayBtn = findViewById(R.id.play);
        mPauseBtn = findViewById(R.id.pause);
        mPlayBtn.setEnabled(false);

		//按下播放按钮后做的处理
        mPlayBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mPlayBtn.setEnabled(false);
                if(mAudioTrack != null){
                    mAudioTrack.play();
                }else{
                	// android p之前的版本初始化AudioTrack的方式,在android p中遗弃了该方式,但是兼容这种写法。
                    // mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 8000,AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT,mAudioData.length, AudioTrack.MODE_STATIC);
                    
                    // android p初始化AudioTrack的方式
                    /*  音频参数(音频参数如果跟音频文件参数不一致,声音播放会出现异常):
					*	streamType:音频流类型 AudioManager.STREAM_MUSIC,音乐(不同的声音分类为不同的音频流类型)
					*	encoding:位宽 AudioFormat.ENCODING_PCM_16BIT,16bit音频文件
					*	sampleRate:采样率 8000,8k的音频文件
					*	channelMask:通道数 AudioFormat.CHANNEL_OUT_MONO,单声道
					*/
                    AudioAttributes.Builder attr = (new AudioAttributes.Builder())
                            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                            .setLegacyStreamType(AudioManager.STREAM_MUSIC)
                            .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
                            .setUsage(AudioAttributes.USAGE_MEDIA);
                    AudioFormat.Builder format = (new AudioFormat.Builder())
                            .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
                            .setSampleRate(8000)
                            .setChannelMask( AudioFormat.CHANNEL_OUT_MONO);
                    /*  AudioTrack 创建模式mode
					*	AudioTrack.MODE_STATIC:播放音频前一次性将全部的音频数据从java传递到native层
					*	AudioTrack.MODE_STREAM:播放音频时将音频数据用数据流的方式从java传递到native层
					*/
                    mAudioTrack = new AudioTrack(attr.build(), format.build(), mAudioData.length, AudioTrack.MODE_STATIC,AudioManager.AUDIO_SESSION_ID_GENERATE);
                    mAudioTrack.write(mAudioData, 0, mAudioData.length);
                    mAudioTrack.play();
                }
            }
        });

        mPauseBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(mAudioTrack != null){
                    mAudioTrack.pause();
                }
                mPlayBtn.setEnabled(true);
            }
        });

		//用一个异步任务将音频数据读出来
        new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... params) {
                try {
                	//音频文件放在res/raw/目录下
                    InputStream in = getResources().openRawResource(R.raw.bingyu);
                    try {
                    	//读出音频流数据
                        ByteArrayOutputStream out = new ByteArrayOutputStream(in.available());
                        for (int b; (b = in.read()) != -1;) {
                            out.write(b);
                        }
                        mAudioData = out.toByteArray();
                    } finally {
                        in.close();
                    }
                } catch (IOException e) {
                }
                return null;
            }

            @Override
            protected void onPostExecute(Void v) {
                mPlayBtn.setEnabled(true);
            }
        }.execute();
    }

	//释放AudioTrack资源
    private void releaseAudioTrack() {
        if (mAudioTrack != null) {
            mAudioTrack.stop();
            mAudioTrack.release();
            mAudioTrack = null;
        }
    }
    
    @Override
    protected void onStop() {
        super.onStop();
        releaseAudioTrack();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        releaseAudioTrack();
    }
}

具体代码项目地址:https://github.com/sunxiaolin2016/Android-Audio

二、Frameworks层

Frameworks层相关代码:frameworks/base/media/java/android/media/

在app中创建AudioTrack的过程中,比较关键的一段代码为:
mAudioTrack = new AudioTrack(attr.build(), format.build(), mAudioData.length, AudioTrack.MODE_STATIC,AudioManager.AUDIO_SESSION_ID_GENERATE);

这段代码将在/frameworks/base/media/java/android/media/AudioTrack.java中做初始化,接下来我们来看AudioTrack中代码:

private AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
            int mode, int sessionId, boolean offload) throws IllegalArgumentException {
    super(attributes, AudioPlaybackConfiguration.PLAYER_TYPE_JAM_AUDIOTRACK);
	
	//省略一部分代码
	...

    /* native层初始化
    * 这里调用了jni代码,/frameworks/base/core/jni/android_media_AudioTrack.cpp中android_media_AudioTrack_setup()函数
    * 最终将调用Audio Library层,frameworks/av/media/libaudioclient/AudioTrack.cpp,创建native AudioTrack object
    * 在Audio Library层,将执行lpTrack->set(...)函数,后面将详细讲解set()函数
    */
    // native initialization
    int initResult = native_setup(new WeakReference<AudioTrack>(this), mAttributes,
            sampleRate, mChannelMask, mChannelIndexMask, mAudioFormat,
            mNativeBufferSizeInBytes, mDataLoadMode, session, 0 /*nativeTrackInJavaObj*/,
            offload);
    if (initResult != SUCCESS) {
        loge("Error code "+initResult+" when initializing AudioTrack.");
        return; // with mState == STATE_UNINITIALIZED
    }

	//省略一部分代码
   	...
}

三、Audio Library层

Library层相关代码:frameworks/av/

Audio Library层是音频处理的核心,主要分为三个部分:AudioTrack,AudioFlinger和AudioPlicy:

  • AudioTrack:负责回放数据的输出;
  • AudioFlinger:音频策略的执行者,负责输入输出流设备的管理及音频流数据的传输处理;
  • AudioPolicy:音频策略的制定者,负责音频设备切换的策略抉择、音量调节策略等;

AudioTrack.cpp所在目录: frameworks/av/media/libaudioclient/
这部分内容被编译成库libaudioclient.so文件。

AudioFlinger.cpp所在目录:frameworks/av/services/audioflinger/
这部分内容被编译成库libaudioflinger.so文件。

AudioPolicyManager.cpp所在目录:frameworks/av/services/audiopolicy/
这部分内容被编译成库libaudioflinger.so文件。

AudioTrack和AudioFlinger的交互原理:

  • audiotrackcblk_t实现了一个环形FIFO;
  • AudioTrack是FIFO的数据生产者;
  • AudioFlinger是FIFO的数据消费者。

下面这张图很好的解释了AudioTrack和AudioFlinger之间的交互关系:
AudioTrack和AudioFlinger交互图

了解了交互原理之后,下面我们来分析一下具体的代码:

AudioTrack.cpp分析:
在frameworks层代码讲到,new AudioTrack()最终会创建生成一个native 的AudioTrack对象,并且执行Set()函数,代码如下:

status_t AudioTrack::set()
{
	//省略一部分代码
	...
	
	// 这里执行了createTrack_l(),接下来我们看createTrack_l()的处理
    // create the IAudioTrack
    status = createTrack_l();
    
	//省略一部分代码
   	...
}

status_t AudioTrack::createTrack_l()
{
	//省略一部分代码
	...
	//音频流类型,音频的采样率等信息放进了input里面
    IAudioFlinger::CreateTrackInput input;
    if (mStreamType != AUDIO_STREAM_DEFAULT) {
        stream_type_to_audio_attributes(mStreamType, &input.attr);
    } else {
        input.attr = mAttributes;
    }
    input.config = AUDIO_CONFIG_INITIALIZER;
    input.config.sample_rate = mSampleRate;
    input.config.channel_mask = mChannelMask;
    input.config.format = mFormat;
    input.config.offload_info = mOffloadInfoCopy;
    input.clientInfo.clientUid = mClientUid;
    input.clientInfo.clientPid = mClientPid;
    input.clientInfo.clientTid = -1;
    
	//省略一部分代码
	...
	
	//跟AudioFlinger进行交互,返回一个Track句柄,可以进行play,pause等控制操作
	//通过track->getCblk();得到在AudioFlinger中创建的FIFO(audiotrackcblk_t);
    sp<IAudioTrack> track = audioFlinger->createTrack(input,
                                                      output,
                                                      &status);
    if (status != NO_ERROR || output.outputId == AUDIO_IO_HANDLE_NONE) {
        ALOGE("AudioFlinger could not create track, status: %d output %d", status, output.outputId);
        if (status == NO_ERROR) {
            status = NO_INIT;
        }
        goto exit;
    }
    
	//省略一部分代码
   	...
}

AudioFlinger.cpp文件分析:
AudioTrack.cpp会通过Binder调用AudioFlinger.cpp中的createTrack()函数,代码如下:

// IAudioFlinger interface
sp<IAudioTrack> AudioFlinger::createTrack(const CreateTrackInput& input,
                                          CreateTrackOutput& output,
                                          status_t *status)
{
	//省略一部分代码
   	...
   	
   	/* AudioSystem::getOutputForAttr() 经过一系列的调用,进入 AudioPolicyManager::getOutputForDevice()
   	*  最终调用 AudioFlinger::openOutput() 根据音频策略,打开输出标识对应的输出流设备并创建相关的PlaybackThread,		
   	*  并保存该 PlaybackThread 对应的 audio_io_handle_t 给 AudioTrack;
   	*/
    lStatus = AudioSystem::getOutputForAttr(&input.attr, &output.outputId, sessionId, &streamType,
                                            clientPid, clientUid, &input.config, input.flags,
                                            &output.selectedDeviceId, &portId);

    if (lStatus != NO_ERROR || output.outputId == AUDIO_IO_HANDLE_NONE) {
        ALOGE("createTrack() getOutputForAttr() return error %d or invalid output handle", lStatus);
        goto Exit;
    }
    
    //省略一部分代码
   	...
   	
   	//根据outputId获取相对应的音频回放线程,音频数据的读写主要在这个PlaybackThread中进行
    Mutex::Autolock _l(mLock);
    PlaybackThread *thread = checkPlaybackThread_l(output.outputId);
    if (thread == NULL) {
        ALOGE("no playback thread found for output handle %d", output.outputId);
        lStatus = BAD_VALUE;
        goto Exit;
    }

    client = registerPid(clientPid);

    PlaybackThread *effectThread = NULL;
    // check if an effect chain with the same session ID is present on another
    // output thread and move it here.
    for (size_t i = 0; i < mPlaybackThreads.size(); i++) {
        sp<PlaybackThread> t = mPlaybackThreads.valueAt(i);
        if (mPlaybackThreads.keyAt(i) != output.outputId) {
            uint32_t sessions = t->hasAudioSession(sessionId);
            if (sessions & ThreadBase::EFFECT_SESSION) {
                effectThread = t.get();
                break;
            }
        }
    }
    ALOGV("createTrack() sessionId: %d", sessionId);

    output.sampleRate = input.config.sample_rate;
    output.frameCount = input.frameCount;
    output.notificationFrameCount = input.notificationFrameCount;
    output.flags = input.flags;

	//这里创建了一个track并最终返回给AudioTrack.cpp中
    track = thread->createTrack_l(client, streamType, input.attr, &output.sampleRate,
                                  input.config.format, input.config.channel_mask,
                                  &output.frameCount, &output.notificationFrameCount,
                                  input.notificationsPerBuffer, input.speed,
                                  input.sharedBuffer, sessionId, &output.flags,
                                  input.clientInfo.clientTid, clientUid, &lStatus, portId);
    
	//省略一部分代码
   	...
}

//接下来我们来看PlaybackThread::createTrack_l中做了哪些处理
// PlaybackThread::createTrack_l() must be called with AudioFlinger::mLock held
sp<AudioFlinger::PlaybackThread::Track> AudioFlinger::PlaybackThread::createTrack_l(
        const sp<AudioFlinger::Client>& client,
        audio_stream_type_t streamType,
        const audio_attributes_t& attr,
        uint32_t *pSampleRate,
        audio_format_t format,
        audio_channel_mask_t channelMask,
        size_t *pFrameCount,
        size_t *pNotificationFrameCount,
        uint32_t notificationsPerBuffer,
        float speed,
        const sp<IMemory>& sharedBuffer,
        audio_session_t sessionId,
        audio_output_flags_t *flags,
        pid_t tid,
        uid_t uid,
        status_t *status,
        audio_port_handle_t portId)
{

	//省略一部分代码
   	...
   	//创建了一个Track对象
   	//核心代码为Track的构造函数,下面我们看看Track的构造函数做了哪些处理
    track = new Track(this, client, streamType, attr, sampleRate, format,
                      channelMask, frameCount,
                      nullptr /* buffer */, (size_t)0 /* bufferSize */, sharedBuffer,
                      sessionId, uid, *flags, TrackBase::TYPE_DEFAULT, portId);

	//省略一部分代码
   	...
}

// Track constructor must be called with AudioFlinger::mLock and ThreadBase::mLock held
AudioFlinger::PlaybackThread::Track::Track(...)
{
	//省略一部分代码
   	...
   	
    if (sharedBuffer == 0) {
    	/* MODE_STREAM
    	*  AudioTrack创建时,数据传输模式为 MODE_STREAM 模式,创建一个 AudioTrackServerProxy 对象,
    	*  PlaybackThread 将持续使用它从 FIFO 上取得可读数据的位置.
    	*/
        mAudioTrackServerProxy = new AudioTrackServerProxy(mCblk, mBuffer, frameCount,
                mFrameSize, !isExternalTrack(), sampleRate);
    } else {
        /* MODE_STATIC
    	*  AudioTrack创建时,数据传输模式为 MODE_STREAM 模式,创建一个 AudioTrackServerProxy 对象,
    	*  PlaybackThread 将持续使用它从 FIFO 上取得可读数据的位置.
    	*/
        mAudioTrackServerProxy = new StaticAudioTrackServerProxy(mCblk, mBuffer, frameCount,
                mFrameSize);
    }
    mServerProxy = mAudioTrackServerProxy;
    
	//省略一部分代码
   	...
}

到这里,整个从AudioTrack到AudioFlinger之间的流程都走下来了。其中还有一些细节,后面在慢慢分析,关于AudioPolicy的音频策略管理部分,我们放到下一章节在讲。

四、HAL层

HAL是AudioFlinger直接访问的对象,AudioFlingerd打开HAL层的输出流设备后,将从FIFO中读到的音频数据发送HAL层,由HAL层输出到硬件设备中进行声音输出。这一点后面在单独发文章出来分析。

参考资料:
1.https://blog.csdn.net/Ch97CKd/article/details/78641457

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sunxiaolin2016

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值