mirror of https://bitbucket.org/ausocean/av.git
codec/mjpeg: tidying up
Separated my code from code that was ported from ffmpeg (differen copyright). Also added utils.go file to house the putBuffer and bytestream types. Reduced copying and use of bytes.Buffer. Instead expanded putBuffer functionality so that I can use this throughout process (reduce copying from buffer to buffer).
This commit is contained in:
parent
2d824707ee
commit
398543ac8e
|
@ -1,12 +1,13 @@
|
|||
/*
|
||||
DESCRIPTION
|
||||
extract.go provides an Extractor to get JPEG from RTP.
|
||||
extract.go provides an Extractor to get JPEG images from an RTP/JPEG stream
|
||||
defined by RFC 2435 (see https://tools.ietf.org/html/rfc2435).
|
||||
|
||||
AUTHOR
|
||||
Saxon Nelson-Milton <saxon@ausocean.org>
|
||||
|
||||
LICENSE
|
||||
Copyright (C) 2017 the Australian Ocean Lab (AusOcean)
|
||||
Copyright (C) 2019 the Australian Ocean Lab (AusOcean)
|
||||
|
||||
It is free software: you can redistribute it and/or modify them
|
||||
under the terms of the GNU General Public License as published by the
|
||||
|
@ -25,71 +26,29 @@ LICENSE
|
|||
package mjpeg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
"errors"
|
||||
|
||||
"bitbucket.org/ausocean/av/protocol/rtp"
|
||||
)
|
||||
|
||||
// Buffer sizes.
|
||||
const (
|
||||
maxRTPSize = 1500 // Max ethernet transmission unit in bytes.
|
||||
)
|
||||
|
||||
var (
|
||||
errNoQTable = errors.New("no quantization table")
|
||||
errReservedQ = errors.New("q value is reserved")
|
||||
)
|
||||
|
||||
var defaultQuantisers = []byte{
|
||||
// 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,
|
||||
|
||||
}
|
||||
const maxRTPSize = 1500 // Max ethernet transmission unit in bytes.
|
||||
|
||||
// Extractor is an Extractor for extracting JPEG from an RTP stream.
|
||||
type Extractor struct {
|
||||
buf *bytes.Buffer // Holds the current JPEG image.
|
||||
dst io.Writer // The destination we'll be writing extracted NALUs to.
|
||||
}
|
||||
|
||||
// NewExtractor returns a new Extractor.
|
||||
func NewExtractor() *Extractor {
|
||||
return &Extractor{
|
||||
buf: new(bytes.Buffer),
|
||||
}
|
||||
}
|
||||
func NewExtractor() *Extractor { return &Extractor{} }
|
||||
|
||||
// Extract will continously read RTP packets from src containing JPEG (in RTP
|
||||
// payload format) and extract the JPEG images, sending them to dst. This
|
||||
// function expects that each read from src will provide a single RTP packet.
|
||||
func (e *Extractor) Extract(dst io.Writer, src io.Reader, delay time.Duration) error {
|
||||
buf := make([]byte, maxRTPSize)
|
||||
|
||||
var (
|
||||
qTables [128][128]byte
|
||||
qTablesLen [128]byte
|
||||
)
|
||||
c := NewCtx(dst)
|
||||
|
||||
for {
|
||||
n, err := src.Read(buf)
|
||||
|
@ -107,186 +66,15 @@ func (e *Extractor) Extract(dst io.Writer, src io.Reader, delay time.Duration) e
|
|||
return fmt.Errorf("could not get RTP payload, failed with err: %v\n", err)
|
||||
}
|
||||
|
||||
b := newByteStream(p)
|
||||
_ = b.get8() // Ignore type-specific flag
|
||||
|
||||
var (
|
||||
off = b.get24() // Fragment offset.
|
||||
t = b.get8() // Type.
|
||||
q = b.get8() // Quantization value.
|
||||
width = b.get8() // Picture width.
|
||||
height = b.get8() // Picture height.
|
||||
dri int // Restart interval.
|
||||
)
|
||||
|
||||
if t&0x40 != 0 {
|
||||
dri = b.get16()
|
||||
_ = b.get16() // Ignore restart count.
|
||||
t &= ^0x40
|
||||
}
|
||||
|
||||
if t > 1 {
|
||||
panic("unimplemented RTP/JPEG type")
|
||||
}
|
||||
|
||||
// Parse quantization table if our offset is 0.
|
||||
if off == 0 {
|
||||
var qTable []byte
|
||||
var qLen int
|
||||
|
||||
if q > 127 {
|
||||
_ = b.get8() // Ignore first byte (reserved for future use).
|
||||
prec := b.get8() // The size of coefficients.
|
||||
qLen = b.get16() // The length of the quantization table.
|
||||
|
||||
if prec != 0 {
|
||||
panic("unsupported precision")
|
||||
}
|
||||
|
||||
if qLen > 0 {
|
||||
qTable = b.getBuf(qLen)
|
||||
|
||||
if q < 255 {
|
||||
if qTablesLen[q-128] == 0 && qLen <= 128 {
|
||||
copy(qTables[q-128][:],qTable)
|
||||
qTablesLen[q-128] = byte(qLen)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if q == 255 {
|
||||
return errNoQTable
|
||||
}
|
||||
|
||||
if qTablesLen[q-128] == 0 {
|
||||
return fmt.Errorf("no quantization tables known for q %d yet",q)
|
||||
}
|
||||
|
||||
qTable = qTables[q-128][:]
|
||||
qLen = int(qTablesLen[q-128])
|
||||
}
|
||||
} else { // q <= 127
|
||||
if q == 0 || q > 99 {
|
||||
return errReservedQ
|
||||
}
|
||||
qTable = defaultQTable(q)
|
||||
qLen = len(qTable)
|
||||
}
|
||||
|
||||
e.buf.Reset()
|
||||
|
||||
err = writeHeader(e.buf, t, width, height, qLen / 64, dri, qTable)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write JPEG header: %w",err)
|
||||
}
|
||||
}
|
||||
|
||||
if e.buf.Len() == 0 {
|
||||
// Must have missed start of frame? So ignore and wait for start.
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO: check that timestamp is consistent
|
||||
// This will need expansion to RTP package to create Timestamp parsing func.
|
||||
|
||||
// TODO: could also check offset with how many bytes we currently have
|
||||
// to determine if there are missing frames.
|
||||
|
||||
// Write frame data
|
||||
err = b.writeTo(e.buf,b.remaining())
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write remaining frame data to output buffer: %w",err)
|
||||
}
|
||||
|
||||
// Also grab the marker so that we know when the JPEG is finished.
|
||||
m, err := rtp.Marker(buf[:n])
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read RTP marker: %w", err)
|
||||
}
|
||||
|
||||
if m {
|
||||
_,err = e.buf.Write([]byte{0xff,codeEOI})
|
||||
err = c.ParseScan(p, m)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write EOI marker: %w",err)
|
||||
}
|
||||
|
||||
_,err = e.buf.WriteTo(dst)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write JPEG to dst: %w",err)
|
||||
return fmt.Errorf("could not parse JPEG scan: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type byteStream struct {
|
||||
bytes []byte
|
||||
i int
|
||||
}
|
||||
|
||||
func newByteStream(b []byte) *byteStream { return &byteStream{bytes: b} }
|
||||
|
||||
func (b *byteStream) get24() int {
|
||||
v := int(b.bytes[b.i])<<16 | int(b.bytes[b.i+1])<<8 | int(b.bytes[b.i+2])
|
||||
b.i += 3
|
||||
return v
|
||||
}
|
||||
|
||||
func (b *byteStream) get8() int {
|
||||
v := int(b.bytes[b.i])
|
||||
b.i++
|
||||
return v
|
||||
}
|
||||
|
||||
func (b *byteStream) get16() int {
|
||||
v := int(b.bytes[b.i])<<8 | int(b.bytes[b.i+1])
|
||||
b.i += 2
|
||||
return v
|
||||
}
|
||||
|
||||
func (b *byteStream) getBuf(n int) []byte {
|
||||
v := b.bytes[b.i:b.i+n]
|
||||
b.i += n
|
||||
return v
|
||||
}
|
||||
|
||||
func (b *byteStream) remaining() int {
|
||||
return len(b.bytes) - b.i
|
||||
}
|
||||
|
||||
func (b *byteStream) writeTo(w io.Writer, n int) error {
|
||||
_n,err := w.Write(b.bytes[b.i:b.i+n])
|
||||
b.i += _n
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func defaultQTable(q int) []byte {
|
||||
f := clip(q,q,99)
|
||||
const tabLen = 128
|
||||
tab := make([]byte,tabLen)
|
||||
|
||||
if q < 50 {
|
||||
q = 5000 / f
|
||||
} else {
|
||||
q = 200 - f*2
|
||||
}
|
||||
|
||||
for i := 0; i < tabLen; i++ {
|
||||
v := (int(defaultQuantisers[i])*q + 50) / 100
|
||||
v = clip(v,1,255)
|
||||
tab[i] = byte(v)
|
||||
}
|
||||
return tab
|
||||
}
|
||||
|
||||
func clip(v, min, max int) int {
|
||||
if v < min {
|
||||
return min
|
||||
}
|
||||
|
||||
if v > max {
|
||||
return max
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
|
|
@ -1,36 +1,45 @@
|
|||
/*
|
||||
DESCRIPTION
|
||||
jpeg.go contains constants, structure and functions specific to the JPEG.
|
||||
jpeg.go contains code ported from FFmpeg's C implementation of an RTP
|
||||
JPEG-compressed Video Depacketizer following RFC 2435. See
|
||||
https://ffmpeg.org/doxygen/2.6/rtpdec__jpeg_8c_source.html and
|
||||
https://tools.ietf.org/html/rfc2435).
|
||||
|
||||
This code can be used to build JPEG images from an RTP/JPEG stream.
|
||||
|
||||
AUTHOR
|
||||
Saxon Nelson-Milton <saxon@ausocean.org>
|
||||
|
||||
LICENSE
|
||||
Copyright (C) 2017 the Australian Ocean Lab (AusOcean)
|
||||
Copyright (c) 2012 Samuel Pitoiset.
|
||||
|
||||
It is free software: you can redistribute it and/or modify them
|
||||
under the terms of the GNU General Public License as published by the
|
||||
Free Software Foundation, either version 3 of the License, or (at your
|
||||
option) any later version.
|
||||
This file is part of FFmpeg.
|
||||
|
||||
It is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
for more details.
|
||||
FFmpeg is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with revid in gpl.txt. If not, see http://www.gnu.org/licenses.
|
||||
FFmpeg is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with FFmpeg; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
package mjpeg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
const maxJPEG = 1000000 // 1 MB (arbitrary)
|
||||
|
||||
// JPEG marker codes.
|
||||
const (
|
||||
codeSOI = 0xd8 // Start of image.
|
||||
|
@ -43,6 +52,11 @@ const (
|
|||
codeEOI = 0xd9 // End of image.
|
||||
)
|
||||
|
||||
var (
|
||||
errNoQTable = errors.New("no quantization table")
|
||||
errReservedQ = errors.New("q value is reserved")
|
||||
)
|
||||
|
||||
var (
|
||||
bitsDCLum = []byte{0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0}
|
||||
bitsDCChr = []byte{0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0}
|
||||
|
@ -96,139 +110,197 @@ var (
|
|||
0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
|
||||
0xf9, 0xfa,
|
||||
}
|
||||
|
||||
defaultQuantisers = []byte{
|
||||
// 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,
|
||||
}
|
||||
)
|
||||
|
||||
type multiError []error
|
||||
|
||||
func (me multiError) Error() string {
|
||||
return fmt.Sprintf("%v", []error(me))
|
||||
// Ctx describes a RTP/JPEG parsing context that will keep track of the current
|
||||
// JPEG (help by p), and the state of the quantization tables.
|
||||
type Ctx struct {
|
||||
qTables [128][128]byte
|
||||
qTablesLen [128]byte
|
||||
p *putBuffer
|
||||
d io.Writer
|
||||
}
|
||||
|
||||
func (me multiError) add(e error) {
|
||||
me = append(me, e)
|
||||
// NewCTX will return a new Ctx.
|
||||
func NewCtx(d io.Writer) *Ctx {
|
||||
return &Ctx{
|
||||
d: d,
|
||||
p: newPutBuffer(make([]byte, maxJPEG)),
|
||||
}
|
||||
}
|
||||
|
||||
type putter struct {
|
||||
idx int
|
||||
// ParseScan will parse a JPEG scan from an RTP/JPEG payload and append
|
||||
func (c *Ctx) ParseScan(p []byte, m bool) error {
|
||||
b := newByteStream(p)
|
||||
_ = b.get8() // Ignore type-specific flag
|
||||
|
||||
off := b.get24() // Fragment offset.
|
||||
t := b.get8() // Type.
|
||||
q := b.get8() // Quantization value.
|
||||
width := b.get8() // Picture width.
|
||||
height := b.get8() // Picture height.
|
||||
|
||||
var dri int // Restart interval.
|
||||
|
||||
if t&0x40 != 0 {
|
||||
dri = b.get16()
|
||||
_ = b.get16() // Ignore restart count.
|
||||
t &= ^0x40
|
||||
}
|
||||
|
||||
func (p *putter) put16(b []byte, v uint16) {
|
||||
binary.BigEndian.PutUint16(b[p.idx:], v)
|
||||
p.idx += 2
|
||||
if t > 1 {
|
||||
panic("unimplemented RTP/JPEG type")
|
||||
}
|
||||
|
||||
func (p *putter) put8(b []byte, v uint8) {
|
||||
b[p.idx] = byte(v)
|
||||
p.idx++
|
||||
// Parse quantization table if our offset is 0.
|
||||
if off == 0 {
|
||||
var qTable []byte
|
||||
var qLen int
|
||||
|
||||
if q > 127 {
|
||||
_ = b.get8() // Ignore first byte (reserved for future use).
|
||||
prec := b.get8() // The size of coefficients.
|
||||
qLen = b.get16() // The length of the quantization table.
|
||||
|
||||
if prec != 0 {
|
||||
panic("unsupported precision")
|
||||
}
|
||||
|
||||
func (p *putter) putBuf(dst, src []byte, l int) {
|
||||
copy(dst[p.idx:], src)
|
||||
p.idx++
|
||||
if qLen > 0 {
|
||||
qTable = b.getBuf(qLen)
|
||||
|
||||
if q < 255 {
|
||||
if c.qTablesLen[q-128] == 0 && qLen <= 128 {
|
||||
copy(c.qTables[q-128][:], qTable)
|
||||
c.qTablesLen[q-128] = byte(qLen)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if q == 255 {
|
||||
return errNoQTable
|
||||
}
|
||||
|
||||
if c.qTablesLen[q-128] == 0 {
|
||||
return fmt.Errorf("no quantization tables known for q %d yet", q)
|
||||
}
|
||||
|
||||
qTable = c.qTables[q-128][:]
|
||||
qLen = int(c.qTablesLen[q-128])
|
||||
}
|
||||
} else { // q <= 127
|
||||
if q == 0 || q > 99 {
|
||||
return errReservedQ
|
||||
}
|
||||
qTable = defaultQTable(q)
|
||||
qLen = len(qTable)
|
||||
}
|
||||
|
||||
c.p.reset()
|
||||
|
||||
writeHeader(c.p, t, width, height, qLen/64, dri, qTable)
|
||||
}
|
||||
|
||||
if c.p.len() == 0 {
|
||||
// Must have missed start of frame? So ignore and wait for start.
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: check that timestamp is consistent
|
||||
// This will need expansion to RTP package to create Timestamp parsing func.
|
||||
|
||||
// TODO: could also check offset with how many bytes we currently have
|
||||
// to determine if there are missing frames.
|
||||
|
||||
// Write frame data
|
||||
err := b.writeTo(c.p, b.remaining())
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write remaining frame data to output buffer: %w", err)
|
||||
}
|
||||
|
||||
if m {
|
||||
// End of image marker.
|
||||
mark(c.p, codeEOI)
|
||||
|
||||
_, err = c.p.writeTo(c.d)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write JPEG to dst: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeHeader writes a JPEG header to the writer w.
|
||||
func writeHeader(w io.Writer, _type, width, height, nbqTab, dri int, qtable []byte) error {
|
||||
func writeHeader(p *putBuffer, _type, width, height, nbqTab, dri int, qtable []byte) {
|
||||
width <<= 3
|
||||
height <<= 3
|
||||
|
||||
// Indicate start of image.
|
||||
err := writeMarker(w, codeSOI)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write SOI marker: %w", err)
|
||||
}
|
||||
|
||||
err = writeMarker(w, codeAPP0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write APP0 marker: %w", err)
|
||||
}
|
||||
mark(p, codeSOI)
|
||||
|
||||
// Write JFIF header.
|
||||
b := make([]byte, 16)
|
||||
p := putter{}
|
||||
p.put16(b, 16)
|
||||
p.putBuf(b, []byte("JFIF"), 5)
|
||||
p.put16(b, 0x0201)
|
||||
p.put8(b, 0)
|
||||
p.put16(b, 1)
|
||||
p.put16(b, 1)
|
||||
p.put8(b, 0)
|
||||
p.put8(b, 0)
|
||||
_, err = w.Write(b)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write JFIF header: %w", err)
|
||||
}
|
||||
mark(p, codeAPP0)
|
||||
p.put16(16)
|
||||
p.putBuf([]byte("JFIF\000"))
|
||||
p.put16(0x0201)
|
||||
p.put8(0)
|
||||
p.put16(1)
|
||||
p.put16(1)
|
||||
p.put8(0)
|
||||
p.put8(0)
|
||||
|
||||
// If we want to define restart interval.
|
||||
// If we want to define restart interval then write that.
|
||||
if dri != 0 {
|
||||
err = writeMarker(w, codeDRI)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write DRI marker code: %w", err)
|
||||
}
|
||||
|
||||
_, err := w.Write([]byte{0x00, 0x04, byte(dri >> 8), byte(dri)})
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write restart interval value: %w", err)
|
||||
}
|
||||
mark(p, codeDRI)
|
||||
p.put16(4)
|
||||
p.put16(uint16(dri))
|
||||
}
|
||||
|
||||
// Define quantization tables.
|
||||
err = writeMarker(w, codeDQT)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write DQI marker code: %w", err)
|
||||
}
|
||||
mark(p, codeDQT)
|
||||
|
||||
// Calculate table size and create slice for table.
|
||||
ts := 2 + nbqTab*(1+64)
|
||||
_, err = w.Write([]byte{byte(ts >> 8), byte(ts)})
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write quantization table size: %w", err)
|
||||
}
|
||||
p.put16(uint16(ts))
|
||||
|
||||
for i := 0; i < nbqTab; i++ {
|
||||
_, err = w.Write([]byte{byte(i)})
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write quantization table entry no.: %w", err)
|
||||
}
|
||||
|
||||
_, err = w.Write(qtable[64*i : (64*i)+64])
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write quantization table entry: %w", err)
|
||||
}
|
||||
p.put8(uint8(i))
|
||||
p.putBuf(qtable[64*i : (64*i)+64])
|
||||
}
|
||||
|
||||
// Define huffman table.
|
||||
err = writeMarker(w, codeDHT)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write DHT marker code: %w", err)
|
||||
}
|
||||
|
||||
var me multiError
|
||||
buf := new(bytes.Buffer)
|
||||
me.add(writeHuffman(buf, 0, 0, bitsDCLum, valDC))
|
||||
me.add(writeHuffman(buf, 0, 1, bitsDCChr, valDC))
|
||||
me.add(writeHuffman(buf, 1, 0, bitsACLum, valACLum))
|
||||
me.add(writeHuffman(buf, 1, 1, bitsACChr, valACChr))
|
||||
if me != nil {
|
||||
return fmt.Errorf("error writing huffman tables: %w", err)
|
||||
}
|
||||
|
||||
l := buf.Len() + 2
|
||||
_, err = w.Write([]byte{byte(l >> 8), byte(l)})
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write quantization table entry: %w", err)
|
||||
}
|
||||
|
||||
_, err = buf.WriteTo(w)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write huffman tables: %w", err)
|
||||
}
|
||||
mark(p, codeDHT)
|
||||
lenIdx := p.len()
|
||||
p.put16(0)
|
||||
writeHuffman(p, 0, 0, bitsDCLum, valDC)
|
||||
writeHuffman(p, 0, 1, bitsDCChr, valDC)
|
||||
writeHuffman(p, 1, 0, bitsACLum, valACLum)
|
||||
writeHuffman(p, 1, 1, bitsACChr, valACChr)
|
||||
p.put16At(uint16(p.len()-lenIdx), lenIdx)
|
||||
|
||||
// Start of frame.
|
||||
err = writeMarker(w, codeSOF0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write SOF0 marker code: %w", err)
|
||||
}
|
||||
mark(p, codeSOF0)
|
||||
|
||||
// Derive sample type.
|
||||
sample := 1
|
||||
|
@ -242,84 +314,84 @@ func writeHeader(w io.Writer, _type, width, height, nbqTab, dri int, qtable []by
|
|||
mtxNo = 1
|
||||
}
|
||||
|
||||
b = make([]byte, 17)
|
||||
p = putter{}
|
||||
p.put16(b, 17)
|
||||
p.put8(b, 8)
|
||||
p.put16(b, uint16(height))
|
||||
p.put16(b, uint16(width))
|
||||
p.put8(b, 3)
|
||||
p.put8(b, 1)
|
||||
p.put8(b, uint8((2<<4)|sample))
|
||||
p.put8(b, 0)
|
||||
p.put8(b, 2)
|
||||
p.put8(b, 1<<4|1)
|
||||
p.put8(b, uint8(mtxNo))
|
||||
p.put8(b, 3)
|
||||
p.put8(b, 1<<4|1)
|
||||
p.put8(b, uint8(mtxNo))
|
||||
_, err = w.Write(b)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write SOF0 info: %w", err)
|
||||
}
|
||||
p.put16(17)
|
||||
p.put8(8)
|
||||
p.put16(uint16(height))
|
||||
p.put16(uint16(width))
|
||||
p.put8(3)
|
||||
p.put8(1)
|
||||
p.put8(uint8((2 << 4) | sample))
|
||||
p.put8(0)
|
||||
p.put8(2)
|
||||
p.put8(1<<4 | 1)
|
||||
p.put8(uint8(mtxNo))
|
||||
p.put8(3)
|
||||
p.put8(1<<4 | 1)
|
||||
p.put8(uint8(mtxNo))
|
||||
|
||||
// Write start of scan.
|
||||
err = writeMarker(w, codeSOS)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write SOS marker code: %w", err)
|
||||
mark(p, codeSOS)
|
||||
p.put16(12)
|
||||
p.put8(3)
|
||||
p.put8(1)
|
||||
p.put8(0)
|
||||
p.put8(2)
|
||||
p.put8(17)
|
||||
p.put8(3)
|
||||
p.put8(17)
|
||||
p.put8(0)
|
||||
p.put8(63)
|
||||
p.put8(0)
|
||||
}
|
||||
|
||||
b = make([]byte, 12)
|
||||
p = putter{}
|
||||
p.put16(b, 12)
|
||||
p.put8(b, 3)
|
||||
p.put8(b, 1)
|
||||
p.put8(b, 0)
|
||||
p.put8(b, 2)
|
||||
p.put8(b, 17)
|
||||
p.put8(b, 3)
|
||||
p.put8(b, 17)
|
||||
p.put8(b, 0)
|
||||
p.put8(b, 63)
|
||||
p.put8(b, 0)
|
||||
_, err = w.Write(b)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write SOS info: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeMarker writes an JPEG marker with code to w.
|
||||
func writeMarker(w io.Writer, code byte) error {
|
||||
_, err := w.Write([]byte{0xff, code})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
// mark writes a marker with code c to the putBuffer p.
|
||||
func mark(p *putBuffer, c byte) {
|
||||
p.put8(0xff)
|
||||
p.put8(c)
|
||||
}
|
||||
|
||||
// writeHuffman write a JPEG huffman table to w.
|
||||
func writeHuffman(w io.Writer, class, id int, bits, values []byte) error {
|
||||
_, err := w.Write([]byte{byte(class<<4 | id)})
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write class and id: %w", err)
|
||||
}
|
||||
func writeHuffman(p *putBuffer, class, id int, bits, values []byte) {
|
||||
p.put8(uint8(class<<4 | id))
|
||||
|
||||
var n int
|
||||
for i := 1; i <= 16; i++ {
|
||||
n += int(bits[i])
|
||||
}
|
||||
|
||||
_, err = w.Write(bits[1:17])
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write first lot of huffman bytes: %w", err)
|
||||
p.putBuf(bits[1:17])
|
||||
p.putBuf(values[0:n])
|
||||
}
|
||||
|
||||
_, err = w.Write(values[0:n])
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write second lot of huffman bytes: %w", err)
|
||||
// defaultQTable returns a default quantization table.
|
||||
func defaultQTable(q int) []byte {
|
||||
f := clip(q, q, 99)
|
||||
const tabLen = 128
|
||||
tab := make([]byte, tabLen)
|
||||
|
||||
if q < 50 {
|
||||
q = 5000 / f
|
||||
} else {
|
||||
q = 200 - f*2
|
||||
}
|
||||
|
||||
return nil
|
||||
for i := 0; i < tabLen; i++ {
|
||||
v := (int(defaultQuantisers[i])*q + 50) / 100
|
||||
v = clip(v, 1, 255)
|
||||
tab[i] = byte(v)
|
||||
}
|
||||
return tab
|
||||
}
|
||||
|
||||
// clip clips the value v to the bounds defined by min and max.
|
||||
func clip(v, min, max int) int {
|
||||
if v < min {
|
||||
return min
|
||||
}
|
||||
|
||||
if v > max {
|
||||
return max
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
DESCRIPTION
|
||||
utils.go provides buffer utilities used by jpeg.go.
|
||||
|
||||
AUTHOR
|
||||
Saxon Nelson-Milton <saxon@ausocean.org>
|
||||
|
||||
LICENSE
|
||||
Copyright (C) 2019 the Australian Ocean Lab (AusOcean)
|
||||
|
||||
It is free software: you can redistribute it and/or modify them
|
||||
under the terms of the GNU General Public License as published by the
|
||||
Free Software Foundation, either version 3 of the License, or (at your
|
||||
option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with revid in gpl.txt. If not, see http://www.gnu.org/licenses.
|
||||
*/
|
||||
|
||||
package mjpeg
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
type putBuffer struct {
|
||||
i int
|
||||
b []byte
|
||||
}
|
||||
|
||||
func newPutBuffer(b []byte) *putBuffer { return &putBuffer{b: b} }
|
||||
|
||||
func (p *putBuffer) Write(b []byte) (int, error) {
|
||||
copy(p.b[p.i:], b)
|
||||
p.i += len(b)
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (p *putBuffer) writeTo(d io.Writer) (int, error) {
|
||||
n, err := d.Write(p.b[0:p.i])
|
||||
p.i -= n
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (p *putBuffer) put16(v uint16) {
|
||||
binary.BigEndian.PutUint16(p.b[p.i:], v)
|
||||
p.i += 2
|
||||
}
|
||||
|
||||
func (p *putBuffer) put8(v uint8) {
|
||||
p.b[p.i] = byte(v)
|
||||
p.i++
|
||||
}
|
||||
|
||||
func (p *putBuffer) putBuf(src []byte) {
|
||||
copy(p.b[p.i:], src)
|
||||
p.i += len(src)
|
||||
}
|
||||
|
||||
func (p *putBuffer) put16At(v uint16, i int) {
|
||||
binary.BigEndian.PutUint16(p.b[i:], v)
|
||||
}
|
||||
|
||||
func (p *putBuffer) reset() {
|
||||
p.i = 0
|
||||
}
|
||||
|
||||
func (p *putBuffer) len() int {
|
||||
return p.i
|
||||
}
|
||||
|
||||
type byteStream struct {
|
||||
bytes []byte
|
||||
i int
|
||||
}
|
||||
|
||||
func newByteStream(b []byte) *byteStream { return &byteStream{bytes: b} }
|
||||
|
||||
func (b *byteStream) get24() int {
|
||||
v := int(b.bytes[b.i])<<16 | int(b.bytes[b.i+1])<<8 | int(b.bytes[b.i+2])
|
||||
b.i += 3
|
||||
return v
|
||||
}
|
||||
|
||||
func (b *byteStream) get8() int {
|
||||
v := int(b.bytes[b.i])
|
||||
b.i++
|
||||
return v
|
||||
}
|
||||
|
||||
func (b *byteStream) get16() int {
|
||||
v := int(b.bytes[b.i])<<8 | int(b.bytes[b.i+1])
|
||||
b.i += 2
|
||||
return v
|
||||
}
|
||||
|
||||
func (b *byteStream) getBuf(n int) []byte {
|
||||
v := b.bytes[b.i : b.i+n]
|
||||
b.i += n
|
||||
return v
|
||||
}
|
||||
|
||||
func (b *byteStream) remaining() int {
|
||||
return len(b.bytes) - b.i
|
||||
}
|
||||
|
||||
func (b *byteStream) writeTo(w io.Writer, n int) error {
|
||||
_n, err := w.Write(b.bytes[b.i : b.i+n])
|
||||
b.i += _n
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue