// Copyright (c) 2020-2022 Nicholas Corgan // SPDX-License-Identifier: BSL-1.0 using System; using System.Linq; namespace Pothosware.SoapySDR { /// /// The base class for representing transmit or receive streams. This /// class will never be used itself. See TxStream and RxStream. /// public class Stream { internal Device _device = null; internal StreamHandle _streamHandle = null; protected bool _active = false; /// /// The underlying stream format. See Pothosware.SoapySDR.StreamFormat. /// public string Format { get; } /// /// The device channels used in this stream. /// public uint[] Channels { get; } /// /// The arguments used in creating this stream. /// public Kwargs StreamArgs { get; } /// /// Whether or not the stream is active. /// public bool Active => _active; // We already used these parameters to create the stream, // this is just for the sake of getters. internal Stream( Device device, string format, uint[] channels, Kwargs kwargs) { _device = device; Format = format; Channels = (uint[])channels.Clone(); StreamArgs = kwargs; } ~Stream() { if(_active) Deactivate(); if(_streamHandle != null) Close(); } /// /// The number of elements per channel this stream can handle in a single read/write call. /// public ulong MTU => _device.GetStreamMTUInternal(_streamHandle); /// /// Activate the stream to prepare it for read/write operations. /// /// Optional stream flags. /// Optional activation time in nanoseconds. Only valid when flags includes Pothosware.SoapySDR.StreamFlags.HasTime. /// Optional element count for burst control. /// An error code for the stream activation. public ErrorCode Activate( StreamFlags flags = StreamFlags.None, long timeNs = 0, uint numElems = 0) { ErrorCode ret; if(_streamHandle != null) { if(!_active) { ret = _device.ActivateStreamInternal( _streamHandle, flags, timeNs, numElems); if (ret == ErrorCode.None) _active = true; } else throw new InvalidOperationException("Stream is already active"); } else throw new InvalidOperationException("Stream is closed"); return ret; } /// /// Deactivate the stream to end read/write operations. /// /// Optional stream flags. /// Optional activation time in nanoseconds. Only valid when flags includes Pothosware.SoapySDR.StreamFlags.HasTime. /// Optional element count for burst control. /// An error code for the stream deactivation. public ErrorCode Deactivate( StreamFlags flags = StreamFlags.None, long timeNs = 0) { ErrorCode ret; if(_streamHandle != null) { if(_active) { ret = _device.DeactivateStreamInternal( _streamHandle, flags, timeNs); if(ret == ErrorCode.None) _active = false; } else throw new InvalidOperationException("Stream is already inactive"); } else throw new InvalidOperationException("Stream is closed"); return ret; } /// /// Close the underlying stream. /// public void Close() { if (_streamHandle != null) { _device.CloseStreamInternal(_streamHandle); _streamHandle = null; } else throw new InvalidOperationException("Stream is already closed"); } // // Utility // protected void ValidateSpan(ReadOnlySpan span) where T : unmanaged { var numChannels = _streamHandle.GetChannels().Length; var format = _streamHandle.GetFormat(); var complexFormatString = Utility.GetFormatString(); if(numChannels != 1) throw new ArgumentException("Stream is configured for a single channel. Cannot accept multiple buffers."); else if (!format.Equals(complexFormatString)) throw new ArgumentException(string.Format("Stream format \"{0}\" is incompatible with buffer type {1}.", format, typeof(T))); else if ((span.Length % 2) != 0) throw new ArgumentException("For complex interleaved streams, input buffers must be of an even size."); } protected void ValidateSpan(Span span) where T : unmanaged => ValidateSpan((ReadOnlySpan)span); protected void ValidateMemory(ReadOnlyMemory[] mems) where T : unmanaged { var numChannels = _streamHandle.GetChannels().Length; var format = _streamHandle.GetFormat(); var complexFormatString = Utility.GetFormatString(); if (mems == null) throw new ArgumentNullException("mems"); else if (numChannels != mems.Length) throw new ArgumentException(string.Format("Stream is configured for {0} channel(s). Cannot accept {1} buffer(s).", numChannels, mems.Length)); else if (!format.Equals(complexFormatString)) throw new ArgumentException(string.Format("Stream format \"{0}\" is incompatible with buffer type {1}.", format, typeof(T))); else if (mems.Select(buff => buff.Length).Distinct().Count() > 1) throw new ArgumentException("All buffers must be of the same length"); else if ((mems[0].Length % 2) != 0) throw new ArgumentException("For complex interleaved streams, input buffers must be of an even size."); } protected void ValidateMemory(Memory[] mems) where T : unmanaged => ValidateMemory(mems.Select(mem => (ReadOnlyMemory)mem).ToArray()); protected void ValidateIntPtrArray(IntPtr[] intPtrs) { var numChannels = _streamHandle.GetChannels().Length; if (intPtrs == null) throw new ArgumentNullException("mems"); else if (intPtrs.Length != numChannels) throw new ArgumentException(string.Format("Stream is configured for {0} channel(s). Cannot accept {1} buffer(s).", numChannels, intPtrs.Length)); else if (intPtrs.Any(x => x == null)) throw new ArgumentNullException("intPtrs"); } // // Object overrides // // For completeness, but a stream is only ever equal to itself public override bool Equals(object other) => ReferenceEquals(this, other); public override int GetHashCode() => HashCodeBuilder.Create() .AddValue(GetType()) .AddValue(_device) .AddValue(Format) .AddValue(Channels) .AddValue(StreamArgs); } }