如何拆分和管道多个NAudio流

时间:2015-07-03 14:06:06

标签: c# stream naudio

我有一个C#项目,使用来自Kinect 1,Kinect 2,Microphone或其他任何内容的输入音频流。

waveIn.DataAvailable += (object sender, WaveInEventArgs e) => {
  lock(buffer){
    var pos = buffer.Position;
              buffer.Write(e.Buffer, 0, e.BytesRecorded);
              buffer.Position = pos;
  }
};

缓冲区变量是来自组件A的Stream,它将由处理Streams的SpeechRecognition组件B处理。

我将添加新的组件C,D,E,在Streams上工作以计算音高,检测声音,做指纹或其他任何事情......

如何为组件C,D,E复制该流?

  • 组件A发送一个事件“我有一个Stream做你想做的事情”我不想通过事件“给我你的你的溪流”来反转逻辑

  • 我正在寻找可以给我一个Stream实例并将处理这项工作的“MultiStream”

组件A

var MultiStream buffer = new MultiStream()
...
SendMyEventWith(buffer)

组件B,C,D,E

public void HandleMyEvent(MultiStream buffer){
  var stream = buffer.GetNewStream();
  var engine = new EngineComponentB()
      engine.SetStream(stream);
}
  • MultiStream必须是一个Stream来包装Write()方法(因为Stream没有数据可用的机制)?
  • 如果某个Stream是Dismbly(),那么MultiStream应该从它的数组中删除它吗?
  • MultiStream必须在Read()上抛出异常才能要求使用GetNewStream()
编辑:Kinect 1提供了一个Stream本身... :-(我应该使用一个Thread来泵入MultiStream吗?

有没有人拥有那种MultiStream Class?

由于

2 个答案:

答案 0 :(得分:1)

不知怎的,我认为溪流并不适合你想做的事情。您正在设置一种情况,即程序的长期运行将无缘无故地扩展数据要求。

我建议发布一个发布/订阅模型,将收到的音频数据发布给订阅者,最好使用多线程方法,以尽量减少不良订阅者的影响。可以找到一些想法here

我之前使用实现IObserver<byte[]>的处理器类完成了这项工作,并使用Queue<byte[]>来存储示例块,直到进程线程为它们做好准备。以下是基类:

public abstract class BufferedObserver<T> : IObserver<T>, IDisposable
{
    private object _lck = new object();

    private IDisposable _subscription = null;
    public bool Subscribed { get { return _subscription != null; } }

    private bool _completed = false;
    public bool Completed { get { return _completed; } }

    protected readonly Queue<T> _queue = new Queue<T>();

    protected bool DataAvailable { get { lock(_lck) { return _queue.Any(); } } }
    protected int AvailableCount { get { lock (_lck) { return _queue.Count; } } }

    protected BufferedObserver()
    {
    }

    protected BufferedObserver(IObservable<T> observable)
    {
        SubscribeTo(observable);
    }

    public virtual void Dispose()
    {
        if (_subscription != null)
        {
            _subscription.Dispose();
            _subscription = null;
        }
    }

    public void SubscribeTo(IObservable<T> observable)
    {
        if (_subscription != null)
            _subscription.Dispose();
        _subscription = observable.Subscribe(this);
        _completed = false;
    }

    public virtual void OnCompleted()
    {
        _completed = true;
    }

    public virtual void OnError(Exception error)
    { }

    public virtual void OnNext(T value)
    {
        lock (_lck)
            _queue.Enqueue(value);
    }

    protected bool GetNext(ref T buffer)
    {
        lock (_lck)
        {
            if (!_queue.Any())
                return false;
            buffer = _queue.Dequeue();
            return true;
        }
    }

    protected T NextOrDefault()
    {
        T buffer = default(T);
        GetNext(ref buffer);
        return buffer;
    }
}

public abstract class Processor<T> : BufferedObserver<T>
{
    private object _lck = new object();
    private Thread _thread = null;

    private object _cancel_lck = new object();
    private bool _cancel_requested = false;
    private bool CancelRequested
    {
        get { lock(_cancel_lck) return _cancel_requested; }
        set { lock(_cancel_lck) _cancel_requested = value; }
    }

    public bool Running { get { return _thread == null ? false : _thread.IsAlive; } }
    public bool Finished { get { return _thread == null ? false : !_thread.IsAlive; } }

    protected Processor(IObservable<T> observable)
        : base(observable)
    { }

    public override void Dispose()
    {
        if (_thread != null && _thread.IsAlive)
        {
            //CancelRequested = true;
            _thread.Join(5000);
        }
        base.Dispose();
    }

    public bool Start()
    {
        if (_thread != null)
            return false;

        _thread = new Thread(threadfunc);
        _thread.Start();
        return true;
    }

    private void threadfunc()
    {
        while (!CancelRequested && (!Completed || _queue.Any()))
        {
            if (DataAvailable)
            {
                T data = NextOrDefault();
                if (data != null && !data.Equals(default(T)))
                    ProcessData(data);
            }
            else
                Thread.Sleep(10);
        }
    }

    // implement this in a sub-class to process the blocks
    protected abstract void ProcessData(T data);
}

这样,只要您需要它就可以保留数据,并且可以根据需要将相同的可观察数据源附加到任意数量的进程线程。

为了完整起见,这是一个实现IObservable<T>的通用类,因此您可以看到它们如何组合在一起。这个甚至有评论:

/// <summary>Generic IObservable implementation</summary>
/// <typeparam name="T">Type of messages being observed</typeparam>
public class Observable<T> : IObservable<T>
{
    /// <summary>Subscription class to manage unsubscription of observers.</summary>
    private class Subscription : IDisposable
    {
        /// <summary>Observer list that this subscription relates to</summary>
        public readonly ConcurrentBag<IObserver<T>> _observers;

        /// <summary>Observer to manage</summary>
        public readonly IObserver<T> _observer;

        /// <summary>Initialize subscription</summary>
        /// <param name="observers">List of subscribed observers to unsubscribe from</param>
        /// <param name="observer">Observer to manage</param>
        public Subscription(ConcurrentBag<IObserver<T>> observers, IObserver<T> observer)
        {
            _observers = observers;
            _observer = observer;
        }

        /// <summary>On disposal remove the subscriber from the subscription list</summary>
        public void Dispose()
        {
            IObserver<T> observer;
            if (_observers != null && _observers.Contains(_observer))
                _observers.TryTake(out observer);
        }
    }

    // list of subscribed observers
    private readonly ConcurrentBag<IObserver<T>> _observers = new ConcurrentBag<IObserver<T>>();

    /// <summary>Subscribe an observer to this observable</summary>
    /// <param name="observer">Observer instance to subscribe</param>
    /// <returns>A subscription object that unsubscribes on destruction</returns>
    /// <remarks>Always returns a subscription.  Ensure that previous subscriptions are disposed
    /// before re-subscribing.</remarks>
    public IDisposable Subscribe(IObserver<T> observer)
    {
        // only add observer if it doesn't already exist:
        if (!_observers.Contains(observer))
            _observers.Add(observer);

        // ...but always return a new subscription.
        return new Subscription(_observers, observer);
    }

    // delegate type for threaded invocation of IObserver.OnNext method
    private delegate void delNext(T value);

    /// <summary>Send <paramref name="data"/> to the OnNext methods of each subscriber</summary>
    /// <param name="data">Data object to send to subscribers</param>
    /// <remarks>Uses delegate.BeginInvoke to send out notifications asynchronously.</remarks>
    public void Notify(T data)
    {
        foreach (var observer in _observers)
        {
            delNext handler = observer.OnNext;
            handler.BeginInvoke(data, null, null);
        }
    }

    // delegate type for asynchronous invocation of IObserver.OnComplete method
    private delegate void delComplete();

    /// <summary>Notify all subscribers that the observable has completed</summary>
    /// <remarks>Uses delegate.BeginInvoke to send out notifications asynchronously.</remarks>
    public void NotifyComplete()
    {
        foreach (var observer in _observers)
        {
            delComplete handler = observer.OnCompleted;
            handler.BeginInvoke(null, null);
        }
    }
}

现在,您可以创建Observable<byte[]>作为感兴趣的Process<byte[]>个实例的发送方。从输入流,音频阅读器等中提取数据块,并将它们传递给Notify方法。只需确保事先克隆数组......

答案 1 :(得分:1)

我不确定这是否是最好的方式,或者它比以前的答案更好,而且我不保证这段代码是完美的,但我编写了一些字面意思是你所要求的东西,因为这很有趣 - 一个MultiStream课程。

您可以在此处找到该课程的代码:http://pastie.org/10289142

用法示例:

MultiStream ms = new MultiStream();

Stream copy1 = ms.CloneStream();
ms.Read( ... );

Stream copy2 = ms.CloneStream();
ms.Read( ... );
运行示例后,

copy1copy2将包含相同的数据,并且在写入MultiStream时它们将继续更新。您可以单独读取,更新位置和处理克隆的流。如果处置,克隆的流将从MultiStream中删除,并且处置Multistream将关闭所有相关和克隆的流(如果不是您想要的行为,则可以更改此流)。尝试写入克隆的流将抛出一个不受支持的异常。