Change how streams expose properties and settings
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
parent
49f274e0f9
commit
38280958a3
|
@ -1 +1,10 @@
|
|||
package decoder
|
||||
|
||||
import "git.gammaspectra.live/S.O.N.G/Ignite/frame"
|
||||
|
||||
type Encoder interface {
|
||||
Decode() (frame.Frame, error)
|
||||
DecodeStream() *frame.Stream
|
||||
Close()
|
||||
Version() string
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"fmt"
|
||||
"git.gammaspectra.live/S.O.N.G/Ignite/colorformat"
|
||||
"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/ivfreader"
|
||||
"io"
|
||||
"sync/atomic"
|
||||
|
@ -21,6 +22,9 @@ type Decoder struct {
|
|||
h *ivfreader.IVFFileHeader
|
||||
cleaned atomic.Bool
|
||||
|
||||
properties frame.StreamProperties
|
||||
firstFrame frame.Frame
|
||||
|
||||
settings C.Dav1dSettings
|
||||
ctx *C.Dav1dContext
|
||||
picture C.Dav1dPicture
|
||||
|
@ -49,10 +53,23 @@ func NewDecoder(r io.Reader, settings map[string]any) (d *Decoder, err error) {
|
|||
if ret := C.dav1d_open(&d.ctx, &d.settings); ret != 0 {
|
||||
return nil, fmt.Errorf("error %d", ret)
|
||||
}
|
||||
if err = d.readIvf(); err != nil {
|
||||
if d.firstFrame, err = d.Decode(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fP := d.firstFrame.Properties()
|
||||
d.properties = frame.StreamProperties{
|
||||
Width: fP.Width,
|
||||
Height: fP.Height,
|
||||
PixelAspectRatio: fP.PixelAspectRatio,
|
||||
ColorFormat: fP.ColorFormat,
|
||||
FrameRate: utilities.Ratio{
|
||||
Numerator: int(d.h.TimebaseDenominator),
|
||||
Denominator: int(d.h.TimebaseNumerator),
|
||||
},
|
||||
FullColorRange: fP.FullColorRange,
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
|
@ -70,6 +87,10 @@ func (d *Decoder) readIvf() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) Properties() frame.StreamProperties {
|
||||
return d.properties
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeFrame() C.int {
|
||||
var res C.int
|
||||
if res = C.dav1d_send_data(d.ctx, &d.data); res < 0 {
|
||||
|
@ -120,24 +141,25 @@ const (
|
|||
)
|
||||
|
||||
func (d *Decoder) pictureToFrame() (frame.Frame, error) {
|
||||
|
||||
properties := d.properties.FrameProperties()
|
||||
|
||||
bitDepth := int(d.picture.p.bpc)
|
||||
height := int(d.picture.p.h)
|
||||
width := int(d.picture.p.w)
|
||||
properties.Height = int(d.picture.p.h)
|
||||
properties.Width = int(d.picture.p.w)
|
||||
|
||||
chromaWidth := 0
|
||||
chromaHeight := 0
|
||||
|
||||
var colorFormat colorformat.ColorFormat
|
||||
|
||||
switch d.picture.p.layout {
|
||||
case C.DAV1D_PIXEL_LAYOUT_I400:
|
||||
colorFormat.Subsampling = colorformat.SubsamplingScheme{J: 4, A: 0, B: 0}
|
||||
properties.ColorFormat.Subsampling = colorformat.SubsamplingScheme{J: 4, A: 0, B: 0}
|
||||
case C.DAV1D_PIXEL_LAYOUT_I420:
|
||||
colorFormat.Subsampling = colorformat.SubsamplingScheme{J: 4, A: 2, B: 0}
|
||||
properties.ColorFormat.Subsampling = colorformat.SubsamplingScheme{J: 4, A: 2, B: 0}
|
||||
case C.DAV1D_PIXEL_LAYOUT_I422:
|
||||
colorFormat.Subsampling = colorformat.SubsamplingScheme{J: 4, A: 2, B: 2}
|
||||
properties.ColorFormat.Subsampling = colorformat.SubsamplingScheme{J: 4, A: 2, B: 2}
|
||||
case C.DAV1D_PIXEL_LAYOUT_I444:
|
||||
colorFormat.Subsampling = colorformat.SubsamplingScheme{J: 4, A: 4, B: 4}
|
||||
properties.ColorFormat.Subsampling = colorformat.SubsamplingScheme{J: 4, A: 4, B: 4}
|
||||
}
|
||||
|
||||
if d.picture.p.layout != C.DAV1D_PIXEL_LAYOUT_I400 {
|
||||
|
@ -150,17 +172,17 @@ func (d *Decoder) pictureToFrame() (frame.Frame, error) {
|
|||
ssHor = 1
|
||||
}
|
||||
|
||||
chromaWidth = (width + ssHor) >> ssHor
|
||||
chromaHeight = (height + ssVer) >> ssVer
|
||||
chromaWidth = (properties.Width + ssHor) >> ssHor
|
||||
chromaHeight = (properties.Height + ssVer) >> ssVer
|
||||
}
|
||||
|
||||
colorFormat.BitDepth = byte(bitDepth)
|
||||
properties.ColorFormat.BitDepth = byte(bitDepth)
|
||||
|
||||
defer C.dav1d_picture_unref(&d.picture)
|
||||
|
||||
if bitDepth > 8 { //16-bit
|
||||
|
||||
yData := unsafe.Slice((*byte)(d.picture.data[planeY]), height*width*2)
|
||||
yData := unsafe.Slice((*byte)(d.picture.data[planeY]), properties.Height*properties.Width*2)
|
||||
var uData, vData []byte
|
||||
|
||||
if d.picture.p.layout != C.DAV1D_PIXEL_LAYOUT_I400 {
|
||||
|
@ -174,9 +196,9 @@ func (d *Decoder) pictureToFrame() (frame.Frame, error) {
|
|||
copy(buf[len(yData):], uData)
|
||||
copy(buf[len(yData)+len(uData):], vData)
|
||||
|
||||
return frame.NewUint16FrameFromBytes(colorFormat, width, height, buf)
|
||||
return frame.NewUint16FrameFromBytes(properties, int64(d.picture.m.timestamp), buf)
|
||||
} else {
|
||||
yData := unsafe.Slice((*byte)(d.picture.data[planeY]), height*width)
|
||||
yData := unsafe.Slice((*byte)(d.picture.data[planeY]), properties.Height*properties.Width)
|
||||
var uData, vData []byte
|
||||
|
||||
if d.picture.p.layout != C.DAV1D_PIXEL_LAYOUT_I400 {
|
||||
|
@ -189,10 +211,25 @@ func (d *Decoder) pictureToFrame() (frame.Frame, error) {
|
|||
copy(buf[len(yData):], uData)
|
||||
copy(buf[len(yData)+len(uData):], vData)
|
||||
|
||||
return frame.NewUint8FrameFromBytes(colorFormat, width, height, buf)
|
||||
return frame.NewUint8FrameFromBytes(properties, int64(d.picture.m.timestamp), buf)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Decoder) DecodeStream() *frame.Stream {
|
||||
stream := frame.NewStream(d.properties)
|
||||
go func() {
|
||||
defer close(stream.Channel)
|
||||
for {
|
||||
if f, err := d.Decode(); err != nil {
|
||||
return
|
||||
} else {
|
||||
stream.Channel <- f
|
||||
}
|
||||
}
|
||||
}()
|
||||
return stream
|
||||
}
|
||||
|
||||
func (d *Decoder) Decode() (frame.Frame, error) {
|
||||
var ret C.int
|
||||
for {
|
||||
|
@ -210,3 +247,7 @@ func (d *Decoder) Decode() (frame.Frame, error) {
|
|||
return nil, fmt.Errorf("error %d", ret)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Decoder) Version() string {
|
||||
return Version()
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"fmt"
|
||||
"git.gammaspectra.live/S.O.N.G/Ignite/colorformat"
|
||||
"git.gammaspectra.live/S.O.N.G/Ignite/frame"
|
||||
"git.gammaspectra.live/S.O.N.G/Ignite/utilities"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -16,12 +15,7 @@ type Stream struct {
|
|||
frameSeekTable []int64
|
||||
parameters map[Parameter][]string
|
||||
|
||||
width int
|
||||
height int
|
||||
frameRate utilities.Ratio
|
||||
pixelAspectRatio utilities.Ratio
|
||||
colorFormat colorformat.ColorFormat
|
||||
colorRangeFull bool
|
||||
properties frame.StreamProperties
|
||||
|
||||
frameSize int
|
||||
|
||||
|
@ -43,7 +37,7 @@ const (
|
|||
const fileMagic = "YUV4MPEG2 "
|
||||
const frameMagic = "FRAME"
|
||||
|
||||
func New(reader io.Reader) (*Stream, error) {
|
||||
func New(reader io.Reader, settings map[string]any) (*Stream, error) {
|
||||
s := &Stream{
|
||||
r: reader,
|
||||
parameters: make(map[Parameter][]string),
|
||||
|
@ -60,24 +54,8 @@ func New(reader io.Reader) (*Stream, error) {
|
|||
return s, nil
|
||||
}
|
||||
|
||||
func (s *Stream) Resolution() (width, height int) {
|
||||
return s.width, s.height
|
||||
}
|
||||
|
||||
func (s *Stream) FrameRate() utilities.Ratio {
|
||||
return s.frameRate
|
||||
}
|
||||
|
||||
func (s *Stream) ColorFormat() colorformat.ColorFormat {
|
||||
return s.colorFormat
|
||||
}
|
||||
|
||||
func (s *Stream) ColorRange() bool {
|
||||
return s.colorRangeFull
|
||||
}
|
||||
|
||||
func (s *Stream) PixelAspectRatio() utilities.Ratio {
|
||||
return s.pixelAspectRatio
|
||||
func (s *Stream) Properties() frame.StreamProperties {
|
||||
return s.properties
|
||||
}
|
||||
|
||||
func (s *Stream) SeekToFrame(frameNumber int) (err error) {
|
||||
|
@ -98,19 +76,36 @@ func (s *Stream) SeekToFrame(frameNumber int) (err error) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *Stream) GetFrame() (frameNumber int, parameters map[Parameter][]string, frameObject frame.Frame, err error) {
|
||||
var index int64
|
||||
func (s *Stream) Decode() (frame.Frame, error) {
|
||||
_, f, err := s.GetFrame()
|
||||
return f, err
|
||||
}
|
||||
func (s *Stream) DecodeStream() *frame.Stream {
|
||||
stream := frame.NewStream(s.properties)
|
||||
go func() {
|
||||
defer close(stream.Channel)
|
||||
for {
|
||||
if f, err := s.Decode(); err != nil {
|
||||
return
|
||||
} else {
|
||||
stream.Channel <- f
|
||||
}
|
||||
}
|
||||
}()
|
||||
return stream
|
||||
}
|
||||
|
||||
frameNumber = s.frameCounter
|
||||
func (s *Stream) GetFrame() (parameters map[Parameter][]string, frameObject frame.Frame, err error) {
|
||||
var index int64
|
||||
|
||||
if seeker, ok := s.r.(io.Seeker); ok {
|
||||
if index, err = seeker.Seek(0, io.SeekCurrent); err != nil {
|
||||
return 0, nil, nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if parameters, err = s.readFrameHeader(); err != nil {
|
||||
return 0, nil, nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if index > 0 {
|
||||
|
@ -122,16 +117,16 @@ func (s *Stream) GetFrame() (frameNumber int, parameters map[Parameter][]string,
|
|||
|
||||
var buf []byte
|
||||
if buf, err = s.readFrameData(); err != nil {
|
||||
return 0, nil, nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if s.colorFormat.BitDepth > 8 {
|
||||
if frameObject, err = frame.NewUint16FrameFromBytes(s.colorFormat, s.width, s.height, buf); err != nil {
|
||||
return 0, nil, nil, err
|
||||
if s.properties.ColorFormat.BitDepth > 8 {
|
||||
if frameObject, err = frame.NewUint16FrameFromBytes(s.properties.FrameProperties(), int64(s.frameCounter), buf); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
} else {
|
||||
if frameObject, err = frame.NewUint8FrameFromBytes(s.colorFormat, s.width, s.height, buf); err != nil {
|
||||
return 0, nil, nil, err
|
||||
if frameObject, err = frame.NewUint8FrameFromBytes(s.properties.FrameProperties(), int64(s.frameCounter), buf); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -227,11 +222,11 @@ func (s *Stream) parseParameters() (err error) {
|
|||
for k, values := range s.parameters {
|
||||
switch k {
|
||||
case ParameterFrameWidth:
|
||||
if s.width, err = strconv.Atoi(values[0]); err != nil {
|
||||
if s.properties.Width, err = strconv.Atoi(values[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
case ParameterFrameHeight:
|
||||
if s.height, err = strconv.Atoi(values[0]); err != nil {
|
||||
if s.properties.Height, err = strconv.Atoi(values[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
case ParameterFrameRate:
|
||||
|
@ -239,10 +234,10 @@ func (s *Stream) parseParameters() (err error) {
|
|||
if len(v) != 2 {
|
||||
return fmt.Errorf("wrong frame rate %s", values[0])
|
||||
}
|
||||
if s.frameRate.Numerator, err = strconv.Atoi(v[0]); err != nil {
|
||||
if s.properties.FrameRate.Numerator, err = strconv.Atoi(v[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
if s.frameRate.Denominator, err = strconv.Atoi(v[1]); err != nil {
|
||||
if s.properties.FrameRate.Denominator, err = strconv.Atoi(v[1]); err != nil {
|
||||
return err
|
||||
}
|
||||
case ParameterInterlacing:
|
||||
|
@ -254,14 +249,14 @@ func (s *Stream) parseParameters() (err error) {
|
|||
if len(v) != 2 {
|
||||
return fmt.Errorf("wrong pixel aspect ratio %s", values[0])
|
||||
}
|
||||
if s.pixelAspectRatio.Numerator, err = strconv.Atoi(v[0]); err != nil {
|
||||
if s.properties.PixelAspectRatio.Numerator, err = strconv.Atoi(v[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
if s.pixelAspectRatio.Denominator, err = strconv.Atoi(v[1]); err != nil {
|
||||
if s.properties.PixelAspectRatio.Denominator, err = strconv.Atoi(v[1]); err != nil {
|
||||
return err
|
||||
}
|
||||
case ParameterColorFormat:
|
||||
if s.colorFormat, err = colorformat.NewColorFormatFromString(values[0]); err != nil {
|
||||
if s.properties.ColorFormat, err = colorformat.NewColorFormatFromString(values[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
case ParameterExtension:
|
||||
|
@ -271,9 +266,9 @@ func (s *Stream) parseParameters() (err error) {
|
|||
switch extVal[0] {
|
||||
case "COLORRANGE":
|
||||
if extVal[1] == "FULL" {
|
||||
s.colorRangeFull = true
|
||||
s.properties.FullColorRange = true
|
||||
} else if extVal[1] == "LIMITED" {
|
||||
s.colorRangeFull = false
|
||||
s.properties.FullColorRange = false
|
||||
} else {
|
||||
return fmt.Errorf("not supported %s: %s", extVal[0], extVal[1])
|
||||
}
|
||||
|
@ -285,7 +280,11 @@ func (s *Stream) parseParameters() (err error) {
|
|||
|
||||
//TODO: check for missing values width, height, colorformat etc.
|
||||
|
||||
s.frameSize, err = s.colorFormat.FrameSize(s.width, s.height)
|
||||
s.frameSize, err = s.properties.ColorFormat.FrameSize(s.properties.Width, s.properties.Height)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Stream) Version() string {
|
||||
return "y4m"
|
||||
}
|
|
@ -4,6 +4,7 @@ import "git.gammaspectra.live/S.O.N.G/Ignite/frame"
|
|||
|
||||
type Encoder interface {
|
||||
Encode(pts int64, f frame.Frame) error
|
||||
EncodeStream(stream *frame.Stream) error
|
||||
Flush() error
|
||||
Close()
|
||||
Version() string
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/S.O.N.G/Ignite/bitstream/obu"
|
||||
"git.gammaspectra.live/S.O.N.G/Ignite/bitstream/y4m"
|
||||
"git.gammaspectra.live/S.O.N.G/Ignite/frame"
|
||||
"io"
|
||||
"runtime"
|
||||
|
@ -38,7 +37,7 @@ const (
|
|||
UsageAllIntra = C.AOM_USAGE_ALL_INTRA
|
||||
)
|
||||
|
||||
func NewEncoder(w io.Writer, stream *y4m.Stream, settings map[string]any) (*Encoder, error) {
|
||||
func NewEncoder(w io.Writer, properties frame.StreamProperties, settings map[string]any) (*Encoder, error) {
|
||||
e := &Encoder{}
|
||||
|
||||
var aomErr C.aom_codec_err_t
|
||||
|
@ -68,13 +67,13 @@ func NewEncoder(w io.Writer, stream *y4m.Stream, settings map[string]any) (*Enco
|
|||
}
|
||||
|
||||
switch true {
|
||||
case stream.ColorFormat().Subsampling.J == 4 && stream.ColorFormat().Subsampling.A == 4 && stream.ColorFormat().Subsampling.B == 4:
|
||||
case properties.ColorFormat.Subsampling.J == 4 && properties.ColorFormat.Subsampling.A == 4 && properties.ColorFormat.Subsampling.B == 4:
|
||||
imageFormat = C.AOM_IMG_FMT_I444
|
||||
e.cfg.g_profile = 1
|
||||
case stream.ColorFormat().Subsampling.J == 4 && stream.ColorFormat().Subsampling.A == 2 && stream.ColorFormat().Subsampling.B == 2:
|
||||
case properties.ColorFormat.Subsampling.J == 4 && properties.ColorFormat.Subsampling.A == 2 && properties.ColorFormat.Subsampling.B == 2:
|
||||
imageFormat = C.AOM_IMG_FMT_I422
|
||||
e.cfg.g_profile = 2
|
||||
case stream.ColorFormat().Subsampling.J == 4 && stream.ColorFormat().Subsampling.A == 2 && stream.ColorFormat().Subsampling.B == 0:
|
||||
case properties.ColorFormat.Subsampling.J == 4 && properties.ColorFormat.Subsampling.A == 2 && properties.ColorFormat.Subsampling.B == 0:
|
||||
imageFormat = C.AOM_IMG_FMT_I420
|
||||
e.cfg.g_profile = 0
|
||||
default:
|
||||
|
@ -82,8 +81,8 @@ func NewEncoder(w io.Writer, stream *y4m.Stream, settings map[string]any) (*Enco
|
|||
|
||||
}
|
||||
|
||||
e.cfg.g_input_bit_depth = C.uint(stream.ColorFormat().BitDepth)
|
||||
e.cfg.g_bit_depth = C.aom_bit_depth_t(stream.ColorFormat().BitDepth)
|
||||
e.cfg.g_input_bit_depth = C.uint(properties.ColorFormat.BitDepth)
|
||||
e.cfg.g_bit_depth = C.aom_bit_depth_t(properties.ColorFormat.BitDepth)
|
||||
|
||||
if e.cfg.g_bit_depth >= 12 {
|
||||
e.cfg.g_bit_depth = 12
|
||||
|
@ -97,12 +96,10 @@ func NewEncoder(w io.Writer, stream *y4m.Stream, settings map[string]any) (*Enco
|
|||
flags |= C.AOM_CODEC_USE_HIGHBITDEPTH
|
||||
}
|
||||
|
||||
width, height := stream.Resolution()
|
||||
|
||||
if e.raw = (*C.aom_image_t)(C.malloc(C.size_t(unsafe.Sizeof(C.aom_image_t{})))); e.raw == nil {
|
||||
return nil, errors.New("error allocating memory")
|
||||
}
|
||||
if C.aom_img_alloc(e.raw, imageFormat, C.uint(width), C.uint(height), 1) == nil {
|
||||
if C.aom_img_alloc(e.raw, imageFormat, C.uint(properties.Width), C.uint(properties.Height), 1) == nil {
|
||||
return nil, errors.New("failed to allocate image")
|
||||
}
|
||||
|
||||
|
@ -110,10 +107,10 @@ func NewEncoder(w io.Writer, stream *y4m.Stream, settings map[string]any) (*Enco
|
|||
encoder.Close()
|
||||
})
|
||||
|
||||
e.cfg.g_w = C.uint(width)
|
||||
e.cfg.g_h = C.uint(height)
|
||||
e.cfg.g_timebase.num = C.int(stream.FrameRate().Denominator)
|
||||
e.cfg.g_timebase.den = C.int(stream.FrameRate().Numerator)
|
||||
e.cfg.g_w = C.uint(properties.Width)
|
||||
e.cfg.g_h = C.uint(properties.Height)
|
||||
e.cfg.g_timebase.num = C.int(properties.FrameRate.Denominator)
|
||||
e.cfg.g_timebase.den = C.int(properties.FrameRate.Numerator)
|
||||
|
||||
e.cfg.g_threads = C.uint(getSettingInt(settings, "threads", int(e.cfg.g_threads)))
|
||||
e.cfg.g_lag_in_frames = C.uint(getSettingInt(settings, "lag-in-frames", int(e.cfg.g_lag_in_frames)))
|
||||
|
@ -140,7 +137,7 @@ func NewEncoder(w io.Writer, stream *y4m.Stream, settings map[string]any) (*Enco
|
|||
return nil, fmt.Errorf("failed to initialize encoder: %s", C.GoString(e.codec.err_detail))
|
||||
}
|
||||
|
||||
if stream.ColorRange() {
|
||||
if properties.FullColorRange {
|
||||
if aomErr = C.aom_codec_control_uint(&e.codec, C.AV1E_SET_COLOR_RANGE, 1); aomErr != 0 {
|
||||
return nil, fmt.Errorf("failed to set color range")
|
||||
}
|
||||
|
@ -185,15 +182,23 @@ func NewEncoder(w io.Writer, stream *y4m.Stream, settings map[string]any) (*Enco
|
|||
}
|
||||
|
||||
var err error
|
||||
if e.w, err = obu.NewWriter(w, width, height, 0x31305641, stream.FrameRate()); err != nil {
|
||||
if e.w, err = obu.NewWriter(w, properties.Width, properties.Height, 0x31305641, properties.FrameRate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (e *Encoder) Encode(pts int64, f frame.Frame) error {
|
||||
//TODO: make this a Source channel
|
||||
func (e *Encoder) EncodeStream(stream *frame.Stream) error {
|
||||
for f := range stream.Channel {
|
||||
if err := e.Encode(f); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Encoder) Encode(f frame.Frame) error {
|
||||
|
||||
if int8F, ok := f.(frame.TypedFrame[uint8]); ok {
|
||||
e.raw.planes[0] = (*C.uint8_t)(unsafe.Pointer(&int8F.GetNativeLuma()[0]))
|
||||
|
@ -214,7 +219,7 @@ func (e *Encoder) Encode(pts int64, f frame.Frame) error {
|
|||
}()
|
||||
defer runtime.KeepAlive(f)
|
||||
|
||||
if _, err := e.encodeFrame(pts, e.raw); err != nil {
|
||||
if _, err := e.encodeFrame(f.PTS(), e.raw); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ import "C"
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/S.O.N.G/Ignite/bitstream/y4m"
|
||||
"git.gammaspectra.live/S.O.N.G/Ignite/frame"
|
||||
"io"
|
||||
"runtime"
|
||||
|
@ -32,7 +31,7 @@ func Version() string {
|
|||
return x264Version
|
||||
}
|
||||
|
||||
func NewEncoder(w io.Writer, stream *y4m.Stream, settings map[string]any) (*Encoder, error) {
|
||||
func NewEncoder(w io.Writer, properties frame.StreamProperties, settings map[string]any) (*Encoder, error) {
|
||||
e := &Encoder{
|
||||
w: w,
|
||||
}
|
||||
|
@ -52,13 +51,13 @@ func NewEncoder(w io.Writer, stream *y4m.Stream, settings map[string]any) (*Enco
|
|||
defaultProfile := "high"
|
||||
|
||||
switch true {
|
||||
case stream.ColorFormat().Subsampling.J == 4 && stream.ColorFormat().Subsampling.A == 4 && stream.ColorFormat().Subsampling.B == 4:
|
||||
case properties.ColorFormat.Subsampling.J == 4 && properties.ColorFormat.Subsampling.A == 4 && properties.ColorFormat.Subsampling.B == 4:
|
||||
e.params.i_csp = C.X264_CSP_I444
|
||||
defaultProfile = "high444"
|
||||
case stream.ColorFormat().Subsampling.J == 4 && stream.ColorFormat().Subsampling.A == 2 && stream.ColorFormat().Subsampling.B == 2:
|
||||
case properties.ColorFormat.Subsampling.J == 4 && properties.ColorFormat.Subsampling.A == 2 && properties.ColorFormat.Subsampling.B == 2:
|
||||
e.params.i_csp = C.X264_CSP_I422
|
||||
defaultProfile = "high422"
|
||||
case stream.ColorFormat().Subsampling.J == 4 && stream.ColorFormat().Subsampling.A == 2 && stream.ColorFormat().Subsampling.B == 0:
|
||||
case properties.ColorFormat.Subsampling.J == 4 && properties.ColorFormat.Subsampling.A == 2 && properties.ColorFormat.Subsampling.B == 0:
|
||||
e.params.i_csp = C.X264_CSP_I420
|
||||
defaultProfile = "high"
|
||||
default:
|
||||
|
@ -69,24 +68,23 @@ func NewEncoder(w io.Writer, stream *y4m.Stream, settings map[string]any) (*Enco
|
|||
profile := C.CString(getSettingString(settings, "profile", defaultProfile))
|
||||
defer C.free(unsafe.Pointer(profile))
|
||||
|
||||
if stream.ColorFormat().BitDepth > 8 {
|
||||
if properties.ColorFormat.BitDepth > 8 {
|
||||
e.params.i_csp |= C.X264_CSP_HIGH_DEPTH
|
||||
if defaultProfile == "high" {
|
||||
defaultProfile = "high10"
|
||||
}
|
||||
}
|
||||
e.params.i_bitdepth = C.int(stream.ColorFormat().BitDepth)
|
||||
e.params.i_bitdepth = C.int(properties.ColorFormat.BitDepth)
|
||||
|
||||
width, height := stream.Resolution()
|
||||
e.params.i_width = C.int(width)
|
||||
e.params.i_height = C.int(height)
|
||||
e.params.i_width = C.int(properties.Width)
|
||||
e.params.i_height = C.int(properties.Height)
|
||||
e.params.b_vfr_input = 0
|
||||
e.params.b_repeat_headers = 1
|
||||
e.params.b_annexb = 1
|
||||
e.params.i_fps_num = C.uint32_t(stream.FrameRate().Numerator)
|
||||
e.params.i_fps_den = C.uint32_t(stream.FrameRate().Denominator)
|
||||
e.params.i_fps_num = C.uint32_t(properties.FrameRate.Numerator)
|
||||
e.params.i_fps_den = C.uint32_t(properties.FrameRate.Denominator)
|
||||
|
||||
if stream.ColorRange() {
|
||||
if properties.FullColorRange {
|
||||
e.params.vui.b_fullrange = 1
|
||||
} else {
|
||||
e.params.vui.b_fullrange = 0
|
||||
|
@ -156,8 +154,16 @@ func NewEncoder(w io.Writer, stream *y4m.Stream, settings map[string]any) (*Enco
|
|||
return e, nil
|
||||
}
|
||||
|
||||
func (e *Encoder) Encode(pts int64, f frame.Frame) error {
|
||||
//TODO: make this a Source channel
|
||||
func (e *Encoder) EncodeStream(stream *frame.Stream) error {
|
||||
for f := range stream.Channel {
|
||||
if err := e.Encode(f); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Encoder) Encode(f frame.Frame) error {
|
||||
var nal *C.x264_nal_t
|
||||
var i_nal C.int
|
||||
var frame_size C.int
|
||||
|
@ -181,7 +187,7 @@ func (e *Encoder) Encode(pts int64, f frame.Frame) error {
|
|||
}()
|
||||
defer runtime.KeepAlive(f)
|
||||
|
||||
e.pictureIn.i_pts = C.int64_t(pts)
|
||||
e.pictureIn.i_pts = C.int64_t(f.PTS())
|
||||
|
||||
if frame_size = C.x264_encoder_encode(e.h, &nal, &i_nal, e.pictureIn, &e.pictureOut); frame_size < 0 {
|
||||
return errors.New("error encoding frame")
|
||||
|
|
|
@ -2,6 +2,7 @@ package frame
|
|||
|
||||
import (
|
||||
"git.gammaspectra.live/S.O.N.G/Ignite/colorformat"
|
||||
"git.gammaspectra.live/S.O.N.G/Ignite/utilities"
|
||||
)
|
||||
|
||||
type AllowedFrameTypes interface {
|
||||
|
@ -9,9 +10,9 @@ type AllowedFrameTypes interface {
|
|||
}
|
||||
|
||||
type Frame interface {
|
||||
Width() int
|
||||
Height() int
|
||||
ColorFormat() colorformat.ColorFormat
|
||||
Properties() Properties
|
||||
// PTS usually frame number
|
||||
PTS() int64
|
||||
Get16(x, y int) (Y uint16, Cb uint16, Cr uint16)
|
||||
Get8(x, y int) (Y uint8, Cb uint8, Cr uint8)
|
||||
}
|
||||
|
@ -23,3 +24,12 @@ type TypedFrame[T AllowedFrameTypes] interface {
|
|||
GetNativeCb() []T
|
||||
GetNativeCr() []T
|
||||
}
|
||||
|
||||
type Properties struct {
|
||||
//TODO: HDR
|
||||
Width int
|
||||
Height int
|
||||
PixelAspectRatio utilities.Ratio
|
||||
ColorFormat colorformat.ColorFormat
|
||||
FullColorRange bool
|
||||
}
|
||||
|
|
|
@ -2,26 +2,24 @@ package frame
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"git.gammaspectra.live/S.O.N.G/Ignite/colorformat"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type uint16Frame struct {
|
||||
colorFormat colorformat.ColorFormat
|
||||
width int
|
||||
height int
|
||||
Y []uint16
|
||||
Cb []uint16
|
||||
Cr []uint16
|
||||
properties Properties
|
||||
Pts int64
|
||||
Y []uint16
|
||||
Cb []uint16
|
||||
Cr []uint16
|
||||
}
|
||||
|
||||
func NewUint16FrameFromBytes(space colorformat.ColorFormat, width, height int, data []byte) (*uint16Frame, error) {
|
||||
if frameLength, _ := space.FrameSize(width, height); frameLength != len(data) {
|
||||
func NewUint16FrameFromBytes(properties Properties, pts int64, data []byte) (*uint16Frame, error) {
|
||||
if frameLength, _ := properties.ColorFormat.FrameSize(properties.Width, properties.Height); frameLength != len(data) {
|
||||
return nil, errors.New("wrong length of data")
|
||||
}
|
||||
|
||||
if space.BitDepth >= 16 {
|
||||
if properties.ColorFormat.BitDepth >= 16 {
|
||||
return nil, errors.New("wrong bit depth")
|
||||
}
|
||||
|
||||
|
@ -29,52 +27,47 @@ func NewUint16FrameFromBytes(space colorformat.ColorFormat, width, height int, d
|
|||
copy(buf, unsafe.Slice((*uint16)(unsafe.Pointer(&data[0])), len(data)/2))
|
||||
runtime.KeepAlive(data)
|
||||
|
||||
iY := space.PlaneLumaSamples(width, height)
|
||||
iCb := space.PlaneCbSamples(width, height)
|
||||
iCr := space.PlaneCrSamples(width, height)
|
||||
iY := properties.ColorFormat.PlaneLumaSamples(properties.Width, properties.Height)
|
||||
iCb := properties.ColorFormat.PlaneCbSamples(properties.Width, properties.Height)
|
||||
iCr := properties.ColorFormat.PlaneCrSamples(properties.Width, properties.Height)
|
||||
|
||||
return &uint16Frame{
|
||||
colorFormat: space,
|
||||
height: height,
|
||||
width: width,
|
||||
Y: buf[:iY],
|
||||
Cb: buf[iY : iY+iCb],
|
||||
Cr: buf[iY+iCb : iY+iCb+iCr],
|
||||
properties: properties,
|
||||
Y: buf[:iY],
|
||||
Cb: buf[iY : iY+iCb],
|
||||
Cr: buf[iY+iCb : iY+iCb+iCr],
|
||||
Pts: pts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (i *uint16Frame) Get16(x, y int) (Y uint16, Cb uint16, Cr uint16) {
|
||||
cy, cb, cr := i.GetNative(x, y)
|
||||
|
||||
return cy << (16 - i.colorFormat.BitDepth), cb << (16 - i.colorFormat.BitDepth), cr << (16 - i.colorFormat.BitDepth)
|
||||
return cy << (16 - i.properties.ColorFormat.BitDepth), cb << (16 - i.properties.ColorFormat.BitDepth), cr << (16 - i.properties.ColorFormat.BitDepth)
|
||||
}
|
||||
|
||||
func (i *uint16Frame) Get8(x, y int) (Y uint8, Cb uint8, Cr uint8) {
|
||||
cy, cb, cr := i.GetNative(x, y)
|
||||
|
||||
return uint8(cy >> (i.colorFormat.BitDepth - 8)), uint8(cb >> (i.colorFormat.BitDepth - 8)), uint8(cr >> (i.colorFormat.BitDepth - 8))
|
||||
return uint8(cy >> (i.properties.ColorFormat.BitDepth - 8)), uint8(cb >> (i.properties.ColorFormat.BitDepth - 8)), uint8(cr >> (i.properties.ColorFormat.BitDepth - 8))
|
||||
}
|
||||
|
||||
func (i *uint16Frame) Width() int {
|
||||
return i.width
|
||||
func (i *uint16Frame) Properties() Properties {
|
||||
return i.properties
|
||||
}
|
||||
|
||||
func (i *uint16Frame) Height() int {
|
||||
return i.height
|
||||
}
|
||||
|
||||
func (i *uint16Frame) ColorFormat() colorformat.ColorFormat {
|
||||
return i.colorFormat
|
||||
func (i *uint16Frame) PTS() int64 {
|
||||
return i.Pts
|
||||
}
|
||||
|
||||
func (i *uint16Frame) GetNative(x, y int) (Y uint16, Cb uint16, Cr uint16) {
|
||||
Yindex := x + y*i.width
|
||||
Yindex := x + y*i.properties.Width
|
||||
|
||||
Cwidth := (i.width * int(i.colorFormat.Subsampling.A)) / int(i.colorFormat.Subsampling.J)
|
||||
if i.colorFormat.Subsampling.B == 0 {
|
||||
Cwidth := (i.properties.Width * int(i.properties.ColorFormat.Subsampling.A)) / int(i.properties.ColorFormat.Subsampling.J)
|
||||
if i.properties.ColorFormat.Subsampling.B == 0 {
|
||||
y /= 2
|
||||
}
|
||||
Cindex := (x*int(i.colorFormat.Subsampling.A))/int(i.colorFormat.Subsampling.J) + y*Cwidth
|
||||
Cindex := (x*int(i.properties.ColorFormat.Subsampling.A))/int(i.properties.ColorFormat.Subsampling.J) + y*Cwidth
|
||||
Y = i.Y[Yindex]
|
||||
Cb = i.Cb[Cindex]
|
||||
Cr = i.Cr[Cindex]
|
||||
|
|
|
@ -2,71 +2,64 @@ package frame
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"git.gammaspectra.live/S.O.N.G/Ignite/colorformat"
|
||||
)
|
||||
|
||||
type uint8Frame struct {
|
||||
colorFormat colorformat.ColorFormat
|
||||
width int
|
||||
height int
|
||||
Y []uint8
|
||||
Cb []uint8
|
||||
Cr []uint8
|
||||
properties Properties
|
||||
Pts int64
|
||||
Y []uint8
|
||||
Cb []uint8
|
||||
Cr []uint8
|
||||
}
|
||||
|
||||
func NewUint8FrameFromBytes(space colorformat.ColorFormat, width, height int, data []byte) (*uint8Frame, error) {
|
||||
if frameLength, _ := space.FrameSize(width, height); frameLength != len(data) {
|
||||
func NewUint8FrameFromBytes(properties Properties, pts int64, data []byte) (*uint8Frame, error) {
|
||||
if frameLength, _ := properties.ColorFormat.FrameSize(properties.Width, properties.Height); frameLength != len(data) {
|
||||
return nil, errors.New("wrong length of data")
|
||||
}
|
||||
|
||||
if space.BitDepth > 8 {
|
||||
if properties.ColorFormat.BitDepth > 8 {
|
||||
return nil, errors.New("wrong bit depth")
|
||||
}
|
||||
|
||||
iY := space.PlaneLumaSamples(width, height)
|
||||
iCb := space.PlaneCbSamples(width, height)
|
||||
iCr := space.PlaneCrSamples(width, height)
|
||||
iY := properties.ColorFormat.PlaneLumaSamples(properties.Width, properties.Height)
|
||||
iCb := properties.ColorFormat.PlaneCbSamples(properties.Width, properties.Height)
|
||||
iCr := properties.ColorFormat.PlaneCrSamples(properties.Width, properties.Height)
|
||||
|
||||
return &uint8Frame{
|
||||
colorFormat: space,
|
||||
height: height,
|
||||
width: width,
|
||||
Y: data[:iY],
|
||||
Cb: data[iY : iY+iCb],
|
||||
Cr: data[iY+iCb : iY+iCb+iCr],
|
||||
properties: properties,
|
||||
Y: data[:iY],
|
||||
Cb: data[iY : iY+iCb],
|
||||
Cr: data[iY+iCb : iY+iCb+iCr],
|
||||
Pts: pts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (i *uint8Frame) Get16(x, y int) (Y uint16, Cb uint16, Cr uint16) {
|
||||
cy, cb, cr := i.GetNative(x, y)
|
||||
|
||||
return uint16(cy) << (16 - i.colorFormat.BitDepth), uint16(cb) << (16 - i.colorFormat.BitDepth), uint16(cr) << (16 - i.colorFormat.BitDepth)
|
||||
return uint16(cy) << (16 - i.properties.ColorFormat.BitDepth), uint16(cb) << (16 - i.properties.ColorFormat.BitDepth), uint16(cr) << (16 - i.properties.ColorFormat.BitDepth)
|
||||
}
|
||||
|
||||
func (i *uint8Frame) Get8(x, y int) (Y uint8, Cb uint8, Cr uint8) {
|
||||
return i.GetNative(x, y)
|
||||
}
|
||||
|
||||
func (i *uint8Frame) Width() int {
|
||||
return i.width
|
||||
func (i *uint8Frame) Properties() Properties {
|
||||
return i.properties
|
||||
}
|
||||
|
||||
func (i *uint8Frame) Height() int {
|
||||
return i.height
|
||||
}
|
||||
|
||||
func (i *uint8Frame) ColorFormat() colorformat.ColorFormat {
|
||||
return i.colorFormat
|
||||
func (i *uint8Frame) PTS() int64 {
|
||||
return i.Pts
|
||||
}
|
||||
|
||||
func (i *uint8Frame) GetNative(x, y int) (Y uint8, Cb uint8, Cr uint8) {
|
||||
Yindex := x + y*i.width
|
||||
Yindex := x + y*i.properties.Width
|
||||
|
||||
Cwidth := (i.width * int(i.colorFormat.Subsampling.A)) / int(i.colorFormat.Subsampling.J)
|
||||
if i.colorFormat.Subsampling.B == 0 {
|
||||
Cwidth := (i.properties.Width * int(i.properties.ColorFormat.Subsampling.A)) / int(i.properties.ColorFormat.Subsampling.J)
|
||||
if i.properties.ColorFormat.Subsampling.B == 0 {
|
||||
y /= 2
|
||||
}
|
||||
Cindex := (x*int(i.colorFormat.Subsampling.A))/int(i.colorFormat.Subsampling.J) + y*Cwidth
|
||||
Cindex := (x*int(i.properties.ColorFormat.Subsampling.A))/int(i.properties.ColorFormat.Subsampling.J) + y*Cwidth
|
||||
Y = i.Y[Yindex]
|
||||
Cb = i.Cb[Cindex]
|
||||
Cr = i.Cr[Cindex]
|
||||
|
|
41
frame/stream.go
Normal file
41
frame/stream.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
package frame
|
||||
|
||||
import (
|
||||
"git.gammaspectra.live/S.O.N.G/Ignite/colorformat"
|
||||
"git.gammaspectra.live/S.O.N.G/Ignite/utilities"
|
||||
)
|
||||
|
||||
type Stream struct {
|
||||
Properties StreamProperties
|
||||
Channel chan Frame
|
||||
}
|
||||
|
||||
func NewStream(properties StreamProperties) *Stream {
|
||||
return &Stream{
|
||||
Properties: properties,
|
||||
Channel: make(chan Frame),
|
||||
}
|
||||
}
|
||||
|
||||
type StreamProperties struct {
|
||||
// Width could be not populated until the first frame is read. Frame can contain different settings.
|
||||
Width int
|
||||
// Height could be not populated until the first frame is read. Frame can contain different settings.
|
||||
Height int
|
||||
// PixelAspectRatio could be not populated until the first frame is read. Frame can contain different settings.
|
||||
PixelAspectRatio utilities.Ratio
|
||||
// ColorFormat could be not populated until the first frame is read. Frame can contain different settings.
|
||||
ColorFormat colorformat.ColorFormat
|
||||
FrameRate utilities.Ratio
|
||||
FullColorRange bool
|
||||
}
|
||||
|
||||
func (p StreamProperties) FrameProperties() Properties {
|
||||
return Properties{
|
||||
Width: p.Width,
|
||||
Height: p.Height,
|
||||
PixelAspectRatio: p.PixelAspectRatio,
|
||||
ColorFormat: p.ColorFormat,
|
||||
FullColorRange: p.FullColorRange,
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue