Android Question AudioStreamer and buffer size

Addo

Well-Known Member
Licensed User
i am using audio streamer to record and audio from microphone

thats how i initialize the audio streamer

B4X:
audioStream.Initialize2(1, "AudioStream", 8000, True, 16, audioStream.VOLUME_MUSIC)

B4X:
Sub AudioStream_RecordBuffer (Data() As Byte)

Dim Packet As UDPPacket

ToastMessageShow(Data.Length, False)
End Sub

on some phones the data.length is too big gives 2048 and some phones its 640

i wanted to low that big length 2048

like dividing the big number by 4

i have been doing this in other ide like following

B4X:
BUFFSIZE := TJAudioRecord.JavaClass.getMinBufferSize(8000,
TJAudioFormat.JavaClass.CHANNEL_IN_MONO,
TJAudioFormat.JavaClass.ENCODING_PCM_16BIT);
stream := TJavaArray<byte>.Create(BUFFSIZE div 4);

AudioRecorder := TJAudioRecord.JavaClass.init
(TJMediaRecorder_AudioSource.JavaClass.MIC, 8000,
TJAudioFormat.JavaClass.CHANNEL_IN_MONO,
TJAudioFormat.JavaClass.ENCODING_PCM_16BIT, BUFFSIZE);

(AudioRecorder as JAudioRecord).startRecording;

so stream 2048 is divided by 4 should give 512 i couldn't figure how to do the same in b4a
 

Addo

Well-Known Member
Licensed User
i have tried

B4X:
Sub AudioStream_RecordBuffer (Data() As Byte)
Dim datasent As Int
Dim in As InputStream
Dim out As OutputStream
Dim sentdata() As Byte

datasent = Data.Length/4

in.InitializeFromBytesArray(Data, 0, datasent)
out.InitializeToBytesArray(0)
File.Copy2(in, out)
  
sentdata = out.ToBytesArray
End sub

but the sound comes out very scratchy
 
Last edited:
Upvote 0

stevel05

Expert
Licensed User
Longtime User
Why is the size of the buffer that important? it's a temporary variable which modern devices can cope with.

The sound gets scratchy because you are only processing 1/4 of the data.
 
Upvote 0

Addo

Well-Known Member
Licensed User
The size is important due to network limitations I have been divided the buffersize In other ide without scratching issues as I show in the first post

I don't clearly know how to divid the byte data correctly to avoid scratching

Also this big size cannot be played on some devices
 
Last edited:
Upvote 0

stevel05

Expert
Licensed User
Longtime User
Have you checked the AudioRecorder class on Android developers to see if the option is available to use with JavaObject?
 
Upvote 0

Addo

Well-Known Member
Licensed User
Sorry I dont understand is the audiostreamer is a wrapper for android audiotrack ? Seems like that for the recording behaviour to me
Because in other ide that uses audiotrack that was a similar case and dividing the buffer seems was solve to all issues on other devices

B4X:
BUFFSIZE := TJAudioRecord.JavaClass.getMinBufferSize(8000,
TJAudioFormat.JavaClass.CHANNEL_IN_MONO,
TJAudioFormat.JavaClass.ENCODING_PCM_16BIT);
stream := TJavaArray<byte>.Create(BUFFSIZE div 4);

I thought that I can do the same in b4a without problems
 
Upvote 0

stevel05

Expert
Licensed User
Longtime User
Sorry, I don't know the library and have not used it, just trying to help. I'm afraid you'll have to wait for Erel to respond.
 
Upvote 0

Addo

Well-Known Member
Licensed User
Sorry, I don't know the library and have not used it, just trying to help. I'm afraid you'll have to wait for Erel to respond.

i looked at your audio track wrapper library

i think if audiostreamer use andrioid audio track then at this line

B4X:
'Get the min buffer size
BufferSize=at.GetMinBuffersize(SampleRate,ChannelConfig,AudioFormat)/4 ' there can be a choice to add divided number
 
Upvote 0

stevel05

Expert
Licensed User
Longtime User
I don't think it's a simple as that because the process needs the returned minimum buffer size to work correctly. Otherwise you would be losing data.

As I say, I don't know the library but I would guess that the streaming buffer size is a different thing than the Audio processing buffer size.
 
Upvote 0

OliverA

Expert
Licensed User
Longtime User
Neither code posted in post #1 and post #7 show enough of what you are currently doing to process the data in smaller junks than what AudioRecord does. All this code does is
BUFFSIZE := TJAudioRecord.JavaClass.getMinBufferSize(8000,
TJAudioFormat.JavaClass.CHANNEL_IN_MONO,
TJAudioFormat.JavaClass.ENCODING_PCM_16BIT);
Determine what the smallest buffer size is for audiorecord to operate properly at the given sample rate, audio channel configuration, and audio format.
stream := TJavaArray<byte>.Create(BUFFSIZE div 4);
You create a buffer that is one-fourth the size of the minimum buffer size that AudioRecord requires to operate properly. As to how this is used, no code is shown.
AudioRecorder := TJAudioRecord.JavaClass.init
(TJMediaRecorder_AudioSource.JavaClass.MIC,
8000,
TJAudioFormat.JavaClass.CHANNEL_IN_MONO,
TJAudioFormat.JavaClass.ENCODING_PCM_16BIT, BUFFSIZE);
An instance of AudioRecord is initialized with the same parameters as the first call, plus the audio source and the buffer size that was returned in the first call (same size, not divided by four. If you did make this buffer smaller, the initialization would fail, since the buffer is now below the minimum size required).
(AudioRecorder as JAudioRecord).startRecording;
You start recording.

Nowhere in this code is it shown how you process only one fourth of what audio record produces, nor do you set up AudioRecord to use a smaller buffer (well, you can't).

If you look at the code you posted in post#2, all you're doing here is only processing one-forth of the incoming data and throwing away the rest. As @stevel05 pointed out, that's why you are getting lousy audio quality.

So, if possible, you need to find the code (same type of code posted in post#1) that does the actual processing of the audio stream and post that/go by that to implement you B4A code.
 
Upvote 0

Addo

Well-Known Member
Licensed User
here is the full code of reading data in the other ide

B4X:
unit AudioPool;

interface
uses System.SysUtils,
System.Classes,
System.SyncObjs,
System.Generics.Collections, AndroidApi.JNI,
Androidapi.JNI.Media, Androidapi.JNI.GraphicsContentViewText,
Androidapi.JNI.Net,
Androidapi.JNIBridge,
Androidapi.JNI.JavaTypes;



{Thread Audio }
type
TAUDIOBOOLThread = class(TThread)
Private
Enable_Play: Boolean;
AudioPlay: JAudioTrack;
AudioStream: TJavaArray<Byte>;
Audiodataset: Integer;
AudioQueue: TQueue<TMemoryStream>;
AudioInQueue: TEvent;

protected
procedure Execute; override;
procedure TerminatedSet; override;

Public
constructor Create(CreateSuspended: Boolean); reintroduce;
destructor Destroy; override;
procedure Queue(var Stream: TMemoryStream);

end;

var
AUDIOBOOLThread: TAUDIOBOOLThread;


implementation

{ TAudioWorkerThread }

constructor TAUDIOBOOLThread.Create(CreateSuspended: Boolean);
begin
inherited Create(CreateSuspended);
FreeOnTerminate := False;
Enable_Play := False;
Audiodataset := TJAudioTrack.JavaClass.getMinBufferSize(8000,
TJAudioFormat.JavaClass.CHANNEL_OUT_MONO,
TJAudioFormat.JavaClass.ENCODING_PCM_16BIT);
AudioStream:= TJavaArray<Byte>.Create(Audiodataset);
AudioPlay:= TJAudioTrack.JavaClass.init(TJAudioManager.JavaClass.STREAM_MUSIC,
8000,
TJAudioFormat.JavaClass.CHANNEL_OUT_MONO,
TJAudioFormat.JavaClass.ENCODING_PCM_16BIT,
Audiodataset,
TJAudioTrack.JavaClass.MODE_STREAM);

AudioQueue := TQueue<TMemoryStream>.Create;
AudioInQueue := TEvent.Create;


end;

destructor TAUDIOBOOLThread.Destroy;
begin
  inherited;
end;

procedure TAUDIOBOOLThread.Execute;
var
AudioDataSize : integer;
Stream: TMemoryStream;
begin



while not Terminated  do
begin

AudioInQueue.WaitFor;

if Terminated then
begin
Exit;
end;

TMonitor.Enter(AudioQueue);
try
Stream := AudioQueue.Dequeue;
if AudioQueue.Count = 0 then
begin
AudioInQueue.ResetEvent;
end;



finally
TMonitor.Exit(AudioQueue);
end;

if not Enable_Play then
begin
AudioPlay.play();
Enable_Play := True;
end;

Stream.Position := 0;
while Stream.Position < Stream.Size do
begin
AudioDataSize := Stream.Read(AudioStream.Data^, AudioDataset);

try
AudioPlay.write(AudioStream, 0, AudioDataSize);

except
//log any exception

end;

end;

end;

end;



procedure TAUDIOBOOLThread.Queue(var Stream: TMemoryStream);
begin
if Terminated then
begin
 Exit;
end;
TMonitor.Enter(AudioQueue);
try
AudioQueue.Enqueue(Stream);
Stream := nil;
if AudioQueue.Count = 1 then
begin
AudioInQueue.SetEvent;
end;

finally
TMonitor.Exit(AudioQueue);
end;

end;

procedure TAUDIOBOOLThread.TerminatedSet;
begin
  inherited;
 AudioInQueue.SetEvent;
end;

end.

specially here

B4X:
Stream.Position := 0;
while Stream.Position < Stream.Size do
begin
AudioDataSize := Stream.Read(AudioStream.Data^, AudioDataset);

try
AudioPlay.write(AudioStream, 0, AudioDataSize);

except
//log any exception

end;

maybe i have to use while loop to avoid this chunk play ?
 
Last edited:
Upvote 0

Addo

Well-Known Member
Licensed User
and the weird thing is the app that i created with the other ide that send exactly the same divided record to the b4a app plays the sound very well

so i can send clear divided audio buffer sized from Fmx app To B4a app and B4a App play the divided buffer very clear

but if i send divided buffer from B4a app To b4a app the sound playes with scratch

an both received length have the same number
 
Last edited:
Upvote 0

OliverA

Expert
Licensed User
Longtime User
In all these examples, everything goes by AudioDataset as size and AudioDataset in both is set to getMinBufferSize's return value. So I don't see any "chunking" anywhere.
 
Upvote 0

OliverA

Expert
Licensed User
Longtime User
Upvote 0

Addo

Well-Known Member
Licensed User
the playing side in fmx code is set to the default getMinBufferSize value and the recording side is in different thread that divide the getMinBufferSize over 4

sending audio from fmx app to b4a app has no problem with playing that divided data in b4a side

only sending from b4a side does have problem which have very big suspicious in my code in b4a to divide the bytes

the recording side fmx code


B4X:
  type
  TAudiocallbackproc = procedure(Sender: TObject; Sbuffer: pointer;
    SBufferSize: Cardinal) of object;

  TAudioStreamThread = class(TThread)
  private
    FonAudiocallbackproc: TAudiocallbackproc;
    //FCs: TCriticalSection;
    Fbuffer: pointer;
    FBufferSize: Cardinal;

    // recorder
    AudioRecorder: JAudioRecord;
    BUFFSIZE: Integer;
    stream: TJavaArray<byte>;
    procedure DoAudiocallbackproc;
    //
  protected
    procedure Execute; override;
  Public
    constructor Create(CreateSuspended: Boolean;
      AonAudiocallbackproc: TAudiocallbackproc); reintroduce;
    destructor Destroy; override;
  end;


{ TAudioStreamThread }

constructor TAudioStreamThread.Create(CreateSuspended: Boolean; AonAudiocallbackproc: TAudiocallbackproc);
begin
inherited Create(CreateSuspended);
FreeOnTerminate := false;
//FCs := TCriticalSection.Create;
FonAudiocallbackproc := AonAudiocallbackproc;

BUFFSIZE := TJAudioRecord.JavaClass.getMinBufferSize(8000,
TJAudioFormat.JavaClass.CHANNEL_IN_MONO,
TJAudioFormat.JavaClass.ENCODING_PCM_16BIT);
stream := TJavaArray<byte>.Create(BUFFSIZE div 4);

AudioRecorder := TJAudioRecord.JavaClass.init
(TJMediaRecorder_AudioSource.JavaClass.MIC, 8000,
TJAudioFormat.JavaClass.CHANNEL_IN_MONO,
TJAudioFormat.JavaClass.ENCODING_PCM_16BIT, BUFFSIZE);

(AudioRecorder as JAudioRecord).startRecording;



end;



procedure TAudioStreamThread.Execute;
var
NewCount: Integer;
begin




NewCount := 0;
while not terminated do
begin

sleep(1);


NewCount := (AudioRecorder as JAudioRecord).read(stream, 0, stream.Length);


if NewCount > 0 then
begin
Fbuffer := stream.Data;


if (Fbuffer <> nil) and (NewCount > 0) then
begin


udpreciver.SendBuffer(ip,port, RawToBytes(Fbuffer^, NewCount));

end;

end;
end;

end;
 
Last edited:
Upvote 0

OliverA

Expert
Licensed User
Longtime User
recording side is in different thread that divide the getMinBufferSize over 4
So where is this? And just for fun, have you tried not to divide the buffer at all (making datasent = Data.length) and see what happens?
 
Upvote 0

Addo

Well-Known Member
Licensed User
code posted above post 17

if i send the data.length will work normal but in case the length is bigger than 1000 then it will not be sent over udp due to network limitation

and on my lenovo phone the length is 2048 and this will not give the ability to transmit the audio over udp on other devices thats why i have to divid the buffer even in the other ide


and here how i recive data in b4a

B4X:
Sub UDPAUD_PacketArrived (Packet As UDPPacket)
    
    Dim in As InputStream
    Dim out As OutputStream
    Dim data() As Byte
    Dim packL As Int
    

packL = Packet.Length

    
Log(packL)


    
in.InitializeFromBytesArray(Packet.Data, Packet.Offset, packL)
out.InitializeToBytesArray(0)
File.Copy2(in, out)
data = out.ToBytesArray
audioStream.Write(data)
in.Close
out.Close
End Sub
 
Upvote 0

OliverA

Expert
Licensed User
Longtime User
In post#2, all the variables are local. What's the actual code and how are you sending the UDP packets?
 
Upvote 0
Top