Skip to content

Commit

Permalink
Add bitrate control to vpx and h264 encoders
Browse files Browse the repository at this point in the history
  • Loading branch information
KW-M committed Jan 20, 2023
1 parent 094e43d commit 98b137d
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 14 deletions.
35 changes: 27 additions & 8 deletions pkg/codec/vpx/vpx.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ type encoder struct {
frame []byte
deadline int
requireKeyFrame bool
targetBitrate int
isKeyFrame bool

mu sync.Mutex
Expand Down Expand Up @@ -200,14 +201,15 @@ func newEncoder(r video.Reader, p prop.Media, params Params, codecIface *C.vpx_c
}
t0 := time.Now().Nanosecond() / 1000000
return &encoder{
r: video.ToI420(r),
codec: codec,
raw: rawNoBuffer,
cfg: cfg,
tStart: t0,
tLastFrame: t0,
deadline: int(params.Deadline / time.Microsecond),
frame: make([]byte, 1024),
r: video.ToI420(r),
codec: codec,
raw: rawNoBuffer,
cfg: cfg,
tStart: t0,
tLastFrame: t0,
deadline: int(params.Deadline / time.Microsecond),
frame: make([]byte, 1024),
targetBitrate: params.BitRate,
}, nil
}

Expand Down Expand Up @@ -260,6 +262,16 @@ func (e *encoder) Read() ([]byte, func(), error) {
if duration == 0 {
duration = 1
}

targetVpxBitrate := C.uint(float32(e.targetBitrate / 1000)) // convert to kilobits / second
if e.cfg.rc_target_bitrate != targetVpxBitrate && targetVpxBitrate >= 1 {
e.cfg.rc_target_bitrate = targetVpxBitrate
rc := C.vpx_codec_enc_config_set(e.codec, e.cfg)
if rc != C.VPX_CODEC_OK {
return nil, func() {}, fmt.Errorf("vpx_codec_enc_config_set failed (%d)", rc)
}
}

var flags int
if e.requireKeyFrame {
flags = flags | C.VPX_EFLAG_FORCE_KF
Expand Down Expand Up @@ -302,6 +314,13 @@ func (e *encoder) ForceKeyFrame() error {
return nil
}

func (e *encoder) SetBitRate(bitrate int) error {
e.mu.Lock()
defer e.mu.Unlock()
e.targetBitrate = bitrate
return nil
}

func (e *encoder) Controller() codec.EncoderController {
return e
}
Expand Down
71 changes: 69 additions & 2 deletions pkg/codec/vpx/vpx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,76 @@ func TestRequestKeyFrame(t *testing.T) {
}
}

func TestShouldImplementBitRateControl(t *testing.T) {
t.SkipNow() // TODO: Implement bit rate control
func TestSetBitrate(t *testing.T) {
for name, factory := range map[string]func() (codec.VideoEncoderBuilder, error){
"VP8": func() (codec.VideoEncoderBuilder, error) {
p, err := NewVP8Params()
return &p, err
},
"VP9": func() (codec.VideoEncoderBuilder, error) {
p, err := NewVP9Params()
// Disable latency to ease test and begin to receive packets for each input frame
p.LagInFrames = 0
return &p, err
},
} {
factory := factory
t.Run(name, func(t *testing.T) {
param, err := factory()
if err != nil {
t.Fatal(err)
}

var initialWidth, initialHeight, width, height int = 320, 240, 320, 240

var cnt uint32
r, err := param.BuildVideoEncoder(
video.ReaderFunc(func() (image.Image, func(), error) {
i := atomic.AddUint32(&cnt, 1)
if i == 3 {
return nil, nil, io.EOF
}
return image.NewYCbCr(
image.Rect(0, 0, width, height),
image.YCbCrSubsampleRatio420,
), func() {}, nil
}),
prop.Media{
Video: prop.Video{
Width: initialWidth,
Height: initialHeight,
FrameRate: 1,
FrameFormat: frame.FormatI420,
},
},
)
if err != nil {
t.Fatal(err)
}
_, rel, err := r.Read()
if err != nil {
t.Fatal(err)
}
rel()
err = r.Controller().(codec.BitRateController).SetBitRate(1000) // 1000 bit/second is ridiculously low, but this is a testcase.
if err != nil {
t.Fatal(err)
}
_, rel, err = r.Read()
if err != nil {
t.Fatal(err)
}
rel()
_, _, err = r.Read()
if err != io.EOF {
t.Fatal(err)
}
})

}
}

func TestShouldImplementBitRateControl(t *testing.T) {
e := &encoder{}
if _, ok := e.Controller().(codec.BitRateController); !ok {
t.Error()
Expand Down
17 changes: 17 additions & 0 deletions pkg/codec/x264/bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#define ERR_ALLOC_PICTURE -3
#define ERR_OPEN_ENGINE -4
#define ERR_ENCODE -5
#define ERR_BITRATE_RECONFIG -6

typedef struct Slice {
unsigned char *data;
Expand Down Expand Up @@ -78,6 +79,22 @@ Encoder *enc_new(x264_param_t param, char *preset, int *rc) {
return NULL;
}

#define RC_MARGIN 10000 /* 1kilobits / second*/
static int apply_target_bitrate(Encoder *e, int target_bitrate) {
int target_encoder_bitrate = (int)target_bitrate / 1000;
if (e->param.rc.i_bitrate == target_encoder_bitrate || target_encoder_bitrate <= 1) {
return 0; // if no change to bitrate or target bitrate is too small, we return no error (0)
}

e->param.rc.i_bitrate = target_encoder_bitrate;
e->param.rc.f_rate_tolerance = 0.1;
e->param.rc.i_vbv_max_bitrate = target_encoder_bitrate + RC_MARGIN / 2;
e->param.rc.i_vbv_buffer_size = e->param.rc.i_vbv_max_bitrate;
e->param.rc.f_vbv_buffer_init = 0.6;
int success = x264_encoder_reconfig(e->h, &e->param);
return success; // 0 on success or negative on error
}

Slice enc_encode(Encoder *e, uint8_t *y, uint8_t *cb, uint8_t *cr, int *rc) {
x264_nal_t *nal;
int i_nal;
Expand Down
13 changes: 13 additions & 0 deletions pkg/codec/x264/x264.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ func (e cerror) Error() string {
return errOpenEngine.Error()
case C.ERR_ENCODE:
return errEncode.Error()
case C.ERR_BITRATE_RECONFIG:
return errSetBitrate.Error()
default:
return "unknown error"
}
Expand All @@ -58,6 +60,7 @@ var (
errAllocPicture = fmt.Errorf("failed to alloc picture")
errOpenEngine = fmt.Errorf("failed to open x264")
errEncode = fmt.Errorf("failed to encode")
errSetBitrate = fmt.Errorf("failed to change x264 encoder bitrate")
)

func newEncoder(r video.Reader, p prop.Media, params Params) (codec.ReadCloser, error) {
Expand Down Expand Up @@ -132,6 +135,16 @@ func (e *encoder) ForceKeyFrame() error {
return nil
}

func (e *encoder) SetBitRate(bitrate int) error {
e.mu.Lock()
defer e.mu.Unlock()
errNum := C.apply_target_bitrate(e.engine, C.int(bitrate))
if err := errFromC(errNum); err != nil {
return err
}
return nil
}

func (e *encoder) Controller() codec.EncoderController {
return e
}
Expand Down
67 changes: 63 additions & 4 deletions pkg/codec/x264/x264_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,34 @@ import (
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/codec/internal/codectest"
"github.com/pion/mediadevices/pkg/frame"
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/prop"
)

func getTestVideoEncoder() (codec.ReadCloser, error) {
p, err := NewParams()
if err != nil {
return nil, err
}
p.BitRate = 200000
enc, err := p.BuildVideoEncoder(video.ReaderFunc(func() (image.Image, func(), error) {
return image.NewYCbCr(
image.Rect(0, 0, 256, 144),
image.YCbCrSubsampleRatio420,
), nil, nil
}), prop.Media{
Video: prop.Video{
Width: 256,
Height: 144,
FrameFormat: frame.FormatI420,
},
})
if err != nil {
return nil, err
}
return enc, nil
}

func TestEncoder(t *testing.T) {
t.Run("SimpleRead", func(t *testing.T) {
p, err := NewParams()
Expand Down Expand Up @@ -69,19 +94,53 @@ func TestEncoder(t *testing.T) {
}

func TestShouldImplementKeyFrameControl(t *testing.T) {
t.SkipNow() // TODO: Implement key frame control

e := &encoder{}
if _, ok := e.Controller().(codec.KeyFrameController); !ok {
t.Error()
}
}

func TestShouldImplementBitRateControl(t *testing.T) {
t.SkipNow() // TODO: Implement bit rate control
func TestNoErrorOnForceKeyFrame(t *testing.T) {
enc, err := getTestVideoEncoder()
if err != nil {
t.Error(err)
}
kfc, ok := enc.Controller().(codec.KeyFrameController)
if !ok {
t.Error()
}
if err := kfc.ForceKeyFrame(); err != nil {
t.Error(err)
}
_, rel, err := enc.Read() // try to read the encoded frame
rel()
if err != nil {
t.Fatal(err)
}
}

func TestShouldImplementBitRateControl(t *testing.T) {
e := &encoder{}
if _, ok := e.Controller().(codec.BitRateController); !ok {
t.Error()
}
}

func TestNoErrorOnSetBitRate(t *testing.T) {
enc, err := getTestVideoEncoder()
if err != nil {
t.Error(err)
}
brc, ok := enc.Controller().(codec.BitRateController)
if !ok {
t.Error()
}
if err := brc.SetBitRate(1000); err != nil { // 1000 bit/second is ridiculously low, but this is a testcase.
t.Error(err)
}
_, rel, err := enc.Read() // try to read the encoded frame
rel()
if err != nil {
t.Fatal(err)
}
}

0 comments on commit 98b137d

Please sign in to comment.