Add support fix JPEG from some MJPEG sources
This commit is contained in:
+49
-12
@@ -9,24 +9,38 @@ import (
|
|||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FixJPEG - reencode JPEG if it has wrong header
|
func FixJPEG(b []byte) []byte {
|
||||||
|
// skip non-JPEG
|
||||||
|
if len(b) < 10 || b[0] != 0xFF || b[1] != markerSOI {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip JPEG without app marker
|
||||||
|
if b[2] == 0xFF && b[3] == markerDQT {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
switch string(b[6:10]) {
|
||||||
|
case "JFIF", "Exif":
|
||||||
|
// skip if header OK for imghdr library
|
||||||
|
// - https://docs.python.org/3/library/imghdr.html
|
||||||
|
return b
|
||||||
|
case "AVI1":
|
||||||
|
// adds DHT tables to JPEG file before SOS marker
|
||||||
|
// useful when you want to save a JPEG frame from an MJPEG stream
|
||||||
|
// - https://github.com/image-rs/jpeg-decoder/issues/76
|
||||||
|
// - https://github.com/pion/mediadevices/pull/493
|
||||||
|
// - https://bugzilla.mozilla.org/show_bug.cgi?id=963907#c18
|
||||||
|
return InjectDHT(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// reencode JPEG if it has wrong header
|
||||||
//
|
//
|
||||||
// for example, this app produce "bad" images:
|
// for example, this app produce "bad" images:
|
||||||
// https://github.com/jacksonliam/mjpg-streamer
|
// https://github.com/jacksonliam/mjpg-streamer
|
||||||
//
|
//
|
||||||
// and they can't be uploaded to the Telegram servers:
|
// and they can't be uploaded to the Telegram servers:
|
||||||
// {"ok":false,"error_code":400,"description":"Bad Request: IMAGE_PROCESS_FAILED"}
|
// {"ok":false,"error_code":400,"description":"Bad Request: IMAGE_PROCESS_FAILED"}
|
||||||
func FixJPEG(b []byte) []byte {
|
|
||||||
// skip non-JPEG
|
|
||||||
if len(b) < 10 || b[0] != 0xFF || b[1] != 0xD8 {
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
// skip if header OK for imghdr library
|
|
||||||
// https://docs.python.org/3/library/imghdr.html
|
|
||||||
if string(b[2:4]) == "\xFF\xDB" || string(b[6:10]) == "JFIF" || string(b[6:10]) == "Exif" {
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
img, err := jpeg.Decode(bytes.NewReader(b))
|
img, err := jpeg.Decode(bytes.NewReader(b))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return b
|
return b
|
||||||
@@ -54,3 +68,26 @@ func Encoder(codec *core.Codec, handler core.HandlerFunc) core.HandlerFunc {
|
|||||||
handler(&clone)
|
handler(&clone)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dhtSize = 432 // known size for 4 default tables
|
||||||
|
|
||||||
|
func InjectDHT(b []byte) []byte {
|
||||||
|
if bytes.Index(b, []byte{0xFF, markerDHT}) > 0 {
|
||||||
|
return b // already exist
|
||||||
|
}
|
||||||
|
|
||||||
|
i := bytes.Index(b, []byte{0xFF, markerSOS})
|
||||||
|
if i < 0 {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
dht := make([]byte, 0, dhtSize)
|
||||||
|
dht = MakeHuffmanHeaders(dht)
|
||||||
|
|
||||||
|
tmp := make([]byte, len(b)+dhtSize)
|
||||||
|
copy(tmp, b[:i])
|
||||||
|
copy(tmp[i:], dht)
|
||||||
|
copy(tmp[i+dhtSize:], b[i:])
|
||||||
|
|
||||||
|
return tmp
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package mjpeg
|
||||||
|
|
||||||
|
const (
|
||||||
|
markerSOF = 0xC0 // Start Of Frame (Baseline Sequential)
|
||||||
|
markerSOI = 0xD8 // Start Of Image
|
||||||
|
markerEOI = 0xD9 // End Of Image
|
||||||
|
markerSOS = 0xDA // Start Of Scan
|
||||||
|
markerDQT = 0xDB // Define Quantization Table
|
||||||
|
markerDHT = 0xC4 // Define Huffman Table
|
||||||
|
)
|
||||||
+15
-15
@@ -143,9 +143,7 @@ var chm_ac_symbols = []byte{
|
|||||||
|
|
||||||
func MakeHeaders(p []byte, t byte, w, h uint16, lqt, cqt []byte) []byte {
|
func MakeHeaders(p []byte, t byte, w, h uint16, lqt, cqt []byte) []byte {
|
||||||
// Appendix A from https://www.rfc-editor.org/rfc/rfc2435
|
// Appendix A from https://www.rfc-editor.org/rfc/rfc2435
|
||||||
p = append(p, 0xFF,
|
p = append(p, 0xFF, markerSOI)
|
||||||
0xD8, // SOI
|
|
||||||
)
|
|
||||||
|
|
||||||
p = MakeQuantHeader(p, lqt, 0)
|
p = MakeQuantHeader(p, lqt, 0)
|
||||||
p = MakeQuantHeader(p, cqt, 1)
|
p = MakeQuantHeader(p, cqt, 1)
|
||||||
@@ -156,8 +154,7 @@ func MakeHeaders(p []byte, t byte, w, h uint16, lqt, cqt []byte) []byte {
|
|||||||
t = 0x22 // hsamp = 2, vsamp = 2
|
t = 0x22 // hsamp = 2, vsamp = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
p = append(p, 0xFF,
|
p = append(p, 0xFF, markerSOF,
|
||||||
0xC0, // SOF
|
|
||||||
0, 17, // size
|
0, 17, // size
|
||||||
8, // bits per component
|
8, // bits per component
|
||||||
byte(h>>8), byte(h&0xFF),
|
byte(h>>8), byte(h&0xFF),
|
||||||
@@ -174,13 +171,9 @@ func MakeHeaders(p []byte, t byte, w, h uint16, lqt, cqt []byte) []byte {
|
|||||||
1, // quant table 1
|
1, // quant table 1
|
||||||
)
|
)
|
||||||
|
|
||||||
p = MakeHuffmanHeader(p, lum_dc_codelens, lum_dc_symbols, 0, 0)
|
p = MakeHuffmanHeaders(p)
|
||||||
p = MakeHuffmanHeader(p, lum_ac_codelens, lum_ac_symbols, 0, 1)
|
|
||||||
p = MakeHuffmanHeader(p, chm_dc_codelens, chm_dc_symbols, 1, 0)
|
|
||||||
p = MakeHuffmanHeader(p, chm_ac_codelens, chm_ac_symbols, 1, 1)
|
|
||||||
|
|
||||||
return append(p, 0xFF,
|
return append(p, 0xFF, markerSOS,
|
||||||
0xDA, // SOS
|
|
||||||
0, 12, // size
|
0, 12, // size
|
||||||
3, // 3 components
|
3, // 3 components
|
||||||
0, // comp 0
|
0, // comp 0
|
||||||
@@ -196,16 +189,23 @@ func MakeHeaders(p []byte, t byte, w, h uint16, lqt, cqt []byte) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func MakeQuantHeader(p []byte, qt []byte, tableNo byte) []byte {
|
func MakeQuantHeader(p []byte, qt []byte, tableNo byte) []byte {
|
||||||
p = append(p, 0xFF, 0xDB, 0, 67, tableNo)
|
p = append(p, 0xFF, markerDQT, 0, 67, tableNo)
|
||||||
return append(p, qt...)
|
return append(p, qt...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeHuffmanHeader(p, codelens, symbols []byte, tableNo, tableClass byte) []byte {
|
func MakeHuffmanHeader(p, codelens, symbols []byte, tableNo, tableClass byte) []byte {
|
||||||
p = append(p,
|
p = append(p, 0xFF, markerDHT,
|
||||||
0xFF, 0xC4, 0,
|
0, byte(3+len(codelens)+len(symbols)), // size
|
||||||
byte(3+len(codelens)+len(symbols)),
|
|
||||||
(tableClass<<4)|tableNo,
|
(tableClass<<4)|tableNo,
|
||||||
)
|
)
|
||||||
p = append(p, codelens...)
|
p = append(p, codelens...)
|
||||||
return append(p, symbols...)
|
return append(p, symbols...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MakeHuffmanHeaders(p []byte) []byte {
|
||||||
|
p = MakeHuffmanHeader(p, lum_dc_codelens, lum_dc_symbols, 0, 0)
|
||||||
|
p = MakeHuffmanHeader(p, lum_ac_codelens, lum_ac_symbols, 0, 1)
|
||||||
|
p = MakeHuffmanHeader(p, chm_dc_codelens, chm_dc_symbols, 1, 0)
|
||||||
|
p = MakeHuffmanHeader(p, chm_ac_codelens, chm_ac_symbols, 1, 1)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user