Android音视频四:AudioTrack数据传递

AudioTrack的共享内存

AudioTrack的两种模式

APP给AudioTrack提供音频数据有2种模式:

MODE_STATIC模式:一次性提前提供数据。使用APP创建共享内存, APP一次性填充数据。playbackthread等数据构造好,取出数据就可以直接使用了,不存在同步问题。对应的playbackThread工作是:获得含有数据的obtainBuffer(APP一次性提交共享内存的数据有可能很多,playbackThread需要进行多次播放)。完成后释放buffer。

MODE_STREAM模式:边播放边提供。使用playbackThread创建共享内存。APP使用obtainBuffer获得buffer, 填充数据后使用releaseBuffer释放buffer。playbackThread一样需要进行多次播放。只不过这里使用的是环形缓冲区机制来,不断传递数据。完成后释放buffer。

从AudioTrack构造看内存共享

  1. # Java::AudioTrack->Java::native_setup->JNI转换->android_media_AudioTrack_setup
  2. static jint android_media_AudioTrack_setup(....) {
  3. // lyh 两种模式
  4. switch (memoryMode) {
  5. // lyh 写一段播一段
  6. case MODE_STREAM:
  7. status = lpTrack->set(AUDIO_STREAM_DEFAULT,...
  8. // shared mem = 0 内存后面由playbackthread来申请
  9. 0,....);
  10. break;
  11. // lyh 一次写完播放
  12. case MODE_STATIC:
  13. // lyh 应用端申请共享内存
  14. if (!lpJniStorage->allocSharedMem(buffSizeInBytes)) {
  15. goto native_init_failure;
  16. }
  17. status = lpTrack->set(AUDIO_STREAM_DEFAULT,...
  18. // shared mem = 应用端创建的共享内存
  19. lpJniStorage->mMemBase,....);
  20. break;
  21. }
  22. }
  1. # AudioTrack::set-> AudioTrack::createTrack_l-> Track的创建
  2. //lyh 构建aduiotrack的流程
  3. status_t AudioTrack::createTrack_l() {
  4. sp<IAudioTrack> track = audioFlinger->createTrack(input,
  5. output,
  6. &status);
  7. /* lyh
  8. *获取track变量 的共享内存 buffer
  9. *当PlaybackThread创建一个PlaybackThread::Track对象时,所需的缓冲区空间
  10. *就已经分配了,这块空间是可以跨进程共享的,所以AudioTrack可以通过track->getCblk
  11. *
  12. **/
  13. sp<IMemory> iMem = track->getCblk();
  14. void *iMemPointer = iMem->unsecurePointer();
  15. audio_track_cblk_t* cblk = static_cast<audio_track_cblk_t*>(iMemPointer);
  16. // mSharedBuffer = App端创建的共享内存 = MODE_STATIC
  17. void* buffers;
  18. if (mSharedBuffer == 0) {
  19. buffers = cblk + 1;
  20. } else {
  21. buffers = mSharedBuffer->unsecurePointer();
  22. }
  23. // 创建对应的client代理
  24. if (mSharedBuffer == 0) {
  25. // MODE_STREAM模式
  26. // 指向 playbackthread提供的Buffer
  27. mProxy = new AudioTrackClientProxy(cblk, buffers, mFrameCount, mFrameSize);
  28. } else {
  29. // MODE_STATIC模式(看名字也知道)
  30. // 指向应用端APP提供的Buffer
  31. mStaticProxy = new StaticAudioTrackClientProxy(cblk, buffers, mFrameCount, mFrameSize);
  32. mProxy = mStaticProxy;
  33. }
  34. }

PlaybackThread的Track建立共享内存

  1. AudioFlinger::ThreadBase::TrackBase::TrackBase(
  2. void *buffer, // 如果应用端创建了内存共享块,就是这个值
  3. size_t bufferSize,
  4. ) {
  5. size_t minBufferSize = buffer == NULL ? roundup(frameCount) : frameCount;
  6. if (buffer == nullptr) {
  7. bufferSize = minBufferSize;
  8. }
  9. // lyh 计算结构体的
  10. size_t size = sizeof(audio_track_cblk_t);
  11. if (buffer == NULL && alloc == ALLOC_CBLK) {
  12. size += bufferSize;
  13. }
  14. // lyh 从之前文章流程来看,client是一定有值的
  15. if (client != 0) {
  16. // lyh 分配一块匿名共享内存
  17. mCblkMemory = client->heap()->allocate(size);
  18. } else {
  19. mCblk = (audio_track_cblk_t *) malloc(size);
  20. }
  21. if (mCblk != NULL) {
  22. // lyh 定位创建对象,可以在特定位置创建对象
  23. // lyh 这里就是在匿名共享内存的首地址创建一个audio_track_cblk_t对象
  24. new(mCblk) audio_track_cblk_t();
  25. switch (alloc) {
  26. case ....
  27. case ALLOC_CBLK:
  28. // clear all buffers
  29. // lyh buffer的初始化
  30. if (buffer == NULL) {
  31. // lyh 指向playbackthread提供的Buffer
  32. mBuffer = (char*)mCblk + sizeof(audio_track_cblk_t);
  33. memset(mBuffer, 0, bufferSize);
  34. } else {
  35. // lyh 指向APP提供的Buffer
  36. mBuffer = buffer;
  37. }
  38. case ....
  39. }
  40. mBufferSize = bufferSize;
  41. }
  1. AudioFlinger::PlaybackThread::Track::Track(...){
  2. // sharedBuffer == 0 -> MODE_STREAM
  3. if (sharedBuffer == 0) {
  4. mAudioTrackServerProxy = new AudioTrackServerProxy(mCblk, mBuffer, frameCount,
  5. mFrameSize, !isExternalTrack(), sampleRate);
  6. } else {
  7. mAudioTrackServerProxy = new StaticAudioTrackServerProxy(mCblk, mBuffer, frameCount,mFrameSize, sampleRate);
  8. }
  9. mServerProxy = mAudioTrackServerProxy;
  10. }

总的来说 上面的过程就是这两句话

  1. AudioTrack中使用AudioTrackClientProxy对象 和 StaticAudioTrackClientProxy对象来管理共享内存。
  2. Track中使用AudioTrackServerProxy对象 和 StaticAudioTrackServerProxy对象 来管理共享内存。
  3. 环形缓冲区感兴趣可以了解一下,个人感觉就是一个双指针的算法题

工作流程

  1. AudioTrack(APP应用端)通过mClientProxy向共享buffer写入数据,
    AudioFlinger(server端)通过 mServerProxy从共享内存中读出数据。
    这样client、server通过proxy对共享内存形成了生产者——消费者模式

  2. Client端:
    AudioTrackClientProxy:: obtainBuffer()从 audio buffer 获取连续的空buffer;
    AudioTrackClientProxy:: releaseBuffer ()将填充了数据的 buffer 放回 audio buffer。

  3. Server端:
    AudioTrackServerProxy:: obtainBuffer()从 audio buffer 获取连续的填充了数据的 buffer;
    AudioTrackServerProxy:: releaseBuffer()将使用完的空buffer 放回 audio buffer。

AudioTrack数据的write和play流程

wrtie

obtainBuffer最终是调用AudioTrackClientProxy的方法,主要就是从共享内存中取出空的buffer,将音频数据写入

  1. ssize_t AudioTrack::write(const void* buffer, size_t userSize, bool blocking)
  2. {
  3. size_t written = 0;
  4. Buffer audioBuffer;
  5. // lyh userSize 用户可用空间
  6. while (userSize >= mFrameSize) {
  7. audioBuffer.frameCount = userSize / mFrameSize;
  8. // lyh 获取buffer中的空余部分
  9. status_t err = obtainBuffer(&audioBuffer,
  10. blocking ? &ClientProxy::kForever : &ClientProxy::kNonBlocking);
  11. if (err < 0) {
  12. if (written > 0) {
  13. break;
  14. }
  15. if (err == TIMED_OUT || err == -EINTR) {
  16. err = WOULD_BLOCK;
  17. }
  18. return ssize_t(err);
  19. }
  20. size_t toWrite = audioBuffer.size;
  21. // lyh 做数据拷贝 保存在i8变量中
  22. memcpy(audioBuffer.i8, buffer, toWrite);
  23. buffer = ((const char *) buffer) + toWrite;
  24. // lyh 剩余可用和已经写入
  25. userSize -= toWrite;
  26. written += toWrite;
  27. // lyh 释放
  28. releaseBuffer(&audioBuffer);
  29. }
  30. return written;
  31. }

Play

  1. 调用流程:从Java层调用Play -> JNI的native_start -> NAtive层面AudioTrack的start -> Binder通信 -> Track的start
  2. 总结:拿到到playbackThread回放线程,然后添加Track,开启Server端去管理共享内存块,接着通过广播环形Playback::threadloop方法,
  1. // lyh AudioTrack.cpp#mAudioTrack->start方法最后流入这
  2. status_t AudioFlinger::PlaybackThread::Track::start(...)
  3. {
  4. // lyh
  5. sp<ThreadBase> thread = mThread.promote();
  6. if (thread != 0) {
  7. if (state == PAUSED || state == PAUSING) {
  8. ...
  9. } else {
  10. // lyh 状态修改
  11. mState = TrackBase::ACTIVE;
  12. }
  13. PlaybackThread *playbackThread = (PlaybackThread *)thread.get();
  14. // lyh
  15. status = playbackThread->addTrack_l(this);
  16. if (status == NO_ERROR || status == ALREADY_EXISTS) {
  17. // for streaming tracks, remove the buffer read stop limit.
  18. // lyh Server端的Proxy开始管理共享内存
  19. mAudioTrackServerProxy->start();
  20. }
  21. }
  22. return status;
  23. }
  1. status_t AudioFlinger::PlaybackThread::addTrack_l(const sp<Track>& track)
  2. {
  3. status_t status = ALREADY_EXISTS;
  4. // lyh 是新增的track
  5. if (mActiveTracks.indexOf(track) < 0) {
  6. if (track->isExternalTrack()) {
  7. // lyh 最后调用到AudioPolicyManager方法中
  8. status = AudioSystem::startOutput(track->portId());
  9. // lyh 加入到活跃Track的数组中
  10. mActiveTracks.add(track);
  11. }
  12. // lyh 通过广播唤醒PlaybackThread.threadLoop()
  13. onAddNewTrack_l();
  14. return status;
  15. }

文章标签:

原文连接:https://juejin.cn/post/7106418810080460808

相关推荐

Android多版本flavor配置之资源文件和清单文件合并介绍

Android 面试题:说一下 PendingIntent 和 Intent 的区别

Intent 跳转 传递list集合

Google Play 开发者账户被封 如何改代码快速上架

百度APP Android包体积优化实践(二)Dex行号优化

使用ComposeDesktop开发一款桌面端多功能APK工具

一年时间过去了,LiveData真的被Flow代替了吗? LiveData会被废弃吗?

Flutter 绘制探索 | 箭头端点的设计

Android Notes | 开发手记 ing...

Flutter 小技巧之优化使用的 BuildContext

Android原生自定义车牌输入法 附带两种实现思路以及源码 EasyVehicleKeyBoard

Dart(五)—泛型、库使用、async和await关键字

Android启动优化深入解析,全面掌握!

Android技术知识点:如何使用数据绑定来消除findViewById()

ExoPlayer客户端解密m3u8音频\u002F视频

第四届青训营阅读打卡活动来啦,奖品、规则全面升级,快来学习吧

视频直播小窗口(悬浮窗)展示方案

Flutter 2 商城App实战指南(支持Null safety)

LaunchedEffect到底为我们处理了什么问题?

Android View | Canvas详解