» » Record sound and display volume on Android

Record sound and display volume on Android

If you've ever recorded audio messages, you've seen the interface animate based on the volume of your voice. Let's repeat this effect:

 

Microphone access


To start recording inside an Android app, you must first give it access to this functionality. Let's create a tag in the Android manifest uses-permissionand specify the permission RECORD_AUDIO:

<uses-permission android:name="android.permission.RECORD_AUDIO" />


We also have to request it at runtime with ActivityCompat.requestPermission:

ActivityCompat.requestPermissions(
    this,
    arrayOf(android.Manifest.permission.RECORD_AUDIO),
    777,
)



Sound recording


Let's create a class to record sound RecordController. It should have two main methods: startand stop. The AAC codec is well suited for voice recording:

fun start() {
    Log.d(TAG, "Start")
    audioRecorder = MediaRecorder().apply{
        setAudioSource(MediaRecorder.AudioSource.MIC)
        setOutputFormat(MediaRecorder.OutputFormat.AAC_ADTS)
        setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
        setOutputFile(getAudioPath())
        prepare()
        start()
        }
}


We will record audio in a temporary directory:

private fun getAudioPath(): String {
    return "${context.cacheDir.absolutePath}${File.pathSeparator}${System.currentTimeMillis()}.wav"
}


After the end of the recording, you need to apply to the media recorder stopand release:

fun stop() {
    audioRecorder?.let {
        Log.d(TAG, "Stop")
        it.stop()
        it.release()
    }
    audioRecorder = null
  }


To work with a state, it is necessary to receive it somehow. To do this, we will make a function that will say whether a recording is currently being made:

fun isAudioRecording() = audioRecorder != null

 

Record button


Now let's make a record button. Create a new Viewone and position it in the center of the screen. Why is it not a button, Viewhuh? Because we will animate it now and we do not need the standard visual effects of clicking, shadows and other things.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="<http://schemas.android.com/apk/res/android>"
    xmlns:app="<http://schemas.android.com/apk/res-auto>"
    xmlns:tools="<http://schemas.android.com/tools>"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <View
        android:id="@+id/start_button"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="@drawable/oval"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>


By clicking on the button, we will execute methods startor stop. On the first click, we start recording, and on the second click, we stop it, so we take the state from the recorder, depending on which we apply the necessary logic:

private fun onButtonclicked() {
    if (recordController.isAudioRecording()) {
        recordController.stop()
    } else {
        recordController.start()
    }
}


If we run the application now, the button will be clicked and even audio recording will work, but this is not visually displayed in any way.

Volume


Now let's do the recording volume tracking. Let's add the corresponding function:

fun getVolume() = audioRecorder?.maxAmplitude ?: 0


This function returns the maximum value since the last call.

At the beginning of the recording, we activate the timer to poll the volume. Let it take data every 100 ms:

private fun onButtonclicked() {
    if (recordController.isAudioRecording()) {
        recordController.stop()
        countDownTimer?.cancel()
        countDownTimer = null
    } else {
        recordController.start()
        countDownTimer = object : CountDownTimer(60_000, 100) {
            override fun onTick(p0: Long) {
                val volume = recordController.getVolume()
                Log.d(TAG, "Volume = $volume")
                handleVolume(volume)
            }

            override fun onFinish() {
            }
        }.apply{
            start()
        }
    }
}

 

Animation


Now let's solve the following problem: when the button is pressed, it is not visually clear whether the recording is in progress and how loudly. Let's create a method handleVolumethat reacts to the volume and changes the size of the button. There Vieware many ways to animate, the simplest is this animate, which allows you to set simple animations very conveniently.

How big should the button be? MediaRecorderreturns the volume value as a 16-bit int with a maximum value of 32767. Let's calculate how far we are from this limit in order to proportionally increase the button:

private fun handleVolume(volume: Int) {
    val scale = min(8.0, volume / MAX_RECORD_AMPLITUDE + 1.0).toFloat()
    Log.d(TAG, "Scale = $scale")

    audioButton.animate()
        .scaleX(scale)
        .scaleY(scale)
        .setInterpolator(interpolator)
        .duration= VOLUME_UPDATE_DURATION
}


Interestingly, this animation works automatically: if we run several times in a loop animate, then the overlay will not occur, each new animation will complete the previous one. Just remember to end recording and animation in methods onDestroyor onpausein case of screen rotation or other events related to Activity.

For a more lively animation, let's use OvershootInterpolator'om, it allows you to go beyond the limits of the available range: the button will seem to pulsate, briefly going beyond the upper limit

Everything turned out to be quite simple. Instead of resizing, you can draw a volume histogram or something else that comes to your mind or your designer


Related Articles

Add Your Comment

reload, if the code cannot be seen

All comments will be moderated before being published.