播放前延迟音频

时间:2014-02-16 10:26:26

标签: c#

我遇到的问题是收到的音频数据会播放结果,所以我提出了在开始播放之前稍微延迟的想法,这样应用程序在开始播放之前就有更多时间收集音频部分。这个想法是它将播放所收集的音频,因为其余部分会在需要之前进入应用程序。

//A raised event from a udp class when data is received (from it's own thread)
void udpClient_DataReceived(byte[] bytes) 
{
    audioQueue.Enqueue (bytes); //ConcurrentQueue

    if (audioQueue.Count > 10 && !playing) { //count > 10 is about a one second delay
        playing = true;
        PlayQueue ();
    }
}

private void PlayQueue()
{
    byte[] a;
    while (audioQueue.Count > 0) {
        audioQueue.TryDequeue (out a);
        audIn.PlayAudio (a);
    }
    playing = false;
}

但是代码有两个问题:

1)如果音频长度短于设定的限制,则在收集更多音频之前不会播放。所以我需要某种延迟,不需要最少量的数据来触发它。

2)有时最后几节被遗漏并留在队列中进行下一场比赛。进入的数据和PlayQueue功能结束的while循环之间存在竞争。有时它会在收到所有数据之前完成。

我不太确定如何在我的代码中解决这两个问题,并感谢您给我的任何帮助。

2 个答案:

答案 0 :(得分:3)

如果您不知道如何使用ManualResetEvent或AutoResetEvent等事件正常工作,您可能会发现自己淹没了多余的代码,只能尝试同步Set&等等,甚至有时甚至添加其他事件。

通常在消费者/生产者问题的情况下,我倾向于使用Interlocked类,它具有惊人的原子功能。 它更干净,没有死锁的风险。

每次队列为空时,此解决方案将缓冲1秒。

    bool play = true; // global keep running flag.
    int m_numberOfSamples = 0;

    // Call this ONCE, at the start of your app.
    void Init()
    {
        // perform any initialization here... maybe allocate queue..
    }

    void EnqueueSample(byte[] bytes)
    {
        audioQueue.Enqueue(bytes); //ConcurrentQueue

        int numberOfSamples = Interlocked.Increment(ref m_numberOfSamples);
        if(numberOfSamples == 1)
        {
            // this is a case of first sample
            // Start a Task with a 1 sec delay
            Task.Factory.StartNew(() =>
                {
                    // Buffering...
                    // if you want to buffer x samples, use 1000*x/SampleRate instead of 1000 for 1 sec.
                    Task.Delay(1000).Wait();
                    PlayQueue();
                }, TaskCreationOptions.LongRunning);
        }
    }

    private void PlayQueue()
    {
        // if we are here, there is at least 1 sample already.
        byte[] a;
        int remainingSamples = 0;
        do
        {
            if (audioQueue.TryDequeue(out a))  // check if we succesfull got an array
            {
                audIn.PlayAudio(a);
                remainingSamples = Interlocked.Decrement(ref m_numberOfSamples);
            }
        }
        while (play && remainingSamples > 0);
        // we got out either by play = false or remainingSamples == 0
        // if remainingSamples == 0, we will get back here with a different **Task**
        // after a new sample has entered into the queue and again we buffer 1 sec using the Task.Delay
    }

答案 1 :(得分:2)

每次新数据包到达时,udpclient都会引发datareceived事件。由于对共享状态(ConcurrentQueue和播放)的访问不是序列化的,因此最终可能会有两个或多个线程在PlayQueue中运行代码,或者当队列变空时,您的播放线程会停止,而静态数据可能会从udpclient到达。另一个问题是你没有检查结果TryDequeue。

我首先要有一个且只有一个线程来读取你的ConcurrentQueue并使用EventwaitHandle派生类型来做交叉线程信号。

你的代码就是这样:

var mre = new ManualResetEvent(false);
Thread audio;
bool play = true; // global keep running flag.

// Call this ONCE, at the start of your app.
void Init()
{
    audio  = new Thread(PlayQueue);
    audio.Start();
}

void udpClient_DataReceived(byte[] bytes) 
{
    audioQueue.Enqueue (bytes); //ConcurrentQueue

    mre.Set(); // this signals that we have data

    // disbale the timer if the stream is done
    // and/or set play to false
    // based on bytes received in your byte array
}

private void PlayQueue()
{
    var aTimer = new System.Timers.Timer(1000); // play after 1 second
    var timeEvent = new ManualResetEvent(false);
    aTimer.Elapsed += (s,e) => { timeEvent.Set(); };  // the time will start the play
    byte[] a;
    mre.WaitOne();  // wait until there is data
    atimer.Enabled = true; // it will start playimg after 1000 miliseconds
    timeEvent.WaitOne(); // wait for the timer

    while (play) {
        if (audioQueue.TryDequeue (out a))  // check if we succesfull got an array
        {
           audIn.PlayAudio (a);
        }
    }
}

将此视为最简单的解决方案。我并不是说这是制造生产级多线程代码所需的所有螺母和螺栓......