Skip to content

Commit 55af09a

Browse files
committed
Add support fix JPEG from some MJPEG sources
1 parent 199fdd6 commit 55af09a

File tree

3 files changed

+73
-26
lines changed

3 files changed

+73
-26
lines changed

pkg/mjpeg/helpers.go

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,38 @@ import (
99
"github.com/pion/rtp"
1010
)
1111

12-
// FixJPEG - reencode JPEG if it has wrong header
13-
//
14-
// for example, this app produce "bad" images:
15-
// https://github.com/jacksonliam/mjpg-streamer
16-
//
17-
// and they can't be uploaded to the Telegram servers:
18-
// {"ok":false,"error_code":400,"description":"Bad Request: IMAGE_PROCESS_FAILED"}
1912
func FixJPEG(b []byte) []byte {
2013
// skip non-JPEG
21-
if len(b) < 10 || b[0] != 0xFF || b[1] != 0xD8 {
14+
if len(b) < 10 || b[0] != 0xFF || b[1] != markerSOI {
2215
return b
2316
}
24-
// skip if header OK for imghdr library
25-
// https://docs.python.org/3/library/imghdr.html
26-
if string(b[2:4]) == "\xFF\xDB" || string(b[6:10]) == "JFIF" || string(b[6:10]) == "Exif" {
17+
18+
// skip JPEG without app marker
19+
if b[2] == 0xFF && b[3] == markerDQT {
20+
return b
21+
}
22+
23+
switch string(b[6:10]) {
24+
case "JFIF", "Exif":
25+
// skip if header OK for imghdr library
26+
// - https://docs.python.org/3/library/imghdr.html
2727
return b
28+
case "AVI1":
29+
// adds DHT tables to JPEG file before SOS marker
30+
// useful when you want to save a JPEG frame from an MJPEG stream
31+
// - https://github.com/image-rs/jpeg-decoder/issues/76
32+
// - https://github.com/pion/mediadevices/pull/493
33+
// - https://bugzilla.mozilla.org/show_bug.cgi?id=963907#c18
34+
return InjectDHT(b)
2835
}
2936

37+
// reencode JPEG if it has wrong header
38+
//
39+
// for example, this app produce "bad" images:
40+
// https://github.com/jacksonliam/mjpg-streamer
41+
//
42+
// and they can't be uploaded to the Telegram servers:
43+
// {"ok":false,"error_code":400,"description":"Bad Request: IMAGE_PROCESS_FAILED"}
3044
img, err := jpeg.Decode(bytes.NewReader(b))
3145
if err != nil {
3246
return b
@@ -54,3 +68,26 @@ func Encoder(codec *core.Codec, handler core.HandlerFunc) core.HandlerFunc {
5468
handler(&clone)
5569
}
5670
}
71+
72+
const dhtSize = 432 // known size for 4 default tables
73+
74+
func InjectDHT(b []byte) []byte {
75+
if bytes.Index(b, []byte{0xFF, markerDHT}) > 0 {
76+
return b // already exist
77+
}
78+
79+
i := bytes.Index(b, []byte{0xFF, markerSOS})
80+
if i < 0 {
81+
return b
82+
}
83+
84+
dht := make([]byte, 0, dhtSize)
85+
dht = MakeHuffmanHeaders(dht)
86+
87+
tmp := make([]byte, len(b)+dhtSize)
88+
copy(tmp, b[:i])
89+
copy(tmp[i:], dht)
90+
copy(tmp[i+dhtSize:], b[i:])
91+
92+
return tmp
93+
}

pkg/mjpeg/jpeg.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package mjpeg
2+
3+
const (
4+
markerSOF = 0xC0 // Start Of Frame (Baseline Sequential)
5+
markerSOI = 0xD8 // Start Of Image
6+
markerEOI = 0xD9 // End Of Image
7+
markerSOS = 0xDA // Start Of Scan
8+
markerDQT = 0xDB // Define Quantization Table
9+
markerDHT = 0xC4 // Define Huffman Table
10+
)

pkg/mjpeg/rfc2435.go

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,7 @@ var chm_ac_symbols = []byte{
143143

144144
func MakeHeaders(p []byte, t byte, w, h uint16, lqt, cqt []byte) []byte {
145145
// Appendix A from https://www.rfc-editor.org/rfc/rfc2435
146-
p = append(p, 0xFF,
147-
0xD8, // SOI
148-
)
146+
p = append(p, 0xFF, markerSOI)
149147

150148
p = MakeQuantHeader(p, lqt, 0)
151149
p = MakeQuantHeader(p, cqt, 1)
@@ -156,8 +154,7 @@ func MakeHeaders(p []byte, t byte, w, h uint16, lqt, cqt []byte) []byte {
156154
t = 0x22 // hsamp = 2, vsamp = 2
157155
}
158156

159-
p = append(p, 0xFF,
160-
0xC0, // SOF
157+
p = append(p, 0xFF, markerSOF,
161158
0, 17, // size
162159
8, // bits per component
163160
byte(h>>8), byte(h&0xFF),
@@ -174,13 +171,9 @@ func MakeHeaders(p []byte, t byte, w, h uint16, lqt, cqt []byte) []byte {
174171
1, // quant table 1
175172
)
176173

177-
p = MakeHuffmanHeader(p, lum_dc_codelens, lum_dc_symbols, 0, 0)
178-
p = MakeHuffmanHeader(p, lum_ac_codelens, lum_ac_symbols, 0, 1)
179-
p = MakeHuffmanHeader(p, chm_dc_codelens, chm_dc_symbols, 1, 0)
180-
p = MakeHuffmanHeader(p, chm_ac_codelens, chm_ac_symbols, 1, 1)
174+
p = MakeHuffmanHeaders(p)
181175

182-
return append(p, 0xFF,
183-
0xDA, // SOS
176+
return append(p, 0xFF, markerSOS,
184177
0, 12, // size
185178
3, // 3 components
186179
0, // comp 0
@@ -196,16 +189,23 @@ func MakeHeaders(p []byte, t byte, w, h uint16, lqt, cqt []byte) []byte {
196189
}
197190

198191
func MakeQuantHeader(p []byte, qt []byte, tableNo byte) []byte {
199-
p = append(p, 0xFF, 0xDB, 0, 67, tableNo)
192+
p = append(p, 0xFF, markerDQT, 0, 67, tableNo)
200193
return append(p, qt...)
201194
}
202195

203196
func MakeHuffmanHeader(p, codelens, symbols []byte, tableNo, tableClass byte) []byte {
204-
p = append(p,
205-
0xFF, 0xC4, 0,
206-
byte(3+len(codelens)+len(symbols)),
197+
p = append(p, 0xFF, markerDHT,
198+
0, byte(3+len(codelens)+len(symbols)), // size
207199
(tableClass<<4)|tableNo,
208200
)
209201
p = append(p, codelens...)
210202
return append(p, symbols...)
211203
}
204+
205+
func MakeHuffmanHeaders(p []byte) []byte {
206+
p = MakeHuffmanHeader(p, lum_dc_codelens, lum_dc_symbols, 0, 0)
207+
p = MakeHuffmanHeader(p, lum_ac_codelens, lum_ac_symbols, 0, 1)
208+
p = MakeHuffmanHeader(p, chm_dc_codelens, chm_dc_symbols, 1, 0)
209+
p = MakeHuffmanHeader(p, chm_ac_codelens, chm_ac_symbols, 1, 1)
210+
return p
211+
}

0 commit comments

Comments
 (0)