TCP上的代码不起作用。我的直播音频流有什么问题?

时间:2018-10-07 07:20:31

标签: delphi tcp indy10

我使用LiveAudioPlayer使用Indy通过TCP流音频。这是客户端的代码:

function TForm1.LiveAudioPlayerDataPtr(Sender: TObject;
 var Buffer: Pointer; var NumLoops: DWORD;
 var FreeIt: Boolean): DWORD;
var
  buf:TIdBytes;
begin
  if not IdTCPClient1.Connected then
    Result := 0    // Stops LiveAudioPlayer
  else
    IdTCPClient1.Socket.ReadBytes(buf, sizeof(buf), TRUE);

  BytesToRaw(buf, WaveFormat, sizeof(WaveFormat));

  if AudioBuffer.Get(Buffer, Result) then
    FreeIt := True
  else
  begin
    Buffer := nil; // When Buffer is nil,
    Result := 10   // Result will be considered as silence milliseconds.
  end
end;

在服务器端,我使用LiveAudioRecorder:

procedure TForm2.IdTCPServer1Execute(AContext: TIdContext);
type
  TWaveFormatInfo = packed record
    WaveFormatSize: Integer;
    WaveFormat: TWaveFormatEx;
  end;
var
  WFI: TWaveFormatInfo;
  Buf: TIdBytes;
  Clients : TList;
  i: integer;
begin
  SetPCMAudioFormatS(@WFI.WaveFormat, LiveAudioRecorder.PCMFormat);
  WFI.WaveFormatSize := SizeOf(WFI.WaveFormat);
  Buf := RawToBytes(WFI, SizeOf(WFI));
  Clients := IdTCPServer1.Contexts.LockList;
  try
    for i := 0 to Clients.Count-1 do
    try
      TIdContext(Clients[i]).Connection.IOHandler.Write(buf);
    except
    end;
  finally
    IdTCPServer1.Contexts.UnlockList;
  end;

但是我需要在服务器端运行一些代码:

procedure TForm2.LiveAudioRecorderData(Sender: TObject;
  const Buffer: Pointer;
   BufferSize: Cardinal; var FreeIt: Boolean);
var
  I: Integer;
  Clients : TList;
begin
   FreeIt := True;
   //** here
end;

如何从WinSock更改为Indy TCP以流式传输声音?

1 个答案:

答案 0 :(得分:1)

您当前在IdTCPServer1Execute()中拥有的代码实际上属于数据所在的LiveAudioRecorderData()

不过,我强烈建议从不使用IOHandler.Write()循环向多个TCP客户端广播数据,就像您所做的那样。一次将您的带宽使用速度降低到仅1个数据包,根本没有并行处理。在向1个客户端发送数据包时,所有其他客户端都被阻止,等待发送自己的数据包。

更好的解决方案是为每个客户端提供自己的线程安全队列以用于出站数据,然后LiveAudioRecorderData()可以根据需要将数据的副本推送到每个客户端的队列中,而IdTCPServer1Execute()可以发送独立于其他客户端正在执行的操作来调用客户端的队列。

例如:

type
  TWaveFormatInfo = packed record
    WaveFormatSize: Integer;
    WaveFormat: TWaveFormatEx;
  end;

  TMyContext = class(TIdServerContext)
  private
    // on modern Delphi versions, consider using
    // TThreadList<TIdBytes> instead...
    Queue: TThreadList;
    QueueHasData: Boolean;
  public    
    constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdThreadList = nil); override;
    destructor Destroy; override;

    procedure AddToQueue(const WFI: TWaveFormatInfo; const Buffer: Pointer; BufferSize: Cardinal);
    procedure CheckQueue;
  end;

...

constructor TMyContext.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdThreadList = nil);
begin
  inherited;
  Queue := TThreadList.Create;
end;

destructor TMyContext.Destroy;
var
  List: TList;
  i: Integer;
begin
  // if not using TThreadList<TIdBytes>...
  if QueueHasData then
  begin
    List := Queue.LockList;
    try
      for i := 0 to List.Count-1 do
        TIdBytes(List[i]) := nil; // decrement the array's refcount
    finally
      Queue.UnlockList;
    end;
  end;
  // end if

  Queue.Free;
  inherited;
end;

procedure TMyContext.AddToQueue(const WFI: TWaveFormatInfo; const Buffer: Pointer; BufferSize: Cardinal);
begin
  Buf: TIdBytes;
  Offset: Integer;
  P: Pointer; // if not using TThreadList<TIdBytes>...
end;
  // each client needs its own local copy of the
  // input Buffer, so copy the data
  // and add it to the queue...
  Offset := Sizeof(Integer) + WFI.WaveFormatSize;
  SetLength(Buf, Offset + BufferSize);
  Move(WFI, Buf[0], Offset);
  if BufferSize > 0 then
    Move(Buffer^, Buf[Offset], BufferSize);

  // if using TThreadList<TIdBytes>...

  {with Queue.LockList do
  try
    Add(Buf);
    QueueHasData := True;
  finally
    Queue.UnlockList;
  end;}

  // else

  TIdBytes(P) := Buf;  // increment the array's refcount
  try
    with Queue.LockList do
    try
      Add(P);
      QueueHasData := True;
    finally
      Queue.UnlockList;
    end;
  except
    TIdBytes(P) := nil; // decrement the array's refcount
    raise;
  end;

  // end if

end;

procedure TMyContext.CheckQueue;
var
  List: TList;
  P: Pointer; // if not using TThreadList<TIdBytes>...
begin
  if QueueHasData then
  begin
    List := Queue.LockList;
    try
      while List.Count > 0 do
      begin
        // if using TThreadList<TIdBytes>... 

        {Connection.IOHandler.Write(List[0]);
        List.Delete(0);} 

        // else

        P := List[0];
        List.Delete(0);
        try
          Connection.IOHandler.Write(TIdBytes(P));
        finally
          TIdBytes(P) := nil; // decrement the array's refcount
        end;

        // end if
      end;
    finally
      QueueHasData := List.Count > 0;
      Queue.UnlockList;
    end;
  end;
end;

private
  WFI: TWaveFormatInfo;

...

procedure TForm2.FormCreate(Sender: TObject);
begin
  SetPCMAudioFormatS(@WFI.WaveFormat, LiveAudioRecorder.PCMFormat);
  WFI.WaveFormatSize := SizeOf(WFI.WaveFormat);
  IdTCPServer1.ContextClass := TMyContext;
end;

procedure TForm2.IdTCPServer1Execute(AContext: TIdContext);
begin
  TMyContext(AContext).CheckQueue;  
  with AContext.Connection.IOHandler do
  begin
    if CheckForDataOnSource(0) then
      InputBuffer.Clear;
    CheckForDisconnect;
  end;
end;

procedure TForm2.LiveAudioRecorderData(Sender: TObject; const Buffer: Pointer; BufferSize: Cardinal; var FreeIt: Boolean);
var
  Clients : TList;
  i: integer;
begin
  FreeIt := True;
  Clients := IdTCPServer1.Contexts.LockList;
  try
    for i := 0 to Clients.Count-1 do
      TMyContext(TIdContext(Clients[i])).AddToQueue(WFI, Buffer, BufferSize);
  finally
    IdTCPServer1.Contexts.UnlockList;
  end;
end;

另一方面,TCP对于将实时媒体广播到多个客户端来说确实是一个糟糕的选择。您确实应该改用UDP,以便可以改用子网广播或多播。这样一来,您的服务器代码就可以仅将一个音频数据块的一个数据包发送到一个特殊的广播/多播IP地址,并通过网络自动将该数据包传递给每个对此感兴趣的客户端,而无需您自己进行传递