10
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Posted at

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

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

##参考文献:
MediaRecorder の概要

#レコーダーアプリ

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

公式によると、

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

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

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

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

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

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

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>

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

##バックエンド

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

まずコードの全体です。

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, "再生中")
                    }
                }
            }
        }
    }
}

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

###認証許可


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なら

grantResults[0] == PackageManager.PERMISSION_GRANTED

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

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

##リスナ

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

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

ただし、【Lv1】イベントとリスナ【Android/Kotlin】のボタンイベント
同様、whenを使ってidごとに処理を分けていきます。

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, "再生中")
  }
}

###録音処理

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

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

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

    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() は、android.media.MediaRecorderのパブリックメソッドです。

今回は、録音開始と、

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

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

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

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

stop()を呼び出してnullを代入しています。

###音声の再生

音声の再生には、MediaPlayerを用います。

MediaRecorderのサンプルにMediaPlayerを用いて音声を再生するサンプルも含まれています。

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

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


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

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

で符号化したファイルを

setDataSource(fileName)

で読み込みます。

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

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

10
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?