2022-06-16 12:34:41 +02:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2021 pedroSG94.
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2021-06-22 16:05:02 +02:00
|
|
|
package com.pedro.encoder.audio;
|
|
|
|
|
|
|
|
import android.media.MediaCodec;
|
|
|
|
import android.media.MediaCodecInfo;
|
|
|
|
import android.media.MediaFormat;
|
|
|
|
import android.util.Log;
|
|
|
|
import androidx.annotation.NonNull;
|
|
|
|
import com.pedro.encoder.BaseEncoder;
|
|
|
|
import com.pedro.encoder.Frame;
|
2022-06-16 12:34:41 +02:00
|
|
|
import com.pedro.encoder.GetFrame;
|
2021-06-22 16:05:02 +02:00
|
|
|
import com.pedro.encoder.input.audio.GetMicrophoneData;
|
|
|
|
import com.pedro.encoder.utils.CodecUtil;
|
|
|
|
import java.nio.ByteBuffer;
|
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Created by pedro on 19/01/17.
|
|
|
|
*
|
|
|
|
* Encode PCM audio data to ACC and return in a callback
|
|
|
|
*/
|
|
|
|
|
|
|
|
public class AudioEncoder extends BaseEncoder implements GetMicrophoneData {
|
|
|
|
|
2022-06-16 12:34:41 +02:00
|
|
|
private final GetAacData getAacData;
|
2021-06-22 16:05:02 +02:00
|
|
|
private int bitRate = 64 * 1024; //in kbps
|
|
|
|
private int sampleRate = 32000; //in hz
|
2022-06-16 12:34:41 +02:00
|
|
|
private int maxInputSize = 0;
|
2021-06-22 16:05:02 +02:00
|
|
|
private boolean isStereo = true;
|
2022-06-16 12:34:41 +02:00
|
|
|
private GetFrame getFrame;
|
|
|
|
private long bytesRead = 0;
|
|
|
|
private boolean tsModeBuffer = false;
|
2021-06-22 16:05:02 +02:00
|
|
|
|
|
|
|
public AudioEncoder(GetAacData getAacData) {
|
|
|
|
this.getAacData = getAacData;
|
2022-06-16 12:34:41 +02:00
|
|
|
TAG = "AudioEncoder";
|
2021-06-22 16:05:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Prepare encoder with custom parameters
|
|
|
|
*/
|
|
|
|
public boolean prepareAudioEncoder(int bitRate, int sampleRate, boolean isStereo,
|
|
|
|
int maxInputSize) {
|
2022-06-16 12:34:41 +02:00
|
|
|
this.bitRate = bitRate;
|
2021-06-22 16:05:02 +02:00
|
|
|
this.sampleRate = sampleRate;
|
2022-06-16 12:34:41 +02:00
|
|
|
this.maxInputSize = maxInputSize;
|
|
|
|
this.isStereo = isStereo;
|
2021-06-22 16:05:02 +02:00
|
|
|
isBufferMode = true;
|
|
|
|
try {
|
2022-06-16 12:34:41 +02:00
|
|
|
MediaCodecInfo encoder = chooseEncoder(CodecUtil.AAC_MIME);
|
|
|
|
if (encoder != null) {
|
|
|
|
Log.i(TAG, "Encoder selected " + encoder.getName());
|
|
|
|
codec = MediaCodec.createByCodecName(encoder.getName());
|
2021-06-22 16:05:02 +02:00
|
|
|
} else {
|
2022-06-16 12:34:41 +02:00
|
|
|
Log.e(TAG, "Valid encoder not found");
|
|
|
|
return false;
|
2021-06-22 16:05:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
int channelCount = (isStereo) ? 2 : 1;
|
|
|
|
MediaFormat audioFormat =
|
|
|
|
MediaFormat.createAudioFormat(CodecUtil.AAC_MIME, sampleRate, channelCount);
|
|
|
|
audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
|
|
|
|
audioFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize);
|
|
|
|
audioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE,
|
|
|
|
MediaCodecInfo.CodecProfileLevel.AACObjectLC);
|
|
|
|
codec.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
|
|
|
|
running = false;
|
|
|
|
Log.i(TAG, "prepared");
|
|
|
|
return true;
|
2022-06-16 12:34:41 +02:00
|
|
|
} catch (Exception e) {
|
2021-06-22 16:05:02 +02:00
|
|
|
Log.e(TAG, "Create AudioEncoder failed.", e);
|
2022-06-16 12:34:41 +02:00
|
|
|
this.stop();
|
2021-06-22 16:05:02 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-16 12:34:41 +02:00
|
|
|
public void setGetFrame(GetFrame getFrame) {
|
|
|
|
this.getFrame = getFrame;
|
|
|
|
}
|
|
|
|
|
2021-06-22 16:05:02 +02:00
|
|
|
/**
|
|
|
|
* Prepare encoder with default parameters
|
|
|
|
*/
|
|
|
|
public boolean prepareAudioEncoder() {
|
2022-06-16 12:34:41 +02:00
|
|
|
return prepareAudioEncoder(bitRate, sampleRate, isStereo, maxInputSize);
|
2021-06-22 16:05:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void start(boolean resetTs) {
|
2022-06-16 12:34:41 +02:00
|
|
|
shouldReset = resetTs;
|
2021-06-22 16:05:02 +02:00
|
|
|
Log.i(TAG, "started");
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void stopImp() {
|
2022-06-16 12:34:41 +02:00
|
|
|
bytesRead = 0;
|
2021-06-22 16:05:02 +02:00
|
|
|
Log.i(TAG, "stopped");
|
|
|
|
}
|
|
|
|
|
2022-06-16 12:34:41 +02:00
|
|
|
@Override
|
|
|
|
public void reset() {
|
|
|
|
stop(false);
|
|
|
|
prepareAudioEncoder(bitRate, sampleRate, isStereo, maxInputSize);
|
|
|
|
restart();
|
|
|
|
}
|
|
|
|
|
2021-06-22 16:05:02 +02:00
|
|
|
@Override
|
|
|
|
protected Frame getInputFrame() throws InterruptedException {
|
2022-06-16 12:34:41 +02:00
|
|
|
return getFrame != null ? getFrame.getInputFrame() : queue.take();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected long calculatePts(Frame frame, long presentTimeUs) {
|
|
|
|
long pts;
|
|
|
|
if (tsModeBuffer) {
|
|
|
|
int channels = isStereo ? 2 : 1;
|
|
|
|
pts = 1000000 * bytesRead / 2 / channels / sampleRate;
|
|
|
|
bytesRead += frame.getSize();
|
|
|
|
} else {
|
|
|
|
pts = System.nanoTime() / 1000 - presentTimeUs;
|
|
|
|
}
|
|
|
|
return pts;
|
2021-06-22 16:05:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void checkBuffer(@NonNull ByteBuffer byteBuffer,
|
|
|
|
@NonNull MediaCodec.BufferInfo bufferInfo) {
|
2022-06-16 12:34:41 +02:00
|
|
|
fixTimeStamp(bufferInfo);
|
2021-06-22 16:05:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void sendBuffer(@NonNull ByteBuffer byteBuffer,
|
|
|
|
@NonNull MediaCodec.BufferInfo bufferInfo) {
|
|
|
|
getAacData.getAacData(byteBuffer, bufferInfo);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set custom PCM data.
|
|
|
|
* Use it after prepareAudioEncoder(int sampleRate, int channel).
|
|
|
|
* Used too with microphone.
|
|
|
|
*/
|
|
|
|
@Override
|
|
|
|
public void inputPCMData(Frame frame) {
|
2022-06-16 12:34:41 +02:00
|
|
|
if (running && !queue.offer(frame)) {
|
2021-06-22 16:05:02 +02:00
|
|
|
Log.i(TAG, "frame discarded");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected MediaCodecInfo chooseEncoder(String mime) {
|
2022-06-16 12:34:41 +02:00
|
|
|
List<MediaCodecInfo> mediaCodecInfoList;
|
|
|
|
if (force == CodecUtil.Force.HARDWARE) {
|
|
|
|
mediaCodecInfoList = CodecUtil.getAllHardwareEncoders(CodecUtil.AAC_MIME);
|
|
|
|
} else if (force == CodecUtil.Force.SOFTWARE) {
|
|
|
|
mediaCodecInfoList = CodecUtil.getAllSoftwareEncoders(CodecUtil.AAC_MIME);
|
2021-06-22 16:05:02 +02:00
|
|
|
} else {
|
2022-06-16 12:34:41 +02:00
|
|
|
//Priority: hardware > software
|
|
|
|
mediaCodecInfoList = CodecUtil.getAllEncoders(CodecUtil.AAC_MIME, true);
|
2021-06-22 16:05:02 +02:00
|
|
|
}
|
2022-06-16 12:34:41 +02:00
|
|
|
|
|
|
|
Log.i(TAG, mediaCodecInfoList.size() + " encoders found");
|
|
|
|
if (mediaCodecInfoList.isEmpty()) return null;
|
|
|
|
else return mediaCodecInfoList.get(0);
|
2021-06-22 16:05:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public void setSampleRate(int sampleRate) {
|
|
|
|
this.sampleRate = sampleRate;
|
|
|
|
}
|
|
|
|
|
2022-06-16 12:34:41 +02:00
|
|
|
public boolean isTsModeBuffer() {
|
|
|
|
return tsModeBuffer;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setTsModeBuffer(boolean tsModeBuffer) {
|
|
|
|
if (!isRunning()) {
|
|
|
|
this.tsModeBuffer = tsModeBuffer;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-22 16:05:02 +02:00
|
|
|
@Override
|
|
|
|
public void formatChanged(@NonNull MediaCodec mediaCodec, @NonNull MediaFormat mediaFormat) {
|
|
|
|
getAacData.onAudioFormat(mediaFormat);
|
|
|
|
}
|
|
|
|
}
|