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 スレッド以下) に遭遇したことがわかりました。timeSetEvent の代わりに、CreateTimerQueue/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に基づいてより高度な実現を試みました:QueryPerformanceFrequencyQueryPerformanceCounter

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;

これらの考えられるすべての解決策には同じ問題があります。ボイス フローが途切れることなく、特に 2 つ以上の通話がある場合、再生中にクリック音がはっきりと聞こえます。私が想像できる唯一の理由は、それtimeSetEventが他のものよりも正確であるということです. ここで何ができるでしょうか?

4

1 に答える 1

0

タイマー数の制限を正確に特定したことを考えると、その制限内に収まるように小さな設計変更を行う必要があるようです。各タイマーは現在、procedure Timer20ms呼び出されたときにごくわずかな量の作業を行います。したがって、単一のタイマーで複数のイベントを設定できるようにすることは実現可能と思われます。

最初のパスとして、1 つのタイマーのみを使用してすべてのイベントを設定してみます。
多数の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各タイマー間でインスタンスを合理的に公平に分散させることができた場合。の16 xsimultaneous-signal-limitインスタンスの処理に近づくことができる場合がありますTMicrophoneThread

于 2013-09-05T18:51:32.320 に答える