fixed bug with audio track creation failure
This commit is contained in:
parent
e068ad52de
commit
fc12c9d932
@ -1,5 +1,4 @@
|
||||
package net.sergeych.karabass
|
||||
|
||||
import android.content.Context
|
||||
import android.media.AudioAttributes
|
||||
import android.media.AudioFormat
|
||||
@ -7,6 +6,7 @@ import android.media.AudioManager
|
||||
import android.media.AudioTrack
|
||||
import android.os.Build
|
||||
import kotlin.math.*
|
||||
import kotlin.random.Random
|
||||
|
||||
class AdvancedTubaSynth(private val context: Context) {
|
||||
|
||||
@ -63,6 +63,57 @@ class AdvancedTubaSynth(private val context: Context) {
|
||||
private val releaseTime = (0.05 * SAMPLE_RATE).toInt() // 50ms релиз
|
||||
private val sustainLevel = 0.8
|
||||
|
||||
// Минимальный буфер для low-latency
|
||||
private val bufferSize = 512 // ~11ms latency
|
||||
|
||||
init {
|
||||
// Создаем AudioTrack один раз при инициализации
|
||||
initializeAudioTrack()
|
||||
}
|
||||
|
||||
private fun initializeAudioTrack() {
|
||||
synchronized(lock) {
|
||||
try {
|
||||
audioTrack = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
val attributes = AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_MEDIA)
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
|
||||
.build()
|
||||
|
||||
val format = AudioFormat.Builder()
|
||||
.setSampleRate(SAMPLE_RATE)
|
||||
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
|
||||
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
|
||||
.build()
|
||||
|
||||
AudioTrack.Builder()
|
||||
.setAudioAttributes(attributes)
|
||||
.setAudioFormat(format)
|
||||
.setBufferSizeInBytes(bufferSize * 2)
|
||||
.build()
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
AudioTrack(
|
||||
AudioManager.STREAM_MUSIC,
|
||||
SAMPLE_RATE,
|
||||
AudioFormat.CHANNEL_OUT_MONO,
|
||||
AudioFormat.ENCODING_PCM_16BIT,
|
||||
bufferSize * 2,
|
||||
AudioTrack.MODE_STREAM
|
||||
)
|
||||
}
|
||||
|
||||
audioTrack?.play()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
// Пытаемся восстановить AudioTrack через некоторое время
|
||||
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
||||
initializeAudioTrack()
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun startNote(frequency: Float) {
|
||||
synchronized(lock) {
|
||||
targetFrequency = frequency
|
||||
@ -105,47 +156,15 @@ class AdvancedTubaSynth(private val context: Context) {
|
||||
}
|
||||
|
||||
private fun startAudioGeneration() {
|
||||
if (isPlaying) return
|
||||
|
||||
shouldStop = false
|
||||
isPlaying = true
|
||||
|
||||
// Минимальный буфер для low-latency
|
||||
val bufferSize = 512 // ~11ms latency
|
||||
|
||||
try {
|
||||
audioTrack = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
val attributes = AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_MEDIA)
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
|
||||
.build()
|
||||
|
||||
val format = AudioFormat.Builder()
|
||||
.setSampleRate(SAMPLE_RATE)
|
||||
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
|
||||
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
|
||||
.build()
|
||||
|
||||
AudioTrack.Builder()
|
||||
.setAudioAttributes(attributes)
|
||||
.setAudioFormat(format)
|
||||
.setBufferSizeInBytes(bufferSize * 2)
|
||||
.build()
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
AudioTrack(
|
||||
AudioManager.STREAM_MUSIC,
|
||||
SAMPLE_RATE,
|
||||
AudioFormat.CHANNEL_OUT_MONO,
|
||||
AudioFormat.ENCODING_PCM_16BIT,
|
||||
bufferSize * 2,
|
||||
AudioTrack.MODE_STREAM
|
||||
)
|
||||
}
|
||||
|
||||
audioTrack?.play()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
isPlaying = false
|
||||
return
|
||||
// Проверяем, что AudioTrack существует и готов
|
||||
val track = audioTrack
|
||||
if (track == null || track.state != AudioTrack.STATE_INITIALIZED) {
|
||||
initializeAudioTrack()
|
||||
}
|
||||
|
||||
currentThread = Thread {
|
||||
@ -161,17 +180,29 @@ class AdvancedTubaSynth(private val context: Context) {
|
||||
|
||||
// Безопасная запись в AudioTrack
|
||||
val track = audioTrack
|
||||
if (track?.playState == AudioTrack.PLAYSTATE_PLAYING) {
|
||||
if (track?.state == AudioTrack.STATE_INITIALIZED && track.playState == AudioTrack.PLAYSTATE_PLAYING) {
|
||||
try {
|
||||
track.write(buffer, 0, buffer.size)
|
||||
val written = track.write(buffer, 0, buffer.size)
|
||||
if (written < buffer.size) {
|
||||
// Проблема с записью - возможно, AudioTrack в плохом состоянии
|
||||
Thread.sleep(1) // Небольшая пауза
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// Игнорируем ошибки записи при остановке
|
||||
if (!shouldStop) {
|
||||
e.printStackTrace()
|
||||
// Пытаемся восстановить AudioTrack
|
||||
android.os.Handler(android.os.Looper.getMainLooper()).post {
|
||||
initializeAudioTrack()
|
||||
}
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
} else {
|
||||
// AudioTrack не готов - пытаемся восстановить
|
||||
android.os.Handler(android.os.Looper.getMainLooper()).post {
|
||||
initializeAudioTrack()
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
@ -185,12 +216,6 @@ class AdvancedTubaSynth(private val context: Context) {
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
} finally {
|
||||
// Безопасная остановка
|
||||
try {
|
||||
audioTrack?.stop()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
isPlaying = false
|
||||
}
|
||||
}.apply {
|
||||
|
@ -1,260 +0,0 @@
|
||||
import android.content.Context
|
||||
import android.media.AudioAttributes
|
||||
import android.media.AudioFormat
|
||||
import android.media.AudioManager
|
||||
import android.media.AudioTrack
|
||||
import android.os.Build
|
||||
import kotlin.math.*
|
||||
import kotlin.random.Random
|
||||
|
||||
class RealisticTubaSynth(private val context: Context) {
|
||||
|
||||
companion object {
|
||||
const val SAMPLE_RATE = 44100
|
||||
const val CUTOFF_FREQ = 800.0
|
||||
}
|
||||
|
||||
private var audioTrack: AudioTrack? = null
|
||||
private var isPlaying = false
|
||||
private var currentThread: Thread? = null
|
||||
|
||||
data class TubaNote(
|
||||
val frequency: Float,
|
||||
val amplitude: Float = 0.8f,
|
||||
val durationMs: Int
|
||||
)
|
||||
|
||||
fun playTubaNote(frequency: Float, durationMs: Int = 1500) {
|
||||
// Останавливаем предыдущее воспроизведение
|
||||
stop()
|
||||
|
||||
val note = TubaNote(frequency, 0.8f, durationMs)
|
||||
generateTubaSound(note)
|
||||
}
|
||||
|
||||
private fun generateTubaSound(note: TubaNote) {
|
||||
currentThread = Thread {
|
||||
isPlaying = true
|
||||
val numSamples = note.durationMs * SAMPLE_RATE / 1000
|
||||
val buffer = ShortArray(numSamples)
|
||||
|
||||
// Фильтр для шума дыхания
|
||||
val alpha = 1 - exp(-2.0 * PI * CUTOFF_FREQ / SAMPLE_RATE)
|
||||
var filteredNoise = 0.0
|
||||
|
||||
val random = Random(System.currentTimeMillis())
|
||||
|
||||
for (i in 0 until numSamples) {
|
||||
if (!isPlaying) break
|
||||
|
||||
val time = i.toDouble() / SAMPLE_RATE
|
||||
|
||||
// Основной waveform тубы с более богатым спектром
|
||||
val tubaWave = calculateTubaWaveform(note.frequency, time)
|
||||
|
||||
// Шум дыхания с фильтром низких частот
|
||||
val whiteNoise = random.nextDouble() * 2 - 1
|
||||
filteredNoise += alpha * (whiteNoise - filteredNoise)
|
||||
|
||||
// Огибающая с резкой атакой и быстрым спадом
|
||||
val envelope = getRealisticTubaEnvelope(i, numSamples, note.frequency)
|
||||
|
||||
// Усиление басов
|
||||
val bassBoost = 1.0 + 0.5 * sin(2 * PI * 60.0 * time)
|
||||
|
||||
// Смешиваем всё вместе
|
||||
var sample = tubaWave + filteredNoise * 0.03 * envelope
|
||||
sample *= envelope * bassBoost
|
||||
|
||||
// Ограничиваем амплитуду
|
||||
sample = sample.coerceIn(-1.0, 1.0)
|
||||
|
||||
buffer[i] = (sample * Short.MAX_VALUE).toInt().toShort()
|
||||
}
|
||||
|
||||
if (isPlaying) {
|
||||
playAudioBuffer(buffer)
|
||||
}
|
||||
}.apply {
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateTubaWaveform(freq: Float, time: Double): Double {
|
||||
// Классификация нот по диапазонам
|
||||
val range = when {
|
||||
freq < 40 -> "pedal" // Педальные ноты (очень низкие)
|
||||
freq < 80 -> "low" // Низкий диапазон
|
||||
freq < 160 -> "middle" // Средний диапазон
|
||||
freq < 240 -> "high" // Высокий диапазон
|
||||
else -> "very_high" // Очень высокий диапазон
|
||||
}
|
||||
|
||||
val fundamental = sin(2 * PI * freq * time)
|
||||
|
||||
// Разные наборы обертонов для разных диапазонов
|
||||
val overtones = when (range) {
|
||||
"pedal" -> {
|
||||
// Педальные ноты: сильные низкие обертоны, меньше высоких
|
||||
sin(2 * PI * freq * 2 * time) * 0.6 +
|
||||
sin(2 * PI * freq * 3 * time) * 0.5 +
|
||||
sin(2 * PI * freq * 4 * time) * 0.4 +
|
||||
sin(2 * PI * freq * 5 * time) * 0.3
|
||||
}
|
||||
"low" -> {
|
||||
// Низкий диапазон: богатый спектр
|
||||
sin(2 * PI * freq * 2 * time) * 0.5 +
|
||||
sin(2 * PI * freq * 3 * time) * 0.45 +
|
||||
sin(2 * PI * freq * 4 * time) * 0.4 +
|
||||
sin(2 * PI * freq * 5 * time) * 0.35 +
|
||||
sin(2 * PI * freq * 6 * time) * 0.25
|
||||
}
|
||||
"middle" -> {
|
||||
// Средний диапазон: сбалансированный спектр
|
||||
sin(2 * PI * freq * 2 * time) * 0.4 +
|
||||
sin(2 * PI * freq * 3 * time) * 0.4 +
|
||||
sin(2 * PI * freq * 4 * time) * 0.35 +
|
||||
sin(2 * PI * freq * 5 * time) * 0.3 +
|
||||
sin(2 * PI * freq * 6 * time) * 0.25 +
|
||||
sin(2 * PI * freq * 7 * time) * 0.2
|
||||
}
|
||||
"high" -> {
|
||||
// Высокий диапазон: больше высоких обертонов
|
||||
sin(2 * PI * freq * 2 * time) * 0.3 +
|
||||
sin(2 * PI * freq * 3 * time) * 0.35 +
|
||||
sin(2 * PI * freq * 4 * time) * 0.3 +
|
||||
sin(2 * PI * freq * 5 * time) * 0.25 +
|
||||
sin(2 * PI * freq * 6 * time) * 0.2 +
|
||||
sin(2 * PI * freq * 7 * time) * 0.15 +
|
||||
sin(2 * PI * freq * 8 * time) * 0.1
|
||||
}
|
||||
else -> {
|
||||
// Очень высокий диапазон: яркий, с преобладанием высоких обертонов
|
||||
sin(2 * PI * freq * 2 * time) * 0.25 +
|
||||
sin(2 * PI * freq * 3 * time) * 0.3 +
|
||||
sin(2 * PI * freq * 4 * time) * 0.25 +
|
||||
sin(2 * PI * freq * 5 * time) * 0.2 +
|
||||
sin(2 * PI * freq * 6 * time) * 0.15 +
|
||||
sin(2 * PI * freq * 7 * time) * 0.1 +
|
||||
sin(2 * PI * freq * 8 * time) * 0.08 +
|
||||
sin(2 * PI * freq * 9 * time) * 0.05
|
||||
}
|
||||
}
|
||||
|
||||
val mixed = fundamental * 0.6 + overtones * 0.8
|
||||
return tanh(mixed * 1.2) / 1.2
|
||||
}
|
||||
|
||||
private fun getRealisticTubaEnvelope(sampleIndex: Int, totalSamples: Int, frequency: Float): Double {
|
||||
val position = sampleIndex.toDouble() / totalSamples
|
||||
|
||||
// Разная огибающая для разных диапазонов
|
||||
val (attack, decay, sustain, release) = when {
|
||||
frequency < 40 -> arrayOf(0.04, 0.2, 0.6, 0.15) // Педальные ноты: медленнее
|
||||
frequency < 100 -> arrayOf(0.025, 0.15, 0.65, 0.1) // Низкие: умеренные
|
||||
frequency < 200 -> arrayOf(0.015, 0.1, 0.7, 0.08) // Средние: быстрее
|
||||
else -> arrayOf(0.01, 0.08, 0.75, 0.06) // Высокие: очень быстрые
|
||||
}
|
||||
|
||||
return when {
|
||||
position < attack -> (position / attack).pow(0.3)
|
||||
position < attack + decay -> {
|
||||
val decayPos = (position - attack) / decay
|
||||
1.0 - (1.0 - sustain) * decayPos
|
||||
}
|
||||
position > 1 - release -> {
|
||||
val releasePos = (position - (1 - release)) / release
|
||||
sustain * (1 - releasePos)
|
||||
}
|
||||
else -> sustain
|
||||
}
|
||||
}
|
||||
private fun playAudioBuffer(buffer: ShortArray) {
|
||||
try {
|
||||
// Создаем новый AudioTrack каждый раз
|
||||
val bufferSize = AudioTrack.getMinBufferSize(
|
||||
SAMPLE_RATE,
|
||||
AudioFormat.CHANNEL_OUT_MONO,
|
||||
AudioFormat.ENCODING_PCM_16BIT
|
||||
)
|
||||
|
||||
val newAudioTrack = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
val attributes = AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_MEDIA)
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
|
||||
.build()
|
||||
|
||||
val format = AudioFormat.Builder()
|
||||
.setSampleRate(SAMPLE_RATE)
|
||||
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
|
||||
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
|
||||
.build()
|
||||
|
||||
AudioTrack.Builder()
|
||||
.setAudioAttributes(attributes)
|
||||
.setAudioFormat(format)
|
||||
.setBufferSizeInBytes(bufferSize)
|
||||
.build()
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
AudioTrack(
|
||||
AudioManager.STREAM_MUSIC,
|
||||
SAMPLE_RATE,
|
||||
AudioFormat.CHANNEL_OUT_MONO,
|
||||
AudioFormat.ENCODING_PCM_16BIT,
|
||||
bufferSize,
|
||||
AudioTrack.MODE_STATIC
|
||||
)
|
||||
}
|
||||
|
||||
newAudioTrack.apply {
|
||||
setVolume(0.8f)
|
||||
|
||||
// Используем MODE_STATIC для однократного воспроизведения
|
||||
write(buffer, 0, buffer.size)
|
||||
|
||||
setPlaybackPositionUpdateListener(object : AudioTrack.OnPlaybackPositionUpdateListener {
|
||||
override fun onMarkerReached(track: AudioTrack) {
|
||||
// Автоматически освобождаем после воспроизведения
|
||||
track.stop()
|
||||
track.release()
|
||||
}
|
||||
|
||||
override fun onPeriodicNotification(track: AudioTrack) {}
|
||||
})
|
||||
|
||||
setNotificationMarkerPosition(buffer.size)
|
||||
play()
|
||||
}
|
||||
|
||||
audioTrack = newAudioTrack
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
isPlaying = false
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
isPlaying = false
|
||||
currentThread?.interrupt()
|
||||
currentThread = null
|
||||
|
||||
audioTrack?.let { track ->
|
||||
try {
|
||||
if (track.playState == AudioTrack.PLAYSTATE_PLAYING) {
|
||||
track.stop()
|
||||
}
|
||||
track.release()
|
||||
} catch (e: IllegalStateException) {
|
||||
// Игнорируем ошибки при остановке уже остановленного трека
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
audioTrack = null
|
||||
}
|
||||
|
||||
fun release() {
|
||||
stop()
|
||||
}
|
||||
}
|
@ -1,199 +0,0 @@
|
||||
package net.sergeych.karabass
|
||||
|
||||
import android.content.Context
|
||||
import android.media.AudioAttributes
|
||||
import android.media.AudioFormat
|
||||
import android.media.AudioManager
|
||||
import android.media.AudioTrack
|
||||
import android.os.Build
|
||||
import kotlin.math.*
|
||||
import kotlin.random.Random
|
||||
|
||||
class TubaSynth(private val context: Context) {
|
||||
|
||||
companion object {
|
||||
const val SAMPLE_RATE = 44100
|
||||
const val CUTOFF_FREQ = 500.0
|
||||
}
|
||||
|
||||
private var audioTrack: AudioTrack? = null
|
||||
private var isPlaying = false
|
||||
|
||||
data class TubaNote(
|
||||
val frequency: Float,
|
||||
val amplitude: Float = 0.8f,
|
||||
val durationMs: Int
|
||||
)
|
||||
|
||||
fun playTubaNote(frequency: Float, durationMs: Int = 2000) {
|
||||
if (isPlaying) {
|
||||
stop()
|
||||
}
|
||||
|
||||
val note = TubaNote(frequency, 0.8f, durationMs)
|
||||
generateTubaSound(note)
|
||||
}
|
||||
|
||||
private fun generateTubaSound(note: TubaNote) {
|
||||
Thread {
|
||||
isPlaying = true
|
||||
val numSamples = note.durationMs * SAMPLE_RATE / 1000
|
||||
val buffer = ShortArray(numSamples)
|
||||
|
||||
// Фильтр для шума дыхания
|
||||
val alpha = 1 - exp(-2.0 * PI * CUTOFF_FREQ / SAMPLE_RATE)
|
||||
var filteredNoise = 0.0
|
||||
|
||||
val random = Random(System.currentTimeMillis())
|
||||
|
||||
for (i in 0 until numSamples) {
|
||||
if (!isPlaying) break
|
||||
|
||||
val time = i.toDouble() / SAMPLE_RATE
|
||||
|
||||
// Основной waveform тубы
|
||||
val tubaWave = calculateTubaWaveform(note.frequency, time)
|
||||
|
||||
// Шум дыхания с фильтром низких частот
|
||||
val whiteNoise = random.nextDouble() * 2 - 1
|
||||
filteredNoise += alpha * (whiteNoise - filteredNoise)
|
||||
|
||||
// Огибающая
|
||||
val envelope = getTubaEnvelope(i, numSamples)
|
||||
|
||||
// Усиление басов
|
||||
val bassBoost = 1.0 + 0.4 * sin(2 * PI * 80.0 * time)
|
||||
|
||||
// Смешиваем всё вместе
|
||||
var sample = tubaWave + filteredNoise * 0.05 * envelope
|
||||
sample *= envelope * bassBoost
|
||||
|
||||
// Ограничиваем амплитуду
|
||||
sample = sample.coerceIn(-1.0, 1.0)
|
||||
|
||||
buffer[i] = (sample * Short.MAX_VALUE).toInt().toShort()
|
||||
}
|
||||
|
||||
if (isPlaying) {
|
||||
playAudioBuffer(buffer)
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
private fun calculateTubaWaveform(freq: Float, time: Double): Double {
|
||||
return (
|
||||
sin(2 * PI * freq * time) * 0.6 +
|
||||
sin(2 * PI * freq * 2 * time) * 0.4 +
|
||||
sin(2 * PI * freq * 3 * time) * 0.3 +
|
||||
sin(2 * PI * freq * 4 * time) * 0.2 +
|
||||
sin(2 * PI * freq * 5 * time) * 0.15 +
|
||||
sin(2 * PI * freq * 6 * time) * 0.1 +
|
||||
sin(2 * PI * freq * 7 * time) * 0.05
|
||||
) * 0.6
|
||||
}
|
||||
|
||||
private fun getTubaEnvelope(sampleIndex: Int, totalSamples: Int): Double {
|
||||
val position = sampleIndex.toDouble() / totalSamples
|
||||
|
||||
val attack = 0.15
|
||||
val decay = 0.1
|
||||
val release = 0.3
|
||||
|
||||
return when {
|
||||
position < attack -> {
|
||||
val x = position / attack
|
||||
x * x * (3 - 2 * x)
|
||||
}
|
||||
position < attack + decay -> {
|
||||
val decayPos = (position - attack) / decay
|
||||
0.9 + 0.1 * (1 - decayPos)
|
||||
}
|
||||
position > 1 - release -> {
|
||||
val releasePos = (position - (1 - release)) / release
|
||||
(1 - releasePos) * 0.9
|
||||
}
|
||||
else -> 0.9
|
||||
}
|
||||
}
|
||||
|
||||
private fun playAudioBuffer(buffer: ShortArray) {
|
||||
try {
|
||||
// Останавливаем предыдущее воспроизведение
|
||||
audioTrack?.stop()
|
||||
audioTrack?.release()
|
||||
|
||||
val bufferSize = AudioTrack.getMinBufferSize(
|
||||
SAMPLE_RATE,
|
||||
AudioFormat.CHANNEL_OUT_MONO,
|
||||
AudioFormat.ENCODING_PCM_16BIT
|
||||
)
|
||||
|
||||
// Создаем AudioTrack с правильными параметрами
|
||||
audioTrack = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
val attributes = AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_MEDIA)
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
|
||||
.build()
|
||||
|
||||
val format = AudioFormat.Builder()
|
||||
.setSampleRate(SAMPLE_RATE)
|
||||
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
|
||||
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
|
||||
.build()
|
||||
|
||||
AudioTrack.Builder()
|
||||
.setAudioAttributes(attributes)
|
||||
.setAudioFormat(format)
|
||||
.setBufferSizeInBytes(bufferSize)
|
||||
.build()
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
AudioTrack(
|
||||
AudioManager.STREAM_MUSIC,
|
||||
SAMPLE_RATE,
|
||||
AudioFormat.CHANNEL_OUT_MONO,
|
||||
AudioFormat.ENCODING_PCM_16BIT,
|
||||
bufferSize,
|
||||
AudioTrack.MODE_STREAM
|
||||
)
|
||||
}
|
||||
|
||||
audioTrack?.apply {
|
||||
// Устанавливаем громкость
|
||||
setVolume(0.8f)
|
||||
|
||||
// Воспроизводим
|
||||
play()
|
||||
|
||||
// Пишем данные
|
||||
write(buffer, 0, buffer.size)
|
||||
|
||||
// Ждем окончания воспроизведения
|
||||
setNotificationMarkerPosition(buffer.size)
|
||||
setPlaybackPositionUpdateListener(object : AudioTrack.OnPlaybackPositionUpdateListener {
|
||||
override fun onMarkerReached(track: AudioTrack) {
|
||||
stop()
|
||||
release()
|
||||
isPlaying = false
|
||||
}
|
||||
|
||||
override fun onPeriodicNotification(track: AudioTrack) {}
|
||||
})
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
isPlaying = false
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
isPlaying = false
|
||||
audioTrack?.stop()
|
||||
}
|
||||
|
||||
fun release() {
|
||||
stop()
|
||||
audioTrack?.release()
|
||||
audioTrack = null
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user