はじめに
Android で AudioTrack を使って音楽を再生するアプリを開発しているのですが、ユーザーからレビューで「ワイヤレスイヤフォンだと音楽が鳴らない」というご指摘をいただき調査した結果、割と謎な手段で問題を回避したので紹介します。
AudioTrack 使用の流れ
private fun createAudioTrack(
seek: Int,
onSeek: ((length: Int, time: Int) -> Unit)?,
onPlayEnded: (() -> Unit)?
) {
audioTrack?.release()
val attributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build()
val format = AudioFormat.Builder()
.setSampleRate(22050)
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.build()
audioTrack = AudioTrack.Builder()
.setAudioAttributes(attributes)
.setAudioFormat(format)
.setBufferSizeInBytes(basicBufferSize * 2)
.setTransferMode(AudioTrack.MODE_STREAM)
.build()
audioTrack?.positionNotificationPeriod = basicBufferSize / 2
audioTrack?.setVolume(masterVolume / 100f)
audioTrack?.setPlaybackPositionUpdateListener(object :
AudioTrack.OnPlaybackPositionUpdateListener {
override fun onMarkerReached(audioTrack: AudioTrack?) {
}
override fun onPeriodicNotification(p0: AudioTrack?) {
// 音楽をループしたりフェードアウトしたりシーク位置を検出したり再enqueueしたり (長いので省略)
}
})
JNI.seek(vgsContext, seek * 22050)
JNI.kobushi(vgsContext, kobushi)
decode(decodeAudioBufferFirst)
audioTrack?.write(decodeAudioBufferFirst, 0, decodeAudioBufferFirst.size)
audioTrack?.play()
decodeAudioBufferLatch = 0
decode(decodeAudioBuffers[decodeAudioBufferLatch])
(ザックリとした処理の流れ)
- デコードした音声データ(22050Hz, mono, 16bit PCM形式)を write
- play
- onPeriodicNotification で次データの enqueue や デコード
原因(?)
最初に AudioTrack のバッファサイズ(setBufferSizeInBytes)と同じデータを write していますが、何故か Bluetooth イヤフォン を接続した状態だと バッファサイズの2倍のデータ を書き込まないと再生が始まらない謎の状態でした。
バッファサイズは 16KB にしているので、先頭に 32KB (約0.74秒) の無音が入ることになりますが、これぐらいなら使い勝手上の問題はないからヨシ...ということで。
対策
最初に バッファサイズの2倍の空データを write する ように修正しました。
バッファサイズの2倍のデータを write しなければ動かないのは多分OSのバグかもしれないので、その内治るかもしれません。
再現環境
- device: Pixel 3
- OS: Android 12 (sequrity update October 5, 2021)
- Bluetooth earphone: Power HBQ Pro