美文网首页
AudioTrack详解

AudioTrack详解

作者: _好好学习 | 来源:发表于2020-05-21 21:11 被阅读0次

    写在前面:AudioTrack是管理和播放单一音频资源的类。AudioTrack仅仅能播放已经解码的PCM流,用于PCM音频流的回放。

    AudioTrack架构图.png
    AudioTrack播放PCM音频
    1. 配置基本参数
      • StreamType音频流类型:系统声音的音频流、音乐播放的音频流、用于通话的音频流、用于通知的音频流等。该参数的含义就是告诉系统,我现在想使用的是哪种类型的声音,这样系统就可以对应管理他们了。
        Android 为什么要定义这么多的流类型?这与 Android 的音频管理策略有关,例如:
        • 音频流的音量管理,调节一个类型的音频流音量,不会影响到其他类型的音频流
        • 根据流类型选择合适的输出设备;比如插着有线耳机期间,音乐声(STREAM_MUSIC)只会输出到有线耳机,而铃声(STREAM_RING)会同时输出到有线耳机和外放。
          一个典型的场景:比如你在听music的时候接到电话,这个时候music播放肯定会停止,此时你只能听到电话,如果你调节音量的话,这个调节肯定只对电话起作用。当电话打完了,再回到music,你肯定不用再调节音量了。
          应用开发者应该根据应用场景选择相应的流类型,以便系统为这道流选择合适的输出设备。
      • Mode模式
        • AudioTrack.MODE_STREAM: STREAM的意思是由用户在应用程序通过write方式把数据一次一次得写到AudioTrack中
        • AudioTrack.MODE_STATIC:STATIC就是数据一次性交付给接收方。
      • 采样率mSampleRateInHz:
      • 音频量化位数mAudioFormat(只支持8bit和16bit两种。)
      • 通道数目mChannelConfig:最多只支持双音道:
    在audioParamCheck()中
    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);
              }
          }
      }
    
    • buffer获取最小缓冲区大小

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

    static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) {
        
        ....
    
        int size = native_get_min_buff_size(sampleRateInHz, channelCount, audioFormat);
        if (size <= 0) {
            loge("getMinBufferSize(): error querying hardware");
            return ERROR;
        }
        else {
            return size;
        }}
    
    

    可以看到,mMinBufferSize取决于采样率、声道数和采样深度三个属性,其中源码缓冲区的大小的实现在native层中:

    //frameworks/base/core/jni/android_media_AudioTrack.cpp
    
    static jint android_media_AudioTrack_get_min_buff_size(JNIEnv*env,  jobject thiz,
    
    jint sampleRateInHertz,jint nbChannels, jint audioFormat) {
    
    int frameCount = 0;
    
    if(AudioTrack::getMinFrameCount(&frameCount, AUDIO_STREAM_DEFAULT,sampleRateInHertz) != NO_ERROR) {
    
        return -1;
    
     }
    
     return  frameCount * nbChannels * (audioFormat ==javaAudioTrackFields.PCM16 ? 2 : 1);
    
    }
    

    使用示例

    int sampleRateInHz=32000;
        int nb_channels=2;
            //固定格式的音频码流
            int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
            //声道布局
            int channelConfig;
            if(nb_channels == 1){
                channelConfig = android.media.AudioFormat.CHANNEL_OUT_MONO;
            }else if(nb_channels == 2){
                channelConfig = android.media.AudioFormat.CHANNEL_OUT_STEREO;
            }else{
                channelConfig = android.media.AudioFormat.CHANNEL_OUT_STEREO;
            }
    
            int bufferSizeInBytes = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
    
            AudioTrack audioTrack = new AudioTrack(
                    AudioManager.STREAM_MUSIC,// 指定流的类型
                    sampleRateInHz,// 设置音频数据的採样率 32k,假设是44.1k就是44100
                    channelConfig,// 设置输出声道为双声道立体声,而CHANNEL_OUT_MONO类型是单声道
                    audioFormat,// 设置音频数据块是8位还是16位。这里设置为16位。
                    bufferSizeInBytes,//缓冲区大小
                    AudioTrack.MODE_STREAM // 设置模式类型,在这里设置为流类型,第二种MODE_STATIC貌似没有什么效果
            );
    audio.play(); // 启动音频设备。以下就能够真正開始音频数据的播放了
    // 打开mp3文件,读取数据,解码等操作省略 ...
    byte[] buffer = new buffer[4096];
    int count;
    while(true)
    {
        // 最关键的是将解码后的数据,从缓冲区写入到AudioTrack对象中
        audio.write(buffer, 0, 4096);
        if(文件结束) break;
    }
    //关闭并释放资源
    audio.stop();
    audio.release();
    

    AudioTrack源码分析

    创建AudioTrack对象

    取得mMinBufferSize后,可以创建一个AudioTrack对象了:

    
    public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode)throws IllegalArgumentException {
        this(streamType, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes, mode, AudioManager.AUDIO_SESSION_ID_GENERATE);
        }
    

    这里是调用了该public AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes, int mode, int sessionId)构造方法:

    
    public AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
            int mode, int sessionId)
                    throws IllegalArgumentException {
        super(attributes, AudioPlaybackConfiguration.PLAYER_TYPE_JAM_AUDIOTRACK);
        
        .....
    
        // native initialization
        int initResult = native_setup(new WeakReference<AudioTrack>(this), mAttributes,
                sampleRate, mChannelMask, mChannelIndexMask, mAudioFormat,
                mNativeBufferSizeInBytes, mDataLoadMode, session, 0 /*nativeTrackInJavaObj*/);
        if (initResult != SUCCESS) {
            loge("Error code "+initResult+" when initializing AudioTrack.");
            return; // with mState == STATE_UNINITIALIZED
        }
    
        mSampleRate = sampleRate[0];
        mSessionId = session[0];
    
        if (mDataLoadMode == MODE_STATIC) {
            mState = STATE_NO_STATIC_DATA;
        } else {
            mState = STATE_INITIALIZED;
        }
    
        baseRegisterPlayer();}
    

    AudioTrack对象的创建过程涉及到native_setup方法

    //*frameworks/base/core/jni/android_media_AudioTrack.cpp
    
    static int  android_media_AudioTrack_native_setup(JNIEnv*env, jobject thiz, jobject weak_this,
            jint streamType, jintsampleRateInHertz, jint javaChannelMask,
            jint audioFormat, jintbuffSizeInBytes, jint memoryMode, jintArray jSession)
    
    {   
        .....
        sp<AudioTrack>lpTrack = new AudioTrack();
        .....
    AudioTrackJniStorage* lpJniStorage =new AudioTrackJniStorage();
    
    //frameworks/av/media/libaudioclient/AudioTrack.cpp
    
    status_t AudioTrack::createTrack_l()
    {
    status_t status;
    bool callbackAdded = false;
    
    const sp<IAudioFlinger>& audioFlinger = AudioSystem::get_audio_flinger();
    …
    
    sp<IAudioTrack> track = audioFlinger->createTrack(input, output, &status);
    
    }
    
    //frameworks/av/media/libaudioclient/include/media/IAudioFlinger.h
    virtual sp<IAudioTrack> createTrack(const CreateTrackInput& input,
    CreateTrackOutput& output,
    status_t *status) = 0;
    

    每一个音频流对应着一个AudioTrack类的一个实例,每个AudioTrack会在创建时注册到 AudioFlinger中,由AudioFlinger把所有的AudioTrack进行混合(Mixer),然后输送到AudioHardware中进行播放,目前Android同时最多可以创建32个音频流,也就是说,Mixer最多会同时处理32个AudioTrack的数据流:


    示意图.png

    AudioPolicyService 与 AudioFlinger 是 Android 音频系统的两大基本服务。前者是音频系统策略的制定者,负责音频设备切换的策略抉择、音量调节策略等;后者是音频系统策略的执行者,负责音频流设备的管理及音频流数据的处理传输,所以 AudioFlinger 也被认为是 Android 音频系统的引擎。

    • AudioFlinger服务启动
    //frameworks/av/media/audioserver/main_audioserver.cpp
    android::hardware::configureRpcThreadpool(4, false /*callerWillJoin*/);
    sp<ProcessState> proc(ProcessState::self());
    sp<IServiceManager> sm = defaultServiceManager();
    ALOGI("ServiceManager: %p", sm.get());
    AudioFlinger::instantiate();
    AudioPolicyService::instantiate();
    //可见 audioserver 把音频相关的服务都加载了,包括 AudioFlinger、AudioPolicyService、RadioService、SoundTriggerHwService。
    // AAudioService should only be used in OC-MR1 and later.
    // And only enable the AAudioService if the system MMAP policy explicitly allows it.
    // This prevents a client from misusing AAudioService when it is not supported.
    aaudio_policy_t mmapPolicy = property_get_int32(AAUDIO_PROP_MMAP_POLICY,
    AAUDIO_POLICY_NEVER);
    if (mmapPolicy == AAUDIO_POLICY_AUTO || mmapPolicy == AAUDIO_POLICY_ALWAYS) {
    AAudioService::instantiate();
    }
    
    SoundTriggerHwService::instantiate();
    ProcessState::self()->startThreadPool();
    IPCThreadState::self()->joinThreadPool();
    

    AudioFlinger 服务启动后,其他进程可以通过 ServiceManager 来获取其代理对象 IAudioFlinger,通过 IAudioFlinger 可以向 AudioFlinger 发出各种服务请求,从而完成自己的音频业务。
    音频流控制最常用的三个接口:

    • AudioFlinger::PlaybackThread::Track::start:开始播放:把该 Track 置 ACTIVE 状态,然后添加到 mActiveTracks 向量中,最后调用AudioFlinger::PlaybackThread::broadcast_l() 告知 PlaybackThread 情况有变
    • AudioFlinger::PlaybackThread::Track::stop:停止播放:把该 Track 置 STOPPED 状态,最后调用 AudioFlinger::PlaybackThread::broadcast_l() 告知 PlaybackThread 情况有变
    • AudioFlinger::PlaybackThread::Track::pause:暂停播放:把该 Track 置 PAUSING 状态,最后调用 AudioFlinger::PlaybackThread::broadcast_l() 告知 PlaybackThread 情况有变

    AudioFlinger 响应的服务请求主要有:

    • 获取硬件设备的配置信息
    • 音量调节
    • 静音操作
    • 音频模式切换
    • 音频参数设置
    • 输入输出流设备管理
    • 音频流管理
    • AudioTrack(写数据):AudioTrack 在 FIFO 中找到一块可用空间,把用户传入的音频数据写入到这块可用空间上,然后更新写位置(对于 AudioFinger 来说,意味 FIFO 上有更多的可读数据了);如果用户传入的数据量比可用空间要大,那么要把用户传入的数据拆分多次写入到 FIFO 中(AudioTrack 和 AudioFlinger 是不同的进程,AudioFlinger 同时也在不停地读取数据,所以 FIFO 可用空间是在不停变化的)
    • AudioFlinger(读数据):AudioFlinger 在 FIFO 中找到一块可读数据块,把可读数据拷贝到目的缓冲区上,然后更新读位置(对于 AudioTrack 来说,意味着 FIFO 上有更多的可用空间了);如果FIFO 上可读数据量比预期的要小,那么要进行多次的读取,才能积累到预期的数据量(AudioTrack 和 AudioFlinger 是不同的进程,AudioTrack 同时也在不停地写入数据,所以 FIFO 可读的数据量是在不停变化的)
      write方法的使用:
    // 创建一个 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();
    

    开始播放

    play()->startImpl()->native_start()->android_media_AudioTrack_start.lpTrack->start()(JNI)->
    mAudioTrack->start()(native)->通过IAudioTrack调用
    virtual status_t start() = 0;

    停止播放
    停止播放音频数据,如果是STREAM模式,会等播放完最后写入buffer的数据才会停止。如果立即停止,要调用pause()方法,然后调用flush方法,会舍弃还没有播放的数据(flush()只在模式为STREAM下可用)。

    stop()->native_stop()->
    android_media_AudioTrack_stop中lpTrack->stop()(JNI)->

    mAudioTrack->stop()->virtual void stop() = 0(IAudioTrack.h)

    释放本地AudioTrack资源

    release()->native_release()->android_media_AudioTrack_release

    
    static void android_media_AudioTrack_release(JNIEnv *env, jobject thiz) {
    sp<AudioTrack> lpTrack = setAudioTrack(env, thiz, 0);
    if (lpTrack == NULL) {
    return;
    }
    //ALOGV("deleting lpTrack: %x\n", (int)lpTrack);
    
    // delete the JNI data
    AudioTrackJniStorage* pJniStorage = (AudioTrackJniStorage *)env->GetLongField(
    thiz, javaAudioTrackFields.jniData);
    // reset the native resources in the Java object so any attempt to access
    // them after a call to release fails.
    env->SetLongField(thiz, javaAudioTrackFields.jniData, 0);
    
    if (pJniStorage) {
    Mutex::Autolock l(sLock);
    audiotrack_callback_cookie *lpCookie = &pJniStorage->mCallbackData;
    //ALOGV("deleting pJniStorage: %x\n", (int)pJniStorage);
    while (lpCookie->busy) {
    if (lpCookie->cond.waitRelative(sLock,
    milliseconds(CALLBACK_COND_WAIT_TIMEOUT_MS)) !=
    NO_ERROR) {
    break;
    }
    }
    sAudioTrackCallBackCookies.remove(lpCookie);
    // delete global refs created in native_setup
    env->DeleteGlobalRef(lpCookie->audioTrack_class);
    env->DeleteGlobalRef(lpCookie->audioTrack_ref);
    delete pJniStorage;
    }
    }
    
    

    返回当前的播放状态

    相关文章

      网友评论

          本文标题:AudioTrack详解

          本文链接:https://www.haomeiwen.com/subject/qoqxahtx.html