Android N的Audio系统(三)

来源:互联网 发布:java list分组 编辑:程序博客网 时间:2024/06/06 05:39
  • AudioTrack Native API

AudioTrack Native API 四种数据传输模式:
这里写图片描述

AudioTrack Native API 音频流类型:

这里写图片描述

AudioTrack Native API 输出标识:

这里写图片描述

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

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

/**************************************************************    Shared memory test*************************************************************/#define BUF_SZ 44100int 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));        sp<AudioTrack> track = new AudioTrack(AUDIO_STREAM_MUSIC,// stream type               rate,               AUDIO_FORMAT_PCM_16_BIT,// word length, PCM               AUDIO_CHANNEL_OUT_MONO,               iMem);        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;}

上个小节还存在一个问题:AudioTrack::getMinFrameCount() 如何计算最低帧数呢?

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

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

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

sample、frame、period、latency 这些概念与 Linux ALSA 及硬件设备的关系非常密切,这里点到即止,如有兴趣深入了解的话,可参考:Linux ALSA 音频系统:逻辑设备篇。

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

这里写图片描述

我们不深入分析 calculateMinFrameCount() 函数了,并不是说这个函数的流程有多复杂,而是它涉及到音频重采样的背景原理,说清楚 how 很容易,但说清楚 why 就很困难了。目前我们只需要知道:这个函数根据硬件设备的配置信息(采样率、周期大小、传输延迟)和音轨的采样率,计算出一个最低帧数(应用程序至少设置多少个帧才能保证声音正常播放)。

说点题外话,Anroid 2.2 时,AudioTrack::getMinFrameCount() 的处理很简单:

这里写图片描述

从这段来看,最低帧数也是基于重采样来计算的,只不过这里的处理很粗糙:afFrameCount 是硬件设备处理单个数据块的帧数,afSampleRate 是硬件设备配置的采样率,sampleRate 是音轨的采样率,如果要把音轨数据重采样到 afSampleRate 上,那么反推算出应用程序最少传入的帧数为 afFrameCount sampleRate / afSampleRate,而为了播放流畅,实际上还要大一点,所以再乘以一个系数(可参照 framebuffer 双缓冲,一个缓冲缓存当前的图像,一个缓冲准备下一幅的图像,这样图像切换更流畅),然后就得出一个可以保证播放流畅的最低帧数 minFrameCount = (afFrameCount sampleRate / afSampleRate) * minBufCount。

为什么 Android 7.0 这方面的处理比 Android 2.2 复杂那么多呢?我想是两个原因:
* Android 7.0 充分考虑了边界处理。
* Android 2.2 只支持采样率 4~48 KHz 的音轨,但 Android 7.0 支持采样率 4~192 KHz 的音轨,因此现在对重采样处理提出更严格的要求。