Код по TCP не работает.В чем моя проблема с потоковым аудио? - PullRequest
0 голосов
/ 07 октября 2018

Я использую LiveAudioPlayer для потоковой передачи аудио через TCP с Indy.Это код клиента:

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 голосов
/ 07 октября 2018

Код, который у вас есть в IdTCPServer1Execute(), действительно принадлежит вместо LiveAudioRecorderData(), где данные.

Хотя я настоятельно предлагаю НИКОГДА вещаниеДанные для нескольких клиентов TCP, используя цикл IOHandler.Write(), как вы делаете.Это замедляет использование полосы пропускания до 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, чтобы вместо этого вы могли использовать широковещательные или многоадресные рассылки.Это позволило бы коду вашего сервера отправлять только 1 пакет на блок аудиоданных на специальный широковещательный / многоадресный IP-адрес, и этот пакет автоматически доставлялся бы сетью каждому клиенту, который заинтересован в этом, а не доставлял вас сам.

...