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