0

I'm using this library: https://github.com/alxrm/audiowave-progressbar to give an audio wave effect like Soundcloud music player.

This is how I implemented:

byte[] data = convert(songsList.get(currentSongIndex).get("songPath"));

final AudioWaveView waveView = (AudioWaveView) findViewById(R.id.wave);

waveView.setScaledData(data);
waveView.setRawData(data, new OnSamplingListener() {
    @Override
    public void onComplete() {

    }
});

waveView.setOnProgressListener(new OnProgressListener() {
    @Override
    public void onStartTracking(float progress) {

    }

    @Override
    public void onStopTracking(float progress) {

    }

    @Override
    public void onProgressChanged(float progress, boolean byUser) {

    }
});

This is the method that converts the file to byte array using the said file's path

public byte[] convert(String path) throws IOException {

    FileInputStream fis = new FileInputStream(path);
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    byte[] b = new byte[1024];

    for (int readNum; (readNum = fis.read(b)) != -1;) {
        bos.write(b, 0, readNum);
    }

    byte[] bytes = bos.toByteArray();

    return bytes;
}

Maybe the problem is due to not getting the array asynchronously, but I'm not sure.

Can anyone help me with this?

EDIT: As @commonsware suggested correctly, I did this:

 class AudioWave extends AsyncTask<String,Integer,String>{
     private Context mContext;
     private View rootView;
     public MusicPlayerActivity m;
     final AudioWaveView waveView = (AudioWaveView) m.findViewById(R.id.wave);

     public AudioWave(MusicPlayerActivity m1){
         m = m1;
     }
     // Runs in UI before background thread is called
     @Override
     protected void onPreExecute() {
         super.onPreExecute();

         // Do something like display a progress bar
     }
    byte[] b2;
     // This is run in a background thread
     @Override
     protected String doInBackground(String... params){
         // get the string from params, which is an array
         String path = params[0];
         try {
             FileInputStream fis = new FileInputStream(path);
             ByteArrayOutputStream bos = new ByteArrayOutputStream();
             byte[] b = new byte[1024];
             try {
                 for (int readNum; (readNum = fis.read(b)) != -1; ) {
                     bos.write(b, 0, readNum);
                 }

             }catch(IOException e){

             }

         byte[] bytes = bos.toByteArray();
             b2=bytes;
             new MusicPlayerActivity().setData(b2);
         }
         catch (FileNotFoundException e){

         }
         return "lol";
     }

     // This is called from background thread but runs in UI
     @Override
     protected void onProgressUpdate(Integer... values) {

         super.onProgressUpdate(values);

         // Do things like update the progress bar
     }

     // This runs in UI when background thread finishes
     @Override
     protected void onPostExecute(String result) {
         waveView.setScaledData(b2);
         waveView.setRawData(b2, new OnSamplingListener() {
             @Override
             public void onComplete() {

             }
         });

         waveView.setOnProgressListener(new OnProgressListener() {
             @Override
             public void onStartTracking(float progress) {

             }

             @Override
             public void onStopTracking(float progress) {

             }

             @Override
             public void onProgressChanged(float progress, boolean byUser) {

             }
         });

         super.onPostExecute(result);
         // Do things like hide the progress bar or change a TextView
     }
 }
mandark
  • 13
  • 7
  • 1
    Please explain in detail what "slows the app down" means. What are you **precise** symptoms? If you are doing this disk I/O on the main application thread, your UI will freeze while that I/O is going on. – CommonsWare Jan 31 '17 at 15:46
  • That `convert()` method basically just hangs my phone (MotoG 3). Yes, it does freezes while the I/O is going on. How can I solve it? – mandark Jan 31 '17 at 15:50
  • Do your disk I/O in a background thread, then update your `waveView` on the main application thread when the I/O is done. An `AsyncTask` is a typical solution for this. – CommonsWare Jan 31 '17 at 15:53
  • Yeah, I am a total noob here so I have a few queries...1- Is this: - `FileInputStream fis = new FileInputStream(path); ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] b = new byte[1024]; for (int readNum; (readNum = fis.read(b)) != -1;) { bos.write(b, 0, readNum); }` what you mean by I/O? \n 2- Any tutorials on how to use `AsyncTask ` for this? – mandark Jan 31 '17 at 16:01
  • Pretty much your entire `convert()` method would move into the background thread. `AsyncTask` is covered by any decent book or course on Android app development, along with countless tutorials, Stack Overflow questions, and the like. `AsyncTask` is also covered in [the documentation](https://developer.android.com/training/displaying-bitmaps/process-bitmap.html). – CommonsWare Jan 31 '17 at 16:04
  • hey. I read that and moved the code to a class extending asynctask. But it still lags because I cant move the part of code which is responsible for setting the audio wave to `doInBackground()` because according to the library, it should be used in a UI thread and not a worker thread. Then I used it in `onPostExecute()`, but then it won't make a difference because it isn't in background anymore. Help please. Code is posted in the edit – mandark Jan 31 '17 at 17:51
  • Get rid of `new MusicPlayerActivity().setData(b2);` (which is seriously scary), log your `FileNotFoundException` information to LogCat via `Log.e()`, and consider Robert's suggestions. Beyond that, you will need to talk to the developers of that library and ask them for advice, or use method tracing to determine exactly where your time is being spent. – CommonsWare Jan 31 '17 at 17:57
  • OSSSUM! It worked! After removing the "seriously scary" part. @CommonsWare FTW! Long Live StackOverflow! – mandark Jan 31 '17 at 18:09

3 Answers3

1

Loading a local file the way you do is totally inefficient:

  1. You are using a very small buffer of 1024 bytes. The larger a buffer is the better speed you get, especially considering that a Smartphone uses flash memory which uses larger memory blocks internally.

  2. You are using ByteArrayOutputStream. This is a dynamically growing byte array. Every time it grows beyond the size of the current buffer everything has do be copied from the old buffer to the new buffer. As it is a local file you know the file size and hence you could initialize the buffer with the full size. But then you don't need ByteArrayOutputStream anymore.

Hence you can simply load the file using one read command:

File f = new File(path);
int size = (int) g.length();
byte[] fileData = new byte[size];
try (DataInputStream in = new DatInpuStream(FileInputStream(f)))) {
    in.readFully(fileData);
}

Anyway this will work only for "small" files as you load it completely into memory. If you AudioWaveView supports it you should with a streaming approach: wrap an FileInputStream with an BufferedInputStream with it's buffer size set to 512KB or so.

Robert
  • 39,162
  • 17
  • 99
  • 152
  • Hey Robert, total beginner here, But isn't it wrong to assign value from FileInputStream to DataInputStream? – mandark Jan 31 '17 at 17:54
  • @mandark: You are right, wrote the code offline and missed this bug. – Robert Jan 31 '17 at 18:42
  • Thanks @Robert, I have got it working for files upto 5-10 mb but I'm sure it would get out of memory for larger files and so, I'll update the code with what you suggested. – mandark Jan 31 '17 at 18:49
0

Doing I/O on the main application thread will freeze your UI for the duration of that I/O, meaning that your UI will be unresponsive. Moving I/O to a background thread is necessary for a well-behaved UI.

Android's insistence that we can only safely update the UI from the main application thread means that you need to use an AsyncTask, a Thread and stuff like runOnUiThread(), RxJava/RxAndroid, or other approaches to arrange both the background work and updating the UI when that background work is done.

In your case, doing the convert() logic on a background thread, and updating the waveView on the main application thread, should help.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
-1

Please use this function to improve performance---

private static byte[] readBytesFromFile(String filePath) {

        FileInputStream fileInputStream = null;
        byte[] bytesArray = null;

        try {

            File file = new File(filePath);
            bytesArray = new byte[(int) file.length()];

            //read file into bytes[]
            fileInputStream = new FileInputStream(file);
            fileInputStream.read(bytesArray);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }

        return bytesArray;

    }
priyanka
  • 171
  • 2
  • 12
  • Hey @priyanka, you see, I was doing a similar thing that created the problem in the first place. – mandark Jan 31 '17 at 18:50
  • Using `read`from FileInputStream is wrong. By definition may read up to the buffer size but it doesn't have to. Therefore you can end up with a buffer only filled half. The only workaround is to read in a loop or to use `DataInputStream.readFully`. – Robert Jan 31 '17 at 20:17