Delphi - Android audio recording and streaming
I am trying to record audio from one Android device (Server) and send to another Android device (Client) to play it live, I am using indy HTTP server to send the stream to the client, I am calling Android API to record audio using AudioRecord and play it on the other side using AudioTrack. Both codes are below.
I get audio from the recorder and play, the problem is that the audio being played is not continuous, it splits every played buffer, like it does almost half the time, for example if I send the 1st sample it seems to be half played. I have already checked the buffer size, the same buffer size I read from AudioRecord is the same size that is played back in AudioTrack. I don't know if the latency is from indy server / client. I've tried many combinations of buffer size, sample rate, audio formats, etc., but the sound still breaks.
Any help would be appreciated.
Here is the server code
unit ServerAudio5;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
System.IOUtils,
System.Diagnostics,
FMX.Platform.Android,
Androidapi.JNI.Net,
Androidapi.JNIBridge,
Androidapi.JNI.JavaTypes,
Androidapi.JNI.GraphicsContentViewText,
Androidapi.JNI.Media, IdContext, IdCustomHTTPServer, IdBaseComponent,
IdComponent, IdCustomTCPServer, IdHTTPServer, FMX.StdCtrls,
FMX.Controls.Presentation, FMX.ScrollBox, FMX.Memo;
type
TForm1 = class(TForm)
Memo1: TMemo;
btnConnect: TButton;
btnDisconnect: TButton;
IdHTTPServer1: TIdHTTPServer;
Timer1: TTimer;
Timer2: TTimer;
procedure FormCreate(Sender: TObject);
procedure IdHTTPServer1CommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
procedure IdHTTPServer1Connect(AContext: TIdContext);
procedure IdHTTPServer1Disconnect(AContext: TIdContext);
procedure Timer1Timer(Sender: TObject);
procedure btnConnectClick(Sender: TObject);
procedure btnDisconnectClick(Sender: TObject);
procedure Timer2Timer(Sender: TObject);
private
{ Private declarations }
Enable_Server: Boolean;
Enable_Audio: Boolean;
AudioRecorder: JAudioRecord;
sampleRate: Integer;
channelConfig: Integer;
audioFormat: Integer;
minBufSize: Integer;
AudioMem: TMemoryStream;
AudioStr: TJavaArray<SmallInt>;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
{$R *.NmXhdpiPh.fmx ANDROID}
procedure TForm1.btnConnectClick(Sender: TObject);
begin
// Start server
if Enable_Server = False then
begin
Enable_Server:= True;
IdHTTPServer1.Active := True;
Memo1.Lines.Add('Server Started');
end;
end;
procedure TForm1.btnDisconnectClick(Sender: TObject);
begin
if Enable_Server = True then
begin
Enable_Server:= False;
IdHTTPServer1.Active := False;
Memo1.Lines.Add('Server Finished');
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Enable_Server:= False;
Enable_Audio:= False;
Timer1.Enabled:= False;
sampleRate:= 11025;
channelConfig:= TJAudioFormat.JavaClass.CHANNEL_IN_MONO;
audioFormat:= TJAudioFormat.JavaClass.ENCODING_PCM_16BIT;
// minBufSize = 1024 Bytes
minBufSize:= TJAudioRecord.JavaClass.getMinBufferSize(sampleRate, channelConfig, audioFormat);
// AudioRecover = 1024*4 = 4096
AudioStr:= TJavaArray<SmallInt>.Create(minBufSize*4);
AudioMem:= TMemoryStream.Create;
AudioRecorder:= TJAudioRecord.JavaClass.init(TJMediaRecorder_AudioSource.JavaClass.MIC,
sampleRate,
channelConfig,
audioFormat,
minBufSize*4);
end;
procedure TForm1.IdHTTPServer1CommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
if ARequestInfo.Document = '/audiorecover' then
begin
AResponseInfo.ResponseNo := 200;
AResponseInfo.ContentType := 'audiorecover';
AResponseInfo.CloseConnection := False;
AResponseInfo.WriteHeader;
// Enable the Audio
Enable_Audio:= True;
end;
end;
procedure TForm1.IdHTTPServer1Connect(AContext: TIdContext);
begin
Memo1.Lines.Add('Connection is make with: '+AContext.Connection.Socket.Binding.PeerIP);
Timer1.Enabled:= True;
end;
procedure TForm1.IdHTTPServer1Disconnect(AContext: TIdContext);
begin
Memo1.Lines.Add('Disconnection is make with: '+AContext.Connection.Socket.Binding.PeerIP);
Enable_Audio:= False;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
index: Integer;
begin
if Enable_Audio then
begin
Memo1.Lines.Add('Start Recording');
Timer1.Enabled:= False;
(AudioRecorder as JAudioRecord).startRecording;
Timer2.Enabled:= True;
end;
end;
procedure TForm1.Timer2Timer(Sender: TObject);
var
index: Integer;
NewCount: Integer;
begin
while True do
begin
if Enable_Audio then
begin
// Read from the AudioRecover
NewCount:= (AudioRecorder as JAudioRecord).read(AudioStr, 0,AudioStr.Length);
// The read command does not read 4096, just 2048
AudioMem.Write(AudioStr.Data^, NewCount); // NewCount = 2048 Bytes
AudioMem.Position:= 0;
//Send the stream using socket
with IdHTTPServer1.Contexts.LockList do
try
for index := 0 to Count-1 do
begin
TIdContext( Items[index] ).Connection.IOHandler.WriteLn('audiorecover');
TIdContext( Items[index] ).Connection.IOHandler.WriteLn(IntToStr(AudioMem.Size));
TIdContext( Items[index] ).Connection.IOHandler.Write(AudioMem);
end;
finally
IdHTTPServer1.Contexts.UnlockList;
end;
AudioMem.Clear;
end;
end;
end
end.
Here is the client code
unit ClientAudio4;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
System.Diagnostics,
FMX.Platform.Android,
Androidapi.JNI.Net,
Androidapi.JNIBridge,
Androidapi.JNI.JavaTypes,
Androidapi.JNI.GraphicsContentViewText,
Androidapi.JNI.Media, FMX.Edit, IdBaseComponent, IdComponent, IdTCPConnection,
IdTCPClient, FMX.ScrollBox, FMX.Memo, FMX.Controls.Presentation, FMX.StdCtrls;
type
TForm1 = class(TForm)
Connect: TButton;
Disconnect: TButton;
Memo1: TMemo;
IdTCPClient1: TIdTCPClient;
Timer1: TTimer;
Host: TEdit;
Timer2: TTimer;
procedure ConnectClick(Sender: TObject);
procedure DisconnectClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure IdTCPClient1Connected(Sender: TObject);
procedure IdTCPClient1Disconnected(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure Timer2Timer(Sender: TObject);
private
{ Private declarations }
Enable_Play: Boolean;
AudioPlay: JAudioTrack;
AudioStream: TJavaArray<SmallInt>;
trackmin: Integer;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
{$R *.NmXhdpiPh.fmx ANDROID}
procedure TForm1.ConnectClick(Sender: TObject);
begin
IdTCPClient1.Host:= Host.Text;
IdTCPClient1.Connect;
end;
procedure TForm1.DisconnectClick(Sender: TObject);
begin
IdTCPClient1.Disconnect;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Host.Text:= '192.168.0.100';
trackmin:= TJAudioTrack.JavaClass.getMinBufferSize(11025,
TJAudioFormat.JavaClass.CHANNEL_OUT_MONO,
TJAudioFormat.JavaClass.ENCODING_PCM_16BIT);
AudioStream:= TJavaArray<SmallInt>.Create(trackmin);
AudioPlay:= TJAudioTrack.JavaClass.init(TJAudioManager.JavaClass.STREAM_MUSIC,
11025,
TJAudioFormat.JavaClass.CHANNEL_OUT_MONO,
TJAudioFormat.JavaClass.ENCODING_PCM_16BIT,
trackmin,
TJAudioTrack.JavaClass.MODE_STREAM);
end;
procedure TForm1.IdTCPClient1Connected(Sender: TObject);
begin
Memo1.Lines.Add('Connection to: '+IdTCPClient1.Host);
Timer1.Enabled:= True;
end;
procedure TForm1.IdTCPClient1Disconnected(Sender: TObject);
begin
Memo1.Lines.Add('Disconnect to: '+IdTCPClient1.Host);
Enable_Play:= False;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
trackmin: Integer;
begin
Timer1.Enabled:= False;
IdTCPClient1.IOHandler.WriteLn('GET '+'/audiorecover'+' HTTP/1.0');
IdTCPClient1.IOHandler.WriteLn;
Enable_Play:= True;
// Start play
(AudioPlay as JAudioTrack).play();
Timer2.Enabled:= True;
end;
procedure TForm1.Timer2Timer(Sender: TObject);
var
S3: String;
S4: String;
CLength: Integer;
AudioRec: TMemoryStream;
begin
while True do
begin
if Enable_Play then
begin
try
// Try to get the header
S3:= Trim(LowerCase(IdTCPClient1.IOHandler.ReadLn));
if S3 = 'audiorecover' then
begin
AudioRec:= TMemoryStream.Create;
// Read the length of the memorystream
S4:= LowerCase(IdTCPClient1.IOHandler.ReadLn);
CLength:= StrToInt(S4);
// Read the stream
IdTCPClient1.IOHandler.ReadStream(AudioRec, CLength);
AudioRec.Position:= 0;
// Set the array
AudioRec.Read(AudioStream.Data^, CLength);
// Write to the AudioTrack to play the video
(AudioPlay as JAudioTrack).write(AudioStream, 0, CLength);
AudioRec.Clear;
AudioRec.Free;
end;
finally
end;
end;
end;
end;
end.
source to share
No one has answered this question yet
Check out similar questions: