Luis A. Florit
Luis A. Florit

Reputation: 2599

Subtle 'ticks' noise in WAVE files with AudioRecord

My app saves WAVE MONO files at 44100 and 16BIT PCM captured with AudioRecord using a RODE VideoMicro. Everything works just fine, except for the fact that I can see some very subtle "ticks" in the WAVE files uniformly spaced by 0.135 seconds. You can hear the ticks if your volume is very loud, or you can see them clearly using Audacity. Here is a sample WAVE file for you to analyze, and here is a screenshot of Audacity with 4 ticks in the soundfile:

enter image description here

Any idea what is happening?

I am not sure where those ticks come from, but I suspect that there is something strange in my code, since the tics are so uniformly spaced. But of course it could be either the phone, or the external microphone, or the interaction between the two. The BufferSize is 3528 and is retrieved with

 static int BufferSize = AudioRecord.getMinBufferSize(SampleRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);

This is the relevant part of my code:

            ...
            recorder = new AudioRecord((denoise.isChecked()) ? MediaRecorder.AudioSource.VOICE_RECOGNITION : MediaRecorder.AudioSource.MIC,
                SampleRate, AudioFormat.CHANNEL_IN_MONO, AudioEncoding, 2 * BufferSize); // 2 bytes in 16bit format

        try {
            recordingThread = new Thread(this::writeAudioDataToPCMFile, "AudioRecorder Thread");
            recordingThread.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
private void writeAudioDataToPCMFile() {
    String auxfile = AveActivity.REC_DIR + "/aux" + new Random().nextInt() + ".pcm";
    short[] sData = new short[BufferSize];
    int product;
    int i, readSize;
    int tooloud;
    int tooloudlim = BufferSize / 50; // 2% clip
    long mean;
    FileOutputStream os;
    try {
        os = new FileOutputStream(auxfile);
    } catch (FileNotFoundException e) {
        runOnUiThread(() -> textsound.setText(r.getString(R.string.errstart)));
        e.printStackTrace();
        return;
    }
    waveformLastList = new ArrayList<>();

    //int j=0;
    while (isRecording) {
        readSize = recorder.read(sData, 0, BufferSize);
        tooloud = 0;
        mean = 0;
        for (i = 0; i < readSize; ++i) {
            product = (int) (sData[i] * gain);
            if (Math.abs(product) <= Short.MAX_VALUE) {
                sData[i] = (short) product;
            } else {
                // Audio clipping!
                sData[i] = (short) (Integer.signum(product) * Short.MAX_VALUE);
                ++tooloud;
            }
            mean += sData[i] * sData[i];
        }
        if (tooloud > tooloudlim)
            runOnUiThread(this::tooLoudWarning);

        try {
            os.write(short2byte(sData), 0, 2 * BufferSize); // 2 bytes in 16bit format
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    try {
        os.close();
        CharSequence time = DateFormat.format("yyMMdd-kkmmss", new Date(System.currentTimeMillis()));
        song = birdname + "_" + time + ((denoise.isChecked()) ? "_NR" : "") + ((db == 0) ? "" : "_db" + db) + "." + songFormat;
        runOnUiThread(() -> textsound.setText(r.getString(R.string.saving, songFormat)));
        PCMtoWAV(new File(auxfile), new File(AveActivity.REC_DIR, song));
    } catch (IOException e) {
        runOnUiThread(() -> textsound.setText(r.getString(R.string.errsaving, songFormat)));
        e.printStackTrace();
    }
    runOnUiThread(() -> {
        activatebtns();
        micColor(Color.WHITE);
    });
}
private void PCMtoWAV(File input, File output) throws IOException {
    // Bien rapido: 1/10 sec para cada minuto de gravacion
    byte[] data = new byte[2 * BufferSize];
    try (FileOutputStream outStream = new FileOutputStream(output)) {
        FileInputStream inStream = new FileInputStream(input);
        // Write WAVE header
        outStream.write(buildWAVHeader(input.length()));
        // Write audio data
        while (inStream.read(data) != -1)
            outStream.write(data);
        // Write Metadata to LIST....INFO chunk
        outStream.write(buildWAVMeta());
        inStream.close();
        outStream.close();
        input.delete();
    }
}

EDIT:

Following Uriel advice, I implemented a BlockingQueue like this:

public void RecordSong() {
    if (isRecording) {
       isRecording = false;
       recorder.stop();
       recorder.release();
       recordingThread.interrupt();
       writingThread.interrupt();
    ...
    } else {
        recorder = new AudioRecord(...);
        try {
          LinkedBlockingDeque<Byte> queue = new LinkedBlockingDeque<>();
          recorder.startRecording();
          recordingThread = new Thread(() -> captureAudioData(queue), "AudioRecorder Capture Thread");
          writingThread = new Thread(() -> writeAudioDataToPCMFile(queue), "AudioRecorder Writing Thread");
          recordingThread.setPriority(Thread.MAX_PRIORITY);
          writingThread.setPriority(Thread.MAX_PRIORITY);
          recordingThread.start();
          writingThread.start();
          ...
       }
  }

private void captureAudioData(LinkedBlockingDeque<Byte> q) {
    ...
    while (isRecording) {
        capture,gain,etc...
    
        try {
            for (byte b : short2byte(sData)) q.put(b);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

private void writeAudioDataToPCMFile(LinkedBlockingDeque<Byte> q) {
    String auxfile = AveActivity.REC_DIR + "/aux" + new Random().nextInt() + ".pcm";
    FileOutputStream os;
    try {
        os = new FileOutputStream(auxfile);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
        return;
    }
    
    try {
        while (true) os.write(q.take());
    } catch (IOException e) {
        e.printStackTrace();
    } catch (InterruptedException e) {
        try {
            os.close();
        } catch (IOException e1) {
            e.printStackTrace();
        }
    }
}

EDIT 2:

I tried the new approach (locked in the bathroom at 2am!). Unfortunately the problem persists. No change. It seems it is either a phone issue or a mic issue. In fact, removing the mic seems to get rid of the ticks, so probably it is a mic issue.

Upvotes: 0

Views: 125

Answers (1)

Uriel Frankel
Uriel Frankel

Reputation: 14622

What you are experiencing is probably a pop sound that is called "popcorn". It usually happen when there is gaps in the wave file. what I suggest you to do is 2 things.

  1. set the thread priority to the highest.
  2. don't read and write on the same thread. use one thread to read, put it inside a queue, and on the second thread write the queue data into a file.

This is what i do in my app and there are no pops. LMK if it helped.

Upvotes: 1

Related Questions