Reputation: 1
I use LiveAudioPlayer to stream audio over TCP with Indy. This is the code of the client:
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;
On the server side, I use 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;
But I need some code on the server side here to work:
procedure TForm2.LiveAudioRecorderData(Sender: TObject;
const Buffer: Pointer;
BufferSize: Cardinal; var FreeIt: Boolean);
var
I: Integer;
Clients : TList;
begin
FreeIt := True;
//** here
end;
How do I change from WinSock to Indy TCP to stream sound?
Upvotes: 0
Views: 148
Reputation: 597036
The code you currently have in IdTCPServer1Execute()
really belongs in LiveAudioRecorderData()
instead, where the data is.
Though, I strongly suggest NEVER broadcasting data to multiple TCP clients using an IOHandler.Write()
loop, like you are doing. It slows down your bandwidth usage to just 1 packet at a time, there is no parallel processing at all. While you are sending a packet to 1 client, all of the other clients are blocked waiting to send their own packets.
A better solution is to give each client its own thread-safe queue for outbound data, then LiveAudioRecorderData()
can push a copy of the data into each client's queue as needed, and IdTCPServer1Execute()
can send the calling client's queue independently of what other clients are doing.
For example:
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;
On the other hand, TCP is a really poor choice for broadcasting live media to multiple clients. You really should use UDP instead so that you can utilize subnet broadcasts or multicasts instead. That would allow your server code to send only 1 packet per block of audio data to a special broadcast/multicast IP address, and have that packet automatically delivered by the network to every client that is interested in it, rather then you doing the delivery yourself.
Upvotes: 1