/// <summary> /// Determine if the device will support a given PCM format /// </summary> /// <param name="sampleFormat">The data format for PCM samples</param> /// <param name="channels">The number of channels</param> /// <param name="sampleRate">The number of frames/second the device will render/capture</param> /// <param name="suggestedLatency">The latency the device will attempt to match</param> /// <param name="asOutput">Marks if the device should be opened as an input or an output</param> /// <returns>True if the device supports this format, false otherwise</returns> public unsafe bool SupportsFormat(PortAudioSampleFormat sampleFormat, int channels, double sampleRate, TimeSpan suggestedLatency, bool asOutput) { PaStreamParameters *inputParams = default; PaStreamParameters *outputParams = default; var param = new PaStreamParameters { DeviceIndex = DeviceIndex, ChannelCount = channels, HostApiSpecificStreamInfo = IntPtr.Zero, SampleFormats = sampleFormat.SampleFormat, SuggestedLatency = new PaTime(suggestedLatency) }; if (asOutput) { outputParams = ¶m; } else { inputParams = ¶m; } return(Native.PortAudio.Pa_IsFormatSupported(inputParams, outputParams, sampleRate) >= PaErrorCode.NoError); }
/// <summary> /// Create a PortAudioDevicePump for an input device /// Note: This MUST be disposed, or you risk breaking audio on the host until the next reboot /// </summary> /// <param name="device">The input device to create a pump for</param> /// <param name="channelCount">The number of channels to capture from the input device</param> /// <param name="sampleFormat">The sample format of the output PCM data</param> /// <param name="suggestedLatency">The latency the device will attempt to achieve</param> /// <param name="sampleRate">The sample rate of the output PCM data</param> /// <param name="callback">The callback that will be invoked when data is produced</param> /// <exception cref="ArgumentNullException">Thrown in the case that the device or callback are null</exception> public unsafe PortAudioDevicePump(PortAudioDevice device, int channelCount, PortAudioSampleFormat sampleFormat, TimeSpan suggestedLatency, double sampleRate, WriteDataCallback callback) { if (device == null) { throw new ArgumentNullException(nameof(device)); } if (callback == null) { throw new ArgumentNullException(nameof(callback)); } PortAudioLifetimeRegistry.Register(this); SampleFormat = sampleFormat; Channels = channelCount; SampleRate = sampleRate; SuggestedLatency = suggestedLatency; _handle = GCHandle.Alloc(this); _isOutput = false; _writeDataCallback = callback; var inputParams = new PaStreamParameters { ChannelCount = channelCount, DeviceIndex = device.DeviceIndex, HostApiSpecificStreamInfo = IntPtr.Zero, SampleFormats = sampleFormat.SampleFormat, SuggestedLatency = new PaTime(suggestedLatency) }; var err = Native.PortAudio.Pa_OpenStream(out _stream, &inputParams, null, sampleRate, FRAMES_TO_BUFFER, PaStreamFlags.NoFlag, StreamCallback, GCHandle.ToIntPtr(_handle)); if (err < PaErrorCode.NoError) { throw PortAudioException.GetException(err); } err = Native.PortAudio.Pa_SetStreamFinishedCallback(_stream, StreamFinishedCallback); if (err < PaErrorCode.NoError) { throw PortAudioException.GetException(err); } }
/// <summary> /// Create a PortAudioDevicePump for an output device /// Note: This MUST be disposed, or you risk breaking audio on the host until the next reboot /// </summary> /// <param name="device">The output device to create a pump for</param> /// <param name="channelCount">The number of channels in the input PCM data</param> /// <param name="sampleFormat">The sample format of the input PCM data</param> /// <param name="suggestedLatency">The latency the device will attempt to achieve</param> /// <param name="sampleRate">The sample rate of playback</param> /// <param name="callback">The callback that will supply data to the output device</param> /// <exception cref="ArgumentNullException">Thrown in the case that the device or callback are null</exception> public unsafe PortAudioDevicePump(PortAudioDevice device, int channelCount, PortAudioSampleFormat sampleFormat, TimeSpan suggestedLatency, double sampleRate, ReadDataCallback callback) { if (device == null) { throw new ArgumentNullException(nameof(device)); } if (callback == null) { throw new ArgumentNullException(nameof(callback)); } PortAudioLifetimeRegistry.Register(this); SampleFormat = sampleFormat; Channels = channelCount; SampleRate = sampleRate; SuggestedLatency = suggestedLatency; _handle = GCHandle.Alloc(this); _isOutput = true; _readDataCallback = callback; var outputParams = new PaStreamParameters { ChannelCount = channelCount, DeviceIndex = device.DeviceIndex, HostApiSpecificStreamInfo = IntPtr.Zero, SampleFormats = sampleFormat.SampleFormat, SuggestedLatency = new PaTime(suggestedLatency) }; var err = Native.PortAudio.Pa_OpenStream(out _stream, null, &outputParams, sampleRate, FRAMES_TO_BUFFER, PaStreamFlags.NoFlag, StreamCallback, GCHandle.ToIntPtr(_handle)); if (err < PaErrorCode.NoError) { throw PortAudioException.GetException(err); } err = Native.PortAudio.Pa_SetStreamFinishedCallback(_stream, StreamFinishedCallback); if (err < PaErrorCode.NoError) { throw PortAudioException.GetException(err); } _dataQueue = new ConcurrentQueue <BufferContainer>(); _bufferPool = new ConcurrentBag <BufferContainer>(); for (var i = 0; i < BUFFER_CHAIN_LENGTH; i++) { var buffer = new byte[FRAMES_TO_BUFFER * (ulong)channelCount * (ulong)sampleFormat.FormatSize]; var readLength = WriteAudioFrame(buffer, buffer.Length); _dataQueue.Enqueue(new BufferContainer(buffer) { ReadLength = readLength }); } _queueCount = new SemaphoreSlim(BUFFER_CHAIN_LENGTH); _poolCount = new SemaphoreSlim(0); _threadEndEvent = new ManualResetEventSlim(false); _processingThreadCancel = new CancellationTokenSource(); Task.Run(() => DataTask(_processingThreadCancel.Token)); }