レコーダーアプリを作ってみました。
録音機能だけみたいときはバックエンドの章だけみていただきたいです。
#環境
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)以降、危険な権限を使用するアプリでは、ランタイムにユーザーの承認を求める必要があります。
とのことなので追加してあげましょう。
<?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タグの直下に書いてやれば良いです。
##フロントエンド
フロントエンドを作っていきますが、最低限のボタンだけ配置します。
上から録音開始ボタン、録音停止ボタン、再生ボタンの3つだけ用意してやれば良いです。
位置とか結構適当なのでごめんなさい。
Buttonのidごとに処理を分けますので、idも適当なものをセットしてあげましょう。
コードにすると以下のようになります。
<?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>
これで雑ですがフロントエンドは完成とします。
##バックエンド
録音機能を実装していきましょう!
まずコードの全体です。
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が補完機能強くて使いやすいし、公式にサンプルが結構転がってるのでいろいろ遊べて楽しいです。