Skip to content

Commit 89c8bf3

Browse files
authored
RSDK-6367 - Fix v4l2m2m encoder padding (viamrobotics#3453)
1 parent 1e9fde3 commit 89c8bf3

File tree

3 files changed

+50
-4
lines changed

3 files changed

+50
-4
lines changed

gostream/codec/h264/encoder.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ const (
2525
pixelFormat = avcodec.AvPixFmtYuv420p
2626
// V4l2m2m Is a V4L2 memory-to-memory H.264 hardware encoder.
2727
V4l2m2m = "h264_v4l2m2m"
28+
// macroBlock is the encoder boundary block size in bytes.
29+
macroBlock = 64
2830
)
2931

3032
type encoder struct {
@@ -96,7 +98,7 @@ func (h *encoder) Encode(ctx context.Context, img image.Image) ([]byte, error) {
9698
return nil, errors.Wrap(err, "cannot read image")
9799
}
98100

99-
h.frame.SetFrameFromImg(yuvImg.(*image.YCbCr))
101+
h.frame.SetFrameFromImgMacroAlign(yuvImg.(*image.YCbCr), macroBlock)
100102
h.frame.SetFramePTS(h.pts)
101103
h.pts++
102104

gostream/ffmpeg/avcodec/avcodec.go

+11-3
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,15 @@ import (
2121
"go.viam.com/rdk/gostream/ffmpeg/avlog"
2222
)
2323

24-
// AvPixFmtYuv420p the pixel format AV_PIX_FMT_YUV420P
25-
const AvPixFmtYuv420p = C.AV_PIX_FMT_YUV420P
24+
const (
25+
// AvPixFmtYuv420p the pixel format AV_PIX_FMT_YUV420P
26+
AvPixFmtYuv420p = C.AV_PIX_FMT_YUV420P
27+
// Target bitrate in bits per second.
28+
bitrate = 1_200_000
29+
// Target bitrate tolerance factor.
30+
// E.g., 2.0 gives 100% deviation from the target bitrate.
31+
bitrateDeviation = 2
32+
)
2633

2734
type (
2835
// Codec an AVCodec
@@ -88,14 +95,15 @@ func (ctxt *Context) FreeContext() {
8895
func (ctxt *Context) SetEncodeParams(width, height int, pxlFmt PixelFormat, hasBFrames bool, gopSize int) {
8996
ctxt.width = C.int(width)
9097
ctxt.height = C.int(height)
91-
ctxt.bit_rate = 2000000
98+
ctxt.bit_rate = bitrate
9299
ctxt.gop_size = C.int(gopSize)
93100
if hasBFrames {
94101
ctxt.has_b_frames = 1
95102
} else {
96103
ctxt.has_b_frames = 0
97104
}
98105
ctxt.pix_fmt = int32(pxlFmt)
106+
ctxt.rc_buffer_size = bitrate * bitrateDeviation
99107
}
100108

101109
// SetFramerate sets the context's framerate

gostream/ffmpeg/avutil/avutil.go

+36
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,42 @@ func ptr(buf []byte) *C.uint8_t {
7373
return (*C.uint8_t)(unsafe.Pointer(h.Data))
7474
}
7575

76+
// SetFrameFromImgMacroAlign sets the frame from the given image.YCbCr adding
77+
// line padding to the image to ensure that the data is aligned to the given boundary.
78+
// For example see alignment requirements for the Raspberry Pi GPU codec:
79+
// https://github.com/raspberrypi/linux/blob/rpi-6.1.y/drivers/staging/vc04_services/bcm2835-codec/bcm2835-v4l2-codec.c#L174
80+
func (f *Frame) SetFrameFromImgMacroAlign(img *image.YCbCr, boundary int) {
81+
// Calculating padded strides
82+
// Rounding up to next multiple of boundary value
83+
paddedYStride := ((img.YStride + boundary - 1) / boundary) * boundary
84+
// UV half the Y stride for 4:2:0
85+
paddedCbCrStride := paddedYStride / 2
86+
87+
// Allocate new buffers with padding
88+
// These will be freed by the GC
89+
paddedY := make([]byte, paddedYStride*img.Rect.Dy())
90+
paddedCb := make([]byte, paddedCbCrStride*img.Rect.Dy()/2)
91+
paddedCr := make([]byte, paddedCbCrStride*img.Rect.Dy()/2)
92+
93+
// Copy data from img to padded buffers line by line
94+
for i := 0; i < img.Rect.Dy(); i++ {
95+
copy(paddedY[i*paddedYStride:(i+1)*paddedYStride], img.Y[i*img.YStride:])
96+
}
97+
for i := 0; i < img.Rect.Dy()/2; i++ {
98+
copy(paddedCb[i*paddedCbCrStride:(i+1)*paddedCbCrStride], img.Cb[i*img.CStride:])
99+
copy(paddedCr[i*paddedCbCrStride:(i+1)*paddedCbCrStride], img.Cr[i*img.CStride:])
100+
}
101+
102+
// Update AVFrame data pointers and linesize
103+
// Casting from go slice to C array without changing memory
104+
f.data[0] = (*C.uchar)(unsafe.Pointer(&paddedY[0]))
105+
f.data[1] = (*C.uchar)(unsafe.Pointer(&paddedCb[0]))
106+
f.data[2] = (*C.uchar)(unsafe.Pointer(&paddedCr[0]))
107+
f.linesize[0] = C.int(paddedYStride)
108+
f.linesize[1] = C.int(paddedCbCrStride)
109+
f.linesize[2] = C.int(paddedCbCrStride)
110+
}
111+
76112
// SetFrameFromImg sets the frame from the given image.YCbCr
77113
func (f *Frame) SetFrameFromImg(img *image.YCbCr) {
78114
f.data[0] = ptr(img.Y)

0 commit comments

Comments
 (0)