코틀린 안드로이드 - 녹음기2(녹음기능 구현)

Jamwon·2021년 7월 21일
0

Kotlin_Android

목록 보기
22/30
post-thumbnail

MediaRecorder

MediaRecorder 공식문서

오디오와 비디오를 record하는데 쓰인다.

일련의 과정들이 진행된다음에 녹음을 할 수있다.
위의 그림이 일반적인 과정.

MediaRecorder recorder = new MediaRecorder();
 recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
 recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
 recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
 recorder.setOutputFile(PATH_NAME);
 recorder.prepare();
 recorder.start();   // Recording is now started
 ...
 recorder.stop();
 recorder.reset();   // You can reuse the object by going back to setAudioSource() step
 recorder.release(); // Now the object cannot be reused
 

예시 코드
AudieEncoder는 마이크에서 들어온 soruce를 AMR_NB방식으로 압축한다는 의미이다.

안드로이드에서 지원하는 audio 포맷이 따로있다.
audio support 공식문서

점으로 표시된건 모든 버전에서 가능

recorder 만들어주기

startRecording()

    private var recorder:MediaRecorder? =null

    private val recordingFilePath :String by lazy {
        "${externalCacheDir?.absolutePath}/recording.3gp"
    }
    
  

두 프로퍼티를 사용!.

    private fun startRecording(){
        recorder = MediaRecorder().apply {
            setAudioSource(MediaRecorder.AudioSource.MIC)
            setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
            setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
            setOutputFile(recordingFilePath)
            prepare()
        }
        recorder?.start()
        state = State.ON_RECORDING
    }

레코딩을 시작하려면 여러가지 과정이 필요하다.

  1. audioSource를 지정해준다.
  2. outputFormat을 지정해준다.
  3. Encoder를 지정해준다.
  4. 녹음물이 저장될곳을 지정해준다.

외부 캐시 디렉토리에 임시적으로 저장할 것이다.
위에 선언해둔 외부 캐시 FilePath를 이용!

recorder?.start()로 녹음기를 실행시킬 수 있다.
그리고 state를 바꿔준다.

stopRecording

    private fun stopRecording(){
        recorder?.run{
            stop()
            release()
        }
        recorder =null
        state = State.AFTER_RECORDING
    }

recorder를 멈추고 메모리를 해제해준다음에 null로 초기화를 해준다.
state를 바꿔준다.

MediaPlayer

MediaPlayer 공식문서

media play의 기본적인 상태 diagram

startPlaying()

private var player: MediaPlayer? = null

플레이어 프로퍼티를 선언해준다.

    private fun startPlaying() {
        player = MediaPlayer().apply {
            setDataSource(recordingFilePath)
            prepare()
        }
        player?.start()
        state = State.ON_PLAYING
    }

함수 위에서 쓰였던 외부 캐시 저장소의 path를 가져다쓴다.

prepare()을 사용한다.
state도 바꿔준다!

stopPlaying()

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

플레이어는 stop를 해줄필요없이 바로 release해주는 것으로 멈출 수 있다. !!

플레이어도 멈추면 null로 초기화해준다.
그리고 state도 바꿔준다.

state변화시 버튼 바뀌기

    private var state = State.BEFORE_RECORDING
        set(value){
            field =value
            resetButton.isEnabled = (value == State.AFTER_RECORDING) ||
                    (value == State.ON_PLAYING)
            recordButton.updateIconWithState(value)
        }

set을 이용해서 state가 바뀔때 마다 recordbutton의 icon을 바꿔준다.

그리고 State에 따라 resetButton이 사용될수 있게 없게를 정해주는 resetButton.isEnabled를 사용한다.

bindView()

    private fun bindViews(){
    	resetButton.setOnClickListener {
            stopPlaying()
            state =State.BEFORE_RECORDING

        }
        recordButton.setOnClickListener{
            when(state){
                State.BEFORE_RECORDING->{
                    startRecording()
                }
                State.ON_RECORDING->{
                    stopRecording()
                }
                State.AFTER_RECORDING->{
                    startPlaying()
                }
                State.ON_PLAYING->{
                    stopPlaying()
                }
            }
        }
    }

각각 recordButton의 icon도 바뀌었지만 그에 해당하는 기능도 연결해줘야하기때문에 recordButton의 onClickListener를 state로 나눠서 기능을 지정해준다.

iniVaribales()

state를 초기값으로 설정하하는 함수이다.

MaiActivity.kt

class MainActivity : AppCompatActivity() {

    private val resetButton: Button by lazy {
        findViewById(R.id.resetButton)
    }

    private val recordButton: RecordButton by lazy {
        findViewById(R.id.recordButton)
    }

    //필요한 권한 선언
    private val requiredPermissions = arrayOf(Manifest.permission.RECORD_AUDIO)

    //초기 state 설정
    private var state = State.BEFORE_RECORDING
        set(value) {
            field = value
            resetButton.isEnabled = (value == State.AFTER_RECORDING) ||
                    (value == State.ON_PLAYING)
            recordButton.updateIconWithState(value)
        }

    //recorder
    private var recorder: MediaRecorder? = null

    private val recordingFilePath: String by lazy {
        "${externalCacheDir?.absolutePath}/recording.3gp"
    }

    //player
    private var player: MediaPlayer? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        requestAudioPermission()
        initViews()
        bindViews()
        initVariables()
    }

    private fun requestAudioPermission() {
        requestPermissions(requiredPermissions, REQUEST_RECORD_AUDIO_PERMISSION)

    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)

        //권한이 부여받은게 맞는지 check 권한부여받았으면 true 아니면 false
        val audioRequestPermissionGranted =
            requestCode == REQUEST_RECORD_AUDIO_PERMISSION &&
                    grantResults.firstOrNull() == PackageManager.PERMISSION_GRANTED

        //권한이 부여되지않으면 어플 종료
        if (!audioRequestPermissionGranted) {
            finish()
        }
    }

    private fun initViews() {
        recordButton.updateIconWithState(state)
    }

    //녹음 할 수 있는 상태 만들기
    private fun startRecording() {
        recorder = MediaRecorder().apply {
            setAudioSource(MediaRecorder.AudioSource.MIC)
            setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
            setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
            setOutputFile(recordingFilePath)
            prepare()
        }
        recorder?.start()
        state = State.ON_RECORDING
    }

    private fun stopRecording() {
        recorder?.run {
            stop()
            release()
        }
        recorder = null
        state = State.AFTER_RECORDING
    }

    private fun startPlaying() {
        player = MediaPlayer().apply {
            setDataSource(recordingFilePath)
            prepare()
        }
        player?.start()
        state = State.ON_PLAYING
    }

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

    private fun bindViews() {
        resetButton.setOnClickListener {
            stopPlaying()
            state =State.BEFORE_RECORDING

        }
        recordButton.setOnClickListener {
            when (state) {
                State.BEFORE_RECORDING -> {
                    startRecording()
                }
                State.ON_RECORDING -> {
                    stopRecording()
                }
                State.AFTER_RECORDING -> {
                    startPlaying()
                }
                State.ON_PLAYING -> {
                    stopPlaying()
                }
            }
        }
    }
    private fun initVariables(){
        state = State.BEFORE_RECORDING
    }

    companion object {
        //permission code 선언
        private const val REQUEST_RECORD_AUDIO_PERMISSION = 201
    }
}

결과화면

gif라 소리는 안들리지만 잘 작동한다!

새로 배운것

MediaRecorder/ MediaPlayer

녹음기와 재생기 녹음기에는 지원하는 코텍등이있어서 참고해서 녹음기를 만들어줘야한다.

recorder = MediaRecorder().apply {
            setAudioSource(MediaRecorder.AudioSource.MIC)
            setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
            setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
            setOutputFile(recordingFilePath)
            prepare()
        }
        recorder?.start()
        state = State.ON_RECORDING
    }

이런식으로 생성해서 사용할수 있다.

  private fun startPlaying() {
        player = MediaPlayer().apply {
            setDataSource(recordingFilePath)
            prepare()
        }
        player?.start()
        state = State.ON_PLAYING
    }

mediaplayer는 이런식으로

set(value)

강의에서 약간 그냥 슉슉 넘어갔다

아무튼 데이터를 개조하고 싶을때 사용한다.

set()이나 get()을 이용해서 set() 내부에서 속성값을 직접 가져오거나 변경이 가능하다고 한다.

getter와 setter

이 블로그 참조
설명을 잘해놓으셧다.

var은 변경 가능한 속성이기 때문에 getter와 setter 메소드를 가질수 있고
val 은 불변이기 때문에 getter메소드만 가질 수 있다.

class MyList {
    var size: Int? = 0
    val isEmpty: Boolean
        get() = this.size == 0
}

이와 같은 함수가 다고 하면
Mylist 객체의 isEmpty라는 속성에 접근할 때에 Mylist객체의 size속성값이 0이면 true를 return 아니면 false를 return 하는 것이라고 한다.

class Person {
    var name: String = "Not Assigned"
        set(value) {
            field = "Dev." + value
        }
}

fun main(args: Array<String>) {
    val person = Person()
    person.name = "Ready"
 
    println(person.name)
}
 

setter에선 Backing Field라고 불리는 field가 있다.
kotlin에선 클래스 안에서 직접적으로 field에 대해 선언할 수는 없지만 필요할떄 자동으로 field라는 식별자를 제공한다.

위의 코드에서 field가 말하는건 'name' 이다.

set(value)에서 value는 'name'의 값이다.

즉 위 코드를 실행하면 Dev.Ready가 출력된다. !!!

이런 중요한걸 몰랐다니...아무튼 잘 알아두자...

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
번외로 when(state) 문도 유용하게 쓰일거 같다.
state에 따라 실행하는 기능이 다르다거나..아무튼 디자인 패턴의 state 패턴이 떠오른다..

다음시간에는 녹음물 비쥬얼라이저!

모두 화이팅!!

profile
한걸음씩 위로 자유롭게

0개의 댓글