Android 音频系统:从 AudioTrack 到 AudioFlinger ----建议看原文,很精彩,透彻

转载:https://blog.csdn.net/zyuanyun/article/details/60890534

Android 音频系统:从 AudioTrack 到 AudioFlinger
原创zyuanyun 最后发布于2017-03-09 00:01:16 阅读数 54011  收藏
展开
1. Android 音频框架概
Audio 是整个 Android 平台非常重要的一个组成部分,负责音频数据的采集和输出、音频流的控制、音频设备的管理、音量调节等,主要包括如下部分:

Audio Application Framework:音频应用框架
AudioTrack:负责回放数据的输出,属 Android 应用框架 API 类
AudioRecord:负责录音数据的采集,属 Android 应用框架 API 类
AudioSystem: 负责音频事务的综合管理,属 Android 应用框架 API 类
Audio Native Framework:音频本地框架
AudioTrack:负责回放数据的输出,属 Android 本地框架 API 类
AudioRecord:负责录音数据的采集,属 Android 本地框架 API 类
AudioSystem: 负责音频事务的综合管理,属 Android 本地框架 API 类
Audio Services:音频服务
AudioPolicyService:音频策略的制定者,负责音频设备切换的策略抉择、音量调节策略等
AudioFlinger:音频策略的执行者,负责输入输出流设备的管理及音频流数据的处理传输
Audio HAL:音频硬件抽象层,负责与音频硬件设备的交互,由 AudioFlinger 直接调用
与 Audio 强相关的有 MultiMedia,MultiMedia 负责音视频的编解码,MultiMedia 将解码后的数据通过 AudioTrack 输出,而 AudioRecord 采集的录音数据交由 MultiMedia 进行编码。

本文分析基于 Android 7.0 - Nougat。

//
// 声明:本文由 http://blog.csdn.net/zyuanyun 原创,转载请注明出处,谢谢!
//

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

2.1. AudioTrack Java API
AudioTrack Java API 两种数据传输模式:

Transfer Mode    Description
MODE_STATIC    应用进程将回放数据一次性付给 AudioTrack,适用于数据量小、时延要求高的场景
MODE_STREAM    用进程需要持续调用 write() 写数据到 FIFO,写数据时有可能遭遇阻塞(等待 AudioFlinger::PlaybackThread 消费之前的数据),基本适用所有的音频场景


AudioTrack Java API 音频流类型:

Stream Type    Description
STREAM_VOICE_CALL    电话语音
STREAM_SYSTEM    系统声音
STREAM_RING    铃声声音,如来电铃声、闹钟铃声等
STREAM_MUSIC    音乐声音
STREAM_ALARM    警告音
STREAM_NOTIFICATION    通知音
STREAM_DTMF    DTMF 音(拨号盘按键音)


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

音频流的音量管理,调节一个类型的音频流音量,不会影响到其他类型的音频流
根据流类型选择合适的输出设备;比如插着有线耳机期间,音乐声(STREAM_MUSIC)只会输出到有线耳机,而铃声(STREAM_RING)会同时输出到有线耳机和外放
这些属于 AudioPolicyService 的内容,本文不展开分析了。应用开发者应该根据应用场景选择相应的流类型,以便系统为这道流选择合适的输出设备。

一个 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)消耗数据的速度,反映到现实的后果就是声音断续卡顿,严重影响听觉体验。

// AudioTrack.java
/**
     * Returns the estimated minimum buffer size required for an AudioTrack
     * object to be created in the {@link #MODE_STREAM} mode.
     * The size is an estimate because it does not consider either the route or the sink,
     * since neither is known yet.  Note that this size doesn't
     * guarantee a smooth playback under load, and higher values should be chosen according to
     * the expected frequency at which the buffer will be refilled with additional data to play.
     * For example, if you intend to dynamically set the source sample rate of an AudioTrack
     * to a higher value than the initial source sample rate, be sure to configure the buffer size
     * based on the highest planned sample rate.
     * @param sampleRateInHz the source sample rate expressed in Hz.
     *   {@link AudioFormat#SAMPLE_RATE_UNSPECIFIED} is not permitted.
     * @param channelConfig describes the configuration of the audio channels.
     *   See {@link AudioFormat#CHANNEL_OUT_MONO} and
     *   {@link AudioFormat#CHANNEL_OUT_STEREO}
     * @param audioFormat the format in which the audio data is represented.
     *   See {@link AudioFormat#ENCODING_PCM_16BIT} and
     *   {@link AudioFormat#ENCODING_PCM_8BIT},
     *   and {@link AudioFormat#ENCODING_PCM_FLOAT}.
     * @return {@link #ERROR_BAD_VALUE} if an invalid parameter was passed,
     *   or {@link #ERROR} if unable to query for output properties,
     *   or the minimum buffer size expressed in bytes.
     */
    static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) {
        int channelCount = 0;
        switch(channelConfig) {
        case AudioFormat.CHANNEL_OUT_MONO:
        case AudioFormat.CHANNEL_CONFIGURATION_MONO:
            channelCount = 1; // 单声道
            break;
        case AudioFormat.CHANNEL_OUT_STEREO:
        case AudioFormat.CHANNEL_CONFIGURATION_STEREO:
            channelCount = 2; // 双声道
            break;
        default:
            if (!isMultichannelConfigSupported(channelConfig)) {
                loge("getMinBufferSize(): Invalid channel configuration.");
                return ERROR_BAD_VALUE;
            } else {
                channelCount = AudioFormat.channelCountFromOutChannelMask(channelConfig);
            }
        }

        if (!AudioFormat.isPublicEncoding(audioFormat)) {
            loge("getMinBufferSize(): Invalid audio format.");
            return ERROR_BAD_VALUE;
        }

        // sample rate, note these values are subject to change
        // Note: AudioFormat.SAMPLE_RATE_UNSPECIFIED is not allowed
        if ( (sampleRateInHz < AudioFormat.SAMPLE_RATE_HZ_MIN) ||
                (sampleRateInHz > AudioFormat.SAMPLE_RATE_HZ_MAX) ) {
            loge("getMinBufferSize(): " + sampleRateInHz + " Hz is not a supported sample rate.");
            return ERROR_BAD_VALUE; // 采样率支持:4KHz~192KHz
        }

        // 调用 JNI 方法,下面分析该函数
        int size = native_get_min_buff_size(sampleRateInHz, channelCount, audioFormat);
        if (size <= 0) {
            loge("getMinBufferSize(): error querying hardware");
            return ERROR;
        }
        else {
            return size;
        }
    }

// android_media_AudioTrack.cpp
// ----------------------------------------------------------------------------
// returns the minimum required size for the successful creation of a streaming AudioTrack
// returns -1 if there was an error querying the hardware.
static jint android_media_AudioTrack_get_min_buff_size(JNIEnv *env,  jobject thiz,
    jint sampleRateInHertz, jint channelCount, jint audioFormat) {

    size_t frameCount;
    // 调用 AudioTrack::getMinFrameCount,这里不深究,到 native 层再分析
    // 这个函数用于确定至少设置多少个 frame 才能保证声音正常播放,也就是最低帧数
    const status_t status = AudioTrack::getMinFrameCount(&frameCount, AUDIO_STREAM_DEFAULT,
            sampleRateInHertz);
    if (status != NO_ERROR) {
        ALOGE("AudioTrack::getMinFrameCount() for sample rate %d failed with status %d",
                sampleRateInHertz, status);
        return -1;
    }
    const audio_format_t format = audioFormatToNative(audioFormat);
    if (audio_has_proportional_frames(format)) {
        const size_t bytesPerSample = audio_bytes_per_sample(format);
        return frameCount * channelCount * bytesPerSample; // PCM 数据最小缓冲区大小
    } else {
        return frameCount;
    }
}

可见最小缓冲区的大小 = 最低帧数 * 声道数 * 采样深度,(采样深度以字节为单位),到这里大家应该有所明悟了吧,在视频中,如果帧数过低,那么画面会有卡顿感,对于音频,道理也是一样的。最低帧数如何求得,我们到 native 层再解释。

关于 MediaPlayer、AudioTrack,更多更详细的 API 接口说明请参考 Android Developer:

MediaPlayer:https://developer.android.com/reference/android/media/MediaPlayer.html
AudioTrack:https://developer.android.com/reference/android/media/AudioTrack.html
2.2. AudioTrack Native API
AudioTrack Native API 四种数据传输模式:

Transfer Mode    Description
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 Type    Description
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;
}

上个小节还存在一个问题: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() 这个函数:

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).
    // 根据 afSample

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 从AudiotrackAudioflingerAndroid音频系统中的两个重要组件。 Audiotrack是一个用于播放音频的类,它可以创建一个音频缓冲区并将其发送到音频输出设备进行播放。它是一个应用程序级别的组件,通常用于播放音乐、语音和其他音频文件。 而AudioflingerAndroid音频系统中的一个系统级别的组件,它是一个音频引擎,负责管理所有音频输入和输出。它接收来自应用程序的音频数据,并将其传递给音频硬件进行处理和播放。它还负责处理音频路由、音量控制和音频效果等功能。 因此,从AudiotrackAudioflinger可以看作是从应用程序级别到系统级别的转变,是Android音频系统中的两个不同层次的组件。 ### 回答2: AudiotrackAudioflinger都是在Android系统中用于音频播放的重要组件。Audiotrack用于创建和播放音频流,而Audioflinger用于管理多个应用程序之间的音频流和混合。 Audiotrack是一个Android中的音频类,可以用于播放音频流。它可以在Android系统中创建一个音频流,并且可以改变音频流的属性。Audiotrack的主要作用是把音频数据从应用程序的内存传输到Android系统音频支持部分。 而Audioflinger是位于Android音频架构中的一个子系统,负责管理系统中所有音频的播放和捕获。它的主要作用是把来自不同应用程序的音频流与硬件设备联系起来,并且确保能够同时正确播放不同的音频流,避免互相干扰。在音频流的处理上,Audioflinger足够强大,它可以动态地混合多个音频流,并且可以控制音量,频率和均衡器。 从AudiotrackAudioflinger的发展,反映出移动设备音频处理技术的不断发展和进步。在早期的Android系统中,为了实现音频播放,开发者们需要使用OpenSL ES这样的第三方库进行音频处理。随着Android系统版本不断升级,AudiotrackAudioflinger成为系统中的基本音频功能,大幅提升了音频处理的效率和质量。AudiotrackAudioflinger的不断完善与升级,也为一些音视频应用开发提供了强有力的支持,这样应用程序可以在播放,录制和处理音频时得到更好的用户体验。总之,随着技术的不断进步,那些基础的音频组件不断完善回馈给大家的是更好的用户体验。 ### 回答3: AudiotrackAndroid系统中一个用于音频输出的类,它提供了许多方法,如setLoopPoints、setPlaybackParams、setStereoVolume等,使开发者可以在自己的应用程序中控制音频播放的各个方面。使用Audiotrack,程序员可以将音频数据写入到该类的缓冲区中,然后Audiotrack会将缓冲区中的数据转换为声音,并输出到音频设备中播放。 但由于Audiotrack只是用于输出音频数据,并不能处理来自不同应用的音频数据,这就需要一个用于管理和处理应用程序之间的音频数据交换的系统服务。因此,Android系统中还有一个系统服务叫做AudioFlingerAudioFlinger位于系统服务层,是Android系统中的一个核心组件。它的主要责任是管理音频任务,处理音频数据和控制音频设备等,是整个Android系统中最底层的音频管理和处理服务。它可以与多个音频驱动程序(如ALSA,OpenSL ES等)进行通信,支持多流、多线程音频数据流的处理,可以同时处理不同应用程之间产生的音频数据。 当应用程序使用Audiotrack播放音频时,AudioFlinger会接收来自Audiotrack音频数据并将其传递给音频驱动程序,然后由音频驱动程序将数据送往声卡,最终将音频数据转化为声音输出。 总之,AudiotrackAudioFlinger都是Android系统中至关重要的音频处理组件。Audiotrack是用于控制音频输出的应用级组件;AudioFlinger系统级组件,用于管理和处理多个应用程序之间的音频数据交换,从而完成音频设备的控制和音频数据的处理。两者紧密协作,为用户提供一个高效更好的音频服务。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值