Android Question "Rewind" function of audiostreamer

Olivier Zeegers

Member
Licensed User
Hello Erel,

I have found the documentation of Audiostreamer in this link: https://www.b4x.com/android/help/audio.html#audiostreamer

I can understand that one can start a recording and later on play it back but I don't see any function to start playing back from a certain position in the stream ?
Like i asked, I want to build an app that mimics a dictaphone, a sort of voice recorder. But the user should be able to press the record button and record his voice and then if he made a mistake pus the rewind button to rewind a little and then either OVERWRITE his previous recording of INSERT a new voice recording.

I try to make a better version of the app dictate + connect: http://www.dictate-connect.com/?page_id=134

You said this was possible in your previous answer to my question, so I bought B4A and B4I, but I don't see how ?

thank you
 

Olivier Zeegers

Member
Licensed User
I have taken the artest1.02 from stevel05 and tried to understand it. When I run this project I only get a black screen with the label Raw Ampl. and a label with dB on it. Nothing is happening ? Or is it recording silently ?

Then I have tried to combine the code from stevel05's project with the demo project of AudioStream from Erel...
Here there is a StartRecording button. When the user clicks it it should start a new thread for recording and display the time on the screen. Instead the app crashes. I've tried to debug and I don't see the program going into the Recording sub (thread). Is this because debugging is not possible with threads ?

I get an error "java.lang.NullPointerException: Attempt to invoke virtual method 'boolean anywheresoftware.b4a.BA.isActivityPaused()' on a null object reference"

Sorry, I know I am novice.. but I am really trying to understand it.
Can you help me ?

I have search the forum for other examples but don't find any. There's no existing Voice Recorder project that I can study and modify.

thank you

B4X:
#Region  Project Attributes
    #ApplicationLabel: DictaPhone
    #VersionCode: 1
    #VersionName:
    'SupportedOrientations possible values: unspecified, landscape or portrait.
    #SupportedOrientations: unspecified
    #CanInstallToExternalStorage: False
#End Region

#Region  Activity Attributes
    #FullScreen: False
    #IncludeTitle: True
#End Region

Sub Process_Globals
    'These global variables will be declared once when the application starts.
    'These variables can be accessed from all modules.
    Private AT As AudioTrack
    Private AR As AudioRecord
    Private Buffers() As Byte
    Private timer1 As Timer
    Private recordingStart As Long
    Private RP As RuntimePermissions
    
    Dim MP As MediaPlayer
    Dim Interrupt As Boolean
    Dim OutFile As RandomAccessFile
    Dim StartTime As Long
    
    Dim BufferSize As Int
    Dim SampleRate As Int
    Dim ChannelConfig As Int
    Dim AudioFormat As Int
    Dim AudioSource As Int
    Dim NoChnls,BitsPerSample,DataSize As Int
    
End Sub

Sub Globals
    Dim Label1 As Label
    Dim btnPlay As Button
    Dim btnStartRecording As Button
    Dim Record As Thread
End Sub

Sub Activity_Create(FirstTime As Boolean)
    
    Activity.LoadLayout("1.bal")
    
    'Initialize Media player for playback
    MP.Initialize

    'Initialize the thread to record on
    Record.Initialise("Rec")
    
    'Set up recording parameters Not all parameters will be supported on all
    'devices
    AudioSource=AR.A_Src_Mic
    SampleRate=44100
    ChannelConfig=AR.Ch_Conf_Mono
    NoChnls=1
    AudioFormat=AR.Af_PCM_16
    BitsPerSample=16
    

    
    'Determine the required min buffer size
    BufferSize=AR.GetMinBufferSize(SampleRate,ChannelConfig,AudioFormat)
    
    If BufferSize < 0 Then
        Msgbox("Buffer error, Hardware does not support recording with the given parameters","Buffer error")
        Activity.Finish
    End If
    
    AR.Initialize(AudioSource,SampleRate,ChannelConfig,AudioFormat,BufferSize)


    'Test settings
'    AR.SetNotificationMarkerPosition(3000)
'    AR.SetPositionNotificationPeriod(2000)

    'delete the file if it exists to avoid problems with Random access files
    If File.Exists(File.DirInternal,"test.wav") Then
        File.Delete(File.DirInternal,"test.wav")
    End If
    
    'Initialize the output file
    Log(File.DirDefaultExternal)
    OutFile.Initialize2(File.DirInternal,"test.wav",False,True)
    
    'Write the Wave file header
    WriteWavHeader


    'enable restriction of recording time for testing
    StartTime=DateTime.Now

    
End Sub

Sub Recording
    'ReSet the data size
    DataSize=0
    
    Log("Recording...")
    'Do the recording
    'I've read that the read methods are blocking and won't return until
    ' the buffer is full.  Which appears to be the case in my testing.
    ' data has to be read pretty much immediately or it will get overwritten
    
    Do While True
        Dim RecData() As Byte
        Dim Sum As Int
        RecData=AR.ReadBytes(0,BufferSize)
        OutFile.WriteBytes(RecData,0,RecData.Length,44+DataSize)
        DataSize=DataSize+RecData.Length
        For i = 0 To 480 Step 2            'Approx 5ms worth of data
            Sum=Sum+(RecData(i)*256)+RecData(i+1)
        Next
        Record.RunOnGuiThread("Showvolume",Array As Object(Sum))
        'Check if interupt requested
        If Interrupt Then Exit
        'Check if recording time is up
        If DateTime.Now > StartTime+"10000" Then Exit
    Loop

End Sub

Sub streamer_RecordBuffer (Buffer() As Byte)
    'collect the recording data
    'Buffers.Add(Buffer)
End Sub

Sub btnStartRecording_Click
    
    AR.StartRecording
    
    'enable restriction of recording time for testing
    StartTime=DateTime.Now
    
    'Start the thread to record on
    
    Record.Start(Me,"Recording",Null)
    
    recordingStart = DateTime.Now
    timer1.Interval = 1
    timer1.Enabled = True
    Timer1_Tick
    btnPlay.Enabled = False
End Sub

Sub Timer1_Tick
    Label1.Text = "Recording: " & _
        Round((DateTime.Now - recordingStart) / DateTime.TicksPerSecond) & " seconds"
End Sub

Sub btnStopRecording_Click
    AR.Stop
    timer1.Enabled = False
    btnPlay.Enabled = True
    Label1.Text = ""
End Sub

Sub btnPlay_Click
    btnStartRecording.Enabled = False
    
    
    'For Each b() As Byte In Buffers
    '    streamer.Write(b)
    'Next
    'streamer.Write(Null) 'when this "message" will be processed, the player will stop.
End Sub

Sub streamer_PlaybackComplete
    Log("PlaybackComplete")
    btnStartRecording.Enabled = True
End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)

End Sub

Sub WriteWavHeader

    Dim Pos,IntLen,ShLen,StrLen As Int
    
    Pos=0
    IntLen=4
    ShLen=2
    StrLen=4
    OutFile.WriteBytes(Array As Byte(Asc("R"),Asc("I"),Asc("F"),Asc("F")),0,StrLen,Pos)
    Pos=Pos+IntLen
    OutFile.WriteInt(0,Pos)                'Final size not yet known
    Pos=Pos+IntLen
    OutFile.WriteBytes(Array As Byte(Asc("W"),Asc("A"),Asc("V"),Asc("E")),0,StrLen,Pos)
    Pos=Pos+IntLen
    OutFile.WriteBytes(Array As Byte(Asc("f"),Asc("m"),Asc("t"),Asc(" ")),0,StrLen,Pos)
    Pos=Pos+IntLen
    OutFile.WriteInt(16,Pos)                'Sub chunk size 16 for PCM
    Pos=Pos+IntLen
    OutFile.WriteShort(1,Pos)                'Audio Format, 1 for PCM
    Pos=Pos+ShLen
    OutFile.WriteShort(1,Pos)                'No of Channels
    Pos=Pos+ShLen
    OutFile.WriteInt(SampleRate,Pos)
    Pos=Pos+IntLen
    OutFile.WriteInt(SampleRate*BitsPerSample*NoChnls/8,Pos)    'Byte Rate
    Pos=Pos+IntLen
    OutFile.WriteShort(NoChnls*BitsPerSample/8,Pos)    'Block align, NumberOfChannels*BitsPerSample/8
    Pos=Pos+ShLen
    OutFile.WriteShort(BitsPerSample,Pos)    'BitsPerSample
    Pos=Pos+ShLen
    OutFile.WriteBytes(Array As Byte(Asc("d"),Asc("a"),Asc("t"),Asc("a")),0,StrLen,Pos)
    Pos=Pos+IntLen
    OutFile.WriteInt(0,Pos)            'Data chunk size (Not yet known)
    Log("Pos "&Pos)
    
End Sub
 
Last edited:
Upvote 0

stevel05

Expert
Licensed User
Longtime User
Is this because debugging is not possible with threads ?

Yes, try running it in release mode.

Also run ARTest in release mode for the same reason. This was written before the Android permission change, you will need to add that if needed.
 
Upvote 0

Olivier Zeegers

Member
Licensed User
OK, I've ATtest running and it plays the WAV file... in Release Mode ;-)

Now when trying to run ARTest I had to add the RunPermissions:

B4X:
    RP.CheckAndRequest(RP.PERMISSION_RECORD_AUDIO)
    Wait for Activity_PermissionResult(permission As String, Result As Boolean)
    If Result = True Then
        AR.Initialize(AudioSource,SampleRate,ChannelConfig,AudioFormat,BufferSize)
        Log("permission ok")
    Else
        Log("permission false")
        Return
    End If

But when I run it in Release Mode I get this log output with errors and the app quits:

** Activity (main) Create, isFirst = true **
** Activity (main) Resume **
permission ok
Pos 40
** Activity (main) Pause, UserClosed = false **
Recording...
sending message to waiting queue (rec_ended)
running waiting messages (1)
Ended false
main_rec_ended (java line: 608)
java.lang.IllegalStateException: stop() called on an uninitialized AudioRecord.
at android.media.AudioRecord.stop(AudioRecord.java:1059)
at stevel05.audiorecord.AudioRecording.Stop(AudioRecording.java:224)
at com.stevel05.artest.main._rec_ended(main.java:608)
at java.lang.reflect.Method.invoke(Native Method)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:191)
at anywheresoftware.b4a.BA$2.run(BA.java:365)
at anywheresoftware.b4a.BA.setActivityPaused(BA.java:437)
at com.stevel05.artest.main$ResumeMessage.run(main.java:306)
at android.os.Handler.handleCallback(Handler.java:790)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6753)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:482)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
java.lang.IllegalStateException: stop() called on an uninitialized AudioRecord.
** Activity (main) Pause, UserClosed = false **
** Activity (main) Resume **


So the programs gets passed 'recording...'
But where does the log message 'sending message to waiting queue (rec_ended) come from ?
and the lines after ?
why does it crash ?

I see that in the Sub Rec_Ended it logs Ended en then False and does not play the test.wav
But why is endedOK false ?

B4X:
Sub Rec_Ended(endedOK As Boolean,Error As String)
    Log("Ended " & endedOK)

    'Stop recording and release resources
    AR.Stop
    AR.release
   
    If endedOK Then
        'Finish writing the WAVE Header
        UpdateHeader
       
        Log("Thread Rec EndedOK "&endedOK&" "&Error)
       
        'Load the recorded file into Media player as a test
        MP.Load(File.DirDefaultExternal,"test.wav")
        MP.Play
    End If
End Sub

When I log the error in Rec_ended: log("Ended " & endedOK & Error) it says:

Ended falseException : Attempt to read from field 'anywheresoftware.b4a.randomaccessfile.RandomAccessFile com.stevel05.artest.main._outfile' on a null object reference



thank you for your help, I realize that I am a newby fooling around trying to understand how b4a works...
 
Last edited:
Upvote 0

stevel05

Expert
Licensed User
Longtime User
It would be easier if you post the whole module, or better still, zip and post the project (File/Export as zip).

When does the error occur?
 
Upvote 0

Olivier Zeegers

Member
Licensed User
It would be easier if you post the whole module, or better still, zip and post the project (File/Export as zip).

When does the error occur?

Here's a zip of the project...
Error occurs directly after activity is created. Nothing is recorded,it gives the error directly.. Could be something with not being able to create test.wav ?
 

Attachments

  • artest_OZ.zip
    8.4 KB · Views: 208
Upvote 0

stevel05

Expert
Licensed User
Longtime User
I didn't realize you were running the unmodified artest project. But, the app you zipped runs fine on my Samsung Android 8 phone.

What device are you running it on?
 
Upvote 0

stevel05

Expert
Licensed User
Longtime User
Couldn't find any reason that it wouldn't work.

Try the attached, hopefully this will work. It runs on my Nexus 7 and Samsung J3 (Android 8).

I have changed the channel config to stereo in case your device doesn't support mono recording. This is just a really basic example, you will need to consider how you want it to work and implement the workflow as you'd like.
 

Attachments

  • DictaphoneTest.zip
    10.8 KB · Views: 212
Upvote 0

Olivier Zeegers

Member
Licensed User
Couldn't find any reason that it wouldn't work.

Try the attached, hopefully this will work. It runs on my Nexus 7 and Samsung J3 (Android 8).

I have changed the channel config to stereo in case your device doesn't support mono recording. This is just a really basic example, you will need to consider how you want it to work and implement the workflow as you'd like.

super !
I quickly looked into it and on my OnePlus it logged 'AR not ready' when I pushed the record button. Will look into it further tonight ;-)

thank you !!
 
Upvote 0

Olivier Zeegers

Member
Licensed User
Couldn't find any reason that it wouldn't work.

Try the attached, hopefully this will work. It runs on my Nexus 7 and Samsung J3 (Android 8).

I have changed the channel config to stereo in case your device doesn't support mono recording. This is just a really basic example, you will need to consider how you want it to work and implement the workflow as you'd like.

Strangely enough your project always gives me an error when I push the Record button. It says 'AR not ready'.

When I log the AR.Getstate in the initialize Sub of Record it logs 1
When I log the AR.Getstate in the Isready Sub of Record it logs 0

** Activity (main) Pause, UserClosed = false **
** Activity (main) Create, isFirst = true **
1 <------------- logged by Initialize sub of Record
true
Playback Buffer Size 14144
1
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
** Activity (main) Resume **
0 <------------------- logged by IsReady sub of Record
Ar Not Ready
** Activity (main) Pause, UserClosed = false **

Any idea why this is happening ?
 
Upvote 0

stevel05

Expert
Licensed User
Longtime User
For some reason the Audiorecord object is not being initialized.

If you look at the code in the Record class, you will see that the AR.initialize call is in a try block, do you see the last exception in the logs when it fails?
 
Upvote 0

stevel05

Expert
Licensed User
Longtime User
I put the check in because I was getting a crash when pressing the record button too soon.

If the initialize sub is returning true, the initilization can't be failing.

You can try commenting out the ready test in the bthRecord_Click sub and see what happens.
 
Upvote 0

stevel05

Expert
Licensed User
Longtime User
Probably not what you want to hear, but there appear to be a lot of problems with the microphone on the Oneplus 5T.

Try googling for Oneplus 5T microphone.

This may have something to do with it. Do you have headphones with a mic you could try?
 
Last edited:
Upvote 0

Olivier Zeegers

Member
Licensed User
For some reason the Audiorecord object is not being initialized.

If you look at the code in the Record class, you will see that the AR.initialize call is in a try block, do you see the last exception in the logs when it fails?

B4X:
Try
        AR.Initialize(AudioSource,SampleRate,ChannelConfig,AudioFormat,BufferSize)
    Catch
        Log(LastException)
        Return False
    End Try

when I debug it goes into the Try and runs Initialize and skips the Catch part, so that looks good
when I log LastException just after this routine i get:

(Exception) Not initialized

But I don't know if that may be the exception of the last time I ran the project..
 
Upvote 0

Olivier Zeegers

Member
Licensed User
I put the check in because I was getting a crash when pressing the record button too soon.

If the initialize sub is returning true, the initilization can't be failing.

You can try commenting out the ready test in the bthRecord_Click sub and see what happens.


Yes Initialization sub returns true

I already commented the ready test out and then it gives this error:

record_start (java line: 206)
java.lang.IllegalStateException: startRecording() called on an uninitialized AudioRecord.
at android.media.AudioRecord.startRecording(AudioRecord.java:1015)
at stevel05.audiorecord.AudioRecording.StartRecording(AudioRecording.java:218)
at com.stevel05.dictaphonetest.record._start(record.java:206)
at com.stevel05.dictaphonetest.main._btnrecord_click(main.java:511)
at java.lang.reflect.Method.invoke(Native Method)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:191)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:175)
at anywheresoftware.b4a.BA.raiseEvent(BA.java:171)
at anywheresoftware.b4a.objects.ViewWrapper$1.onClick(ViewWrapper.java:80)
at android.view.View.performClick(View.java:6367)
at android.view.View$PerformClick.run(View.java:25032)
at android.os.Handler.handleCallback(Handler.java:790)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6753)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:482)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
java.lang.IllegalStateException: startRecording() called on an uninitialized AudioRecord.
 
Upvote 0

Olivier Zeegers

Member
Licensed User
Probably not what you want to here, but there appear to be a lot of problems with the microphone on the Oneplus 5T.

Try googling for Oneplus 5T microphone.

This may have something to do with it. Do you have headphones with a mic you could try?

Halas, no difference when trying with headphones with microphone.... :(
 
Upvote 0

stevel05

Expert
Licensed User
Longtime User
Damn!

As you can see from the code, the IsReady test just double checks that the AudioRecord object is initialized. So the initialization is not failing with a catchable error, but it's not working either. As I said, I added the test because it would crash if I tried to press the record button immediately after the app started.

You could try starting the app, and giving it a few seconds (maybe 15-20) before trying to record and see if that works.

Alternatively do you have another device to try it on, as much for my peace of mind than anything else.

The only other thing I can think of is to try different samplerates to see if it will work on a different one. You will have to change the sample rate in the playback class as well.
 
Upvote 0

Olivier Zeegers

Member
Licensed User
Damn!

As you can see from the code, the IsReady test just double checks that the AudioRecord object is initialized. So the initialization is not failing with a catchable error, but it's not working either. As I said, I added the test because it would crash if I tried to press the record button immediately after the app started.

You could try starting the app, and giving it a few seconds (maybe 15-20) before trying to record and see if that works.

Alternatively do you have another device to try it on, as much for my peace of mind than anything else.

The only other thing I can think of is to try different samplerates to see if it will work on a different one. You will have to change the sample rate in the playback class as well.

hi,

Giving it a few seconds give the same result..
I checked the permissions in the settings and it said microphone and storage permission OK.

I will try with different samplerates and look for a mate with a different device..

strange though... can we run another test ?
 
Upvote 0

stevel05

Expert
Licensed User
Longtime User
Have you used the device for any type of recording from the mic in the past. Did it work with the Audiostreamer when you tried it?

You could download a dictaphone type app from the play store and see if it works with that.
 
Upvote 0
Top