Android音频学习(十七)——音频数据流转

上一节主要讲解了通过应用下发属性参数找到对应的output,output与playbackThread是一一对应的,AudioFlinger创建PlaybackThread后,通过mPlaybackThreads.add(*output, thread)将output 和 PlaybackThread 添加到键值对向量 mPlaybackThreads 中,由于 output 和 PlaybackThread 是一一对应的关系,因此拿到一个 output,就能遍历键值对向量 mPlaybackThreads 找到它对应的 PlaybackThread,可以简单理解 output为 PlaybackThread 的索引号或线程 id。

找到output后,就可以找到音频传递数据的个PlaybackThread线程,这个线程相当于一个中间角色,应用层进程将音频数据以匿名共享内存方式传递给PlaybackThread,而PlaybackThread也是以匿名共享内存方式传递数据给HAL层。下面重点看下AudioTrack与AudioFlinger的共享内存是如何创建并使用的。

上一章节讲到在createTrack时最终会调用到 track = new Track()创建track,Track继承于TrackBase,在TrackBase的构造函数中申请了一块共享内存

cpp 复制代码
AudioFlinger::ThreadBase::TrackBase::TrackBase{
    size_t minBufferSize = buffer == NULL ? roundup(frameCount) : frameCount;
    // check overflow when computing bufferSize due to multiplication by mFrameSize.
    if (minBufferSize < frameCount  // roundup rounds down for values above UINT_MAX / 2
            || mFrameSize == 0   // format needs to be correct
            || minBufferSize > SIZE_MAX / mFrameSize) {
        android_errorWriteLog(0x534e4554, "34749571");
        return;
    }
    minBufferSize *= mFrameSize;

    if (buffer == nullptr) {
        bufferSize = minBufferSize; // allocated here.
    } else if (minBufferSize > bufferSize) {
        android_errorWriteLog(0x534e4554, "38340117");
        return;
    }

    size_t size = sizeof(audio_track_cblk_t);
    if (buffer == NULL && alloc == ALLOC_CBLK) {
        // check overflow when computing allocation size for streaming tracks.
        if (size > SIZE_MAX - bufferSize) {
            android_errorWriteLog(0x534e4554, "34749571");
            return;
        }
        size += bufferSize;
    }

    if (client != 0) {
        mCblkMemory = client->heap()->allocate(size);
        if (mCblkMemory == 0 ||
                (mCblk = static_cast<audio_track_cblk_t *>(mCblkMemory->unsecurePointer())) == NULL) {
            ALOGE("%s(%d): not enough memory for AudioTrack size=%zu", __func__, mId, size);
            client->heap()->dump("AudioTrack");
            mCblkMemory.clear();
            return;
        }
    } else {
        mCblk = (audio_track_cblk_t *) malloc(size);
        if (mCblk == NULL) {
            ALOGE("%s(%d): not enough memory for AudioTrack size=%zu", __func__, mId, size);
            return;
        }
    }

    // construct the shared structure in-place.
    if (mCblk != NULL) {
        new(mCblk) audio_track_cblk_t();
        switch (alloc) {   
        case ALLOC_CBLK:
            // clear all buffers
            if (buffer == NULL) {
                mBuffer = (char*)mCblk + sizeof(audio_track_cblk_t);
                memset(mBuffer, 0, bufferSize);
            } else {
                mBuffer = buffer;
#if 0
                mCblk->mFlags = CBLK_FORCEREADY;    // FIXME hack, need to fix the track ready logic
#endif
            }
            ......
        }
        mBufferSize = bufferSize;

}

client是在AudioFlinger中创建的:

client = registerPid(clientPid);

--->client = new Client(this, pid);

cpp 复制代码
AudioFlinger::Client::Client(const sp<AudioFlinger>& audioFlinger, pid_t pid)
    :   RefBase(),
        mAudioFlinger(audioFlinger),
        mPid(pid)
{
    mMemoryDealer = new MemoryDealer(
            audioFlinger->getClientSharedHeapSize(),
            (std::string("AudioFlinger::Client(") + std::to_string(pid) + ")").c_str());
}

MemoryDealer是Android 提供的一个内存分配的工具类 ,用于管理内存堆,注意该类是不具备跨进程通信能力的,它仅在服务端工作。MemoryDealer的构造函数中,会创建一个MemoryHeapBase和一个SimpleBestFitAllocator对象。

cpp 复制代码
MemoryDealer::MemoryDealer(size_t size, const char* name)
: mHeap(new MemoryHeapBase(size, 0, name)),
mAllocator(new SimpleBestFitAllocator(size))
{
}

MemoryHeapBase构造函数如下:

cpp 复制代码
MemoryHeapBase::MemoryHeapBase(size_t size, uint32_t flags, char const * name)
    : mFD(-1), mSize(0), mBase(MAP_FAILED), mFlags(flags),
      mDevice(nullptr), mNeedUnmap(false), mOffset(0)
{
    const size_t pagesize = getpagesize();
    size = ((size + pagesize-1) & ~(pagesize-1));
    int fd = ashmem_create_region(name == nullptr ? "MemoryHeapBase" : name, size);
    ALOGE_IF(fd<0, "error creating ashmem region: %s", strerror(errno));
    if (fd >= 0) {
        if (mapfd(fd, size) == NO_ERROR) {
            if (flags & READ_ONLY) {
                ashmem_set_prot_region(fd, PROT_READ);
            }
        }
    }
}

先调用Ashmem提供的函数库函数ashmem_create_region()完成匿名内存的创建,接着调用mapfd将匿名内存映射至本进程(这里的本进程是AudioFlinger所在的system_server进程)。至此完成了前文实现共享内存的步骤1和步骤2。 SimpleBestFitAllocator负责记录MemoryHeapBase的分块(已经分配了多少块),辅助完成Allocation(继承自MemoryBase)的构造。

SimpleBestFitAllocator构造函数代码如下:

cpp 复制代码
SimpleBestFitAllocator::SimpleBestFitAllocator(size_t size)
{
    size_t pagesize = getpagesize();
    mHeapSize = ((size + pagesize-1) & ~(pagesize-1));

    chunk_t* node = new chunk_t(0, mHeapSize / kMemoryAlign);
    mList.insertHead(node);
}

创建一个chunk_t,插入mList,chunk_t大小是size/kMemoryAlign。后续随着AudioTrack的创建,会把chunk_t拆分成更小的chunk_t。

再回到TrackBase, client->heap()->allocate返回mCblkMemory,返回值类型是IMemory。

mBuffer = (char*)mCblk + sizeof(audio_track_cblk_t);这里的mBuffer则是传输音频数据的共享内存的首地址,为申请内存地址加上数据结构audio_track_cblk_t的偏址,

| |

| -------------------> mCblkMemory <--------------------- |

| |

+--------------------+------------------------------------+

| audio_track_cblk_t | FIFO |

+--------------------+------------------------------------+

^ ^

| |

mCblk mBuffer

以上为申请共享内存的流程,下面看下客户端和服务端是如何使用共享内存的。

首先看服务端:

在创建track对象时,调用其构造函数,将会创建服务端代理对象mAudioTrackServerProxy,它负责管理与同步匿名共享内存的使用,shareBuffer是应用下发的参数,为0是MODE_STREAM模式,mBuffer为以上介绍的共享内存地址,不为0则是MODE_STATIC模式,mBuffer为应用传过来的内存地址。

cpp 复制代码
AudioFlinger::PlaybackThread::Track::Track:TrackBase{
......   
    if (sharedBuffer == 0) {
        mAudioTrackServerProxy = new AudioTrackServerProxy(mCblk, mBuffer, frameCount,
                mFrameSize, !isExternalTrack(), sampleRate);
    } else {
        mAudioTrackServerProxy = new StaticAudioTrackServerProxy(mCblk, mBuffer, frameCount,
                mFrameSize, sampleRate);
    }
    mServerProxy = mAudioTrackServerProxy;
......
}

再看客户端

cpp 复制代码
status_t AudioTrack::createTrack_l()
{
......
    sp<IAudioTrack> track = audioFlinger->createTrack(input,
                                                      output,
                                                      &status);
    sp<IMemory> iMem = track->getCblk();
    if (iMem == 0) {
        ALOGE("%s(%d): Could not get control block", __func__, mPortId);
        status = NO_INIT;
        goto exit;
    }
    ......
    mAudioTrack = track;
    mCblkMemory = iMem;    
    ......
    void *iMemPointer = iMem->unsecurePointer();
    if (iMemPointer == NULL) {
        ALOGE("%s(%d): Could not get control block pointer", __func__, mPortId);
        status = NO_INIT;
        goto exit;
    }
    ......

    // Starting address of buffers in shared memory.  If there is a shared buffer, buffers
    // is the value of pointer() for the shared buffer, otherwise buffers points
    // immediately after the control block.  This address is for the mapping within client
    // address space.  AudioFlinger::TrackBase::mBuffer is for the server address space.
    void* buffers;
    if (mSharedBuffer == 0) {
        buffers = cblk + 1;
    } else {
        // TODO: Using unsecurePointer() has some associated security pitfalls
        //       (see declaration for details).
        //       Either document why it is safe in this case or address the
        //       issue (e.g. by copying).
        buffers = mSharedBuffer->unsecurePointer();
        if (buffers == NULL) {
            ALOGE("%s(%d): Could not get buffer pointer", __func__, mPortId);
            status = NO_INIT;
            goto exit;
        }
    }
    ......
    if (mSharedBuffer == 0) {
        mStaticProxy.clear();
        mProxy = new AudioTrackClientProxy(cblk, buffers, mFrameCount, mFrameSize);
    } else {
        mStaticProxy = new StaticAudioTrackClientProxy(cblk, buffers, mFrameCount, mFrameSize);
        mProxy = mStaticProxy;
    }
......
}

先通过getCblk就是获取服务端TrackBase的mCblkMemory成员,即共享内存的总管理,再通过unsecurePointer获取分配内存映射的首地址,首地址是一个audio_track_cblk_t成员,所以buffer的地址要+1。

clientProxy与ServerProxy之间通信方式是Binder通信IPC,他们通信的内容就是匿名共享内存的信息,怎么写入,写入到哪个地方等;音频数据读写走的是下面的匿名共享内存buffer。

应用层往共享内存写数据:

应用下发write方法时调用obtainbuffer(),该方法主要是获取可写入数据的剩余空间地址,并更新指针便于下次写入。

cpp 复制代码
ssize_t AudioTrack::write(const void* buffer, size_t userSize, bool blocking)
{
    ...
        while (userSize >= mFrameSize) {
        audioBuffer.frameCount = userSize / mFrameSize;

        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);
        }

        size_t toWrite = audioBuffer.size;
        memcpy(audioBuffer.i8, buffer, toWrite);
        buffer = ((const char *) buffer) + toWrite;
        userSize -= toWrite;
        written += toWrite;

        releaseBuffer(&audioBuffer);
    }
    ...
}

再看服务端,应用写入数据前会调用start()方法,该方法调用mAudioTrack的start,即调用到服务端的track->start方法,该方法首先将track加入到队列中,然后给AudioFlinger的PlaybackThread线程发送消息,让PlaybackThread线程开始播放,就会调用PlaybackThread线程的主函数AudioFlinger::PlaybackThread::threadLoop

在threadLoop中,调用了threadLoop_mix和threadLoop_write。

如果是DirectOutputThread,则不需要混音,把数据直接拷贝到curBuf,即mSinkBuffer中即可。

cpp 复制代码
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();
}

如果是MixThread,则需要先进行混音,调用mAudio->process,下一章再具体说明。

cpp 复制代码
void AudioFlinger::MixerThread::threadLoop_mix()
{
    // mix buffers...
    mAudioMixer->process();
    mCurrentWriteLength = mSinkBufferSize;
    // increase sleep time progressively when application underrun condition clears.
    // Only increase sleep time if the mixer is ready for two consecutive times to avoid
    // that a steady state of alternating ready/not ready conditions keeps the sleep time
    // such that we would underrun the audio HAL.
    if ((mSleepTimeUs == 0) && (sleepTimeShift > 0)) {
        sleepTimeShift--;
    }
    mSleepTimeUs = 0;
    mStandbyTimeNs = systemTime() + mStandbyDelayNs;
    //TODO: delay standby when effects have a tail

}

最后,再通过threadLoop_write将数据写入到HAL层,再到驱动,最后通过音频接口硬件输出声音,这一步流程后续再详细说明。

相关推荐
Brookty8 小时前
【算法】双指针(二)复写零
学习·算法
小周不长肉8 小时前
视频串行解串器(SerDes)介绍
音视频
懒人村杂货铺8 小时前
[特殊字符] 跨端视频通话实战:腾讯云 TRTC + IM(React Native & Web)
react native·音视频·腾讯云
滴滴滴嘟嘟嘟.8 小时前
Qt UDP通信学习
qt·学习·udp
努力的小帅9 小时前
C++_哈希
开发语言·c++·学习·算法·哈希算法·散列表
zzZ65659 小时前
支持向量机(SVM)学习报告
学习·机器学习·支持向量机
昨日之日200615 小时前
Wan2.2-S2V - 音频驱动图像生成电影级质量的数字人视频 ComfyUI工作流 支持50系显卡 一键整合包下载
人工智能·音视频
Hello_Embed18 小时前
STM32HAL 快速入门(十九):UART 编程(二)—— 中断方式实现收发及局限分析
笔记·stm32·单片机·嵌入式硬件·学习
天上的光19 小时前
关于学习的一些感悟
学习