Skip to content

Commit c2f95fa

Browse files
committed
chore: refactor H.264 depay as a class
Instead of keeping the buffer outside of the parser, use a class instead which keeps track of the buffer internally. The `.parse` method on the class either returns a new H.264 message, or null (in case there is no message yet). In all cases, it keeps an internal buffer with data that's part of the next message. This leverages the parser's logic to determine if the message is an I-frame in order to determine the start of the stream (instead of coding it explicitly in the transform stream).
1 parent f7dca94 commit c2f95fa

File tree

2 files changed

+76
-76
lines changed

2 files changed

+76
-76
lines changed

lib/components/h264depay/index.ts

+17-24
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,18 @@
11
import { Tube } from '../component'
22
import { Transform } from 'stream'
3-
import { MessageType, Message, RtpMessage } from '../message'
3+
import { MessageType, Message } from '../message'
44
import { VideoMedia } from '../../utils/protocols/sdp'
5-
import { payload, payloadType } from '../../utils/protocols/rtp'
6-
import { h264depay } from './parser'
5+
import { payloadType } from '../../utils/protocols/rtp'
6+
import { H264DepayParser, NAL_TYPES } from './parser'
77

88
export class H264Depay extends Tube {
99
constructor() {
1010
let h264PayloadType: number
1111
let idrFound = false
1212

13-
// Incoming
14-
15-
let buffer = Buffer.alloc(0)
16-
let parseMessage: (buffer: Buffer, rtp: RtpMessage) => Buffer = () =>
17-
Buffer.alloc(0)
13+
const h264DepayParser = new H264DepayParser()
1814

19-
const checkIdr = (msg: RtpMessage) => {
20-
const rtpPayload = payload(msg.data)
21-
const nalType = rtpPayload[0] & 0x1f
22-
const fuNalType = rtpPayload[1] & 0x1f
23-
if ((nalType === 28 && fuNalType === 5) || nalType === 5) {
24-
idrFound = true
25-
}
26-
}
15+
// Incoming
2716

2817
const incoming = new Transform({
2918
objectMode: true,
@@ -45,21 +34,25 @@ export class H264Depay extends Tube {
4534
msg.type === MessageType.RTP &&
4635
payloadType(msg.data) === h264PayloadType
4736
) {
48-
if (!idrFound) {
49-
checkIdr(msg)
50-
}
51-
if (idrFound) {
52-
buffer = parseMessage(buffer, msg)
37+
const h264Message = h264DepayParser.parse(msg)
38+
39+
// Skip if not a full H264 frame, or when there hasn't been an I-frame yet
40+
if (
41+
h264Message === null ||
42+
(!idrFound && h264Message.nalType !== NAL_TYPES.IDR_PICTURE)
43+
) {
44+
callback()
45+
return
5346
}
54-
callback()
47+
48+
idrFound = true
49+
callback(undefined, h264Message)
5550
} else {
5651
// Not a message we should handle
5752
callback(undefined, msg)
5853
}
5954
},
6055
})
61-
const callback = incoming.push.bind(incoming)
62-
parseMessage = (buffer, rtp) => h264depay(buffer, rtp, callback)
6356

6457
// outgoing will be defaulted to a PassThrough stream
6558
super(incoming)

lib/components/h264depay/parser.ts

+59-52
Original file line numberDiff line numberDiff line change
@@ -28,30 +28,58 @@ First byte in payload (rtp payload header):
2828

2929
const h264Debug = debug('msl:h264depay')
3030

31-
export function h264depay(
32-
buffered: Buffer,
33-
rtp: RtpMessage,
34-
callback: (msg: H264Message) => void,
35-
) {
36-
const rtpPayload = payload(rtp.data)
37-
const type = rtpPayload[0] & 0x1f
31+
export class H264DepayParser {
32+
private _buffer: Buffer
3833

39-
if (type === 28) {
40-
/* FU-A NALU */ const fuIndicator = rtpPayload[0]
41-
const fuHeader = rtpPayload[1]
42-
const startBit = !!(fuHeader >> 7)
43-
const nalType = fuHeader & 0x1f
44-
const nal = (fuIndicator & 0xe0) | nalType
45-
const stopBit = fuHeader & 64
46-
if (startBit) {
47-
return Buffer.concat([
48-
Buffer.from([0, 0, 0, 0, nal]),
49-
rtpPayload.slice(2),
50-
])
51-
} else if (stopBit) {
52-
/* receieved end bit */ const h264frame = Buffer.concat([
53-
buffered,
54-
rtpPayload.slice(2),
34+
constructor() {
35+
this._buffer = Buffer.alloc(0)
36+
}
37+
38+
parse(rtp: RtpMessage): H264Message | null {
39+
const rtpPayload = payload(rtp.data)
40+
const type = rtpPayload[0] & 0x1f
41+
42+
if (type === 28) {
43+
/* FU-A NALU */ const fuIndicator = rtpPayload[0]
44+
const fuHeader = rtpPayload[1]
45+
const startBit = !!(fuHeader >> 7)
46+
const nalType = fuHeader & 0x1f
47+
const nal = (fuIndicator & 0xe0) | nalType
48+
const stopBit = fuHeader & 64
49+
if (startBit) {
50+
this._buffer = Buffer.concat([
51+
Buffer.from([0, 0, 0, 0, nal]),
52+
rtpPayload.slice(2),
53+
])
54+
return null
55+
} else if (stopBit) {
56+
/* receieved end bit */ const h264frame = Buffer.concat([
57+
this._buffer,
58+
rtpPayload.slice(2),
59+
])
60+
h264frame.writeUInt32BE(h264frame.length - 4, 0)
61+
const msg: H264Message = {
62+
data: h264frame,
63+
type: MessageType.H264,
64+
timestamp: timestamp(rtp.data),
65+
ntpTimestamp: rtp.ntpTimestamp,
66+
payloadType: payloadType(rtp.data),
67+
nalType: nalType,
68+
}
69+
this._buffer = Buffer.alloc(0)
70+
return msg
71+
} else {
72+
// Put the received data on the buffer and cut the header bytes
73+
this._buffer = Buffer.concat([this._buffer, rtpPayload.slice(2)])
74+
return null
75+
}
76+
} else if (
77+
(type === NAL_TYPES.NON_IDR_PICTURE || type === NAL_TYPES.IDR_PICTURE) &&
78+
this._buffer.length === 0
79+
) {
80+
/* Single NALU */ const h264frame = Buffer.concat([
81+
Buffer.from([0, 0, 0, 0]),
82+
rtpPayload,
5583
])
5684
h264frame.writeUInt32BE(h264frame.length - 4, 0)
5785
const msg: H264Message = {
@@ -60,37 +88,16 @@ export function h264depay(
6088
timestamp: timestamp(rtp.data),
6189
ntpTimestamp: rtp.ntpTimestamp,
6290
payloadType: payloadType(rtp.data),
63-
nalType: nalType,
91+
nalType: type,
6492
}
65-
callback(msg)
66-
return Buffer.alloc(0)
93+
this._buffer = Buffer.alloc(0)
94+
return msg
6795
} else {
68-
// Put the received data on the buffer and cut the header bytes
69-
return Buffer.concat([buffered, rtpPayload.slice(2)])
70-
}
71-
} else if (
72-
(type === NAL_TYPES.NON_IDR_PICTURE || type === NAL_TYPES.IDR_PICTURE) &&
73-
buffered.length === 0
74-
) {
75-
/* Single NALU */ const h264frame = Buffer.concat([
76-
Buffer.from([0, 0, 0, 0]),
77-
rtpPayload,
78-
])
79-
h264frame.writeUInt32BE(h264frame.length - 4, 0)
80-
const msg: H264Message = {
81-
data: h264frame,
82-
type: MessageType.H264,
83-
timestamp: timestamp(rtp.data),
84-
ntpTimestamp: rtp.ntpTimestamp,
85-
payloadType: payloadType(rtp.data),
86-
nalType: type,
96+
h264Debug(
97+
`H264depayComponent can only extract types 1,5 and 28, got ${type}`,
98+
)
99+
this._buffer = Buffer.alloc(0)
100+
return null
87101
}
88-
callback(msg)
89-
return Buffer.alloc(0)
90-
} else {
91-
h264Debug(
92-
`H264depayComponent can only extract types 1,5 and 28, got ${type}`,
93-
)
94-
return Buffer.alloc(0)
95102
}
96103
}

0 commit comments

Comments
 (0)