簡単なレコーダーアプリを作る【Android/Kotlin】


レコーダーアプリを作ってみました。
録音機能だけみたいときはバックエンドの章だけみていただきたいです。


#環境
macOS Big Sur version 11.0.1
Android Studio 4.1.1
kotlin 1.4.10
実機: Pixel 4a (5G)


##参考文献：
・[MediaRecorder の概要](https://developer.android.com/guide/topics/media/mediarecorder?hl=ja)



#レコーダーアプリ



##権限の追加
録音機能を使うには、まず、アプリのマニフェストファイルに権限を追加する必要があるらしいです。

公式によると、
>RECORD_AUDIO はユーザーのプライバシーを危険にさらすおそれがあるため、「危険な」権限とみなされます。Android 6.0（API レベル 23）以降、危険な権限を使用するアプリでは、ランタイムにユーザーの承認を求める必要があります。

とのことなので追加してあげましょう。

```xml:AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.ru_1218.myrecoder">
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyRecoder">

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
```

manifestタグの直下に書いてやれば良いです。


##フロントエンド
フロントエンドを作っていきますが、最低限のボタンだけ配置します。

![スクリーンショット 2020-11-15 9.30.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573500/2a813ed7-1b14-f46a-3938-7e214632dcf9.png)

上から録音開始ボタン、録音停止ボタン、再生ボタンの3つだけ用意してやれば良いです。

位置とか結構適当なのでごめんなさい。

Buttonのidごとに処理を分けますので、idも適当なものをセットしてあげましょう。
コードにすると以下のようになります。

```xml:activity_main.xml
<?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">

    <Button
        android:id="@+id/redord"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="120dp"
        android:layout_marginTop="50dp"
        android:text="@string/record_button"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/stop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="120dp"
        android:layout_marginTop="12dp"
        android:text="@string/stop_button"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/redord" />

    <Button
        android:id="@+id/playback"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="120dp"
        android:layout_marginTop="50dp"
        android:text="@string/PLAYBACK"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/stop" />

</androidx.constraintlayout.widget.ConstraintLayout>
```


これで雑ですがフロントエンドは完成とします。


##バックエンド

録音機能を実装していきましょう!

まずコードの全体です。

```kotlin:MainActivity.kt

package com.example.ru_1218.myrecoder

import android.Manifest
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.content.pm.PackageManager
import android.media.MediaPlayer
import android.media.MediaRecorder
import java.io.IOException
import android.util.Log
import android.widget.Button
import androidx.core.app.ActivityCompat

private const val LOG_TAG = "AudioRecordTest"
private const val REQUEST_RECORD_AUDIO_PERMISSION = 200

class MainActivity : AppCompatActivity() {
    private var recorder: MediaRecorder? = null
    private var fileName: String = ""

    private var player: MediaPlayer? = null


    private var permissionToRecordAccepted = false
    private var permissions: Array<String> = arrayOf(Manifest.permission.RECORD_AUDIO)

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        permissionToRecordAccepted = if (requestCode == REQUEST_RECORD_AUDIO_PERMISSION) {
            grantResults[0] == PackageManager.PERMISSION_GRANTED
        } else {
            false
        }
        if (!permissionToRecordAccepted) finish()
    }

    private fun onRecord(start: Boolean) = if (start) {
        startRecording()
    } else {
        stopRecording()
    }

    private fun onPlay(start: Boolean) = if (start) {
        startPlaying()
    } else {
        stopPlaying()
    }

    private fun startPlaying() {
        player = MediaPlayer().apply {
            try {
                setDataSource(fileName)
                prepare()
                start()
            } catch (e: IOException) {
                Log.e(LOG_TAG, "prepare() failed")
            }
        }
    }

    private fun stopPlaying() {
        player?.release()
        player = null
    }





    private fun startRecording() {
        recorder = MediaRecorder().apply {
            setAudioSource(MediaRecorder.AudioSource.MIC)
            setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
            setOutputFile(fileName)
            setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)

            try {
                prepare()
            } catch (e: IOException) {
                Log.e(LOG_TAG, "prepare() failed")
            }

            start()
        }
    }

    private fun stopRecording() {
        recorder?.apply {
            stop()
            release()
        }
        recorder = null
    }


    override fun onCreate(savedInstanceState: Bundle?) {
        fileName = "${externalCacheDir?.absolutePath}/audiorecordtest.3gp"
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        ActivityCompat.requestPermissions(this, permissions, REQUEST_RECORD_AUDIO_PERMISSION)

        val record = findViewById<Button>(R.id.redord) //録音オブジェクト取得
        val stop = findViewById<Button>(R.id.stop) //録音停止オブジェクト取得
        val playback = findViewById<Button>(R.id.playback) //再生オブジェクト取得

        val listener = RecordButton() //レコードボタンリスナのインスタンス生成

        record.setOnClickListener(listener) //レコードボタンリスナの設定
        stop.setOnClickListener(listener)
        playback.setOnClickListener(listener)

    }

    //クリックイベントの設定
    private inner class RecordButton : View.OnClickListener {
        override fun onClick(v: View?) {
            Log.i(LOG_TAG, "クリック成功")
            Log.i(LOG_TAG, fileName)

            if(v != null){
                when(v.id){
                    //録音開始ボタン
                    R.id.redord -> {
                        onRecord(true)
                        Log.i(LOG_TAG, "録音開始")
                    }
                    //録音停止ボタン
                    R.id.stop -> {
                        onRecord(false)
                        Log.i(LOG_TAG, "録音終了")
                    }

                    R.id.playback -> {
                        onPlay(true)
                        Log.i(LOG_TAG, "再生中")
                    }
                }
            }
        }
    }
}
```

ほぼほぼ公式の丸写しなのですが簡単に解説していきます。

###認証許可

```kotlin

override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        permissionToRecordAccepted = if (requestCode == REQUEST_RECORD_AUDIO_PERMISSION) {
            grantResults[0] == PackageManager.PERMISSION_GRANTED
        } else {
            false
        }
        if (!permissionToRecordAccepted) finish()
    }
```

雑に説明するとrequestCodeを受け取ってそれが200なら

```kotlin
grantResults[0] == PackageManager.PERMISSION_GRANTED
```

でbool値を求め、permissionToRecordAcceptedに代入します。
結果、permissionToRecordAcceptedがtrueなら

オブジェクト取得->リスナのインスタンス生成->リスナの設定と処理が進んでいきます。

##リスナ

今回は、録音、停止、再生の3つのリスナを設定していきます。

```kotlin
record.setOnClickListener(listener)
stop.setOnClickListener(listener)
playback.setOnClickListener(listener)

```

ただし、[【Lv1】イベントとリスナ【Android/Kotlin】のボタンイベント](https://qiita.com/orca_div_/items/7da3c6d691271f3a5eb7#2個目のボタンイベントを追加)
同様、whenを使ってidごとに処理を分けていきます。


```kotlin
when(v.id){
  //録音開始ボタン
  R.id.redord -> {
      onRecord(true)
      Log.i(LOG_TAG, "録音開始")
  }
  //録音停止ボタン
  R.id.stop -> {
      onRecord(false)
      Log.i(LOG_TAG, "録音終了")
  }

  R.id.playback -> {
      onPlay(true)
      Log.i(LOG_TAG, "再生中")
  }
}
```

###録音処理


```kotlin
private fun onRecord(start: Boolean) = if (start) {
    startRecording()
} else {
    stopRecording()
}
```

onRecordメソッドはbool値を取るので、trueな録音開始、falseなら停止とハンドラを分けることができます。

開始に使うstartRecordingメソッドはこれです。
公式通りですね。

```kotlin
    private fun startRecording() {
        recorder = MediaRecorder().apply {
            setAudioSource(MediaRecorder.AudioSource.MIC)
            setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
            setOutputFile(fileName)
            setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)

            try {
                prepare()
            } catch (e: IOException) {
                Log.e(LOG_TAG, "prepare() failed")
            }

            start()
        }
    }
```
必要なオプション？をセットして、start()で録音開始です。
[start()](https://developer.android.com/reference/android/media/MediaRecorder?hl=ja#start()) は、android.media.MediaRecorderのパブリックメソッドです。

今回は、録音開始と、

```kotlin
fileName = "${externalCacheDir?.absolutePath}/audiorecordtest.3gp"
setOutputFile(fileName)
```

録音したデータを符号化して保存する役割を持ちます。
ただし、事前にprepare()を呼び出しておくことが条件なようです。


録音停止はシンプルですね。

```kotlin
private fun stopRecording() {
        recorder?.apply {
            stop()
            release()
        }
        recorder = null
    }
```
[stop()](https://developer.android.com/reference/android/media/MediaRecorder?hl=ja#stop())を呼び出してnullを代入しています。


###音声の再生

音声の再生には、[MediaPlayer](https://developer.android.com/guide/topics/media/mediaplayer?hl=ja)を用います。

[MediaRecorder](https://developer.android.com/guide/topics/media/mediarecorder?hl=ja)のサンプルにMediaPlayerを用いて音声を再生するサンプルも含まれています。


そのまんま使っております。

```kotlin
    private fun startPlaying() {
        player = MediaPlayer().apply {
            try {
                setDataSource(fileName)
                prepare()
                start()
            } catch (e: IOException) {
                Log.e(LOG_TAG, "prepare() failed")
            }
        }
    }


```

startRecordingメソッドより非常にシンプルです。

```kotlin
fileName = "${externalCacheDir?.absolutePath}/audiorecordtest.3gp"
setOutputFile(fileName)
```
で符号化したファイルを

```kotlin
setDataSource(fileName)
```
で読み込みます。

それからMediaPlayerのstartメソッドで再生します。


####まとめ
android studioが補完機能強くて使いやすいし、公式にサンプルが結構転がってるのでいろいろ遊べて楽しいです。