我遇到的问题是收到的音频数据会播放结果,所以我提出了在开始播放之前稍微延迟的想法,这样应用程序在开始播放之前就有更多时间收集音频部分。这个想法是它将播放所收集的音频,因为其余部分会在需要之前进入应用程序。
//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循环之间存在竞争。有时它会在收到所有数据之前完成。
我不太确定如何在我的代码中解决这两个问题,并感谢您给我的任何帮助。
答案 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);
}
}
}
将此视为最简单的解决方案。我并不是说这是制造生产级多线程代码所需的所有螺母和螺栓......