Android音频管理:AudioFlinger分析

1.Android音频框架概述

Audio是Android平台非常重要的一个组成部分,负责音频数据的输出采集,音频流的控制,音量调节,音频设备的管理等。我们先来简单介绍一下播放一首音乐的流程:首先应用层点击播放音乐的时候应用会通过mediaplayer接口去启动Android系统的播放器NuPlayer。其实mp3或者flac等格式的音频文件就是一个特殊的压缩文件,所以播放一个音频文件首先要做的就是要去解压缩,Nuplayer通过mediaextractor(高通的有自己的解封装模块mmparser)去解封装文件,获取到音频的一些基本信息(歌名,歌手,持续时间等),然后把解封装好的数据通过ACodec传给OMX去解码(排除offload模式的adsp侧解码),然后NuPlayer拿到解码后的pcm数据通过AudioSink与AudioTrack交互去把pcm数据传给device设备,最后NuPlayer通过自身的子类NuPlayerRenderer去渲染播放。NuPlayer同样是属于一个非常复杂的大的框架,今天我们首先来分析,AudioTrack拿到pcm数据之后是怎么传递给硬件设备去播放的。我们以一个图来展示一下这个框架。
在这里插入图片描述Audio框架图(此图来自于网络)
接下来我们来简单介绍一下Audio的一些类:

  • Audio Application framework:音频应用框架
    • AudioTrack:负责音频数据的输出,属 Android 应用框架 API 类
    • AudioRecord:负责录音数据的采集,属 Android 应用框架 API 类
    • AudioSystem:负责音频策略的管理,属 Android 应用框架 API 类
  • Audio Native framework:音频本地框架
    • AudioTrack:负责音频数据的输出,属 Android 应用框架 API 类
    • AudioRecord:负责录音数据的采集,属 Android 应用框架 API 类
    • AudioSystem:负责音频策略的管理,属 Android 应用框架 API 类
  • Audio Services:音频服务
    • AudioPolicyService:音频策略的制定者,负责音频设备切换的策略抉择,音量调节等
    • AudioFlinger:音频策略的执行者,负责输入输出流设备的管理以及pcm数据的处理传输
  • Audio HAL:硬件抽象层,介于Audio Services到kernel之间,负责音频与硬件设备的交互,是由AudioFLinger调用的。

2.AudioTrack API概述

播放声音可以使用MediaPlayer和AudioTrack,MediaPlayer可以通过OMX去解码,所以可以播放flac等音频文件。但是AudioTrack只能播放解码后的pcm数据,通过上面的介绍我们也知道MediaPlayer解码后的数据也是需要去创建对应的track去把pcm数据传送给AudioTrack去输出的。

2.1.AudioTrack Java API

AudioTrack Java API两种数据传输方式:

  • MODE_STATIC:应用进程一次性将数据传送给AudioTrack,适用于数据量小,延时要求较高的场景
  • MODE_STREAM:进程通过持续调用write把数据写到FIFO中,写数据的时候有可能会有阻塞,因为需要等待AudioFlinger处理完之前的数据,基本上适用于所有的音频场景。
StreamTypeDescription
STREAM_VOICE_CALL通话
STREAM_SYSTEM系统声音
STREAM_RING铃声,来电铃声闹钟铃声等
STREAM_ALARM告警音
STREAM_NOTIFICATION通知音
STREAM_DTMF拨号盘按键音
STREAM_MUSIC音乐声音

这些流类型主要是提供给AudioPolicy进行音频流的管理使用,这样就可以做到每个类型的音频流的音量可以独立开来,互不影响。并且也包括根据不同的流类型去选择不同的输出设备,这属于AudioPolicy策略管理的范畴,本文不做过多分析了。

2.2.AudioTrack Native API

AudioTrack Native API的数据传输模式:

Transfer ModeDescription
TRANSFER_CALLBACK在 AudioTrackThread 线程中通过 audioCallback 回调函数主动从应用进程那里索取数据,ToneGenerator 采用这种模式
TRANSFER_OBTAIN应用进程需要调用 obtainBuffer()/releaseBuffer() 填充数据
TRANSFER_SYNC应用进程需要持续调用 write() 写数据到 FIFO,对应于 AudioTrack Java API 的 MODE_STREAM 模式
TRANSFER_SHARED应用进程将回放数据一次性付给 AudioTrack,对应于 AudioTrack Java API 的 MODE_STATIC 模式

AudioTrack Native API 输出模式标识:

AUDIO_OUTPUT_FLAGDescription
AUDIO_OUTPUT_FLAG_DIRECT直接输出到音频设备,不需要软件混音,一般用于HDMI
AUDIO_OUTPUT_FLAG_PRIMARY音频流需要输出到主音频设备,一般用于铃声
AUDIO_OUTPUT_FLAG_FAST音频流需要快速输出到音频设备,一般用于按键音游戏背景音等对延时要求较高的场景
AUDIO_OUTPUT_FLAG_DEEP_BUFFER对延时要求不高的场景,音频视频播放等
AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD没有经过软件解码,直接到adsp解码

到这里AudioTrack就介绍完成了,下面我们需要来简单看一下Application是如何调用AudioTrack的。

2.3.从Application到AudioTrack

首先应用程序需要通过new AudioTrack去创建AudioTrack,然后调用play函数去播放,接着调用write函数写入数据。当播放完成之后调用stop停止播放并调用release函数去释放track。下面我们来简单分析一下这几个对应的函数。

2.3.1.AudioTrack的构造函数

构造函数肯定是做一些初始化操作

参数意义
streamType流类型,比如music,system等
sampleRateInHz采样率
channelConfig通道数
audioFormat音频格式,AudioFormat类中有相关定义
bufferSizeInBytes最小数据缓冲区的大小
mode模式,也就是上面介绍的MODE_STATIC和MODE_STATIC

这里面bufferSizeInBytes有个疑问,这个值多大合适呢?通过分析代码我们发现,这个值是可以通过调用getMinBufferSize函数去计算的,所以我们需要简单的看一下getMinBufferSize函数。

2.3.2.getMinBufferSize函数分析

getMinBufferSize函数会通过jni调用到native的AudioTrack::getMinFrameCount函数中,从函数参数来看,这个函数的返回值取决于采样率,采样深度和通道数这三个属性。MODE_STREAM 模式下,应用程序重点参考其返回值然后确定分配多大的数据缓冲区。如果数据缓冲区分配得过小,那么播放声音会频繁遭遇 underrun,underrun 是指生产者AudioTrack提供数据的速度跟不上消费者AudioFlinger消耗数据的速度,反映到现实的后果就是声音断续卡顿,严重影响听觉体验。简单看一下这块的代码

status_t AudioTrack::getMinFrameCount(
        size_t* frameCount,
        audio_stream_type_t streamType,
        uint32_t sampleRate)
{
    if (frameCount == NULL) {
        return BAD_VALUE;
    }
    uint32_t afSampleRate;
    status_t status;
    status = AudioSystem::getOutputSamplingRate(&afSampleRate, streamType);
    if (status != NO_ERROR) {
        ALOGE("Unable to query output sample rate for stream type %d; status %d",
                streamType, status);
        return status;
    }
    size_t afFrameCount;
    status = AudioSystem::getOutputFrameCount(&afFrameCount, streamType);
    if (status != NO_ERROR) {
        ALOGE("Unable to query output frame count for stream type %d; status %d",
                streamType, status);
        return status;
    }
    uint32_t afLatency;
    status = AudioSystem::getOutputLatency(&afLatency, streamType);
    if (status != NO_ERROR) {
        ALOGE("Unable to query output latency for stream type %d; status %d",
                streamType, status);
        return status;
    }

    if (*frameCount == 0) {
        ALOGE("AudioTrack::getMinFrameCount failed for streamType %d, sampleRate %u",
                streamType, sampleRate);
        return BAD_VALUE;
    }
    ALOGV("getMinFrameCount=%zu: afFrameCount=%zu, afSampleRate=%u, afLatency=%u",
            *frameCount, afFrameCount, afSampleRate, afLatency);
    return NO_ERROR;
}

通过上面的代码我们可以看到getMinFrameCount通过调用AudioSystem的getOutputSamplingRate去获取采样率,getOutputFrameCount函数去获取帧数,getOutputLatency函数去获取latency的大小,然后取得afSampleRate、afFrameCount、afLatency之后调用calculateMinFrameCount函数去计算一个最低帧数,calculateMinFrameCount这个函数会涉及到重采样的一些原理。AudioSystem的这三个函数会通过binder调用到AudioFlinger中对应的函数中去,这个后面再分析。这里有几个概念需要了解一下,帧、latency和重采样的概念。

  • 帧:帧表示一个完整的声音单元,所谓的声音单元是指一个采样样本;如果是双声道,那么一个完整的声音单元就是 2 个样本,如果是 5.1 声道,那么一个完整的声音单元就是 6 个样本了。帧的大小(一个完整的声音单元的数据量)等于声道数乘以采样深度,即 frameSize = channelCount * bytesPerSample。帧的概念非常重要,无论是框架层还是内核层,都是以帧为单位去管理音频数据缓冲区的。
  • latency:传输延迟的意思,传输延迟表示一个周期的音频数据的传输时间。可能有些读者一脸懵逼,一个周期的音频数据,这又是啥?我们再引入周期(period)的概念:Linux ALSA 把数据缓冲区划分为若干个块,dma 每传输完一个块上的数据即发出一个硬件中断,cpu 收到中断信号后,再配置 dma 去传输下一个块上的数据;一个块即是一个周期,周期大小(periodSize)即是一个数据块的帧数。再回到传输延迟(latency),传输延迟等于周期大小除以采样率,即 latency = periodSize / sampleRate。
  • 重采样:音频重采样是指这样的一个过程——把一个采样率的数据转换为另一个采样率的数据。Android 原生系统上,音频硬件设备一般都工作在一个固定的采样率上(如 48 KHz),因此所有音轨数据都需要重采样到这个固定的采样率上,然后再输出。为什么这么做?系统中可能存在多个音轨同时播放,而每个音轨的采样率可能是不一致的;比如在播放音乐的过程中,来了一个提示音,这时需要把音乐和提示音混音并输出到硬件设备,而音乐的采样率和提示音的采样率不一致,问题来了,如果硬件设备工作的采样率设置为音乐的采样率的话,那么提示音就会失真;因此最简单见效的解决方法是:硬件设备工作的采样率固定一个值,所有音轨在 AudioFlinger 都重采样到这个采样率上,混音后输出到硬件设备,保证所有音轨听起来都不失真。

3.AudioFlinger概述

AudioFlinger和AudioPolicy都是音频中非常重要的两个服务,AudioPolicy负责音频策略的制定,AudioFLinger负责策略的执行以及音频流设备的管理和数据的传输等。

3.1.AudioFlinger一些类介绍

  • AudioResampler.cpp:重采样处理类,采样率转换和声道转换等。AudioMixer在混音的时候会用到,代码在frameworks/av/media/libaudioprocessing下
  • AudioMixer.cpp:混音处理类,包括重采样、音量调节、声道转换等。是MixerThread调用的,代码在frameworks/av/media/libaudioprocessing下
  • Effects.cpp:音效处理类
  • Tracks.cpp:音频流管理类,控制音频流的状态,比如start,stop等
  • Threads.cpp:播放和录音线程类,播放是从环形FIFO读取数据混音然后把数据写到输出流设备中,录音是从输入流设备中读取数据进行重采样然后把数据写到FIFO中
  • AudioFlinger.cpp:对外提供服务的一个接口

AudioFlinger服务是由audioserver加载启动的。在main_audioserver.cpp中

        android::hardware::configureRpcThreadpool(4, false /*callerWillJoin*/);
        sp<ProcessState> proc(ProcessState::self());
        sp<IServiceManager> sm = defaultServiceManager();
        ALOGI("ServiceManager: %p", sm.get());
        AudioFlinger::instantiate();
        AudioPolicyService::instantiate();

        aaudio_policy_t mmapPolicy = property_get_int32(AAUDIO_PROP_MMAP_POLICY,
                                                        AAUDIO_POLICY_NEVER);
        if (mmapPolicy == AAUDIO_POLICY_AUTO || mmapPolicy == AAUDIO_POLICY_ALWAYS) {
            AAudioService::instantiate();
        }

        SoundTriggerHwService::instantiate();

我们看到在audioserver启动的时候会把AudioFlinger,AudioPolicy和SoundTrigger都加载上。

3.2.AudioFlinger对外提供的接口

InterfaceDescription
sampleRate获取硬件设备的采样率
format获取硬件设备的音频格式
frameCount获取硬件设备的周期帧数
latency获取硬件设备的传输延迟
setMasterVolume调节主输出设备的音量
setMasterMute主输出设备静音
setStreamVolume调节指定流类型的音量
setStreamMute指定流静音
setVoiceVolume调节通话音量
setMicMutemic输入静音
setMode切换模式,normal,ringtone,call,communication
setParameters设置音频参数,调用hal接口,切换通道用的比较多
getParameters获取音频参数
openOutput打开输出流设备,并创建PlaybackThread对象
closeOutput关闭输出流设备,并销毁所有track
openInput打开输入流设备,创建RecordThread对象
closeInput关闭输入流设备
createTrack新建输出流管理对象,找到对应的播放线程,创建track并返回track代理对象的TrackHandle
openRecord新建输入流管理对象,找到RecordThread,创建RecordTrack并返回RecordHandle

3.3.AudioFlinger输入输出线程

  • ThreadBase:播放和录制线程的基类
  • RecordThread:录制线程,由ThreadBase派生
  • PlaybackThread:播放线程,由ThreadBase派生
  • MixerThread:混音线程,由PlaybackThread派生,对应的flag是AUDIO_OUTPUT_FLAG_PRIMARY,AUDIO_OUTPUT_FLAG_FAST和AUDIO_OUTPUT_FLAG_DEEP_BUFFER
  • DirectOutputThread:直接输出线程,由PlaybackThread派生,AUDIO_OUTPUT_FLAG_DIRECT,不需要软件混音,直接输出到音频设备
  • DuplicatingThread:复制播放类,由MixerThread派生,负责把音频数据复制到其他输出设备,主声卡,蓝牙和USB等同时输出的情况
  • OffloadThread:硬解输出线程,由DirectOutputThread派生,AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD ,没有经过软件解码直接输出到硬件设备由adsp解码。
    在这里插入图片描述

3.4.AudioFlinger的track管理

AudioTrack把音频流数据送入到对应的 PlaybackThread 中,那么应用进程想控制这些音频流的话,比如开始播放 start()、停止播放 stop()、暂停播放 pause(),怎么办呢?注意应用进程与 AudioFlinger 并不在一个进程上。这就需要 AudioFlinger 提供音频流管理功能,并提供一套通讯接口可以让应用进程跨进程控制 AudioFlinger 中的音频流状态。

AudioFlinger 音频流管理由 AudioFlinger::PlaybackThread::Track 实现,Track 与 AudioTrack 是一对一的关系,一个 AudioTrack 创建后,那么 AudioFlinger 会创建一个 Track 与之对应;PlaybackThread 与 AudioTrack/Track 是一对多的关系,一个 PlaybackThread 可以挂着多个 Track。AudioTrack 创建后,AudioPolicyManager 根据 AudioTrack 的输出标识和流类型,找到对应的输出流设备和 PlaybackThread(如果没有找到的话,则系统会打开对应的输出流设备并新建一个 PlaybackThread),然后创建一个 Track 并挂到这个 PlaybackThread 下面。
对于track的管理有两个成员容器比较重要:

  • mTracks:PlaybackThread创建的所有track都添加到这里。
  • mActiveTracks:设置了active状态需要播放的track会添加到这里,PlaybackThread找到这里所有的active track,把这些track中的数据进行混音然后输出。

4.从AudioTrack到AudioFlinger

接下来我们需要跟踪代码来看一下播放一个音乐,是怎样把数据从AudioTrack传输到AudioFlinger并到hal层甚至最终到达adsp的。
首先看一下AudioTrack的构造函数,构造函数会调用到set函数中

4.1.AudioTrack的set函数分析

status_t AudioTrack::set(
        audio_stream_type_t streamType,
        uint32_t sampleRate,
        audio_format_t format,
        audio_channel_mask_t channelMask,
        size_t frameCount,
        audio_output_flags_t flags,
        callback_t cbf,
        void* user,
        int32_t notificationFrames,
        const sp<IMemory>& sharedBuffer,
        bool threadCanCallJava,
        audio_session_t sessionId,
        transfer_type transferType,
        const audio_offload_info_t *offloadInfo,
        uid_t uid,
        pid_t pid,
        const audio_attributes_t* pAttributes,
        bool doNotReconnect,
        float maxRequiredSpeed,
        audio_port_handle_t selectedDeviceId)
{

    mSharedBuffer = sharedBuffer;
    //。。。
    //如果回调函数为NULL,那么就创建AudioTrackThread去处理audiocallback回调
    if (cbf != NULL) {
        mAudioTrackThread = new AudioTrackThread(*this);
        mAudioTrackThread->run("AudioTrack", ANDROID_PRIORITY_AUDIO, 0 /*stack*/);
        // thread begins in paused state, and will not reference us until start()
    }
    //。。。
    //调用createTrack_l函数去创建一个对应的track
    // create the IAudioTrack
    {
        AutoMutex lock(mLock);
        status = createTrack_l();
    }
}

从上面的代码中我们可以看到set函数首先会去检查参数的合法性,然后初始化音量等,然后调用createTrack_l函数去创建track。
注:这篇文章以输出为主线展开分析

4.2.AudioTrack的createTrack_l函数分析

status_t AudioTrack::createTrack_l()
{
   //通过binder去调用AudioFlinger的createTrack函数
    sp<IAudioTrack> track = audioFlinger->createTrack(input,
                                                      output,
                                                      &status);
     //AudioFlinger创建track对象的时候会分配一个FIFO,这里获取这个FIFO的控制块                                                 
    // FIXME compare to AudioRecord
    sp<IMemory> iMem = track->getCblk();
    if (iMem == 0) {
        ALOGE("%s(%d): Could not get control block", __func__, mPortId);
        status = NO_INIT;
        goto exit;
    }
    //匿名共享内存首地址
    void *iMemPointer = iMem->pointer();
    if (iMemPointer == NULL) {
        ALOGE("%s(%d): Could not get control block pointer", __func__, mPortId);
        status = NO_INIT;
        goto exit;
    }
    //保存 AudioFlinger::PlaybackThread::Track 的代理对象 IAudioTrack
    mAudioTrack = track;
    //保存匿名共享内存首地址
    mCblkMemory = iMem;
    IPCThreadState::self()->flushCommands();

   //控制块位于AudioFlinger分配的共享内存的首部
    audio_track_cblk_t* cblk = static_cast<audio_track_cblk_t*>(iMemPointer);
    mCblk = cblk;

    //保存audio_io_handle_t,通过binder从AudioFlinger获取的,用它能找到对应的播放线程
    mOutput = output.outputId;
    mRefreshRemaining = true;

    // update proxy
    //更新共享内存
    if (mSharedBuffer == 0) {
        mStaticProxy.clear();
        mProxy = new AudioTrackClientProxy(cblk, buffers, mFrameCount, mFrameSize);
    } else {
        mStaticProxy = new StaticAudioTrackClientProxy(cblk, buffers, mFrameCount, mFrameSize);
        mProxy = mStaticProxy;
    }
    }
}

通过上面的代码分析我们知道createTrack_l会通过binder调用到AudioFlinger的createTrack函数去打开输出设备,创建共享内存以及创建track等操作。

4.3.AudioFlinger的createTrack函数分析

sp<IAudioTrack> AudioFlinger::createTrack(const CreateTrackInput& input,
                                          CreateTrackOutput& output,
                                          status_t *status)
{
    //通过AudioSystem,经过binder调用会到AudioPolicyManager的getOutputForDevices函数,然后通过一些列调用,会回到AudioFlinger的openOutput函数中,然后根据不同的flag会创建对应的播放线程
    lStatus = AudioSystem::getOutputForAttr(&localAttr, &output.outputId, sessionId, &streamType,
                                            clientPid, clientUid, &input.config, input.flags,
                                            &output.selectedDeviceId, &portId, &secondaryOutputs);
        
        //在playbackThread上创建一个音频流管理对象track                                    
        track = thread->createTrack_l(client, streamType, localAttr, &output.sampleRate,
                                      input.config.format, input.config.channel_mask,
                                      &output.frameCount, &output.notificationFrameCount,
                                      input.notificationsPerBuffer, input.speed,
                                      input.sharedBuffer, sessionId, &output.flags,
                                      callingPid, input.clientInfo.clientTid, clientUid,
                                      &lStatus, portId);
     
     //创建 Track 的通讯代理 TrackHandle 并返回它                                
    // return handle to client
    trackHandle = new TrackHandle(track);

4.4.openOutput流程分析

openOutput函数直接调用到AudioFlinger::openOutput_l函数,这里面会调用到AudioHwDevice::openOutputStream函数,再到AudioStreamOut::open函数,然后到DeviceHalLocal::openOutputStream函数,然后根据audio_hw_device结构体会跟到audio_hw的adev_open_output_stream函数,这是hal层的函数,在这里去打开对应的output device以及根据flag,stream和device去找到对应的usecase和snd_device。再回到AudioFlinger::openOutput_l函数,这里面会根据不同的flag去创建对应的播放线程。然后我们回到AudioFlinger::createTrack函数中去看看createTrack_l函数做了什么,这个函数主要是new了一个track,并且把track添加到mTracks容器中,方便PlaybackThread 管理,接下来我们看一下track是怎么创建的,重点关注FIFO的创建。我们用一张图来总结一下各个播放线程所对应的hal层的usecase。
在这里插入图片描述

4.5.track的创建

AudioFlinger::PlaybackThread::Track::Track(
            PlaybackThread *thread,
            const sp<Client>& client,
            audio_stream_type_t streamType,
            const audio_attributes_t& attr,
            uint32_t sampleRate,
            audio_format_t format,
            audio_channel_mask_t channelMask,
            size_t frameCount,
            void *buffer,
            size_t bufferSize,
            const sp<IMemory>& sharedBuffer,
            audio_session_t sessionId,
            pid_t creatorPid,
            uid_t uid,
            audio_output_flags_t flags,
            track_type type,
            audio_port_handle_t portId,
            size_t frameCountToBeReady)
    :   TrackBase(thread, client, attr, sampleRate, format, channelMask, frameCount,
                  (sharedBuffer != 0) ? sharedBuffer->pointer() : buffer,
                  (sharedBuffer != 0) ? sharedBuffer->size() : bufferSize,
                  sessionId, creatorPid, uid, true /*isOut*/,
                  (type == TYPE_PATCH) ? ( buffer == NULL ? ALLOC_LOCAL : ALLOC_NONE) : ALLOC_CBLK,
                  type, portId),
    mFillingUpStatus(FS_INVALID),
    // mRetryCount initialized later when needed
    mSharedBuffer(sharedBuffer),
    mStreamType(streamType),
    mMainBuffer(thread->sinkBuffer()),
    mAuxBuffer(NULL),
    mAuxEffectId(0), mHasVolumeController(false),
    mPresentationCompleteFrames(0),
    mFrameMap(16 /* sink-frame-to-track-frame map memory */),
    mVolumeHandler(new media::VolumeHandler(sampleRate)),
    mOpPlayAudioMonitor(OpPlayAudioMonitor::createIfNeeded(uid, attr, id(), streamType)),
    // mSinkTimestamp
    mFrameCountToBeReady(frameCountToBeReady),
    mFastIndex(-1),
    mCachedVolume(1.0),
    /* The track might not play immediately after being active, similarly as if its volume was 0.
     * When the track starts playing, its volume will be computed. */
    mFinalVolume(0.f),
    mResumeToStopping(false),
    mFlushHwPending(false),
    mFlags(flags)
{
    if (mCblk == NULL) {
        return;
    }
}

通过上面的代码,我们发现当mCblk不为NULL的时候,函数才能继续执行下去,所以我们需要看一下mCblk 是在哪分配的,也就是FIFO的创建,我们找到了它的基类TrackBase。

AudioFlinger::ThreadBase::TrackBase::TrackBase(
            ThreadBase *thread,
            const sp<Client>& client,
            uint32_t sampleRate,
            audio_format_t format,
            audio_channel_mask_t channelMask,
            size_t frameCount,
            void *buffer,
            size_t bufferSize,
            audio_session_t sessionId,
            uid_t clientUid,
            bool isOut,
            alloc_type alloc,
            track_type type,
            audio_port_handle_t portId)
    :   RefBase(),
        mThread(thread),
        mClient(client),
        mCblk(NULL),
        // mBuffer, mBufferSize
        mState(IDLE),
        mSampleRate(sampleRate),
        mFormat(format),
        mChannelMask(channelMask),
        mChannelCount(isOut ?
                audio_channel_count_from_out_mask(channelMask) :
                audio_channel_count_from_in_mask(channelMask)),
        mFrameSize(audio_has_proportional_frames(format) ?
                mChannelCount * audio_bytes_per_sample(format) : sizeof(int8_t)),
        mFrameCount(frameCount),
        mSessionId(sessionId),
        mIsOut(isOut),
        mId(android_atomic_inc(&nextTrackId)),
        mTerminated(false),
        mType(type),
        mThreadIoHandle(thread->id()),
        mPortId(portId),
        mIsInvalid(false)
{

    if (client != 0) {
        //分配一块共享内存
        mCblkMemory = client->heap()->allocate(size);
        if (mCblkMemory == 0 ||
                (mCblk = static_cast<audio_track_cblk_t *>(mCblkMemory->pointer())) == NULL) {
            ALOGE("not enough memory for AudioTrack size=%zu", size);
            client->heap()->dump("AudioTrack");
            mCblkMemory.clear();
            return;
        }
    } else {
        // 这是 C++ 的 placement new(定位创建对象)语法:new(@BUFFER) @CLASS();
        // 可以在特定内存位置上构造一个对象
        // 这里,在匿名共享内存首地址上构造了一个 audio_track_cblk_t 对象
        // 这样 AudioTrack 与 AudioFlinger 都能访问这个 audio_track_cblk_t 对象了
        mCblk = (audio_track_cblk_t *) malloc(size);
        if (mCblk == NULL) {
            ALOGE("not enough memory for AudioTrack size=%zu", size);
            return;
        }
    }

    // construct the shared structure in-place.
    if (mCblk != NULL) {
        new(mCblk) audio_track_cblk_t();
	}
}

到这里,output的打开,track的创建以及FIFO的创建并与AudioFlinger的交互就分析完成了。所以接下来我们要从start函数入手去分析一下具体数据是怎么传输过去的。

4.6.AudioTrack到AudioFlinger的start

AudioTrack的start函数主要是用于实现Android的音频输出。我们先简单的看一下这个函数,首先是判断一些状态,播放暂停等,然后会通过binder调用到PlaybackThread::Track::start函数。这个函数首先是取出openOutput那个步骤创建的对应的thread(我们以MixerThread为例),然后调用的createTrack_l函数把thread添加到mPlaybackThreads进行管理,在这一步的时候我们就要取出这个thread,然后调用playbackThread->addTrack_l函数。
addTrack_l这个函数首先会判断track是否是新添加的,如果是新添加的就会调用AudioSystem::startOutput去初始化操作(这个函数通过AudioPolicyManager::startOutput会调用到AudioFlinger的setParameters然后到hal层的out_set_parameters去做一些播放前的准备工作),接下来把这个track添加到mActiveTracks中,然后发送广播通知Thread开始工作。接下来AuidoTrack会调用write函数去写数据。

4.7.out_set_parameters函数分析

out_set_parameters函数会调用到select_devices函数去选择输出设备,然后select_devices函数首先会调用到get_usecase_from_list函数去获取对应的usecase。然后调用audio_extn_auto_hal_get_output_snd_device函数根据usecase去获取对应的out_snd_device,然后通过platform_get_snd_device_name函数获取device name(这个device name就是与mixer_paths上的配置相对应),以及acdb_id等。然后会调用到enable_snd_device函数,然后通过audio_route_apply_and_update_path函数去找到device name在mixer_paths中对应的通路并打开。我们简单举个例子看一下device的对应关系,比如媒体播放,framework层打开的设备是AUDIO_DEVICE_OUT_BUS,到hal层首先通过audio_extn_auto_hal_get_output_snd_device获取到的hal层的输出设备out_snd_device是SND_DEVICE_OUT_BUS_MEDIA,然后通过platform_get_snd_device_name获取到的device name是“bus-speaker”。这块涉及hal层的打开通路的流程,感兴趣的可以看我的下一篇文章AudioHAL分析

4.8.写数据的过程分析

  • AudioTrack:AudioTrack 在 FIFO 中找到一块可用空间,把用户传入的音频数据写入到这块可用空间上,然后更新写位置(对于 AudioFinger 来说,意味 FIFO 上有更多的可读数据了);如果用户传入的数据量比可用空间要大,那么要把用户传入的数据拆分多次写入到 FIFO 中(AudioTrack 和 AudioFlinger 是不同的进程,AudioFlinger 同时也在不停地读取数据,所以 FIFO 可用空间是在不停变化的)
  • AudioFlinger:AudioFlinger 在 FIFO 中找到一块可读数据块,把可读数据拷贝到目的缓冲区上,然后更新读位置(对于 AudioTrack 来说,意味着 FIFO 上有更多的可用空间了);如果FIFO 上可读数据量比预期的要小,那么要进行多次的读取,才能积累到预期的数据量(AudioTrack 和 AudioFlinger 是不同的进程,AudioTrack 同时也在不停地写入数据,所以 FIFO 可读的数据量是在不停变化的)
    如果AudioTrack能及时产生数据,并且AudioFlinger总能及时消耗数据,那么就不会有异常出现,事实整明,容易出现以下几种异常:
  • Block: AudioFlinger长时间不读取数据,导致AudioTrack长时间获取不到可用空间,也就无法写入数据。这种情况一般都是底层驱动发生异常导致的。
  • Underrun:AudioTrack写数据的速度跟不上AudioFlinger读取数据的速度,这就会导致声音断续,这种情况一般都是应用程序不能及时写入数据,或者是缓冲区分配的过小导致的。AudioFlinger对这点做了容错处理,当发现underrun的时候,先短时间的睡眠,不着急读取数据,让应用程序准备更多的数据。

4.9.write函数分析

首先我们来看一下AudioTrack的write函数,这个函数比较简单,首先是obtainBuffer在FIFO中找到一块可用空间,然后memcpy把用户传入的数据copy到这个FIFO上,最后releaseBuffer更新可用空间。我们需要重点分析一下AudioFlinger读取FIFO的过程。

5.AudioFlinger读取FIFO

前面一章我们分析了AudioTrack的创建以及与AudioFlinger的交换,下面我们重点来看一下AudioFlinger是怎样读取FIFO中的数据的。
首先我们前面介绍过,在AudioFlinger的addTrack_l函数会发送广播去通知对应的thread开始工作,所以我们需要先看一下PlaybackThread::threadLoop函数。

5.1.PlaybackThread::threadLoop函数分析

这个函数比较大比较复杂,这里就不贴代码了,就介绍一下这个函数的功能

  • 退出循环的条件:exitPending() 返回 false
  • processConfigEvents_l:处理配置事件;当有配置改变的事件发生时,需要调用 sendConfigEvent_l() 来通知 PlaybackThread,这样 PlaybackThread 才能及时处理配置事件;常见的配置事件是切换音频通路。
  • standby:检查是否符合standby条件,如果没有active的track,就threadLoop_standby关闭音频硬件设备来节省能耗。
  • prepareTracks_l:准备音频流和混音器,这个函数也比较复杂。
    1. 遍历 mActiveTracks,逐个处理 mActiveTracks 上的 Track,检查该 Track 是否为 ACTIVE 状态
    2. 如果 Track 设置是 ACTIVE 状态,则再检查该 Track 的数据是否准备就绪了
    3. 根据音频流的音量值、格式、声道数、音轨的采样率、硬件设备的采样率,配置好混音器参数
    4. 如果 Track 的状态是 PAUSED 或 STOPPED,则把该 Track 添加到 tracksToRemove 向量中
  • threadLoop_mix():读取所有置了 ACTIVE 状态的音频流数据,混音器开始处理这些数据
  • threadLoop_write(): 把混音器处理后的数据写到输出流设备
  • threadLoop_removeTracks(): 把 tracksToRemove 上的所有 Track 从 mActiveTracks 中移除出来;这样下一次循环时就不会处理这些 Track 了

5.2.DirectOutputThread::threadLoop_mix函数分析

void AudioFlinger::DirectOutputThread::threadLoop_mix()
{
    size_t frameCount = mFrameCount;
    int8_t *curBuf = (int8_t *)mSinkBuffer;
    // output audio to hardware
    while (frameCount) {
        AudioBufferProvider::Buffer buffer;
        buffer.frameCount = frameCount;
        status_t status = mActiveTrack->getNextBuffer(&buffer);
        if (status != NO_ERROR || buffer.raw == NULL) {
            // no need to pad with 0 for compressed audio
            if (audio_has_proportional_frames(mFormat)) {
                memset(curBuf, 0, frameCount * mFrameSize);
            }
            break;
        }
        memcpy(curBuf, buffer.raw, buffer.frameCount * mFrameSize);
        frameCount -= buffer.frameCount;
        curBuf += buffer.frameCount * mFrameSize;
        mActiveTrack->releaseBuffer(&buffer);
    }
    mCurrentWriteLength = curBuf - (int8_t *)mSinkBuffer;
    mSleepTimeUs = 0;
    mStandbyTimeNs = systemTime() + mStandbyDelayNs;
    mActiveTrack.clear();
}

首先是一个wile循环,mFrameCount是PCM设备处理单个数据块的帧数,上层必须积累足够的mFrameCount数据才会写入到PCM中,所以mFrameCount也就是AudioFlinger预期的数据量。然后调用mActiveTrack->getNextBuffer获取可读数据,再调用memcpy把FIFO可读数据拷贝到mSinkBuffer目的缓冲区,最后调用releaseBuffer释放已读数据更新FIFO的位置,对于AudioTrack客户端来说就意味着有更多可用空间了。

5.2.PlaybackThread::threadLoop_write函数分析

这个函数会调用mOutput->write把上一步threadLoop_mix读取到的pcm数据通过HAL层最终写入到Codec中,mOutput->write会通过binder调用到hal层的out_write函数。out_write函数首先会判断是否是属于standby,如果是的话会调用start_output_stream函数去打开设备,然后就是调用pcm_write函数向codec中去写入数据。到这里AudioFlinger的一个大体流程就分析完成了,最后我们以一张图来总结一下从AudioTrack到AudioFlinger的过程。

  • AudioTrack到AudioFlinger关系图:
    在这里插入图片描述
  • 32
    点赞
  • 165
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值