Add pool optimization to frame decoders

This commit is contained in:
Lukas Herman
2020-10-30 21:12:19 -07:00
parent 640eeb0cc0
commit 7df20d004e
7 changed files with 209 additions and 166 deletions

8
pkg/frame/buffer.go Normal file
View File

@@ -0,0 +1,8 @@
package frame
import "image"
type buffer struct {
image image.Image
raw []uint8
}

View File

@@ -6,7 +6,9 @@ import (
"image/jpeg" "image/jpeg"
) )
func decodeMJPEG(frame []byte, width, height int) (image.Image, func(), error) { func decodeMJPEG() decoderFunc {
img, err := jpeg.Decode(bytes.NewReader(frame)) return func(frame []byte, width, height int) (image.Image, func(), error) {
return img, func() {}, err img, err := jpeg.Decode(bytes.NewReader(frame))
return img, func() {}, err
}
} }

View File

@@ -5,22 +5,22 @@ import (
) )
func NewDecoder(f Format) (Decoder, error) { func NewDecoder(f Format) (Decoder, error) {
var decoder decoderFunc var buildDecoder func() decoderFunc
switch f { switch f {
case FormatI420: case FormatI420:
decoder = decodeI420 buildDecoder = decodeI420
case FormatNV21: case FormatNV21:
decoder = decodeNV21 buildDecoder = decodeNV21
case FormatYUY2: case FormatYUY2:
decoder = decodeYUY2 buildDecoder = decodeYUY2
case FormatUYVY: case FormatUYVY:
decoder = decodeUYVY buildDecoder = decodeUYVY
case FormatMJPEG: case FormatMJPEG:
decoder = decodeMJPEG buildDecoder = decodeMJPEG
default: default:
return nil, fmt.Errorf("%s is not supported", f) return nil, fmt.Errorf("%s is not supported", f)
} }
return decoder, nil return buildDecoder(), nil
} }

View File

@@ -5,47 +5,51 @@ import (
"image" "image"
) )
func decodeI420(frame []byte, width, height int) (image.Image, func(), error) { func decodeI420() decoderFunc {
yi := width * height return func(frame []byte, width, height int) (image.Image, func(), error) {
cbi := yi + width*height/4 yi := width * height
cri := cbi + width*height/4 cbi := yi + width*height/4
cri := cbi + width*height/4
if cri > len(frame) { if cri > len(frame) {
return nil, func() {}, fmt.Errorf("frame length (%d) less than expected (%d)", len(frame), cri) return nil, func() {}, fmt.Errorf("frame length (%d) less than expected (%d)", len(frame), cri)
}
return &image.YCbCr{
Y: frame[:yi],
YStride: width,
Cb: frame[yi:cbi],
Cr: frame[cbi:cri],
CStride: width / 2,
SubsampleRatio: image.YCbCrSubsampleRatio420,
Rect: image.Rect(0, 0, width, height),
}, func() {}, nil
} }
return &image.YCbCr{
Y: frame[:yi],
YStride: width,
Cb: frame[yi:cbi],
Cr: frame[cbi:cri],
CStride: width / 2,
SubsampleRatio: image.YCbCrSubsampleRatio420,
Rect: image.Rect(0, 0, width, height),
}, func() {}, nil
} }
func decodeNV21(frame []byte, width, height int) (image.Image, func(), error) { func decodeNV21() decoderFunc {
yi := width * height return func(frame []byte, width, height int) (image.Image, func(), error) {
ci := yi + width*height/2 yi := width * height
ci := yi + width*height/2
if ci > len(frame) { if ci > len(frame) {
return nil, func() {}, fmt.Errorf("frame length (%d) less than expected (%d)", len(frame), ci) return nil, func() {}, fmt.Errorf("frame length (%d) less than expected (%d)", len(frame), ci)
}
var cb, cr []byte
for i := yi; i < ci; i += 2 {
cb = append(cb, frame[i])
cr = append(cr, frame[i+1])
}
return &image.YCbCr{
Y: frame[:yi],
YStride: width,
Cb: cb,
Cr: cr,
CStride: width / 2,
SubsampleRatio: image.YCbCrSubsampleRatio420,
Rect: image.Rect(0, 0, width, height),
}, func() {}, nil
} }
var cb, cr []byte
for i := yi; i < ci; i += 2 {
cb = append(cb, frame[i])
cr = append(cr, frame[i+1])
}
return &image.YCbCr{
Y: frame[:yi],
YStride: width,
Cb: cb,
Cr: cr,
CStride: width / 2,
SubsampleRatio: image.YCbCrSubsampleRatio420,
Rect: image.Rect(0, 0, width, height),
}, func() {}, nil
} }

View File

@@ -12,66 +12,70 @@ import (
// void decodeUYVYCGO(uint8_t* y, uint8_t* cb, uint8_t* cr, uint8_t* uyvy, int width, int height); // void decodeUYVYCGO(uint8_t* y, uint8_t* cb, uint8_t* cr, uint8_t* uyvy, int width, int height);
import "C" import "C"
func decodeYUY2(frame []byte, width, height int) (image.Image, func(), error) { func decodeYUY2() decoderFunc {
yi := width * height return func(frame []byte, width, height int) (image.Image, func(), error) {
ci := yi / 2 yi := width * height
fi := yi + 2*ci ci := yi / 2
fi := yi + 2*ci
if len(frame) != fi { if len(frame) != fi {
return nil, func() {}, fmt.Errorf("frame length (%d) less than expected (%d)", len(frame), fi) return nil, func() {}, fmt.Errorf("frame length (%d) less than expected (%d)", len(frame), fi)
}
y := make([]byte, yi)
cb := make([]byte, ci)
cr := make([]byte, ci)
C.decodeYUY2CGO(
(*C.uchar)(&y[0]),
(*C.uchar)(&cb[0]),
(*C.uchar)(&cr[0]),
(*C.uchar)(&frame[0]),
C.int(width), C.int(height),
)
return &image.YCbCr{
Y: y,
YStride: width,
Cb: cb,
Cr: cr,
CStride: width / 2,
SubsampleRatio: image.YCbCrSubsampleRatio422,
Rect: image.Rect(0, 0, width, height),
}, func() {}, nil
} }
y := make([]byte, yi)
cb := make([]byte, ci)
cr := make([]byte, ci)
C.decodeYUY2CGO(
(*C.uchar)(&y[0]),
(*C.uchar)(&cb[0]),
(*C.uchar)(&cr[0]),
(*C.uchar)(&frame[0]),
C.int(width), C.int(height),
)
return &image.YCbCr{
Y: y,
YStride: width,
Cb: cb,
Cr: cr,
CStride: width / 2,
SubsampleRatio: image.YCbCrSubsampleRatio422,
Rect: image.Rect(0, 0, width, height),
}, func() {}, nil
} }
func decodeUYVY(frame []byte, width, height int) (image.Image, func(), error) { func decodeUYVY() decoderFunc {
yi := width * height return func(frame []byte, width, height int) (image.Image, func(), error) {
ci := yi / 2 yi := width * height
fi := yi + 2*ci ci := yi / 2
fi := yi + 2*ci
if len(frame) != fi { if len(frame) != fi {
return nil, func() {}, fmt.Errorf("frame length (%d) less than expected (%d)", len(frame), fi) return nil, func() {}, fmt.Errorf("frame length (%d) less than expected (%d)", len(frame), fi)
}
y := make([]byte, yi)
cb := make([]byte, ci)
cr := make([]byte, ci)
C.decodeUYVYCGO(
(*C.uchar)(&y[0]),
(*C.uchar)(&cb[0]),
(*C.uchar)(&cr[0]),
(*C.uchar)(&frame[0]),
C.int(width), C.int(height),
)
return &image.YCbCr{
Y: y,
YStride: width,
Cb: cb,
Cr: cr,
CStride: width / 2,
SubsampleRatio: image.YCbCrSubsampleRatio422,
Rect: image.Rect(0, 0, width, height),
}, func() {}, nil
} }
y := make([]byte, yi)
cb := make([]byte, ci)
cr := make([]byte, ci)
C.decodeUYVYCGO(
(*C.uchar)(&y[0]),
(*C.uchar)(&cb[0]),
(*C.uchar)(&cr[0]),
(*C.uchar)(&frame[0]),
C.int(width), C.int(height),
)
return &image.YCbCr{
Y: y,
YStride: width,
Cb: cb,
Cr: cr,
CStride: width / 2,
SubsampleRatio: image.YCbCrSubsampleRatio422,
Rect: image.Rect(0, 0, width, height),
}, func() {}, nil
} }

View File

@@ -5,74 +5,97 @@ package frame
import ( import (
"fmt" "fmt"
"image" "image"
"sync"
) )
func decodeYUY2(frame []byte, width, height int) (image.Image, func(), error) { func decodeYUY2() decoderFunc {
yi := width * height pool := sync.Pool{
ci := yi / 2 New: func() interface{} {
fi := yi + 2*ci return &buffer{
image: &image.YCbCr{},
if len(frame) != fi { }
return nil, func() {}, fmt.Errorf("frame length (%d) less than expected (%d)", len(frame), fi) },
} }
y := make([]byte, yi) return func(frame []byte, width, height int) (image.Image, func(), error) {
cb := make([]byte, ci) buff := pool.Get().(*buffer)
cr := make([]byte, ci)
fast := 0 yi := width * height
slow := 0 ci := yi / 2
for i := 0; i < fi; i += 4 { fi := yi + 2*ci
y[fast] = frame[i]
cb[slow] = frame[i+1] if len(frame) != fi {
y[fast+1] = frame[i+2] pool.Put(buff)
cr[slow] = frame[i+3] return nil, func() {}, fmt.Errorf("frame length (%d) less than expected (%d)", len(frame), fi)
fast += 2 }
slow++
if len(buff.raw) < fi {
need := fi - len(buff.raw)
buff.raw = append(buff.raw, make([]uint8, need)...)
}
y := buff.raw[:yi:yi]
cb := buff.raw[yi : yi+ci : yi+ci]
cr := buff.raw[yi+ci : fi : fi]
fast := 0
slow := 0
for i := 0; i < fi; i += 4 {
y[fast] = frame[i]
cb[slow] = frame[i+1]
y[fast+1] = frame[i+2]
cr[slow] = frame[i+3]
fast += 2
slow++
}
img := buff.image.(*image.YCbCr)
img.Y = y
img.YStride = width
img.Cb = cb
img.Cr = cr
img.CStride = width / 2
img.SubsampleRatio = image.YCbCrSubsampleRatio422
img.Rect.Min.X = 0
img.Rect.Min.Y = 0
img.Rect.Max.X = width
img.Rect.Max.Y = height
return img, func() { pool.Put(buff) }, nil
} }
return &image.YCbCr{
Y: y,
YStride: width,
Cb: cb,
Cr: cr,
CStride: width / 2,
SubsampleRatio: image.YCbCrSubsampleRatio422,
Rect: image.Rect(0, 0, width, height),
}, func() {}, nil
} }
func decodeUYVY(frame []byte, width, height int) (image.Image, func(), error) { func decodeUYVY() decoderFunc {
yi := width * height return func(frame []byte, width, height int) (image.Image, func(), error) {
ci := yi / 2 yi := width * height
fi := yi + 2*ci ci := yi / 2
fi := yi + 2*ci
if len(frame) != fi { if len(frame) != fi {
return nil, func() {}, fmt.Errorf("frame length (%d) less than expected (%d)", len(frame), fi) return nil, func() {}, fmt.Errorf("frame length (%d) less than expected (%d)", len(frame), fi)
}
y := make([]byte, yi)
cb := make([]byte, ci)
cr := make([]byte, ci)
fast := 0
slow := 0
for i := 0; i < fi; i += 4 {
cb[slow] = frame[i]
y[fast] = frame[i+1]
cr[slow] = frame[i+2]
y[fast+1] = frame[i+3]
fast += 2
slow++
}
return &image.YCbCr{
Y: y,
YStride: width,
Cb: cb,
Cr: cr,
CStride: width / 2,
SubsampleRatio: image.YCbCrSubsampleRatio422,
Rect: image.Rect(0, 0, width, height),
}, func() {}, nil
} }
y := make([]byte, yi)
cb := make([]byte, ci)
cr := make([]byte, ci)
fast := 0
slow := 0
for i := 0; i < fi; i += 4 {
cb[slow] = frame[i]
y[fast] = frame[i+1]
cr[slow] = frame[i+2]
y[fast+1] = frame[i+3]
fast += 2
slow++
}
return &image.YCbCr{
Y: y,
YStride: width,
Cb: cb,
Cr: cr,
CStride: width / 2,
SubsampleRatio: image.YCbCrSubsampleRatio422,
Rect: image.Rect(0, 0, width, height),
}, func() {}, nil
} }

View File

@@ -27,7 +27,7 @@ func TestDecodeYUY2(t *testing.T) {
Rect: image.Rect(0, 0, width, height), Rect: image.Rect(0, 0, width, height),
} }
img, _, err := decodeYUY2(input, width, height) img, _, err := decodeYUY2()(input, width, height)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -56,7 +56,7 @@ func TestDecodeUYVY(t *testing.T) {
Rect: image.Rect(0, 0, width, height), Rect: image.Rect(0, 0, width, height),
} }
img, _, err := decodeUYVY(input, width, height) img, _, err := decodeUYVY()(input, width, height)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -76,11 +76,13 @@ func BenchmarkDecodeYUY2(b *testing.B) {
sz := sz sz := sz
b.Run(fmt.Sprintf("%dx%d", sz.width, sz.height), func(b *testing.B) { b.Run(fmt.Sprintf("%dx%d", sz.width, sz.height), func(b *testing.B) {
input := make([]byte, sz.width*sz.height*2) input := make([]byte, sz.width*sz.height*2)
decode := decodeYUY2()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, _, err := decodeYUY2(input, sz.width, sz.height) _, release, err := decode(input, sz.width, sz.height)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
release()
} }
}) })
} }