Ignite/encoder/libaom/libaom.go
DataHoarder 6fbd6290ce
Some checks failed
continuous-integration/drone/push Build is failing
Add noise width/height to libaom
2023-11-05 18:16:55 +01:00

759 lines
22 KiB
Go

//go:build cgo && !disable_library_libaom
package libaom
/*
#cgo pkg-config: aom
#include "libaom.h"
*/
import "C"
import (
"errors"
"fmt"
"git.gammaspectra.live/S.O.N.G/Ignite/color"
"git.gammaspectra.live/S.O.N.G/Ignite/frame"
"git.gammaspectra.live/S.O.N.G/Ignite/utilities"
"git.gammaspectra.live/S.O.N.G/Ignite/utilities/filmgrain"
"git.gammaspectra.live/S.O.N.G/Ignite/utilities/obuwriter"
"golang.org/x/exp/constraints"
"io"
"log"
"maps"
"os"
"runtime"
"strconv"
"strings"
"sync/atomic"
"unsafe"
)
type Encoder struct {
w *obuwriter.Writer
cleaned atomic.Bool
cfg C.aom_codec_enc_cfg_t
codec C.aom_codec_ctx_t
raw *C.aom_image_t
rawBuffer []byte
framesIn, framesOut int
frameStatistics frame.FrameStatistics
resourcePinner runtime.Pinner
logger utilities.Logger
free []func()
}
var libaomVersion = "libaom-av1 " + C.GoString(C.aom_codec_version_str()) + " ABI " + strconv.FormatUint(C.AOM_ENCODER_ABI_VERSION, 10)
func Version() string {
return libaomVersion
}
const (
UsageGoodQuality = C.AOM_USAGE_GOOD_QUALITY
UsageRealtime = C.AOM_USAGE_REALTIME
UsageAllIntra = C.AOM_USAGE_ALL_INTRA
)
func NewEncoder(w io.Writer, properties frame.StreamProperties, settings map[string]any, logger utilities.Logger) (*Encoder, error) {
e := &Encoder{
frameStatistics: make(frame.FrameStatistics),
logger: logger,
}
clonedSettings := make(map[string]any)
if modelPath := os.Getenv("VMAF_MODEL_PATH"); modelPath != "" {
clonedSettings["vmaf-model-path"] = modelPath
}
maps.Copy(clonedSettings, settings)
photonNoiseIso := getSettingUnsigned[uint](clonedSettings, "photon-noise-iso", 0)
photonNoiseTransferFunction := filmgrain.GetTransferFunction(getSettingString(clonedSettings, "photon-noise-transfer", "bt709"))
if photonNoiseIso > 0 && photonNoiseTransferFunction != nil {
//create table
noiseWidth := getSettingUnsigned[uint](clonedSettings, "photon-noise-width", uint(properties.Width))
noiseHeight := getSettingUnsigned[uint](clonedSettings, "photon-noise-height", uint(properties.Height))
table, err := filmgrain.CreatePhotonNoiseTable(int(noiseWidth), int(noiseHeight), float64(photonNoiseIso), photonNoiseTransferFunction)
if err != nil {
return nil, err
}
tmpFile, err := os.CreateTemp(os.TempDir(), "photon-table*.tbl")
if err != nil {
return nil, err
}
_, err = tmpFile.Write(table)
if err != nil {
return nil, err
}
tmpName := tmpFile.Name()
tmpFile.Close()
e.free = append(e.free, func() {
os.Remove(tmpName)
})
clonedSettings["film-grain-table"] = tmpName
}
var aomErr C.aom_codec_err_t
encoder := C.aom_codec_av1_cx()
if encoder == nil {
return nil, errors.New("unsupported codec")
}
e.cfg.g_usage = C.uint(getSettingUnsigned(clonedSettings, "usage", uint(UsageGoodQuality)))
if getSettingBool(clonedSettings, "good", false) {
e.cfg.g_usage = UsageGoodQuality
}
if getSettingBool(clonedSettings, "rt", false) {
e.cfg.g_usage = UsageRealtime
}
if getSettingBool(clonedSettings, "allintra", false) {
e.cfg.g_usage = UsageAllIntra
}
var imageFormat C.aom_img_fmt_t
var flags C.aom_codec_flags_t
if aomErr = C.aom_codec_enc_config_default(encoder, &e.cfg, e.cfg.g_usage); aomErr != 0 {
return nil, errors.New("failed to get default codec config")
}
var bitsPerSample, decH, decV C.int
switch true {
case properties.ColorSpace.ChromaSampling.J == 4 && properties.ColorSpace.ChromaSampling.A == 4 && properties.ColorSpace.ChromaSampling.B == 4:
imageFormat = C.AOM_IMG_FMT_I444
e.cfg.g_profile = 1
switch properties.ColorSpace.BitDepth {
case C.AOM_BITS_8:
bitsPerSample = 24
case C.AOM_BITS_10:
bitsPerSample = 30
case C.AOM_BITS_12:
bitsPerSample = 36
}
decH = 1
decV = 1
case properties.ColorSpace.ChromaSampling.J == 4 && properties.ColorSpace.ChromaSampling.A == 2 && properties.ColorSpace.ChromaSampling.B == 2:
imageFormat = C.AOM_IMG_FMT_I422
e.cfg.g_profile = 2
switch properties.ColorSpace.BitDepth {
case C.AOM_BITS_8:
bitsPerSample = 16
case C.AOM_BITS_10:
bitsPerSample = 20
case C.AOM_BITS_12:
bitsPerSample = 24
}
decH = 2
decV = 1
case properties.ColorSpace.ChromaSampling.J == 4 && properties.ColorSpace.ChromaSampling.A == 2 && properties.ColorSpace.ChromaSampling.B == 0:
imageFormat = C.AOM_IMG_FMT_I420
e.cfg.g_profile = 0
switch properties.ColorSpace.BitDepth {
case C.AOM_BITS_8:
bitsPerSample = 12
case C.AOM_BITS_10:
bitsPerSample = 15
case C.AOM_BITS_12:
bitsPerSample = 18
}
decH = 2
decV = 2
case properties.ColorSpace.ChromaSampling.J == 4 && properties.ColorSpace.ChromaSampling.A == 0 && properties.ColorSpace.ChromaSampling.B == 0:
//mono is defined as 4:2:0, but monochrome is set on config
imageFormat = C.AOM_IMG_FMT_I420
e.cfg.g_profile = 0
e.cfg.monochrome = 1
bitsPerSample = C.int(properties.ColorSpace.BitDepth)
decH = 0
decV = 2
default:
return nil, errors.New("unsupported input chroma subsampling")
}
e.cfg.g_input_bit_depth = C.uint(properties.ColorSpace.BitDepth)
e.cfg.g_bit_depth = C.aom_bit_depth_t(properties.ColorSpace.BitDepth)
if e.cfg.g_bit_depth >= C.AOM_BITS_12 { //only bitdepths up to 12 are supported, see aom_bit_depth_t
e.cfg.g_bit_depth = C.AOM_BITS_12
e.cfg.g_profile = 2
}
if e.cfg.g_input_bit_depth > C.AOM_BITS_8 {
imageFormat |= C.AOM_IMG_FMT_HIGHBITDEPTH
}
if e.cfg.g_bit_depth > C.AOM_BITS_8 {
flags |= C.AOM_CODEC_USE_HIGHBITDEPTH
}
frameSize, err := properties.ColorSpace.FrameSize(properties.Width, properties.Height)
if err != nil {
return nil, err
}
bytesPerSample := 1
if properties.ColorSpace.BitDepth > C.AOM_BITS_8 {
bytesPerSample = 2
}
e.raw = &C.aom_image_t{}
e.raw.bit_depth = C.uint(properties.ColorSpace.BitDepth)
//todo: color primaries
e.raw.cp = C.AOM_CICP_CP_UNSPECIFIED
//todo: transfer characteristics
e.raw.tc = C.AOM_CICP_TC_UNSPECIFIED
//todo: matrix coefficients
e.raw.mc = C.AOM_CICP_MC_UNSPECIFIED
e.raw.monochrome = C.int(e.cfg.monochrome)
e.raw.fmt = imageFormat
switch properties.ColorSpace.ChromaSamplePosition {
case color.ChromaSamplePositionLeft:
e.raw.csp = C.AOM_CSP_VERTICAL
case color.ChromaSamplePositionCenter:
e.raw.csp = C.AOM_CSP_UNKNOWN
case color.ChromaSamplePositionTopLeft:
e.raw.csp = C.AOM_CSP_COLOCATED
default:
e.raw.csp = C.AOM_CSP_UNKNOWN
}
if properties.FullColorRange {
e.raw._range = C.AOM_CR_FULL_RANGE
} else {
e.raw._range = C.AOM_CR_STUDIO_RANGE
}
e.cfg.g_w = C.uint(properties.Width)
e.cfg.g_h = C.uint(properties.Height)
e.raw.w = e.cfg.g_w
e.raw.d_w = e.cfg.g_w
e.raw.h = e.cfg.g_h
e.raw.d_h = e.cfg.g_h
e.raw.bps = bitsPerSample
e.raw.x_chroma_shift = C.uint(decH >> 1)
e.raw.y_chroma_shift = C.uint(decV >> 1)
c_w := (C.int(properties.Width) + decH - 1) / decH
c_w *= C.int(bytesPerSample)
e.rawBuffer = make([]byte, frameSize)
e.resourcePinner.Pin(e.raw)
e.resourcePinner.Pin(unsafe.SliceData(e.rawBuffer))
iY := properties.ColorSpace.ChromaSampling.PlaneLumaSamples(properties.Width, properties.Height)
iCb := properties.ColorSpace.ChromaSampling.PlaneCbSamples(properties.Width, properties.Height)
iCr := properties.ColorSpace.ChromaSampling.PlaneCrSamples(properties.Width, properties.Height)
iY *= bytesPerSample
iCb *= bytesPerSample
iCr *= bytesPerSample
e.raw.stride[C.AOM_PLANE_Y] = C.int(properties.Width * bytesPerSample)
e.raw.stride[C.AOM_PLANE_U] = c_w
e.raw.stride[C.AOM_PLANE_V] = e.raw.stride[C.AOM_PLANE_U]
e.raw.planes[C.AOM_PLANE_Y] = (*C.uchar)(unsafe.Pointer(unsafe.SliceData(e.rawBuffer[:iY])))
e.raw.planes[C.AOM_PLANE_U] = (*C.uchar)(unsafe.Pointer(unsafe.SliceData(e.rawBuffer[iY : iY+iCb])))
e.raw.planes[C.AOM_PLANE_V] = (*C.uchar)(unsafe.Pointer(unsafe.SliceData(e.rawBuffer[iY+iCb : iY+iCb+iCr])))
runtime.SetFinalizer(e, func(encoder *Encoder) {
encoder.Close()
})
/*!\brief Stream timebase units
*
* Indicates the smallest interval of time, in seconds, used by the stream.
* For fixed frame rate material, or variable frame rate material where
* frames are timed at a multiple of a given clock (ex: video capture),
* the \ref RECOMMENDED method is to set the timebase to the reciprocal
* of the frame rate (ex: 1001/30000 for 29.970 Hz NTSC). This allows the
* pts to correspond to the frame number, which can be handy. For
* re-encoding video from containers with absolute time timestamps, the
* \ref RECOMMENDED method is to set the timebase to that of the parent
* container or multimedia framework (ex: 1/1000 for ms, as in FLV).
*/
timeBase := properties.TimeBase()
e.cfg.g_timebase.num = C.int(timeBase.Numerator)
e.cfg.g_timebase.den = C.int(timeBase.Denominator)
// boolean settings
if getSettingBool(clonedSettings, "large-scale-tile", e.cfg.large_scale_tile != 0) {
e.cfg.large_scale_tile = 1
}
if getSettingBool(clonedSettings, "monochrome", e.cfg.monochrome != 0) {
e.cfg.monochrome = 1
}
if getSettingBool(clonedSettings, "enable-fwd-kf", e.cfg.fwd_kf_enabled != 0) {
e.cfg.fwd_kf_enabled = 1
}
if getSettingBool(clonedSettings, "kf-disabled", false) {
e.cfg.kf_mode = C.AOM_KF_DISABLED
}
// integer settings
type uintSettingPair struct {
p *C.uint
n string
}
for _, s := range []uintSettingPair{
{&e.cfg.g_threads, "threads"},
{&e.cfg.g_lag_in_frames, "lag-in-frames"},
{&e.cfg.g_forced_max_frame_width, "forced_max_frame_width"},
{&e.cfg.g_forced_max_frame_height, "forced_max_frame_height"},
{&e.cfg.rc_dropframe_thresh, "drop-frame"},
{&e.cfg.rc_resize_mode, "resize-mode"},
{&e.cfg.rc_resize_denominator, "resize-denominator"},
{&e.cfg.rc_resize_kf_denominator, "resize-kf-denominator"},
{(*C.uint)(&e.cfg.rc_superres_mode), "superres-mode"},
{&e.cfg.rc_superres_denominator, "superres-denominator"},
{&e.cfg.rc_superres_kf_denominator, "superres-kf-denominator"},
{&e.cfg.rc_superres_qthresh, "superres-qthresh"},
{&e.cfg.rc_superres_kf_qthresh, "superres-kf-qthresh"},
{&e.cfg.rc_target_bitrate, "target-bitrate"},
{&e.cfg.rc_min_quantizer, "min-q"},
{&e.cfg.rc_max_quantizer, "max-q"},
{&e.cfg.rc_undershoot_pct, "undershoot-pct"},
{&e.cfg.rc_overshoot_pct, "overshoot-pct"},
{&e.cfg.rc_buf_sz, "buf-sz"},
{&e.cfg.rc_buf_initial_sz, "buf-initial-sz"},
{&e.cfg.rc_buf_optimal_sz, "buf-optimal-sz"},
//{&e.cfg.rc_2pass_vbr_bias_pct, "bias-pct"},
//{&e.cfg.rc_2pass_vbr_minsection_pct, "minsection-pct"},
//{&e.cfg.rc_2pass_vbr_maxsection_pct, "maxsection-pct"},
{&e.cfg.kf_min_dist, "kf-min-dist"},
{&e.cfg.kf_max_dist, "kf-max-dist"},
{&e.cfg.sframe_dist, "sframe-dist"},
{&e.cfg.sframe_mode, "sframe-mode"},
{&e.cfg.use_fixed_qp_offsets, "use-fixed-qp-offsets"},
} {
//todo: unset setting from map
*s.p = C.uint(getSettingUnsigned(clonedSettings, s.n, uint(*s.p)))
}
// string/enum settings
endUsage := getSettingString(clonedSettings, "end-usage", "vbr")
switch endUsage {
case "vbr":
e.cfg.rc_end_usage = C.AOM_VBR
case "cbr":
e.cfg.rc_end_usage = C.AOM_CBR
case "cq":
e.cfg.rc_end_usage = C.AOM_CQ
case "q":
e.cfg.rc_end_usage = C.AOM_Q
default:
return nil, errors.New("unknown end-usage setting: " + endUsage)
}
//TODO: find all settings not set on AV1 encoder and place them on e.cfg
if aomErr = C.aom_codec_enc_init_ver(&e.codec, encoder, &e.cfg, flags, C.AOM_ENCODER_ABI_VERSION); aomErr != 0 {
return nil, fmt.Errorf("failed to initialize encoder: err %d %s", aomErr, C.GoString(e.codec.err_detail))
}
e.free = append(e.free, func() {
C.aom_codec_destroy(&e.codec)
})
if properties.FullColorRange {
if aomErr = C.aom_codec_control_uint(&e.codec, C.AV1E_SET_COLOR_RANGE, C.AOM_CR_FULL_RANGE); aomErr != 0 {
return nil, fmt.Errorf("failed to set color range")
}
} else {
if aomErr = C.aom_codec_control_uint(&e.codec, C.AV1E_SET_COLOR_RANGE, C.AOM_CR_STUDIO_RANGE); aomErr != 0 {
return nil, fmt.Errorf("failed to set color range")
}
}
//??? aomenc does this
if properties.ColorSpace.BitDepth == 12 {
if aomErr = C.aom_codec_control_uint(&e.codec, C.AV1E_SET_CHROMA_SUBSAMPLING_X, C.uint(decH>>1)); aomErr != 0 {
return nil, fmt.Errorf("failed to set chroma subsampling X")
}
if aomErr = C.aom_codec_control_uint(&e.codec, C.AV1E_SET_CHROMA_SUBSAMPLING_Y, C.uint(decV>>1)); aomErr != 0 {
return nil, fmt.Errorf("failed to set chroma subsampling Y")
}
}
//TODO: fill all
controlSettings := map[string]C.int{
"cpu-used": C.AOME_SET_CPUUSED,
"auto-alt-ref": C.AOME_SET_ENABLEAUTOALTREF,
"sharpness": C.AOME_SET_SHARPNESS,
"row-mt": C.AV1E_SET_ROW_MT,
//"fp-mt": C.AV1E_SET_FP_MT,
"tile-columns": C.AV1E_SET_TILE_COLUMNS,
"tile-rows": C.AV1E_SET_TILE_ROWS,
"cq-level": C.AOME_SET_CQ_LEVEL,
"max-reference-frames": C.AV1E_SET_MAX_REFERENCE_FRAMES,
}
for key, controlKey := range controlSettings {
if _, ok := clonedSettings[key]; ok {
val := getSettingUnsigned[uint](clonedSettings, key, 0)
if aomErr = C.aom_codec_control_uint(&e.codec, controlKey, C.uint(val)); aomErr != 0 {
return nil, fmt.Errorf("error setting parameter %s: %s", key, C.GoString(C.aom_codec_error_detail(&e.codec)))
}
}
}
if fgt := getSettingString(clonedSettings, "film-grain-table", ""); fgt != "" {
strVal := C.CString(fgt)
defer C.free(unsafe.Pointer(strVal))
if aomErr = C.aom_codec_control_charptr(&e.codec, C.AV1E_SET_FILM_GRAIN_TABLE, strVal); aomErr != 0 {
return nil, fmt.Errorf("error setting FILM_GRAIN_TABLE parameter: %s", C.GoString(C.aom_codec_error_detail(&e.codec)))
}
}
for k, v := range clonedSettings {
if err := func() error {
var strVal *C.char
if val, ok := v.(string); ok {
strVal = C.CString(val)
} else if val, ok := v.(int); ok {
strVal = C.CString(strconv.FormatInt(int64(val), 10))
} else if val, ok := v.(int64); ok {
strVal = C.CString(strconv.FormatInt(val, 10))
} else if val, ok := v.(uint); ok {
strVal = C.CString(strconv.FormatUint(uint64(val), 10))
} else if val, ok := v.(uint64); ok {
strVal = C.CString(strconv.FormatUint(val, 10))
} else if val, ok := v.(bool); ok {
if val {
strVal = C.CString("1")
} else {
strVal = C.CString("0")
}
}
if strVal != nil {
defer C.free(unsafe.Pointer(strVal))
} else {
return fmt.Errorf("could not get parameter %s", k)
}
strKey := C.CString(k)
defer C.free(unsafe.Pointer(strKey))
if ret := C.aom_codec_set_option(&e.codec, strKey, strVal); ret != 0 {
if ret == C.AOM_CODEC_INVALID_PARAM {
return fmt.Errorf("bad parameter value %s for %s: %s", C.GoString(strVal), k, C.GoString(C.aom_codec_error_detail(&e.codec)))
} else if ret == C.AOM_CODEC_ERROR {
return fmt.Errorf("error setting parameter %s: %s", k, C.GoString(C.aom_codec_error_detail(&e.codec)))
} else {
return fmt.Errorf("error setting parameter %s: %s", k, C.GoString(C.aom_codec_error_detail(&e.codec)))
}
}
return nil
}(); err != nil {
return nil, err
}
}
if e.w, err = obuwriter.NewWriter(w, properties.Width, properties.Height, 0x31305641, timeBase); err != nil {
return nil, err
}
return e, nil
}
func (e *Encoder) EncodeStream(stream *frame.Stream) error {
for f := range stream.Channel() {
if err := e.Encode(f); err != nil {
return err
}
}
return e.Flush()
}
func (e *Encoder) Encode(f frame.Frame) error {
/*luma := f.GetLuma()
cb := f.GetCb()
cr := f.GetCr()
copy(unsafe.Slice((*byte)(e.raw.planes[C.AOM_PLANE_Y]), len(luma)), luma)
copy(unsafe.Slice((*byte)(e.raw.planes[C.AOM_PLANE_U]), len(cb)), cb)
copy(unsafe.Slice((*byte)(e.raw.planes[C.AOM_PLANE_V]), len(cr)), cr)
*/
copy(e.rawBuffer, f.GetJoint())
if _, err := e.encodeFrame(f.PTS(), f.NextPTS(), e.raw); err != nil {
return err
}
runtime.KeepAlive(f)
return nil
}
func (e *Encoder) encodeFrame(pts, nextPts int64, raw *C.aom_image_t) (pkts int, err error) {
var aomErr C.aom_codec_err_t
if aomErr = C.aom_codec_encode(&e.codec, raw, C.long(pts), C.ulong(nextPts-pts), 0); aomErr != C.AOM_CODEC_OK {
if aomErr == C.AOM_CODEC_INCAPABLE {
return 0, errors.New("error encoding frame: AOM_CODEC_INCAPABLE")
} else if aomErr == C.AOM_CODEC_INVALID_PARAM {
return 0, errors.New("error encoding frame: AOM_CODEC_INVALID_PARAM")
} else if aomErr == C.AOM_CODEC_ERROR {
return 0, errors.New("error encoding frame: AOM_CODEC_ERROR")
} else {
return 0, errors.New("error encoding frame")
}
}
if raw != nil {
e.framesIn++
}
var quant64 C.int
if e.framesIn >= int(e.cfg.g_lag_in_frames) {
//TODO: maybe call this on new packets?
if aomErr = C.aom_codec_control_intptr(&e.codec, C.AOME_GET_LAST_QUANTIZER_64, &quant64); aomErr != C.AOM_CODEC_OK {
return 0, errors.New("error getting LAST_QUANTIZER_64")
}
}
var iter C.aom_codec_iter_t
for {
pkt := C.aom_codec_get_cx_data(&e.codec, &iter)
if pkt == nil {
break
}
pkts++
if pkt.kind == C.AOM_CODEC_CX_FRAME_PKT {
partitionId := int(C.aom_get_pkt_partition_id(pkt))
if partitionId > 0 {
return 0, errors.New("partition id not supported")
}
packetPts := uint64(C.aom_get_pkt_pts(pkt))
buf := unsafe.Slice((*byte)(C.aom_get_pkt_buf(pkt)), int(C.aom_get_pkt_sz(pkt)))
flags := C.aom_get_pkt_flags(pkt)
e.frameStatistics[frame.FrameStatisticsKeyNumber] = e.framesOut
e.frameStatistics[frame.FrameStatisticsKeyPts] = int64(packetPts)
e.frameStatistics[frame.FrameStatisticsKeyQuantizer] = int(quant64)
e.frameStatistics[frame.FrameStatisticsKeySize] = len(buf)
e.frameStatistics[frame.FrameStatisticsKeyIsKeyFrame] = false
e.frameStatistics[frame.FrameStatisticsKeyIsIntraFrame] = false
var flagStr []string
if flags&C.AOM_FRAME_IS_KEY > 0 {
e.frameStatistics[frame.FrameStatisticsKeyIsKeyFrame] = true
flagStr = append(flagStr, "K-Frame")
}
if flags&C.AOM_FRAME_IS_DROPPABLE > 0 {
flagStr = append(flagStr, "droppable")
}
if flags&C.AOM_FRAME_IS_INTRAONLY > 0 {
e.frameStatistics[frame.FrameStatisticsKeyIsIntraFrame] = true
flagStr = append(flagStr, "I-Frame")
}
if flags&C.AOM_FRAME_IS_SWITCH > 0 {
flagStr = append(flagStr, "S-Frame")
}
if flags&C.AOM_FRAME_IS_ERROR_RESILIENT > 0 {
flagStr = append(flagStr, "resilient")
}
if flags&C.AOM_FRAME_IS_DELAYED_RANDOM_ACCESS_POINT > 0 {
//Forward keyframe
e.frameStatistics[frame.FrameStatisticsKeyIsKeyFrame] = true
flagStr = append(flagStr, "delayedrandom")
}
e.logger.Printf("libaom: partition = %d, n = %d, pts = %d, quant64 = %d, bytes = %d, flags = %s", partitionId, e.framesOut, packetPts, quant64, len(buf), strings.Join(flagStr, ", "))
if err = e.w.WriteFrameBytes(packetPts, buf); err != nil {
return pkts, err
}
e.framesOut++
runtime.KeepAlive(buf)
} else {
log.Printf("kind %d", pkt.kind)
}
}
return pkts, nil
}
func (e *Encoder) Statistics() frame.Statistics {
if len(e.frameStatistics) > 0 {
return frame.Statistics{
frame.StatisticsKeyFramesIn: e.framesIn,
frame.StatisticsKeyFramesOut: e.framesOut,
frame.StatisticsKeyLastFrameOut: maps.Clone(e.frameStatistics),
}
} else {
return frame.Statistics{
frame.StatisticsKeyFramesIn: e.framesIn,
frame.StatisticsKeyFramesOut: e.framesOut,
}
}
}
func (e *Encoder) Flush() error {
var pkts int
var err error
for {
if pkts, err = e.encodeFrame(-1, 0, nil); err != nil {
return err
}
if pkts == 0 {
break
}
}
_ = e.w.WriteLength(uint32(e.framesIn))
return nil
}
func (e *Encoder) Close() {
if e.cleaned.Swap(true) == false {
e.resourcePinner.Unpin()
if e.raw != nil {
e.raw = nil
}
if e.rawBuffer != nil {
e.rawBuffer = nil
}
C.aom_codec_destroy(&e.codec)
}
}
func (e *Encoder) Version() string {
return Version()
}
func getSettingBool(m map[string]any, name string, fallback bool) bool {
if v, ok := m[name]; ok {
if val, ok := v.(string); ok {
delete(m, name)
return val == "false" || val == "f" || val == "n"
}
if val, ok := v.(int); ok {
delete(m, name)
return val != 0
}
if val, ok := v.(int64); ok {
delete(m, name)
return val != 0
}
if val, ok := v.(uint); ok {
delete(m, name)
return val != 0
}
if val, ok := v.(uint64); ok {
delete(m, name)
return val != 0
}
return true
}
return fallback
}
func getSettingString(m map[string]any, name string, fallback string) string {
if v, ok := m[name]; ok {
if val, ok := v.(string); ok {
delete(m, name)
return val
}
if val, ok := v.(int); ok {
delete(m, name)
return strconv.FormatInt(int64(val), 10)
}
if val, ok := v.(int64); ok {
delete(m, name)
return strconv.FormatInt(val, 10)
}
if val, ok := v.(uint); ok {
delete(m, name)
return strconv.FormatUint(uint64(val), 10)
}
if val, ok := v.(uint64); ok {
delete(m, name)
return strconv.FormatUint(val, 10)
}
if val, ok := v.(bool); ok {
delete(m, name)
if val {
return "1"
} else {
return "0"
}
}
}
return fallback
}
func getSettingUnsigned[T constraints.Unsigned](m map[string]any, name string, fallback T) T {
if v, ok := m[name]; ok {
if val, ok := v.(string); ok {
if intVal, err := strconv.ParseUint(val, 10, 0); err != nil {
delete(m, name)
return T(intVal)
} else {
return fallback
}
}
if val, ok := v.(int); ok {
delete(m, name)
return T(val)
}
if val, ok := v.(int64); ok {
delete(m, name)
return T(val)
}
if val, ok := v.(uint); ok {
delete(m, name)
return T(val)
}
if val, ok := v.(uint64); ok {
delete(m, name)
return T(val)
}
if val, ok := v.(C.int); ok {
delete(m, name)
return T(val)
}
if val, ok := v.(C.uint); ok {
delete(m, name)
return T(val)
}
if val, ok := v.(bool); ok {
delete(m, name)
if val {
return 1
} else {
return 0
}
}
}
return fallback
}