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