Сбой приложения Android Studio при запуске потока

Я хотел бы начать с отказа от ответственности, что я очень новичок в Android Studio и Java Script, и программирование даже не является моей сильной стороной. Тем не менее, я пытаюсь разработать приложение для синтезатора звука, и, хотя я могу заставить все работать, я не могу заставить звук транслироваться. По сути, я могу использовать AudioTrack в статическом режиме для воспроизведения рассчитанных звуковых волн продолжительностью до 10 секунд, однако я не могу динамически воспроизводить несколько нажатий клавиш (в приложении я настроил 25-клавишную клавиатуру с 2 октавами), и я не могу удерживать вниз ключ в течение длительного времени либо. У меня есть ползунок длины, который регулирует продолжительность воспроизводимой ноты от 0,1 до 10 секунд. Я хотел бы иметь возможность нажимать несколько клавиш одновременно, а также удерживать клавишу в течение длительного периода времени и чтобы приложение непрерывно выводило звук. Я провел некоторое исследование, и кажется, что лучший способ добиться этого — использовать потоковый режим библиотеки AudioTrack и использовать потоки? Я очень не знаком ни с библиотекой, ни с потоками, ни даже с языком.

Я следовал руководству по потоковой передаче звука, предоставленному здесь, за исключением того, что я пробовал немного другой подход, который, как мне кажется, лучше сработает в моем случае. Я разместил свой тестовый код ниже (обратите внимание, что это не весь мой код для воспроизведения разных звуков и тому подобного, а вместо этого только самый минимум с одной настройкой клавиши (c4), и я пытаюсь заставить его непрерывно воспроизводить звук. Когда я впервые нажимаю клавишу, он воспроизводит звук до тех пор, пока я удерживаю клавишу. Однако после того, как я отпускаю клавишу и нажимаю ее снова, приложение просто вылетает. Я не совсем уверен, почему это Я пробовал разные вещи, такие как вызов функции audioThread.start() только один раз или попытку остановить поток, но безуспешно. Если бы я вызвал функцию audioThread.start() только один раз, приложение не вылетает, когда я снова нажимаю клавишу, но он также не воспроизводит звук. Когда он воспроизводит звук, он вылетает из приложения, если я нажимаю клавишу второй раз.

Я также полностью описал свою проблему выше, потому что я не уверен, что использование потоков - лучший способ сделать это. В идеале хотелось бы, чтобы я мог нажимать любую из 25 клавиш на клавиатуре, а аудиотека стримила бы звуки для одновременно нажатых клавиш, а не выводила ничего, если ничего не нажималось. Мне не нравится идея запуска 25 разных потоков, потому что мой звуковой буфер настраивается в каждом потоке, и я думаю, что предыдущий буфер будет перезаписан, если я воспроизведу несколько нот одновременно? Пожалуйста, дайте мне знать, если вы думаете, что я иду в неправильном направлении, используя потоки для приложения, к которому я стремлюсь. Я думал, что потоки будут лучше, потому что мне нужно выполнить много вычислений перед потоковой передачей аудио (макс. 25 x 6 x 3 = 450 триггерных операций для расчета одного образца моей формы волны), и поэтому я подумал, что каким-то образом конвейеризация вычислений позволит будь лучше?

В любом случае, пожалуйста, дайте мне знать, почему я не могу заставить тестовую установку воспроизводить звук, когда я нажимаю клавишу во второй раз? Я также использую Lenovo tabM10 FHD Plus, если это актуальная информация. Это также мой первый раз, когда я использую stackOverflow, поэтому я извиняюсь, если я проделал ужасную работу по форматированию своего сообщения. Если вы считаете, что я должен отредактировать свой пост, чтобы быть более кратким / ясным в отношении моей проблемы, сообщите мне, и я это исправлю. Я заранее приношу извинения за неудобства.

Мой тестовый код:

package com.example.ece496_simplesynth;

import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.view.MotionEventCompat;

import android.graphics.Color;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.Build;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;

public class MainActivity extends AppCompatActivity {

    private AudioTrack audioTrack;
    private int intBufferSize;
    private short[] shortAudioData;
    private boolean isActive = false;

    private Thread audioThread;

    private boolean keyPress = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final View c4 = (View) findViewById(R.id.c4);

        c4.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                int action = motionEvent.getAction();//.getActionMasked(motionEvent);
                if (action == MotionEvent.ACTION_DOWN) {
                    c4.setBackgroundColor(Color.parseColor("#ed1b24")); //"#81DAF5"
                    keyPress = true;
                    audioThread.start();
                }

                else if (action == MotionEvent.ACTION_UP) {
                    c4.setBackgroundColor(Color.parseColor("#FFFFFF"));
                    keyPress = false;
                    //audioThread.stop();
                }
                return true;
            }
        });

        audioThread = new Thread(new Runnable() {
            @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
            @Override
            public void run() {
                threadLoop();
            }
        });


    }
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void threadLoop(){
        int intSampleRate = AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC);
        intBufferSize = AudioTrack.getMinBufferSize(intSampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
        shortAudioData = new short[intBufferSize];

        audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
                intSampleRate,
                AudioFormat.CHANNEL_OUT_MONO,
                AudioFormat.ENCODING_PCM_16BIT,
                intBufferSize,
                AudioTrack.MODE_STREAM);

        audioTrack.setPlaybackRate(intSampleRate);



        int n = 0;
        float x;
        float TS = 1.0f/intSampleRate;
        float freq = 440.0f;
        float PI = 3.14159f;
        float omega = 2.0f*PI*freq*TS;

        if(keyPress){
            audioTrack.play();
        }
        while(keyPress){

            for(int i = 0; i < shortAudioData.length; ++i){
                if(n == Integer.MAX_VALUE | n < 0){
                    n = 0;
                }
                x = (float)(Math.sin((float)(omega*n)));
                ++n;
                shortAudioData[i] = (short)(x * Short.MAX_VALUE);
            }

            audioTrack.write(shortAudioData, 0, shortAudioData.length);
        }

        audioTrack.stop();


    }
}


person Miraj Shah    schedule 11.03.2021    source источник
comment
Обратите внимание, что у вас есть один экземпляр следующих переменных intBufferSize, shortAudioData, audioTrack, поэтому, когда вы нажимаете одну клавишу, он запускает цикл потока и, например, устанавливает дорожку autio, но когда вы нажимаете следующую клавишу, он запускает другой цикл потока. и перезаписать предыдущую звуковую дорожку во время ее воспроизведения. Рассмотрите возможность перемещения ранее упомянутых переменных внутри метода threadLoop.   -  person sorifiend    schedule 11.03.2021
comment
Кроме того, у вас есть один audioThread, но каждое нажатие клавиши пытается снова запустить поток, это не сработает так, как вы предполагали. Вам либо нужно создавать новый поток каждый раз, когда нажимается клавиша, либо вам нужно использовать один поток для управления списком звуков/клавиш для воспроизведения и т. д.   -  person sorifiend    schedule 11.03.2021
comment
Я думаю, вы правы. Первоначально я планировал иметь вектор, содержащий список всех нажатых в данный момент клавиш, и поток вычислял бы единый буфер, перебирая все нажатые клавиши и суммируя вместе сигналы различных частот. Однако я не совсем уверен, как создать отдельный поток для расчета материала и передать его в audioTrack. Я попытался запустить поток отдельно, когда впервые создал его, добавив .start() после нового потока (Runable...), но это не сработало. Должен ли я создавать поток вне onCreate?   -  person Miraj Shah    schedule 11.03.2021
comment
Я не знаю, как работает audioManager, но должен быть другой способ поставить в очередь другой звук для воспроизведения без необходимости возиться с потоками. Однако, основываясь на вашем коде и учебнике, тогда да, вам нужно будет создать новый поток для каждого нажатия клавиши. Учебник, которому вы следовали, очень плохой, если вы хотите использовать несколько звуков, я предлагаю поискать лучший учебник, который позволяет использовать несколько звуков или звуковых дорожек одновременно. Это может быть полезно: stackoverflow.com/questions/2627101/   -  person sorifiend    schedule 11.03.2021


Ответы (1)


Я написал что-то похожее на ваш проект, используя чистую Java. Я думаю, что есть несколько рабочих планов. Путь, который я выбрал, заключался в том, чтобы запуск клавиатуры (действия нажатия/отпускания клавиши) использовал слабую связь с предварительно выделенным банком синтезаторов. Методы синтезаторов, которые производят PCM, и микшер, объединяющий выходные данные синтезаторов, находились в одном выделенном аудиопотоке. С Java я использовал SourceDataLine для вывода. В Android вы можете транслировать необработанный PCM, используя AudioTrack.

Количество одновременных заметок, которые вы можете поддерживать, вероятно, будет ограничено сложностью вычислений, используемых вашим алгоритмом синтеза. Я думаю, что общий план состоит в том, чтобы работа выполнялась небольшими блоками, например, длиной в несколько миллисекунд. В аудиопотоке вычисляется следующий массив PCM в каждом синтезаторе, который в данный момент активен с нотой, затем на этапе микширования аудио содержимое каждого массива добавляется в один массив, который записывается через функцию AudioTrack. Один человек, с которым я консультировался, рекомендовал размер буфера 256 кадров PCM, как оптимальное место для получения высокой пропускной способности при минимальной задержке, но я не знаю и не умею определить, так ли это и будет ли это работать в среде Android.

Код, который отслеживает нажатия отдельных клавиш, должен также отслеживать, какому синтезатору была назначена задача создания ноты, чтобы он мог отправить команду отпускания нужному синтезатору, когда произойдет отпускание клавиши.

В качестве предупреждения я обнаружил, что многие клавиатуры не поддерживают действительно независимые клавиши. Например, моя система не зарегистрирует новое нажатие клавиши, если будет нажато некоторое количество соседних клавиш, например: если у вас уже отключен QWR, ваша клавиатура может быть не в состоянии зарегистрировать дополнительное нажатие клавиши E. Я не исследовал детали этого недостатка, и просто примирился со степенью непредсказуемости.

Я не пробовал делать это с сенсорным экраном. Кнопки клавиатуры, которые я сделал, реагируют на щелчки мыши, но на сенсорном экране есть только одна мышь, а не несколько пальцев.

Это интересный проект, и вы узнаете много нового об управлении потоками и ресурсами.

person Phil Freihofner    schedule 11.03.2021