Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,12 @@ class CameraFragment: Fragment(), ConnectChecker {
if (!folder.exists()) folder.mkdir()
val sdf = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault())
recordPath = "${folder.absolutePath}/${sdf.format(Date())}.mp4"
bRecord.setImageResource(R.drawable.pause_icon)
genericStream.startRecord(recordPath) { status ->
if (status == RecordController.Status.RECORDING) {
bRecord.setImageResource(R.drawable.stop_icon)
}
}
bRecord.setImageResource(R.drawable.pause_icon)
} else {
genericStream.stopRecord()
bRecord.setImageResource(R.drawable.record_icon)
Expand Down
2 changes: 1 addition & 1 deletion common/src/main/java/com/pedro/common/Extensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ fun ByteBuffer.removeInfo(info: MediaFrame.Info): ByteBuffer {
try {
position(info.offset)
limit(info.size)
} catch (ignored: Exception) { }
} catch (_: Exception) { }
return slice()
}

Expand Down
110 changes: 4 additions & 106 deletions encoder/src/main/java/com/pedro/encoder/video/VideoEncoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ private boolean sendSPSandPPS(MediaFormat mediaFormat) {
} else if (type.equals(CodecUtil.H265_MIME)) {
ByteBuffer bufferInfo = mediaFormat.getByteBuffer("csd-0");
if (bufferInfo != null) {
List<ByteBuffer> byteBufferList = extractVpsSpsPpsFromH265(bufferInfo);
List<ByteBuffer> byteBufferList = VideoEncoderHelper.extractVpsSpsPpsFromH265(bufferInfo);
oldSps = byteBufferList.get(1);
oldPps = byteBufferList.get(2);
oldVps = byteBufferList.get(0);
Expand Down Expand Up @@ -393,108 +393,6 @@ protected MediaCodecInfo chooseEncoder(String mime) {
return null;
}

/**
* decode sps and pps if the encoder never call to MediaCodec.INFO_OUTPUT_FORMAT_CHANGED
*/
private Pair<ByteBuffer, ByteBuffer> decodeSpsPpsFromBuffer(ByteBuffer outputBuffer, int length) {
byte[] csd = new byte[length];
outputBuffer.get(csd, 0, length);
outputBuffer.rewind();
int i = 0;
int spsIndex = -1;
int ppsIndex = -1;
while (i < length - 4) {
if (csd[i] == 0 && csd[i + 1] == 0 && csd[i + 2] == 0 && csd[i + 3] == 1) {
if (spsIndex == -1) {
spsIndex = i;
} else {
ppsIndex = i;
break;
}
}
i++;
}
if (spsIndex != -1 && ppsIndex != -1) {
byte[] sps = new byte[ppsIndex];
System.arraycopy(csd, spsIndex, sps, 0, ppsIndex);
byte[] pps = new byte[length - ppsIndex];
System.arraycopy(csd, ppsIndex, pps, 0, length - ppsIndex);
return new Pair<>(ByteBuffer.wrap(sps), ByteBuffer.wrap(pps));
}
return null;
}

/**
* You need find 0 0 0 1 byte sequence that is the initiation of vps, sps and pps
* buffers.
*
* @param csd0byteBuffer get in mediacodec case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED
* @return list with vps, sps and pps
*/
private List<ByteBuffer> extractVpsSpsPpsFromH265(ByteBuffer csd0byteBuffer) {
List<ByteBuffer> byteBufferList = new ArrayList<>();
int vpsPosition = -1;
int spsPosition = -1;
int ppsPosition = -1;
int contBufferInitiation = 0;
int length = csd0byteBuffer.remaining();
byte[] csdArray = new byte[length];
csd0byteBuffer.get(csdArray, 0, length);
csd0byteBuffer.rewind();
for (int i = 0; i < csdArray.length; i++) {
if (contBufferInitiation == 3 && csdArray[i] == 1) {
if (vpsPosition == -1) {
vpsPosition = i - 3;
} else if (spsPosition == -1) {
spsPosition = i - 3;
} else {
ppsPosition = i - 3;
}
}
if (csdArray[i] == 0) {
contBufferInitiation++;
} else {
contBufferInitiation = 0;
}
}
byte[] vps = new byte[spsPosition];
byte[] sps = new byte[ppsPosition - spsPosition];
byte[] pps = new byte[csdArray.length - ppsPosition];
for (int i = 0; i < csdArray.length; i++) {
if (i < spsPosition) {
vps[i] = csdArray[i];
} else if (i < ppsPosition) {
sps[i - spsPosition] = csdArray[i];
} else {
pps[i - ppsPosition] = csdArray[i];
}
}
byteBufferList.add(ByteBuffer.wrap(vps));
byteBufferList.add(ByteBuffer.wrap(sps));
byteBufferList.add(ByteBuffer.wrap(pps));
return byteBufferList;
}

/**
*
* @param buffer key frame
* @return av1 ObuSequence
*/
private ByteBuffer extractObuSequence(ByteBuffer buffer, MediaCodec.BufferInfo bufferInfo) {
//we can only extract info from keyframes
if (bufferInfo.flags != MediaCodec.BUFFER_FLAG_KEY_FRAME) return null;
byte[] av1Data = new byte[buffer.remaining()];
buffer.get(av1Data);
Av1Parser av1Parser = new Av1Parser();
List<Obu> obuList = av1Parser.getObus(av1Data);
for (Obu obu: obuList) {
if (av1Parser.getObuType(obu.getHeader()[0]) == ObuType.SEQUENCE_HEADER) {
return ByteBuffer.wrap(obu.getFullData());
}
}
return null;
}

@Override
protected Frame getInputFrame() throws InterruptedException {
Frame frame = queue.take();
Expand Down Expand Up @@ -534,7 +432,7 @@ protected void checkBuffer(@NonNull ByteBuffer byteBuffer, @NonNull MediaCodec.B
fixTimeStamp(bufferInfo);
if (!spsPpsSetted && type.equals(CodecUtil.H264_MIME)) {
Log.i(TAG, "formatChanged not called, doing manual sps/pps extraction...");
Pair<ByteBuffer, ByteBuffer> buffers = decodeSpsPpsFromBuffer(byteBuffer.duplicate(), bufferInfo.size);
Pair<ByteBuffer, ByteBuffer> buffers = VideoEncoderHelper.decodeSpsPpsFromBuffer(byteBuffer.duplicate(), bufferInfo.size);
if (buffers != null) {
Log.i(TAG, "manual sps/pps extraction success");
oldSps = buffers.first;
Expand All @@ -547,7 +445,7 @@ protected void checkBuffer(@NonNull ByteBuffer byteBuffer, @NonNull MediaCodec.B
}
} else if (!spsPpsSetted && type.equals(CodecUtil.H265_MIME)) {
Log.i(TAG, "formatChanged not called, doing manual vps/sps/pps extraction...");
List<ByteBuffer> byteBufferList = extractVpsSpsPpsFromH265(byteBuffer.duplicate());
List<ByteBuffer> byteBufferList = VideoEncoderHelper.extractVpsSpsPpsFromH265(byteBuffer.duplicate());
if (byteBufferList.size() == 3) {
Log.i(TAG, "manual vps/sps/pps extraction success");
oldSps = byteBufferList.get(1);
Expand All @@ -560,7 +458,7 @@ protected void checkBuffer(@NonNull ByteBuffer byteBuffer, @NonNull MediaCodec.B
}
} else if (!spsPpsSetted && type.equals(CodecUtil.AV1_MIME)) {
Log.i(TAG, "formatChanged not called, doing manual av1 extraction...");
ByteBuffer obuSequence = extractObuSequence(byteBuffer.duplicate(), bufferInfo);
ByteBuffer obuSequence = VideoEncoderHelper.extractObuSequence(byteBuffer.duplicate(), bufferInfo);
if (obuSequence != null) {
oldSps = obuSequence;
getVideoData.onVideoInfo(obuSequence, null, null);
Expand Down
137 changes: 137 additions & 0 deletions encoder/src/main/java/com/pedro/encoder/video/VideoEncoderHelper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
*
* * Copyright (C) 2024 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.video;

import android.media.MediaCodec;
import android.util.Pair;

import com.pedro.common.av1.Av1Parser;
import com.pedro.common.av1.Obu;
import com.pedro.common.av1.ObuType;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

/**
* Created by pedro on 3/12/25.
*/
public class VideoEncoderHelper {
/**
* decode sps and pps if the encoder never call to MediaCodec.INFO_OUTPUT_FORMAT_CHANGED
*/
public static Pair<ByteBuffer, ByteBuffer> decodeSpsPpsFromBuffer(ByteBuffer outputBuffer, int length) {
byte[] csd = new byte[length];
outputBuffer.get(csd, 0, length);
outputBuffer.rewind();
int i = 0;
int spsIndex = -1;
int ppsIndex = -1;
while (i < length - 4) {
if (csd[i] == 0 && csd[i + 1] == 0 && csd[i + 2] == 0 && csd[i + 3] == 1) {
if (spsIndex == -1) {
spsIndex = i;
} else {
ppsIndex = i;
break;
}
}
i++;
}
if (spsIndex != -1 && ppsIndex != -1) {
byte[] sps = new byte[ppsIndex];
System.arraycopy(csd, spsIndex, sps, 0, ppsIndex);
byte[] pps = new byte[length - ppsIndex];
System.arraycopy(csd, ppsIndex, pps, 0, length - ppsIndex);
return new Pair<>(ByteBuffer.wrap(sps), ByteBuffer.wrap(pps));
}
return null;
}

/**
* You need find 0 0 0 1 byte sequence that is the initiation of vps, sps and pps
* buffers.
*
* @param csd0byteBuffer get in mediacodec case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED
* @return list with vps, sps and pps
*/
public static List<ByteBuffer> extractVpsSpsPpsFromH265(ByteBuffer csd0byteBuffer) {
List<ByteBuffer> byteBufferList = new ArrayList<>();
int vpsPosition = -1;
int spsPosition = -1;
int ppsPosition = -1;
int contBufferInitiation = 0;
int length = csd0byteBuffer.remaining();
byte[] csdArray = new byte[length];
csd0byteBuffer.get(csdArray, 0, length);
csd0byteBuffer.rewind();
for (int i = 0; i < csdArray.length; i++) {
if (contBufferInitiation == 3 && csdArray[i] == 1) {
if (vpsPosition == -1) {
vpsPosition = i - 3;
} else if (spsPosition == -1) {
spsPosition = i - 3;
} else {
ppsPosition = i - 3;
}
}
if (csdArray[i] == 0) {
contBufferInitiation++;
} else {
contBufferInitiation = 0;
}
}
byte[] vps = new byte[spsPosition];
byte[] sps = new byte[ppsPosition - spsPosition];
byte[] pps = new byte[csdArray.length - ppsPosition];
for (int i = 0; i < csdArray.length; i++) {
if (i < spsPosition) {
vps[i] = csdArray[i];
} else if (i < ppsPosition) {
sps[i - spsPosition] = csdArray[i];
} else {
pps[i - ppsPosition] = csdArray[i];
}
}
byteBufferList.add(ByteBuffer.wrap(vps));
byteBufferList.add(ByteBuffer.wrap(sps));
byteBufferList.add(ByteBuffer.wrap(pps));
return byteBufferList;
}

/**
*
* @param buffer key frame
* @return av1 ObuSequence
*/
public static ByteBuffer extractObuSequence(ByteBuffer buffer, MediaCodec.BufferInfo bufferInfo) {
//we can only extract info from keyframes
if (bufferInfo.flags != MediaCodec.BUFFER_FLAG_KEY_FRAME) return null;
byte[] av1Data = new byte[buffer.remaining()];
buffer.get(av1Data);
Av1Parser av1Parser = new Av1Parser();
List<Obu> obuList = av1Parser.getObus(av1Data);
for (Obu obu: obuList) {
if (av1Parser.getObuType(obu.getHeader()[0]) == ObuType.SEQUENCE_HEADER) {
return ByteBuffer.wrap(obu.getFullData());
}
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -952,7 +952,10 @@ public RecordController.Status getRecordStatus() {
protected abstract void getVideoDataImp(ByteBuffer videoBuffer, MediaCodec.BufferInfo info);

public void setRecordController(BaseRecordController recordController) {
if (!isRecording()) this.recordController = recordController;
if (!isRecording()) {
recordController.updateInfo(this.recordController);
this.recordController = recordController;
}
}

private final GetCameraData getCameraData = frame -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1048,7 +1048,10 @@ public boolean isOnPreview() {
protected abstract void getVideoDataImp(ByteBuffer videoBuffer, MediaCodec.BufferInfo info);

public void setRecordController(BaseRecordController recordController) {
if (!isRecording()) this.recordController = recordController;
if (!isRecording()) {
recordController.updateInfo(this.recordController);
this.recordController = recordController;
}
}

private final GetMicrophoneData getMicrophoneData = frame -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,10 @@ public RecordController.Status getRecordStatus() {
protected abstract void getVideoDataImp(ByteBuffer videoBuffer, MediaCodec.BufferInfo info);

public void setRecordController(BaseRecordController recordController) {
if (!isRecording()) this.recordController = recordController;
if (!isRecording()) {
recordController.updateInfo(this.recordController);
this.recordController = recordController;
}
}

private final GetMicrophoneData getMicrophoneData = frame -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -729,7 +729,10 @@ public void setAudioExtractor(Extractor extractor) {
protected abstract void getAudioDataImp(ByteBuffer audioBuffer, MediaCodec.BufferInfo info);

public void setRecordController(BaseRecordController recordController) {
if (!isRecording()) this.recordController = recordController;
if (!isRecording()) {
recordController.updateInfo(this.recordController);
this.recordController = recordController;
}
}

private final GetMicrophoneData getMicrophoneData = frame -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,10 @@ public boolean resetAudioEncoder() {
protected abstract void getAudioDataImp(ByteBuffer audioBuffer, MediaCodec.BufferInfo info);

public void setRecordController(BaseRecordController recordController) {
if (!isRecording()) this.recordController = recordController;
if (!isRecording()) {
recordController.updateInfo(this.recordController);
this.recordController = recordController;
}
}

private final GetMicrophoneData getMicrophoneData = frame -> {
Expand Down
8 changes: 5 additions & 3 deletions library/src/main/java/com/pedro/library/base/StreamBase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,10 @@ abstract class StreamBase(
* This method allow record in other format or even create your custom implementation and record in a new format.
*/
fun setRecordController(recordController: BaseRecordController) {
if (!isRecording) this.recordController = recordController
if (!isRecording) {
recordController.updateInfo(this.recordController)
this.recordController = recordController
}
}

/**
Expand Down Expand Up @@ -593,8 +596,8 @@ abstract class StreamBase(

override fun getVideoData(videoBuffer: ByteBuffer, info: MediaCodec.BufferInfo) {
fpsListener.calculateFps()
getVideoDataImp(videoBuffer, info)
if (!differentRecordResolution) recordController.recordVideo(videoBuffer, info)
getVideoDataImp(videoBuffer, info)
}

override fun onVideoFormat(mediaFormat: MediaFormat) {
Expand All @@ -613,7 +616,6 @@ abstract class StreamBase(
}

override fun onVideoFormat(mediaFormat: MediaFormat) {
val isOnlyVideo = audioSource is NoAudioSource
recordController.setVideoFormat(mediaFormat)
}
}
Expand Down
Loading