有人试过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
比其他人更准确。可以在这做什么?
答案 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
的实例。