Android音視頻入門(一):音頻的錄制和播放

Android音視頻入門(一):音頻的錄制和播放

一、前言

當(dāng)我們使用各種播放器,系統(tǒng)API來完成音視頻播放和錄制的時候,其實底層已經(jīng)幫我做了很多看不到的工作。比如如何進行采集、編解碼、合成、壓縮等一系列工作,各種庫其實都已經(jīng)幫我實現(xiàn)了,我們只需要按規(guī)范調(diào)用API來完成我們想要的功能。但是在未來的學(xué)習(xí)過程中我們會一一的深入內(nèi)部去探討各個知識點。

下面是整理的關(guān)于android音視頻開發(fā)的學(xué)習(xí)腦圖,分別把采集、渲染、編碼、傳輸相關(guān)的知識點拆分了下,通過這個圖可以大概的了解目前的學(xué)習(xí)位置。

知識大概
image

demo截屏

image

image

二、使用系統(tǒng)API完成音視頻采集和播放

2.1 播放音視頻文件

在android開發(fā)中系統(tǒng)的api已經(jīng)提供了一個叫MediaRecorder的類給開發(fā)者來完成音視頻的播放,底層的一系列細(xì)節(jié)都被封裝了起來,所以我們現(xiàn)在使用api完成體驗一下成功的感覺。

2.1.1 MediaPlayer 概覽

MediaPlayer官方介紹

Android 多媒體框架支持播放各種常見媒體類型,以便您輕松地將音頻、視頻和圖片集成到應(yīng)用中。您可以使用 MediaPlayer API,播放存儲在應(yīng)用資源(原始資源)內(nèi)的媒體文件、文件系統(tǒng)中的獨立文件或者通過網(wǎng)絡(luò)連接獲得的數(shù)據(jù)流中的音頻或視頻。

下面我們使用MediaPlayer來完成音視頻的播放,播放源支持網(wǎng)絡(luò)文件和本地文件。

配置清單文件的權(quán)限

    <uses-permission android:name="android.permission.INTERNET" />
    
    //喚醒鎖定權(quán)限 - 如果播放器應(yīng)用需要防止屏幕變暗或處理器進入休眠狀態(tài),或者要使用 MediaPlayer.setScreenOnWhilePlaying() 或 MediaPlayer.setWakeMode() 方法,則您必須申請此權(quán)限。
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    
    

部分核心代碼

 //播放視頻
 var mediaPlayer: MediaPlayer? = MediaPlayer()
    private fun startMedia() {
        if (!mediaPlayer?.isPlaying!!) {
            //設(shè)置播放源 支持本地文件和網(wǎng)絡(luò)文件
            mediaPlayer?.setDataSource("https://rbv01.ku6.com/wifi/o_1dv2knrei18ju12l7l2vgj210oe11kvs")
            //開始異步準(zhǔn)備
            mediaPlayer?.prepareAsync()
            //設(shè)置監(jiān)聽
            mediaPlayer?.setOnPreparedListener {
                //開始播放
                mediaPlayer?.start()
            }
        }
    }

    //播放音頻
    private fun startAudio() {
        if (!mediaPlayer?.isPlaying!!) {
            val path = "$filesDir/record_mp3/vick.aac"
            mediaPlayer?.setDataSource(this, Uri.parse(path))
            mediaPlayer?.prepareAsync()
            mediaPlayer?.setOnPreparedListener {
                mediaPlayer?.start()
            }
        }
    }

2.1.2 AudioTrack 概覽

AudioTrack只支持wav格式的音頻文件,因為wav格式的音頻文件大部分都是PCM流。AudioTrack不創(chuàng)建解碼器,所以只能播放不需要解碼的wav文件。

MediaPlayer在framework層還是會創(chuàng)建AudioTrack,把解碼后的PCM數(shù)流傳遞給AudioTrack,AudioTrack再傳遞給AudioFlinger進行混音,然后才傳遞給硬件播放,所以是MediaPlayer包含了AudioTrack。

部分核心代碼

class MeAudioTrack : PlayerAudioI {
    private var audioData: ByteArray? = null
    var audioTrack: AudioTrack? = null
    override fun initData() {
        //通過 write 寫文件

    }

    override fun start(filePath: String) {
        //讀文件
        stop()
//        play1(filePath)
        playInModeStream(filePath)


    }

    var mHandler: Handler = Handler()
    var mRunnable: Runnable = Runnable {
        playInModeStatic()
    }

    /**
     * 播放,使用stream模式
     */
    private fun playInModeStream(path: String) {
        /*
        * SAMPLE_RATE_INHZ 對應(yīng)pcm音頻的采樣率
        * channelConfig 對應(yīng)pcm音頻的聲道
        * AUDIO_FORMAT 對應(yīng)pcm音頻的格式
        * */
        val channelConfig = AudioFormat.CHANNEL_OUT_MONO
        val minBufferSize =
            AudioTrack.getMinBufferSize(SAMPLE_RATE_INHZ, channelConfig, AUDIO_FORMAT)
        audioTrack = AudioTrack(
            AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                .build(),
            AudioFormat.Builder().setSampleRate(SAMPLE_RATE_INHZ)
                .setEncoding(AUDIO_FORMAT)
                .setChannelMask(channelConfig)
                .build(),
            minBufferSize,
            AudioTrack.MODE_STREAM,
            AudioManager.AUDIO_SESSION_ID_GENERATE
        )
        audioTrack?.play()

        val file = File(path)
        try {
            val fileInputStream = FileInputStream(file)
            Thread(Runnable {
                try {
                    val tempBuffer = ByteArray(minBufferSize)
                    while (fileInputStream.available() > 0) {
                        val readCount = fileInputStream.read(tempBuffer)
                        if (readCount == AudioTrack.ERROR_INVALID_OPERATION || readCount == AudioTrack.ERROR_BAD_VALUE) {
                            continue
                        }
                        if (readCount != 0 && readCount != -1) {
                            audioTrack?.write(tempBuffer, 0, readCount)
                        }
                    }
                } catch (e: IOException) {
                    e.printStackTrace()
                }
            }).start()

        } catch (e: IOException) {
            e.printStackTrace()
        }

    }

    private fun playInModeStatic() {
        audioTrack = AudioTrack(
            AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                .build(),
            AudioFormat.Builder().setSampleRate(22050)
                .setEncoding(AudioFormat.ENCODING_PCM_8BIT)
                .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
                .build(),
            audioData?.size!!,
            AudioTrack.MODE_STATIC,
            AudioManager.AUDIO_SESSION_ID_GENERATE
        )
        audioTrack?.write(audioData, 0, audioData?.size!!)
        audioTrack?.play()
    }

    private fun play1(path: String) {
        // static模式,需要將音頻數(shù)據(jù)一次性write到AudioTrack的內(nèi)部緩沖區(qū)
        Thread {
            audioData = FileUtils.readFile2Bytes(path)
            mHandler.post(mRunnable)
        }.run { start() }

    }

    override fun pause() {
    }

    override fun stop() {
        audioTrack?.stop()
        audioTrack?.release()
    }

}

2.2 采集音視頻

Android提供了兩套音頻采集的API,MediaRecorder 和 AudioRecord前者是一個更加上層一點的API。

2.2.1 MediaRecorder

MediaRecorder 概覽

MediaRecorder類似于mediaPlayer是一個偏向上層的api,支持音頻的采集和視頻的采集。

MediaRecorder官方介紹

部分核心代碼

 private var mediaRecorder: MediaRecorder? = null
    override fun start() {
        mediaRecorder = MediaRecorder()
        //輸出目錄
        mediaRecorder?.setOutputFile(getPath())
        //設(shè)置采集源
        mediaRecorder?.setAudioSource(MediaRecorder.AudioSource.MIC)
        //設(shè)置輸出格式 3GPP media file format
        mediaRecorder?.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
        //編解碼的格式 AMR (Narrowband) audio codec 
        mediaRecorder?.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
        mediaRecorder?.prepare()
        mediaRecorder?.start()
    }

    override fun stop() {
        mediaRecorder?.stop()
        mediaRecorder?.release()
        mediaRecorder = null

    }
    
    private fun getPath(): String {
        val path =
            mContext?.getExternalFilesDir("media").toString() + File.separator
        mFileName = System.currentTimeMillis().toString() + ".aac"
        Log.i("VICK", path + mFileName)
        mFilePath = path + mFileName
        return mFilePath
    }

2.2.2 AudioRecord采集

部分核心代碼

class AudioRecorder : AudioRecorderI {


    /**
     * 采樣率,現(xiàn)在能夠保證在所有設(shè)備上使用的采樣率是44100Hz, 但是其他的采樣率(22050, 16000, 11025)在一些設(shè)備上也可以使用。
     */
    val SAMPLE_RATE_INHZ = 44100

    /**
     * 聲道數(shù)。CHANNEL_IN_MONO and CHANNEL_IN_STEREO. 其中CHANNEL_IN_MONO是可以保證在所有設(shè)備能夠使用的。
     */
    val CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO
    /**
     * 返回的音頻數(shù)據(jù)的格式。 ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, and ENCODING_PCM_FLOAT.
     */
    val AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT

    var isRecord = false
    var mContext: Context? = null
    override fun getFilePath(): String {
        return mFilePath
    }

    var mFileName = ""
    var mFilePath = ""
    override fun getFileName(): String {
        return mFileName
    }

    override fun initData(context: Context) {
        mContext = context
    }

    var audioRecord: AudioRecord? = null
    override fun start() {
        //獲得最小緩沖區(qū)大小,用于記錄音頻所用。
        val minBufferSize = getMinBufferSize(
            44100,
            AudioFormat.CHANNEL_IN_MONO,
            AudioFormat.ENCODING_PCM_16BIT
        )
        //構(gòu)建錄音對象
        audioRecord = AudioRecord(
            MediaRecorder.AudioSource.MIC, 44100, AudioFormat.CHANNEL_IN_MONO,
            AudioFormat.ENCODING_PCM_16BIT, minBufferSize
        )
        mFileName = System.currentTimeMillis().toString() + ".acc"
        val byteArray = ByteArray(minBufferSize)
        val file = File(mContext?.getExternalFilesDir("media"), mFileName)
        mFilePath = file.toString()
        audioRecord?.startRecording()
        isRecord = true
        //開啟線程寫入錄音數(shù)據(jù)
        Thread {
            var outputStream: FileOutputStream? = null
            try {
                outputStream = FileOutputStream(file)
                while (isRecord) {
                    //audioRecord通過  read()   方法來讀取數(shù)據(jù)
                    val read = audioRecord?.read(byteArray, 0, minBufferSize)
                    //不出錯就寫入文件
                    if (AudioRecord.ERROR_INVALID_OPERATION != read) {
                        outputStream.write(byteArray)
                    }
                }

            } catch (e: Exception) {
                e.printStackTrace()
            } finally {
                outputStream?.close()
            }


        }.start()
    }

    override fun pause() {
    }

    override fun stop() {
        isRecord = false
        audioRecord?.stop()
        audioRecord?.release()
        audioRecord = null
    }

    override fun destroy() {
    }

}

2.2.3 采集相機數(shù)據(jù)

SurfaceView或TextureView采集相機數(shù)據(jù)

class CmaraActivity : AppCompatActivity(), SurfaceHolder.Callback {
    override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {


    }

    override fun surfaceDestroyed(holder: SurfaceHolder?) {
    }

    override fun surfaceCreated(holder: SurfaceHolder?) {
        camera?.setPreviewDisplay(holder)
        camera?.startPreview()

    }


    var surfaceView: SurfaceView? = null
    var textureView: TextureView? = null
    var camera: android.hardware.Camera? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.camera_activity)
        surfaceView = findViewById(R.id.surface)
        textureView = findViewById(R.id.texture)
        camera = android.hardware.Camera.open()
        camera?.setDisplayOrientation(90)
        val parameters = camera?.parameters
        parameters?.pictureFormat = ImageFormat.NV21
        camera?.parameters = parameters
        camera?.setPreviewCallback(object : android.hardware.Camera.PreviewCallback {
            override fun onPreviewFrame(data: ByteArray?, camera: android.hardware.Camera?) {
                Log.i("vick", data.toString())
            }

        })

//        surfaceView?.holder?.addCallback(this)
        textureView?.surfaceTextureListener = object : TextureView.SurfaceTextureListener {
            override fun onSurfaceTextureSizeChanged(
                surface: SurfaceTexture?,
                width: Int,
                height: Int
            ) {

            }

            override fun onSurfaceTextureUpdated(surface: SurfaceTexture?) {
            }

            override fun onSurfaceTextureDestroyed(surface: SurfaceTexture?): Boolean {
                camera?.release()
                return false
            }

            override fun onSurfaceTextureAvailable(
                surface: SurfaceTexture?,
                width: Int,
                height: Int
            ) {
                camera?.setPreviewTexture(surface)
                camera?.startPreview()
            }

        }
    }


}

三、總結(jié)

播放

目前知道系統(tǒng)提供給開發(fā)者的api有MediaPlayer一個高度封裝的類,可以播放器本地音視頻文件或者網(wǎng)絡(luò)文件,使用AudioTrack可以播放原始的音頻文件。

采集

MediaRecorder和AudioRecord可以采集音頻文件,MediaRecorder 底層封裝了AudioRecord,比如直播需要傳輸?shù)脑捑托枰盟鼇碜觥?/p>

github下載地址

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容