ndroid 音频系统:AudioTrack 源码深度解析(从 Java 构造到 Native 启动)

framework/base/media/java/android/media/AudioTrack.java

  • @IntDef({ ENCAPSULATION_MODE_NONE, ENCAPSULATION_MODE_ELEMENTARY_STREAM, // ENCAPSULATION_MODE_HANDLE, @SystemApi }) @Retention(RetentionPolicy.SOURCE) public @interface EncapsulationMode {}
    • 定义一个新注解
  • 这是一个java的封装层
    • 他几乎不自己干活,干活的逻辑再native层中
    • 这一层就是将App的调用整理成对native的转发
java 复制代码
private AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
            int mode, int sessionId, boolean offload, int encapsulationMode,
            @Nullable TunerConfiguration tunerConfiguration)
                    throws IllegalArgumentException {
        // 进父类
        // AudioTrack的父类并不是Object 而是PlayBase(音频播放基类)
        // 作用:
        //      用PLAYER_TYPE_JAM_AUDIOTRACK向AudioService注册这个player
        //      从此这个AudioTrack进入音频焦点/音量/播放状态的统一管理链路 AudioPlaybackConfiguration能查到你的App正在播放音频靠的就是它
        super(attributes, AudioPlaybackConfiguration.PLAYER_TYPE_JAM_AUDIOTRACK);
        // mState already == STATE_UNINITIALIZED

        mConfiguredAudioAttributes = attributes; // object copy not needed, immutable.

        // 参数校验 异常直接throw抛出 调用者的代码问题
        if (format == null) {
            throw new IllegalArgumentException("Illegal null AudioFormat");
        }

        // Deep Buffer省电模式
        // 如果满足省电条件(一般为MODE_STREAM + 音乐类usage + 缓冲够大),给mAttributes加上FLAG_DEEP_BUFFER、去掉FLAG_LOW_LATENCY
        // 深缓冲 Deep Buffer:更大的延迟但是芯片能进低功耗;
        // 低延迟适合游戏、通话但是费电,这是Android的权衡
        // Check if we should enable deep buffer mode
        if (shouldEnablePowerSaving(mAttributes, format, bufferSizeInBytes, mode)) {
            mAttributes = new AudioAttributes.Builder(mAttributes)
                .replaceFlags((mAttributes.getAllFlags()
                        | AudioAttributes.FLAG_DEEP_BUFFER)
                        & ~AudioAttributes.FLAG_LOW_LATENCY)
                .build();
        }

        // 后面的native回调(播放位置、marker事件)要靠Handler + Looper转发给Java,所以这里先记住"创建这个AudioTrack的线程的Looper"
        // remember which looper is associated with the AudioTrack instantiation
        Looper looper;
        if ((looper = Looper.myLooper()) == null) {
            looper = Looper.getMainLooper();
        }
        // 从AudioFormat提取参数 关键:propertySetMask
        // getPropertySetMask() 位掩码。AudioFormat允许只设置一部分属性,所以用掩码来判断,哪些是被显示设置了。
        // 没设升到就用默认立体声。最后audioParamCheck(...)做合法性检查 非法就throw
        int rate = format.getSampleRate();
        // SAMPLE_RATE_UNSPECIFIED 一个占位符常量
        // 如果创建一个AudioFormat,但没有明确指定采样率多少,系统就会给他贴上这个标签
        if (rate == AudioFormat.SAMPLE_RATE_UNSPECIFIED) {
            rate = 0;
        }

        int channelIndexMask = 0;
        if ((format.getPropertySetMask()
                & AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_INDEX_MASK) != 0) {
            channelIndexMask = format.getChannelIndexMask();
        }
        int channelMask = 0;
        if ((format.getPropertySetMask()
                & AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK) != 0) {
            channelMask = format.getChannelMask();
        } else if (channelIndexMask == 0) { // if no masks at all, use stereo
            channelMask = AudioFormat.CHANNEL_OUT_FRONT_LEFT
                    | AudioFormat.CHANNEL_OUT_FRONT_RIGHT;
        }
        int encoding = AudioFormat.ENCODING_DEFAULT;
        if ((format.getPropertySetMask() & AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_ENCODING) != 0) {
            encoding = format.getEncoding();
        }
        audioParamCheck(rate, channelMask, channelIndexMask, encoding, mode);
        mOffloaded = offload;
        mStreamType = AudioSystem.STREAM_DEFAULT;

        audioBuffSizeCheck(bufferSizeInBytes);

        mInitializationLooper = looper;

        if (sessionId < 0) {
            throw new IllegalArgumentException("Invalid audio session ID: "+sessionId);
        }
        // WeakReference<AudioTrack> 传自己:native层想回调java(位置事件等)时只持有弱引用,不会造成java对象泄露。这是Android JNI回调的经典写法
        // int[] 当出参:JNI没有引用传递,所以用数据包一层,native往里面写,Java再读回来
        //      sampleRate[0] 在传 0(未指定)时会被 native 改成真实输出采样率;session[0] 传 0 表示"让 native 分配",回填真实 session id(AudioSystem.AUDIO_SESSION_ID_ALLOCATE == 0)
        // 失败不 throw,静默 return:native 初始化失败(如驱动问题)只打日志,靠 mState == STATE_UNINITIALIZED 让上层察觉------这是"运行时失败 vs 参数错误"两种处理方式的对照。
        int[] sampleRate = new int[] {mSampleRate};
        int[] session = new int[1];
        session[0] = sessionId;
        // native initialization
        int initResult = native_setup(new WeakReference<AudioTrack>(this), mAttributes,
                sampleRate, mChannelMask, mChannelIndexMask, mAudioFormat,
                mNativeBufferSizeInBytes, mDataLoadMode, session, 0 /*nativeTrackInJavaObj*/,
                offload, encapsulationMode, tunerConfiguration,
                getCurrentOpPackageName());
        if (initResult != SUCCESS) {
            loge("Error code "+initResult+" when initializing AudioTrack.");
            return; // with mState == STATE_UNINITIALIZED
        }

        mSampleRate = sampleRate[0]; // ← native 回填
        mSessionId = session[0]; // ← native 回填

        // TODO: consider caching encapsulationMode and tunerConfiguration in the Java object.
        // 给A/V同步/隧道式播放(音视频走HDMI需同步)用的,涉及压缩流帧头对齐  
        if ((mAttributes.getFlags() & AudioAttributes.FLAG_HW_AV_SYNC) != 0) {
            int frameSizeInBytes;
            if (AudioFormat.isEncodingLinearFrames(mAudioFormat)) {
                frameSizeInBytes = mChannelCount * AudioFormat.getBytesPerSample(mAudioFormat);
            } else {
                frameSizeInBytes = 1;
            }
            mOffset = ((int) Math.ceil(HEADER_V2_SIZE_BYTES / frameSizeInBytes)) * frameSizeInBytes;
        }

        if (mDataLoadMode == MODE_STATIC) {
            mState = STATE_NO_STATIC_DATA; // 静态模式:还没数据,需 loadStaticData()
        } else {
            mState = STATE_INITIALIZED; // 流式模式:直接可用
        }
        // 完成 PlayerBase 注册闭环,把 native 对象和 AudioService 里的 player 记录绑定。
        baseRegisterPlayer(mSessionId);
        native_setPlayerIId(mPlayerIId); // mPlayerIId now ready to send to native AudioTrack.
    }
java 复制代码
/**
     * State of an AudioTrack that was not successfully initialized upon creation.
     */
    public static final int STATE_UNINITIALIZED = 0;
    /**
     * State of an AudioTrack that is ready to be used.
     */
    public static final int STATE_INITIALIZED   = 1;
    /**
     * State of a successfully initialized AudioTrack that uses static data,
     * but that hasn't received that data yet.
     */
    public static final int STATE_NO_STATIC_DATA = 2;
  • 两个状态维度
  • mState 构造状态:是否初始化成功
  • mPlayState 播放状态:PLAYSTATE_STOPPED/PAUSED/PLAYING 由 play/pause/stop 驱动
  • 几乎所有方法开头都有一句 if (mState != STATE_INITIALIZED) throw new IllegalStateException(...)
java 复制代码
//---------------------------------------------------------
    // Transport control methods
    //--------------------
    /**
     * Starts playing an AudioTrack.
     * <p>
     * If track's creation mode is {@link #MODE_STATIC}, you must have called one of
     * the write methods ({@link #write(byte[], int, int)}, {@link #write(byte[], int, int, int)},
     * {@link #write(short[], int, int)}, {@link #write(short[], int, int, int)},
     * {@link #write(float[], int, int, int)}, or {@link #write(ByteBuffer, int, int)}) prior to
     * play().
     * <p>
     * If the mode is {@link #MODE_STREAM}, you can optionally prime the data path prior to
     * calling play(), by writing up to <code>bufferSizeInBytes</code> (from constructor).
     * If you don't call write() first, or if you call write() but with an insufficient amount of
     * data, then the track will be in underrun state at play().  In this case,
     * playback will not actually start playing until the data path is filled to a
     * device-specific minimum level.  This requirement for the path to be filled
     * to a minimum level is also true when resuming audio playback after calling stop().
     * Similarly the buffer will need to be filled up again after
     * the track underruns due to failure to call write() in a timely manner with sufficient data.
     * For portability, an application should prime the data path to the maximum allowed
     * by writing data until the write() method returns a short transfer count.
     * This allows play() to start immediately, and reduces the chance of underrun.
     *
     * @throws IllegalStateException if the track isn't properly initialized
     */

    // play()
    //├─ 校验 mState == STATE_INITIALIZED,否则抛异常
    //├─ 判断 start delay:getStartDelayMs() != 0 ? 开子线程延迟后 startImpl
    //│                                       : 直接 startImpl
    //└─ startImpl()
        //├─ synchronized(mRoutingChangeListeners)
        //│    └─ 需要时启用 native 路由回调
        //└─ synchronized(mPlayStateLock)
            //├─ baseStart(0)   → PlayerBase 向 AudioService 报告"开始播了"
            //├─ native_start() → JNI → libaudioclient → Binder → AudioFlinger
            //└─ 状态机:PAUSED_STOPPING → STOPPING;否则 → PLAYING + 清 mOffloadEosPending
    public void play()
    throws IllegalStateException {
        if (mState != STATE_INITIALIZED) {
            throw new IllegalStateException("play() called on uninitialized AudioTrack.");
        }
        //FIXME use lambda to pass startImpl to superclass
        // start delay(延迟启动) ------ 音频焦点/音量渐变的协调
        // getStartDelayMs() 来自父类的PlayerBase的mStartDelatMs,由AudioService在音频焦点恢复/音量渐升场景下使用setStartDelayMs()(@hide)设置
        // 音量从0渐变到目标值需要时间,如果不等它ramp完就start,会"啪"的一下大声。所以先睡delay毫秒,音量渐到不表后再真正启动
        // 细节:
        //      baseSetStartDelayMs(0)把延迟清零,避免下次重复
        //      子线程里catch(IllegalStateException)但静默吞掉 ------ sleep期间用户可能已经stop/改状态了,延迟的start再报错没意义
        final int delay = getStartDelayMs();
        if (delay == 0) {
            startImpl();
        } else {
            new Thread() {
                public void run() {
                    try {
                        Thread.sleep(delay);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    baseSetStartDelayMs(0);
                    try {
                        startImpl();
                    } catch (IllegalStateException e) {
                        // fail silently for a state exception when it is happening after
                        // a delayed start, as the player state could have changed between the
                        // call to start() and the execution of startImpl()
                    }
                }
            }.start();
        }
    }
    // testEnableNativeRoutingCallbacksLocked ------ native路由回调
    // 如果Java侧挂了OnRoutingChangedListener(监听路由变化:耳机 <-> 外放),就启用native侧的路由回调上报,让native把实际路由变化推回Java。mEnableSelfRoutingMonitor 保证只启用一次
    // 在 synchronized(mRoutingChangeListeners) 里,保护监听器列表
    private void startImpl() {
        synchronized (mRoutingChangeListeners) {
            if (!mEnableSelfRoutingMonitor) {
                mEnableSelfRoutingMonitor = testEnableNativeRoutingCallbacksLocked();
            }
        }
        synchronized(mPlayStateLock) {
            // baseStart(0) 向系统"打卡"
            // PlayerBase.baseStart(deviceId)通过Binder通知AudioService(更新AudioPlaybackConfiguration),让他知道 这个player开始播放了 这是音频焦点、音量、以及"系统能看到哪个App在播"的基础
            // 参数0:注释写着 // unknown device at this point------此时路由设备还没定下来,先传 0。旁边 
            // FIXME see b/179218630 说明理想情况应传 native_getRoutedDeviceId(),但有 bug 所以先注释掉。
            baseStart(0); // unknown device at this point
            // native_start() ------ 通往 native 的分界线
            // Java 层到此为止。之后:JNI(AudioTrack_native_start)→ libaudioclient/AudioTrack::start() → Binder(IAudioFlinger)→ AudioFlinger 里真正的 track 启动。
            native_start();
            // FIXME see b/179218630
            //baseStart(native_getRoutedDeviceId());
            // PLAYSTATE_PAUSED_STOPPING → PLAYSTATE_STOPPING:offload(硬件卸载)播放时,pause() 后硬件还会把缓冲放完;这期间如果又调了 stop(),就进入 PAUSED_STOPPING(暂停中待停止),等这次 startImpl 再把它推进到 STOPPING。这是硬件播放特有的暂停/停止语义------普通模式没有这个状态。
            //否则 → PLAYSTATE_PLAYING,并清 mOffloadEosPending:offload 模式下 EOS(流尾)由硬件异步上报,这个标志记录"是否已到结尾",重新播放前必须清掉。
            if (mPlayState == PLAYSTATE_PAUSED_STOPPING) {
                mPlayState = PLAYSTATE_STOPPING;
            } else {
                mPlayState = PLAYSTATE_PLAYING;
                mOffloadEosPending = false;
            }
        }
    }
相关推荐
KoPa13 分钟前
HeySmart:事件总线——异步解耦的艺术
前端·后端
用户9210802628614 分钟前
AI SSE Client 和普通 SSE Client 有什么不同:一次生成任务背后的坑与设计边界
前端
烬羽18 分钟前
为 Agent 管好一张“上下文预算”:从数条数到数 token
架构·langchain·agent
xierui12312318 分钟前
Anthropic安全复盘:会操作电脑的 Agent 如何分阶段上岗
人工智能·网络安全·架构·系统架构
飞鸟真人18 分钟前
亿级用户IM系统 之 接入网关负载均衡架构(一、网络结构与DPVS部署)
架构·负载均衡·亿级im系统
PedroQue9922 分钟前
@meng-xi/create-uni-app v1.0.0 正式发布
前端·uni-app
烬羽22 分钟前
把 Agent 的记忆写进文件:内存 vs 文件,两把钥匙搞定多会话
架构·langchain·agent
judezh24 分钟前
一次 agent 请求要经过 12 个服务,它们分别在替你做什么
微服务·架构