Skip to content

Commit 20e8c50

Browse files
KW-M3DRX
andauthored
Implement bitrate controller in vpx and h264 codecs (#467)
Add bitrate control to vpx and h264 encoders Co-authored-by: Jingyang Kang <3drxkjy@gmail.com>
1 parent 7211d07 commit 20e8c50

5 files changed

Lines changed: 189 additions & 14 deletions

File tree

pkg/codec/vpx/vpx.go

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ type encoder struct {
7474
frame []byte
7575
deadline int
7676
requireKeyFrame bool
77+
targetBitrate int
7778
isKeyFrame bool
7879

7980
mu sync.Mutex
@@ -200,14 +201,15 @@ func newEncoder(r video.Reader, p prop.Media, params Params, codecIface *C.vpx_c
200201
}
201202
t0 := time.Now()
202203
return &encoder{
203-
r: video.ToI420(r),
204-
codec: codec,
205-
raw: rawNoBuffer,
206-
cfg: cfg,
207-
tStart: t0,
208-
tLastFrame: t0,
209-
deadline: int(params.Deadline / time.Microsecond),
210-
frame: make([]byte, 1024),
204+
r: video.ToI420(r),
205+
codec: codec,
206+
raw: rawNoBuffer,
207+
cfg: cfg,
208+
tStart: t0,
209+
tLastFrame: t0,
210+
deadline: int(params.Deadline / time.Microsecond),
211+
frame: make([]byte, 1024),
212+
targetBitrate: params.BitRate,
211213
}, nil
212214
}
213215

@@ -261,6 +263,16 @@ func (e *encoder) Read() ([]byte, func(), error) {
261263
if duration == 0 {
262264
duration = 1
263265
}
266+
267+
targetVpxBitrate := C.uint(float32(e.targetBitrate / 1000)) // convert to kilobits / second
268+
if e.cfg.rc_target_bitrate != targetVpxBitrate && targetVpxBitrate >= 1 {
269+
e.cfg.rc_target_bitrate = targetVpxBitrate
270+
rc := C.vpx_codec_enc_config_set(e.codec, e.cfg)
271+
if rc != C.VPX_CODEC_OK {
272+
return nil, func() {}, fmt.Errorf("vpx_codec_enc_config_set failed (%d)", rc)
273+
}
274+
}
275+
264276
var flags int
265277
if e.requireKeyFrame {
266278
flags = flags | C.VPX_EFLAG_FORCE_KF
@@ -303,6 +315,13 @@ func (e *encoder) ForceKeyFrame() error {
303315
return nil
304316
}
305317

318+
func (e *encoder) SetBitRate(bitrate int) error {
319+
e.mu.Lock()
320+
defer e.mu.Unlock()
321+
e.targetBitrate = bitrate
322+
return nil
323+
}
324+
306325
func (e *encoder) Controller() codec.EncoderController {
307326
return e
308327
}

pkg/codec/vpx/vpx_test.go

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,9 +232,76 @@ func TestRequestKeyFrame(t *testing.T) {
232232
}
233233
}
234234

235-
func TestShouldImplementBitRateControl(t *testing.T) {
236-
t.SkipNow() // TODO: Implement bit rate control
235+
func TestSetBitrate(t *testing.T) {
236+
for name, factory := range map[string]func() (codec.VideoEncoderBuilder, error){
237+
"VP8": func() (codec.VideoEncoderBuilder, error) {
238+
p, err := NewVP8Params()
239+
return &p, err
240+
},
241+
"VP9": func() (codec.VideoEncoderBuilder, error) {
242+
p, err := NewVP9Params()
243+
// Disable latency to ease test and begin to receive packets for each input frame
244+
p.LagInFrames = 0
245+
return &p, err
246+
},
247+
} {
248+
factory := factory
249+
t.Run(name, func(t *testing.T) {
250+
param, err := factory()
251+
if err != nil {
252+
t.Fatal(err)
253+
}
254+
255+
var initialWidth, initialHeight, width, height int = 320, 240, 320, 240
256+
257+
var cnt uint32
258+
r, err := param.BuildVideoEncoder(
259+
video.ReaderFunc(func() (image.Image, func(), error) {
260+
i := atomic.AddUint32(&cnt, 1)
261+
if i == 3 {
262+
return nil, nil, io.EOF
263+
}
264+
return image.NewYCbCr(
265+
image.Rect(0, 0, width, height),
266+
image.YCbCrSubsampleRatio420,
267+
), func() {}, nil
268+
}),
269+
prop.Media{
270+
Video: prop.Video{
271+
Width: initialWidth,
272+
Height: initialHeight,
273+
FrameRate: 1,
274+
FrameFormat: frame.FormatI420,
275+
},
276+
},
277+
)
278+
if err != nil {
279+
t.Fatal(err)
280+
}
281+
_, rel, err := r.Read()
282+
if err != nil {
283+
t.Fatal(err)
284+
}
285+
rel()
286+
err = r.Controller().(codec.BitRateController).SetBitRate(1000) // 1000 bit/second is ridiculously low, but this is a testcase.
287+
if err != nil {
288+
t.Fatal(err)
289+
}
290+
_, rel, err = r.Read()
291+
if err != nil {
292+
t.Fatal(err)
293+
}
294+
rel()
295+
_, _, err = r.Read()
296+
if err != io.EOF {
297+
t.Fatal(err)
298+
}
299+
})
237300

301+
}
302+
}
303+
304+
func TestShouldImplementBitRateControl(t *testing.T) {
238305
e := &encoder{}
239306
if _, ok := e.Controller().(codec.BitRateController); !ok {
240307
t.Error()

pkg/codec/x264/bridge.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#define ERR_ALLOC_PICTURE -3
99
#define ERR_OPEN_ENGINE -4
1010
#define ERR_ENCODE -5
11+
#define ERR_BITRATE_RECONFIG -6
1112

1213
typedef struct Slice {
1314
unsigned char *data;
@@ -78,6 +79,22 @@ Encoder *enc_new(x264_param_t param, char *preset, int *rc) {
7879
return NULL;
7980
}
8081

82+
#define RC_MARGIN 10000 /* 1kilobits / second*/
83+
static int apply_target_bitrate(Encoder *e, int target_bitrate) {
84+
int target_encoder_bitrate = (int)target_bitrate / 1000;
85+
if (e->param.rc.i_bitrate == target_encoder_bitrate || target_encoder_bitrate <= 1) {
86+
return 0; // if no change to bitrate or target bitrate is too small, we return no error (0)
87+
}
88+
89+
e->param.rc.i_bitrate = target_encoder_bitrate;
90+
e->param.rc.f_rate_tolerance = 0.1;
91+
e->param.rc.i_vbv_max_bitrate = target_encoder_bitrate + RC_MARGIN / 2;
92+
e->param.rc.i_vbv_buffer_size = e->param.rc.i_vbv_max_bitrate;
93+
e->param.rc.f_vbv_buffer_init = 0.6;
94+
int success = x264_encoder_reconfig(e->h, &e->param);
95+
return success; // 0 on success or negative on error
96+
}
97+
8198
Slice enc_encode(Encoder *e, uint8_t *y, uint8_t *cb, uint8_t *cr, int *rc) {
8299
x264_nal_t *nal;
83100
int i_nal;

pkg/codec/x264/x264.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ func (e cerror) Error() string {
3939
return errOpenEngine.Error()
4040
case C.ERR_ENCODE:
4141
return errEncode.Error()
42+
case C.ERR_BITRATE_RECONFIG:
43+
return errSetBitrate.Error()
4244
default:
4345
return "unknown error"
4446
}
@@ -58,6 +60,7 @@ var (
5860
errAllocPicture = fmt.Errorf("failed to alloc picture")
5961
errOpenEngine = fmt.Errorf("failed to open x264")
6062
errEncode = fmt.Errorf("failed to encode")
63+
errSetBitrate = fmt.Errorf("failed to change x264 encoder bitrate")
6164
)
6265

6366
func newEncoder(r video.Reader, p prop.Media, params Params) (codec.ReadCloser, error) {
@@ -133,6 +136,16 @@ func (e *encoder) ForceKeyFrame() error {
133136
return nil
134137
}
135138

139+
func (e *encoder) SetBitRate(bitrate int) error {
140+
e.mu.Lock()
141+
defer e.mu.Unlock()
142+
errNum := C.apply_target_bitrate(e.engine, C.int(bitrate))
143+
if err := errFromC(errNum); err != nil {
144+
return err
145+
}
146+
return nil
147+
}
148+
136149
func (e *encoder) Controller() codec.EncoderController {
137150
return e
138151
}

pkg/codec/x264/x264_test.go

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,34 @@ import (
77
"github.com/pion/mediadevices/pkg/codec"
88
"github.com/pion/mediadevices/pkg/codec/internal/codectest"
99
"github.com/pion/mediadevices/pkg/frame"
10+
"github.com/pion/mediadevices/pkg/io/video"
1011
"github.com/pion/mediadevices/pkg/prop"
1112
)
1213

14+
func getTestVideoEncoder() (codec.ReadCloser, error) {
15+
p, err := NewParams()
16+
if err != nil {
17+
return nil, err
18+
}
19+
p.BitRate = 200000
20+
enc, err := p.BuildVideoEncoder(video.ReaderFunc(func() (image.Image, func(), error) {
21+
return image.NewYCbCr(
22+
image.Rect(0, 0, 256, 144),
23+
image.YCbCrSubsampleRatio420,
24+
), nil, nil
25+
}), prop.Media{
26+
Video: prop.Video{
27+
Width: 256,
28+
Height: 144,
29+
FrameFormat: frame.FormatI420,
30+
},
31+
})
32+
if err != nil {
33+
return nil, err
34+
}
35+
return enc, nil
36+
}
37+
1338
func TestEncoder(t *testing.T) {
1439
t.Run("SimpleRead", func(t *testing.T) {
1540
p, err := NewParams()
@@ -69,19 +94,53 @@ func TestEncoder(t *testing.T) {
6994
}
7095

7196
func TestShouldImplementKeyFrameControl(t *testing.T) {
72-
t.SkipNow() // TODO: Implement key frame control
73-
7497
e := &encoder{}
7598
if _, ok := e.Controller().(codec.KeyFrameController); !ok {
7699
t.Error()
77100
}
78101
}
79102

80-
func TestShouldImplementBitRateControl(t *testing.T) {
81-
t.SkipNow() // TODO: Implement bit rate control
103+
func TestNoErrorOnForceKeyFrame(t *testing.T) {
104+
enc, err := getTestVideoEncoder()
105+
if err != nil {
106+
t.Error(err)
107+
}
108+
kfc, ok := enc.Controller().(codec.KeyFrameController)
109+
if !ok {
110+
t.Error()
111+
}
112+
if err := kfc.ForceKeyFrame(); err != nil {
113+
t.Error(err)
114+
}
115+
_, rel, err := enc.Read() // try to read the encoded frame
116+
rel()
117+
if err != nil {
118+
t.Fatal(err)
119+
}
120+
}
82121

122+
func TestShouldImplementBitRateControl(t *testing.T) {
83123
e := &encoder{}
84124
if _, ok := e.Controller().(codec.BitRateController); !ok {
85125
t.Error()
86126
}
87127
}
128+
129+
func TestNoErrorOnSetBitRate(t *testing.T) {
130+
enc, err := getTestVideoEncoder()
131+
if err != nil {
132+
t.Error(err)
133+
}
134+
brc, ok := enc.Controller().(codec.BitRateController)
135+
if !ok {
136+
t.Error()
137+
}
138+
if err := brc.SetBitRate(1000); err != nil { // 1000 bit/second is ridiculously low, but this is a testcase.
139+
t.Error(err)
140+
}
141+
_, rel, err := enc.Read() // try to read the encoded frame
142+
rel()
143+
if err != nil {
144+
t.Fatal(err)
145+
}
146+
}

0 commit comments

Comments
 (0)