Route guidance audio through a selectable navigation stream type
CarPlay marks guidance audio apart from media, but the sink always built its AudioTrack from usage/content-type attributes. On head units that expose navigation on a dedicated mixer output, guidance shares the media channels, so the next turn is announced over the music in every seat. BYD puts navigation on stream type 14, the driver-side speaker. Add an "Audio routing" section with a 0-20 stream type grid and a test tone, persist the selection, and build the AudioTrack on that stream type. When the ROM rejects the stream type the track is rebuilt from usage attributes, so devices without such an output keep today's behavior (the diagnostic log reports route=usage or route=streamType=N(fallback=usage)). The advanced audio channel mapping switch is now visible on the mobile flavor too, still off by default, so usage/content-type routing can be picked at runtime instead of by editing resources, and the automotive map routes default and compatibility to the navigation bus rather than sharing media.
This commit is contained in:
1 parent
e6135d36e6
commit
e85285e03d
8 files changed
+262
-32
No files matched your search
@@ -39,6 +39,7 @@ object AirPlayPersistence {
|
||||
private const val KEY_HEVC_ENABLED = "hevc_enabled"
|
||||
private const val KEY_HEVC_SOFTWARE_DECODER = "hevc_software_decoder"
|
||||
private const val KEY_ADVANCED_AUDIO_CHANNEL_MAPPING = "advanced_audio_channel_mapping"
|
||||
private const val KEY_NAVIGATION_STREAM_TYPE = "navigation_stream_type"
|
||||
private const val KEY_WIRELESS_ENABLED = "wireless_enabled"
|
||||
private const val KEY_WIRELESS_HOTSPOT_MODE = "wireless_hotspot_mode"
|
||||
private const val KEY_MANUAL_HOTSPOT_SSID = "manual_hotspot_ssid"
|
||||
@@ -131,6 +132,16 @@ object AirPlayPersistence {
|
||||
.apply()
|
||||
}
|
||||
|
||||
fun loadNavigationStreamType(context: Context): Int =
|
||||
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||
.getInt(KEY_NAVIGATION_STREAM_TYPE, 14)
|
||||
|
||||
fun saveNavigationStreamType(context: Context, streamType: Int) {
|
||||
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit()
|
||||
.putInt(KEY_NAVIGATION_STREAM_TYPE, streamType)
|
||||
.apply()
|
||||
}
|
||||
|
||||
fun loadWirelessEnabled(context: Context): Boolean =
|
||||
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||
.getBoolean(KEY_WIRELESS_ENABLED, true)
|
||||
|
||||
@@ -275,6 +275,7 @@ class CarPlayHostActivity : ComponentActivity() {
|
||||
private var hevcSoftwareDecoderEnabled = false
|
||||
private var advancedAudioChannelMappingSupported = false
|
||||
private var advancedAudioChannelMapping = false
|
||||
private var navigationStreamType = 14
|
||||
private var debugLogsEnabled = false
|
||||
private var autoStartOnBoot = false
|
||||
private var manufacturer = AirPlayPersistence.DEFAULT_MANUFACTURER
|
||||
@@ -437,6 +438,7 @@ class CarPlayHostActivity : ComponentActivity() {
|
||||
advancedAudioChannelMapping =
|
||||
advancedAudioChannelMappingSupported &&
|
||||
AirPlayPersistence.loadAdvancedAudioChannelMapping(this)
|
||||
navigationStreamType = AirPlayPersistence.loadNavigationStreamType(this)
|
||||
debugLogsEnabled = AirPlayPersistence.loadDebugLogsEnabled(this)
|
||||
autoStartOnBoot = AirPlayPersistence.loadAutoStartOnBoot(this)
|
||||
manufacturer = AirPlayPersistence.loadManufacturer(this)
|
||||
@@ -561,6 +563,9 @@ class CarPlayHostActivity : ComponentActivity() {
|
||||
requestLocationPermission()
|
||||
}
|
||||
wirelessPermissionsReady = !wirelessEnabled || hasRequiredWirelessPermissions()
|
||||
advancedAudioChannelMapping =
|
||||
advancedAudioChannelMappingSupported &&
|
||||
AirPlayPersistence.loadAdvancedAudioChannelMapping(this)
|
||||
if (DiLink51ClusterLayout.automatic(this) && clusterMonitor == null) {
|
||||
clusterMonitor = DiLink51ClusterMonitor(this, ::onClusterActivityState).also { it.start() }
|
||||
} else if (!DiLink51ClusterLayout.automatic(this)) {
|
||||
@@ -960,7 +965,7 @@ class CarPlayHostActivity : ComponentActivity() {
|
||||
settingsSwitchRow(
|
||||
label = "Advanced audio channel mapping",
|
||||
checked = advancedAudioChannelMapping,
|
||||
description = "Route AAOS audio buses by CarPlay audio type",
|
||||
description = "Use usage / content-type routing instead of stream type",
|
||||
) { checked ->
|
||||
advancedAudioChannelMapping = checked
|
||||
appendLog(
|
||||
@@ -2913,6 +2918,7 @@ class CarPlayHostActivity : ComponentActivity() {
|
||||
videoHeight = videoHeight,
|
||||
preferSoftwareHevcDecoder = hevcSoftwareDecoderEnabled,
|
||||
advancedAudioChannelMapping = advancedAudioChannelMapping,
|
||||
navigationStreamType = navigationStreamType,
|
||||
onScreenStreamActiveChanged = { type, active ->
|
||||
onScreenStreamStateChanged(controllerGeneration, type, active)
|
||||
},
|
||||
|
||||
@@ -12,12 +12,15 @@ import android.content.res.Configuration
|
||||
import android.graphics.Color
|
||||
import android.graphics.Typeface
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.media.AudioFormat
|
||||
import android.media.AudioTrack
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@@ -50,6 +53,9 @@ class DiPlayActivity : ComponentActivity() {
|
||||
private var initialLaunch = true
|
||||
private var notificationTransport = true
|
||||
private var exportInProgress = false
|
||||
private var navigationStreamType = 14
|
||||
private var testToneTrack: AudioTrack? = null
|
||||
private var toneStop: Runnable? = null
|
||||
private var exportButton: Button? = null
|
||||
private val notificationPermission = registerForActivityResult(ActivityResultContracts.RequestPermission()) {
|
||||
connect(notificationTransport)
|
||||
@@ -243,6 +249,42 @@ class DiPlayActivity : ComponentActivity() {
|
||||
AirPlayPersistence.saveHideTopBar(this, it); AirPlayPersistence.saveHideBottomBar(this, it)
|
||||
}
|
||||
}
|
||||
section(content, "Audio routing") { card ->
|
||||
val channelTitle = label("Navigation stream type", 18, TEXT, true)
|
||||
val channelHint = label("Tap a number to test. 14 = driver speaker on BYD.", 14, MUTED)
|
||||
val grid = channelSelector()
|
||||
val saveButton = button("Save", true) {
|
||||
AirPlayPersistence.saveNavigationStreamType(this, navigationStreamType)
|
||||
toast("Saved: $navigationStreamType")
|
||||
}
|
||||
fun applyChannelEnabled(enabled: Boolean) {
|
||||
val alpha = if (enabled) 1f else 0.4f
|
||||
listOf(channelTitle, channelHint, saveButton).forEach {
|
||||
it.isEnabled = enabled
|
||||
it.alpha = alpha
|
||||
}
|
||||
for (i in 0 until grid.childCount) {
|
||||
grid.getChildAt(i).let { child ->
|
||||
child.isEnabled = enabled
|
||||
child.alpha = alpha
|
||||
}
|
||||
}
|
||||
}
|
||||
val aaosSupported = resources.getBoolean(R.bool.config_advanced_audio_channel_mapping)
|
||||
if (aaosSupported) {
|
||||
toggle(card, "Advanced audio channel mapping",
|
||||
"Use usage / content-type routing instead of stream type",
|
||||
AirPlayPersistence.loadAdvancedAudioChannelMapping(this)) {
|
||||
AirPlayPersistence.saveAdvancedAudioChannelMapping(this, it)
|
||||
applyChannelEnabled(!it)
|
||||
}
|
||||
}
|
||||
card.addView(channelTitle)
|
||||
card.addView(channelHint)
|
||||
card.addView(grid)
|
||||
card.addView(saveButton, matchButton(12, 56))
|
||||
if (aaosSupported) applyChannelEnabled(!AirPlayPersistence.loadAdvancedAudioChannelMapping(this))
|
||||
}
|
||||
if (com.shilapi.xcertplay.hud.BydOutputSettings.available(this)) section(content, "BYD navigation", R.drawable.ic_dp_navigation) { card ->
|
||||
toggle(card, "Navigation on HUD and instrument cluster",
|
||||
"Show phone navigation arrows, distance and street names on supported BYD displays. Vehicle compatibility varies.",
|
||||
@@ -799,6 +841,90 @@ class DiPlayActivity : ComponentActivity() {
|
||||
}
|
||||
private fun openSystem(intent: Intent) { runCatching { startActivity(intent) }.onFailure { toast("Open this setting from your car’s Settings app.") } }
|
||||
private fun toast(message: String) { Toast.makeText(this, message, Toast.LENGTH_LONG).show() }
|
||||
|
||||
private fun playTestTone(streamType: Int) {
|
||||
toneStop?.let { handler.removeCallbacks(it) }
|
||||
toneStop = null
|
||||
testToneTrack?.let { runCatching { it.stop(); it.release() } }
|
||||
testToneTrack = null
|
||||
val pcm = assets.open("navigation_test.pcm").use { it.readBytes() }
|
||||
val track = AudioTrack(
|
||||
streamType,
|
||||
44100,
|
||||
AudioFormat.CHANNEL_OUT_MONO,
|
||||
AudioFormat.ENCODING_PCM_16BIT,
|
||||
pcm.size,
|
||||
AudioTrack.MODE_STREAM
|
||||
)
|
||||
if (track.state != AudioTrack.STATE_INITIALIZED) {
|
||||
Log.w("DiPlay", "playTestTone streamType=$streamType state=${track.state}")
|
||||
track.release()
|
||||
toast("Stream type $streamType is unavailable here (state=${track.state})")
|
||||
return
|
||||
}
|
||||
track.write(pcm, 0, pcm.size)
|
||||
track.play()
|
||||
Log.i("DiPlay", "playTestTone streamType=$streamType state=${track.state} playState=${track.playState}")
|
||||
testToneTrack = track
|
||||
val stop = Runnable {
|
||||
track.stop()
|
||||
track.release()
|
||||
if (testToneTrack === track) testToneTrack = null
|
||||
toneStop = null
|
||||
}
|
||||
toneStop = stop
|
||||
handler.postDelayed(stop, 4500)
|
||||
}
|
||||
|
||||
private val channelButtons = mutableListOf<Button>()
|
||||
|
||||
private fun paintChannel(index: Int, selected: Boolean) {
|
||||
val target = channelButtons.getOrNull(index) ?: return
|
||||
target.isSelected = selected
|
||||
target.setTextColor(if (selected) BG else TEXT)
|
||||
target.background = android.graphics.drawable.RippleDrawable(
|
||||
ColorStateList.valueOf(0x336F9FD9),
|
||||
rounded(if (selected) ACCENT else SURFACE, if (selected) ACCENT else BORDER),
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
private fun channelSelector(): ViewGroup {
|
||||
channelButtons.clear()
|
||||
val grid = GridLayout(this).apply {
|
||||
columnCount = 7
|
||||
rowCount = 3
|
||||
setPadding(0, dp(8), 0, dp(8))
|
||||
}
|
||||
for (i in 0..20) {
|
||||
val btn = Button(this).apply {
|
||||
text = i.toString()
|
||||
isAllCaps = false
|
||||
textSize = 16f
|
||||
minHeight = dp(48)
|
||||
stateListAnimator = null
|
||||
setOnClickListener {
|
||||
val previous = navigationStreamType
|
||||
navigationStreamType = i
|
||||
if (previous != i) {
|
||||
paintChannel(previous, false)
|
||||
paintChannel(i, true)
|
||||
}
|
||||
playTestTone(i)
|
||||
}
|
||||
}
|
||||
val params = GridLayout.LayoutParams().apply {
|
||||
width = 0
|
||||
height = dp(48)
|
||||
columnSpec = GridLayout.spec(GridLayout.UNDEFINED, 1f)
|
||||
setMargins(dp(4), dp(4), dp(4), dp(4))
|
||||
}
|
||||
grid.addView(btn, params)
|
||||
channelButtons.add(btn)
|
||||
paintChannel(i, i == navigationStreamType)
|
||||
}
|
||||
return grid
|
||||
}
|
||||
private fun version() = packageManager.getPackageInfo(packageName, 0).versionName ?: "0.1.0-beta.1"
|
||||
private fun section(parent: LinearLayout, title: String, icon: Int? = null, build: (LinearLayout) -> Unit) {
|
||||
val card = card()
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<resources>
|
||||
<bool name="config_advanced_audio_channel_mapping">false</bool>
|
||||
<bool name="config_advanced_audio_channel_mapping">true</bool>
|
||||
</resources>
|
||||
Binary file not shown.
@@ -36,6 +36,7 @@ class AndroidMediaSink(
|
||||
private val videoHeight: Int = 720,
|
||||
private val preferSoftwareHevcDecoder: Boolean = false,
|
||||
private val advancedAudioChannelMapping: Boolean = false,
|
||||
private val navigationStreamType: Int = AudioChannelMapper.DEFAULT_NAVIGATION_STREAM_TYPE,
|
||||
onScreenStreamActiveChanged: ((Int, Boolean) -> Unit)? = null,
|
||||
private val mediaBufferMillis: Int = MediaAudioBuffer.DEFAULT_MILLIS,
|
||||
private val onAudioDiagnostic: (String) -> Unit = {},
|
||||
@@ -173,7 +174,7 @@ class AndroidMediaSink(
|
||||
val existing = audioRenderers[type]
|
||||
if (existing?.format == format) return existing
|
||||
existing?.close()
|
||||
return AudioRenderer(format, advancedAudioChannelMapping, mediaBufferMillis, onAudioDiagnostic).also { audioRenderers[type] = it }
|
||||
return AudioRenderer(format, advancedAudioChannelMapping, navigationStreamType, mediaBufferMillis, onAudioDiagnostic).also { audioRenderers[type] = it }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -497,6 +498,7 @@ private fun MediaFormat.intOrNull(key: String): Int? =
|
||||
private class AudioRenderer(
|
||||
val format: AudioFormat,
|
||||
private val advancedAudioChannelMapping: Boolean,
|
||||
private val navigationStreamType: Int,
|
||||
private val mediaBufferMillis: Int,
|
||||
private val report: (String) -> Unit,
|
||||
) : Closeable {
|
||||
@@ -637,33 +639,78 @@ private class AudioRenderer(
|
||||
val plan = MediaAudioBuffer.plan(format.audioType, format.sampleRate, format.channels, minBuffer, mediaBufferMillis)
|
||||
val frameBytes = if (format.channels >= 2) 4 else 2
|
||||
bytesPerSecond = format.sampleRate * frameBytes
|
||||
val built = AudioTrack.Builder()
|
||||
.setAudioAttributes(audioAttributes())
|
||||
.setAudioFormat(
|
||||
AndroidAudioFormat.Builder()
|
||||
.setEncoding(encoding)
|
||||
.setSampleRate(format.sampleRate)
|
||||
.setChannelMask(channelMask)
|
||||
.build(),
|
||||
val built: AudioTrack
|
||||
var routeLabel: String
|
||||
if (advancedAudioChannelMapping) {
|
||||
val attributes = audioAttributes()
|
||||
routeLabel = "usage"
|
||||
built = AudioTrack.Builder()
|
||||
.setAudioAttributes(attributes)
|
||||
.setAudioFormat(pcmFormat(encoding, channelMask))
|
||||
.setTransferMode(AudioTrack.MODE_STREAM)
|
||||
.setBufferSizeInBytes(plan.trackBufferBytes)
|
||||
.build()
|
||||
} else {
|
||||
val streamType = streamType()
|
||||
routeLabel = "streamType=$streamType"
|
||||
var candidate = AudioTrack(
|
||||
streamType,
|
||||
format.sampleRate,
|
||||
channelMask,
|
||||
encoding,
|
||||
plan.trackBufferBytes,
|
||||
AudioTrack.MODE_STREAM,
|
||||
)
|
||||
.setBufferSizeInBytes(plan.trackBufferBytes)
|
||||
.setTransferMode(AudioTrack.MODE_STREAM)
|
||||
.build()
|
||||
if (candidate.state != AudioTrack.STATE_INITIALIZED) {
|
||||
candidate.release()
|
||||
candidate = AudioTrack.Builder()
|
||||
.setAudioAttributes(audioAttributes())
|
||||
.setAudioFormat(pcmFormat(encoding, channelMask))
|
||||
.setTransferMode(AudioTrack.MODE_STREAM)
|
||||
.setBufferSizeInBytes(plan.trackBufferBytes)
|
||||
.build()
|
||||
routeLabel = "streamType=$streamType(fallback=usage)"
|
||||
Log.w(TAG, "streamType=$streamType rejected by this ROM; falling back to usage-based track")
|
||||
}
|
||||
built = candidate
|
||||
}
|
||||
track = built
|
||||
val capacityBytes = built.bufferSizeInFrames * frameBytes
|
||||
startThresholdBytes = MediaAudioBuffer.startBytesFor(plan.startBytes, capacityBytes, PREBUFFER_WRITE_CHUNK_BYTES)
|
||||
report("Audio: ready audioType=${format.audioType} codec=${format.codec} " +
|
||||
"rate=${format.sampleRate} channels=${format.channels} " +
|
||||
"route=$routeLabel " +
|
||||
"bufferMs=${capacityBytes * 1000L / bytesPerSecond} startMs=${startThresholdBytes * 1000L / bytesPerSecond}")
|
||||
Log.i(
|
||||
TAG,
|
||||
"audio track prepared type=${format.payloadType} audioType=${format.audioType} " +
|
||||
"codec=${format.codec} " +
|
||||
"rate=${format.sampleRate} channels=${format.channels} " +
|
||||
"buffer=${capacityBytes * 1000L / bytesPerSecond}ms start=${startThresholdBytes * 1000L / bytesPerSecond}ms",
|
||||
"route=$routeLabel " +
|
||||
"buffer=${capacityBytes * 1000L / bytesPerSecond}ms start=${startThresholdBytes * 1000L / bytesPerSecond}",
|
||||
)
|
||||
}
|
||||
|
||||
private fun pcmFormat(encoding: Int, channelMask: Int) = AndroidAudioFormat.Builder()
|
||||
.setSampleRate(format.sampleRate)
|
||||
.setChannelMask(channelMask)
|
||||
.setEncoding(encoding)
|
||||
.build()
|
||||
|
||||
private fun streamType(): Int {
|
||||
val mode = if (advancedAudioChannelMapping) {
|
||||
AudioChannelMappingMode.AUTOMOTIVE_BUS
|
||||
} else {
|
||||
AudioChannelMappingMode.MOBILE_COMPATIBLE
|
||||
}
|
||||
return AudioChannelMapper.map(
|
||||
audioType = format.audioType,
|
||||
payloadType = format.payloadType,
|
||||
mode = mode,
|
||||
navigationStreamType = navigationStreamType,
|
||||
).streamType
|
||||
}
|
||||
|
||||
private fun aacAudioSpecificConfig(): ByteArray {
|
||||
val frequencyIndex = MediaCodecSupport.aacFrequencyIndex(format.sampleRate)
|
||||
val value = (AAC_OBJECT_TYPE_LC shl 11) or
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.shilapi.xcertplay.media
|
||||
|
||||
import android.media.AudioManager
|
||||
|
||||
internal enum class AudioChannelMappingMode {
|
||||
MOBILE_COMPATIBLE,
|
||||
AUTOMOTIVE_BUS,
|
||||
@@ -20,59 +22,67 @@ internal enum class AudioContentType {
|
||||
internal data class AudioChannelSelection(
|
||||
val channel: AudioChannel,
|
||||
val contentType: AudioContentType,
|
||||
val streamType: Int = AudioManager.STREAM_MUSIC,
|
||||
)
|
||||
|
||||
/**
|
||||
* Maps CarPlay stream metadata to Android AudioAttributes values.
|
||||
* Maps CarPlay stream metadata to Android audio routing values.
|
||||
*
|
||||
* AAOS routes AudioTrack instances by usage, so the automotive mode keeps generic media streams
|
||||
* on the media bus instead of reusing mobile's navigation fallback for default/compatibility.
|
||||
* Each selection carries the [AudioChannel] / [AudioContentType] pair used for usage-based
|
||||
* AudioAttributes, plus the stream type used when the stream-type AudioTrack path is active.
|
||||
* Guidance audio always resolves to [AudioChannel.NAVIGATION] so it can be routed away from the
|
||||
* media bus, and `default` / `compatibility` follow it in the automotive mode rather than
|
||||
* sharing the media bus.
|
||||
*/
|
||||
internal object AudioChannelMapper {
|
||||
const val STREAM_TYPE_MAIN_HIGH_AUDIO = 102
|
||||
const val DEFAULT_NAVIGATION_STREAM_TYPE = 14
|
||||
|
||||
fun map(
|
||||
audioType: String,
|
||||
payloadType: Int,
|
||||
mode: AudioChannelMappingMode,
|
||||
navigationStreamType: Int = DEFAULT_NAVIGATION_STREAM_TYPE,
|
||||
): AudioChannelSelection {
|
||||
val normalized = audioType.lowercase()
|
||||
return when (mode) {
|
||||
AudioChannelMappingMode.MOBILE_COMPATIBLE -> mapMobileCompatible(normalized, payloadType)
|
||||
AudioChannelMappingMode.AUTOMOTIVE_BUS -> mapAutomotiveBus(normalized, payloadType)
|
||||
AudioChannelMappingMode.MOBILE_COMPATIBLE -> mapMobileCompatible(normalized, payloadType, navigationStreamType)
|
||||
AudioChannelMappingMode.AUTOMOTIVE_BUS -> mapAutomotiveBus(normalized, payloadType, navigationStreamType)
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapMobileCompatible(
|
||||
audioType: String,
|
||||
payloadType: Int,
|
||||
navigationStreamType: Int,
|
||||
): AudioChannelSelection = when (audioType) {
|
||||
"telephony" -> AudioChannelSelection(AudioChannel.PHONE, AudioContentType.SPEECH)
|
||||
"speechrecognition" ->
|
||||
AudioChannelSelection(AudioChannel.ASSISTANT, AudioContentType.SPEECH)
|
||||
"media" -> AudioChannelSelection(AudioChannel.MEDIA, AudioContentType.MUSIC)
|
||||
"default", "alert", "compatibility" ->
|
||||
AudioChannelSelection(AudioChannel.NAVIGATION, AudioContentType.SPEECH)
|
||||
else -> mainHighAudioOrNavigation(payloadType)
|
||||
AudioChannelSelection(AudioChannel.NAVIGATION, AudioContentType.SPEECH, navigationStreamType)
|
||||
else -> mainHighAudioOrNavigation(payloadType, navigationStreamType)
|
||||
}
|
||||
|
||||
private fun mapAutomotiveBus(
|
||||
audioType: String,
|
||||
payloadType: Int,
|
||||
navigationStreamType: Int,
|
||||
): AudioChannelSelection = when (audioType) {
|
||||
"telephony" -> AudioChannelSelection(AudioChannel.PHONE, AudioContentType.SPEECH)
|
||||
"speechrecognition" ->
|
||||
AudioChannelSelection(AudioChannel.ASSISTANT, AudioContentType.SPEECH)
|
||||
"media", "default", "compatibility" ->
|
||||
AudioChannelSelection(AudioChannel.MEDIA, AudioContentType.MUSIC)
|
||||
"alert" -> AudioChannelSelection(AudioChannel.NAVIGATION, AudioContentType.SPEECH)
|
||||
else -> mainHighAudioOrNavigation(payloadType)
|
||||
"media" -> AudioChannelSelection(AudioChannel.MEDIA, AudioContentType.MUSIC)
|
||||
"alert", "default", "compatibility" ->
|
||||
AudioChannelSelection(AudioChannel.NAVIGATION, AudioContentType.SPEECH, navigationStreamType)
|
||||
else -> mainHighAudioOrNavigation(payloadType, navigationStreamType)
|
||||
}
|
||||
|
||||
private fun mainHighAudioOrNavigation(payloadType: Int): AudioChannelSelection =
|
||||
private fun mainHighAudioOrNavigation(payloadType: Int, navigationStreamType: Int): AudioChannelSelection =
|
||||
if (payloadType == STREAM_TYPE_MAIN_HIGH_AUDIO) {
|
||||
AudioChannelSelection(AudioChannel.MEDIA, AudioContentType.MUSIC)
|
||||
} else {
|
||||
AudioChannelSelection(AudioChannel.NAVIGATION, AudioContentType.SPEECH)
|
||||
AudioChannelSelection(AudioChannel.NAVIGATION, AudioContentType.SPEECH, navigationStreamType)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.shilapi.xcertplay.media
|
||||
|
||||
import android.media.AudioManager
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
@@ -34,19 +35,28 @@ class AudioChannelMappingTest {
|
||||
payloadType = 100,
|
||||
channel = AudioChannel.NAVIGATION,
|
||||
contentType = AudioContentType.SPEECH,
|
||||
streamType = AudioChannelMapper.DEFAULT_NAVIGATION_STREAM_TYPE,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun automotiveMappingUsesTheBusSpecificCarPlayTypes() {
|
||||
listOf("media", "default", "compatibility").forEach { audioType ->
|
||||
assertMapped(
|
||||
mode = AudioChannelMappingMode.AUTOMOTIVE_BUS,
|
||||
audioType = "media",
|
||||
payloadType = 100,
|
||||
channel = AudioChannel.MEDIA,
|
||||
contentType = AudioContentType.MUSIC,
|
||||
)
|
||||
listOf("default", "compatibility").forEach { audioType ->
|
||||
assertMapped(
|
||||
mode = AudioChannelMappingMode.AUTOMOTIVE_BUS,
|
||||
audioType = audioType,
|
||||
payloadType = 100,
|
||||
channel = AudioChannel.MEDIA,
|
||||
contentType = AudioContentType.MUSIC,
|
||||
channel = AudioChannel.NAVIGATION,
|
||||
contentType = AudioContentType.SPEECH,
|
||||
streamType = AudioChannelMapper.DEFAULT_NAVIGATION_STREAM_TYPE,
|
||||
)
|
||||
}
|
||||
assertMapped(
|
||||
@@ -69,6 +79,7 @@ class AudioChannelMappingTest {
|
||||
payloadType = 100,
|
||||
channel = AudioChannel.NAVIGATION,
|
||||
contentType = AudioContentType.SPEECH,
|
||||
streamType = AudioChannelMapper.DEFAULT_NAVIGATION_STREAM_TYPE,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -87,6 +98,24 @@ class AudioChannelMappingTest {
|
||||
payloadType = 100,
|
||||
channel = AudioChannel.NAVIGATION,
|
||||
contentType = AudioContentType.SPEECH,
|
||||
streamType = AudioChannelMapper.DEFAULT_NAVIGATION_STREAM_TYPE,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun navigationStreamTypeIsOnlyAppliedToGuidanceChannels() {
|
||||
val custom = 7
|
||||
assertEquals(
|
||||
AudioChannelSelection(AudioChannel.NAVIGATION, AudioContentType.SPEECH, custom),
|
||||
AudioChannelMapper.map("alert", 100, AudioChannelMappingMode.MOBILE_COMPATIBLE, custom),
|
||||
)
|
||||
assertEquals(
|
||||
AudioChannelSelection(AudioChannel.NAVIGATION, AudioContentType.SPEECH, custom),
|
||||
AudioChannelMapper.map("default", 100, AudioChannelMappingMode.AUTOMOTIVE_BUS, custom),
|
||||
)
|
||||
assertEquals(
|
||||
AudioChannelSelection(AudioChannel.MEDIA, AudioContentType.MUSIC, AudioManager.STREAM_MUSIC),
|
||||
AudioChannelMapper.map("media", 100, AudioChannelMappingMode.AUTOMOTIVE_BUS, custom),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -96,9 +125,10 @@ class AudioChannelMappingTest {
|
||||
payloadType: Int,
|
||||
channel: AudioChannel,
|
||||
contentType: AudioContentType,
|
||||
streamType: Int = AudioManager.STREAM_MUSIC,
|
||||
) {
|
||||
assertEquals(
|
||||
AudioChannelSelection(channel, contentType),
|
||||
AudioChannelSelection(channel, contentType, streamType),
|
||||
AudioChannelMapper.map(audioType, payloadType, mode),
|
||||
)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user