fixed segfault bug

This commit is contained in:
Sergey Chernov 2025-10-13 15:15:57 +04:00
parent 37ec0f7a6d
commit a78093c6b8
3 changed files with 72 additions and 54 deletions

1
.gitignore vendored
View File

@ -9,3 +9,4 @@
.externalNativeBuild
.cxx
local.properties
/app/release/

View File

@ -1,5 +1,4 @@
package net.sergeych.karabass
import android.content.Context
import android.media.AudioAttributes
import android.media.AudioFormat
@ -40,6 +39,7 @@ class AdvancedTubaSynth(private val context: Context) {
@Volatile private var audioTrack: AudioTrack? = null
@Volatile private var isPlaying = false
@Volatile private var shouldStop = false
@Volatile private var isReleased = false
private var currentThread: Thread? = null
// Синхронизированные переменные для потокобезопасности
@ -55,23 +55,24 @@ class AdvancedTubaSynth(private val context: Context) {
private var envelopeState = 0 // 0: idle, 1: attack, 2: release
// Параметры огибающей
private val attackSamples = (0.02 * SAMPLE_RATE).toInt() // Увеличил до 20ms для более плавного старта
private val releaseSamples = (0.2 * SAMPLE_RATE).toInt() // 200ms релиз
private val attackSamples = (0.02 * SAMPLE_RATE).toInt()
private val releaseSamples = (0.2 * SAMPLE_RATE).toInt()
// Минимальный буфер для low-latency
private val bufferSize = 1024 // Увеличил буфер для стабильности
private val bufferSize = 1024
// Для управления выводом
private var lastBufferWritten = false
private var silenceBuffersWritten = 0
private val requiredSilenceBuffers = 3 // Дополнительные буферы тишины после затухания
private val requiredSilenceBuffers = 3
init {
initializeAudioTrack()
}
private fun initializeAudioTrack() {
private fun initializeAudioTrack(): Boolean {
synchronized(lock) {
if (isReleased) return false
try {
// Освобождаем старый AudioTrack если есть
audioTrack?.let { track ->
@ -81,11 +82,11 @@ class AdvancedTubaSynth(private val context: Context) {
}
track.release()
} catch (e: Exception) {
e.printStackTrace()
// Игнорируем ошибки при освобождении
}
}
audioTrack = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val newTrack = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val attributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
@ -100,7 +101,7 @@ class AdvancedTubaSynth(private val context: Context) {
AudioTrack.Builder()
.setAudioAttributes(attributes)
.setAudioFormat(format)
.setBufferSizeInBytes(bufferSize * 4) // Увеличил размер буфера
.setBufferSizeInBytes(bufferSize * 4)
.build()
} else {
@Suppress("DEPRECATION")
@ -109,22 +110,29 @@ class AdvancedTubaSynth(private val context: Context) {
SAMPLE_RATE,
AudioFormat.CHANNEL_OUT_MONO,
AudioFormat.ENCODING_PCM_16BIT,
bufferSize * 4, // Увеличил размер буфера
bufferSize * 4,
AudioTrack.MODE_STREAM
)
}
audioTrack?.play()
if (newTrack.state == AudioTrack.STATE_INITIALIZED) {
audioTrack = newTrack
newTrack.play()
return true
} else {
newTrack.release()
return false
}
} catch (e: Exception) {
e.printStackTrace()
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
initializeAudioTrack()
}, 100)
return false
}
}
}
fun startNote(frequency: Float) {
if (isReleased) return
synchronized(lock) {
targetFrequency = frequency
@ -135,7 +143,6 @@ class AdvancedTubaSynth(private val context: Context) {
envelopeSamples = 0
envelopeValue = 0.0
currentFrequency = frequency
lastBufferWritten = false
silenceBuffersWritten = 0
// Сбрасываем фазу при начале новой ноты для предотвращения щелчков
@ -152,6 +159,8 @@ class AdvancedTubaSynth(private val context: Context) {
}
fun stopNote() {
if (isReleased) return
synchronized(lock) {
if (isNoteOn) {
isNoteOn = false
@ -162,29 +171,39 @@ class AdvancedTubaSynth(private val context: Context) {
}
fun changeNote(frequency: Float) {
if (isReleased) return
synchronized(lock) {
targetFrequency = frequency
}
}
private fun startAudioGeneration() {
if (isPlaying) return
if (isPlaying || isReleased) return
shouldStop = false
isPlaying = true
lastBufferWritten = false
silenceBuffersWritten = 0
// Проверяем и при необходимости пересоздаем AudioTrack
val track = audioTrack
if (track == null || track.state != AudioTrack.STATE_INITIALIZED) {
initializeAudioTrack()
if (!initializeAudioTrack()) {
isPlaying = false
return
}
}
currentThread = Thread {
val buffer = ShortArray(bufferSize)
var currentAudioTrack: AudioTrack? = null
try {
while (!shouldStop && isPlaying) {
// Получаем ссылку на AudioTrack один раз в начале
synchronized(lock) {
currentAudioTrack = audioTrack
}
while (!shouldStop && isPlaying && !isReleased) {
// Заполняем буфер
var allSamplesZero = true
for (i in buffer.indices) {
@ -196,7 +215,7 @@ class AdvancedTubaSynth(private val context: Context) {
}
// Безопасная запись в AudioTrack
val track = audioTrack
val track = currentAudioTrack
if (track?.state == AudioTrack.STATE_INITIALIZED && track.playState == AudioTrack.PLAYSTATE_PLAYING) {
try {
track.write(buffer, 0, buffer.size)
@ -204,18 +223,29 @@ class AdvancedTubaSynth(private val context: Context) {
// Если это последний буфер с данными, запоминаем это
if (allSamplesZero && envelopeState == 0) {
silenceBuffersWritten++
if (silenceBuffersWritten >= requiredSilenceBuffers) {
lastBufferWritten = true
}
} else {
silenceBuffersWritten = 0
lastBufferWritten = false
}
} catch (e: Exception) {
if (!shouldStop) {
e.printStackTrace()
android.os.Handler(android.os.Looper.getMainLooper()).post {
initializeAudioTrack()
if (!shouldStop && !isReleased) {
// Пытаемся восстановить AudioTrack
synchronized(lock) {
if (initializeAudioTrack()) {
currentAudioTrack = audioTrack
} else {
break
}
}
}
}
} else {
// AudioTrack не готов
if (!shouldStop && !isReleased) {
synchronized(lock) {
if (initializeAudioTrack()) {
currentAudioTrack = audioTrack
} else {
break
}
}
}
@ -225,12 +255,11 @@ class AdvancedTubaSynth(private val context: Context) {
// 1. Огибающая завершила релиз
// 2. Мы записали несколько буферов тишины для гарантии
// 3. Нет активной ноты
if (lastBufferWritten) {
if (silenceBuffersWritten >= requiredSilenceBuffers) {
synchronized(lock) {
if (!isNoteOn && envelopeState == 0) {
// Даем дополнительное время на вывод последних буферов
Thread.sleep(50)
isPlaying = false
break
}
}
}
@ -247,6 +276,8 @@ class AdvancedTubaSynth(private val context: Context) {
private fun generateSample(): Double {
synchronized(lock) {
if (isReleased) return 0.0
// Плавное изменение частоты для легато
if (abs(currentFrequency - targetFrequency) > 0.1) {
currentFrequency += (targetFrequency - currentFrequency) * 0.2f
@ -318,21 +349,13 @@ class AdvancedTubaSynth(private val context: Context) {
// Смешиваем с весами, характерными для тубы
val mixed = fundamental * 0.7 + overtone2 * 0.5 + overtone3 * 0.3 + overtone4 * 0.2 + overtone5 * 0.15
val result = tanh(mixed)
// Очень мягкое ограничение на резкие переходы
return if (abs(result) > 0.8) {
result * 0.99 // Слегка уменьшаем пики
} else {
result
}
// Очень мягкое ограничение для теплого звука
// return tanh(mixed)
return tanh(mixed)
}
fun isActive(): Boolean {
synchronized(lock) {
return isNoteOn || envelopeState != 0
return !isReleased && (isNoteOn || envelopeState != 0)
}
}
@ -343,20 +366,14 @@ class AdvancedTubaSynth(private val context: Context) {
}
fun release() {
isReleased = true
shouldStop = true
isPlaying = false
// Даем время на завершение релиза и вывод всех буферов
try {
Thread.sleep(300)
} catch (e: InterruptedException) {
// Игнорируем
}
// Безопасная остановка потока
// Останавливаем поток генерации
currentThread?.let { thread ->
try {
thread.join(200)
thread.join(500) // Увеличил время ожидания
} catch (e: InterruptedException) {
thread.interrupt()
}
@ -375,7 +392,7 @@ class AdvancedTubaSynth(private val context: Context) {
}
track.release()
} catch (e: Exception) {
e.printStackTrace()
// Игнорируем ошибки при освобождении
}
}
audioTrack = null

View File

@ -1,6 +1,6 @@
[versions]
agp = "8.13.0"
kotlin = "2.0.21"
kotlin = "2.2.20"
coreKtx = "1.10.1"
junit = "4.13.2"
junitVersion = "1.1.5"