From 11088d78643c04c6b57ef321d5e2efaf40c62a65 Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Thu, 1 Aug 2019 11:50:35 +0200 Subject: [PATCH] Create AudioNoise --- .../video/encoding/AudioMediaEncoder.java | 28 +-------- .../cameraview/video/encoding/AudioNoise.java | 59 +++++++++++++++++++ .../video/encoding/MediaEncoder.java | 1 + 3 files changed, 63 insertions(+), 25 deletions(-) create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/AudioNoise.java diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/AudioMediaEncoder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/AudioMediaEncoder.java index d0b5b973..9e9dac09 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/AudioMediaEncoder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/AudioMediaEncoder.java @@ -33,12 +33,6 @@ public class AudioMediaEncoder extends MediaEncoder { private static final boolean PERFORMANCE_FILL_GAPS = true; private static final int PERFORMANCE_MAX_GAPS = 8; - private final static Random NOISE = new Random(); - - private static short noise() { - return (short) NOISE.nextInt(50); - } - private boolean mRequestStop = false; private AudioEncodingThread mEncoder; private AudioRecordingThread mRecorder; @@ -47,7 +41,7 @@ public class AudioMediaEncoder extends MediaEncoder { private AudioConfig mConfig; private InputBufferPool mInputBufferPool = new InputBufferPool(); private final LinkedBlockingQueue mInputBufferQueue = new LinkedBlockingQueue<>(); - private ByteBuffer mNoiseBuffer; + private AudioNoise mAudioNoise; // Just to debug performance. private int mDebugSendCount = 0; @@ -84,6 +78,7 @@ public class AudioMediaEncoder extends MediaEncoder { mMediaCodec.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); mMediaCodec.start(); mByteBufferPool = new ByteBufferPool(mConfig.frameSize(), mConfig.bufferPoolMaxSize()); + mAudioNoise = new AudioNoise(mConfig); } @EncoderThread @@ -304,31 +299,14 @@ public class AudioMediaEncoder extends MediaEncoder { long gapStart = mTimestamp.getGapStartUs(mLastTimeUs); long frameUs = AudioTimestamp.bytesToUs(mConfig.frameSize(), mConfig.byteRate()); LOG.w("read thread - GAPS: trying to add", gaps, "noise buffers. PERFORMANCE_MAX_GAPS:", PERFORMANCE_MAX_GAPS); - - // Prepare the noise buffer. - if (mNoiseBuffer == null) { - LOG.w("read thread - GAPS: creating noise buffer."); - mNoiseBuffer = ByteBuffer.allocateDirect(mConfig.frameSize()).order(ByteOrder.nativeOrder()); - while (mNoiseBuffer.hasRemaining()) { - // Assume remaining() is not an odd number! - // Also assuming byte order is little endian in Android. - short noise = noise(); - mNoiseBuffer.put((byte) noise); - mNoiseBuffer.put((byte) (noise >> 8)); - } - } - - // Fill all gaps. - // Well, at most PERFORMANCE_MAX_GAPS. for (int i = 0; i < Math.min(gaps, PERFORMANCE_MAX_GAPS); i++) { ByteBuffer noiseBuffer = mByteBufferPool.get(); if (noiseBuffer == null) { LOG.e("read thread - GAPS: aborting because we have no free buffer."); break; } - mNoiseBuffer.clear(); noiseBuffer.clear(); - noiseBuffer.put(mNoiseBuffer); + mAudioNoise.fill(noiseBuffer); noiseBuffer.rewind(); enqueue(noiseBuffer, gapStart, false); gapStart += frameUs; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/AudioNoise.java b/cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/AudioNoise.java new file mode 100644 index 00000000..ec60645c --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/AudioNoise.java @@ -0,0 +1,59 @@ +package com.otaliastudios.cameraview.video.encoding; + +import androidx.annotation.NonNull; + +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.ShortBuffer; +import java.util.Random; + +/** + * An AudioNoise instance offers buffers of noise that we can use when recording + * some samples failed for some reason. + * + * Since we can't create noise anytime it's needed - that would be expensive and + * slow down the recording thread - we create a big noise buffer at start time. + * + * We'd like to work with {@link ShortBuffer}s, but this requires converting the + * input buffer to ShortBuffer each time, and this can be expensive. + */ +class AudioNoise { + + private final static int FRAMES = 1; // After testing, it looks like this is the best setup + private final static Random RANDOM = new Random(); + + private final ByteBuffer mNoiseBuffer; + + AudioNoise(@NonNull AudioConfig config) { + //noinspection ConstantConditions + if (config.sampleSizePerChannel != 2) { + throw new IllegalArgumentException("AudioNoise expects 2bytes-1short samples."); + } + mNoiseBuffer = ByteBuffer + .allocateDirect(config.frameSize() * FRAMES) + .order(ByteOrder.nativeOrder()); + double i = 0; + double frequency = config.frameSize() / 2D; // each X samples, the signal repeats + double step = Math.PI / frequency; // the increase in radians + double max = 10; // might choose this from 0 to Short.MAX_VALUE + while (mNoiseBuffer.hasRemaining()) { + short noise = (short) (Math.sin(++i * step) * max); + mNoiseBuffer.put((byte) noise); + mNoiseBuffer.put((byte) (noise >> 8)); + } + mNoiseBuffer.rewind(); + } + + void fill(@NonNull ByteBuffer outBuffer) { + mNoiseBuffer.clear(); + if (mNoiseBuffer.capacity() == outBuffer.remaining()) { + mNoiseBuffer.position(0); // Happens if FRAMES = 1. + } else { + mNoiseBuffer.position(RANDOM.nextInt(mNoiseBuffer.capacity() + - outBuffer.remaining())); + } + mNoiseBuffer.limit(mNoiseBuffer.position() + outBuffer.remaining()); + outBuffer.put(mNoiseBuffer); + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/MediaEncoder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/MediaEncoder.java index 53435713..12a52743 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/MediaEncoder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/MediaEncoder.java @@ -325,6 +325,7 @@ public abstract class MediaEncoder { mOutputBufferPool = null; mBuffers = null; setState(STATE_STOPPED); + mWorker.destroy(); } /**