From a78093c6b8337bd0a387ad8f15385273af29afaa Mon Sep 17 00:00:00 2001 From: sergeych Date: Mon, 13 Oct 2025 15:15:57 +0400 Subject: [PATCH] fixed segfault bug --- .gitignore | 1 + .../sergeych/karabass/AdvancedTubaSynth.kt | 123 ++++++++++-------- gradle/libs.versions.toml | 2 +- 3 files changed, 72 insertions(+), 54 deletions(-) diff --git a/.gitignore b/.gitignore index 071fe3c..0cd7fdf 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ .externalNativeBuild .cxx local.properties +/app/release/ diff --git a/app/src/main/java/net/sergeych/karabass/AdvancedTubaSynth.kt b/app/src/main/java/net/sergeych/karabass/AdvancedTubaSynth.kt index 1b31cf1..eb50aed 100644 --- a/app/src/main/java/net/sergeych/karabass/AdvancedTubaSynth.kt +++ b/app/src/main/java/net/sergeych/karabass/AdvancedTubaSynth.kt @@ -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 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 616ab0f..28c2e61 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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"