分享

[Audio]从AudioTrack到AudioFlinger(下)

 开花结果 2022-03-08
文转载自zyuanyun的博客,原文地址为:https://blog.csdn.net/zyuanyun/article/details/60890534
转载请注明作者及原文链接。

4. AudioTrack 实例创建

现在我们开始分析 AudioTrack 的创建过程,特别留意 AudioTrack 与 AudioFlinger 如何建立联系、用于 AudioTrack 与 AudioFlinger 交换数据的匿名共享内存如何分配。

4.1. AudioTrack & AudioFlinger 相关类

首先看一下 AudioTrack & AudioFlinger 的类图,理一下 AudioFlinger 的主要类及其关系、AudioTrack 与 AudioFlinger 之间的联系,后面将以该图为脉络展开分析。

  • AudioFlinger::PlaybackThread:回放线程基类,不同输出标识的音频流对应不同类型的 PlaybackThread 实例(分为四种:MixerThread、DirectOutputThread、DuplicatingThread、OffloadThread),具体见 3.4. AudioFlinger 回放录制线程 小节,所有的 PlaybackThread 实例都会添加到 AudioFlinger.mPlaybackThreads 向量中;这个向量的定义: DefaultKeyedVector< audio_io_handle_t, sp > mPlaybackThreads;,可见 audio_io_handle_t 是与 PlaybackThread 是一一对应的,由已知的 audio_io_handle_t 就能找到对应的 PlaybackThread;audio_io_handle_t 在创建 PlaybackThread 时由系统分配,这个值是全局唯一的

  • AudioFlinger::PlaybackThread::Track:音频流管理类,创建一块匿名共享内存用于 AudioTrack 与 AudioFlinger 之间的数据交换(方便起见,这块匿名共享内存,以后均简单称为 FIFO),同时实现 start()、stop()、pause() 等音频流常用控制手段;注意,多个 Track 对象可能都注册到同一个 PlaybackThread 中(尤其对于 MixerThread 而言,一个 MixerThread 往往挂着多个 Track 对象),这多个 Track 对象都会添加到 PlaybackThread.mTracks 向量中统一管理

  • AudioFlinger::TrackHandle:Track 对象只负责音频流管理业务,对外并没有提供跨进程的 Binder 调用接口,而应用进程又需要对音频流进行控制,所以需要一个对象来代理 Track 的跨进程通讯,这个角色就是 TrackHandle,AudioTrack 通过它与 Track 交互

  • AudioTrack:Android 音频系统对外提供的一个 API 类,负责音频流数据输出;每个音频流对应着一个 AudioTrack 实例,不同输出标识的 AudioTrack 会匹配到不同的 AudioFlinger::PlaybackThread;AudioTrack 与 AudioFlinger::PlaybackThread 之间通过 FIFO 来交换音频数据,AudioTrack 是 FIFO 生产者,AudioFlinger::PlaybackThread 是 FIFO 消费者

  • AudioTrack::AudioTrackThread:数据传输模式为 TRANSFER_CALLBACK 时,需要创建该线程,它通过调用 audioCallback 回调函数主动从用户进程处索取数据并填充到 FIFO 上;数据传输模式为 TRANSFER_SYNC 时,则不需要创建这个线程,因为用户进程会持续调用 AudioTrack.write() 填充数据到 FIFO;数据传输模式为 TRANSFER_SHARED 时,也不需要创建这个线程,因为用户进程会创建一块匿名共享内存,并把要播放的音频数据一次性拷贝到这块匿名共享内存上了

  • IAudioTrack:IAudioTrack 是链结 AudioTrack 与 AudioFlinger 的桥梁;它在 AudioTrack 端的对象是 BpAudioTrack,在 AudioFlinger 端的对象是 BnAudioTrack,从图中不难看出,AudioFlinger::TrackHandle 继承自 BnAudioTrack,而 AudioFlinger::TrackHandle 恰恰是AudioFlinger::PlaybackThread::Track 的代理对象,所以 AudioTrack 得到 IAudioTrack 实例后,就可以调用 IAudioTrack 的接口与 AudioFlinger::PlaybackThread::Track 交互

audio_io_handle_t

这里再详细说明一下 audio_io_handle_t,它是 AudioTrack/AudioRecord/AudioSystem、AudioFlinger、AudioPolicyManager 之间一个重要的链结点。3.4. AudioFlinger 回放录制线程 小节在 AudioFlinger::openOutput_l() 注释中大致说明了它的来历及其作用,现在回顾下:当打开输出流设备及创建 PlaybackThread 时,系统会分配一个全局唯一的值作为 audio_io_handle_t,并把 audio_io_handle_t 和 PlaybackThread 添加到键值对向量 mPlaybackThreads 中,由于 audio_io_handle_t 和 PlaybackThread 是一一对应的关系,因此拿到一个 audio_io_handle_t,就能遍历键值对向量 mPlaybackThreads 找到它对应的 PlaybackThread,可以简单理解 audio_io_handle_t 为 PlaybackThread 的索引号或线程 id。由于 audio_io_handle_t 具有 PlaybackThread 索引特性,所以应用进程想获取 PlaybackThread 某些信息的话,只需要传入对应的 audio_io_handle_t 即可。例如 AudioFlinger::format(audio_io_handle_t output),这是 AudioFlinger 的一个服务接口,用户进程可以通过该接口获取某个 PlaybackThread 配置的音频格式:

audio_format_t AudioFlinger::format(audio_io_handle_t output) const{
    Mutex::Autolock _l(mLock);
    // checkPlaybackThread_l() 根据传入的 audio_io_handle_t,从键值对向量    // mPlaybackThreads 中找到它对应的 PlaybackThread    PlaybackThread *thread = checkPlaybackThread_l(output);
    if (thread == NULL) {
        ALOGW("format() unknown thread %d", output);
        return AUDIO_FORMAT_INVALID;
    }
    return thread->format();}AudioFlinger::PlaybackThread *AudioFlinger::checkPlaybackThread_l(audio_io_handle_t output) const{
    return mPlaybackThreads.valueFor(output).get();}

4.2. AudioTrack 构造过程

当我们构造一个 AudioTrack 实例时(以 MODE_STREAM/TRANSFER_SYNC 模式为例,这也是最常用的模式了,此时 sharedBuffer 为空),系统都发生了什么事?阐述下大致流程: 1. 如果 cbf(audioCallback 回调函数)非空,那么创建 AudioTrackThread 线程处理 audioCallback 回调函数(MODE_STREAM 模式时,cbf 为空); 2. 根据 streamType(流类型)、flags(输出标识)等参数调用 AudioSystem::getOutputForAttr();经过一系列的调用,进入 AudioPolicyManager::getOutputForDevice(): 1. 如果输出标识置了 AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD 或 AUDIO_OUTPUT_FLAG_DIRECT,那么最终调用 AudioFlinger::openOutput() 打开输出标识对应的输出流设备并创建相应的 PlaybackThread,保存该 PlaybackThread 对应的 audio_io_handle_t 给 AudioTrack; 2. 如果输出标识是其他类型,那么根据策略选择一个输出流设备和 PlaybackThread,并保存该 PlaybackThread 对应的 audio_io_handle_t 给 AudioTrack;别忘了在 3.4. AudioFlinger 回放录制线程 小节中提到:系统启动时,就已经打开 primary_out、low_latency、deep_buffer 这三种输出流设备,并创建对应的 PlaybackThread 了; 3. 通过 Binder 机制调用 AudioFlinger::createTrack()(注意 step2 中 AudioTrack 已经拿到一个 audio_io_handle_t 了,此时把这个 audio_io_handle_t 传入给 createTrack()): 1. 根据传入的 audio_io_handle_t 找到它对应的 PlaybackThread; 2. PlaybackThread 新建一个音频流管理对象 Track;Track 构造时会分配一块匿名共享内存用于 AudioFlinger 与 AudioTrack 的数据交换缓冲区(FIFO)及其控制块(audio_track_cblk_t),并创建一个 AudioTrackServerProxy 对象(PlaybackThread 将使用它从 FIFO 上取得可读数据的位置); 3. 最后新建一个 Track 的通讯代理 TrackHandle,并以 IAudioTrack 作为返回值给 AudioTrack(TrackHandle、BnAudioTrack、BpAudioTrack、IAudioTrack 的关系见上一个小节); 4. 通过 IAudioTrack 接口,取得 AudioFlinger 中的 FIFO 控制块(audio_track_cblk_t),由此再计算得到 FIFO 的首地址; 5. 创建一个 AudioTrackClientProxy 对象(AudioTrack 将使用它从 FIFO 上取得可用空间的位置);

AudioTrack 由此建立了和 AudioFlinger 的全部联系工作:

  • 通过 IAudioTrack 接口可以控制该音轨的状态,例如 start、stop、pause

  • 持续写入数据到 FIFO 上,实现音频连续播放

  • 通过 audio_io_handle_t,可以找到它对应的 PlaybackThread,从而查询该 PlaybackThread 的相关信息,如所设置的采样率、格式等等

构造 1 个 AudioTrack 实例时,AudioFlinger 会有 1 个 PlaybackThread 实例、1 个 Track 实例、1 个 TrackHandle 实例、1 个 AudioTrackServerProxy 实例、1 块 FIFO 与之对应。

当同时构造 1 个 AudioTrack with AUDIO_OUTPUT_FLAG_PRIMARY、1 个 AudioTrack with AUDIO_OUTPUT_FLAG_FAST、3 个 AudioTrack with AUDIO_OUTPUT_FLAG_DEEP_BUFFER、1 个 AudioTrack with AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD、1 个 AudioTrack with AUDIO_OUTPUT_FLAG_DIRECT 时(事实上,Android 音频策略不允许出现这种情形的),AudioFlinger 拥有的 PlaybackThread、Track、TrackHandle 实例如下图所示:

最后附上相关代码的流程分析,我本意是不多贴代码的,但不上代码总觉得缺点什么,这里我尽量把代码精简,提取主干,忽略细节。

AudioTrack::AudioTrack(
        audio_stream_type_t streamType,    // 音频流类型:如 Music、Voice-Call、DTMF、Alarm 等等        uint32_t sampleRate,               // 采样率:如 16KHz、44.1KHz、48KHz 等等        audio_format_t format,             // 音频格式:如 PCM、MP3、AAC 等等        audio_channel_mask_t channelMask,  // 声道数:如 Mono(单声道)、Stereo(双声道)        const sp<IMemory>& sharedBuffer,   // 共享内存缓冲区:数据模式是 MODE_STATIC 时使用,数据模式是 MODE_STREAM 时为空        audio_output_flags_t flags,        // 输出标识位,详见 AUDIO_OUTPUT_FLAG 描述        callback_t cbf,                    // 回调函数        void* user,                        // 回调函数的参数        uint32_t notificationFrames,
        int sessionId,
        transfer_type transferType,        // 数据传输类型        const audio_offload_info_t *offloadInfo,
        int uid,
        pid_t pid,
        const audio_attributes_t* pAttributes,
        bool doNotReconnect)
    : mStatus(NO_INIT),
      mIsTimed(false),
      mPreviousPriority(ANDROID_PRIORITY_NORMAL),
      mPreviousSchedulingGroup(SP_DEFAULT),
      mPausedPosition(0),
      mSelectedDeviceId(AUDIO_PORT_HANDLE_NONE){
    mStatus = set(streamType, sampleRate, format, channelMask,
            0 /*frameCount*/, flags, cbf, user, notificationFrames,
            sharedBuffer, false /*threadCanCallJava*/, sessionId, transferType, offloadInfo,
            uid, pid, pAttributes, doNotReconnect);}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,
        uint32_t notificationFrames,
        const sp<IMemory>& sharedBuffer,
        bool threadCanCallJava,
        int sessionId,
        transfer_type transferType,
        const audio_offload_info_t *offloadInfo,
        int uid,
        pid_t pid,
        const audio_attributes_t* pAttributes,
        bool doNotReconnect){
    // 参数格式合法性检查、音轨音量初始化
    // 如果 cbf 非空,那么创建 AudioTrackThread 线程处理 audioCallback 回调函数    if (cbf != NULL) {
        mAudioTrackThread = new AudioTrackThread(*this, threadCanCallJava);
        mAudioTrackThread->run("AudioTrack", ANDROID_PRIORITY_AUDIO, 0 /*stack*/);
        // thread begins in paused state, and will not reference us until start()    }

    // create the IAudioTrack    status_t status = createTrack_l();

    //......}status_t AudioTrack::createTrack_l(){
    // 获取 IAudioFlinger,通过 binder 请求 AudioFlinger 服务    const sp<IAudioFlinger>& audioFlinger = AudioSystem::get_audio_flinger();
    if (audioFlinger == 0) {
        ALOGE("Could not get audioflinger");
        return NO_INIT;
    }

    //......
    // AudioSystem::getOutputForAttr() 经过一系列的调用,进入 AudioPolicyManager::getOutputForDevice()    // 如果输出标识置了 AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD 或 AUDIO_OUTPUT_FLAG_DIRECT,    // 那么最终调用 AudioFlinger::openOutput() 打开输出标识对应的输出流设备并创建相关的    // PlaybackThread,保存该 PlaybackThread 对应的 audio_io_handle_t 给 AudioTrack;    // 如果输出标识是其他类型,那么根据策略选择一个输出流设备和 PlaybackThread,并保存该    // PlaybackThread 对应的 audio_io_handle_t 给 AudioTrack    audio_io_handle_t output;
    status = AudioSystem::getOutputForAttr(attr, &output,
                                           (audio_session_t)mSessionId, &streamType, mClientUid,
                                           mSampleRate, mFormat, mChannelMask,
                                           mFlags, mSelectedDeviceId, mOffloadInfo);

    //......
    // 向 AudioFlinger 发出 createTrack 请求    sp<IAudioTrack> track = audioFlinger->createTrack(streamType,
                                                      mSampleRate,
                                                      mFormat,
                                                      mChannelMask,
                                                      &temp,
                                                      &trackFlags,
                                                      mSharedBuffer,
                                                      output,
                                                      tid,
                                                      &mSessionId,
                                                      mClientUid,
                                                      &status);
    //......
    // AudioFlinger 创建 Track 对象时会分配一个 FIFO,这里获取 FIFO 的控制块    sp<IMemory> iMem = track->getCblk();
    if (iMem == 0) {
        ALOGE("Could not get control block");
        return NO_INIT;
    }
    // 匿名共享内存首地址    void *iMemPointer = iMem->pointer();
    if (iMemPointer == NULL) {
        ALOGE("Could not get control block pointer");
        return NO_INIT;
    }
    mAudioTrack = track; // 保存 AudioFlinger::PlaybackThread::Track 的代理对象 IAudioTrack    mCblkMemory = iMem; // 保存匿名共享内存首地址
    // 控制块位于 AudioFlinger 分配的匿名共享内存的首部    audio_track_cblk_t* cblk = static_cast<audio_track_cblk_t*>(iMemPointer);
    mCblk = cblk;
    mOutput = output; // 保存返回的 audio_io_handle_t,用它可以找到对应的 PlaybackThread    //......
    // update proxy    if (mSharedBuffer == 0) {
        // 当 mSharedBuffer 为空,意味着音轨数据模式为 MODE_STREAM,那么创建 AudioTrackClientProxy 对象        mStaticProxy.clear();
        mProxy = new AudioTrackClientProxy(cblk, buffers, frameCount, mFrameSize);
    } else {
        // 当 mSharedBuffer 非空,意味着音轨数据模式为 MODE_STATIC,那么创建 StaticAudioTrackClientProxy 对象        mStaticProxy = new StaticAudioTrackClientProxy(cblk, buffers, frameCount, mFrameSize);
        mProxy = mStaticProxy;
    }

    //......}

AudioFlinger::createTrack(),顾名思义,创建一个 Track 对象,将用于音频流的控制:

sp<IAudioTrack> AudioFlinger::createTrack(
        audio_stream_type_t streamType,
        uint32_t sampleRate,
        audio_format_t format,
        audio_channel_mask_t channelMask,
        size_t *frameCount,
        IAudioFlinger::track_flags_t *flags,
        const sp<IMemory>& sharedBuffer,
        audio_io_handle_t output,
        pid_t tid,
        int *sessionId,
        int clientUid,
        status_t *status){
    sp<PlaybackThread::Track> track;
    sp<TrackHandle> trackHandle;
    sp<Client> client;
    status_t lStatus;
    int lSessionId;

    //......
    {
        Mutex::Autolock _l(mLock);
        // 根据传入来的 audio_io_handle_t,找到对应的 PlaybackThread        PlaybackThread *thread = checkPlaybackThread_l(output);
        if (thread == NULL) {
            ALOGE("no playback thread found for output handle %d", output);
            lStatus = BAD_VALUE;
            goto Exit;
        }

        //......
        // 在 PlaybackThread 上创建一个音频流管理对象 Track        track = thread->createTrack_l(client, streamType, sampleRate, format,
                channelMask, frameCount, sharedBuffer, lSessionId, flags, tid, clientUid, &lStatus);
        //......
        setAudioHwSyncForSession_l(thread, (audio_session_t)lSessionId);
    }

    //......
    // 创建 Track 的通讯代理 TrackHandle 并返回它    trackHandle = new TrackHandle(track);Exit:
    *status = lStatus;
    return trackHandle;}sp<AudioFlinger::PlaybackThread::Track> AudioFlinger::PlaybackThread::createTrack_l(
        const sp<AudioFlinger::Client>& client,
        audio_stream_type_t streamType,
        uint32_t sampleRate,
        audio_format_t format,
        audio_channel_mask_t channelMask,
        size_t *pFrameCount,
        const sp<IMemory>& sharedBuffer,
        int sessionId,
        IAudioFlinger::track_flags_t *flags,
        pid_t tid,
        int uid,
        status_t *status){
    size_t frameCount = *pFrameCount;
    sp<Track> track;
    status_t lStatus;

    bool isTimed = (*flags & IAudioFlinger::TRACK_TIMED) != 0;

    // ......
    { // scope for mLock        Mutex::Autolock _l(mLock);

        // ......
        if (!isTimed) {
            // 创建 Track,等会再看看 Track 构造函数干些啥            track = new Track(this, client, streamType, sampleRate, format,
                              channelMask, frameCount, NULL, sharedBuffer,
                              sessionId, uid, *flags, TrackBase::TYPE_DEFAULT);
        } else {
            // 创建 TimedTrack,带时间戳的 Track?这里不深究            track = TimedTrack::create(this, client, streamType, sampleRate, format,
                    channelMask, frameCount, sharedBuffer, sessionId, uid);
        }

        // ......
        // 把创建的 Track 添加到 mTracks 向量中,方便 PlaybackThread 统一管理        mTracks.add(track);

        // ......    }

    lStatus = NO_ERROR;Exit:
    *status = lStatus;
    return track;}// ----------------------------------------------------------------------------// 如下是 TrackHandle 的相关代码,可以看到,TrackHandle 其实就是一个壳子,是 Track 的包装类// 所有 TrackHandle 接口都是调向 Track 的// Google 为什么要搞这么一则?Track 是 PlaybackThread 内部使用的,不适宜对外暴露,但应用进程// 又确实需要控制音频流的状态(start、stop、pause),所以就采取这么一种方式实现AudioFlinger::TrackHandle::TrackHandle(const sp<AudioFlinger::PlaybackThread::Track>& track)
    : BnAudioTrack(),
      mTrack(track){}AudioFlinger::TrackHandle::~TrackHandle() {
    // just stop the track on deletion, associated resources    // will be freed from the main thread once all pending buffers have    // been played. Unless it's not in the active track list, in which    // case we free everything now...    mTrack->destroy();}sp<IMemory> AudioFlinger::TrackHandle::getCblk() const {
    return mTrack->getCblk();}status_t AudioFlinger::TrackHandle::start() {
    return mTrack->start();}void AudioFlinger::TrackHandle::stop() {
    mTrack->stop();}void AudioFlinger::TrackHandle::flush() {
    mTrack->flush();}void AudioFlinger::TrackHandle::pause() {
    mTrack->pause();}// ----------------------------------------------------------------------------

最后,我们看看 Track 的构造过程,主要分析数据 FIFO 及它的控制块是如何分配的:

AudioFlinger::PlaybackThread::Track::Track(
            PlaybackThread *thread,
            const sp<Client>& client,
            audio_stream_type_t streamType,
            uint32_t sampleRate,
            audio_format_t format,
            audio_channel_mask_t channelMask,
            size_t frameCount,
            void *buffer,
            const sp<IMemory>& sharedBuffer,
            int sessionId,
            int uid,
            IAudioFlinger::track_flags_t flags,
            track_type type)
    :   TrackBase(thread, client, sampleRate, format, channelMask, frameCount,
                  (sharedBuffer != 0) ? sharedBuffer->pointer() : buffer,
                  sessionId, uid, flags, true /*isOut*/,
                  (type == TYPE_PATCH) ? ( buffer == NULL ? ALLOC_LOCAL : ALLOC_NONE) : ALLOC_CBLK,
                  type),
    mFillingUpStatus(FS_INVALID),
    // mRetryCount initialized later when needed    mSharedBuffer(sharedBuffer),
    mStreamType(streamType),
    mName(-1),  // see note below    mMainBuffer(thread->mixBuffer()),
    mAuxBuffer(NULL),
    mAuxEffectId(0), mHasVolumeController(false),
    mPresentationCompleteFrames(0),
    mFastIndex(-1),
    mCachedVolume(1.0),
    mIsInvalid(false),
    mAudioTrackServerProxy(NULL),
    mResumeToStopping(false),
    mFlushHwPending(false){
    // client == 0 implies sharedBuffer == 0    ALOG_ASSERT(!(client == 0 && sharedBuffer != 0));

    ALOGV_IF(sharedBuffer != 0, "sharedBuffer: %p, size: %d", sharedBuffer->pointer(),
            sharedBuffer->size());

    // 检查 FIFO 控制块(audio_track_cblk_t)是否分配好了,上面代码并未分配 audio_track_cblk_t    // 因此只可能是构造 TrackBase 时分配的,等下再看看 TrackBase 的构造函数    if (mCblk == NULL) {
        return;
    }

    if (sharedBuffer == 0) {
        // 数据传输模式为 MODE_STREAM 模式,创建一个 AudioTrackServerProxy 对象        // PlaybackThread 将持续使用它从 FIFO 上取得可读数据的位置        mAudioTrackServerProxy = new AudioTrackServerProxy(mCblk, mBuffer, frameCount,
                mFrameSize, !isExternalTrack(), sampleRate);
    } else {
        // 数据传输模式为 MODE_STATIC 模式,创建一个 StaticAudioTrackServerProxy 对象        mAudioTrackServerProxy = new StaticAudioTrackServerProxy(mCblk, mBuffer, frameCount,
                mFrameSize);
    }
    mServerProxy = mAudioTrackServerProxy;

    // 为 Track 分配一个名称,AudioMixer 会根据 TrackName 找到对应的 Track    mName = thread->getTrackName_l(channelMask, format, sessionId);
    if (mName < 0) {
        ALOGE("no more track names available");
        return;
    }
    // ......}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,
            int sessionId,
            int clientUid,
            IAudioFlinger::track_flags_t flags,
            bool isOut,
            alloc_type alloc,
            track_type type)
    :   RefBase(),
        mThread(thread),
        mClient(client),
        mCblk(NULL),
        // mBuffer        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_is_linear_pcm(format) ?
                mChannelCount * audio_bytes_per_sample(format) : sizeof(int8_t)),
        mFrameCount(frameCount),
        mSessionId(sessionId),
        mFlags(flags),
        mIsOut(isOut),
        mServerProxy(NULL),
        mId(android_atomic_inc(&nextTrackId)),
        mTerminated(false),
        mType(type),
        mThreadIoHandle(thread->id()){
    // ......
    // ALOGD("Creating track with %d buffers @ %d bytes", bufferCount, bufferSize);    size_t size = sizeof(audio_track_cblk_t);
    size_t bufferSize = (buffer == NULL ? roundup(frameCount) : frameCount) * mFrameSize;
    if (buffer == NULL && alloc == ALLOC_CBLK) {
        // 这个 size 将是分配的匿名共享内存的大小        // 等于控制块的大小(sizeof(audio_track_cblk_t)加上数据 FIFO的大小(bufferSize)        // 待会看到这块内存的结构,就明白这样分配的意义了        size += bufferSize;
    }

    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=%u", size);
            client->heap()->dump("AudioTrack");
            mCblkMemory.clear();
            return;
        }
    } else {
        // this syntax avoids calling the audio_track_cblk_t constructor twice        mCblk = (audio_track_cblk_t *) new uint8_t[size];
        // assume mCblk != NULL    }

    // construct the shared structure in-place.    if (mCblk != NULL) {
        // 这是 C++ 的 placement new(定位创建对象)语法:new(@BUFFER) @CLASS();        // 可以在特定内存位置上构造一个对象        // 这里,在匿名共享内存首地址上构造了一个 audio_track_cblk_t 对象        // 这样 AudioTrack 与 AudioFlinger 都能访问这个 audio_track_cblk_t 对象了        new(mCblk) audio_track_cblk_t();

        // 如下分配数据 FIFO,将用于 AudioTrack 与 AudioFlinger 的数据交换        switch (alloc) {
        // ......        case ALLOC_CBLK:
            // clear all buffers            if (buffer == NULL) {
                // 数据传输模式为 MODE_STREAM/TRANSFER_SYNC 时,数据 FIFO 的分配                // 数据 FIFO 的首地址紧靠控制块(audio_track_cblk_t)之后                //   |                                                         |                //   | -------------------> mCblkMemory <--------------------- |                //   |                                                         |                //   +--------------------+------------------------------------+                //   | audio_track_cblk_t |             Buffer                 |                //   +--------------------+------------------------------------+                //   ^                    ^                //   |                    |                //   mCblk               mBuffer                mBuffer = (char*)mCblk + sizeof(audio_track_cblk_t);
                memset(mBuffer, 0, bufferSize);
            } else {
                // 数据传输模式为 MODE_STATIC/TRANSFER_SHARED 时,直接指向 sharedBuffer                // sharedBuffer 是应用进程分配的匿名共享内存,应用进程已经一次性把数据                // 写到 sharedBuffer 来了,AudioFlinger 可以直接从这里读取                //   +--------------------+    +-----------------------------------+                //   | audio_track_cblk_t |    |            sharedBuffer           |                //   +--------------------+    +-----------------------------------+                //   ^                         ^                //   |                         |                //   mCblk                    mBuffer                mBuffer = buffer;
            }
            break;
        // ......        }

        // ......    }}

5. AudioTrack 数据写入

AudioTrack 实例构造后,应用程序接着可以写入音频数据了。如之前所描述:AudioTrack 与 AudioFlinger 是 生产者-消费者 的关系:

  • AudioTrack:AudioTrack 在 FIFO 中找到一块可用空间,把用户传入的音频数据写入到这块可用空间上,然后更新写位置(对于 AudioFinger 来说,意味 FIFO 上有更多的可读数据了);如果用户传入的数据量比可用空间要大,那么要把用户传入的数据拆分多次写入到 FIFO 中(AudioTrack 和 AudioFlinger 是不同的进程,AudioFlinger 同时也在不停地读取数据,所以 FIFO 可用空间是在不停变化的)

  • AudioFlinger:AudioFlinger 在 FIFO 中找到一块可读数据块,把可读数据拷贝到目的缓冲区上,然后更新读位置(对于 AudioTrack 来说,意味着 FIFO 上有更多的可用空间了);如果FIFO 上可读数据量比预期的要小,那么要进行多次的读取,才能积累到预期的数据量(AudioTrack 和 AudioFlinger 是不同的进程,AudioTrack 同时也在不停地写入数据,所以 FIFO 可读的数据量是在不停变化的)

上面的过程中,如果 AudioTrack 总能及时生产数据,并且 AudioFlinger 总能及时消耗掉这些数据,那么整个过程将是非常和谐的;但系统可能会发生异常,出现如下的状态:

  • Block:AudioFlinger 长时间不读取 FIFO 上的可读数据,使得 AudioTrack 长时间获取不到可用空间,无法写入数据;这种情况的根本原因大多是底层驱动发生阻塞异常,导致 AudioFlinger 无法继续写数据到硬件设备中,AudioFlinger 本身并没有错

  • Underrun:AudioTrack 写入数据的速度跟不上 AudioFlinger 读取数据的速度,使得 AudioFlinger 不能及时获取到预期的数据量,反映到现实的后果就是声音断续;这种情况的根本原因大多是应用程序不能及时写入数据或者缓冲区分配过小,AudioTrack 本身并没有错;AudioFlinger 针对这点做了容错处理:当发现 underrun 时,先陷入短时间的睡眠,不急着读取数据,让应用程序准备更多的数据(如果某一天做应用的哥们意识到自己的错误原来由底层的兄弟默默埋单了,会不会感动得哭了^_^)

5.1. AudioTrack 写数据流程

我们看一下 AudioTrack 写数据的代码,流程很简单:obtainBuffer() 在 FIFO 中找到一块可用区间,memcpy() 把用户传入的音频数据拷贝到这个可用区间上,releaseBuffer() 更新写位置。

ssize_t AudioTrack::write(const void* buffer, size_t userSize, bool blocking){
    if (mTransfer != TRANSFER_SYNC) {
        return INVALID_OPERATION;
    }

    if (isDirect()) {
        AutoMutex lock(mLock);
        int32_t flags = android_atomic_and(
                            ~(CBLK_UNDERRUN | CBLK_LOOP_CYCLE | CBLK_LOOP_FINAL | CBLK_BUFFER_END),
                            &mCblk->mFlags);
        if (flags & CBLK_INVALID) {
            return DEAD_OBJECT;
        }
    }

    if (ssize_t(userSize) < 0 || (buffer == NULL && userSize != 0)) {
        // Sanity-check: user is most-likely passing an error code, and it would        // make the return value ambiguous (actualSize vs error).        ALOGE("AudioTrack::write(buffer=%p, size=%zu (%zd)", buffer, userSize, userSize);
        return BAD_VALUE;
    }

    size_t written = 0;
    Buffer audioBuffer;

    while (userSize >= mFrameSize) {
        // 单帧数据量 frameSize = channelCount * bytesPerSample        //   对于双声道,16位采样的音频数据来说,frameSize = 2 * 2 = 4(bytes)        // 用户传入的数据帧数 frameCount = userSize / frameSize        audioBuffer.frameCount = userSize / mFrameSize;

        // obtainBuffer() 从 FIFO 上得到一块可用区间        status_t err = obtainBuffer(&audioBuffer,
                blocking ? &ClientProxy::kForever : &ClientProxy::kNonBlocking);
        if (err < 0) {
            if (written > 0) {
                break;
            }
            if (err == TIMED_OUT || err == -EINTR) {
                err = WOULD_BLOCK;
            }
            return ssize_t(err);
        }

        // toWrite 是 FIFO 可用区间的大小,可能比 userSize(用户传入数据的大小)要小        //   因此用户传入的数据可能要拆分多次拷贝到 FIFO 上        // 注意:AudioTrack 和 AudioFlinger 是不同的进程,AudioFlinger 同时也在不停地        //   消耗数据,所以 FIFO 可用区间是在不停变化的        size_t toWrite = audioBuffer.size;
        memcpy(audioBuffer.i8, buffer, toWrite); // 把用户数据拷贝到 FIFO 可用区间        buffer = ((const char *) buffer) + toWrite; // 未拷贝数据的位置        userSize -= toWrite; // 未拷贝数据的大小        written += toWrite; // 已拷贝数据的大小
        // releaseBuffer() 更新 FIFO 写位置        // 对于 AudioFinger 来说,意味 FIFO 上有更多的可读数据        releaseBuffer(&audioBuffer);
    }

    if (written > 0) {
        mFramesWritten += written / mFrameSize;
    }
    return written;}

5.2. AudioFlinger 读数据流程

AudioFlinger 消费数据的流程稍微复杂一点,3.4. AudioFlinger 回放录制线程 小节中描述了 AudioFlinger::PlaybackThread::threadLoop() 工作流程,这里不累述了,我们把焦点放在“如何从 FIFO 读取数据”节点上。

我们以 DirectOutputThread/OffloadThread 为例说明(MixerThread 读数据也是类似的过程,只不过是在 AudioMixer 中进行的,3.7. AudioFlinger 混音器处理 小节中有相关描述)。

void AudioFlinger::DirectOutputThread::threadLoop_mix(){
    // mFrameCount 是硬件设备(PCM 设备)处理单个数据块的帧数(周期大小)    //   上层必须积累了足够多(mFrameCount)的数据,才写入到 PCM 设备    //   所以 mFrameCount 也就是 AudioFlinger 预期的数据量    size_t frameCount = mFrameCount;
    // mSinkBuffer 目的缓冲区,threadLoop_write() 会把 mSinkBuffer 上的数据写到 PCM 设备    int8_t *curBuf = (int8_t *)mSinkBuffer;
    // output audio to hardware    // FIFO 上可读的数据量可能要比预期的要小,因此可能需要多次读取才能积累足够的数据量    // 注意:AudioTrack 和 AudioFlinger 是不同的进程,AudioTrack 同时也在不停地生产数据    //   所以 FIFO 可读的数据量是在不停变化的    while (frameCount) {
        AudioBufferProvider::Buffer buffer;
        buffer.frameCount = frameCount;
        // getNextBuffer() 从 FIFO 上获取可读数据块        status_t status = mActiveTrack->getNextBuffer(&buffer);
        if (status != NO_ERROR || buffer.raw == NULL) {
            memset(curBuf, 0, frameCount * mFrameSize);
            break;
        }
        // memcpy() 把 FIFO 可读数据拷贝到 mSinkBuffer 目的缓冲区        memcpy(curBuf, buffer.raw, buffer.frameCount * mFrameSize);
        frameCount -= buffer.frameCount;
        curBuf += buffer.frameCount * mFrameSize;
        // releaseBuffer() 更新 FIFO 读位置        // 对于 AudioTrack 来说,意味着 FIFO 上有更多的可用空间        mActiveTrack->releaseBuffer(&buffer);
    }
    mCurrentWriteLength = curBuf - (int8_t *)mSinkBuffer;
    mSleepTimeUs = 0;
    mStandbyTimeNs = systemTime() + mStandbyDelayNs;
    mActiveTrack.clear();}

5.3. 环形 FIFO 管理

在上述过程中,不知大家有无意识到:整个过程中,最难的是如何协调生产者与消费者之间的步调。上文所说的 FIFO 是环形 FIFO,AudioTrack 写指针、AudioFlinger 读指针都是基于 FIFO 当前的读写位置来计算的。

  • AudioTrack 与 AudioFlinger 不在同一个进程上,怎么保证读写指针的线程安全

  • 读写指针越过 FIFO 后,怎么处理

  • AudioTrack 写数据完成后,需要同步状态给 AudioFlinger,让 AudioFlinger 知道当前有可读数据了,而 AudioFlinger 读数据完成后,也需要同步状态给 AudioTrack,让 AudioTrack 知道当前有可用空间了;这里采取什么同步机制

我们回顾下创建 AudioTrack 对象时,FIFO 及其控制块的结构如下所示:

  • MODE_STREAM 模式下的匿名共享内存结构:

|                                                         |
  | -------------------> mCblkMemory <--------------------- |
  |                                                         |
  +--------------------+------------------------------------+
  | audio_track_cblk_t |               FIFO                 |
  +--------------------+------------------------------------+
  ^                    ^
  |                    |
mCblk               mBuffer

mCblk = static_cast<audio_track_cblk_t *>(mCblkMemory->pointer());
new(mCblk) audio_track_cblk_t();
mBuffer = (char*)mCblk + sizeof(audio_track_cblk_t);
  • MODE_STATIC 模式下的匿名共享内存结构:

+--------------------+    +-----------------------------------+
  | audio_track_cblk_t |    |         FIFO (sharedBuffer)       |
  +--------------------+    +-----------------------------------+
  ^                         ^
  |                         |
mCblk                    mBuffer

mCblk = (audio_track_cblk_t *) new uint8_t[size];
new(mCblk) audio_track_cblk_t();
mBuffer = sharedBuffer->pointer()

FIFO 管理相关的类图:

  • AudioTrackClientProxy:MODE_STREAM 模式下,生产者 AudioTrack 使用它在 FIFO 中找到可用空间的位置

  • AudioTrackServerProxy:MODE_STREAM 模式下,消费者 AudioFlinger::PlaybackThread 使用它在 FIFO 中找到可读数据的位置

  • StaticAudioTrackClientProxy:MODE_STATIC 模式下,生产者 AudioTrack 使用它在 FIFO 中找到可用空间的位置

  • StaticAudioTrackServerProxy:MODE_STATIC 模式下,消费者 AudioFlinger::PlaybackThread 使用它在 FIFO 中找到可读数据的位置

  • AudioRecordClientProxy:消费者 AudioRecord 使用它在 FIFO 中找到可读数据的位置

  • AudioTrackServerProxy:生产者 AudioFlinger::RecordThread 使用它在 FIFO 中找到可用空间的位置

到这里,我决定结束本文了。环形 FIFO 管理是 Android 音频系统的精髓,一个小节并不足以描述其原理及实现细节;Android 环形 FIFO 的实现可说得上精妙绝伦,其他项目如果要用到环形 FIFO,不妨多借鉴它。因此我想另写一篇博文详细分析 Android 环形 FIFO 的原理及实现,初定提纲如下,以作备忘:

1. 传统环形 FIFO 的原理

2.Android 环形 FIFO 的原理

3.读写指针的线程安全

4.Futex 进程同步机制

5.Android 环形 FIFO 的实现

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多