Android AudioTrack

一、AudioTrack介绍

播放声音可以使用 MediaPlayer 和 AudioTrack,两者都提供 Java API 给应用开发者使用。两者的差别在于:MediaPlayer 可以播放多种格式的音源,如 mp3、flac、wma、ogg、wav 等,而 AudioTrack 只能播放解码后的 PCM 数据流。从上面 Android 音频系统架构图来看:MediaPlayer 在 Native 层会创建对应的音频解码器和一个 AudioTrack,解码后的数据交由 AudioTrack 输出。所以 MediaPlayer 的应用场景更广,一般情况下使用它也更方便;只有一些对声音时延要求非常苛刻的应用场景才需要用到 AudioTrack。

AudioTrack由JAVA代码、JNI代码、C++代码组成:

AudioTrack代码位于:

frameworks/base/media/java/android/media/AudioTrack.java

frameworks/base/core/jni/android_media_AudioTrack.cpp

frameworks/base/core/jni/android_media_AudioTrack.h

frameworks/av/media/libaudioclient/AudioTrack.cpp

frameworks/av/media/libaudioclient/AudioTrack.h

AudioTrack的定义:

public class AudioTrack extends PlayerBase implements AudioRouting, VolumeAutomation {}
class AudioTrack : public AudioSystem::AudioDeviceCallback {}

二、AudioTrack API

AudioTrack分为JAVA API和Native API,下面分别介绍它们。

1、AudioTrack Java API

AudioTrack Java API如下:

  • AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode) :构造函数

  • AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode, int sessionId) :构造函数

  • AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat) :构造函数

  • static int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat):计算最小缓冲区大小,参数同构造函数中三个参数。

  • void play():开始播放

  • int write(byte[] audioData, int offsetInBytes, int sizeInBytes) :写入音频数据到硬件有两个方法,返回成功写入的数据个数

  • int write(short[] audioData, int offsetInShorts, int sizeInShorts) :写入音频数据到硬件有两个方法,返回成功写入的数据个数

  • void stop():停止播放

  • void pause():暂停播放

  • void release():释放资源

  • int getState():获取状态

  • int getPlayState():获取播放状态

  • int setVolume(float gain):设置音量(0~1.0)

其中AudioTrack Java API 两种数据传输模式:

modeDescription

MODE_STATIC

应用进程将回放数据一次性付给 AudioTrack,适用于数据量小、时延要求高的场景

MODE_STREAM

用进程需要持续调用 write() 写数据到 FIFO,写数据时有可能遭遇阻塞(等待 AudioFlinger::PlaybackThread 消费之前的数据),基本适用所有的音频场景

其中AudioTrack Java API 音频流类型:

Stream TypeDescription

STREAM_VOICE_CALL

电话语音

STREAM_SYSTEM

系统声音

STREAM_RING

铃声声音,如来电铃声、闹钟铃声等

STREAM_MUSIC

音乐声音

STREAM_ALARM

警告音

STREAM_NOTIFICATION

通知音

STREAM_DTMF

DTMF 音(拨号盘按键音)



Android 为什么要定义这么多的流类型?这与 Android 的音频管理策略有关,例如:

  • 音频流的音量管理,调节一个类型的音频流音量,不会影响到其他类型的音频流

  • 根据流类型选择合适的输出设备;比如插着有线耳机期间,音乐声(STREAM_MUSIC)只会输出到有线耳机,而铃声(STREAM_RING)会同时输出到有线耳机和外放

一个 AudioTrack Java API 的测试例子(MODE_STREAM 模式):

 //Test case 1: setStereoVolume() with max volume returns SUCCESS
    @LargeTest
    public void testSetStereoVolumeMax() throws Exception {
        // constants for test
        final String TEST_NAME = "testSetStereoVolumeMax";
        final int TEST_SR = 22050;
        final int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO;
        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
        final int TEST_MODE = AudioTrack.MODE_STREAM;
        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
 
        //-------- initialization --------------
        // 稍后详细分析 getMinBufferSize
        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
        // 创建一个 AudioTrack 实例
        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, 
                minBuffSize, TEST_MODE);
        byte data[] = new byte[minBuffSize/2];
        //--------    test        --------------
        // 调用 write() 写入回放数据
        track.write(data, 0, data.length);
        track.write(data, 0, data.length);
        // 调用 play() 开始播放
        track.play();
        float maxVol = AudioTrack.getMaxVolume();
        assertTrue(TEST_NAME, track.setStereoVolume(maxVol, maxVol) == AudioTrack.SUCCESS);
        //-------- tear down      --------------
        // 播放完成后,调用 release() 释放 AudioTrack 实例
        track.release();
    }

说明下 getMinBufferSize() 接口,字面意思是返回最小数据缓冲区的大小,它是声音能正常播放的最低保障,从函数参数来看,返回值取决于采样率、采样深度、声道数这三个属性。MODE_STREAM 模式下,应用程序重点参考其返回值然后确定分配多大的数据缓冲区。如果数据缓冲区分配得过小,那么播放声音会频繁遭遇 underrun,underrun 是指生产者(AudioTrack)提供数据的速度跟不上消费者(AudioFlinger::PlaybackThread)消耗数据的速度,反映到现实的后果就是声音断续卡顿,严重影响听觉体验。

2、AudioTrack Native API

AudioTrack Native API如下:

  • AudioTrack( audio_stream_type_t streamType, uint32_t sampleRate, audio_format_t format, audio_channel_mask_t channelMask, const sp<IMemory>& sharedBuffer):构造函数

  • status_t start():开始播放

  • void stop():停止播放

  • void flush():完成播放

  • void pause():暂停播放

  • status_t setVolume(float left, float right):设置音量

  • status_t setVolume(float volume):设置音量

  • status_t setSampleRate(uint32_t sampleRate):设置采样率

  • status_t setPlaybackRate(const AudioPlaybackRate &playbackRate):设置音频播放速率

  • status_t setLoop(uint32_t loopStart, uint32_t loopEnd, int loopCount):设置音频循环播放

  • status_t setOutputDevice(audio_port_handle_t deviceId):设置输出设备

  • status_t setPosition(uint32_t position):设置音频当前位置

  • ssize_t setBufferSizeInFrames(size_t size):设置音频缓冲区大小,单位为帧

  • status_t setParameters(const String8& keyValuePairs):设置音频播放参数

  • static status_t getMinFrameCount(size_t* frameCount, audio_stream_type_t streamType, uint32_t sampleRate):获取音频缓冲区最小的帧数需求

        AudioTrack Native API 四种数据传输模式:

Transfer ModeDescription

TRANSFER_CALLBACK

在 AudioTrackThread 线程中通过 audioCallback 回调函数主动从应用进程那里索取数据,ToneGenerator 采用这种模式

TRANSFER_OBTAIN

应用进程需要调用 obtainBuffer()/releaseBuffer() 填充数据,目前我还没有见到实际的使用场景

TRANSFER_SYNC

应用进程需要持续调用 write() 写数据到 FIFO,写数据时有可能遭遇阻塞(等待 AudioFlinger::PlaybackThread 消费之前的数据),基本适用所有的音频场景;对应于 AudioTrack Java API 的 MODE_STREAM 模式

TRANSFER_SHARED

应用进程将回放数据一次性付给 AudioTrack,适用于数据量小、时延要求高的场景;对应于 AudioTrack Java API 的 MODE_STATIC 模式

        AudioTrack Native API 音频流类型:

Stream TypeDescription

AUDIO_STREAM_VOICE_CALL

电话语音

AUDIO_STREAM_SYSTEM

系统声音

AUDIO_STREAM_RING

铃声声音,如来电铃声、闹钟铃声等

AUDIO_STREAM_MUSIC

音乐声音

AUDIO_STREAM_ALARM

警告音

AUDIO_STREAM_NOTIFICATION

通知音

AUDIO_STREAM_DTMF

DTMF 音(拨号盘按键音)

AudioTrack Native API 输出标识:

AUDIO_OUTPUT_FLAG

Description

AUDIO_OUTPUT_FLAG_DIRECT

表示音频流直接输出到音频设备,不需要软件混音,一般用于 HDMI 设备声音输出

AUDIO_OUTPUT_FLAG_PRIMARY

表示音频流需要输出到主输出设备,一般用于铃声类声音

AUDIO_OUTPUT_FLAG_FAST

表示音频流需要快速输出到音频设备,一般用于按键音、游戏背景音等对时延要求高的场景

AUDIO_OUTPUT_FLAG_DEEP_BUFFER

表示音频流输出可以接受较大的时延,一般用于音乐、视频播放等对时延要求不高的场景

AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD

表示音频流没有经过软件解码,需要输出到硬件解码器,由硬件解码器进行解码

我们根据不同的播放场景,使用不同的输出标识,如按键音、游戏背景音对输出时延要求很高,那么就需要置 AUDIO_OUTPUT_FLAG_FAST,具体可以参考 ToneGenerator、SoundPool 和 OpenSL ES。

一个 AudioTrack Natvie API 的测试例子(MODE_STATIC/TRANSFER_SHARED 模式),代码文件位置:frameworks/base/media/tests/audiotests/shared_mem_test.cpp:

int AudioTrackTest::Test01() {


    sp<MemoryDealer> heap;
    sp<IMemory> iMem;
    uint8_t* p;
 
    short smpBuf[BUF_SZ];
    long rate = 44100;
    unsigned long phi;
    unsigned long dPhi;
    long amplitude;
    long freq = 1237;
    float f0;
 
    f0 = pow(2., 32.) * freq / (float)rate;
    dPhi = (unsigned long)f0;
    amplitude = 1000;
    phi = 0;
    Generate(smpBuf, BUF_SZ, amplitude, phi, dPhi);  // fill buffer
 
    for (int i = 0; i < 1024; i++) {
        // 分配一块匿名共享内存
        heap = new MemoryDealer(1024*1024, "AudioTrack Heap Base");
 
        iMem = heap->allocate(BUF_SZ*sizeof(short));
 
        // 把音频数据拷贝到这块匿名共享内存上
        p = static_cast<uint8_t*>(iMem->pointer());
        memcpy(p, smpBuf, BUF_SZ*sizeof(short));
 
        // 构造一个 AudioTrack 实例,该 AudioTrack 的数据方式是 MODE_STATIC
        // 音频数据已经一次性拷贝到共享内存上了,不用再调用 track->write() 填充数据了
        sp<AudioTrack> track = new AudioTrack(AUDIO_STREAM_MUSIC,// stream type
               rate,
               AUDIO_FORMAT_PCM_16_BIT,// word length, PCM
               AUDIO_CHANNEL_OUT_MONO,
               iMem);
 
        // 检查 AudioTrack 实例是否构造成功了
        status_t status = track->initCheck();
        if(status != NO_ERROR) {
            track.clear();
            ALOGD("Failed for initCheck()");
            return -1;
        }
 
        // start play
        ALOGD("start");
        track->start(); // 开始播放
 
        usleep(20000);
 
        ALOGD("stop");
        track->stop(); // 停止播放
        iMem.clear();
        heap.clear();
        usleep(20000);
    }
 
    return 0;
}

首先要了解音频领域中,帧(frame)的概念:帧表示一个完整的声音单元,所谓的声音单元是指一个采样样本;如果是双声道,那么一个完整的声音单元就是 2 个样本,如果是 5.1 声道,那么一个完整的声音单元就是 6 个样本了。帧的大小(一个完整的声音单元的数据量)等于声道数乘以采样深度,即 frameSize = channelCount * bytesPerSample。帧的概念非常重要,无论是框架层还是内核层,都是以帧为单位去管理音频数据缓冲区的。

其次还得了解音频领域中,传输延迟(latency)的概念:传输延迟表示一个周期的音频数据的传输时间。可能有些读者一脸懵逼,一个周期的音频数据,这又是啥?我们再引入周期(period)的概念:Linux ALSA 把数据缓冲区划分为若干个块,dma 每传输完一个块上的数据即发出一个硬件中断,cpu 收到中断信号后,再配置 dma 去传输下一个块上的数据;一个块即是一个周期,周期大小(periodSize)即是一个数据块的帧数。再回到传输延迟(latency),传输延迟等于周期大小除以采样率,即 latency = periodSize / sampleRate。

最后了解下音频重采样:音频重采样是指这样的一个过程——把一个采样率的数据转换为另一个采样率的数据。Android 原生系统上,音频硬件设备一般都工作在一个固定的采样率上(如 48 KHz),因此所有音轨数据都需要重采样到这个固定的采样率上,然后再输出。为什么这么做?系统中可能存在多个音轨同时播放,而每个音轨的采样率可能是不一致的;比如在播放音乐的过程中,来了一个提示音,这时需要把音乐和提示音混音并输出到硬件设备,而音乐的采样率和提示音的采样率不一致,问题来了,如果硬件设备工作的采样率设置为音乐的采样率的话,那么提示音就会失真;因此最简单见效的解决方法是:硬件设备工作的采样率固定一个值,所有音轨在 AudioFlinger 都重采样到这个采样率上,混音后输出到硬件设备,保证所有音轨听起来都不失真。

了解这些前置知识后,我们再分析 AudioTrack::getMinFrameCount() 这个函数:

status_t AudioTrack::getMinFrameCount(
        size_t* frameCount,
        audio_stream_type_t streamType,
        uint32_t sampleRate)
{
    if (frameCount == NULL) {
        return BAD_VALUE;
    }
 
    // 通过 binder 调用到 AudioFlinger::sampleRate(),取得硬件设备的采样率
    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;
    }
    // 通过 binder 调用到 AudioFlinger::frameCount(),取得硬件设备的周期大小
    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;
    }
    // 通过 binder 调用到 AudioFlinger::latency(),取得硬件设备的传输延迟
    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;
    }
 
    // When called from createTrack, speed is 1.0f (normal speed).
    // This is rechecked again on setting playback rate (TODO: on setting sample rate, too).
    // 根据 afSampleRate、afFrameCount、afLatency 计算出一个最低帧数
    *frameCount = calculateMinFrameCount(afLatency, afFrameCount, afSampleRate, sampleRate, 1.0f);
 
    // The formula above should always produce a non-zero value under normal circumstances:
    // AudioTrack.SAMPLE_RATE_HZ_MIN <= sampleRate <= AudioTrack.SAMPLE_RATE_HZ_MAX.
    // Return error in the unlikely event that it does not, as that's part of the API contract.
    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;
}
 
// 有兴趣的可以研究 calculateMinFrameCount() 的实现,需大致了解重采样算法原理
static size_t calculateMinFrameCount(
        uint32_t afLatencyMs, uint32_t afFrameCount, uint32_t afSampleRate,
        uint32_t sampleRate, float speed)
{
    // Ensure that buffer depth covers at least audio hardware latency
    uint32_t minBufCount = afLatencyMs / ((1000 * afFrameCount) / afSampleRate);
    if (minBufCount < 2) {
        minBufCount = 2;
    }
    ALOGV("calculateMinFrameCount afLatency %u  afFrameCount %u  afSampleRate %u  "
            "sampleRate %u  speed %f  minBufCount: %u",
            afLatencyMs, afFrameCount, afSampleRate, sampleRate, speed, minBufCount);
    return minBufCount * sourceFramesNeededWithTimestretch(
            sampleRate, afFrameCount, afSampleRate, speed);
}
 
static inline size_t sourceFramesNeededWithTimestretch(
        uint32_t srcSampleRate, size_t dstFramesRequired, uint32_t dstSampleRate,
        float speed) {
    // required is the number of input frames the resampler needs
    size_t required = sourceFramesNeeded(srcSampleRate, dstFramesRequired, dstSampleRate);
    // to deliver this, the time stretcher requires:
    return required * (double)speed + 1 + 1; // accounting for rounding dependencies
}
 
// Returns the source frames needed to resample to destination frames.  This is not a precise
// value and depends on the resampler (and possibly how it handles rounding internally).
// Nevertheless, this should be an upper bound on the requirements of the resampler.
// If srcSampleRate and dstSampleRate are equal, then it returns destination frames, which
// may not be true if the resampler is asynchronous.
static inline size_t sourceFramesNeeded(
        uint32_t srcSampleRate, size_t dstFramesRequired, uint32_t dstSampleRate) {
    // +1 for rounding - always do this even if matched ratio (resampler may use phases not ratio)
    // +1 for additional sample needed for interpolation
    return srcSampleRate == dstSampleRate ? dstFramesRequired :
            size_t((uint64_t)dstFramesRequired * srcSampleRate / dstSampleRate + 1 + 1);
}

三、AudioTrack流程分析

CreateTrack流程分析

Android13 CreateTrack流程分析-CSDN博客

AudioTrack Play流程分析

Android13 AudioTrack Play流程分析-CSDN博客

AudioTrack write流程分析

Android13 AudioTrack write流程分析-CSDN博客

AudioTrack getMinBufferSize流程分析

Android13 AudioTrack getMinBufferSize流程分析-CSDN博客

  • 25
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值