AudioTrack 实现原理分析

本次分析基于 Android 1.6 源码,虽然随着 Android 版本的不断迭代更新,很多类和函数有很大变化,但是基本原理没有变。

一、构造函数

分析一个类,首先看它的构造函数:
[AudioTrack.java]

public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat,
        int bufferSizeInBytes, int mode)
throws IllegalArgumentException {
    mState = STATE_UNINITIALIZED;
    ...
    // 检查参数是否有效
    audioParamCheck(streamType, sampleRateInHz, channelConfig, audioFormat, mode);
    // 检查 buffer 空间是否够用,此值由 getMinBufferSize 获取。
    audioBuffSizeCheck(bufferSizeInBytes);

    // native 部分初始化,native 部分会创建一个 AudioTrack 同名类的对象
    int initResult = native_setup(new WeakReference<AudioTrack>(this),
            mStreamType, mSampleRate, mChannelCount, mAudioFormat,
            mNativeBufferSizeInBytes, mDataLoadMode);
    if (initResult != SUCCESS) {
        loge("Error code "+initResult+" when initializing AudioTrack.");
        return; // with mState == STATE_UNINITIALIZED
    }

    // AudioTrack 有两种工作模式:MODE_STATIC 在 play 开始时一次性将音频数据写入 buffer 中;MODE_STREAM 在 play 过程中通过 write 分段写入 buffer 中。 
    if (mDataLoadMode == MODE_STATIC) {
        mState = STATE_NO_STATIC_DATA;
    } else {
        mState = STATE_INITIALIZED;
    }
}

native 部分初始化:
[android_media_AudioTrack.cpp]

static int android_media_AudioTrack_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
        jint streamType, jint sampleRateInHertz, jint nbChannels,
        jint audioFormat, jint buffSizeInBytes, jint memoryMode)
{
    ...
    // 获取音频硬件 buffer 中可以容纳的帧数
    if (AudioSystem::getOutputFrameCount(&afFrameCount, streamType) != NO_ERROR) {
        LOGE("Error creating AudioTrack: Could not get AudioSystem frame count.");
        return AUDIOTRACK_ERROR_SETUP_AUDIOSYSTEM;
    }
    // 获取音频硬件采样率
    if (AudioSystem::getOutputSamplingRate(&afSampleRate, streamType) != NO_ERROR) {
        LOGE("Error creating AudioTrack: Could not get AudioSystem sampling rate.");
        return AUDIOTRACK_ERROR_SETUP_AUDIOSYSTEM;
    }
    // 仅支持单声道和双声道
    if ((nbChannels == 0) || (nbChannels > 2)) {
        LOGE("Error creating AudioTrack: channel count is not 1 or 2.");
        return AUDIOTRACK_ERROR_SETUP_INVALIDCHANNELCOUNT;
    }
    
    // 将 java 层定义的流类型转换成 native 流类型
    AudioSystem::stream_type atStreamType;
    if (streamType == javaAudioTrackFields.STREAM_VOICE_CALL) {
        atStreamType = AudioSystem::VOICE_CALL;
    } else if (streamType == javaAudioTrackFields.STREAM_SYSTEM) {
        atStreamType = AudioSystem::SYSTEM;
    } else if (streamType == javaAudioTrackFields.STREAM_RING) {
        atStreamType = AudioSystem::RING;
    } else if (streamType == javaAudioTrackFields.STREAM_MUSIC) {
        atStreamType = AudioSystem::MUSIC;
    } else if (streamType == javaAudioTrackFields.STREAM_ALARM) {
        atStreamType = AudioSystem::ALARM;
    } else if (streamType == javaAudioTrackFields.STREAM_NOTIFICATION) {
        atStreamType = AudioSystem::NOTIFICATION;
    } else if (streamType == javaAudioTrackFields.STREAM_BLUETOOTH_SCO) {
        atStreamType = AudioSystem::BLUETOOTH_SCO;
    } else {
        LOGE("Error creating AudioTrack: unknown stream type.");
        return AUDIOTRACK_ERROR_SETUP_INVALIDSTREAMTYPE;
    }

    // 仅支持 PCM16 和 PCM8 两种采样精度
    if ((audioFormat != javaAudioTrackFields.PCM16) && (audioFormat != javaAudioTrackFields.PCM8)) {
        LOGE("Error creating AudioTrack: unsupported audio format.");
        return AUDIOTRACK_ERROR_SETUP_INVALIDFORMAT;
    }
    ...
    // 计算用户缓存区可以容纳的帧数量
    int bytesPerSample = audioFormat == javaAudioTrackFields.PCM16 ? 2 : 1;
    int format = audioFormat == javaAudioTrackFields.PCM16 ?
            AudioSystem::PCM_16_BIT : AudioSystem::PCM_8_BIT;
    int frameCount;
    if (buffSizeInBytes == -1) {
        frameCount = (sampleRateInHertz*afFrameCount)/afSampleRate;
    } else {
        frameCount = buffSizeInBytes / (nbChannels * bytesPerSample);
    }
    
    // 用户分配共享内存,后续详细分析
    AudioTrackJniStorage* lpJniStorage = new AudioTrackJniStorage();
    ...
    // 引用 java AudioTrack 对象
    lpJniStorage->mCallbackData.audioTrack_class = (jclass)env->NewGlobalRef(clazz);
    // weak 引用 java AudioTrack 对象
    lpJniStorage->mCallbackData.audioTrack_ref = env->NewGlobalRef(weak_this);
    lpJniStorage->mStreamType = atStreamType;
    
    // 创建 native AudioTrack 对象
    AudioTrack* lpTrack = new AudioTrack();
    ...
    // 初始化 native AudioTrack 对象
    if (memoryMode == javaAudioTrackFields.MODE_STREAM) {
        lpTrack->set(
            atStreamType,// stream type
            sampleRateInHertz,
            format,// word length, PCM
            nbChannels,
            frameCount,
            0,// flags
            audioCallback, &(lpJniStorage->mCallbackData),//callback, callback data (user)
            0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack
            0,// shared mem
            true);// thread can call Java       
    } else if (memoryMode == javaAudioTrackFields.MODE_STATIC) {
        // MODE_STATIC 模式 AudioTrack 需要分配共享内存
        if (!lpJniStorage->allocSharedMem(buffSizeInBytes)) {
            LOGE("Error creating AudioTrack in static mode: error creating mem heap base");
            goto native_init_failure;
        }
        lpTrack->set(
            atStreamType,// stream type
            sampleRateInHertz,
            format,// word length, PCM
            nbChannels,
            frameCount,
            0,// flags
            audioCallback, &(lpJniStorage->mCallbackData),//callback, callback data (user));
            0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack
            lpJniStorage->mMemBase,// shared mem
            true);// thread can call Java
    }
    ...
    // 保存我们新创建的 C++ AudioTrack 到 java 对象的 "nativeTrackInJavaObj" 域(mNativeTrackInJavaObj)
    env->SetIntField(thiz, javaAudioTrackFields.nativeTrackInJavaObj, (int)lpTrack);
    // 保存 JNI 资源到 java 对象的 "jniData" 域,以便后续释放这些资源
    env->SetIntField(thiz, javaAudioTrackFields.jniData, (int)lpJniStorage);


    return AUDIOTRACK_SUCCESS;
	...    
}

注意 Android 中类由 java 和 C++ 两部分实现是一种常见的手段,他们之间通常是相互引用的关系。比如 java 部分 AudioTrack 对象通过 mNativeTrackInJavaObj 域引用 C++ 的 AudioTrack 对象。 C++ 的 AudioTrack 对象虽然没有直接引用 java 部分的 AudioTrack 类,但是 native 部分通过 lpJniStorage 的 mCallbackData 成员引用 java 的 AudioTrack 对象,而 C++ AudioTrack的 mUserData 成员引用了 mCallbackData,所以 C++ AudioTrack 对象也引用了 java 的 AudioTrack 对象。

由于创建 C++ AudioTrack时采用了无参构造函数,此构造函数并无有效代码,初始化通过 AudioTrack 的 set 函数完成。
[AudioTrack.cpp]

status_t AudioTrack::set(
        int streamType,
        uint32_t sampleRate,
        int format,
        int channelCount,
        int frameCount,
        uint32_t flags,
        callback_t cbf,
        void* user,
        int notificationFrames,
        const sp<IMemory>& sharedBuffer,
        bool threadCanCallJava)
{
    // 确保 AudioTrack 只被初始化一次
    if (mAudioTrack != 0) {
        LOGE("Track already in use");
        return INVALID_OPERATION;
    }

    // AudioSystem 可以看作 AudioFlinger 的客户端代理
    const sp<IAudioFlinger>& audioFlinger = AudioSystem::get_audio_flinger();
    if (audioFlinger == 0) {
       LOGE("Could not get audioflinger");
       return NO_INIT;
    }
    // 获取硬件采样率
    int afSampleRate;
    if (AudioSystem::getOutputSamplingRate(&afSampleRate, streamType) != NO_ERROR) {
        return NO_INIT;
    }
    // 获取硬件缓存区容纳的帧数量
    int afFrameCount;
    if (AudioSystem::getOutputFrameCount(&afFrameCount, streamType) != NO_ERROR) {
        return NO_INIT;
    }
    // 获取硬件延迟时间
    uint32_t afLatency;
    if (AudioSystem::getOutputLatency(&afLatency, streamType) != NO_ERROR) {
        return NO_INIT;
    }
    ...
    // 调用 AudioFlinger 创建 track
    status_t status;
    sp<IAudioTrack> track = audioFlinger->createTrack(getpid(),
                streamType, sampleRate, format, channelCount, frameCount, flags, sharedBuffer, &status);

    if (track == 0) {
        LOGE("AudioFlinger could not create track, status: %d", status);
        return status;
    }
    // 通过 track 获取共享内存
    sp<IMemory> cblk = track->getCblk();
    if (cblk == 0) {
        LOGE("Could not get control block");
        return NO_INIT;
    }
    ...
    mAudioTrack = track;
    mCblkMemory = cblk;
    mCblk = static_cast<audio_track_cblk_t*>(cblk->pointer());
    mCblk->out = 1;
    // Update buffer size in case it has been limited by AudioFlinger during track creation
    mFrameCount = mCblk->frameCount;
    if (sharedBuffer == 0) {
        mCblk->buffers = (char*)mCblk + sizeof(audio_track_cblk_t);
    } else {
        mCblk->buffers = sharedBuffer->pointer();
         // Force buffer full condition as data is already present in shared memory
        mCblk->stepUser(mFrameCount);
    }
    mCblk->volume[0] = mCblk->volume[1] = 0x1000;
    mVolume[LEFT] = 1.0f;
    mVolume[RIGHT] = 1.0f;
    mStreamType = streamType;
    mFormat = format;
    mChannelCount = channelCount;
    mSharedBuffer = sharedBuffer;
    mMuted = false;
    mActive = 0;
    mCbf = cbf;
    mNotificationFrames = notificationFrames;
    mRemainingFrames = notificationFrames;
    mUserData = user;
    mLatency = afLatency + (1000*mFrameCount) / sampleRate;
    mLoopCount = 0;
    mMarkerPosition = 0;
    mMarkerReached = false;
    mNewPosition = 0;
    mUpdatePeriod = 0;

    return NO_ERROR;
}

AudioTrack 的初始化,非常重要两个步骤:

  • 调用 AudioFlinger 调用 createTrack 函数,创建一个 IAudioTrack 对象,并保存到 mAudioTrack 变量中。本质上 mAudioTrack 是一个继承了 IAudioTrack 的代理,服务端在 AudioFlinger 内部实现。可以说 IAudioTrack 与 AudioTrack 是同生共死,后续音频播放控制都是通过 mAudioTrack 完成,也可以认为 AudioTrack 就是 IAudioTrack 的代理。
  • 通过 IAudioFlinger 的 getCblk 函数获取共享内存,并转换成 audio_track_cblk_t 对象保存到 mCblk 变量中。

由于上面代码中多次调用 AudioSystem,故对 AudioSystem 进行简要分析,AudioSystem 的所有成员皆为静态,因此它也没有构造函数,我们从 get_audio_flinger 函数开始分析:

const sp<IAudioFlinger>& AudioSystem::get_audio_flinger()
{
    Mutex::Autolock _l(gLock);
    // gAudioFlinger 作为 AudioSystem 的私有静态成员,保存了 AudioFlinger 服务的唯一引用。
    // 这种设计类似于单例模型
    if (gAudioFlinger.get() == 0) {
        sp<IServiceManager> sm = defaultServiceManager();
        sp<IBinder> binder;
        do {
            binder = sm->getService(String16("media.audio_flinger"));
            if (binder != 0)
                break;
            LOGW("AudioFlinger not published, waiting...");
            usleep(500000); // 0.5 s
        } while(true);
        ...
        // 缓存频发访问的参数
        for (int output = 0; output < NUM_AUDIO_OUTPUT_TYPES; output++) {
            gOutFrameCount[output] = (int)gAudioFlinger->frameCount(output);
            gOutSamplingRate[output] = (int)gAudioFlinger->sampleRate(output);
            gOutLatency[output] = gAudioFlinger->latency(output);
        }
        gA2dpEnabled = gAudioFlinger->isA2dpEnabled();
    }
    LOGE_IF(gAudioFlinger==0, "no AudioFlinger!?");
    return gAudioFlinger;
}

从以上代码可以看到,第一次调用 get_audio_flinger 是会获取 AudioFlinger 服务并保存到 gAudioFlinger 中,后续调用会直接返回 gAudioFlinger 指向的 AudioFlinger 的代理。并且在获取 AudioFlinger 服务的同时,会通过 AudioFlinger 服务获取所有音频输出设备的常用参数,保存到成员变量中,起到缓存作用。这些参数包括:硬件缓冲区可以容纳帧数、硬件支持采样率、硬件延迟。

AudioSystem 其他成员函数,都是通过 AudioFlinger 服务获取或设置一些参数的功能函数,以获取硬件采样率为例:

status_t AudioSystem::getOutputSamplingRate(int* samplingRate, int streamType)
{
    int output = getOutput(streamType);
    
    if (output == NUM_AUDIO_OUTPUT_TYPES) return PERMISSION_DENIED;

    // gOutSamplingRate[] is updated by getOutput() which calls get_audio_flinger()
    LOGV("getOutputSamplingRate() streamType %d, output %d, sampling rate %d", streamType, output, gOutSamplingRate[output]);
    
    *samplingRate = gOutSamplingRate[output];
    
    return NO_ERROR;
}

由于采样率在获取 AudioFlinger 服务时,已将缓存到 gOutSamplingRate 数组变量中,因此只需要根据输出设备类型,取出相应的值即可,这样提高了程序运行的性能。

二、获取合适的 buffer 空间

getMinBufferSize 使用我们播放音频时调用的第一个函数:
[AudioTrack.java]

static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) {
    ...
    int size = native_get_min_buff_size(sampleRateInHz, channelCount, audioFormat);
    ...
}

[android_media_AudioTrack.cpp]

static jint android_media_AudioTrack_get_min_buff_size(JNIEnv *env,  jobject thiz,
    jint sampleRateInHertz, jint nbChannels, jint audioFormat) {
    int afSamplingRate;
    int afFrameCount;
    uint32_t afLatency;
 
    // 从硬件获取到的采样速率,模拟器中为:44100
    if (AudioSystem::getOutputSamplingRate(&afSamplingRate) != NO_ERROR) {
        return -1;
    }
    // 硬件 buffer 空间可以容纳的帧数,计算方法为:mFrameCount = output->bufferSize() / output->channelCount() / sizeof(int16_t);
    // bufferSize 为缓冲区大小:4096;channelCount 为通道数:2;一个采样数据占 2 个字节。则 mFrameCount 等于 1024。
    if (AudioSystem::getOutputFrameCount(&afFrameCount) != NO_ERROR) {
        return -1;
    }
    // 硬件延迟时间,单位为ms,模拟器中为:20ms
    if (AudioSystem::getOutputLatency(&afLatency) != NO_ERROR) {
        return -1;
    }
    // minBufCount:在延迟时间内,可以填充硬件缓冲区的次数,至少为 2 次。
    uint32_t minBufCount = afLatency / ((1000 * afFrameCount)/afSamplingRate);
    if (minBufCount < 2) minBufCount = 2;
    // minFrameCount:使用上层采样率(sampleRateInHertz),填充 minBufCount 次的缓冲需要的帧数
    uint32_t minFrameCount = (afFrameCount*sampleRateInHertz*minBufCount)/afSamplingRate;
    // minBuffSize:minFrameCount 帧需占用的字节数
    int minBuffSize = minFrameCount
            * (audioFormat == javaAudioTrackFields.PCM16 ? 2 : 1)
            * nbChannels;
    return minBuffSize;
}

注意:音频帧表示全部声道一次采样的数据量。比如 PCM 16,双声道 1 帧等于 2x2=4 字节。
经过以上分析,getMinBufferSize 值为硬件延迟时间内上层需要消耗缓存区大小(minBufCount < 2 时,则为硬件消耗两个缓冲区数据,上层需要消耗缓存区大小)。注意上层使用的缓冲区大小和底层的缓存区大小是不一致的,这是由于 AudioFlinger 会对上层音频数据进行重采样。

三、启动音乐播放

[AudioTrack.java]

public void play() throws IllegalStateException {
    ...
    synchronized(mPlayStateLock) {
        native_start();
        mPlayState = PLAYSTATE_PLAYING;
    }
}

[android_media_AudioTrack.cpp]

static void
android_media_AudioTrack_start(JNIEnv *env, jobject thiz)
{
    AudioTrack *lpTrack = (AudioTrack *)env->GetIntField(
        thiz, javaAudioTrackFields.nativeTrackInJavaObj);
    if (lpTrack == NULL ) {
        jniThrowException(env, "java/lang/IllegalStateException",
            "Unable to retrieve AudioTrack pointer for start()");
    }

    lpTrack->start();
}

[AudioTrack.cpp]

void AudioTrack::start()
{
        ...
        mAudioTrack->start();
        ...
}

四、写音频数据

[AudioTrack.java]

public int write(byte[] audioData,int offsetInBytes, int sizeInBytes) {
    ...
    return native_write_byte(audioData, offsetInBytes, sizeInBytes, mAudioFormat);
}

[android_media_AudioTrack.cpp]

static jint android_media_AudioTrack_native_write(JNIEnv *env,  jobject thiz,
                                                  jbyteArray javaAudioData,
                                                  jint offsetInBytes, jint sizeInBytes,
                                                  jint javaAudioFormat) {
    ...
    jint written = writeToTrack(lpTrack, javaAudioFormat, cAudioData, offsetInBytes, sizeInBytes);
    ...
    return written;
}

实际上会调用 writeToTrack 实现:

jint writeToTrack(AudioTrack* pTrack, jint audioFormat, jbyte* data,
                  jint offsetInBytes, jint sizeInBytes) {
    // give the data to the native AudioTrack object (the data starts at the offset)
    ssize_t written = 0;
    // MODE_STREAM 模式 shared mem 为空,调用 write 函数
    // MODE_STATIC 模式 shared mem 不为空,则将数据拷贝到 shared mem 中
    if (pTrack->sharedBuffer() == 0) {
        written = pTrack->write(data + offsetInBytes, sizeInBytes);
    } else {
        ...
    }
    return written;
}

这里仅分析 MODE_STREAM 模式,那么会调用 C++ AudioTrack 的 write 函数传输音频数据:

ssize_t AudioTrack::write(const void* buffer, size_t userSize)
{
    ...
    ssize_t written = 0;
    const int8_t *src = (const int8_t *)buffer;
    Buffer audioBuffer;
    do {
        audioBuffer.frameCount = userSize/mChannelCount;
        if (mFormat == AudioSystem::PCM_16_BIT) {
            audioBuffer.frameCount >>= 1;
        }
        // 调用 obtainBuffer() 函数从共享内区获取内存,参数 -1 表示无线等待
        status_t err = obtainBuffer(&audioBuffer, -1);
        if (err < 0) {
            // out of buffers, return #bytes written
            if (err == status_t(NO_MORE_BUFFERS))
                break;
            return ssize_t(err);
        }
        size_t toWrite;
        // PCM_8_BIT 精度的数据将转换成 PCM_16_BIT 写入共享内存
        // PCM_16_BIT 精度数据则会直接拷贝到共享内存
        if (mFormat == AudioSystem::PCM_8_BIT) {
            // Divide capacity by 2 to take expansion into account
            toWrite = audioBuffer.size>>1;
            // 8 to 16 bit conversion
            int count = toWrite;
            int16_t *dst = (int16_t *)(audioBuffer.i8);
            while(count--) {
                *dst++ = (int16_t)(*src++^0x80) << 8;
            }
        }else {
            toWrite = audioBuffer.size;
            memcpy(audioBuffer.i8, src, toWrite);
            src += toWrite;
        }
        userSize -= toWrite;
        written += toWrite;
        // 释放共享内存
        releaseBuffer(&audioBuffer);
    } while (userSize);

    return written;
}

write 函数主要做了两件事:

  • 调用 obtainBuffer 从共享内存空间获取内存。
  • 将用音频数据拷贝到分配的共享内存中。

五、停止播放

[AudioTrack.java]

public void stop() throws IllegalStateException {
    ...
    synchronized(mPlayStateLock) {
        native_stop();
        mPlayState = PLAYSTATE_STOPPED;
    }
}

[android_media_AudioTrack.cpp]

static void android_media_AudioTrack_start(JNIEnv *env, jobject thiz)
{
    AudioTrack *lpTrack = (AudioTrack *)env->GetIntField(
        thiz, javaAudioTrackFields.nativeTrackInJavaObj);
    if (lpTrack == NULL ) {
        jniThrowException(env, "java/lang/IllegalStateException",
            "Unable to retrieve AudioTrack pointer for start()");
    }

    lpTrack->start();
}

[AudioTrack.cpp]

void AudioTrack::stop()
{
        ...
        mCblk->cv.signal();
        mAudioTrack->stop();
        ...
}
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要实现web android语音,您可以按照以下步骤进行操作: 1. 在Android端,您可以使用AudioRecord类来录制音频数据,并使用WebSocket将其实时传输到服务器。您需要设置采样率、声道数等参数,并将录制的音频数据转换为字节数组。然后,您可以使用OkHttp或Java-WebSocket等库来实现WebSocket连接,并将字节数组发送到服务器。 2. 在服务器端,您需要解码接收到的字节数组,并将其转换为音频数据。您可以使用Java Sound API或其他音频处理库来实现。然后,您可以使用WebSocket将处理后的音频数据实时发送回客户端。 3. 在web端,您可以使用WebRTC或其他音视频通信库来实现语音通话。您需要创建一个音频流,并将其连接到服务器端的WebSocket,以接收实时音频数据。然后,您可以使用AudioContext和MediaStream等API来播放接收到的音频流。 4. 在Android端和web端,您可以使用AudioTrack类来播放音频数据。Android端的AudioTrack可以直接播放从服务器接收到的音频数据,而web端的AudioTrack可以播放通过WebRTC接收到的音频数据。 5. 对于后端,您可以使用Spring Boot框架来搭建WebSocket服务器,并使用Spring WebSocket API来实现WebSocket连接和消息处理。您需要定义一个WebSocketHandler类来处理接收到的音频数据,并将其发送到连接的客户端。 需要注意的是,实现语音通话需要考虑网络延迟和带宽等问题。您可以设置合适的传输速率和压缩算法来优化传输效率,同时使用QoS技术来保证语音通话的质量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

翻滚吧香香

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值