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:
Saxon 2019-11-23 14:23:35 +10:30
parent e3b6d2101a
commit 90ab359599
3 changed files with 385 additions and 405 deletions

View File

@ -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)
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
}

View File

@ -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
func (p *putter) put16(b []byte, v uint16) {
binary.BigEndian.PutUint16(b[p.idx:], v)
p.idx += 2
}
off := b.get24() // Fragment offset.
t := b.get8() // Type.
q := b.get8() // Quantization value.
width := b.get8() // Picture width.
height := b.get8() // Picture height.
func (p *putter) put8(b []byte, v uint8) {
b[p.idx] = byte(v)
p.idx++
}
var dri int // Restart interval.
func (p *putter) putBuf(dst, src []byte, l int) {
copy(dst[p.idx:], src)
p.idx++
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 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)
}
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
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)
}
// 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)
}
_, err = w.Write(values[0:n])
if err != nil {
return fmt.Errorf("could not write second lot of huffman bytes: %w", err)
}
return nil
p.putBuf(bits[1:17])
p.putBuf(values[0:n])
}
// 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
}
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
}

120
codec/mjpeg/utils.go Normal file
View File

@ -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
}