1. 程式人生 > >《android多媒體api》之AudioRecord原始音訊pcm錄製api

《android多媒體api》之AudioRecord原始音訊pcm錄製api

《android多媒體api》系列是整合梳理android開發中經常用到的媒體相關api;多媒體開發主要內容有音訊、視訊錄製播放、攝像頭操作、錄製操作、流媒體、直播、推流、拉流等方面;最近幾年移動直播和視訊應用發展猶如雨後春筍一般直插雲霄,呃。。好吧這段比喻可以不用看了!!,反正行業興起肯定催生了很多多媒體相關應用開發程式設計師。那麼怎樣才能成為多媒體開發程式設計師,首先必須要熟練使用和了解android自帶的多媒體api,並且還要掌握pcm、yuv、rgb、h264、aac、flv、mpegts、mp4、udp、rtp、rtmp等等眾多檔案格式和流媒體協議等等。所以這裡整理android相關多媒體api,提供給想從事流媒體同學作為參照,同樣還是要鳴謝網路上那些具有分享精神大神們!!

基本概念:

  1. 視訊播放:demuxer(解複用)->分離出音訊流和視訊流->decoder(解碼)->播放原始資料(例如:pcm yuv)
  2. 視訊錄製:採集原始資料(例如:pcm yuv)->encoder(編碼)->muxer(封裝格式 例如:mp4 3gp)
  3. 流媒體協議:udp、rtp、rtmp、rtcp、rtsp等
  4. 音視訊封裝格式:mp4 、3gp、flv等
  5. 音視訊編碼格式:aac、amr、h264、h265等
  6. 原始音視訊資料格式:pcm 、yuv、rgb等

流程圖:
image

文章目錄:

AudioRecord是什麼?
AudioRecord是可以錄製原始音訊資料pcm的api,如果是一些音樂錄製,或者直播語音等都需要使用音訊資料前置處理,比如:降噪、多音訊合成、特效音效處理等等。那麼就需要獲取原始音訊資料後處理完畢後在編碼,因為編碼後的資料是不能夠處理降噪、特效等操作的。那麼就下來看看怎麼用AudioRecord來錄製原始音訊資料;下面做了一個demo,主要是錄製音訊後儲存到檔案中去。pcm錄音時候需要制定幾個重要引數,這幾個引數在以後播放的時候也要對應設定,要不然無法播放。錄製時候還需要設定錄製緩衝區大小,快取區越大,記憶體溢位風險越小。

pcm引數:

1、取樣率
2、聲道數
3、位寬

首先視訊音訊錄製是屬於使用者敏感資訊,所以使用之前一定要申請許可權:

<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
/>

基於AudioRecord錄音功能:

xml佈局檔案:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <SurfaceView
        android:id="@+id/surfaceView1"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"/>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_gravity="bottom|center_horizontal"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:onClick="onClick"
            android:id="@+id/start_btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="開始"/>


        <Button
            android:onClick="onClick"
            android:layout_marginLeft="80dp"
            android:id="@+id/btnStop"
            android:layout_width="80dip"
            android:layout_height="wrap_content"
            android:text="停止"/>
    </LinearLayout>
</FrameLayout>

java程式碼:

package com.jared.helloffmpeg;

import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.media.*;
import android.media.AudioRecord;
import android.os.Bundle;
import android.os.Environment;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Toast;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class RecordMP4Push extends Activity implements View.OnClickListener, SurfaceHolder.Callback {

    private SurfaceView surfaceView;
    private byte[] outBuf;
    private boolean isStart=false;
    private AudioRecord audioRecord;
    private int bufferSize;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        setContentView(R.layout.record_aac_and_pcm);

        surfaceView=findViewById(R.id.surfaceView1);
        surfaceView.getHolder().addCallback(this);
    }

    private void initAudioRecord() {
        int sampleRateInHz = 48000;//取樣率
    int channel= AudioFormat.CHANNEL_IN_STEREO;//聲道數
    int audioFormat=AudioFormat.ENCODING_PCM_16BIT;//位寬
        bufferSize = AudioRecord.getMinBufferSize(sampleRateInHz, channel, audioFormat);
        audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRateInHz, channel, audioFormat, bufferSize*4);
        outBuf=new byte[bufferSize];
        Log.i(getClass().getSimpleName(), "init record="+bufferSize);
    }

    @Override
    public void onClick(View view) {
        if (view.getId()==R.id.start_btn)
        {
            isStart=true;
            initAudioRecord();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    startRecord();
                }
            }).start();
            Toast.makeText(this, "開始錄製",Toast.LENGTH_SHORT).show();
        }
        if (view.getId()==R.id.btnStop)
        {
            isStart=false;
            Toast.makeText(this, "停止錄製",Toast.LENGTH_SHORT).show();
        }
    }

    private void startRecord() {
        FileOutputStream fileOutputStream=null;
        try {
            audioRecord.startRecording();
            fileOutputStream=new FileOutputStream(new File(Environment.getExternalStorageDirectory().getAbsolutePath()+"/outputs.pcm"));
            while (isStart)
            {
                int len = audioRecord.read(outBuf, 0, bufferSize);
                if (len == AudioRecord.ERROR_INVALID_OPERATION || len == AudioRecord.ERROR_BAD_VALUE) {
                    continue;
                }
                if (len != 0 && len != -1) {
                    fileOutputStream.write(outBuf, 0, len);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                fileOutputStream.flush();
                fileOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            audioRecord.stop();
            audioRecord.release();
            audioRecord=null;
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {

    }

    @Override
    public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {

    }
}