@IntDef({ ENCAPSULATION_MODE_NONE, ENCAPSULATION_MODE_ELEMENTARY_STREAM, // ENCAPSULATION_MODE_HANDLE, @SystemApi }) @Retention(RetentionPolicy.SOURCE) public @interface EncapsulationMode {}
- 这是一个java的封装层
- 他几乎不自己干活,干活的逻辑再native层中
- 这一层就是将App的调用整理成对native的转发
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.
}
/**
* 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(...)
//---------------------------------------------------------
// 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;
}
}
}