Search code examples
javaandroidaudioaudio-recordingjava-audio

Exactly synchronize AudioRecord and AudioTrack in Android


I am trying to

    1. Record PCM data using AudioRecord and save them to a .wav file. (Done)
    1. Later on record another PCM file while playing the previously recorded file
    1. Save the new recording to another file
    1. Mix (overlay) the first and the new recording.

My problem is that the second recorded file (2.) and the first recorded file (1.) are not synchronous. Once I mix them together I hear a delay that I did not record. To test my app, I said 'Test 1 2 3' into the microphone. In the second recording I said 'Test 1 2 3' at the same time. However after mixing (overlaying) my 2 files, I get a delay.

What did I do wrong?

final Thread recordingThread = new Thread(new Runnable() {
                final int SAMPLING_RATE = 44100;
                final int AUDIO_SOURCE = MediaRecorder.AudioSource.MIC;
                final int CHANNEL_IN_CONFIG = AudioFormat.CHANNEL_IN_MONO;
                final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
                final int BUFFER_SIZE = AudioRecord.getMinBufferSize(SAMPLING_RATE, CHANNEL_IN_CONFIG, AUDIO_FORMAT);
                final String AUDIO_RECORDING_FILE_NAME = project.getPath()+"/track"+String.valueOf(project.getTrackNumber())+".raw";
                final int playbackBufferSize = AudioTrack.getMinBufferSize(SAMPLING_RATE, CHANNEL_IN_CONFIG, AUDIO_FORMAT);


                @Override
                public void run() {
                    android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
                    Log.d("Record", "[Starting recording]");

                    byte audioData[] = new byte[BUFFER_SIZE];

                    boolean playSound = true;

                    ByteArrayOutputStream playbackOutput = new ByteArrayOutputStream();
                    BufferedInputStream in = null;
                    try {
                        in = new BufferedInputStream(new FileInputStream(project.getPath()+"/output.wav"));
                    } catch (FileNotFoundException e) {
                        playSound = false;
                    }

                    int playbackRead = 1;
                    byte[] playbackBuffer = new byte[BUFFER_SIZE];

                    AudioRecord recorder = new AudioRecord(AUDIO_SOURCE,
                            SAMPLING_RATE, CHANNEL_IN_CONFIG,
                            AUDIO_FORMAT, BUFFER_SIZE);

                    AudioTrack player = new AudioTrack(AudioManager.STREAM_MUSIC, SAMPLING_RATE, AudioFormat.CHANNEL_OUT_MONO, AUDIO_FORMAT, playbackBufferSize, AudioTrack.MODE_STREAM);
                    player.play();

                    recorder.startRecording();
                    String filePath = AUDIO_RECORDING_FILE_NAME;
                    BufferedOutputStream os = null;
                    try {
                        os = new BufferedOutputStream(new FileOutputStream(filePath));
                    } catch (FileNotFoundException e) {
                        Log.e("Record", "File not found for recording ", e);
                    }

                    while (!mStop) {

                        int status = recorder.read(audioData, 0, audioData.length);

                        if (status == AudioRecord.ERROR_INVALID_OPERATION ||
                                status == AudioRecord.ERROR_BAD_VALUE) {
                            Log.e("Record", "Error reading audio data!");
                            return;
                        }

                        try {


                            os.write(audioData, 0, audioData.length);
                            if (playSound) {
                                try {
                                    if (playbackRead > 0) {
                                        playbackRead = in.read(playbackBuffer);
                                    }
                                    if (playbackRead > 0){
                                        player.write(playbackBuffer, 0, playbackBuffer.length);
                                    }
                                } catch (IOException e) {
                                    Toast.makeText(MainActivity.this, "ERROR!", Toast.LENGTH_SHORT).show();
                                    return;
                                }
                            }
                        } catch (IOException e) {
                            Log.e("Record", "Error saving recording ", e);
                            return;
                        }
                    }

                    try {
                        os.close();

                        recorder.stop();
                        recorder.release();
                        player.stop();
                        player.release();

                        Log.v("Record", "Recording done…");
                        mStop = false;
                        File out = new File(project.getPath()+"/output.wav");
                        if (!out.exists()) {
                            out.createNewFile();
                            SoundUtils.rawToWave(new File(AUDIO_RECORDING_FILE_NAME), out, SAMPLING_RATE);
                        } else {
                            mixSound(project.getPath()+"/track1.raw", project.getPath()+"/track2.raw", project.getPath()+"/track3.raw", project.getPath()+"/track4.raw");
                        }

                    } catch (IOException e) {
                        Log.e("Record", "Error when releasing", e);
                    }
                }
            });

Explanation:

  • Running in a thread
  • Creating AudioTrack and AudioRecord with the same settings
  • boolean playSound: true if the first recording is already done and the wav is available
  • in the loop:
    • Read the recorder audio data and write them to the output stream
    • Read a part of the previously recorded wav and write it to the player

(- Once there is nothing to play anymore, playbackRead is -1)

After that I try to mix my recordings. However my second recording is having a delay which I did not record.

What am I doing wrong? How do I (almost) completely correctly synchronize AudioRecord and AudioTrack so that when I say something it is played back at the position of the background recording as it was when I recorded it?


Solution

  • Finally I found a very exect solution and I would like to share it with everyone facing this problem in the future. I printed out every short value of the pcm data I was recording. It looked like this:

    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ..., 0.0025, 0.0026, 0.0163, 0.0123, ...
    

    As you can see, there are many zero's at the beginning of the recording. But in a normal quite environment there is always a (very quite) noise in the background. It can't be exactly 0.

    I thought, that maybe, this range, where all the 0's are is the time from the start of the recording till the first sound acutally receives the file.

    => This is the delay

    In the second recording (while playing the previous recording as a background sound) I removed the starting 0's from the whole data so that the sound starts immediately. I then mixed both recordings together and they are very exactly synchronized.

    I do not know whether this is a way how the delay could be removed in all situations but I tried it on several phones and it worked well. I am happy that I was able to solve my problem.

    Long story short: To remove the delay, remove all the 0's that are in the front of your sound data.