Add x264 codec

This commit is contained in:
Lukas Herman
2020-02-24 23:26:11 -08:00
parent 31227a18b2
commit ee73705329
3 changed files with 182 additions and 1 deletions

View File

@@ -27,7 +27,8 @@ jobs:
&& sudo apt-get install --no-install-recommends -y \
libopus-dev \
libva-dev \
libvpx-dev
libvpx-dev \
libx264-dev
- name: go vet
run: go vet ./...
- name: go build

88
pkg/codec/x264/bridge.h Normal file
View File

@@ -0,0 +1,88 @@
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <x264.h>
typedef struct Slice {
unsigned char *data;
int data_len;
} Slice;
typedef struct Encoder {
x264_t *h;
x264_picture_t pic_in, pic_out;
x264_param_t param;
} Encoder;
Encoder *enc_new(x264_param_t param) {
Encoder *e = (Encoder *)malloc(sizeof(Encoder));
if (x264_param_default_preset(&e->param, "veryfast", "zerolatency") < 0)
goto fail;
/* Configure non-default params */
e->param.i_csp = param.i_csp;
e->param.i_width = param.i_width;
e->param.i_height = param.i_height;
e->param.i_fps_num = param.i_fps_num;
e->param.i_fps_den = 1;
// Intra refres:
e->param.i_keyint_max = param.i_fps_num;
e->param.b_intra_refresh = 1;
// Rate control:
e->param.rc.i_rc_method = X264_RC_CRF;
e->param.rc.f_rf_constant = 25;
e->param.rc.f_rf_constant_max = 35;
// For streaming:
e->param.b_repeat_headers = 1;
e->param.b_annexb = 1;
if (x264_param_apply_profile(&e->param, "baseline") < 0)
goto fail;
if (x264_picture_alloc(&e->pic_in, param.i_csp, param.i_width, param.i_height) < 0)
goto fail;
#undef fail
#define fail fail2
e->h = x264_encoder_open(&e->param);
if (!e->h)
goto fail2;
return e;
#undef fail
fail2:
x264_picture_clean(&e->pic_in);
fail:
free(e);
// TODO: set appropriate errno
errno = -1;
return NULL;
}
Slice enc_encode(Encoder *e, uint8_t *y, uint8_t *cb, uint8_t *cr) {
x264_nal_t *nal;
int i_nal;
e->pic_in.img.plane[0] = y;
e->pic_in.img.plane[1] = cb;
e->pic_in.img.plane[2] = cr;
int frame_size = x264_encoder_encode(e->h, &nal, &i_nal, &e->pic_in, &e->pic_out);
Slice s = {.data_len = frame_size};
if (frame_size <= 0) {
return s;
}
e->pic_in.i_pts++;
s.data = nal->p_payload;
return s;
}
void enc_close(Encoder *e) {
x264_picture_clean(&e->pic_in);
free(e);
}

92
pkg/codec/x264/x264.go Normal file
View File

@@ -0,0 +1,92 @@
// Package x264 implements H264 encoder.
// This package requires libx264 headers and libraries to be built.
// Reference: https://code.videolan.org/videolan/x264/blob/master/example.c
package x264
// #cgo pkg-config: x264
// #include "bridge.h"
import "C"
import (
"fmt"
"image"
"io"
"unsafe"
"github.com/pion/mediadevices/pkg/codec"
mio "github.com/pion/mediadevices/pkg/io"
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/prop"
"github.com/pion/webrtc/v2"
)
type encoder struct {
engine *C.Encoder
buff []byte
r video.Reader
}
var (
errInitEngine = fmt.Errorf("failed to initialize x264")
errApplyProfile = fmt.Errorf("failed to apply profile")
errAllocPicture = fmt.Errorf("failed to alloc picture")
errOpenEngine = fmt.Errorf("failed to open x264")
errEncode = fmt.Errorf("failed to encode")
)
func init() {
codec.Register(webrtc.H264, codec.VideoEncoderBuilder(newEncoder))
}
func newEncoder(r video.Reader, p prop.Media) (io.ReadCloser, error) {
engine, err := C.enc_new(C.x264_param_t{
i_csp: C.X264_CSP_I420,
i_width: C.int(p.Width),
i_height: C.int(p.Height),
})
if err != nil {
return nil, errInitEngine
}
e := encoder{
engine: engine,
r: video.ToI420(r),
}
return &e, nil
}
func (e *encoder) Read(p []byte) (int, error) {
if e.buff != nil {
n, err := mio.Copy(p, e.buff)
if err == nil {
e.buff = nil
}
return n, err
}
img, err := e.r.Read()
if err != nil {
return 0, err
}
yuvImg := img.(*image.YCbCr)
s := C.enc_encode(
e.engine,
(*C.uchar)(&yuvImg.Y[0]),
(*C.uchar)(&yuvImg.Cb[0]),
(*C.uchar)(&yuvImg.Cr[0]),
)
if s.data_len < 0 {
return 0, errEncode
}
encoded := C.GoBytes(unsafe.Pointer(s.data), s.data_len)
n, err := mio.Copy(p, encoded)
if err != nil {
e.buff = encoded
}
return n, err
}
func (e *encoder) Close() error {
C.enc_close(e.engine)
return nil
}