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.

      

+3


source to share





All Articles