forked from zanjie1999/esp32-cam-rtsp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCStreamer.cpp
342 lines (287 loc) · 12.2 KB
/
CStreamer.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
#include "CStreamer.h"
#include <stdio.h>
CStreamer::CStreamer(SOCKET aClient, u_short width, u_short height) : m_Client(aClient)
{
printf("Creating TSP streamer\n");
m_RtpServerPort = 0;
m_RtcpServerPort = 0;
m_RtpClientPort = 0;
m_RtcpClientPort = 0;
m_SequenceNumber = 0;
m_Timestamp = 0;
m_SendIdx = 0;
m_TCPTransport = false;
m_RtpSocket = NULLSOCKET;
m_RtcpSocket = NULLSOCKET;
m_width = width;
m_height = height;
m_prevMsec = 0;
};
CStreamer::~CStreamer()
{
udpsocketclose(m_RtpSocket);
udpsocketclose(m_RtcpSocket);
};
int CStreamer::SendRtpPacket(unsigned const char * jpeg, int jpegLen, int fragmentOffset, BufPtr quant0tbl, BufPtr quant1tbl)
{
#define KRtpHeaderSize 12 // size of the RTP header
#define KJpegHeaderSize 8 // size of the special JPEG payload header
#define MAX_FRAGMENT_SIZE 1100 // FIXME, pick more carefully
int fragmentLen = MAX_FRAGMENT_SIZE;
if(fragmentLen + fragmentOffset > jpegLen) // Shrink last fragment if needed
fragmentLen = jpegLen - fragmentOffset;
bool isLastFragment = (fragmentOffset + fragmentLen) == jpegLen;
// Do we have custom quant tables? If so include them per RFC
bool includeQuantTbl = quant0tbl && quant1tbl && fragmentOffset == 0;
uint8_t q = includeQuantTbl ? 128 : 0x5e;
static char RtpBuf[2048]; // Note: we assume single threaded, this large buf we keep off of the tiny stack
int RtpPacketSize = fragmentLen + KRtpHeaderSize + KJpegHeaderSize + (includeQuantTbl ? (4 + 64 * 2) : 0);
memset(RtpBuf,0x00,sizeof(RtpBuf));
// Prepare the first 4 byte of the packet. This is the Rtp over Rtsp header in case of TCP based transport
RtpBuf[0] = '$'; // magic number
RtpBuf[1] = 0; // number of multiplexed subchannel on RTPS connection - here the RTP channel
RtpBuf[2] = (RtpPacketSize & 0x0000FF00) >> 8;
RtpBuf[3] = (RtpPacketSize & 0x000000FF);
// Prepare the 12 byte RTP header
RtpBuf[4] = 0x80; // RTP version
RtpBuf[5] = 0x1a | (isLastFragment ? 0x80 : 0x00); // JPEG payload (26) and marker bit
RtpBuf[7] = m_SequenceNumber & 0x0FF; // each packet is counted with a sequence counter
RtpBuf[6] = m_SequenceNumber >> 8;
RtpBuf[8] = (m_Timestamp & 0xFF000000) >> 24; // each image gets a timestamp
RtpBuf[9] = (m_Timestamp & 0x00FF0000) >> 16;
RtpBuf[10] = (m_Timestamp & 0x0000FF00) >> 8;
RtpBuf[11] = (m_Timestamp & 0x000000FF);
RtpBuf[12] = 0x13; // 4 byte SSRC (sychronization source identifier)
RtpBuf[13] = 0xf9; // we just an arbitrary number here to keep it simple
RtpBuf[14] = 0x7e;
RtpBuf[15] = 0x67;
// Prepare the 8 byte payload JPEG header
RtpBuf[16] = 0x00; // type specific
RtpBuf[17] = (fragmentOffset & 0x00FF0000) >> 16; // 3 byte fragmentation offset for fragmented images
RtpBuf[18] = (fragmentOffset & 0x0000FF00) >> 8;
RtpBuf[19] = (fragmentOffset & 0x000000FF);
/* These sampling factors indicate that the chrominance components of
type 0 video is downsampled horizontally by 2 (often called 4:2:2)
while the chrominance components of type 1 video are downsampled both
horizontally and vertically by 2 (often called 4:2:0). */
RtpBuf[20] = 0x00; // type (fixme might be wrong for camera data) https://tools.ietf.org/html/rfc2435
RtpBuf[21] = q; // quality scale factor was 0x5e
RtpBuf[22] = m_width / 8; // width / 8
RtpBuf[23] = m_height / 8; // height / 8
int headerLen = 24; // Inlcuding jpeg header but not qant table header
if(includeQuantTbl) { // we need a quant header - but only in first packet of the frame
//printf("inserting quanttbl\n");
RtpBuf[24] = 0; // MBZ
RtpBuf[25] = 0; // 8 bit precision
RtpBuf[26] = 0; // MSB of lentgh
int numQantBytes = 64; // Two 64 byte tables
RtpBuf[27] = 2 * numQantBytes; // LSB of length
headerLen += 4;
memcpy(RtpBuf + headerLen, quant0tbl, numQantBytes);
headerLen += numQantBytes;
memcpy(RtpBuf + headerLen, quant1tbl, numQantBytes);
headerLen += numQantBytes;
}
// printf("Sending timestamp %d, seq %d, fragoff %d, fraglen %d, jpegLen %d\n", m_Timestamp, m_SequenceNumber, fragmentOffset, fragmentLen, jpegLen);
// append the JPEG scan data to the RTP buffer
memcpy(RtpBuf + headerLen,jpeg + fragmentOffset, fragmentLen);
fragmentOffset += fragmentLen;
m_SequenceNumber++; // prepare the packet counter for the next packet
IPADDRESS otherip;
IPPORT otherport;
socketpeeraddr(m_Client, &otherip, &otherport);
// RTP marker bit must be set on last fragment
if (m_TCPTransport) // RTP over RTSP - we send the buffer + 4 byte additional header
socketsend(m_Client,RtpBuf,RtpPacketSize + 4);
else // UDP - we send just the buffer by skipping the 4 byte RTP over RTSP header
udpsocketsend(m_RtpSocket,&RtpBuf[4],RtpPacketSize, otherip, m_RtpClientPort);
return isLastFragment ? 0 : fragmentOffset;
};
void CStreamer::InitTransport(u_short aRtpPort, u_short aRtcpPort, bool TCP)
{
m_RtpClientPort = aRtpPort;
m_RtcpClientPort = aRtcpPort;
m_TCPTransport = TCP;
if (!m_TCPTransport)
{ // allocate port pairs for RTP/RTCP ports in UDP transport mode
for (u_short P = 6970; P < 0xFFFE; P += 2)
{
m_RtpSocket = udpsocketcreate(P);
if (m_RtpSocket)
{ // Rtp socket was bound successfully. Lets try to bind the consecutive Rtsp socket
m_RtcpSocket = udpsocketcreate(P + 1);
if (m_RtcpSocket)
{
m_RtpServerPort = P;
m_RtcpServerPort = P+1;
break;
}
else
{
udpsocketclose(m_RtpSocket);
udpsocketclose(m_RtcpSocket);
};
}
};
};
};
u_short CStreamer::GetRtpServerPort()
{
return m_RtpServerPort;
};
u_short CStreamer::GetRtcpServerPort()
{
return m_RtcpServerPort;
};
void CStreamer::streamFrame(unsigned const char *data, uint32_t dataLen, uint32_t curMsec)
{
if(m_prevMsec == 0) // first frame init our timestamp
m_prevMsec = curMsec;
// compute deltat (being careful to handle clock rollover with a little lie)
uint32_t deltams = (curMsec >= m_prevMsec) ? curMsec - m_prevMsec : 100;
m_prevMsec = curMsec;
// locate quant tables if possible
BufPtr qtable0, qtable1;
if(!decodeJPEGfile(&data, &dataLen, &qtable0, &qtable1)) {
printf("can't decode jpeg data\n");
return;
}
int offset = 0;
do {
offset = SendRtpPacket(data, dataLen, offset, qtable0, qtable1);
} while(offset != 0);
// Increment ONLY after a full frame
uint32_t units = 90000; // Hz per RFC 2435
m_Timestamp += (units * deltams / 1000); // fixed timestamp increment for a frame rate of 25fps
m_SendIdx++;
if (m_SendIdx > 1) m_SendIdx = 0;
};
#include <assert.h>
// search for a particular JPEG marker, moves *start to just after that marker
// This function fixes up the provided start ptr to point to the
// actual JPEG stream data and returns the number of bytes skipped
// APP0 e0
// DQT db
// DQT db
// DHT c4
// DHT c4
// DHT c4
// DHT c4
// SOF0 c0 baseline (not progressive) 3 color 0x01 Y, 0x21 2h1v, 0x00 tbl0
// - 0x02 Cb, 0x11 1h1v, 0x01 tbl1 - 0x03 Cr, 0x11 1h1v, 0x01 tbl1
// therefore 4:2:2, with two separate quant tables (0 and 1)
// SOS da
// EOI d9 (no need to strip data after this RFC says client will discard)
bool findJPEGheader(BufPtr *start, uint32_t *len, uint8_t marker) {
// per https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format
unsigned const char *bytes = *start;
// kinda skanky, will break if unlucky and the headers inxlucde 0xffda
// might fall off array if jpeg is invalid
// FIXME - return false instead
while(bytes - *start < *len) {
uint8_t framing = *bytes++; // better be 0xff
if(framing != 0xff) {
printf("malformed jpeg, framing=%x\n", framing);
return false;
}
uint8_t typecode = *bytes++;
if(typecode == marker) {
unsigned skipped = bytes - *start;
//printf("found marker 0x%x, skipped %d\n", marker, skipped);
*start = bytes;
// shrink len for the bytes we just skipped
*len -= skipped;
return true;
}
else {
// not the section we were looking for, skip the entire section
switch(typecode) {
case 0xd8: // start of image
{
break; // no data to skip
}
case 0xe0: // app0
case 0xdb: // dqt
case 0xc4: // dht
case 0xc0: // sof0
case 0xda: // sos
{
// standard format section with 2 bytes for len. skip that many bytes
uint32_t len = bytes[0] * 256 + bytes[1];
//printf("skipping section 0x%x, %d bytes\n", typecode, len);
bytes += len;
break;
}
default:
printf("unexpected jpeg typecode 0x%x\n", typecode);
break;
}
}
}
printf("failed to find jpeg marker 0x%x", marker);
return false;
}
// the scan data uses byte stuffing to guarantee anything that starts with 0xff
// followed by something not zero, is a new section. Look for that marker and return the ptr
// pointing there
void skipScanBytes(BufPtr *start) {
BufPtr bytes = *start;
while(true) { // FIXME, check against length
while(*bytes++ != 0xff);
if(*bytes++ != 0) {
*start = bytes - 2; // back up to the 0xff marker we just found
return;
}
}
}
void nextJpegBlock(BufPtr *bytes) {
uint32_t len = (*bytes)[0] * 256 + (*bytes)[1];
//printf("going to next jpeg block %d bytes\n", len);
*bytes += len;
}
// When JPEG is stored as a file it is wrapped in a container
// This function fixes up the provided start ptr to point to the
// actual JPEG stream data and returns the number of bytes skipped
bool decodeJPEGfile(BufPtr *start, uint32_t *len, BufPtr *qtable0, BufPtr *qtable1) {
// per https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format
unsigned const char *bytes = *start;
if(!findJPEGheader(&bytes, len, 0xd8)) // better at least look like a jpeg file
return false; // FAILED!
// Look for quant tables if they are present
*qtable0 = NULL;
*qtable1 = NULL;
BufPtr quantstart = *start;
uint32_t quantlen = *len;
if(!findJPEGheader(&quantstart, &quantlen, 0xdb)) {
printf("error can't find quant table 0\n");
}
else {
// printf("found quant table %x\n", quantstart[2]);
*qtable0 = quantstart + 3; // 3 bytes of header skipped
nextJpegBlock(&quantstart);
if(!findJPEGheader(&quantstart, &quantlen, 0xdb)) {
printf("error can't find quant table 1\n");
}
else {
// printf("found quant table %x\n", quantstart[2]);
}
*qtable1 = quantstart + 3;
nextJpegBlock(&quantstart);
}
if(!findJPEGheader(start, len, 0xda))
return false; // FAILED!
// Skip the header bytes of the SOS marker FIXME why doesn't this work?
uint32_t soslen = (*start)[0] * 256 + (*start)[1];
*start += soslen;
*len -= soslen;
// start scanning the data portion of the scan to find the end marker
BufPtr endmarkerptr = *start;
uint32_t endlen = *len;
skipScanBytes(&endmarkerptr);
if(!findJPEGheader(&endmarkerptr, &endlen, 0xd9))
return false; // FAILED!
// endlen must now be the # of bytes between the start of our scan and
// the end marker, tell the caller to ignore bytes afterwards
*len = endmarkerptr - *start;
return true;
}