AudioTrack详解

2020-05-21  本文已影响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);
          }
      }
  }

字面意思是返回最小数据缓冲区的大小,它是声音能正常播放的最低保障,从函数参数来看,返回值取决于采样率、采样深度、声道数这三个属性。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 音频系统的引擎。

//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 响应的服务请求主要有:

// 创建一个 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;
}
}

返回当前的播放状态

上一篇下一篇

猜你喜欢

热点阅读