mirror of
https://github.com/pion/mediadevices.git
synced 2025-09-26 20:41:46 +08:00
Add x264 codec
This commit is contained in:
3
.github/workflows/ci.yaml
vendored
3
.github/workflows/ci.yaml
vendored
@@ -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
88
pkg/codec/x264/bridge.h
Normal 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
92
pkg/codec/x264/x264.go
Normal 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
|
||||
}
|
Reference in New Issue
Block a user