/*************************************************************************************** * * IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. * * By downloading, copying, installing or using the software you agree to this license. * If you do not agree to this license, do not download, install, * copy or use the software. * * Copyright (C) 2014-2024, Happytimesoft Corporation, all rights reserved. * * Redistribution and use in binary forms, with or without modification, are permitted. * * Unless required by applicable law or agreed to in writing, software distributed * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific * language governing permissions and limitations under the License. * ****************************************************************************************/ #include "sys_inc.h" #include "mjpeg.h" #include "mjpeg_rtp_rx.h" #include "mjpeg_tables.h" static void mjpeg_create_huffman_header ( uint8*& p, uint8 const* codelens, int ncodes, uint8 const* symbols, int nsymbols, int tableNo, int tableClass ) { *p++ = 0xff; *p++ = MARKER_DHT; *p++ = 0; /* length msb */ *p++ = 3 + ncodes + nsymbols; /* length lsb */ *p++ = (tableClass << 4) | tableNo; memcpy(p, codelens, ncodes); p += ncodes; memcpy(p, symbols, nsymbols); p += nsymbols; } uint32 mjpeg_compute_header_size(uint32 qtlen, uint32 dri) { uint32 qtlen_half = qtlen/2; // in case qtlen is odd; shouldn't happen qtlen = qtlen_half*2; uint32 numQtables = qtlen > 64 ? 2 : 1; return 485 + numQtables*5 + qtlen + (dri > 0 ? 6 : 0); } int mjpeg_create_header ( uint8* buf, uint32 type, uint32 w, uint32 h, uint8 const* qtables, uint32 qtlen, uint32 dri ) { uint8 *ptr = buf; uint32 numQtables = qtlen > 64 ? 2 : 1; // MARKER_SOI: *ptr++ = 0xFF; *ptr++ = MARKER_SOI; // MARKER_APP_FIRST: *ptr++ = 0xFF; *ptr++ = MARKER_APP_FIRST; *ptr++ = 0x00; *ptr++ = 0x10; // size of chunk *ptr++ = 'J'; *ptr++ = 'F'; *ptr++ = 'I'; *ptr++ = 'F'; *ptr++ = 0x00; *ptr++ = 0x01; *ptr++ = 0x01; // JFIF format version (1.1) *ptr++ = 0x00; // no units *ptr++ = 0x00; *ptr++ = 0x01; // Horizontal pixel aspect ratio *ptr++ = 0x00; *ptr++ = 0x01; // Vertical pixel aspect ratio *ptr++ = 0x00; *ptr++ = 0x00; // no thumbnail // MARKER_DRI: if (dri > 0) { *ptr++ = 0xFF; *ptr++ = MARKER_DRI; *ptr++ = 0x00; *ptr++ = 0x04; // size of chunk *ptr++ = (uint8)(dri >> 8); *ptr++ = (uint8)(dri); // restart interval } // MARKER_DQT (luma): uint32 tableSize = numQtables == 1 ? qtlen : qtlen/2; *ptr++ = 0xFF; *ptr++ = MARKER_DQT; *ptr++ = 0x00; *ptr++ = tableSize + 3; // size of chunk *ptr++ = 0x00; // precision(0), table id(0) memcpy(ptr, qtables, tableSize); qtables += tableSize; ptr += tableSize; if (numQtables > 1) { tableSize = qtlen - qtlen/2; // MARKER_DQT (chroma): *ptr++ = 0xFF; *ptr++ = MARKER_DQT; *ptr++ = 0x00; *ptr++ = tableSize + 3; // size of chunk *ptr++ = 0x01; // precision(0), table id(1) memcpy(ptr, qtables, tableSize); qtables += tableSize; ptr += tableSize; } // MARKER_SOF0: *ptr++ = 0xFF; *ptr++ = MARKER_SOF0; *ptr++ = 0x00; *ptr++ = 0x11; // size of chunk *ptr++ = 0x08; // sample precision *ptr++ = (uint8)(h >> 8); *ptr++ = (uint8)(h); // number of lines (must be a multiple of 8) *ptr++ = (uint8)(w >> 8); *ptr++ = (uint8)(w); // number of columns (must be a multiple of 8) *ptr++ = 0x03; // number of components *ptr++ = 0x01; // id of component *ptr++ = type ? 0x22 : 0x21; // sampling ratio (h,v) *ptr++ = 0x00; // quant table id *ptr++ = 0x02; // id of component *ptr++ = 0x11; // sampling ratio (h,v) *ptr++ = numQtables == 1 ? 0x00 : 0x01; // quant table id *ptr++ = 0x03; // id of component *ptr++ = 0x11; // sampling ratio (h,v) *ptr++ = numQtables == 1 ? 0x00 : 0x01; // quant table id mjpeg_create_huffman_header(ptr, lum_dc_codelens, sizeof lum_dc_codelens, lum_dc_symbols, sizeof lum_dc_symbols, 0, 0); mjpeg_create_huffman_header(ptr, lum_ac_codelens, sizeof lum_ac_codelens, lum_ac_symbols, sizeof lum_ac_symbols, 0, 1); mjpeg_create_huffman_header(ptr, chm_dc_codelens, sizeof chm_dc_codelens, chm_dc_symbols, sizeof chm_dc_symbols, 1, 0); mjpeg_create_huffman_header(ptr, chm_ac_codelens, sizeof chm_ac_codelens, chm_ac_symbols, sizeof chm_ac_symbols, 1, 1); // MARKER_SOS: *ptr++ = 0xFF; *ptr++ = MARKER_SOS; *ptr++ = 0x00; *ptr++ = 0x0C; // size of chunk *ptr++ = 0x03; // number of components *ptr++ = 0x01; // id of component *ptr++ = 0x00; // huffman table id (DC, AC) *ptr++ = 0x02; // id of component *ptr++ = 0x11; // huffman table id (DC, AC) *ptr++ = 0x03; // id of component *ptr++ = 0x11; // huffman table id (DC, AC) *ptr++ = 0x00; // start of spectral *ptr++ = 0x3F; // end of spectral *ptr++ = 0x00; // successive approximation bit position (high, low) return (int)(ptr - buf); } // The default 'luma' and 'chroma' quantizer tables, in zigzag order: static uint8 const defaultQuantizers[128] = { // luma table: 16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24, 40, 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60, 57, 51, 56, 55, 64, 72, 92, 78, 64, 68, 87, 69, 55, 56, 80, 109, 81, 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112, 100, 120, 92, 101, 103, 99, // chroma table: 17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99 }; void mjpeg_make_default_qtables(uint8* resultTables, uint32 Q) { int factor = Q; int q; if (Q < 1) factor = 1; else if (Q > 99) factor = 99; if (Q < 50) { q = 5000 / factor; } else { q = 200 - factor*2; } for (int i = 0; i < 128; ++i) { int newVal = (defaultQuantizers[i]*q + 50)/100; if (newVal < 1) newVal = 1; else if (newVal > 255) newVal = 255; resultTables[i] = newVal; } } BOOL mjpeg_data_rx(MJPEGRXI * p_rxi, uint8 * p_data, int len) { uint8* headerStart = p_data; uint32 packetSize = len; uint32 resultSpecialHeaderSize = 0; uint8* qtables = NULL; uint32 qtlen = 0; uint32 dri = 0; // There's at least 8-byte video-specific header /* 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type-specific | Fragment Offset | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type | Q | Width | Height | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ resultSpecialHeaderSize = 8; uint32 Offset = (uint32)((uint32)headerStart[1] << 16 | (uint32)headerStart[2] << 8 | (uint32)headerStart[3]); uint32 Type = (uint32)headerStart[4]; uint32 type = Type & 1; uint32 Q = (uint32)headerStart[5]; uint32 width = (uint32)headerStart[6] * 8; uint32 height = (uint32)headerStart[7] * 8; if (width == 0) { width = p_rxi->width ? p_rxi->width : 256*8; } if (height == 0) { height = p_rxi->height ? p_rxi->height : 256*8; } if (Type > 63) { // Restart Marker header present /* 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Restart Interval |F|L| Restart Count | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ if (packetSize < resultSpecialHeaderSize + 4) { return FALSE; } uint32 RestartInterval = (uint32)((uint16)headerStart[resultSpecialHeaderSize] << 8 | (uint16)headerStart[resultSpecialHeaderSize + 1]); dri = RestartInterval; resultSpecialHeaderSize += 4; } if (Offset == 0) { if (Q > 127) { // Quantization Table header present /* 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | MBZ | Precision | Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Quantization Table Data | | ... | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ if (packetSize < resultSpecialHeaderSize + 4) { return FALSE; } uint32 MBZ = (uint32)headerStart[resultSpecialHeaderSize]; if (MBZ == 0) { // uint32 Precision = (uint32)headerStart[resultSpecialHeaderSize + 1]; uint32 Length = (uint32)((uint16)headerStart[resultSpecialHeaderSize + 2] << 8 | (uint16)headerStart[resultSpecialHeaderSize + 3]); //ASSERT(Length == 128); resultSpecialHeaderSize += 4; if (packetSize < resultSpecialHeaderSize + Length) { return FALSE; } qtlen = Length; qtables = &headerStart[resultSpecialHeaderSize]; resultSpecialHeaderSize += Length; } } } // If this is the first (or only) fragment of a JPEG frame if (Offset == 0) { uint8 newQtables[128]; if (qtlen == 0) { // A quantization table was not present in the RTP JPEG header, // so use the default tables, scaled according to the "Q" factor: mjpeg_make_default_qtables(newQtables, Q); qtables = newQtables; qtlen = sizeof newQtables; } p_rxi->d_offset = mjpeg_create_header(p_rxi->p_buf, type, width, height, qtables, qtlen, dri); } if ((p_rxi->d_offset + packetSize - resultSpecialHeaderSize) >= p_rxi->buf_len) { log_print(HT_LOG_ERR, "%s, fragment packet too big %d!!!", __FUNCTION__, p_rxi->d_offset + packetSize - resultSpecialHeaderSize); return FALSE; } memcpy(p_rxi->p_buf + p_rxi->d_offset, headerStart + resultSpecialHeaderSize, packetSize - resultSpecialHeaderSize); p_rxi->d_offset += packetSize - resultSpecialHeaderSize; // The RTP "M" (marker) bit indicates the last fragment of a frame: if (p_rxi->rtprxi.rxf_marker) { if (p_rxi->d_offset >= 2 && !(p_rxi->p_buf[p_rxi->d_offset-2] == 0xFF && p_rxi->p_buf[p_rxi->d_offset-1] == MARKER_EOI)) { p_rxi->p_buf[p_rxi->d_offset++] = 0xFF; p_rxi->p_buf[p_rxi->d_offset++] = MARKER_EOI; } if (p_rxi->pkt_func) { p_rxi->pkt_func(p_rxi->p_buf, p_rxi->d_offset, p_rxi->rtprxi.ts, p_rxi->rtprxi.seq, p_rxi->user_data); } p_rxi->d_offset = 0; } return TRUE; } BOOL mjpeg_rtp_rx(MJPEGRXI * p_rxi, uint8 * p_data, int len) { if (p_rxi == NULL) { return FALSE; } if (!rtp_data_rx(&p_rxi->rtprxi, p_data, len)) { return FALSE; } return mjpeg_data_rx(p_rxi, p_rxi->rtprxi.p_data, p_rxi->rtprxi.len); } BOOL mjpeg_rxi_init(MJPEGRXI * p_rxi, VRTPRXCBF cbf, void * p_userdata) { memset(p_rxi, 0, sizeof(MJPEGRXI)); p_rxi->buf_len = RTP_MAX_VIDEO_BUFF; p_rxi->p_buf_org = (uint8 *)malloc(p_rxi->buf_len); if (p_rxi->p_buf_org == NULL) { return FALSE; } p_rxi->p_buf = p_rxi->p_buf_org + 32; p_rxi->buf_len -= 32; p_rxi->pkt_func = cbf; p_rxi->user_data = p_userdata; return TRUE; } void mjpeg_rxi_deinit(MJPEGRXI * p_rxi) { if (p_rxi->p_buf_org) { free(p_rxi->p_buf_org); } memset(p_rxi, 0, sizeof(MJPEGRXI)); }