【问题标题】:Android Studio App crash when running a thread运行线程时Android Studio App崩溃
【发布时间】:2021-03-11 03:01:54
【问题描述】:

我想先声明一下,我对 Android Studio 和 Java Script 非常陌生,而且编程甚至不是我的强项。但是,我正在尝试开发一个音频合成器应用程序,虽然我可以让一切正常工作,但我无法让音频流式传输。本质上,我可以在静态模式下使用 AudioTrack 播放计算出的声音波形长达 10 秒,但是,我无法动态播放多个按键(我在应用程序上设置了 2 个八度音阶 25 键键盘),我无法按住长按一个键也不行。我有一个长度滑块,可以将正在播放的音符的持续时间从 0.1 秒调整到 10 秒。我希望能够一次按下多个键,以及长时间按住一个键并让应用程序连续输出声音。我做了一些研究,似乎实现这一目标的最佳方法是使用 AudioTrack 库的“流”模式,并使用线程?我对库、线程甚至语言都非常陌生。

我正在关注here 提供的音频流教程,但我尝试了一种稍微不同的方法,我相信这种方法在我的情况下会更好。我在下面发布了我的测试代码(请注意,这不是我播放不同声音等的所有代码,而是只有一个键设置(c4)的最低限度,我试图让它连续播放声音。当我第一次按下键时,只要按住键,它就会播放声音。但是,当我松开键并再次按下它后,应用程序就崩溃了。我不太清楚为什么会这样正在发生。我尝试了不同的方法,例如只调用一次“audioThread.start()”函数,或者尝试停止线程,但我没有成功。如果我只调用了一次“audioThread.start()”函数,当我再次按下键时应用程序不会崩溃,但它也不会播放声音。当它播放声音时,如果我再次按下键它会崩溃应用程序。

我还给出了上述问题的全部范围,因为我不确定使用线程是否是解决此问题的最佳方法。理想情况下,我希望它可以按下键盘上的 25 个键中的任何一个,并且音频库会流式传输同时按下的键的声音,如果没有按下任何内容,则不会输出任何内容。我不喜欢运行 25 个不同线程的想法,因为我的音频缓冲区是在每个线程中设置的,我认为如果我一次播放多个音符会覆盖以前的缓冲区?如果您认为我在针对我的目标应用程序使用线程时朝着错误的方向前进,请告诉我。我认为线程会更好,因为我必须在流式传输音频之前进行大量计算(最大,25 x 6 x 3 = 450 次触发操作来计算我的波形的一个样本),所以我认为以某种方式流水线化计算会会更好吗?

无论哪种方式,请告诉我为什么我无法在下面的测试设置中播放第二次按键时的声音?如果这是相关信息,我也在使用联想 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();


    }
}

【问题讨论】:

  • 请注意,您有以下变量的单个实例intBufferSizeshortAudioDataaudioTrack,因此当您按下一个键时,它将启动线程循环并设置自动轨道,但是当您按下下一个键时,它将启动另一个线程循环并在它仍在播放时覆盖上一个音轨。考虑在threadLoop 方法中移动前面提到的变量。
  • 另外,你有一个audioThread,但每次按键都会尝试重新启动线程,这不会按照你的意愿工作。您需要在每次按下键时创建一个全新的线程,或者您需要使用单个线程来管理要播放的声音/键列表等。
  • 我认为你是对的。最初,我计划有一个向量,其中包含当前按下的所有键的列表,线程将通过迭代所有按下的键并将不同频率的波形相加来计算单个缓冲区。但是,我不太确定如何创建一个单独的线程来计算内容,并将其传递给 audioTrack。当我第一次创建线程时,我尝试通过在“新线程(可运行...)”之后添加“.start()”来单独启动线程,但这不起作用。我必须在 onCreate 之外创建线程吗?
  • 我不知道音频 AudioManager 是如何工作的,但应该有另一种方法来排队播放另一个声音,而无需搞乱线程。但是,根据您的代码和教程,是的,您需要为每次按键创建一个新线程。如果您想使用多个声音,您遵循的教程是一个非常糟糕的教程,我建议寻找一个更好的教程,它允许同时使用多个声音或音轨。这可能会有所帮助:stackoverflow.com/questions/2627101/…

标签: java multithreading android-studio audio audiotrack


【解决方案1】:

我使用纯 Java 编写了 something similar to your project。我认为有一些可行的计划。我采取的方法是让键盘触发(按键/按键释放操作)采用松散耦合到预先分配的合成器库。产生 PCM 的合成器方法和组合合成器输出的混音器驻留在一个专用的“音频线程”上。对于 Java,我使用 SourceDataLine 进行输出。在 Android 中,您可以使用 AudioTrack 流式传输原始 PCM。

您可以支持的并发音符数量可能会受到合成算法所采用的计算复杂性的限制。我认为一个常见的计划是在小块中完成工作,例如几毫秒的长度。在“音频线程”中,计算每个合成器中当前使用音符激活的下一个 PCM 数组,然后“音频混合”步骤将每个数组的内容添加到通过AudioTrack 写入的单个数组中。我咨询过的一个人建议使用 256 帧 PCM 的缓冲区大小,作为以最小延迟获得高吞吐量的最佳选择,但我无法确定这是否属实,或者它是否适用于 Android 环境。

跟踪单个按键的按键操作的代码还必须跟踪哪个合成器被分配了产生音符的任务,以便在按键释放发生时它可以向正确的合成器发送“释放”命令。

提醒一下,我发现许多键盘不支持真正独立的按键。例如,如果按下了一些相邻的键,我的系统将不会注册新的按键,例如:如果您已经关闭 QWR,您的键盘可能无法注册 E 的附加按键。我没有研究这个亏空的细节,只是让自己接受了某种程度的不可预测性。

我还没试过用触摸屏来做这件事。我制作的键盘“按钮”响应鼠标点击,但触摸屏上只有一个鼠标,而不是多个手指。

这是一个有趣的项目,您将学到很多关于管理线程和资源的知识。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-04
    • 2015-05-03
    • 2021-02-14
    相关资源
    最近更新 更多