mirror of
https://github.com/gen2brain/x264-go.git
synced 2024-09-10 09:31:13 +08:00
279 lines
5.2 KiB
Go
279 lines
5.2 KiB
Go
// Package x264 provides H.264/MPEG-4 AVC codec encoder based on [x264](https://www.videolan.org/developers/x264.html) library.
|
|
package x264
|
|
|
|
/*
|
|
#include <stdlib.h>
|
|
*/
|
|
import "C"
|
|
|
|
import (
|
|
"fmt"
|
|
"image"
|
|
"io"
|
|
|
|
"github.com/gen2brain/x264-go/x264c"
|
|
)
|
|
|
|
// Logging constants.
|
|
const (
|
|
LogNone int32 = iota - 1
|
|
LogError
|
|
LogWarning
|
|
LogInfo
|
|
LogDebug
|
|
)
|
|
|
|
// Options represent encoding options.
|
|
type Options struct {
|
|
// Frame width.
|
|
Width int
|
|
// Frame height.
|
|
Height int
|
|
// Frame rate.
|
|
FrameRate int
|
|
// Tunings: film, animation, grain, stillimage, psnr, ssim, fastdecode, zerolatency.
|
|
Tune string
|
|
// Presets: ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow, placebo.
|
|
Preset string
|
|
// Profiles: baseline, main, high, high10, high422, high444.
|
|
Profile string
|
|
// RateControl: cqp, crf, abr.
|
|
RateControl string
|
|
// RateConstant.
|
|
RateConstant float32
|
|
// RateMax.
|
|
RateMax float32
|
|
// Log level.
|
|
LogLevel int32
|
|
}
|
|
|
|
// Encoder type.
|
|
type Encoder struct {
|
|
e *x264c.T
|
|
w io.Writer
|
|
|
|
img *YCbCr
|
|
opts *Options
|
|
|
|
csp int32
|
|
pts int64
|
|
dts int64
|
|
|
|
nnals int32
|
|
nals []*x264c.Nal
|
|
|
|
picIn x264c.Picture
|
|
|
|
tpf int64
|
|
}
|
|
|
|
// NewEncoder returns new x264 encoder.
|
|
func NewEncoder(w io.Writer, opts *Options) (e *Encoder, err error) {
|
|
e = &Encoder{}
|
|
|
|
e.w = w
|
|
e.pts = 0
|
|
e.opts = opts
|
|
|
|
e.csp = x264c.CspI420
|
|
|
|
e.nals = make([]*x264c.Nal, 3)
|
|
e.img = NewYCbCr(image.Rect(0, 0, e.opts.Width, e.opts.Height))
|
|
|
|
param := x264c.Param{}
|
|
|
|
if e.opts.Preset != "" && e.opts.Tune != "" {
|
|
ret := x264c.ParamDefaultPreset(¶m, e.opts.Preset, e.opts.Tune)
|
|
if ret < 0 {
|
|
err = fmt.Errorf("x264: invalid preset/tune name")
|
|
return
|
|
}
|
|
} else {
|
|
x264c.ParamDefault(¶m)
|
|
}
|
|
|
|
param.IWidth = int32(e.opts.Width)
|
|
param.IHeight = int32(e.opts.Height)
|
|
param.ICsp = e.csp
|
|
param.ILogLevel = e.opts.LogLevel
|
|
param.IBitdepth = 8
|
|
|
|
param.BVfrInput = 0
|
|
param.BRepeatHeaders = 1
|
|
param.BAnnexb = 1
|
|
|
|
param.BIntraRefresh = 1
|
|
param.IKeyintMax = int32(e.opts.FrameRate)
|
|
param.IFpsNum = uint32(e.opts.FrameRate)
|
|
param.IFpsDen = 1
|
|
|
|
if e.opts.Profile != "" {
|
|
ret := x264c.ParamApplyProfile(¶m, e.opts.Profile)
|
|
if ret < 0 {
|
|
err = fmt.Errorf("x264: invalid profile name")
|
|
return
|
|
}
|
|
}
|
|
|
|
if e.opts.RateControl != "" {
|
|
switch e.opts.RateControl {
|
|
case "cqp":
|
|
param.Rc.IRcMethod = x264c.RcCqp
|
|
if e.opts.RateConstant != 0 {
|
|
param.Rc.IQpConstant = int32(e.opts.RateConstant)
|
|
}
|
|
if e.opts.RateMax != 0 {
|
|
param.Rc.IQpMax = int32(e.opts.RateMax)
|
|
}
|
|
case "crf":
|
|
param.Rc.IRcMethod = x264c.RcCrf
|
|
if e.opts.RateConstant != 0 {
|
|
param.Rc.FRfConstant = e.opts.RateConstant
|
|
}
|
|
if e.opts.RateMax != 0 {
|
|
param.Rc.FRfConstantMax = e.opts.RateMax
|
|
}
|
|
case "abr":
|
|
param.Rc.IRcMethod = x264c.RcAbr
|
|
if e.opts.RateMax != 0 {
|
|
param.Rc.IVbvMaxBitrate = int32(e.opts.RateMax)
|
|
}
|
|
}
|
|
}
|
|
|
|
var picIn x264c.Picture
|
|
x264c.PictureInit(&picIn)
|
|
e.picIn = picIn
|
|
|
|
e.e = x264c.EncoderOpen(¶m)
|
|
if e.e == nil {
|
|
err = fmt.Errorf("x264: cannot open the encoder")
|
|
return
|
|
}
|
|
|
|
ret := x264c.EncoderHeaders(e.e, e.nals, &e.nnals)
|
|
if ret < 0 {
|
|
err = fmt.Errorf("x264: cannot encode headers")
|
|
return
|
|
}
|
|
|
|
if ret > 0 {
|
|
b := C.GoBytes(e.nals[0].PPayload, C.int(ret))
|
|
n, er := e.w.Write(b)
|
|
if er != nil {
|
|
err = er
|
|
return
|
|
}
|
|
|
|
if int(ret) != n {
|
|
err = fmt.Errorf("x264: error writing headers, size=%d, n=%d", ret, n)
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Encode encodes image.
|
|
func (e *Encoder) Encode(im image.Image) (err error) {
|
|
var picOut x264c.Picture
|
|
|
|
_, rgba := im.(*image.RGBA)
|
|
_, ycbcr := im.(*YCbCr)
|
|
|
|
if rgba {
|
|
e.img.ToYCbCr(im)
|
|
} else if ycbcr {
|
|
e.img = im.(*YCbCr)
|
|
} else {
|
|
e.img.ToYCbCrDraw(im)
|
|
}
|
|
|
|
picIn := e.picIn
|
|
|
|
picIn.Img.ICsp = e.csp
|
|
|
|
picIn.Img.IPlane = 3
|
|
picIn.Img.IStride[0] = int32(e.opts.Width)
|
|
picIn.Img.IStride[1] = int32(e.opts.Width) / 2
|
|
picIn.Img.IStride[2] = int32(e.opts.Width) / 2
|
|
|
|
picIn.Img.Plane[0] = C.CBytes(e.img.Y)
|
|
picIn.Img.Plane[1] = C.CBytes(e.img.Cb)
|
|
picIn.Img.Plane[2] = C.CBytes(e.img.Cr)
|
|
|
|
picIn.IPts = e.pts
|
|
e.pts++
|
|
|
|
defer func() {
|
|
picIn.FreePlane(0)
|
|
picIn.FreePlane(1)
|
|
picIn.FreePlane(2)
|
|
}()
|
|
|
|
ret := x264c.EncoderEncode(e.e, e.nals, &e.nnals, &picIn, &picOut)
|
|
if ret < 0 {
|
|
err = fmt.Errorf("x264: cannot encode picture")
|
|
return
|
|
}
|
|
|
|
if ret > 0 {
|
|
b := C.GoBytes(e.nals[0].PPayload, C.int(ret))
|
|
|
|
n, er := e.w.Write(b)
|
|
if er != nil {
|
|
err = er
|
|
return
|
|
}
|
|
|
|
if int(ret) != n {
|
|
err = fmt.Errorf("x264: error writing payload, size=%d, n=%d", ret, n)
|
|
}
|
|
}
|
|
|
|
e.dts = picOut.IDts
|
|
|
|
return
|
|
}
|
|
|
|
// Timestamp returns the current PTS and DTS.
|
|
func (e *Encoder) Timestamp() (int64, int64) {
|
|
return e.pts, e.dts
|
|
}
|
|
|
|
// Flush flushes encoder.
|
|
func (e *Encoder) Flush() (err error) {
|
|
var picOut x264c.Picture
|
|
|
|
for x264c.EncoderDelayedFrames(e.e) > 0 {
|
|
ret := x264c.EncoderEncode(e.e, e.nals, &e.nnals, nil, &picOut)
|
|
if ret < 0 {
|
|
err = fmt.Errorf("x264: cannot encode picture")
|
|
return
|
|
}
|
|
|
|
if ret > 0 {
|
|
b := C.GoBytes(e.nals[0].PPayload, C.int(ret))
|
|
|
|
n, er := e.w.Write(b)
|
|
if er != nil {
|
|
err = er
|
|
return
|
|
}
|
|
|
|
if int(ret) != n {
|
|
err = fmt.Errorf("x264: error writing payload, size=%d, n=%d", ret, n)
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Close closes encoder.
|
|
func (e *Encoder) Close() error {
|
|
picIn := e.picIn
|
|
x264c.PictureClean(&picIn)
|
|
x264c.EncoderClose(e.e)
|
|
return nil
|
|
}
|