Skip to content

Commit 49d8ec8

Browse files
authored
Merge pull request #1998 from pedroSG94/feature/flv-record-h265-av1
Feature/flv record h265 av1
2 parents 5dc1671 + b649ae6 commit 49d8ec8

28 files changed

Lines changed: 571 additions & 184 deletions

File tree

app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,12 @@ class CameraFragment: Fragment(), ConnectChecker {
142142
if (!folder.exists()) folder.mkdir()
143143
val sdf = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault())
144144
recordPath = "${folder.absolutePath}/${sdf.format(Date())}.mp4"
145+
bRecord.setImageResource(R.drawable.pause_icon)
145146
genericStream.startRecord(recordPath) { status ->
146147
if (status == RecordController.Status.RECORDING) {
147148
bRecord.setImageResource(R.drawable.stop_icon)
148149
}
149150
}
150-
bRecord.setImageResource(R.drawable.pause_icon)
151151
} else {
152152
genericStream.stopRecord()
153153
bRecord.setImageResource(R.drawable.record_icon)

common/src/main/java/com/pedro/common/Extensions.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ fun ByteBuffer.removeInfo(info: MediaFrame.Info): ByteBuffer {
7272
try {
7373
position(info.offset)
7474
limit(info.size)
75-
} catch (ignored: Exception) { }
75+
} catch (_: Exception) { }
7676
return slice()
7777
}
7878

encoder/src/main/java/com/pedro/encoder/video/VideoEncoder.java

Lines changed: 8 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -325,15 +325,15 @@ private boolean sendSPSandPPS(MediaFormat mediaFormat) {
325325
ByteBuffer bufferInfo = mediaFormat.getByteBuffer("csd-0");
326326
//we need an av1ConfigurationRecord with sequenceObu to work
327327
if (bufferInfo != null && bufferInfo.remaining() > 4) {
328-
oldSps = bufferInfo;
329-
getVideoData.onVideoInfo(bufferInfo, null, null);
328+
oldSps = bufferInfo.duplicate();
329+
getVideoData.onVideoInfo(oldSps, null, null);
330330
return true;
331331
}
332332
//H265
333333
} else if (type.equals(CodecUtil.H265_MIME)) {
334334
ByteBuffer bufferInfo = mediaFormat.getByteBuffer("csd-0");
335335
if (bufferInfo != null) {
336-
List<ByteBuffer> byteBufferList = extractVpsSpsPpsFromH265(bufferInfo);
336+
List<ByteBuffer> byteBufferList = VideoEncoderHelper.extractVpsSpsPpsFromH265(bufferInfo.duplicate());
337337
oldSps = byteBufferList.get(1);
338338
oldPps = byteBufferList.get(2);
339339
oldVps = byteBufferList.get(0);
@@ -345,8 +345,8 @@ private boolean sendSPSandPPS(MediaFormat mediaFormat) {
345345
ByteBuffer sps = mediaFormat.getByteBuffer("csd-0");
346346
ByteBuffer pps = mediaFormat.getByteBuffer("csd-1");
347347
if (sps != null && pps != null) {
348-
oldSps = sps;
349-
oldPps = pps;
348+
oldSps = sps.duplicate();
349+
oldPps = pps.duplicate();
350350
oldVps = null;
351351
getVideoData.onVideoInfo(oldSps, oldPps, oldVps);
352352
return true;
@@ -393,108 +393,6 @@ protected MediaCodecInfo chooseEncoder(String mime) {
393393
return null;
394394
}
395395

396-
/**
397-
* decode sps and pps if the encoder never call to MediaCodec.INFO_OUTPUT_FORMAT_CHANGED
398-
*/
399-
private Pair<ByteBuffer, ByteBuffer> decodeSpsPpsFromBuffer(ByteBuffer outputBuffer, int length) {
400-
byte[] csd = new byte[length];
401-
outputBuffer.get(csd, 0, length);
402-
outputBuffer.rewind();
403-
int i = 0;
404-
int spsIndex = -1;
405-
int ppsIndex = -1;
406-
while (i < length - 4) {
407-
if (csd[i] == 0 && csd[i + 1] == 0 && csd[i + 2] == 0 && csd[i + 3] == 1) {
408-
if (spsIndex == -1) {
409-
spsIndex = i;
410-
} else {
411-
ppsIndex = i;
412-
break;
413-
}
414-
}
415-
i++;
416-
}
417-
if (spsIndex != -1 && ppsIndex != -1) {
418-
byte[] sps = new byte[ppsIndex];
419-
System.arraycopy(csd, spsIndex, sps, 0, ppsIndex);
420-
byte[] pps = new byte[length - ppsIndex];
421-
System.arraycopy(csd, ppsIndex, pps, 0, length - ppsIndex);
422-
return new Pair<>(ByteBuffer.wrap(sps), ByteBuffer.wrap(pps));
423-
}
424-
return null;
425-
}
426-
427-
/**
428-
* You need find 0 0 0 1 byte sequence that is the initiation of vps, sps and pps
429-
* buffers.
430-
*
431-
* @param csd0byteBuffer get in mediacodec case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED
432-
* @return list with vps, sps and pps
433-
*/
434-
private List<ByteBuffer> extractVpsSpsPpsFromH265(ByteBuffer csd0byteBuffer) {
435-
List<ByteBuffer> byteBufferList = new ArrayList<>();
436-
int vpsPosition = -1;
437-
int spsPosition = -1;
438-
int ppsPosition = -1;
439-
int contBufferInitiation = 0;
440-
int length = csd0byteBuffer.remaining();
441-
byte[] csdArray = new byte[length];
442-
csd0byteBuffer.get(csdArray, 0, length);
443-
csd0byteBuffer.rewind();
444-
for (int i = 0; i < csdArray.length; i++) {
445-
if (contBufferInitiation == 3 && csdArray[i] == 1) {
446-
if (vpsPosition == -1) {
447-
vpsPosition = i - 3;
448-
} else if (spsPosition == -1) {
449-
spsPosition = i - 3;
450-
} else {
451-
ppsPosition = i - 3;
452-
}
453-
}
454-
if (csdArray[i] == 0) {
455-
contBufferInitiation++;
456-
} else {
457-
contBufferInitiation = 0;
458-
}
459-
}
460-
byte[] vps = new byte[spsPosition];
461-
byte[] sps = new byte[ppsPosition - spsPosition];
462-
byte[] pps = new byte[csdArray.length - ppsPosition];
463-
for (int i = 0; i < csdArray.length; i++) {
464-
if (i < spsPosition) {
465-
vps[i] = csdArray[i];
466-
} else if (i < ppsPosition) {
467-
sps[i - spsPosition] = csdArray[i];
468-
} else {
469-
pps[i - ppsPosition] = csdArray[i];
470-
}
471-
}
472-
byteBufferList.add(ByteBuffer.wrap(vps));
473-
byteBufferList.add(ByteBuffer.wrap(sps));
474-
byteBufferList.add(ByteBuffer.wrap(pps));
475-
return byteBufferList;
476-
}
477-
478-
/**
479-
*
480-
* @param buffer key frame
481-
* @return av1 ObuSequence
482-
*/
483-
private ByteBuffer extractObuSequence(ByteBuffer buffer, MediaCodec.BufferInfo bufferInfo) {
484-
//we can only extract info from keyframes
485-
if (bufferInfo.flags != MediaCodec.BUFFER_FLAG_KEY_FRAME) return null;
486-
byte[] av1Data = new byte[buffer.remaining()];
487-
buffer.get(av1Data);
488-
Av1Parser av1Parser = new Av1Parser();
489-
List<Obu> obuList = av1Parser.getObus(av1Data);
490-
for (Obu obu: obuList) {
491-
if (av1Parser.getObuType(obu.getHeader()[0]) == ObuType.SEQUENCE_HEADER) {
492-
return ByteBuffer.wrap(obu.getFullData());
493-
}
494-
}
495-
return null;
496-
}
497-
498396
@Override
499397
protected Frame getInputFrame() throws InterruptedException {
500398
Frame frame = queue.take();
@@ -534,7 +432,7 @@ protected void checkBuffer(@NonNull ByteBuffer byteBuffer, @NonNull MediaCodec.B
534432
fixTimeStamp(bufferInfo);
535433
if (!spsPpsSetted && type.equals(CodecUtil.H264_MIME)) {
536434
Log.i(TAG, "formatChanged not called, doing manual sps/pps extraction...");
537-
Pair<ByteBuffer, ByteBuffer> buffers = decodeSpsPpsFromBuffer(byteBuffer.duplicate(), bufferInfo.size);
435+
Pair<ByteBuffer, ByteBuffer> buffers = VideoEncoderHelper.decodeSpsPpsFromBuffer(byteBuffer.duplicate(), bufferInfo.size);
538436
if (buffers != null) {
539437
Log.i(TAG, "manual sps/pps extraction success");
540438
oldSps = buffers.first;
@@ -547,7 +445,7 @@ protected void checkBuffer(@NonNull ByteBuffer byteBuffer, @NonNull MediaCodec.B
547445
}
548446
} else if (!spsPpsSetted && type.equals(CodecUtil.H265_MIME)) {
549447
Log.i(TAG, "formatChanged not called, doing manual vps/sps/pps extraction...");
550-
List<ByteBuffer> byteBufferList = extractVpsSpsPpsFromH265(byteBuffer.duplicate());
448+
List<ByteBuffer> byteBufferList = VideoEncoderHelper.extractVpsSpsPpsFromH265(byteBuffer.duplicate());
551449
if (byteBufferList.size() == 3) {
552450
Log.i(TAG, "manual vps/sps/pps extraction success");
553451
oldSps = byteBufferList.get(1);
@@ -560,7 +458,7 @@ protected void checkBuffer(@NonNull ByteBuffer byteBuffer, @NonNull MediaCodec.B
560458
}
561459
} else if (!spsPpsSetted && type.equals(CodecUtil.AV1_MIME)) {
562460
Log.i(TAG, "formatChanged not called, doing manual av1 extraction...");
563-
ByteBuffer obuSequence = extractObuSequence(byteBuffer.duplicate(), bufferInfo);
461+
ByteBuffer obuSequence = VideoEncoderHelper.extractObuSequence(byteBuffer.duplicate(), bufferInfo);
564462
if (obuSequence != null) {
565463
oldSps = obuSequence;
566464
getVideoData.onVideoInfo(obuSequence, null, null);
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
*
3+
* * Copyright (C) 2024 pedroSG94.
4+
* *
5+
* * Licensed under the Apache License, Version 2.0 (the "License");
6+
* * you may not use this file except in compliance with the License.
7+
* * You may obtain a copy of the License at
8+
* *
9+
* * http://www.apache.org/licenses/LICENSE-2.0
10+
* *
11+
* * Unless required by applicable law or agreed to in writing, software
12+
* * distributed under the License is distributed on an "AS IS" BASIS,
13+
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* * See the License for the specific language governing permissions and
15+
* * limitations under the License.
16+
*
17+
*/
18+
19+
package com.pedro.encoder.video;
20+
21+
import android.media.MediaCodec;
22+
import android.util.Pair;
23+
24+
import com.pedro.common.av1.Av1Parser;
25+
import com.pedro.common.av1.Obu;
26+
import com.pedro.common.av1.ObuType;
27+
28+
import java.nio.ByteBuffer;
29+
import java.util.ArrayList;
30+
import java.util.List;
31+
32+
/**
33+
* Created by pedro on 3/12/25.
34+
*/
35+
public class VideoEncoderHelper {
36+
/**
37+
* decode sps and pps if the encoder never call to MediaCodec.INFO_OUTPUT_FORMAT_CHANGED
38+
*/
39+
public static Pair<ByteBuffer, ByteBuffer> decodeSpsPpsFromBuffer(ByteBuffer outputBuffer, int length) {
40+
byte[] csd = new byte[length];
41+
outputBuffer.get(csd, 0, length);
42+
outputBuffer.rewind();
43+
int i = 0;
44+
int spsIndex = -1;
45+
int ppsIndex = -1;
46+
while (i < length - 4) {
47+
if (csd[i] == 0 && csd[i + 1] == 0 && csd[i + 2] == 0 && csd[i + 3] == 1) {
48+
if (spsIndex == -1) {
49+
spsIndex = i;
50+
} else {
51+
ppsIndex = i;
52+
break;
53+
}
54+
}
55+
i++;
56+
}
57+
if (spsIndex != -1 && ppsIndex != -1) {
58+
byte[] sps = new byte[ppsIndex];
59+
System.arraycopy(csd, spsIndex, sps, 0, ppsIndex);
60+
byte[] pps = new byte[length - ppsIndex];
61+
System.arraycopy(csd, ppsIndex, pps, 0, length - ppsIndex);
62+
return new Pair<>(ByteBuffer.wrap(sps), ByteBuffer.wrap(pps));
63+
}
64+
return null;
65+
}
66+
67+
/**
68+
* You need find 0 0 0 1 byte sequence that is the initiation of vps, sps and pps
69+
* buffers.
70+
*
71+
* @param csd0byteBuffer get in mediacodec case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED
72+
* @return list with vps, sps and pps
73+
*/
74+
public static List<ByteBuffer> extractVpsSpsPpsFromH265(ByteBuffer csd0byteBuffer) {
75+
List<ByteBuffer> byteBufferList = new ArrayList<>();
76+
int vpsPosition = -1;
77+
int spsPosition = -1;
78+
int ppsPosition = -1;
79+
int contBufferInitiation = 0;
80+
int length = csd0byteBuffer.remaining();
81+
byte[] csdArray = new byte[length];
82+
csd0byteBuffer.get(csdArray, 0, length);
83+
csd0byteBuffer.rewind();
84+
for (int i = 0; i < csdArray.length; i++) {
85+
if (contBufferInitiation == 3 && csdArray[i] == 1) {
86+
if (vpsPosition == -1) {
87+
vpsPosition = i - 3;
88+
} else if (spsPosition == -1) {
89+
spsPosition = i - 3;
90+
} else {
91+
ppsPosition = i - 3;
92+
}
93+
}
94+
if (csdArray[i] == 0) {
95+
contBufferInitiation++;
96+
} else {
97+
contBufferInitiation = 0;
98+
}
99+
}
100+
if (vpsPosition == -1 || spsPosition == -1 || ppsPosition == -1) return byteBufferList;
101+
byte[] vps = new byte[spsPosition];
102+
byte[] sps = new byte[ppsPosition - spsPosition];
103+
byte[] pps = new byte[csdArray.length - ppsPosition];
104+
for (int i = 0; i < csdArray.length; i++) {
105+
if (i < spsPosition) {
106+
vps[i] = csdArray[i];
107+
} else if (i < ppsPosition) {
108+
sps[i - spsPosition] = csdArray[i];
109+
} else {
110+
pps[i - ppsPosition] = csdArray[i];
111+
}
112+
}
113+
byteBufferList.add(ByteBuffer.wrap(vps));
114+
byteBufferList.add(ByteBuffer.wrap(sps));
115+
byteBufferList.add(ByteBuffer.wrap(pps));
116+
return byteBufferList;
117+
}
118+
119+
/**
120+
*
121+
* @param buffer key frame
122+
* @return av1 ObuSequence
123+
*/
124+
public static ByteBuffer extractObuSequence(ByteBuffer buffer, MediaCodec.BufferInfo bufferInfo) {
125+
//we can only extract info from keyframes
126+
if (bufferInfo.flags != MediaCodec.BUFFER_FLAG_KEY_FRAME) return null;
127+
byte[] av1Data = new byte[buffer.remaining()];
128+
buffer.get(av1Data);
129+
Av1Parser av1Parser = new Av1Parser();
130+
List<Obu> obuList = av1Parser.getObus(av1Data);
131+
for (Obu obu: obuList) {
132+
if (av1Parser.getObuType(obu.getHeader()[0]) == ObuType.SEQUENCE_HEADER) {
133+
return ByteBuffer.wrap(obu.getFullData());
134+
}
135+
}
136+
return null;
137+
}
138+
}

library/src/main/java/com/pedro/library/base/Camera1Base.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -952,7 +952,10 @@ public RecordController.Status getRecordStatus() {
952952
protected abstract void getVideoDataImp(ByteBuffer videoBuffer, MediaCodec.BufferInfo info);
953953

954954
public void setRecordController(BaseRecordController recordController) {
955-
if (!isRecording()) this.recordController = recordController;
955+
if (!isRecording()) {
956+
recordController.updateInfo(this.recordController);
957+
this.recordController = recordController;
958+
}
956959
}
957960

958961
private final GetCameraData getCameraData = frame -> {

library/src/main/java/com/pedro/library/base/Camera2Base.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1048,7 +1048,10 @@ public boolean isOnPreview() {
10481048
protected abstract void getVideoDataImp(ByteBuffer videoBuffer, MediaCodec.BufferInfo info);
10491049

10501050
public void setRecordController(BaseRecordController recordController) {
1051-
if (!isRecording()) this.recordController = recordController;
1051+
if (!isRecording()) {
1052+
recordController.updateInfo(this.recordController);
1053+
this.recordController = recordController;
1054+
}
10521055
}
10531056

10541057
private final GetMicrophoneData getMicrophoneData = frame -> {

library/src/main/java/com/pedro/library/base/DisplayBase.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,10 @@ public RecordController.Status getRecordStatus() {
573573
protected abstract void getVideoDataImp(ByteBuffer videoBuffer, MediaCodec.BufferInfo info);
574574

575575
public void setRecordController(BaseRecordController recordController) {
576-
if (!isRecording()) this.recordController = recordController;
576+
if (!isRecording()) {
577+
recordController.updateInfo(this.recordController);
578+
this.recordController = recordController;
579+
}
577580
}
578581

579582
private final GetMicrophoneData getMicrophoneData = frame -> {

library/src/main/java/com/pedro/library/base/FromFileBase.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -729,7 +729,10 @@ public void setAudioExtractor(Extractor extractor) {
729729
protected abstract void getAudioDataImp(ByteBuffer audioBuffer, MediaCodec.BufferInfo info);
730730

731731
public void setRecordController(BaseRecordController recordController) {
732-
if (!isRecording()) this.recordController = recordController;
732+
if (!isRecording()) {
733+
recordController.updateInfo(this.recordController);
734+
this.recordController = recordController;
735+
}
733736
}
734737

735738
private final GetMicrophoneData getMicrophoneData = frame -> {

library/src/main/java/com/pedro/library/base/OnlyAudioBase.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,10 @@ public boolean resetAudioEncoder() {
284284
protected abstract void getAudioDataImp(ByteBuffer audioBuffer, MediaCodec.BufferInfo info);
285285

286286
public void setRecordController(BaseRecordController recordController) {
287-
if (!isRecording()) this.recordController = recordController;
287+
if (!isRecording()) {
288+
recordController.updateInfo(this.recordController);
289+
this.recordController = recordController;
290+
}
288291
}
289292

290293
private final GetMicrophoneData getMicrophoneData = frame -> {

0 commit comments

Comments
 (0)