B4A Library AudioRecord Library

This is an Audio Recording Library based on the Android AudioRecord object which allows capturing of sound input to a data file for further processing (maybe realtime if we're lucky).

Included in the demo program is a WAV file header routine that allows saving captured sound as an uncompressed wav file.

Please read the documentation of the AudioRecord object. It's a little more complicated that the media recorder version, but gives additional flexibility.

Please test it and hopefully we can get it to work well for us.

Added libs 1.01 - Additional constants and Capitalized

12/6/2012 Artest updated to better manage thread on closedown.

For use with V2 of B4a (more specifically the latest threading library) you'll need to add a parameter to the start thread call in artest. Line 89, Record.Start("Recording",Null) becomes Record.Start(Null,"Recording",Null)

artest 1155
 

Attachments

  • AudioRecord1.01.zip
    4.9 KB · Views: 2,853
  • artest1.02.zip
    8.2 KB · Views: 1,071
Last edited:

stevel05

Expert
Licensed User
Longtime User
Finding silence is not too bad as the values of each byte will be 0 (in a signed byte or within a tolerance), but filtering I wouldn't know where to start either unfortunately.
 

NeoTechni

Well-Known Member
Licensed User
Longtime User
Lets say I want to show the soundwave being recorded (visualization) How would I go about doing that?
 

stevel05

Expert
Licensed User
Longtime User
Theoretically you'd just plot each sample against time as the X axis and the value of the sample as the Y axis.

The maximum value is defined by the sample depth i.e. 8bit, 16 bit etc and is treated as a signed integer, which complicates things a little. So for an 8 bit sample, the displayed centre line would actually be 128.

I haven't looked into it in detail, but I assume that you'd need to do some sort of averaging so you don't have to plot every sample, unless you're zoomed right in.
 

stevel05

Expert
Licensed User
Longtime User
See my answer to your other question, you'd have to capture it first, then decide how big a data set to display/average in one go. Audio record will probably give you most control over how much data you choose to work with.
 

NeoTechni

Well-Known Member
Licensed User
Longtime User
See my answer to your other question, you'd have to capture it first, then decide how big a data set to display/average in one go. Audio record will probably give you most control over how much data you choose to work with.

But that's not really an answer. I'm asking how to do that? Preferably with example code?
 

stevel05

Expert
Licensed User
Longtime User
Ok, I was trying to point you in the right direction. There is sample code in the library post. It should be fairly straightforward to extract what you need from there.
 

NeoTechni

Well-Known Member
Licensed User
Longtime User
I changed the demo code to:

B4X:
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
      RecData=AR.ReadBytes(0,BufferSize)
      LogBytes(RecData)
'      OutFile.WriteBytes(RecData,0,RecData.Length,44+DataSize)
      DataSize=DataSize+RecData.Length
      'Check if recording time is up
      If DateTime.Now > StartTime+"10000" Then Exit
   Loop

End Sub


Sub LogBytes(RecData() As Byte)
   Dim tempstr As StringBuilder ,temp As Int ,temp2 As Int 
   tempstr.Initialize 
   tempstr.Append( "Bytes:"  )
   For temp = 0 To RecData.Length-1 Step 2
      'temp2 =RecData(temp) Normalize(RecData(temp))
      'If temp2<0 Then temp2 = 127 - temp2
      
      temp2 = Combine(RecData(temp+1), RecData(temp))
      tempstr.Append( " " & temp2 )
   Next
   Log(tempstr.ToString)
End Sub
Sub Normalize(Data As Byte) As Int
   If Data<0 Then
      Return 127 - Data
   Else
      Return Data
   End If
End Sub
Sub Combine(Byte1 As Byte, Byte2 As Byte) As Int
   Return Normalize(Byte1) + (Normalize(Byte2)*256)
End Sub

I am assuming it's Byte2 Byte1, Byte2 Byte1, etc

And I get data like:
Bytes: 6144 6144 6144 6144 6144 6144 6144 6144 6144 6144 6144 6144 6144 6144 6144 6144 6144 6144 6144 6144 6144 6144 6144 6144 4096 6144 4096 6144 4096 6144 4096 8192 0 12288 38784 20480 44928 28672 53120 59136 30848 36608 61313 6317 30894 12483 10439 40902 44997 34755 34753 12478 26813 24764 28860 14523 2234 26810 36794 53177 55224 2231 49079 42935 61366 28853 59317 14516 24756 179 4275 6322 14514 53170 36786 20657 61361 38833 12464 55216 36784 18607 61359 40879 16558 30894 44974 173 28845 28845 59309 36781 10412 28844 55212 36780 10411 26795 16555 53163 51115 10410 4266 18602 65450 53162 2217 44970 12457 12457 51113 47017 42921 10408 8360 22696 57256 63400 42920 34728 36776 14503 16551 18599 55207 61351 47015 34727 40871 10406 20646 18598 22694 24742 22694 61350 6309 165 6309 2213 6309 2213 63397 59301 61349 57253 59301 61349 53157 8356 164 4260 2212 4260 2212 4260 61348 59300 59300 57252 55204 53156 55204 57252 2211 16547 12451 14499 18595 20643 22691 24739 20643 53155 40867 40867 34723 36771 34723 34723 34723 162 162 16546 63394 65442 61346 61346 59298 59298 57250 55202 57250 55202 59298 42914 8353 2209 10401 6305 8353 8353 8353 8353 10401 12449 10401 16545 12449 59297 47009 53153 49057 47009 44961 47009 47009 44961 47009 44961 47009 47009 44961 47009 40865 47009 34721 16544 12448 16544 18592 20640 18592 20640 18592 22688 22688 24736 26784 28832 65440 63392 63392 57248 55200 53152 53152 55200 57248 53152 57248 47008 2207 36768 34720 36768 36768 34720 159 2207 10399 12447 18591 24735 26783 65439 30879 63391 61343 61343 61343 61343 59295 61343 59295 61343 57247 59295 57247 59295 57247 59295 57247 55199 57247 57247 59295 59295 61343 59295 63391 61343 61343 57247 55199 57247 51103 51103 47007 53151 34719 8350 4254 4254 2206 4254 6302 8350 8350 10398 8350 10398 8350 10398 10398 16542 18590 14494 20638 20638 24734 24734 28830 28830 28830 28830 26782 30878 30878 65438 61342 57246 57246 57246 55198 55198 55198 53150 49054 49054 47006 51102 44958 57246 28830 65438 28830 28830 28830 26782 30878 28830 65438 65438 63390 59294 59294 59294 59294 59294 59294 55198 53150 55198 51102 49054 47006 42910 42910 38814 36766 36766 36766 34718 34718 38814 38814 38814 38814 38814 38814 40862 40862 40862 38814 38814 40862 38814 38814 157 2205 4253 6301 8349 8349 8349 8349 10397 10397 14493 12445 12445 16541 18589 20637 22685 20637 22685 22685 22685 22685 22685 20637 22685 2205 38814 36766 38814 157 157 2205 6301 8349 10397 12445 14493 14493 14493 14493 12445 16541 16541 16541 16541 16541 18589 20637 22685 22685 24733 26781 28829 65437 63389 63389 63389 59293 63389 61341 63389 63389 61341 63389 30877 61341 24733 14493 22685 18589 26781 26781 28829 28829 30877 30877 65437 63389 61341 61341 59293 57245 59293 57245 55197 55197 49053 47005 44957 40861 38813 36765 34717 36765 36765 36765 53149 49053 49053 51101 47005 47005 49053 42909 40861 156 2204 6300 6300 8348 10396 6300 6300 4252 8348 4252 2204 4252 4252 8348 12444 16540 22684 10396 34717 156 36765 156 38813 36765 36765 156 156 2204 4252 6300 6300 8348 6300 8348 8348 10396 12444 10396 10396 8348 8348 4252 6300 156 2204 2204 4252 34717 55197 49053 51101 51101 51101 49053 49053 42909 42909 42909 40861 156 156 4252 6300 8348 12444 12444 16540 14492 16540 14492 16540 16540 18588 22684 26780 12444 2204 8348 4252 4252 4252 2204 156 156 4252 4252 8348 10396 12444 12444 14492 14492 16540 18588 16540 16540 12444 12444 10396 12444 14492 14492 16540 14492 22684 10396 42909 156 36765 156 34717 156 34717 156 2204 4252 4252 2204 4252 4252 10396 8348 12444 14492 18588 18588 20636 20636 18588 20636 18588 14492 14492 16540 14492 14492 12444 42909 40861 36765 36765 156 2204 4252 6300 2204 34717 156 2204 2204 2204 4252 6300 8348 6300 12444 8348 12444 14492 14492 14492 12444 16540 14492 18588 20636 18588 22684 20636 34717 38813 34717 34717 34717 34717 34717 156 156 2204 156 34717 156 4252 4252 4252 6300 10396 14492 16540 20636 20636 22684 22684 22684 24732 24732 30876 26780 65436 14492 34717 8348 4252 10396 10396 16540 16540 22684 24732 22684 24732 22684 24732 24732 22684 22684 22684 226

Does that look right to you?
 
Last edited:

NeoTechni

Well-Known Member
Licensed User
Longtime User
To fit all the numbers on screen I find a blocksize (#of points / width)
then i average that number of points together to get a single value and use it to draw the graph
But the graph seems so random. Am I doing the math wrong (wrong byte order?) or something?

spectrograph.png


EDIT: I swapped the byte order and got something that looks better.
 
Last edited:

NeoTechni

Well-Known Member
Licensed User
Longtime User
I'm having problems.
It's now saying I'm trying to record with AR not being initialized
I have not changed the code since the last time. And it was working since.

It goes:
B4X:
AR.Initialize(AudioSource,SampleRate,ChannelConfig,AudioFormat,BufferSize)
      AR.StartRecording

getDeviceForInputSource()input source 1, device 00040000


getInput() inputSource 1, samplingRate 44100, format 1, channels 10, acoustics 0
ALSAStreamOps - input - format = 1, channels = 16, rate = 44100
ALSAStreamOps - default - format = 2, channels = 1, rate = 44100
Initialized ALSA CAPTURE device NULL_Device
open (0,0x40000) = -2
setInDevice(0, 0x40000) = -2
getInput() failed opening input: samplingRate 44100, format 1, channels 16
Could not get audio input for record source 1
Error creating AudioRecord instance: initialization check failed.
[ android.media.AudioRecord ] Error code -20 when initializing native AudioRecord object.


java.lang.IllegalStateException: startRecording() called on an uninitialized AudioRecord.
at android.media.AudioRecord.startRecording(AudioRecord.java:506)
at stevel05.audiorecord.AudioRecording.StartRecording(AudioRecording.java:218)
at com.omnicorp.lcarui.test.main._startrecording(main.java:6965)
at com.omnicorp.lcarui.test.main._showsection(main.java:6390)
at com.omnicorp.lcarui.test.main._handleevents(main.java:2578)
at com.omnicorp.lcarui.test.main._activity_gesturestouch(main.java:734)
at com.omnicorp.lcarui.test.main._activity_touch(main.java:995)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:145)
at anywheresoftware.b4a.BA$2.run(BA.java:236)
at android.os.Handler.handleCallback(Handler.java:587)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:123)
at android.app.ActivityThread.main(ActivityThread.java:3687)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:842)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:600)
at dalvik.system.NativeStart.main(Native Method)
java.lang.IllegalStateException: startRecording() called on an uninitialized AudioRecord.
 

NeoTechni

Well-Known Member
Licensed User
Longtime User
Sometimes I just get no data from it.

When it does work, I get a repeating wave form.

B4X:
Sub Recording
   DataSize=0
   Do While True
      Dim RecData() As Byte
      RecData=AR.ReadBytes(0,BufferSize)
      LCARSeffects2.AddPoints(1, RecData)'graphid = 1
'      OutFile.WriteBytes(RecData,0,RecData.Length,44+DataSize)
      DataSize=DataSize+RecData.Length
      'Check if recording time is up
      If DateTime.Now > StartTime+"10000" OR CurrentSection<>18 Then Exit
   Loop
End Sub

I've programmed the addpoints sub to only accept one recdata, then do nothing with the rest until the object is redrawn.

But within one recdata, I see the same wave form repeated about X (Where X increases with the frequency of the noise I'm making) times. Is there are way to get the 1 sample set/know how much is one sample? That way I can fill the screen with the single sample rather than X copies of it? Or is this correct behavior?
 

stevel05

Expert
Licensed User
Longtime User
What is the source you are trying to analyze? Anything more than a simple sine wave is likely introduce harmonics at different frequencies which colour the sound making it far more difficult to measure the fundamental frequency / sample length.

I'm afraid that I have not looked into this analysis. If you can get the correct results from a pure sine wave, then you can be reasonably certain that the capture is working as it should. Extracting further data will then require more knowledge than I have unfortunately.

You could try an audio recording forum to get some ideas.
 

stevel05

Expert
Licensed User
Longtime User
I found some errors when restarting after having stopped the recording before it replayed fully, these were caused by the recording thread not shutting down correctly. I have changed the ARTest prog to fix this, namely capturing the back key in activity_keypress and the line in the record thread sub to exit if an interrupt is requested, does this help with your errors?
 

NeoTechni

Well-Known Member
Licensed User
Longtime User
I can still get a failure to initialize

getDeviceForInputSource()input source 1, device 00040000


getInput() inputSource 1, samplingRate 44100, format 1, channels 10, acoustics 0
ALSAStreamOps - input - format = 1, channels = 16, rate = 44100
ALSAStreamOps - default - format = 2, channels = 1, rate = 44100
Initialized ALSA CAPTURE device NULL_Device
open (0,0x40000) = -2
setInDevice(0, 0x40000) = -2
getInput() failed opening input: samplingRate 44100, format 1, channels 16
Could not get audio input for record source 1
Error creating AudioRecord instance: initialization check failed.
[ android.media.AudioRecord ] Error code -20 when initializing native AudioRecord object.
 

NeoTechni

Well-Known Member
Licensed User
Longtime User
Interesting, when this happens, even Google's voice recognition window reports an "Audio problem" and can't record.

Does this help?

http://stackoverflow.com/questions/2397490/voice-recognition-connection-error
Ok... problem fixed.

It appears you cannot use android:launchMode="singleInstance" when using the RecognizerIntent. I removed this from the manifest and the Voice Search runs correctly.

Thanks again Steve, for the info on USB debugging on the device. Very handy.
 
Last edited:

stevel05

Expert
Licensed User
Longtime User
I've tried a few times but can't get the test app to fail on my phone, are you using any other audio resources within your app at the same time?
 

NeoTechni

Well-Known Member
Licensed User
Longtime User
Yes, I'm using a media player object.
I'm also leaving the screen that uses the audio record object, and coming back to it.
 

stevel05

Expert
Licensed User
Longtime User
If you'd like to post your app I'd be happy to run it on my device to see if I get the same issues. You can PM me and I'll give you my email if you don't want to post it.
 
Top