Best way to use Androids Audiotrack - android

I'm implementing an Android app to handle PCM sound data (for long sounds i.e. music tracks).
I decided to use the AudioTrack class to play the music, but it can't be placed in the same thread as the activity, because is blocking the whole app - and here is the question - should I put AudioTrack operations in a separate thread or in an async task (or any other option)? What would be the best way?
I need to play / pause / stop and change music files.
Now I'm trying to manage it by a java thread and its almost ok - playing is ok, pausing almost (its "dropping" a part of the sound), but when I'm changing the music file, the previous isn't stopping and theres a mixed output (I think theres something wrong with my thread implementation).
And - no, I can't use the MediaPlayer in this app (I want to modify the PCM data on the fly).
I was searching for some help in google and here on stackoverflow, but nothing helped me.
Here is my current implementation, if somebody want to take a look.
public class AudioPlayerManager {
private AudioTrack track;
private Thread thread;
private AudioTrackThread trackThread = new AudioTrackThread();
public int getPlayState() {
return track.getPlayState();
}
public int getAudioSession() {
return track.getAudioSessionId();
}
class AudioTrackThread implements Runnable {
private String filePath = "/music/wav/musicfile.wav";
#Override
public void run() {
try {
playWaveFile();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
public void playWaveFile() throws FileNotFoundException {
int minSize = AudioTrack.getMinBufferSize(44100,
AudioFormat.CHANNEL_OUT_STEREO,
AudioFormat.ENCODING_PCM_16BIT);
track = new AudioTrack(AudioManager.STREAM_MUSIC, 44100,
AudioFormat.CHANNEL_OUT_STEREO,
AudioFormat.ENCODING_PCM_16BIT, minSize,
AudioTrack.MODE_STREAM);
File file = new File(FileListingUtils.getExternalStorageRootFile(),
filePath);
InputStream is = new FileInputStream(file);
byte[] music = new byte[512];
track.play();
try {
handleSound(is, music);
} catch (IOException e) {
e.printStackTrace();
}
track.stop();
track.release();
}
private void handleSound(InputStream is, byte[] music)
throws IOException {
int i;
while ((i = is.read(music)) != -1) {
track.write(music, 0, i);
}
}
}
public void playNew() {
if (track != null) {
track.stop();
//track.flush();
track.release();
}
thread = new Thread(trackThread);
thread.start();
}
public void playOrResume() {
this.track.play();
}
public void pause() {
this.track.pause();
}
}
Thanks for every help!
M.

Your thread life cycle doesnt seem to be right. Firstly You will have to close(interrupt)your current thread every time playback is stopped or finished and create a new instance of the thread and close the old thread.
Secondly, you are calling track.stop()from outside your thread. You must implement a public stop method inside your thread and then insert finalisation code there and then call yourthread.stop() in your playNew() method.
so here is the pseudocode:
public void playNew() {
//the following if clause should go to thread's stop() method.
// if (track != null) {
// track.stop();
//track.flush();
// track.release();
// }
//create a new thread if its null
if(thread ==null){
thread = new Thread(new AudioTrackThread());
thread.start();}
//stop the current thread
else{
thread.stop();
thread=null;}
}
about your implementation, somewhere i read that to play music, a service and a thread will be a better option.Read about services and threads in android documentation.

Related

How to stop worker thread in Android

I found and use some method bellow but it is not work for me:
myThread.stop() //it is not safe but I am tried that
myThread.interupt
Here is my program: I wanna play video using Videoview when video finish. If user no choose the next video in 120s then my app will finish.
My video view code:
Uri uri = Uri.parse(filePath);
videoView = findViewById(R.id.videoView);
videoView.setVideoURI(uri);
waitingThread w8 = new waitingThread();
//set params video before play
videoView.setOnPreparedListener(mediaPlayer -> {
PlaybackParams playbackParams = new PlaybackParams();
playbackParams.setSpeed(DeviceConfig.getInstance().getVideoPeed());// 1.25 1.5 2 2.5
mediaPlayer.setPlaybackParams(playbackParams);
// I am tryied using stop thread here
// w8.stop()
// or w8.interrupt();
videoView.start();
});
videoView.setOnErrorListener((mediaPlayer, i, i1) -> {
Log.d(TAG,"Video Error");
//send error message to server
return false;
});
//I call thread when video complete
videoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mediaPlayer) {
**waiting();** -> w8.start //edited i start thread here
}
});
My thread waiting
private class waitingThread extends Thread{
public void run() {
try {
while (!isInterrupted()) {
timeCount ++;
Thread.sleep(1000);
Log.d(TAG, "Time count : " + timeCount);
if(timeCount == 120){
finish();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//i am try to using this funtion but still not work too.
public void stopping(){
Thread.currentThread().interrupt();
// timeCount = 0;
// Log.d(TAG, "Stopping, reset time :" + timeCount);
}
}
Brief my idea: When video start play, thread waiting will be stopped. When video finish program will waiting a time if no video chose in time to wait program will finish, if any video chose then thread w8 stop.
My problem: when I choose the next video, my thread "w8" still keep running. That is make my app finished while video playing
Plz help me how to fix that problem or any same idea to work are appreciated
You don't want to call interrupt on Thread.currentThread. Thread.currentThread is the thread currently running- it's the thread you're calling the function on. It's not the thread object you just created. Instead it would be this.interrupt(). Or just get rid of the function entirely and call interrupt directly.
Introducing your own boolean variable might help
class waitingThread extends Thread{
boolean stop;
public void run(){
while(!stop){
//your task
}
stop = false; //
}
public void stopping(){
stop= true;
}
}

How to trigger Vibration on Sound Input?

I am trying to create an android application where I filter one specific frequency of a beep and make the phone vibrate.
I am taking input from the MIC of mobile and using MediaRecorder class, by using this class, I can record, save and play the input. Now I need my mobile to vibrate whenever there is a beep/or any sound.
The input is given by a wire to the Headphone jack of the mobile so I know that there is only one frequency being input.
I have a button, Clicking which starts recording.
I have Permissions to vibrate and record in my manifest file already.
record.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
try {
isRecording=true;
myAudioRecorder.prepare();
myAudioRecorder.start();
...
}
I also tried to search the internet and found kind of the similar question here but I am unable to find any correct answer.
However, I can make the phone vibrate on clicking another button and here is the snipt of code,
Vibrator vibrate;
vibrate = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
Btn1.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v) {
vibrate.vibrate(800);
}
}
I tried calling a Vibrator inside recorder.start(); function but this makes the phone vibrate even when there is no sound anymore.
I also tried getting help from this question so whenever there is silence, the phone should not vibrate, but I am getting confused, I somehow understand that there should be a Boolean which gets true when there is sound and make the phone vibrate, but I am unable to put this logic into code.
Please let me know what can I do in this context and which direction should I be searching in?
UPDATE
I found this toturial for showing the progress bar with amplitude of input sound, it works fine and I tried to make the phone vibrate when there is some value in buffer, Now it vibrates even when the amplitude is zero, I guess thats because of the fact that every vibration makes noise which leads the phone to vibrate. I am unable to check the function via TOAST because of java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare(). Is there any suggestion?
For your main problem, maybe you can check for the amplitude of the sound, and only vibrate if a minimum threshold has been reached. Something like this:
private class DetectAmplitude extends AsyncTask<Void, Void, Void> {
private MediaRecorder mRecorder = null;
private final static int MAX_AMPLITUDE = 32768;
//TODO: Investigate what is the ideal value for this parameter
private final static int MINIMUM_REQUIRED_AVERAGE = 5000;
#Override
protected Void doInBackground(Void... params) {
Boolean soundStarted = true;
if (mRecorder == null) {
mRecorder = new MediaRecorder();
mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mRecorder.setOutputFile("/dev/null");
try {
mRecorder.prepare();
} catch (IllegalStateException e) {
soundStarted = false;
Log.e(TAG, "Could not detect background noise. Error preparing recorder: " + e.getMessage());
} catch (IOException e) {
soundStarted = false;
Log.e(TAG, "Could not detect background noise. Error preparing recorder: " + e.getMessage());
}
try {
mRecorder.start();
} catch (RuntimeException e) {
Log.e(TAG, "Could not detect background noise. Error starting recorder: " + e.getMessage());
soundStarted = false;
mRecorder.release();
mRecorder = null;
}
}
if (soundStarted) {
// Compute a simple average of the amplitude over one
// second
int nMeasures = 100;
int sumAmpli = 0;
mRecorder.getMaxAmplitude(); // First call returns 0
int n = 0;
for (int i = 0; i < nMeasures; i++) {
if (mRecorder != null) {
int maxAmpli = mRecorder.getMaxAmplitude();
if (maxAmpli > 0) {
sumAmpli += maxAmpli;
n++;
}
} else {
return null;
}
try {
Thread.sleep(1000 / nMeasures);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
mRecorder.stop();
mRecorder.release();
mRecorder = null;
final float avgAmpli = (float) sumAmpli / n;
if (avgAmpli > MINIMUM_REQUIRED_AVERAGE) {
//TODO: Vibrate the device here
}
}
return null;
}
}
For more information regarding the detection of sound level, please refer to the following:
android: detect sound level
What does Android's getMaxAmplitude() function for the MediaRecorder actually give me?
Regarding the exception java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare(), that is happening because the Toast needs to run on the main thread of your app. If your Thread code (like an AsyncTask) is inside an Activity, you can try the following:
runOnUiThread(new Runnable() {
#Override
public void run() {
//Call your Toast here
}
});
Otherwise, you need to somehow pass the conclusion of your method to the Activity for it to run the Toast.
EDIT:
If you want to use this from a Button, you could set its OnClickListener on your Activity's onCreate() call and execute the AsyncTask there. For example:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.your_layout);
Button button = (Button)findViewById(R.id.your_button_id);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
new DetectAmplitude().execute(new Void[]{});
}
});
}
I suggest you take a look at how AsyncTask works before using this in production code.
You want to sample the audio, and analyze it immediately.
MediaRecorder seems to high level for this, it only captures to file. You probably want to use AudioRecorder instead, as it gives direct access to the input samples.
In order to detect a specific tone, you can use the Goertzel algorithm on the input samples. Here is a C++ implementation I did years ago that could serve as an example.
In order to detect any sound over a certain threshold, you can use Root Mean Square analysis on the input samples and make it trigger once the loudness reaches your threshold. Here is a Python example that reacts to loud noises from a microphone.
Try this:
Btn1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
v.post(new Runnable() {
#Override
public void run() {
vibrate.vibrate(800);
}
});
}
});
You can try this:
Handler handler;
Runnable r;
handler = new Handler();
r = new Runnable() {
public void run() {
Vibrator vib = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
vib.vibrate(500);
handler.postDelayed(r, 1000);
}
};
handler.post(r);

Media Player Class in Android the implementation scenario

for last three weeks I have worked on a Media Player in Android.I am trying to find a solution of how can I make my Media Player to change the song when it's already playing one.
Here is my Listener on the RecyclerView
musicList.addOnItemTouchListener(
new RecyclerItemClickListener(getApplicationContext(), new RecyclerItemClickListener.OnItemClickListener() {
#Override
public void onItemClick(View view, final int position) {
currentPosition = position;
if(!mediaPlayer.isPlaying()){
musicThread.start();
} else {
mediaPlayer.reset();
}
}
})
);
}
and my Thread is this:
final Thread musicThread = new Thread(new Runnable(){
#Override
public void run() {
try {
URL = getMusicURL(myDataset[currentPosition]);
try {
mediaPlayer.setDataSource(URL);
//mediaPlayer.prepare(); // might take long! (for buffering, etc)
mediaPlayer.prepareAsync(); // prepare async to not block main thread
} catch (IOException e) {
e.printStackTrace();
Log.i("TEST","Eroare: "+e.getMessage());
}
} catch (StorageApiException e) {
e.printStackTrace();
Log.i("TEST","Eroare: "+e.getMessage());
}
}
});
I think you have a mess. First of all, you dont need a thread to play music, the own mediaplayer API does it for you when you call mediaPlayer.start(). However, you have to care about the time it takes to prepare the data source if you are for example streaming online music. For this, just use mediaPlayer.prepareAsync() and register a callback. When it has finished preparing, you can automatically start playing or do whatever you want.
If you want to change the data source, just follow the automaton map that you can find in MediaPlayer docs. Essentially, when the user selects another track, you register the call in your button listener, then reset the mediaPlayer, and recall all prepare, start... cycle again. By the way, it is advised to deploy all your mediaplayer code into a service so that it can keep playing even though the user has closed your activity.

Simple way to load multiple audio files?

I've been trying to find a good way to load a large amount of audio files.
The code below works fine, but I need to load even more files and that's when I run into issues.
It's a quiz and when the users choose the correct answer, a new sound should be played. I have to load 50+ sounds.
The current way I load the files:
this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
spool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
Context myActivity = getApplicationContext();
primSounds.add(spool.load(myActivity, R.raw.int_prim_c_mel, 1));
primSounds.add(spool.load(myActivity, R.raw.int_prim_d_mel, 1));
primSounds.add(spool.load(myActivity, R.raw.int_prim_e_mel, 1));
kvintSounds.add(spool.load(myActivity, R.raw.int_kvint_c_mel, 1));
kvintSounds.add(spool.load(myActivity, R.raw.int_kvint_d_mel, 1));
kvintSounds.add(spool.load(myActivity, R.raw.int_kvint_e_mel, 1));
oktSounds.add(spool.load(myActivity, R.raw.int_okt_c_mel, 1));
oktSounds.add(spool.load(myActivity, R.raw.int_okt_d_mel, 1));
oktSounds.add(spool.load(myActivity, R.raw.int_okt_e_mel, 1));
...
The problem is that this way make the onCreate "too heavy"
Here you have a possible workaround.
But in your case, you'll have 50 sounds loaded at once at some point. This means that you'll have quite a LOT of memory in use.
Having to face a similar situation before myself ,i think you can use the following code:
SoundtrackManager.java
public void prepareSoundtrack(int soundtrackId, Context context) {
try {
if (mMediaPlayer != null) {
mMediaPlayer.release();
mMediaPlayer = null;
}
mMediaPlayer = MediaPlayer.create(context, soundtrackId);
} catch (Exception e) {
mMediaPlayer.release();
e.printStackTrace();
}
}
public void play() {
if (mMediaPlayer != null) {
try {
mMediaPlayer.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
using:
mSoundTrackManager.prepareSoundtrack(R.raw.the_sound, mContext);
mSoundTrackManager.play();
You can call play() as long as the sound being played does not change.
The downside is having to create a MediaPlayer for each sound. But you wont have all of them on memory at the same time. Also, they are loaded/played instantly.
EDIT:
This could be improved by making a "pool" of MediaPlayer objects in the SoundtrackManager class. So, if one of the random sounds is needed and happens to be on that "pool", you can avoid the constant MediaPlayer re-setting.

Android - Issue stopping a thread

In my application I start a thread that runs a couple audio services by pushing a button, then attempt to stop it by pushing a stop button. The audio portion stops fine, but I see in the debugging window that the thread is still running. Any help would be greatly appreciated
Here is the thread that is started, goTime is initialized to null as a global variable:
goTime = new Thread(new Runnable() {
public void run() {
rec.startRecording();
player.play();
while(isRecording && !Thread.currentThread().isInterrupted()) {
if(goTime.isInterrupted()){
return;
}
ix=0;
while(ix<bufLeng){
short[] bufferor = buffers[ix++ % buffers.length];
lastRead = ix;
rec.read(bufferor, 0,
bufferor.length);
buffers[ix]=bufferor;
inter.onMarkerReached(rec);
}
}
if(goTime.isInterrupted()){
return;
}
}});
goTime.start();
And here is the method called when the stop button is pressed:
public void stop(View view){
isRecording=false;
System.out.println("stop called");
rec.stop();
player.stop();
player.flush();
player.release();
goTime.interrupt();
//isRecording=true;
}
sometimes the values of objects between threads are cached by the JVM(or in this case Dalvik)
try declaring your isRecording like so:
private volatile boolean isRecording;
The volatile keyword will ensure the values are not cached but always written directly

Categories

Resources