如何在不丢失功能的情况下替换timeSetEvent函数?

时间:2013-09-05 13:19:02

标签: delphi timer sip rtp

有人试过SIP Delphi component吗?我前一段时间以合理的价格购买了它,以替换为Dialogic HMP编写的旧代码。看起来邮件支持并不隐含,文档和帮助都没有,但是可用的代码我不会有麻烦。直到现在,当我遇到无法找到解决方案的问题时,它们才出现。 呼叫期间的库每隔20 ms通过UDP发送一个小的RTP数据包,为了保持这些间隔相等,它使用winsdk函数timeSetEvent。以下是代码的摘录(我对其进行了简化以使事情更加清晰):

Interface
type
// RTP packet header
TRTPHeader = packed record
   Byte80: Byte;
   PayloadType: Byte;
   SeqNo: WORD;
   TimeStamp: DWORD;
   SSRC: DWORD;
end; 

//RTP packet structure
TRTP = packed record
  H: TRTPHeader;
  Payload: packed array [0 .. 1023] of Byte;
end; 

//class realisation of ISipCall interface
TCall = class(TInterfacedObject, ISipCall)
  FRtpPacketToSend:TRTP;//RTP packet
//callback function, it is invoked by TMicrophoneThread regularly
  procedure OnMicrophone(const Buffer: Pointer);
end;

//Thread class for timing purposes
TMicrophoneThread = class(TThread)
public
  FCall: TCall;//call associated with this thread
  FEvent: THandle;// Event handle
  FTimerHandle: THandle;// Timer handle
  procedure Execute; override;
  constructor Create(const ACall: TCall);
  destructor Destroy; override;
end; 

implementation

procedure TCall.OnMicrophone(const Buffer: Pointer); //callback function, it is invoked by TMicrophoneThread regularly
var socket: TSocket;
begin
//preparing FRtpPacketToSend data, initializing socket, Remote server address
//win32 function, sends data to the “Remote” server
  sendto(socket, FRtpPacketToSend, sizeof(FRtpPacketToSend), 0, @Remote, SizeOf(Remote));
end;

//callback function invoked by windows timer every 20 ms
procedure Timer20ms(uTimerID, uMessage: UINT; dwUser, dw1, dw2: DWORD_PTR); stdcall; 
begin
  SetEvent(TMicrophoneThread(dwUser).FEvent);//Sets the TMicrophoneThread event
end;

constructor TMicrophoneThread.Create(ACall: TCall);
begin
  inherited;
  FCall:=ACall;
  FEvent := CreateEvent(nil, False, False, nil);
//Setting timer
  FTimerHandle := timeSetEvent(20, 0, @Timer20ms, Cardinal(Self), TIME_CALLBACK_FUNCTION + TIME_PERIODIC);
end;

destructor TMicrophoneThread.Destroy;
begin
  timeKillEvent(FTimerHandle);//removing timer
  CloseHandle(FEvent);
  inherited;
end;

procedure TMicrophoneThread.Execute;
var
  buf: array [0 .. 159] of SmallInt;//buffer data, looks like for storing data between function calls
begin
  FillChar(buf, SizeOf(buf), 0);
  Repeat
//waiting for the timer to set FEvent from Timer20ms function
    if (WaitForSingleObject(FEvent, INFINITE) <> WAIT_TIMEOUT) and not Terminated then
    begin
      if not Terminated then
        try
          FCall.OnMicrophone(@buf);
        except
        end;
    end;
  until Terminated;
end;

//Using these classes:
// Sip call object
Call:=TCall.Create;
// TMicrophoneThread object creates timer and every 20 ms invokes OnMicrophone function to send UDP data in realtime
Mth= TMicrophoneThread.Create(Call);

此代码工作正常,语音数据流畅。但令我惊讶的是,它可以完美地工作,直到同时呼叫的数量超过16,第17和其他呼叫不接收定时器信号。我发现这个函数已被标记为过时,有些人遇到了与此函数相同的未记录限制 - 不超过16个线程。 我尝试使用CreateTimerQueue / CreateTimerQueueTimer使用不同的参数代替timeSetEvent:

implementation
var
  TimerQueue: THandle;
....
procedure WaitOrTimerCallback(lpParameter: Pointer; TimerOrWaitFired: BOOL); stdcall;
begin
  SetEvent(TMicrophoneThread(lpParameter).FEvent);
end;

constructor TMicrophoneThread.Create(ACall: TCall);
begin
  inherited;
  FCall:=ACall;
  FEvent := CreateEvent(nil, False, False, nil);
  //Setting timer
  CreateTimerQueueTimer(FTimerHandle, TimerQueue, @WaitOrTimerCallback, Self, 0, 20, 0);
end;
...
initialization
TimerQueue := CreateTimerQueue;

我还尝试Sleep及其基于QueryPerformanceFrequency / QueryPerformanceCounter的更高级实现:

procedure TMicrophoneThread.Execute;
var
  buf: array [0 .. 159] of SmallInt;
  waittime: integer;
begin
  FillChar(buf, SizeOf(buf), 0);
  repeat
    if not Terminated then
      try
        FCall.OnMicrophone(@buf);
        waittime:=round((Now - FCall.GetStartTime)*MSecsPerDay)
        if waittime<20 then
          Sleep(20-waittime)
      except
      end;
  until Terminated;
end;

所有这些可能的解决方案都存在同样的问题 - 语音流不再是连续的,并且您在播放过程中会明显地听到咔嗒声,特别是如果您有两个或更多呼叫。我能想象的唯一原因是timeSetEvent比其他人更准确。可以在这做什么?

1 个答案:

答案 0 :(得分:0)

鉴于您已经确定了计时器数量的限制,一个小的设计变更保持在该限制内似乎是有序的。调用procedure Timer20ms时,每个计时器当前的工作量可忽略不计。因此,允许单个计时器设置多个事件似乎是可行的。

作为第一遍,我会尝试只使用一个计时器来设置所有事件 我怀疑这将是一个解决方案,因为同时发送(恢复)大量TMicrophoneThread个实例不太可能不会引起其他问题。但是看看有多少可以顺利处理(让我们称之为simultaneous-signal-limit)会很有用;因为在你需要考虑扩展到更好/更多的硬件之前,这可能是确定硬限制的一个因素。

constructor TMicrophoneThread.Create(ACall: TCall);
begin
  inherited;
  FCall:=ACall;
  FEvent := CreateEvent(nil, False, False, nil);
  { Instead of setting a new timer, add the event to a list. }
  TimerEvents.Add(FEvent);
end;

destructor TMicrophoneThread.Destroy;
begin
  { Instead of removing the timer, remove the event }
  TimerEvents.Remove(FEvent);
  CloseHandle(FEvent);
  inherited;
end;

procedure Timer20ms(uTimerID, uMessage: UINT; dwUser, dw1, dw2: DWORD_PTR); stdcall; 
{ The timer callback sets all events in the list. }
var
  LTimers: TList;
begin
  { I'm illustrating this code where TimerEvents is implemented as a TThreadList.
    If you can ensure all access to the list happens from the same thread,
    you'll be able to do away with the locks - which would be better.  }
  LTimers := TThreadList(dwUser).LockList;
  try
    for LoopI := 0 to LTimers.Count - 1 do
      SetEvent(THandle(LTimers[LoopI]));
  finally
    TThreadList(dwUser).UnlockList;
  end;
end;

一旦完成此实验,您可以查看运行多个计时器。每个都有自己的清单。如果你错开了计时器,并设法在每个计时器上合理公平地分发TMicrophoneThread个实例;您可以接近处理{em> 16 x simultaneous-signal-limit TMicrophoneThread的实例。