| @@ -114,18 +114,28 @@ type IngestSRTConfig struct { | |||||
| } | } | ||||
| type IngestAES67Config struct { | type IngestAES67Config struct { | ||||
| SDPPath string `json:"sdpPath"` | |||||
| SDP string `json:"sdp"` | |||||
| MulticastGroup string `json:"multicastGroup"` | |||||
| Port int `json:"port"` | |||||
| InterfaceName string `json:"interfaceName"` | |||||
| PayloadType int `json:"payloadType"` | |||||
| SampleRateHz int `json:"sampleRateHz"` | |||||
| Channels int `json:"channels"` | |||||
| Encoding string `json:"encoding"` | |||||
| PacketTimeMs int `json:"packetTimeMs"` | |||||
| JitterDepthPackets int `json:"jitterDepthPackets"` | |||||
| ReadBufferBytes int `json:"readBufferBytes"` | |||||
| SDPPath string `json:"sdpPath"` | |||||
| SDP string `json:"sdp"` | |||||
| Discovery IngestAES67DiscoveryConfig `json:"discovery"` | |||||
| MulticastGroup string `json:"multicastGroup"` | |||||
| Port int `json:"port"` | |||||
| InterfaceName string `json:"interfaceName"` | |||||
| PayloadType int `json:"payloadType"` | |||||
| SampleRateHz int `json:"sampleRateHz"` | |||||
| Channels int `json:"channels"` | |||||
| Encoding string `json:"encoding"` | |||||
| PacketTimeMs int `json:"packetTimeMs"` | |||||
| JitterDepthPackets int `json:"jitterDepthPackets"` | |||||
| ReadBufferBytes int `json:"readBufferBytes"` | |||||
| } | |||||
| type IngestAES67DiscoveryConfig struct { | |||||
| Enabled bool `json:"enabled"` | |||||
| StreamName string `json:"streamName"` | |||||
| TimeoutMs int `json:"timeoutMs"` | |||||
| InterfaceName string `json:"interfaceName"` | |||||
| SAPGroup string `json:"sapGroup"` | |||||
| SAPPort int `json:"sapPort"` | |||||
| } | } | ||||
| func Default() Config { | func Default() Config { | ||||
| @@ -182,6 +192,9 @@ func Default() Config { | |||||
| Channels: 2, | Channels: 2, | ||||
| }, | }, | ||||
| AES67: IngestAES67Config{ | AES67: IngestAES67Config{ | ||||
| Discovery: IngestAES67DiscoveryConfig{ | |||||
| TimeoutMs: 3000, | |||||
| }, | |||||
| PayloadType: 97, | PayloadType: 97, | ||||
| SampleRateHz: 48000, | SampleRateHz: 48000, | ||||
| Channels: 2, | Channels: 2, | ||||
| @@ -318,17 +331,30 @@ func (c Config) Validate() error { | |||||
| if ingestKind == "aes67" || ingestKind == "aoip" || ingestKind == "aoip-rtp" { | if ingestKind == "aes67" || ingestKind == "aoip" || ingestKind == "aoip-rtp" { | ||||
| hasSDP := strings.TrimSpace(c.Ingest.AES67.SDP) != "" | hasSDP := strings.TrimSpace(c.Ingest.AES67.SDP) != "" | ||||
| hasSDPPath := strings.TrimSpace(c.Ingest.AES67.SDPPath) != "" | hasSDPPath := strings.TrimSpace(c.Ingest.AES67.SDPPath) != "" | ||||
| discoveryEnabled := c.Ingest.AES67.Discovery.Enabled || strings.TrimSpace(c.Ingest.AES67.Discovery.StreamName) != "" | |||||
| if hasSDP && hasSDPPath { | if hasSDP && hasSDPPath { | ||||
| return fmt.Errorf("ingest.aes67.sdp and ingest.aes67.sdpPath are mutually exclusive") | return fmt.Errorf("ingest.aes67.sdp and ingest.aes67.sdpPath are mutually exclusive") | ||||
| } | } | ||||
| if !hasSDP && !hasSDPPath { | if !hasSDP && !hasSDPPath { | ||||
| if strings.TrimSpace(c.Ingest.AES67.MulticastGroup) == "" { | |||||
| if strings.TrimSpace(c.Ingest.AES67.MulticastGroup) == "" && !discoveryEnabled { | |||||
| return fmt.Errorf("ingest.aes67.multicastGroup is required when ingest.kind=%s", ingestKind) | return fmt.Errorf("ingest.aes67.multicastGroup is required when ingest.kind=%s", ingestKind) | ||||
| } | } | ||||
| if c.Ingest.AES67.Port <= 0 || c.Ingest.AES67.Port > 65535 { | |||||
| if (c.Ingest.AES67.Port <= 0 || c.Ingest.AES67.Port > 65535) && !discoveryEnabled { | |||||
| return fmt.Errorf("ingest.aes67.port must be 1..65535") | return fmt.Errorf("ingest.aes67.port must be 1..65535") | ||||
| } | } | ||||
| } | } | ||||
| if c.Ingest.AES67.Discovery.TimeoutMs < 0 { | |||||
| return fmt.Errorf("ingest.aes67.discovery.timeoutMs must be >= 0") | |||||
| } | |||||
| if c.Ingest.AES67.Discovery.SAPPort < 0 || c.Ingest.AES67.Discovery.SAPPort > 65535 { | |||||
| return fmt.Errorf("ingest.aes67.discovery.sapPort must be 0..65535") | |||||
| } | |||||
| if discoveryEnabled && strings.TrimSpace(c.Ingest.AES67.Discovery.StreamName) == "" { | |||||
| return fmt.Errorf("ingest.aes67.discovery.streamName is required when discovery is enabled") | |||||
| } | |||||
| if discoveryEnabled && c.Ingest.AES67.Port > 65535 { | |||||
| return fmt.Errorf("ingest.aes67.port must be 1..65535") | |||||
| } | |||||
| if c.Ingest.AES67.PayloadType < 0 || c.Ingest.AES67.PayloadType > 127 { | if c.Ingest.AES67.PayloadType < 0 || c.Ingest.AES67.PayloadType > 127 { | ||||
| return fmt.Errorf("ingest.aes67.payloadType must be 0..127") | return fmt.Errorf("ingest.aes67.payloadType must be 0..127") | ||||
| } | } | ||||
| @@ -203,6 +203,41 @@ func TestValidateAcceptsAES67WithSDPOnly(t *testing.T) { | |||||
| } | } | ||||
| } | } | ||||
| func TestValidateAcceptsAES67WithDiscoveryOnly(t *testing.T) { | |||||
| cfg := Default() | |||||
| cfg.Ingest.Kind = "aes67" | |||||
| cfg.Ingest.AES67.MulticastGroup = "" | |||||
| cfg.Ingest.AES67.Port = 0 | |||||
| cfg.Ingest.AES67.Discovery.StreamName = "AES67-MAIN" | |||||
| if err := cfg.Validate(); err != nil { | |||||
| t.Fatalf("expected aes67 discovery config to validate: %v", err) | |||||
| } | |||||
| } | |||||
| func TestValidateRejectsAES67DiscoveryWithoutStreamName(t *testing.T) { | |||||
| cfg := Default() | |||||
| cfg.Ingest.Kind = "aes67" | |||||
| cfg.Ingest.AES67.MulticastGroup = "" | |||||
| cfg.Ingest.AES67.Port = 0 | |||||
| cfg.Ingest.AES67.Discovery.Enabled = true | |||||
| cfg.Ingest.AES67.Discovery.StreamName = "" | |||||
| if err := cfg.Validate(); err == nil { | |||||
| t.Fatal("expected discovery streamName validation error") | |||||
| } | |||||
| } | |||||
| func TestValidateRejectsAES67DiscoverySAPPortOutOfRange(t *testing.T) { | |||||
| cfg := Default() | |||||
| cfg.Ingest.Kind = "aes67" | |||||
| cfg.Ingest.AES67.MulticastGroup = "" | |||||
| cfg.Ingest.AES67.Port = 0 | |||||
| cfg.Ingest.AES67.Discovery.StreamName = "AES67-MAIN" | |||||
| cfg.Ingest.AES67.Discovery.SAPPort = 70000 | |||||
| if err := cfg.Validate(); err == nil { | |||||
| t.Fatal("expected discovery sapPort validation error") | |||||
| } | |||||
| } | |||||
| func TestValidateRejectsUnsupportedIngestPCMShape(t *testing.T) { | func TestValidateRejectsUnsupportedIngestPCMShape(t *testing.T) { | ||||
| cfg := Default() | cfg := Default() | ||||
| cfg.Ingest.Stdin.SampleRateHz = 0 | cfg.Ingest.Stdin.SampleRateHz = 0 | ||||
| @@ -30,11 +30,18 @@ func WithReceiverFactory(factory ReceiverFactory) Option { | |||||
| } | } | ||||
| } | } | ||||
| func WithDetail(detail string) Option { | |||||
| return func(s *Source) { | |||||
| s.detail = detail | |||||
| } | |||||
| } | |||||
| type Source struct { | type Source struct { | ||||
| id string | id string | ||||
| cfg aoiprxkit.Config | cfg aoiprxkit.Config | ||||
| factory ReceiverFactory | factory ReceiverFactory | ||||
| detail string | |||||
| chunks chan ingest.PCMChunk | chunks chan ingest.PCMChunk | ||||
| errs chan error | errs chan error | ||||
| @@ -89,6 +96,10 @@ func New(id string, cfg aoiprxkit.Config, opts ...Option) *Source { | |||||
| } | } | ||||
| func (s *Source) Descriptor() ingest.SourceDescriptor { | func (s *Source) Descriptor() ingest.SourceDescriptor { | ||||
| detail := s.detail | |||||
| if detail == "" { | |||||
| detail = fmt.Sprintf("rtp://%s:%d", s.cfg.MulticastGroup, s.cfg.Port) | |||||
| } | |||||
| return ingest.SourceDescriptor{ | return ingest.SourceDescriptor{ | ||||
| ID: s.id, | ID: s.id, | ||||
| Kind: "aes67", | Kind: "aes67", | ||||
| @@ -97,7 +108,7 @@ func (s *Source) Descriptor() ingest.SourceDescriptor { | |||||
| Codec: "l24", | Codec: "l24", | ||||
| Channels: s.cfg.Channels, | Channels: s.cfg.Channels, | ||||
| SampleRateHz: s.cfg.SampleRateHz, | SampleRateHz: s.cfg.SampleRateHz, | ||||
| Detail: fmt.Sprintf("rtp://%s:%d", s.cfg.MulticastGroup, s.cfg.Port), | |||||
| Detail: detail, | |||||
| } | } | ||||
| } | } | ||||
| @@ -112,6 +112,20 @@ func TestSourceEmitsChunksAndMapsStats(t *testing.T) { | |||||
| } | } | ||||
| } | } | ||||
| func TestSourceDescriptorSupportsDetailOverride(t *testing.T) { | |||||
| src := New("aes67-test", aoiprxkit.Config{ | |||||
| MulticastGroup: "239.10.20.30", | |||||
| Port: 5004, | |||||
| SampleRateHz: 48000, | |||||
| Channels: 2, | |||||
| }, WithDetail("rtp://239.10.20.30:5004 (SAP s=AES67-MAIN)")) | |||||
| desc := src.Descriptor() | |||||
| if desc.Detail != "rtp://239.10.20.30:5004 (SAP s=AES67-MAIN)" { | |||||
| t.Fatalf("detail=%q", desc.Detail) | |||||
| } | |||||
| } | |||||
| func readChunk(t *testing.T, ch <-chan ingest.PCMChunk) ingest.PCMChunk { | func readChunk(t *testing.T, ch <-chan ingest.PCMChunk) ingest.PCMChunk { | ||||
| t.Helper() | t.Helper() | ||||
| select { | select { | ||||
| @@ -1,6 +1,7 @@ | |||||
| package factory | package factory | ||||
| import ( | import ( | ||||
| "context" | |||||
| "fmt" | "fmt" | ||||
| "io" | "io" | ||||
| "net/http" | "net/http" | ||||
| @@ -24,12 +25,23 @@ type Deps struct { | |||||
| HTTP *http.Client | HTTP *http.Client | ||||
| SRTOpener aoiprxkit.SRTConnOpener | SRTOpener aoiprxkit.SRTConnOpener | ||||
| AES67ReceiverFactory aoip.ReceiverFactory | AES67ReceiverFactory aoip.ReceiverFactory | ||||
| AES67Discover AES67DiscoverFunc | |||||
| } | } | ||||
| type AudioIngress interface { | type AudioIngress interface { | ||||
| WritePCM16(data []byte) (int, error) | WritePCM16(data []byte) (int, error) | ||||
| } | } | ||||
| type AES67DiscoverRequest struct { | |||||
| StreamName string | |||||
| Timeout time.Duration | |||||
| InterfaceName string | |||||
| SAPGroup string | |||||
| SAPPort int | |||||
| } | |||||
| type AES67DiscoverFunc func(ctx context.Context, req AES67DiscoverRequest) (aoiprxkit.SAPAnnouncement, error) | |||||
| func BuildSource(cfg config.Config, deps Deps) (ingest.Source, AudioIngress, error) { | func BuildSource(cfg config.Config, deps Deps) (ingest.Source, AudioIngress, error) { | ||||
| switch normalizeIngestKind(cfg.Ingest.Kind) { | switch normalizeIngestKind(cfg.Ingest.Kind) { | ||||
| case "", "none": | case "", "none": | ||||
| @@ -71,7 +83,7 @@ func BuildSource(cfg config.Config, deps Deps) (ingest.Source, AudioIngress, err | |||||
| src := srt.New("srt-main", srtCfg, opts...) | src := srt.New("srt-main", srtCfg, opts...) | ||||
| return src, nil, nil | return src, nil, nil | ||||
| case "aes67", "aoip", "aoip-rtp": | case "aes67", "aoip", "aoip-rtp": | ||||
| aoipCfg, err := buildAES67Config(cfg) | |||||
| aoipCfg, detail, err := buildAES67Config(cfg, deps) | |||||
| if err != nil { | if err != nil { | ||||
| return nil, nil, err | return nil, nil, err | ||||
| } | } | ||||
| @@ -79,6 +91,9 @@ func BuildSource(cfg config.Config, deps Deps) (ingest.Source, AudioIngress, err | |||||
| if deps.AES67ReceiverFactory != nil { | if deps.AES67ReceiverFactory != nil { | ||||
| opts = append(opts, aoip.WithReceiverFactory(deps.AES67ReceiverFactory)) | opts = append(opts, aoip.WithReceiverFactory(deps.AES67ReceiverFactory)) | ||||
| } | } | ||||
| if detail != "" { | |||||
| opts = append(opts, aoip.WithDetail(detail)) | |||||
| } | |||||
| src := aoip.New("aes67-main", aoipCfg, opts...) | src := aoip.New("aes67-main", aoipCfg, opts...) | ||||
| return src, nil, nil | return src, nil, nil | ||||
| default: | default: | ||||
| @@ -114,7 +129,7 @@ func normalizeIngestKind(kind string) string { | |||||
| return strings.ToLower(strings.TrimSpace(kind)) | return strings.ToLower(strings.TrimSpace(kind)) | ||||
| } | } | ||||
| func buildAES67Config(cfg config.Config) (aoiprxkit.Config, error) { | |||||
| func buildAES67Config(cfg config.Config, deps Deps) (aoiprxkit.Config, string, error) { | |||||
| base := aoiprxkit.DefaultConfig() | base := aoiprxkit.DefaultConfig() | ||||
| ing := cfg.Ingest.AES67 | ing := cfg.Ingest.AES67 | ||||
| if strings.TrimSpace(ing.InterfaceName) != "" { | if strings.TrimSpace(ing.InterfaceName) != "" { | ||||
| @@ -142,24 +157,25 @@ func buildAES67Config(cfg config.Config) (aoiprxkit.Config, error) { | |||||
| base.ReadBufferBytes = ing.ReadBufferBytes | base.ReadBufferBytes = ing.ReadBufferBytes | ||||
| } | } | ||||
| sdpText := strings.TrimSpace(ing.SDP) | |||||
| if sdpText == "" && strings.TrimSpace(ing.SDPPath) != "" { | |||||
| data, err := os.ReadFile(filepath.Clean(ing.SDPPath)) | |||||
| if err != nil { | |||||
| return aoiprxkit.Config{}, fmt.Errorf("read ingest.aes67.sdpPath: %w", err) | |||||
| } | |||||
| sdpText = string(data) | |||||
| sdpText, discoveredStreamName, err := resolveAES67SDP(ing, deps) | |||||
| if err != nil { | |||||
| return aoiprxkit.Config{}, "", err | |||||
| } | } | ||||
| if sdpText != "" { | if sdpText != "" { | ||||
| info, err := aoiprxkit.ParseMinimalSDP(sdpText) | info, err := aoiprxkit.ParseMinimalSDP(sdpText) | ||||
| if err != nil { | if err != nil { | ||||
| return aoiprxkit.Config{}, fmt.Errorf("parse ingest.aes67 SDP: %w", err) | |||||
| return aoiprxkit.Config{}, "", fmt.Errorf("parse ingest.aes67 SDP: %w", err) | |||||
| } | } | ||||
| parsed, err := aoiprxkit.ConfigFromSDP(base, info) | parsed, err := aoiprxkit.ConfigFromSDP(base, info) | ||||
| if err != nil { | if err != nil { | ||||
| return aoiprxkit.Config{}, fmt.Errorf("map ingest.aes67 SDP: %w", err) | |||||
| return aoiprxkit.Config{}, "", fmt.Errorf("map ingest.aes67 SDP: %w", err) | |||||
| } | |||||
| detail := "" | |||||
| if discoveredStreamName != "" { | |||||
| detail = fmt.Sprintf("rtp://%s:%d (SAP s=%s)", parsed.MulticastGroup, parsed.Port, discoveredStreamName) | |||||
| } | } | ||||
| return parsed, nil | |||||
| return parsed, detail, nil | |||||
| } | } | ||||
| if strings.TrimSpace(ing.MulticastGroup) != "" { | if strings.TrimSpace(ing.MulticastGroup) != "" { | ||||
| base.MulticastGroup = strings.TrimSpace(ing.MulticastGroup) | base.MulticastGroup = strings.TrimSpace(ing.MulticastGroup) | ||||
| @@ -168,7 +184,77 @@ func buildAES67Config(cfg config.Config) (aoiprxkit.Config, error) { | |||||
| base.Port = ing.Port | base.Port = ing.Port | ||||
| } | } | ||||
| if err := base.Validate(); err != nil { | if err := base.Validate(); err != nil { | ||||
| return aoiprxkit.Config{}, err | |||||
| return aoiprxkit.Config{}, "", err | |||||
| } | } | ||||
| return base, nil | |||||
| return base, "", nil | |||||
| } | |||||
| func resolveAES67SDP(ing config.IngestAES67Config, deps Deps) (string, string, error) { | |||||
| sdpText := strings.TrimSpace(ing.SDP) | |||||
| if sdpText == "" && strings.TrimSpace(ing.SDPPath) != "" { | |||||
| data, err := os.ReadFile(filepath.Clean(ing.SDPPath)) | |||||
| if err != nil { | |||||
| return "", "", fmt.Errorf("read ingest.aes67.sdpPath: %w", err) | |||||
| } | |||||
| sdpText = string(data) | |||||
| } | |||||
| if sdpText != "" { | |||||
| return sdpText, "", nil | |||||
| } | |||||
| discoveryEnabled := ing.Discovery.Enabled || strings.TrimSpace(ing.Discovery.StreamName) != "" | |||||
| if !discoveryEnabled { | |||||
| return "", "", nil | |||||
| } | |||||
| timeout := time.Duration(ing.Discovery.TimeoutMs) * time.Millisecond | |||||
| if timeout <= 0 { | |||||
| timeout = 3 * time.Second | |||||
| } | |||||
| req := AES67DiscoverRequest{ | |||||
| StreamName: strings.TrimSpace(ing.Discovery.StreamName), | |||||
| Timeout: timeout, | |||||
| InterfaceName: strings.TrimSpace(ing.Discovery.InterfaceName), | |||||
| SAPGroup: strings.TrimSpace(ing.Discovery.SAPGroup), | |||||
| SAPPort: ing.Discovery.SAPPort, | |||||
| } | |||||
| discover := deps.AES67Discover | |||||
| if discover == nil { | |||||
| discover = discoverAES67ViaSAP | |||||
| } | |||||
| announcement, err := discover(context.Background(), req) | |||||
| if err != nil { | |||||
| return "", "", fmt.Errorf("discover ingest.aes67 stream %q via SAP: %w", req.StreamName, err) | |||||
| } | |||||
| if strings.TrimSpace(announcement.SDP) == "" { | |||||
| return "", "", fmt.Errorf("discover ingest.aes67 stream %q via SAP: empty SDP payload", req.StreamName) | |||||
| } | |||||
| return announcement.SDP, req.StreamName, nil | |||||
| } | |||||
| func discoverAES67ViaSAP(ctx context.Context, req AES67DiscoverRequest) (aoiprxkit.SAPAnnouncement, error) { | |||||
| if req.StreamName == "" { | |||||
| return aoiprxkit.SAPAnnouncement{}, fmt.Errorf("stream name must not be empty") | |||||
| } | |||||
| listenerCfg := aoiprxkit.DefaultSAPListenerConfig() | |||||
| if req.InterfaceName != "" { | |||||
| listenerCfg.InterfaceName = req.InterfaceName | |||||
| } | |||||
| if req.SAPGroup != "" { | |||||
| listenerCfg.Group = req.SAPGroup | |||||
| } | |||||
| if req.SAPPort > 0 { | |||||
| listenerCfg.Port = req.SAPPort | |||||
| } | |||||
| sf, err := aoiprxkit.NewStreamFinder(listenerCfg) | |||||
| if err != nil { | |||||
| return aoiprxkit.SAPAnnouncement{}, err | |||||
| } | |||||
| if err := sf.Start(ctx); err != nil { | |||||
| return aoiprxkit.SAPAnnouncement{}, err | |||||
| } | |||||
| defer sf.Stop() | |||||
| waitCtx, cancel := context.WithTimeout(ctx, req.Timeout) | |||||
| defer cancel() | |||||
| return sf.WaitForStreamName(waitCtx, req.StreamName) | |||||
| } | } | ||||
| @@ -2,7 +2,12 @@ package factory | |||||
| import ( | import ( | ||||
| "bytes" | "bytes" | ||||
| "context" | |||||
| "errors" | |||||
| "testing" | "testing" | ||||
| "time" | |||||
| "aoiprxkit" | |||||
| "github.com/jan/fm-rds-tx/internal/config" | "github.com/jan/fm-rds-tx/internal/config" | ||||
| ) | ) | ||||
| @@ -156,6 +161,56 @@ func TestBuildSourceAES67FromInlineSDP(t *testing.T) { | |||||
| } | } | ||||
| } | } | ||||
| func TestBuildSourceAES67WithDiscovery(t *testing.T) { | |||||
| cfg := config.Default() | |||||
| cfg.Ingest.Kind = "aes67" | |||||
| cfg.Ingest.AES67.MulticastGroup = "" | |||||
| cfg.Ingest.AES67.Port = 0 | |||||
| cfg.Ingest.AES67.Discovery.StreamName = "AES67-MAIN" | |||||
| cfg.Ingest.AES67.Discovery.TimeoutMs = 1500 | |||||
| var gotReq AES67DiscoverRequest | |||||
| src, _, err := BuildSource(cfg, Deps{ | |||||
| AES67Discover: func(_ context.Context, req AES67DiscoverRequest) (aoiprxkit.SAPAnnouncement, error) { | |||||
| gotReq = req | |||||
| return aoiprxkit.SAPAnnouncement{ | |||||
| SDP: "v=0\r\ns=AES67-MAIN\r\nc=IN IP4 239.10.20.30\r\nm=audio 5004 RTP/AVP 97\r\na=rtpmap:97 L24/48000/2\r\na=ptime:1\r\n", | |||||
| }, nil | |||||
| }, | |||||
| }) | |||||
| if err != nil { | |||||
| t.Fatalf("build source: %v", err) | |||||
| } | |||||
| if gotReq.StreamName != "AES67-MAIN" { | |||||
| t.Fatalf("discovery streamName=%q want AES67-MAIN", gotReq.StreamName) | |||||
| } | |||||
| if gotReq.Timeout != 1500*time.Millisecond { | |||||
| t.Fatalf("discovery timeout=%s want 1500ms", gotReq.Timeout) | |||||
| } | |||||
| desc := src.Descriptor() | |||||
| if desc.Detail != "rtp://239.10.20.30:5004 (SAP s=AES67-MAIN)" { | |||||
| t.Fatalf("descriptor detail=%q", desc.Detail) | |||||
| } | |||||
| } | |||||
| func TestBuildSourceAES67DiscoveryError(t *testing.T) { | |||||
| cfg := config.Default() | |||||
| cfg.Ingest.Kind = "aes67" | |||||
| cfg.Ingest.AES67.MulticastGroup = "" | |||||
| cfg.Ingest.AES67.Port = 0 | |||||
| cfg.Ingest.AES67.Discovery.StreamName = "AES67-MAIN" | |||||
| _, _, err := BuildSource(cfg, Deps{ | |||||
| AES67Discover: func(_ context.Context, req AES67DiscoverRequest) (aoiprxkit.SAPAnnouncement, error) { | |||||
| _ = req | |||||
| return aoiprxkit.SAPAnnouncement{}, errors.New("timeout") | |||||
| }, | |||||
| }) | |||||
| if err == nil { | |||||
| t.Fatalf("expected discovery error") | |||||
| } | |||||
| } | |||||
| func TestBuildSourceUnsupportedKind(t *testing.T) { | func TestBuildSourceUnsupportedKind(t *testing.T) { | ||||
| cfg := config.Default() | cfg := config.Default() | ||||
| cfg.Ingest.Kind = "nope" | cfg.Ingest.Kind = "nope" | ||||