1

有人试过SIP Delphi 组件吗?我前段时间以合理的价格购买了它,以替换为 Dialogic HMP 编写的旧代码。似乎没有暗示邮件支持,文档和帮助也不存在,尽管使用可用的代码我不会遇到麻烦。直到现在,当我陷入无法找到解决方案的问题时,它们才出现。在调用过程中,库每 20 毫秒通过 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/而不是 timeSetEvent :CreateTimerQueueTimer

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比其他人更准确。在这里可以做什么?

4

1 回答 1

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在每个计时器之间合理公平地分配实例;您可能能够接近处理16simultaneous-signal-limitTMicrophoneThread.

于 2013-09-05T18:51:32.320 回答