Android Question Noise detector by microphone

Lakhtin_V

Active Member
Licensed User
Longtime User
I want to make a noise detector. I need to periodically read the signal level from the microphone. Sound recording using the WAV MP3 standards is not important. It is not important for me to know what the microphone hears, the main thing is the noise level (sound volume). How can such a problem be solved?
 

emexes

Expert
Licensed User
It is not important for me to know what the microphone hears, the main thing is the noise level (sound volume). How can such a problem be solved?

Back to being serious 🍻

You might need to pretend to record the audio, and then I assume you'll get chunks of audio samples back, probably as Array As Byte (for 8-bit audio) or Array As Short (for 16-bit audio). If the arrays cover a long enough time period to cover multiple cycles of the dominant sound (100 ms would be great, 10 ms is getting a bit borderline) then my first thought would be to average the sample-to-sample differences (absolute, not signed) eg:

B4X:
Dim MicrophoneData() As Short = 'add code here that gets microphone audio sample

If MicrophoneData.Length > 0 Then
    Dim SumOfDifferences As Long = 0    'an Int would probably be large enough, but... to be sure, to be sure
    For I = 1 to MicrophoneData.Length - 1
        SumOfDifferences = SumOfDifferences + Abs(MicrophoneData(I) - MicrophoneData(I - 1))
    Next I
    Dim AverageDifferenceAkaSoundLevel As Int = SumOfDifferences / (MicrophoneData.Length - 1)
End If


edit: I'm mildly concerned that Abs() might always be Double math, not Int, in which case this might work better:
B4X:
        SumOfDifferences = SumOfDifferences + Bit.And(MicrophoneData(I) - MicrophoneData(I - 1), 0xFFFF)
 
Last edited:
Upvote 0

Johan Schoeman

Expert
Licensed User
Longtime User
 
Upvote 0

Lakhtin_V

Active Member
Licensed User
Longtime User
This code works. The volume level changes slightly. But does not respond to really loud sound. Values do not change with strong noise and silence.

Analysis of the volume level of the signal from the microphone:
#Region  Project Attributes
    #ApplicationLabel: Knock
    #VersionCode: 1
    #VersionName:
    #BridgeLogger: True
    '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 timer1 As Timer
    Private streamer As AudioStreamer
End Sub

Sub Globals
    'These global variables will be redeclared each time the activity is created.
    Dim buffers As List
    Dim Lbl As Button
    Dim btnPlay As Button
    Dim btnStartRec As Button
    Dim btnStopRec As Button
    Private rp As RuntimePermissions
'    Private recordingStart As Long
End Sub

Sub Activity_Create(FirstTime As Boolean)
    buffers.Initialize
    timer1.Initialize("timer1", 1000)
    Screen
End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)

End Sub

Sub Screen

    Lbl.Initialize("None")
    Lbl.Color=Colors.LightGray
    Lbl.Gravity=Gravity.FILL
    Lbl.Text="Время"
    Activity.AddView(Lbl, 20%x, 31%y, 60%x, 10%y)
    
    btnStartRec.Initialize("StartRec")
    btnStartRec.Color=Colors.Gray
    btnStartRec.Gravity=Gravity.FILL
    btnStartRec.Text="START"
    Activity.AddView(btnStartRec, 10%x, 2%y, 30%x, 10%y)
    
    btnStopRec.Initialize("StopRec")
    btnStopRec.Color=Colors.Gray
    btnStopRec.Gravity=Gravity.FILL
    btnStopRec.Text="STOP"
    Activity.AddView(btnStopRec, 50%x, 2%y, 30%x, 10%y)
    
    btnPlay.Initialize("Play")
    btnPlay.Color=Colors.Gray
    btnPlay.Gravity=Gravity.FILL
    btnPlay.Text="PLAY"
    Activity.AddView(btnPlay, 10%x, 16%y, 30%x, 10%y)

End Sub

Private Sub StartRec_Click
    rp.CheckAndRequest(rp.PERMISSION_RECORD_AUDIO)
    Wait For Activity_PermissionResult (Permission As String, Result As Boolean)
    If Result = False Then
        MsgboxAsync("No permission", "")
        Return
    End If
    If streamer.PlayerBufferSize = 0 Then
        streamer.Initialize("streamer", 44100, True, 16, streamer.VOLUME_MUSIC)
'        streamer.Initialize("streamer", 92000, True, 16, streamer.VOLUME_MUSIC)
    End If
    buffers.Clear
    streamer.StartRecording
    recordingStart = DateTime.Now
    timer1.Enabled = True
    Timer1_Tick
    btnPlay.Enabled = False
End Sub

Private Sub StopRec_Click
    streamer.StopRecording
    timer1.Enabled = False
    btnPlay.Enabled = True
    Lbl.Text = ""
End Sub

Private Sub Play_Click
    btnStartRec.Enabled = False
    streamer.StartPlaying
    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

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

Private Sub streamer_RecordBuffer (Buffer() As Byte)
    buffers.Add(Buffer)
End Sub

Sub streamer_Error
    Log(LastException)
End Sub

Private Sub Timer1_Tick
    Dim razmer As Long, v As Int, b As String, db As Float
    Log("tick")
'    Lbl.Text = "Recording: " & _
'    Round((DateTime.Now - recordingStart) / DateTime.TicksPerSecond) & " seconds"
    razmer=buffers.size
    If razmer>22 Then
        b=buffers.Get(razmer-1)
        v=Bit.ParseInt(b.SubString(4),16)
        db=Logarithm(v,10)
        Lbl.Text = "Size bufer " & razmer & "  Vol: " & db
    End If
'    Lbl.Text = sm.dBValue
End Sub
 
Upvote 0

emexes

Expert
Licensed User
This code works. The volume level changes slightly. But does not respond to really loud sound. Values do not change with strong noise and silence.

This makes me wonder if perhaps loud sounds with high (unsigned?) sample numbers are being interpreted as low (signed, negative) sample numbers and all of that is cancelling out to (approximately) zero.

I will see if I can get your code running here and check this hypothesis out. 🤔
 
Upvote 0

emexes

Expert
Licensed User
I will see if I can get your code running here and check this hypothesis out. 🤔

Righto, the only oddity so far is that I had to uncomment "Private recordingStart As Long" to make it compilable. 👍
Yikes wtf? it ran first try: recorded audio, and played it back too 🏆 now to check out sound level stuff 🔎
Buffers is a List of received recorded Byte()s - so I'll Log the lengths of those as they're received
That's working, so now to work out what format those bytes are in eg 8 or 16 bit samples, big or little endian, signed or unsigned, mono or stero
Lol I am such an Einstein 🙃 just realised most of that is specified in the AudioStreamer.Initialize call :rolleyes:
Righto mono 16 so now just need to work out if big or little endian, and signed (two's complement or sign+magnitude) or unsigned (presumably offset by 32768)
Looks like little endian two's complement = sounds like job for ByteConverter.ShortsFromBytes
Ok, after a few more off-the-track-and-into-the-bushes incidents :rolleyes: I think I've got enough to get you to the finish line. 🍻
 
Last edited:
Upvote 0

Lakhtin_V

Active Member
Licensed User
Longtime User
Righto, the only oddity so far is that I had to uncomment "Private recordingStart As Long" to make it compilable. 👍
Yikes wtf? it ran first try: recorded audio, and played it back too 🏆 now to check out sound level stuff 🔎
Buffers is a List of received recorded Byte()s - so I'll Log the lengths of those as they're received
That's working, so now to work out what format those bytes are in eg 8 or 16 bit samples, big or little endian, signed or unsigned, mono or stero
Lol I am such an Einstein 🙃 just realised most of that is specified in the AudioStreamer.Initialize call :rolleyes:
Righto mono 16 so now just need to work out if big or little endian, and signed (two's complement or sign+magnitude) or unsigned (presumably offset by 32768)
Looks like little endian two's complement = sounds like job for ByteConverter.ShortsFromBytes
in the buffer's element, the first 3 bytes are always the same [B@ . So I'm trying to parse the rest of the 7 byte string. But I don't know how to correctly decode the right-useful part of the buffer element. Simply converting to decimal is probably not the right solution.
 
Upvote 0

emexes

Expert
Licensed User
Ok, after a few more off-the-track-and-into-the-bushes incidents :rolleyes: I think I've got enough to get you to the finish line. 🍻

Add SlowDown global:
Sub Process_Globals
    'These global variables will be declared once when the application starts.
    'These variables can be accessed from all modules.
    Private xui As XUI
 
    Private timer1 As Timer
    Private streamer As AudioStreamer
 
    Dim SlowDown As Int = 0
End Sub

Add a few lines to the audio samples handler:
Private Sub streamer_RecordBuffer (Buffer() As Byte)
    buffers.Add(Buffer)
 
    SlowDown = SlowDown + Buffer.Length
    If SlowDown > 30000 Then
        SlowDown = SlowDown - 30000
    
        Dim bc As ByteConverter
        bc.LittleEndian = True
        Dim Sample16() As Short = bc.ShortsFromBytes(Buffer)
    
        Dim ThisSample As Short = Sample16(0)
        Dim MinSample As Short = ThisSample
        Dim MaxSample As Short = ThisSample
        Dim DiffSum As Long = 0
        For I = 1 To Sample16.Length - 1    'note starts from 1 not 0, so that there is always a previous sample
            Dim PreviousSample As Short = ThisSample
            ThisSample = Sample16(I)

            Dim Difference As Int = ThisSample - PreviousSample
            If Difference < 0 Then    'manual absolute to keep inner loop free of floating point operations
                Difference = -Difference
            End If
            DiffSum = DiffSum + Difference

            If ThisSample < MinSample Then
                MinSample = ThisSample
            else if ThisSample > MaxSample Then
                MaxSample = ThisSample
            End If
        Next
        Dim DiffAverage As Int = DiffSum / (Sample16.Length - 1)
    
        Log(DateTime.Now & TAB & Sample16.Length & TAB & MinSample & TAB & MaxSample & TAB & DiffAverage)
    End If
 
End Sub

Log output of me counting 1, 2, 3, 4, 5:
Logger connected to:  HMD Global Nokia C01 Plus
--------- beginning of system
--------- beginning of crash
--------- beginning of main
tick
1665559949653    1792    -81        105        17
1665559949970    1792    -49        56        7
tick
1665559950333    1792    -41        41        4
1665559950667    1792    -39        57        5
1665559951005    1792    -217    255        48
tick
1665559951349    1792    -516    367        51
1665559951684    1792    -1236    1699    71
1665559952003    1792    -130    111        16
tick
1665559952365    1792    -8554    5381    108
1665559952699    1792    -3947    4140    141
1665559953063    1792    -235    218        34
tick
1665559953382    1792    -145    104        17
1665559953716    1792    -81        87        12
1665559954079    1792    -11357    9402    261
tick
1665559954397    1792    -1281    1476    26
1665559954748    1792    -74        81        12
1665559955093    1792    -100    101        12
tick
1665559955415    1792    -18565    25289    773
1665559955795    1792    -1056    1148    53
1665559956111    1792    -85        95        12
tick
1665559956430    1792    -83        93        12
1665559956791    1792    -99        90        13
1665559957126    1792    -25817    23718    636
tick
1665559957444    1792    -528    398        26
1665559957808    1792    -83        75        12
1665559958142    1792    -3554    4522    50
tick
1665559958520    1792    -3402    2693    42
1665559958823    1792    -27953    27997    930
1665559959156    1792    -385    395        64
tick
1665559959521    1792    -109    116        22
1665559959839    1792    -184    182        31
1665559960173    1792    -148    261        21
tick
1665559960537    1792    -78        94        12
1665559960856    1792    -77        72        11
1665559961188    1792    -85        91        11
tick
1665559961552    1792    -81        96        12
 
Last edited:
Upvote 0

Lakhtin_V

Active Member
Licensed User
Longtime User
Add SlowDown global:
Sub Process_Globals
    'These global variables will be declared once when the application starts.
    'These variables can be accessed from all modules.
    Private xui As XUI
 
    Private timer1 As Timer
    Private streamer As AudioStreamer
 
    Dim SlowDown As Int = 0
End Sub

Add a few lines to the audio samples handler:
Private Sub streamer_RecordBuffer (Buffer() As Byte)
    buffers.Add(Buffer)
 
    SlowDown = SlowDown + Buffer.Length
    If SlowDown > 30000 Then
        SlowDown = SlowDown - 30000
     
        Dim bc As ByteConverter
        bc.LittleEndian = True
        Dim Sample16() As Short = bc.ShortsFromBytes(Buffer)
     
        Dim DiffSum As Long
        Dim PreviousSample As Short = Sample16(0)
        Dim MinSample As Short = PreviousSample   'save one array dereference - woohoo!
        Dim MaxSample As Short = PreviousSample
        For I = 1 To Sample16.Length - 1    'note starts from 1 not 0, so that
            Dim Difference As Int = Sample16(I) - Sample16(I - 1)
            If Difference < 0 Then
                Difference = -Difference
            End If
            DiffSum = DiffSum + Difference

            If Sample16(I) < MinSample Then
                MinSample = Sample16(I)
            else if Sample16(I) > MaxSample Then
                MaxSample = Sample16(I)
            End If
        Next
        Dim DiffAverage As Int = DiffSum / (Sample16.Length - 1)
     
        Log(DateTime.Now & TAB & Sample16.Length & TAB & MinSample & TAB & MaxSample & TAB & DiffAverage)
    End If
 
End Sub

Log output of me counting 1, 2, 3, 4, 5:
Logger connected to:  HMD Global Nokia C01 Plus
--------- beginning of system
--------- beginning of crash
--------- beginning of main
tick
1665559949653    1792    -81        105        17
1665559949970    1792    -49        56        7
tick
1665559950333    1792    -41        41        4
1665559950667    1792    -39        57        5
1665559951005    1792    -217    255        48
tick
1665559951349    1792    -516    367        51
1665559951684    1792    -1236    1699    71
1665559952003    1792    -130    111        16
tick
1665559952365    1792    -8554    5381    108
1665559952699    1792    -3947    4140    141
1665559953063    1792    -235    218        34
tick
1665559953382    1792    -145    104        17
1665559953716    1792    -81        87        12
1665559954079    1792    -11357    9402    261
tick
1665559954397    1792    -1281    1476    26
1665559954748    1792    -74        81        12
1665559955093    1792    -100    101        12
tick
1665559955415    1792    -18565    25289    773
1665559955795    1792    -1056    1148    53
1665559956111    1792    -85        95        12
tick
1665559956430    1792    -83        93        12
1665559956791    1792    -99        90        13
1665559957126    1792    -25817    23718    636
tick
1665559957444    1792    -528    398        26
1665559957808    1792    -83        75        12
1665559958142    1792    -3554    4522    50
tick
1665559958520    1792    -3402    2693    42
1665559958823    1792    -27953    27997    930
1665559959156    1792    -385    395        64
tick
1665559959521    1792    -109    116        22
1665559959839    1792    -184    182        31
1665559960173    1792    -148    261        21
tick
1665559960537    1792    -78        94        12
1665559960856    1792    -77        72        11
1665559961188    1792    -85        91        11
tick
1665559961552    1792    -81        96        12
 
Upvote 0

Lakhtin_V

Active Member
Licensed User
Longtime User
Thank you very much for the help! Everything works, until it is clear what value the number 30000 has, this is 30000 cycles with a frequency 44100 ?
from
streamer.Initialize("streamer", 44100, True, 16, streamer.VOLUME_MUSIC)
 
Upvote 0

Lakhtin_V

Active Member
Licensed User
Longtime User
If I want to control loudness in sections from 1/1000 to 1/100 of a second, how best to debug the streamer settings ?
 
Upvote 0

emexes

Expert
Licensed User
Thank you very much for the help! Everything works, until it is clear what value the number 30000 has, this is 30000 cycles with a frequency 44100 ?
from streamer.Initialize("streamer", 44100, True, 16, streamer.VOLUME_MUSIC)

The 30000 was just to slow down the sound level measurement/calculation/log rate. Otherwise the log queue fills up and there is massive lag between the microphone input and the log output.

30000 was an arbitrary choice - it means the sound level stuff is done every ~30000 bytes ie ~15000 samples ie about 3 times per second.

In your live realtime stuff, where you're not slowed down by debug mode or by the log speed, you'd do the sound level stuff on every received buffer, which on my phone is every 3584 bytes = 1792 samples = 44100/1792 times per second (mental math: 25 times per second).
 
Last edited:
Upvote 0

emexes

Expert
Licensed User
In your live realtime stuff, where you're not slowed down by debug mode or by the log speed, you'd do the sound level stuff on every received buffer, which on my phone is every 3584 bytes = 1792 samples = 44100/1792 times per second (mental math: 25 times per second).

Add ProgressBar in three lines:
Sub Globals
    'These global variables will be redeclared each time the activity is created.
    Dim buffers As List
    Dim Lbl As Button
    Dim btnPlay As Button
    Dim btnStartRec As Button
    Dim btnStopRec As Button
    Private rp As RuntimePermissions
    Private recordingStart As Long    'was commented out... but why?
    Dim SoundLevelBar As ProgressBar
End Sub

Sub Screen

    Lbl.Initialize("None")
    Lbl.Color=Colors.LightGray
    Lbl.Gravity=Gravity.FILL
    Lbl.Text="Время"
    Activity.AddView(Lbl, 20%x, 31%y, 60%x, 10%y)
 
    btnStartRec.Initialize("StartRec")
    btnStartRec.Color=Colors.Gray
    btnStartRec.Gravity=Gravity.FILL
    btnStartRec.Text="START"
    Activity.AddView(btnStartRec, 10%x, 2%y, 30%x, 10%y)
 
    btnStopRec.Initialize("StopRec")
    btnStopRec.Color=Colors.Gray
    btnStopRec.Gravity=Gravity.FILL
    btnStopRec.Text="STOP"
    Activity.AddView(btnStopRec, 50%x, 2%y, 30%x, 10%y)
 
    btnPlay.Initialize("Play")
    btnPlay.Color=Colors.Gray
    btnPlay.Gravity=Gravity.FILL
    btnPlay.Text="PLAY"
    Activity.AddView(btnPlay, 10%x, 16%y, 30%x, 10%y)

    SoundLevelBar.Initialize("SoundLevelBar")
    Activity.AddView(SoundLevelBar, 10%x, 60%y, 80%x, 10%y)

End Sub
Full Steam Ahead! (no slowing down for logs) (or icebergs) (and d*mn the torpedoes):
Private Sub streamer_RecordBuffer (Buffer() As Byte)
    buffers.Add(Buffer)
'    Log(Buffer.Length)
 
'    SlowDown = SlowDown + Buffer.Length
'    If SlowDown > 30000 Then
'        SlowDown = SlowDown - 30000
    
        Dim bc As ByteConverter
        bc.LittleEndian = True
        Dim Sample16() As Short = bc.ShortsFromBytes(Buffer)
    
        Dim ThisSample As Short = Sample16(0)
        Dim MinSample As Short = ThisSample
        Dim MaxSample As Short = ThisSample
        Dim DiffSum As Long
        For I = 1 To Sample16.Length - 1    'note starts from 1 not 0
            Dim PreviousSample As Short = ThisSample
            ThisSample = Sample16(I)

            Dim Difference As Int = ThisSample - PreviousSample
            If Difference < 0 Then
                Difference = -Difference
            End If
            DiffSum = DiffSum + Difference

            If ThisSample < MinSample Then
                MinSample = ThisSample
            else if ThisSample > MaxSample Then
                MaxSample = ThisSample
            End If
        Next
        Dim DiffAverage As Int = DiffSum / (Sample16.Length - 1)
'        Log(DiffAverage)
    
'        Log(DateTime.Now & TAB & Sample16.Length & TAB & MinSample & TAB & MaxSample & TAB & DiffAverage)
        SoundLevelBar.Progress = Logarithm(DiffAverage, 32768) * 100
'    End If
 
End Sub
 
Last edited:
Upvote 0

emexes

Expert
Licensed User
If I want to control loudness in sections from 1/1000 to 1/100 of a second, how best to debug the streamer settings ?

I don't know if you can control how frequently the streamer sends you blocks of audio samples. There is no obvious parameter for it exposed by AudioStreamer.
 
Upvote 0

Lakhtin_V

Active Member
Licensed User
Longtime User
I don't know if you can control how frequently the streamer sends you blocks of audio samples. There is no obvious parameter for it exposed by AudioStreamer.
I thought maybe if I increase the frequency in the streamer, then the element in the buffer will become shorter. What frequency values can be used to make the duration of the analyzed buffer element smaller. Perhaps some fixed standard frequencies should be used?
streamer.Initialize("streamer", 44100, True, 16, streamer.VOLUME_MUSIC)
 
Upvote 0

emexes

Expert
Licensed User
I thought maybe if I increase the frequency in the streamer, then the element in the buffer will become shorter. What frequency values can be used to make the duration of the analyzed buffer element smaller.

Are you trying to reduce the amount of data being processed? Is the 44100 samples per second loading the CPU and slowing down other programs?

Or are you trying to make it respond faster? On my phone, I'm getting new data 24 times per second, which seems fast enough for a noise detector.

Or are you trying to detect super-short sounds, like a clap or thunder or a gunshot, and averaging the audio level over 40 milliseconds is muffling it out?
 
Upvote 0
Top