|
|
|
@ -12,9 +12,6 @@ import android.view.Surface;
|
|
|
|
|
|
|
|
|
|
import com.common.commonlib.utils.LogUtils;
|
|
|
|
|
|
|
|
|
|
import org.easydarwin.PushHelper;
|
|
|
|
|
import org.easydarwin.easypusher.BuildConfig;
|
|
|
|
|
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.nio.ByteBuffer;
|
|
|
|
|
|
|
|
|
@ -34,13 +31,15 @@ public class AudioEncoderCore extends MediaEncoderCore implements Runnable {
|
|
|
|
|
|
|
|
|
|
protected static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
|
|
|
|
|
|
|
|
|
|
private int mSampleRate = 8000;
|
|
|
|
|
private int mSampleRate = 44100;
|
|
|
|
|
|
|
|
|
|
private int mChannelCount = 1;
|
|
|
|
|
|
|
|
|
|
private int mBitRate = 16000;
|
|
|
|
|
private int mBitRate = 128000;
|
|
|
|
|
|
|
|
|
|
// private int mMaxInputSize = 16384;
|
|
|
|
|
|
|
|
|
|
private int mMaxInputSize = 1920;
|
|
|
|
|
int AUDIO_BUFFER_SIZE;
|
|
|
|
|
|
|
|
|
|
private AudioRecord mAudioRecord;
|
|
|
|
|
|
|
|
|
@ -63,14 +62,14 @@ public class AudioEncoderCore extends MediaEncoderCore implements Runnable {
|
|
|
|
|
format.setInteger(MediaFormat.KEY_SAMPLE_RATE, mSampleRate);
|
|
|
|
|
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, mChannelCount);
|
|
|
|
|
format.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);
|
|
|
|
|
format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, mMaxInputSize);
|
|
|
|
|
// format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, mMaxInputSize);
|
|
|
|
|
|
|
|
|
|
// Create a MediaCodec encoder, and configure it with our format. Get a Surface
|
|
|
|
|
// we can use for input and wrap it with a class that handles the EGL work.
|
|
|
|
|
try {
|
|
|
|
|
mEncoder = MediaCodec.createEncoderByType(MIME_TYPE);
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
LogUtils.e(e);
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
|
|
|
|
@ -79,6 +78,8 @@ public class AudioEncoderCore extends MediaEncoderCore implements Runnable {
|
|
|
|
|
LogUtils.v(String.format("%s prepareEncoder, mEncoder = %s", getClass().getSimpleName(), mEncoder));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int minBufferSize;
|
|
|
|
|
|
|
|
|
|
@SuppressLint("MissingPermission")
|
|
|
|
|
private void prepareRecorder() {
|
|
|
|
|
switch (mChannelCount) {
|
|
|
|
@ -92,24 +93,25 @@ public class AudioEncoderCore extends MediaEncoderCore implements Runnable {
|
|
|
|
|
throw new IllegalArgumentException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int minBufferSize = AudioRecord.getMinBufferSize(mSampleRate,
|
|
|
|
|
minBufferSize = AudioRecord.getMinBufferSize(mSampleRate,
|
|
|
|
|
mChannelConfig, AUDIO_FORMAT);
|
|
|
|
|
|
|
|
|
|
AUDIO_BUFFER_SIZE = 2 * minBufferSize;
|
|
|
|
|
mAudioRecord = new AudioRecord(
|
|
|
|
|
MediaRecorder.AudioSource.MIC, // source
|
|
|
|
|
MediaRecorder.AudioSource.CAMCORDER, // source
|
|
|
|
|
mSampleRate, // sample rate, hz
|
|
|
|
|
mChannelConfig, // channels
|
|
|
|
|
AUDIO_FORMAT, // audio format
|
|
|
|
|
minBufferSize); // buffer size (bytes)
|
|
|
|
|
AUDIO_BUFFER_SIZE); // buffer size (bytes)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void start() {
|
|
|
|
|
if (!mRecording) {
|
|
|
|
|
// mRecording = true;
|
|
|
|
|
// mAudioRecord.startRecording();
|
|
|
|
|
//
|
|
|
|
|
// TaskUtils.execute(this);
|
|
|
|
|
mRecording = true;
|
|
|
|
|
mAudioRecord.startRecording();
|
|
|
|
|
|
|
|
|
|
new Thread(this).start();
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -123,6 +125,87 @@ public class AudioEncoderCore extends MediaEncoderCore implements Runnable {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void drainEncoder(boolean endOfStream) {
|
|
|
|
|
// LogUtils.v(String.format("%s drainEncoder: end = %b", getClass().getSimpleName(), endOfStream));
|
|
|
|
|
|
|
|
|
|
final int TIMEOUT_USEC = 10000;
|
|
|
|
|
if (VERBOSE) Log.d(TAG, "drainEncoder(" + endOfStream + ")");
|
|
|
|
|
|
|
|
|
|
if (endOfStream && isSurfaceInput()) {
|
|
|
|
|
if (VERBOSE) Log.d(TAG, "sending EOS to encoder");
|
|
|
|
|
mEncoder.signalEndOfInputStream();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ByteBuffer[] encoderOutputBuffers = mEncoder.getOutputBuffers();
|
|
|
|
|
while (true) {
|
|
|
|
|
int encoderStatus = mEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC);
|
|
|
|
|
|
|
|
|
|
if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
|
|
|
|
|
// no output available yet
|
|
|
|
|
if (!endOfStream) {
|
|
|
|
|
break; // out of while
|
|
|
|
|
} else {
|
|
|
|
|
if (VERBOSE) Log.d(TAG, "no output available, spinning to await EOS");
|
|
|
|
|
}
|
|
|
|
|
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
|
|
|
|
|
// not expected for an encoder
|
|
|
|
|
encoderOutputBuffers = mEncoder.getOutputBuffers();
|
|
|
|
|
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
|
|
|
|
|
// should happen before receiving buffers, and should only happen once
|
|
|
|
|
MediaFormat newFormat = mEncoder.getOutputFormat();
|
|
|
|
|
Log.d(TAG, "encoder output format changed: " + newFormat);
|
|
|
|
|
|
|
|
|
|
// now that we have the Magic Goodies, start the muxer
|
|
|
|
|
mTrackIndex = mMuxer.addTrack(newFormat);
|
|
|
|
|
} else if (encoderStatus < 0) {
|
|
|
|
|
Log.w(TAG, "unexpected result from encoder.dequeueOutputBuffer: " +
|
|
|
|
|
encoderStatus);
|
|
|
|
|
// let's ignore it
|
|
|
|
|
} else {
|
|
|
|
|
ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
|
|
|
|
|
if (encodedData == null) {
|
|
|
|
|
throw new RuntimeException("encoderOutputBuffer " + encoderStatus +
|
|
|
|
|
" was null");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!mMuxer.isStarted()) {
|
|
|
|
|
mBufferInfo.size = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
|
|
|
|
|
// The codec config data was pulled out and fed to the muxer when we got
|
|
|
|
|
// the INFO_OUTPUT_FORMAT_CHANGED status. Ignore it.
|
|
|
|
|
if (VERBOSE) Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG");
|
|
|
|
|
mBufferInfo.size = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mBufferInfo.size != 0) {
|
|
|
|
|
// adjust the ByteBuffer values to match BufferInfo (not needed?)
|
|
|
|
|
encodedData.position(mBufferInfo.offset);
|
|
|
|
|
encodedData.limit(mBufferInfo.offset + mBufferInfo.size);
|
|
|
|
|
|
|
|
|
|
mMuxer.writeSampleData(mTrackIndex, encodedData, mBufferInfo);
|
|
|
|
|
if (VERBOSE) {
|
|
|
|
|
Log.d(TAG, "sent " + mBufferInfo.size + " bytes to muxer, ts=" +
|
|
|
|
|
mBufferInfo.presentationTimeUs);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mEncoder.releaseOutputBuffer(encoderStatus, false);
|
|
|
|
|
|
|
|
|
|
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
|
|
|
|
|
if (!endOfStream) {
|
|
|
|
|
Log.w(TAG, "reached end of stream unexpectedly");
|
|
|
|
|
} else {
|
|
|
|
|
if (VERBOSE) Log.d(TAG, "end of stream reached");
|
|
|
|
|
}
|
|
|
|
|
break; // out of while
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected boolean isSurfaceInput() {
|
|
|
|
|
return false;
|
|
|
|
@ -131,14 +214,14 @@ public class AudioEncoderCore extends MediaEncoderCore implements Runnable {
|
|
|
|
|
@Override
|
|
|
|
|
public void run() {
|
|
|
|
|
while (mRecording) {
|
|
|
|
|
// drainEncoder(false);
|
|
|
|
|
// drainAudio(false);
|
|
|
|
|
drainAudio(false);
|
|
|
|
|
drainEncoder(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// drainAudio(true);
|
|
|
|
|
drainAudio(true);
|
|
|
|
|
|
|
|
|
|
mAudioRecord.stop();
|
|
|
|
|
// drainEncoder(true);
|
|
|
|
|
drainEncoder(true);
|
|
|
|
|
|
|
|
|
|
release();
|
|
|
|
|
}
|
|
|
|
@ -146,104 +229,32 @@ public class AudioEncoderCore extends MediaEncoderCore implements Runnable {
|
|
|
|
|
private void drainAudio(boolean endOfStream) {
|
|
|
|
|
// LogUtils.v(String.format("drainAudio %b", endOfStream));
|
|
|
|
|
|
|
|
|
|
ByteBuffer[] inputBuffers = mEncoder.getInputBuffers();
|
|
|
|
|
int bufferIndex = mEncoder.dequeueInputBuffer(-1); // wait indefinitely
|
|
|
|
|
if (bufferIndex >= 0) {
|
|
|
|
|
ByteBuffer inputBuffer = inputBuffers[bufferIndex];
|
|
|
|
|
ByteBuffer inputBuffer = mEncoder.getInputBuffer(bufferIndex);
|
|
|
|
|
inputBuffer.clear();
|
|
|
|
|
|
|
|
|
|
int len = mAudioRecord.read(inputBuffer, SAMPLES_PER_FRAME * 2); // read blocking
|
|
|
|
|
long ptsUs = System.nanoTime() / 1000;
|
|
|
|
|
|
|
|
|
|
// byte[] buffer = new byte[minBufferSize];
|
|
|
|
|
// int len = mAudioRecord.read(buffer, 0, minBufferSize); // read blocking
|
|
|
|
|
// long ptsUs = System.nanoTime() / 1000;
|
|
|
|
|
// if (len > 0) {
|
|
|
|
|
// inputBuffer.put(buffer);
|
|
|
|
|
// if (endOfStream) {
|
|
|
|
|
// mEncoder.queueInputBuffer(bufferIndex, 0, buffer.length, ptsUs, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
|
|
|
|
|
// } else {
|
|
|
|
|
// mEncoder.queueInputBuffer(bufferIndex, 0, buffer.length, ptsUs, 0);
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
int bytesRead = mAudioRecord.read(inputBuffer, AUDIO_BUFFER_SIZE);
|
|
|
|
|
long presentationTimeUs = System.nanoTime() / 1000;
|
|
|
|
|
if (endOfStream) {
|
|
|
|
|
mEncoder.queueInputBuffer(bufferIndex, 0, len, ptsUs, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
|
|
|
|
|
} else {
|
|
|
|
|
mEncoder.queueInputBuffer(bufferIndex, 0, len, ptsUs, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void drainEncoder(boolean endOfStream) {
|
|
|
|
|
final int TIMEOUT_USEC = 10000;
|
|
|
|
|
if (VERBOSE) Log.d(TAG, "drainEncoder(" + endOfStream + ")");
|
|
|
|
|
|
|
|
|
|
if (endOfStream && isSurfaceInput()) {
|
|
|
|
|
if (VERBOSE) Log.d(TAG, "sending EOS to encoder");
|
|
|
|
|
mEncoder.signalEndOfInputStream();
|
|
|
|
|
}
|
|
|
|
|
ByteBuffer mBuffer = ByteBuffer.allocate(10240);
|
|
|
|
|
ByteBuffer[] encoderOutputBuffers = mEncoder.getOutputBuffers();
|
|
|
|
|
int encoderStatus = mEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC);
|
|
|
|
|
|
|
|
|
|
if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
|
|
|
|
|
// no output available yet
|
|
|
|
|
if (!endOfStream) {
|
|
|
|
|
mEncoder.queueInputBuffer(bufferIndex, 0, bytesRead, presentationTimeUs, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
|
|
|
|
|
} else {
|
|
|
|
|
if (VERBOSE) Log.d(TAG, "no output available, spinning to await EOS");
|
|
|
|
|
}
|
|
|
|
|
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
|
|
|
|
|
// not expected for an encoder
|
|
|
|
|
encoderOutputBuffers = mEncoder.getOutputBuffers();
|
|
|
|
|
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
|
|
|
|
|
// should happen before receiving buffers, and should only happen once
|
|
|
|
|
MediaFormat newFormat = mEncoder.getOutputFormat();
|
|
|
|
|
Log.d(TAG, "encoder output format changed: " + newFormat);
|
|
|
|
|
|
|
|
|
|
// now that we have the Magic Goodies, start the muxer
|
|
|
|
|
// mTrackIndex = mMuxer.addTrack(newFormat);
|
|
|
|
|
} else if (encoderStatus < 0) {
|
|
|
|
|
Log.w(TAG, "unexpected result from encoder.dequeueOutputBuffer: " +
|
|
|
|
|
encoderStatus);
|
|
|
|
|
// let's ignore it
|
|
|
|
|
} else {
|
|
|
|
|
ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
|
|
|
|
|
if (encodedData == null) {
|
|
|
|
|
throw new RuntimeException("encoderOutputBuffer " + encoderStatus +
|
|
|
|
|
" was null");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!mMuxer.isStarted()) {
|
|
|
|
|
mBufferInfo.size = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
|
|
|
|
|
// The codec config data was pulled out and fed to the muxer when we got
|
|
|
|
|
// the INFO_OUTPUT_FORMAT_CHANGED status. Ignore it.
|
|
|
|
|
if (VERBOSE) Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG");
|
|
|
|
|
mBufferInfo.size = 0;
|
|
|
|
|
mEncoder.queueInputBuffer(bufferIndex, 0, bytesRead,
|
|
|
|
|
presentationTimeUs, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mBufferInfo.size != 0) {
|
|
|
|
|
encodedData.get(mBuffer.array(), 7, mBufferInfo.size);
|
|
|
|
|
encodedData.clear();
|
|
|
|
|
mBuffer.position(7 + mBufferInfo.size);
|
|
|
|
|
addADTStoPacket(mBuffer.array(), mBufferInfo.size + 7);
|
|
|
|
|
mBuffer.flip();
|
|
|
|
|
PushHelper.INSTANCE.pushData(mBuffer.array(), mBufferInfo.size + 7, mBufferInfo.presentationTimeUs / 1000);
|
|
|
|
|
if (BuildConfig.DEBUG)
|
|
|
|
|
Log.i(TAG, String.format("push audio stamp:%d", mBufferInfo.presentationTimeUs / 1000));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mEncoder.releaseOutputBuffer(encoderStatus, false);
|
|
|
|
|
|
|
|
|
|
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
|
|
|
|
|
if (!endOfStream) {
|
|
|
|
|
Log.w(TAG, "reached end of stream unexpectedly");
|
|
|
|
|
} else {
|
|
|
|
|
if (VERBOSE) Log.d(TAG, "end of stream reached");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void addADTStoPacket(byte[] packet, int packetLen) {
|
|
|
|
|
packet[0] = (byte) 0xFF;
|
|
|
|
|
packet[1] = (byte) 0xF1;
|
|
|
|
|
packet[2] = (byte) (((2 - 1) << 6) + (11 << 2) + (1 >> 2));
|
|
|
|
|
packet[3] = (byte) (((1 & 3) << 6) + (packetLen >> 11));
|
|
|
|
|
packet[4] = (byte) ((packetLen & 0x7FF) >> 3);
|
|
|
|
|
packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F);
|
|
|
|
|
packet[6] = (byte) 0xFC;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|