Change how streams expose properties and settings
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
DataHoarder 2022-11-10 13:01:35 +01:00
parent 49f274e0f9
commit 38280958a3
Signed by: DataHoarder
SSH key fingerprint: SHA256:OLTRf6Fl87G52SiR7sWLGNzlJt4WOX+tfI2yxo0z7xk
10 changed files with 264 additions and 166 deletions

View file

@ -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
}

View file

@ -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()
}

View file

@ -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"
}

View file

@ -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

View file

@ -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
}

View file

@ -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")

View file

@ -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
}

View file

@ -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]

View file

@ -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
View 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,
}
}