Воспроизведение потока PCM на Android с помощью AudioTrack приводит к появлению артефактовAndroid

Форум для тех, кто программирует под Android
Ответить Пред. темаСлед. тема
Anonymous
 Воспроизведение потока PCM на Android с помощью AudioTrack приводит к появлению артефактов

Сообщение Anonymous »

Я получаю PCM из оболочки собственной библиотеки WebRTC и пытаюсь воспроизвести его.
Вот моя реализация JVM для настольного компьютера, она отлично работает, по крайней мере, в Linux:

Код: Выделить всё

class NativeWebRTCClientDesktopImpl : NativeWebRTCClient() {

private val audioPlayer = AudioPlayer()

override fun onAudioReceivedInternal(data: ByteBuffer) {
audioPlayer.onAudioReceivedInternal(data)
}

class AudioPlayer {
private val audioLine: SourceDataLine

private val sampleRate = SAMPLE_RATE
private val channels = CHANNELS_COUNT
private val bitsPerSample = BITS_PER_SAMPLE

init {
val format = AudioFormat(
sampleRate.toFloat(),
bitsPerSample,
channels,
true,
false
)

audioLine = AudioSystem.getSourceDataLine(format).apply {
open(format)
start()
}
}

fun onAudioReceivedInternal(data: ByteBuffer) {
val bytes = ByteArray(data.remaining())
data.get(bytes)

audioLine.write(bytes, 0, bytes.size)
}

fun cleanup() {
audioLine.drain()
audioLine.stop()
audioLine.close()
}
}

private companion object {
private const val CHANNELS_COUNT = 2
private const val SAMPLE_RATE = 48_000
private const val BITS_PER_SAMPLE = 16
}
}
Но когда я пытаюсь воспроизвести тот же поток на Android с реализацией Android, звук заикается и появляется шум:

Код: Выделить всё

class NativeWebRTCClientAndroidImpl(
private val audioManager: AudioManager,
) : NativeWebRTCClient() {
private val logger = Logger.withTag(LOG_TAG)

private val audioPlayer = AudioPlayer()

override fun onAudioReceivedInternal(data: ByteBuffer) {
audioPlayer.onAudioReceivedInternal(data)
}

private inner class AudioPlayer {

private val frameSize = 960 // WebRTC frame size
private val bytesPerFrame = frameSize * 2 * 2 // stereo * 16bit

private val audioTrack: AudioTrack
private val bufferSize: Int = AudioTrack.getMinBufferSize(
SAMPLE_RATE,
CHANNEL_CONFIG,
AUDIO_FORMAT
)

private val tempDataBuffer = ByteArray(TEMP_DATA_BUFFER_SIZE)
private val dataBuffer = ByteArray(bufferSize)
private var currentBufferPosition = 0

private var speakerDevice: AudioDeviceInfo? = null

private val routingCallback = AudioTrack.OnRoutingChangedListener { forceSpeakerOutput() }

init {
logger.d { "Buffer size: $bufferSize" }

val devices = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
devices.forEach {
logger.d { "Available device: ${it.type} - ${it.productName}" }
}

speakerDevice = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
.firstOrNull { device ->
device.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER
}

val audioAttributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
.build()

val audioFormat = AudioFormat.Builder()
.setSampleRate(SAMPLE_RATE)
.setEncoding(AUDIO_FORMAT)
.setChannelMask(CHANNEL_CONFIG)
.build()

audioTrack = AudioTrack.Builder()
.setAudioAttributes(audioAttributes)
.setAudioFormat(audioFormat)
.setBufferSizeInBytes(bufferSize)
.setTransferMode(AudioTrack.MODE_STREAM)
.build()

audioTrack.addOnRoutingChangedListener(routingCallback, null)

audioManager.apply {
mode = AudioManager.MODE_NORMAL
isSpeakerphoneOn = true
}

speakerDevice?.let {
if (!audioTrack.setPreferredDevice(it)) {
logger.e { "Failed to set ${it.type} as preferred device"  }
}
}

audioTrack.play()
}

private fun forceSpeakerOutput() {
speakerDevice?.let { device ->
if (audioTrack.preferredDevice?.id != device.id) {
audioTrack.preferredDevice = device
}
}
audioManager.isSpeakerphoneOn = true
}

fun onAudioReceivedInternal(data: ByteBuffer) {
val dataSize = data.remaining()
data.get(tempDataBuffer, 0, dataSize)

// I've also tried to simply do audioTrack.write() with the received data without buffering
// Result is the same

// The idea here was to wait until min buffer will be filled
var offset = 0
while (offset < dataSize) {
val copyLength = minOf(bufferSize - currentBufferPosition, dataSize - offset)

tempDataBuffer.copyInto(
dataBuffer,
currentBufferPosition,
offset,
offset + copyLength
)

currentBufferPosition += copyLength
offset += copyLength

logger.d { "Copied $copyLength to buffer, ${dataSize - offset} left" }

if (currentBufferPosition == bufferSize) {
audioTrack.write(dataBuffer, 0, bufferSize, AudioTrack.WRITE_BLOCKING)
currentBufferPosition = 0
}
}
}
}

private companion object {
private const val LOG_TAG = "NativeWebRTCClient"

private const val SAMPLE_RATE = 48000
private const val CHANNEL_CONFIG = AudioFormat.CHANNEL_OUT_STEREO
private const val AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT

private const val TEMP_DATA_BUFFER_SIZE = 1024 * 128
}
}
Вот пример: https://soundcloud.com/art-s-394736082/ ... 0e3ff8b776
Я тоже попробовал это (так как на стороне C++ это int16_t):

Код: Выделить всё

fun onAudioReceivedInternal(data: ByteBuffer) {
data.order(ByteOrder.LITTLE_ENDIAN)

val shortBuffer = data.asShortBuffer()
val shortArray = ShortArray(shortBuffer.remaining())
shortBuffer.get(shortArray)

audioTrack.write(shortArray, 0, shortArray.size, AudioTrack.WRITE_BLOCKING)
}
Чуть лучше, с меньшим количеством прерываний, но все равно очень шумно, с большим количеством «трещин».
Аудиоформат правильный.
Что может быть не так?
UPD: Добавлена ​​минимальная демо: https://github.com/RankoR/android-pcm-experiments
Звучит не так плохо, как мой WebRTC-поток, но и у него есть заметные артефакты.

Подробнее здесь: https://stackoverflow.com/questions/792 ... -artifacts
Реклама
Ответить Пред. темаСлед. тема

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

  • Похожие темы
    Ответы
    Просмотры
    Последнее сообщение

Вернуться в «Android»