Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Supportg711 #1358

Merged
merged 25 commits into from
Dec 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ dependencies {

- [X] Get upload bandwidth used.
- [x] RTMP auth (basic and digest).
- [x] H264, H265 and AAC support.
- [x] H264, H265, AAC and G711 support.
- [x] TCP/UDP.
- [x] RTSPS.

Expand Down
21 changes: 21 additions & 0 deletions common/src/main/java/com/pedro/common/AudioCodec.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright (C) 2023 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.
*/

package com.pedro.common

enum class AudioCodec {
G711, AAC
}
1 change: 1 addition & 0 deletions encoder/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,6 @@ afterEvaluate {
}

dependencies {
testImplementation 'junit:junit:4.13.2'
api 'androidx.annotation:annotation:1.7.0'
}
38 changes: 35 additions & 3 deletions encoder/src/main/java/com/pedro/encoder/BaseEncoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;

import com.pedro.encoder.audio.G711Codec;
import com.pedro.encoder.utils.CodecUtil;

import java.nio.ByteBuffer;
Expand All @@ -39,6 +40,7 @@
public abstract class BaseEncoder implements EncoderCallback {

protected String TAG = "BaseEncoder";
protected final G711Codec g711Codec = new G711Codec();
private final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
private HandlerThread handlerThread;
protected BlockingQueue<Frame> queue = new ArrayBlockingQueue<>(80);
Expand All @@ -53,11 +55,20 @@ public abstract class BaseEncoder implements EncoderCallback {
protected boolean prepared = false;
private Handler handler;
private EncoderErrorCallback encoderErrorCallback;
protected String type;

public void setEncoderErrorCallback(EncoderErrorCallback encoderErrorCallback) {
this.encoderErrorCallback = encoderErrorCallback;
}

public String getType() {
return type;
}

public void setType(String type) {
this.type = type;
}

public void restart() {
start(false);
initCodec();
Expand All @@ -76,15 +87,15 @@ protected void setCallback() {
handlerThread = new HandlerThread(TAG);
handlerThread.start();
handler = new Handler(handlerThread.getLooper());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !type.equals(CodecUtil.G711_MIME)) {
createAsyncCallback();
codec.setCallback(callback, handler);
}
}

private void initCodec() {
codec.start();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
if (!type.equals(CodecUtil.G711_MIME)) codec.start();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || type.equals(CodecUtil.G711_MIME)) {
handler.post(() -> {
while (running) {
try {
Expand Down Expand Up @@ -169,6 +180,10 @@ public void stop(boolean resetTs) {
protected abstract MediaCodecInfo chooseEncoder(String mime);

protected void getDataFromEncoder() throws IllegalStateException {
if (type.equals(CodecUtil.G711_MIME)) {
processG711();
return;
}
if (isBufferMode) {
int inBufferIndex = codec.dequeueInputBuffer(0);
if (inBufferIndex >= 0) {
Expand Down Expand Up @@ -292,4 +307,21 @@ public void onOutputFormatChanged(@NonNull MediaCodec mediaCodec,
}
};
}

private void processG711() {
try {
Frame frame = getInputFrame();
while (frame == null) frame = getInputFrame();
byte[] data = g711Codec.encode(frame.getBuffer(), frame.getOffset(), frame.getSize());
ByteBuffer buffer = ByteBuffer.wrap(data, 0, data.length);
bufferInfo.presentationTimeUs = calculatePts(frame, presentTimeUs);
bufferInfo.size = data.length;
bufferInfo.offset = 0;
sendBuffer(buffer, bufferInfo);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (NullPointerException | IndexOutOfBoundsException e) {
Log.i(TAG, "Encoding error", e);
}
}
}
14 changes: 12 additions & 2 deletions encoder/src/main/java/com/pedro/encoder/audio/AudioEncoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public class AudioEncoder extends BaseEncoder implements GetMicrophoneData {

public AudioEncoder(GetAacData getAacData) {
this.getAacData = getAacData;
type = CodecUtil.AAC_MIME;
TAG = "AudioEncoder";
}

Expand All @@ -64,8 +65,17 @@ public boolean prepareAudioEncoder(int bitRate, int sampleRate, boolean isStereo
this.maxInputSize = maxInputSize;
this.isStereo = isStereo;
isBufferMode = true;

try {
MediaCodecInfo encoder = chooseEncoder(CodecUtil.AAC_MIME);
if (type.equals(CodecUtil.G711_MIME)) {
g711Codec.configure(sampleRate, isStereo ? 2 : 1);
setCallback();
running = false;
Log.i(TAG, "prepared");
prepared = true;
return true;
}
MediaCodecInfo encoder = chooseEncoder(type);
if (encoder != null) {
Log.i(TAG, "Encoder selected " + encoder.getName());
codec = MediaCodec.createByCodecName(encoder.getName());
Expand All @@ -76,7 +86,7 @@ public boolean prepareAudioEncoder(int bitRate, int sampleRate, boolean isStereo

int channelCount = (isStereo) ? 2 : 1;
MediaFormat audioFormat =
MediaFormat.createAudioFormat(CodecUtil.AAC_MIME, sampleRate, channelCount);
MediaFormat.createAudioFormat(type, sampleRate, channelCount);
audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
audioFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize);
audioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE,
Expand Down
82 changes: 82 additions & 0 deletions encoder/src/main/java/com/pedro/encoder/audio/G711Codec.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright (C) 2023 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.
*/
package com.pedro.encoder.audio

import kotlin.experimental.inv

/**
* G711A (pcm-alaw) encoder/decoder
*/
class G711Codec {

fun configure(sampleRate: Int, channels: Int) {
require(!(sampleRate != 8000 || channels != 1)) {
"G711 codec only support 8000 sampleRate and mono channel"
}
}

fun encode(buffer: ByteArray, offset: Int, size: Int): ByteArray {
var j = offset
val count = size / 2
val out = ByteArray(count)
for (i in 0 until count) {
val sample = (buffer[j++].toInt() and 0xff or (buffer[j++].toInt() shl 8)).toShort()
out[i] = linearToALawSample(sample)
}
return out
}

fun decode(src: ByteArray, offset: Int, len: Int): ByteArray {
var j = 0
val out = ByteArray(src.size * 2)
for (i in 0 until len) {
val s = aLawDecompressTable[src[i + offset].toInt() and 0xff]
out[j++] = s.toByte()
out[j++] = (s.toInt() shr 8).toByte()
}
return out
}

private fun linearToALawSample(mySample: Short): Byte {
var sample = mySample
val sign = sample.inv().toInt() shr 8 and 0x80
if (sign != 0x80) sample = (-sample).toShort()
if (sample > cClip) sample = cClip.toShort()
var s: Int = if (sample >= 256) {
val exponent = aLawCompressTable[sample.toInt() shr 8 and 0x7F].toInt()
val mantissa = sample.toInt() shr exponent + 3 and 0x0F
exponent shl 4 or mantissa
} else {
sample.toInt() shr 4
}
s = s xor (sign xor 0x55)
return s.toByte()
}

companion object {
private const val cClip = 32635
/** decompress table constants */
private val aLawDecompressTable = shortArrayOf(
-5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736, -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784, -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368, -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392, -22016, -20992, -24064, -23040, -17920, -16896, -19968, -18944, -30208, -29184, -32256, -31232, -26112, -25088, -28160, -27136, -11008, -10496, -12032, -11520, -8960, -8448, -9984, -9472, -15104, -14592, -16128, -15616, -13056, -12544, -14080, -13568, -344, -328, -376,
-360, -280, -264, -312, -296, -472, -456, -504, -488, -408, -392, -440, -424, -88, -72, -120, -104, -24, -8, -56, -40, -216, -200, -248, -232, -152, -136, -184, -168, -1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184, -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696, -688, -656, -752, -720, -560, -528, -624, -592, -944, -912, -1008, -976, -816, -784, -880, -848, 5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736, 7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784, 2752, 2624,
3008, 2880, 2240, 2112, 2496, 2368, 3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392, 22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944, 30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136, 11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472, 15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568, 344, 328, 376, 360, 280, 264, 312, 296, 472, 456, 504, 488, 408, 392, 440, 424, 88, 72, 120, 104, 24, 8, 56, 40, 216, 200, 248, 232, 152, 136, 184, 168, 1376, 1312, 1504, 1440, 1120,
1056, 1248, 1184, 1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696, 688, 656, 752, 720, 560, 528, 624, 592, 944, 912, 1008, 976, 816, 784, 880, 848
)
private val aLawCompressTable = byteArrayOf(
1, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public class CodecUtil {
public static final String H264_MIME = "video/avc";
public static final String H265_MIME = "video/hevc";
public static final String AAC_MIME = "audio/mp4a-latm";
public static final String G711_MIME = "audio/pcm_alaw";
public static final String VORBIS_MIME = "audio/ogg";
public static final String OPUS_MIME = "audio/opus";

Expand Down
10 changes: 1 addition & 9 deletions encoder/src/main/java/com/pedro/encoder/video/VideoEncoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,13 @@ public class VideoEncoder extends BaseEncoder implements GetCameraData {
private int iFrameInterval = 2;
//for disable video
private final FpsLimiter fpsLimiter = new FpsLimiter();
private String type = CodecUtil.H264_MIME;
private FormatVideoEncoder formatVideoEncoder = FormatVideoEncoder.YUV420Dynamical;
private int avcProfile = -1;
private int avcProfileLevel = -1;

public VideoEncoder(GetVideoData getVideoData) {
this.getVideoData = getVideoData;
type = CodecUtil.H264_MIME;
TAG = "VideoEncoder";
}

Expand Down Expand Up @@ -289,14 +289,6 @@ public int getBitRate() {
return bitRate;
}

public String getType() {
return type;
}

public void setType(String type) {
this.type = type;
}

@Override
public void inputYUVData(Frame frame) {
if (running && !queue.offer(frame)) {
Expand Down
47 changes: 47 additions & 0 deletions encoder/src/test/java/com/pedro/encoder/G711CodecTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (C) 2023 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.
*/

package com.pedro.encoder

import com.pedro.encoder.audio.G711Codec
import org.junit.Assert.assertArrayEquals
import org.junit.Test

/**
* Created by pedro on 1/12/23.
*/
class G711CodecTest {

private val encoder = G711Codec()

@Test
fun `test encode audio PCM to G711`() {
val bufferPCM = byteArrayOf(24, 48, 64, 88)
val bufferG711 = byteArrayOf(-67, -93)

val result = encoder.encode(bufferPCM, 0, bufferPCM.size)
assertArrayEquals(bufferG711, result)
}

@Test
fun `test decode audio G711 to PCM`() {
val bufferPCM = byteArrayOf(0, 49, 0, 90)
val bufferG711 = byteArrayOf(-67, -93)

val result = encoder.decode(bufferG711, 0, bufferG711.size)
assertArrayEquals(bufferPCM, result)
}
}
13 changes: 10 additions & 3 deletions library/src/main/java/com/pedro/library/base/Camera1Base.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;

import com.pedro.common.AudioCodec;
import com.pedro.common.VideoCodec;
import com.pedro.encoder.EncoderErrorCallback;
import com.pedro.encoder.audio.AudioEncoder;
Expand Down Expand Up @@ -1006,11 +1007,17 @@ public void onVideoFormat(@NonNull MediaFormat mediaFormat) {
public abstract StreamBaseClient getStreamClient();

public void setVideoCodec(VideoCodec codec) {
recordController.setVideoMime(
codec == VideoCodec.H265 ? CodecUtil.H265_MIME : CodecUtil.H264_MIME);
videoEncoder.setType(codec == VideoCodec.H265 ? CodecUtil.H265_MIME : CodecUtil.H264_MIME);
setVideoCodecImp(codec);
recordController.setVideoCodec(codec);
videoEncoder.setType(codec == VideoCodec.H265 ? CodecUtil.H265_MIME : CodecUtil.H264_MIME);
}

public void setAudioCodec(AudioCodec codec) {
setAudioCodecImp(codec);
recordController.setAudioCodec(codec);
audioEncoder.setType(codec == AudioCodec.G711 ? CodecUtil.G711_MIME : CodecUtil.AAC_MIME);
}

protected abstract void setVideoCodecImp(VideoCodec codec);
protected abstract void setAudioCodecImp(AudioCodec codec);
}
13 changes: 10 additions & 3 deletions library/src/main/java/com/pedro/library/base/Camera2Base.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;

import com.pedro.common.AudioCodec;
import com.pedro.common.VideoCodec;
import com.pedro.encoder.EncoderErrorCallback;
import com.pedro.encoder.audio.AudioEncoder;
Expand Down Expand Up @@ -1062,11 +1063,17 @@ public void onVideoFormat(@NonNull MediaFormat mediaFormat) {
public abstract StreamBaseClient getStreamClient();

public void setVideoCodec(VideoCodec codec) {
recordController.setVideoMime(
codec == VideoCodec.H265 ? CodecUtil.H265_MIME : CodecUtil.H264_MIME);
videoEncoder.setType(codec == VideoCodec.H265 ? CodecUtil.H265_MIME : CodecUtil.H264_MIME);
setVideoCodecImp(codec);
recordController.setVideoCodec(codec);
videoEncoder.setType(codec == VideoCodec.H265 ? CodecUtil.H265_MIME : CodecUtil.H264_MIME);
}

public void setAudioCodec(AudioCodec codec) {
setAudioCodecImp(codec);
recordController.setAudioCodec(codec);
audioEncoder.setType(codec == AudioCodec.G711 ? CodecUtil.G711_MIME : CodecUtil.AAC_MIME);
}

protected abstract void setVideoCodecImp(VideoCodec codec);
protected abstract void setAudioCodecImp(AudioCodec codec);
}
Loading