diff --git a/Makefile b/Makefile index 3b6a2ac3..55c726ae 100644 --- a/Makefile +++ b/Makefile @@ -84,6 +84,11 @@ ifneq ($strip $(FT)),) $(eval TAGS += ffmpeg) endif +# DVB bindings +dvb: + @echo "Targetting dvb" + $(eval TAGS += dvb) + # Chromaprint bindings chromaprint: darwin $(eval FT = $(shell PKG_CONFIG_PATH="$(PKG_CONFIG_PATH)" pkg-config --silence-errors --modversion libchromaprint)) @@ -191,6 +196,9 @@ googlecast: builddir protogen mediakit: builddir ffmpeg chromaprint PKG_CONFIG_PATH="$(PKG_CONFIG_PATH)" $(GO) build -o ${BUILDDIR}/mediakit -tags "$(TAGS)" ${GOFLAGS} ./cmd/mediakit +dvbkit: builddir dvb + PKG_CONFIG_PATH="$(PKG_CONFIG_PATH)" $(GO) build -o ${BUILDDIR}/dvbkit -tags "$(TAGS)" ${GOFLAGS} ./cmd/dvbkit + gx: builddir rpi egl drm gbm PKG_CONFIG_PATH="$(PKG_CONFIG_PATH)" $(GO) build -o ${BUILDDIR}/gx -tags "$(TAGS)" ${GOFLAGS} ./cmd/gx diff --git a/cmd/dvbkit/app.go b/cmd/dvbkit/app.go new file mode 100644 index 00000000..82febd75 --- /dev/null +++ b/cmd/dvbkit/app.go @@ -0,0 +1,90 @@ +package main + +import ( + "context" + "fmt" + "os" + "time" + + "github.com/djthorpe/gopi/v3" +) + +//////////////////////////////////////////////////////////////////////////////// +// TYPES + +type app struct { + gopi.Unit + gopi.Logger + gopi.DVBManager + gopi.DVBTuner + + timeout *time.Duration + tuner *uint + params []gopi.DVBTunerParams +} + +//////////////////////////////////////////////////////////////////////////////// +// LIFECYCLE + +func (this *app) Define(cfg gopi.Config) error { + this.timeout = cfg.FlagDuration("timeout", 2*time.Second, "Tune timeout") + this.tuner = cfg.FlagUint("tuner", 0, "Tuner identifier") + return nil +} + +func (this *app) New(cfg gopi.Config) error { + this.Require(this.Logger, this.DVBManager) + + args := cfg.Args() + if len(args) != 1 { + return gopi.ErrBadParameter.WithPrefix("file") + } + + // Set the tuner + tuners := this.DVBManager.Tuners() + for _, tuner := range tuners { + if tuner.Id() == *this.tuner { + this.DVBTuner = tuner + } + } + if this.DVBTuner == nil { + return gopi.ErrNotFound.WithPrefix("Tuner") + } + + // Parse tuner params + if fh, err := os.Open(args[0]); err != nil { + return err + } else if params, err := this.DVBManager.ParseTunerParams(fh); err != nil { + return err + } else { + this.params = params + } + + // Return success + return nil +} + +func (this *app) Run(ctx context.Context) error { + + // Tune all channels + for _, param := range this.params { + fmt.Println("Tune:", this.DVBTuner, param.Name()) + tunectx, cancel := context.WithTimeout(ctx, *this.timeout) + defer cancel() + + if err := this.Tune(tunectx, this.DVBTuner, param); err != nil { + fmt.Println(" Error:", err) + } else if tunectx.Err() == nil { + fmt.Println(" SCAN") + this.ScanNIT(this.DVBTuner) + <-tunectx.Done() + + } + } + + // Wait for interrupt + fmt.Println("Press CTRL+C to end") + <-ctx.Done() + + return ctx.Err() +} diff --git a/cmd/dvbkit/main.go b/cmd/dvbkit/main.go new file mode 100644 index 00000000..7da225ef --- /dev/null +++ b/cmd/dvbkit/main.go @@ -0,0 +1,11 @@ +package main + +import ( + "os" + + tool "github.com/djthorpe/gopi/v3/pkg/tool" +) + +func main() { + os.Exit(tool.CommandLine("dvbkit", os.Args[1:], new(app))) +} diff --git a/cmd/dvbkit/units.go b/cmd/dvbkit/units.go new file mode 100644 index 00000000..4acb0993 --- /dev/null +++ b/cmd/dvbkit/units.go @@ -0,0 +1,7 @@ +package main + +import ( + _ "github.com/djthorpe/gopi/v3/pkg/event" + _ "github.com/djthorpe/gopi/v3/pkg/file" + _ "github.com/djthorpe/gopi/v3/pkg/media/dvb" +) diff --git a/media.go b/media.go index 356a624f..a9c077c6 100644 --- a/media.go +++ b/media.go @@ -152,6 +152,9 @@ type DVBManager interface { // Tune to parameters with timeout Tune(context.Context, DVBTuner, DVBTunerParams) error + + // Temporary methods + ScanNIT(DVBTuner) error } // DVBTunerParams represents tune parameters diff --git a/pkg/file/filepoll_linux.go b/pkg/file/filepoll_linux.go index 59be3656..7bce1941 100644 --- a/pkg/file/filepoll_linux.go +++ b/pkg/file/filepoll_linux.go @@ -178,8 +178,6 @@ func (this *filepoll) call(fd uintptr, flags gopi.FilePollFlags) { } } else if handler, exists := this.funcs[fd]; exists { handler(fd, flags) - } else { - this.Print("Filepoll: Unable to handle fd=", fd, " flags=", flags) } } diff --git a/pkg/media/dvb/filter.go b/pkg/media/dvb/filter.go new file mode 100644 index 00000000..c825076d --- /dev/null +++ b/pkg/media/dvb/filter.go @@ -0,0 +1,234 @@ +// +build dvb + +package dvb + +import ( + "fmt" + "os" + "strconv" + "sync" + + gopi "github.com/djthorpe/gopi/v3" + ts "github.com/djthorpe/gopi/v3/pkg/media/internal/ts" + dvb "github.com/djthorpe/gopi/v3/pkg/sys/dvb" + multierror "github.com/hashicorp/go-multierror" +) + +//////////////////////////////////////////////////////////////////////////////// +// TYPES + +type Filter struct { + sync.RWMutex + dev *os.File +} + +type SectionFilter struct { + Filter + *dvb.DMXSectionFilter +} + +type StreamFilter struct { + Filter + *dvb.DMXStreamFilter +} + +//////////////////////////////////////////////////////////////////////////////// +// LIFECYCLE + +func NewSectionFilter(tuner *Tuner, pid uint16, tids ...ts.TableType) (*SectionFilter, error) { + this := new(SectionFilter) + + // Check incoming parameters + if tuner == nil { + return nil, gopi.ErrBadParameter.WithPrefix("NewSectionFilter") + } + if len(tids) == 0 { + return nil, gopi.ErrBadParameter.WithPrefix("NewSectionFilter") + } + + // Open device + if dev, err := tuner.DMXOpen(); err != nil { + return nil, err + } else { + this.dev = dev + } + + // Create filter with 0ms timeout (no timeout) + this.DMXSectionFilter = dvb.NewSectionFilter(pid, 0, dvb.DMX_NONE) + for i, tid := range tids { + this.DMXSectionFilter.Set(i, uint8(tid), 0xFF, 0x00) + } + + // Set filter + if err := dvb.DMXSetSectionFilter(this.dev.Fd(), this.DMXSectionFilter); err != nil { + this.dev.Close() + return nil, err + } + + // Return success + return this, nil +} + +func NewStreamFilter(tuner *Tuner, pid uint16, in dvb.DMXInput, out dvb.DMXOutput, stream dvb.DMXStreamType) (*StreamFilter, error) { + this := new(StreamFilter) + + // Check incoming parameters + if tuner == nil { + return nil, gopi.ErrBadParameter.WithPrefix("NewStreamFilter") + } + + // Open device + if dev, err := tuner.DMXOpen(); err != nil { + return nil, err + } else { + this.dev = dev + } + + // Create filter + this.DMXStreamFilter = dvb.NewStreamFilter(pid, in, out, stream, dvb.DMX_NONE) + + // Set filter + if err := dvb.DMXSetStreamFilter(this.dev.Fd(), this.DMXStreamFilter); err != nil { + this.dev.Close() + return nil, err + } + + // Return success + return this, nil +} + +func (this *Filter) Dispose() error { + this.RWMutex.Lock() + defer this.RWMutex.Unlock() + + var result error + if this.dev != nil { + if err := this.dev.Close(); err != nil { + result = multierror.Append(result, err) + } + } + + // Release resources + this.dev = nil + + // Return success + return result +} + +//////////////////////////////////////////////////////////////////////////////// +// PROPERTIES + +func (this *Filter) Fd() uintptr { + this.RWMutex.RLock() + defer this.RWMutex.RUnlock() + + if this.dev == nil { + return 0 + } else { + return this.dev.Fd() + } +} + +//////////////////////////////////////////////////////////////////////////////// +// PUBLIC METHODS + +func (this *Filter) Start() error { + this.RWMutex.Lock() + defer this.RWMutex.Unlock() + + if this.dev == nil { + return gopi.ErrOutOfOrder.WithPrefix("Start") + } + + return dvb.DMXStart(this.dev.Fd()) +} + +func (this *Filter) Stop() error { + this.RWMutex.Lock() + defer this.RWMutex.Unlock() + + if this.dev == nil { + return gopi.ErrOutOfOrder.WithPrefix("Stop") + } + + return dvb.DMXStop(this.dev.Fd()) +} + +func (this *Filter) AddPid(pid uint16) error { + this.RWMutex.Lock() + defer this.RWMutex.Unlock() + + if this.dev == nil { + return gopi.ErrOutOfOrder.WithPrefix("AddPid") + } + + return dvb.DMXAddPid(this.dev.Fd(), pid) +} + +func (this *Filter) AddPids(pids []uint16) error { + this.RWMutex.Lock() + defer this.RWMutex.Unlock() + + if this.dev == nil { + return gopi.ErrOutOfOrder.WithPrefix("AddPids") + } + + var result error + for _, pid := range pids { + if err := dvb.DMXAddPid(this.dev.Fd(), pid); err != nil { + result = multierror.Append(result, err) + } + } + + // Success + return result +} + +func (this *Filter) SetBufferSize(size uint32) error { + this.RWMutex.Lock() + defer this.RWMutex.Unlock() + + if this.dev == nil { + return gopi.ErrOutOfOrder.WithPrefix("SetBufferSize") + } + + return dvb.DMXSetBufferSize(this.dev.Fd(), size) +} + +func (this *Filter) RemovePid(pid uint16) error { + this.RWMutex.Lock() + defer this.RWMutex.Unlock() + + if this.dev == nil { + return gopi.ErrOutOfOrder.WithPrefix("RemovePid") + } + + return dvb.DMXRemovePid(this.dev.Fd(), pid) +} + +//////////////////////////////////////////////////////////////////////////////// +// STRINGIFY + +func (this *SectionFilter) String() string { + this.RWMutex.RLock() + defer this.RWMutex.RUnlock() + + str := "" +} + +func (this *StreamFilter) String() string { + this.RWMutex.RLock() + defer this.RWMutex.RUnlock() + + str := "" +} diff --git a/pkg/media/dvb/filter_test.go b/pkg/media/dvb/filter_test.go new file mode 100644 index 00000000..dda804b6 --- /dev/null +++ b/pkg/media/dvb/filter_test.go @@ -0,0 +1,34 @@ +// +build dvb + +package dvb_test + +import ( + "testing" + + gopi "github.com/djthorpe/gopi/v3" + _ "github.com/djthorpe/gopi/v3/pkg/file" + dvb "github.com/djthorpe/gopi/v3/pkg/media/dvb" + tool "github.com/djthorpe/gopi/v3/pkg/tool" +) + +type FilterApp struct { + gopi.Unit + gopi.DVBManager +} + +func Test_Filter_001(t *testing.T) { + tool.Test(t, nil, new(FilterApp), func(app *FilterApp) { + tuners := app.DVBManager.Tuners() + if len(tuners) == 0 { + t.Skip("Skipping test, no device") + } + for _, tuner := range tuners { + if f, err := dvb.NewSectionFilter(tuner.(*dvb.Tuner), 0xFFFF, 0xFF); err != nil { + t.Error(err) + } else { + t.Log(f) + f.Dispose() + } + } + }) +} diff --git a/pkg/media/dvb/manager.go b/pkg/media/dvb/manager.go index 0f98f024..93a3953e 100644 --- a/pkg/media/dvb/manager.go +++ b/pkg/media/dvb/manager.go @@ -11,8 +11,9 @@ import ( "time" gopi "github.com/djthorpe/gopi/v3" + ts "github.com/djthorpe/gopi/v3/pkg/media/internal/ts" dvb "github.com/djthorpe/gopi/v3/pkg/sys/dvb" - "github.com/hashicorp/go-multierror" + multierror "github.com/hashicorp/go-multierror" ) //////////////////////////////////////////////////////////////////////////////// @@ -21,32 +22,56 @@ import ( type Manager struct { gopi.Unit gopi.Logger + gopi.FilePoll sync.RWMutex frontend map[uint]*os.File demux map[uint]*os.File + section map[uint][]*SectionFilter + stream map[uint][]*StreamFilter } //////////////////////////////////////////////////////////////////////////////// // LIFECYCLE func (this *Manager) New(gopi.Config) error { - this.Require(this.Logger) + this.Require(this.Logger, this.FilePoll) // Set up file descriptor maps this.frontend = make(map[uint]*os.File) this.demux = make(map[uint]*os.File) + // Set up filter maps + this.section = make(map[uint][]*SectionFilter) + this.stream = make(map[uint][]*StreamFilter) + // Return success return nil } func (this *Manager) Dispose() error { + var result error + + // Close filters + for key, filters := range this.section { + for _, filter := range filters { + if err := this.StopSectionFilter(key, filter); err != nil { + result = multierror.Append(result, err) + } + } + } + for key, filters := range this.stream { + for _, filter := range filters { + if err := this.StopStreamFilter(key, filter); err != nil { + result = multierror.Append(result, err) + } + } + } + + // Lock for exclusive access this.RWMutex.Lock() defer this.RWMutex.Unlock() - var result error - // Close file descriptors for _, fh := range this.demux { if err := fh.Close(); err != nil { @@ -62,6 +87,8 @@ func (this *Manager) Dispose() error { // Release resources this.demux = nil this.frontend = nil + this.section = nil + this.stream = nil // Return any errors return result @@ -109,6 +136,8 @@ func (this *Manager) Tune(ctx context.Context, tuner gopi.DVBTuner, params gopi. return err } else if err := tuner_.Validate(params_); err != nil { return err + } else if err := this.StopFilters(tuner_); err != nil { + return err } else if err := dvb.FETune(fd, params_.TuneParams); err != nil { return err } @@ -132,11 +161,12 @@ func (this *Manager) Tune(ctx context.Context, tuner gopi.DVBTuner, params gopi. } switch { case status&dvb.FE_HAS_LOCK == dvb.FE_HAS_LOCK: + this.Debug(" ", status) return nil case status == dvb.FE_NONE: // Do nothing, no tune status default: - this.Debug(" status=", status) + this.Debug(" ", status) } } } @@ -145,6 +175,130 @@ func (this *Manager) Tune(ctx context.Context, tuner gopi.DVBTuner, params gopi. return nil } +// StartSectionFilter starts scanning for specific pid & tid +func (this *Manager) StartSectionFilter(tuner *Tuner, pid uint16, tids ...ts.TableType) (*SectionFilter, error) { + this.RWMutex.Lock() + defer this.RWMutex.Unlock() + + // Create filter + filter, err := NewSectionFilter(tuner, pid, tids...) + if err != nil { + return nil, err + } + this.Debug("StartSectionFilter", filter) + + // Watch for read + if err := this.FilePoll.Watch(filter.Fd(), gopi.FILEPOLL_FLAG_READ, func(uintptr, gopi.FilePollFlags) { + // Parse data + if section, err := ts.NewSection(filter.dev); err != nil { + this.Print("ERROR:", err) + } else { + this.Print(section) + } + }); err != nil { + filter.Dispose() + return nil, err + } + + // Start filtering + if err := filter.Start(); err != nil { + this.FilePoll.Unwatch(filter.Fd()) + filter.Dispose() + return nil, err + } else { + key := tuner.Id() + this.section[key] = append(this.section[key], filter) + } + + // Return success + return filter, nil +} + +// StopFilters stops all filters for a tuner +func (this *Manager) StopFilters(tuner *Tuner) error { + // Check parameters + if tuner == nil { + return gopi.ErrBadParameter.WithPrefix("StopFilters") + } + + // Tuner key + key := tuner.Id() + + // Sensitive section - get filters + this.RWMutex.RLock() + sections, _ := this.section[key] + streams, _ := this.stream[key] + this.RWMutex.RUnlock() + + // Stop filters + var result error + for _, filter := range sections { + if err := this.StopSectionFilter(key, filter); err != nil { + result = multierror.Append(result, err) + } + } + for _, filter := range streams { + if err := this.StopStreamFilter(key, filter); err != nil { + result = multierror.Append(result, err) + } + } + + // Return any errors + return result +} + +// StopSectionFilter stops section filter +func (this *Manager) StopSectionFilter(key uint, filter *SectionFilter) error { + this.RWMutex.Lock() + defer this.RWMutex.Unlock() + + // Check parameters + if filter == nil { + return gopi.ErrBadParameter.WithPrefix("StopSectionFilter") + } + if _, exists := this.section[key]; exists == false { + return gopi.ErrBadParameter.WithPrefix("StopSectionFilter") + } + + // Remove from list of filters + this.section[key] = removeSectionFilter(this.section[key], filter) + + // Debug + this.Debug("StopSectionFilter", filter) + + var result error + // Unwatch + if err := this.FilePoll.Unwatch(filter.Fd()); err != nil { + result = multierror.Append(result, err) + } + // Stop filtering + if err := filter.Stop(); err != nil { + result = multierror.Append(result, err) + } + // Dispose filter + if err := filter.Dispose(); err != nil { + result = multierror.Append(result, err) + } + + return result +} + +// StopStreamFilter stops stream filter +func (this *Manager) StopStreamFilter(key uint, filter *StreamFilter) error { + this.RWMutex.Lock() + defer this.RWMutex.Unlock() + return gopi.ErrNotImplemented +} + +// ScanNIT looks for a NIT table section +func (this *Manager) ScanNIT(tuner gopi.DVBTuner) error { + if _, err := this.StartSectionFilter(tuner.(*Tuner), uint16(0x10), ts.NIT); err != nil { + return err + } else { + return nil + } +} + //////////////////////////////////////////////////////////////////////////////// // STRINGIFY @@ -159,6 +313,17 @@ func (this *Manager) String() string { //////////////////////////////////////////////////////////////////////////////// // PRIVATE METHODS +// removeSectionFilter +func removeSectionFilter(arr []*SectionFilter, filter *SectionFilter) []*SectionFilter { + for i, elem := range arr { + if elem == filter { + return append(arr[:i], arr[i+1:]...) + } + } + // Filter not in array + return arr +} + // getFrontend returns file descriptor for frontend func (this *Manager) getFrontend(tuner *Tuner) (uintptr, error) { this.RWMutex.Lock() diff --git a/pkg/media/dvb/manager_test.go b/pkg/media/dvb/manager_test.go index 3a8c3365..d6edecc2 100644 --- a/pkg/media/dvb/manager_test.go +++ b/pkg/media/dvb/manager_test.go @@ -3,6 +3,8 @@ package dvb_test import ( + "context" + "errors" "os" "testing" "time" @@ -10,7 +12,6 @@ import ( gopi "github.com/djthorpe/gopi/v3" _ "github.com/djthorpe/gopi/v3/pkg/media/dvb" tool "github.com/djthorpe/gopi/v3/pkg/tool" - "golang.org/x/net/context" ) type ManagerApp struct { @@ -72,7 +73,9 @@ func Test_Manager_003(t *testing.T) { t.Log("Tuning", param.Name()) ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) defer cancel() - if err := app.DVBManager.Tune(ctx, devices[0], param); err != nil { + if err := app.DVBManager.Tune(ctx, devices[0], param); errors.Is(err, context.DeadlineExceeded) { + t.Log(" Tune Timeout") + } else if err != nil { t.Error(err) } else { t.Log(" Tune OK") diff --git a/pkg/media/dvb/tuner.go b/pkg/media/dvb/tuner.go index e232e43f..12265d6a 100644 --- a/pkg/media/dvb/tuner.go +++ b/pkg/media/dvb/tuner.go @@ -6,7 +6,7 @@ import ( "fmt" "os" - "github.com/djthorpe/gopi/v3" + gopi "github.com/djthorpe/gopi/v3" dvb "github.com/djthorpe/gopi/v3/pkg/sys/dvb" ) diff --git a/pkg/media/internal/ts/header.go b/pkg/media/internal/ts/header.go new file mode 100644 index 00000000..c8cee074 --- /dev/null +++ b/pkg/media/internal/ts/header.go @@ -0,0 +1,46 @@ +package ts + +import ( + "encoding/binary" + "fmt" + "io" +) + +//////////////////////////////////////////////////////////////////////////////// +// TYPES + +type Header struct { + NetworkId uint16 + Version uint8 + Section uint8 + LastSection uint8 +} + +//////////////////////////////////////////////////////////////////////////////// +// PUBLIC METHODS + +func NewHeader(r io.Reader) (*Header, error) { + this := new(Header) + + if err := binary.Read(r, binary.LittleEndian, this); err != nil { + return nil, err + } else { + // TODO: Current + //Current = this.Version&0x01 != 0x00 + this.Version = (this.Version >> 1) & 0x1F + } + + return this, nil +} + +//////////////////////////////////////////////////////////////////////////////// +// STRINGIFY + +func (this *Header) String() string { + str := "" +} diff --git a/pkg/media/internal/ts/nit.go b/pkg/media/internal/ts/nit.go new file mode 100644 index 00000000..a5d906cd --- /dev/null +++ b/pkg/media/internal/ts/nit.go @@ -0,0 +1,43 @@ +package ts + +import ( + "fmt" + "io" +) + +//////////////////////////////////////////////////////////////////////////////// +// TYPES + +type NITSection struct { + *Header + *Table +} + +//////////////////////////////////////////////////////////////////////////////// +// PUBLIC METHODS + +func NewNITSection(r io.Reader) (*NITSection, error) { + this := new(NITSection) + + if header, err := NewHeader(r); err != nil { + return nil, err + } else if rows, err := NewTable(r); err != nil { + return nil, err + } else { + this.Header = header + this.Table = rows + } + + // Return success + return this, nil +} + +//////////////////////////////////////////////////////////////////////////////// +// STRINGIFY + +func (this *NITSection) String() string { + str := "" +} diff --git a/pkg/media/internal/ts/table.go b/pkg/media/internal/ts/table.go new file mode 100644 index 00000000..c46fb0f7 --- /dev/null +++ b/pkg/media/internal/ts/table.go @@ -0,0 +1,79 @@ +package ts + +import ( + "encoding/binary" + "encoding/hex" + "fmt" + "io" + "strings" +) + +//////////////////////////////////////////////////////////////////////////////// +// TYPES + +type Table struct { + Rows []Row +} + +type RowHeader struct { + Tag uint8 + Length uint8 +} + +type Row struct { + RowHeader + data []byte +} + +//////////////////////////////////////////////////////////////////////////////// +// PUBLIC METHODS + +func NewTable(r io.Reader) (*Table, error) { + this := new(Table) + + // Read length of descriptor that follow in bytes + var length uint16 + if err := binary.Read(r, binary.LittleEndian, &length); err != nil { + return nil, err + } else { + // Top four bits are reserved for future use, length is 12 bits + length &= 0x0FFF + } + + // Read rows until length is zero + for length > 0 { + fmt.Println("remaining length=", length) + var row Row + if err := binary.Read(r, binary.LittleEndian, &row.RowHeader); err != nil { + return nil, err + } + fmt.Printf(" tag=%02X length=%v\n", row.RowHeader.Tag, row.RowHeader.Length) + row.data = make([]byte, int(row.Length)) + if _, err := r.Read(row.data); err != nil { + return nil, err + } + if row.Tag == 0x40 { // Network name + fmt.Printf(" data=%q\n", string(row.data)) + } + + // Append row + this.Rows = append(this.Rows, row) + + // Decrement by 2 bytes and length of data + length -= 2 + length -= uint16(row.Length) + } + + return this, nil +} + +//////////////////////////////////////////////////////////////////////////////// +// STRINGIFY + +func (this *Table) String() string { + str := "" + for _, row := range this.Rows { + str += fmt.Sprintf("<0x%02X=%v> ", row.Tag, hex.EncodeToString(row.data)) + } + return strings.TrimSuffix(str, " ") +} diff --git a/pkg/media/internal/ts/ts.go b/pkg/media/internal/ts/ts.go new file mode 100644 index 00000000..84883822 --- /dev/null +++ b/pkg/media/internal/ts/ts.go @@ -0,0 +1,120 @@ +package ts + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" +) + +// Ref: https://www.etsi.org/deliver/etsi_ts/101200_101299/101211/01.11.01_60/ts_101211v011101p.pdf +// Ref: https://www.etsi.org/deliver/etsi_en/300400_300499/300468/01.03.01_60/en_300468v010301p.pdf + +//////////////////////////////////////////////////////////////////////////////// +// TYPES + +type TableType uint8 + +type SectionHeader struct { + TableId TableType + Length uint16 +} + +type Section struct { + SectionHeader + *NITSection +} + +//////////////////////////////////////////////////////////////////////////////// +// CONSTANTS + +const ( + PAT TableType = 0x00 + CAT TableType = 0x01 + PMT TableType = 0x02 + NIT TableType = 0x40 + NIT_OTHER TableType = 0x41 + SDT TableType = 0x42 + SDT_OTHER TableType = 0x46 + BAT TableType = 0x4A + EIT TableType = 0x4E + EIT_OTHER TableType = 0x4F + TDT TableType = 0x70 +) + +//////////////////////////////////////////////////////////////////////////////// +// PUBLIC METHODS + +func NewSection(r io.Reader) (*Section, error) { + this := new(Section) + + // Read table type and length + if err := binary.Read(r, binary.LittleEndian, &this.SectionHeader); err != nil { + return nil, err + } else { + this.Length = this.Length & 0x0FFF + } + + // Read buffer of data + data := make([]byte, this.Length) + if _, err := r.Read(data); err != nil { + return nil, err + } + + // Parse data + switch this.TableId { + case NIT, NIT_OTHER: + if nit, err := NewNITSection(bytes.NewReader(data)); err != nil { + return nil, err + } else { + this.NITSection = nit + } + } + + // Success + return this, nil +} + +//////////////////////////////////////////////////////////////////////////////// +// STRINGIFY + +func (this *Section) String() string { + str := "" +} + +func (f TableType) String() string { + switch f { + case PAT: + return "PAT" + case CAT: + return "CAT" + case PMT: + return "PMT" + case NIT: + return "NIT" + case NIT_OTHER: + return "NIT_OTHER" + case SDT: + return "SDT" + case SDT_OTHER: + return "SDT_OTHER" + case BAT: + return "BAT" + case EIT: + return "EIT" + case EIT_OTHER: + return "EIT_OTHER" + case TDT: + return "TDT" + default: + return fmt.Sprintf("0x%02X", uint8(f)) + } +} diff --git a/pkg/sys/dvb/demux.go b/pkg/sys/dvb/demux.go index 19af32aa..11a46191 100644 --- a/pkg/sys/dvb/demux.go +++ b/pkg/sys/dvb/demux.go @@ -103,6 +103,7 @@ var ( // PUBLIC METHODS func DMXStart(fd uintptr) error { + fmt.Println("DMXStart", fd) if err := dvb_ioctl(fd, DMX_START, unsafe.Pointer(nil)); err != 0 { return os.NewSyscallError("DMX_START", err) } else { @@ -111,6 +112,7 @@ func DMXStart(fd uintptr) error { } func DMXStop(fd uintptr) error { + fmt.Println("DMXStop", fd) if err := dvb_ioctl(fd, DMX_STOP, unsafe.Pointer(nil)); err != 0 { return os.NewSyscallError("DMX_STOP", err) } else { @@ -126,16 +128,17 @@ func DMXSetBufferSize(fd uintptr, size uint32) error { } } -func DMXSetSectionFilter(fd uintptr, filter DMXSectionFilter) error { - if err := dvb_ioctl(fd, DMX_SET_FILTER, unsafe.Pointer(&filter)); err != 0 { +func DMXSetSectionFilter(fd uintptr, filter *DMXSectionFilter) error { + fmt.Println("DMXSetSectionFilter", fd, filter) + if err := dvb_ioctl(fd, DMX_SET_FILTER, unsafe.Pointer(filter)); err != 0 { return os.NewSyscallError("DMX_SET_FILTER", err) } else { return nil } } -func DMXSetStreamFilter(fd uintptr, filter DMXStreamFilter) error { - if err := dvb_ioctl(fd, DMX_SET_PES_FILTER, unsafe.Pointer(&filter)); err != 0 { +func DMXSetStreamFilter(fd uintptr, filter *DMXStreamFilter) error { + if err := dvb_ioctl(fd, DMX_SET_PES_FILTER, unsafe.Pointer(filter)); err != 0 { return os.NewSyscallError("DMX_SET_PES_FILTER", err) } else { return nil @@ -176,8 +179,8 @@ func DMXGetStreamPids(fd uintptr) (map[DMXStreamType]uint16, error) { //////////////////////////////////////////////////////////////////////////////// // DMXSectionFilter -func NewSectionFilter(pid uint16, timeout uint32, flags DMXFlags) DMXSectionFilter { - filter := DMXSectionFilter{ +func NewSectionFilter(pid uint16, timeout uint32, flags DMXFlags) *DMXSectionFilter { + filter := &DMXSectionFilter{ pid: C.__u16(pid), filter: C.struct_dmx_filter{}, timeout: C.__u32(timeout), @@ -186,7 +189,13 @@ func NewSectionFilter(pid uint16, timeout uint32, flags DMXFlags) DMXSectionFilt return filter } -func (f DMXSectionFilter) String() string { +func (f *DMXSectionFilter) Set(i int, tid, mask, mode uint8) { + f.filter.filter[i] = C.__u8(tid) + f.filter.mask[i] = C.__u8(mask) + f.filter.mode[i] = C.__u8(mode) +} + +func (f *DMXSectionFilter) String() string { str := "