Skip to content

Commit 6e10c57

Browse files
authored
Merge pull request #211 from Botts-Innovative-Research/ffmpeg-support-for-h265
Add support for H264/HEVC in FFMPEG
2 parents 5231ea8 + 58fd36e commit 6e10c57

2 files changed

Lines changed: 81 additions & 7 deletions

File tree

sensors/video/sensorhub-driver-ffmpeg/src/main/java/org/sensorhub/impl/sensor/ffmpeg/outputs/VideoOutput.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
public class VideoOutput<T extends ISensorModule<?>> extends AbstractSensorOutput<T> implements DataBufferListener {
3737
private static final String CODEC_MJPEG = "JPEG";
3838
private static final String CODEC_H264 = "H264";
39+
private static final String CODEC_H265 = "H265";
3940
private static final Logger logger = LoggerFactory.getLogger(VideoOutput.class.getSimpleName());
4041
private static final int MAX_NUM_TIMING_SAMPLES = 10;
4142

@@ -83,11 +84,13 @@ public VideoOutput(T parentSensor, int[] videoFrameDimensions, String codecName,
8384
this.codecName = CODEC_MJPEG;
8485
} else if (codecName.equalsIgnoreCase("h264")) {
8586
this.codecName = CODEC_H264;
87+
} else if (codecName.equalsIgnoreCase("hevc") || codecName.equalsIgnoreCase("h265")) {
88+
this.codecName = CODEC_H265;
8689
} else {
8790
this.codecName = codecName;
88-
logger.warn("Codec {} is neither H264 nor MJPEG. Compression set using FFmpeg name.", codecName);
91+
logger.warn("Codec {} is not one of H264, H265, or MJPEG. Compression set using FFmpeg name.", codecName);
8992
((AbstractModule<?>) parentSensor).reportStatus("Video codec = " + codecName.toUpperCase() + ". Use the transcoder process module" +
90-
" with H264 or MJPEG output for compatibility with OSH visualization and serialization.");
93+
" with H264, H265, or MJPEG output for compatibility with OSH visualization and serialization.");
9194
}
9295

9396
logger.debug("Video output created.");

sensors/video/sensorhub-driver-ffmpeg/src/main/java/org/sensorhub/mpegts/StreamContext.java

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,16 @@ public void openCodecContext(AVFormatContext avFormatContext) throws IllegalStat
154154
setCodecName(codec.name().getString());
155155
setCodecId(codec.id());
156156

157-
if (isInjectingExtradata && codecId == avcodec.AV_CODEC_ID_H264) {
157+
if (isInjectingExtradata) {
158158
BytePointer extra = params.extradata();
159159
int extraLen = params.extradata_size();
160-
extraData = getAnnexBExtradata(extra, extraLen);
160+
if (codecId == avcodec.AV_CODEC_ID_H264) {
161+
extraData = getAnnexBExtradata(extra, extraLen);
162+
} else if (codecId == avcodec.AV_CODEC_ID_HEVC) {
163+
extraData = getHvccAnnexBExtradata(extra, extraLen);
164+
} else {
165+
extraData = null;
166+
}
161167
} else {
162168
extraData = null;
163169
}
@@ -178,7 +184,9 @@ public void processPacket(AVPacket avPacket) {
178184
byte[] dataBuffer = new byte[avPacket.size()];
179185
avPacket.data().get(dataBuffer);
180186

181-
// Add extradata if the packet has an h264 keyframe
187+
// Prepend cached parameter sets ahead of every keyframe so late-join
188+
// decoders have the VPS/SPS/PPS they need. Applies to both H.264 (SPS/PPS)
189+
// and HEVC (VPS/SPS/PPS) when extradata injection is enabled.
182190
if (extraData != null && (avPacket.flags() & avcodec.AV_PKT_FLAG_KEY) != 0) {
183191
getDataBufferListener().onDataBuffer(new DataBufferRecord(avPacket.pts() * getStreamTimeBase(), extraData));
184192
}
@@ -187,8 +195,9 @@ public void processPacket(AVPacket avPacket) {
187195
}
188196

189197
/**
190-
* Converts the given extradata from AVCC format to Annex B format.
191-
* If the data is already in Annex B format, it is returned directly.
198+
* Converts the given H.264 extradata from AVCC format (AVCDecoderConfigurationRecord,
199+
* ISO/IEC 14496-15 §5.2.4.1) to Annex B format. If the data is already in Annex B format,
200+
* it is returned directly.
192201
*
193202
* @param extradata A BytePointer containing the codec extradata. Must not be null.
194203
* @param size The size of the extradata in bytes.
@@ -235,4 +244,66 @@ private byte[] getAnnexBExtradata(BytePointer extradata, int size) {
235244
}
236245
return out.toByteArray();
237246
}
247+
248+
/**
249+
* Converts HEVC extradata from HVCC format (HEVCDecoderConfigurationRecord,
250+
* ISO/IEC 14496-15 §8.3.3.1.2) to Annex B format. Walks the {@code numOfArrays}
251+
* table and emits every contained NAL (typically VPS=32, SPS=33, PPS=34) prefixed
252+
* with the 4-byte 0x00000001 start code so that downstream pipelines can decode
253+
* without out-of-band parameter sets.
254+
* <p>
255+
* If the data is already in Annex B format, it is returned directly.
256+
*
257+
* @param extradata A BytePointer containing the codec extradata. Must not be null.
258+
* @param size The size of the extradata in bytes.
259+
* @return A byte array containing the Annex B formatted VPS/SPS/PPS NALs, or
260+
* {@code null} if the data is invalid, too short, or cannot be parsed.
261+
*/
262+
private byte[] getHvccAnnexBExtradata(BytePointer extradata, int size) {
263+
// HVCC has a 22-byte fixed header followed by numOfArrays (1 byte),
264+
// so the minimum useful size is 23 bytes.
265+
if (extradata == null || size < 23) return null;
266+
267+
byte[] data = new byte[size];
268+
extradata.get(data);
269+
270+
// If already Annex B, pass straight through.
271+
if (data[0] == 0x00 && data[1] == 0x00 && (data[2] == 0x01 || (data[2] == 0x00 && data[3] == 0x01))) {
272+
return data;
273+
}
274+
275+
// configurationVersion must be 1 per ISO/IEC 14496-15.
276+
if ((data[0] & 0xFF) != 1) {
277+
logger.warn("Unsupported HVCC configurationVersion: {}", data[0] & 0xFF);
278+
return null;
279+
}
280+
281+
ByteArrayOutputStream out = new ByteArrayOutputStream();
282+
try {
283+
int pos = 22;
284+
int numOfArrays = data[pos++] & 0xFF;
285+
286+
for (int i = 0; i < numOfArrays; i++) {
287+
if (pos + 3 > data.length) return null;
288+
289+
pos++;
290+
int numNalus = ((data[pos++] & 0xFF) << 8) | (data[pos++] & 0xFF);
291+
292+
for (int j = 0; j < numNalus; j++) {
293+
if (pos + 2 > data.length) return null;
294+
int nalLen = ((data[pos++] & 0xFF) << 8) | (data[pos++] & 0xFF);
295+
if (nalLen <= 0 || pos + nalLen > data.length) return null;
296+
out.write(new byte[]{0x00, 0x00, 0x00, 0x01});
297+
out.write(data, pos, nalLen);
298+
pos += nalLen;
299+
}
300+
}
301+
} catch (Exception e) {
302+
logger.error("Error extracting VPS/SPS/PPS from HVCC extradata", e);
303+
return null;
304+
}
305+
306+
byte[] result = out.toByteArray();
307+
return result.length == 0 ? null : result;
308+
}
238309
}

0 commit comments

Comments
 (0)