1. 程式人生 > >Android音視訊開發初探之AudioRecord與AudioTrack完成音訊採集與播放

Android音視訊開發初探之AudioRecord與AudioTrack完成音訊採集與播放

有陣子沒出文章,接下來爭取每週一更,將沉澱的東西記錄下來,廢話不多說
剛接觸了音視訊方面,趁熱乎記錄一下,歡迎大家指正

接下來會分為一下幾點來介紹:

  1. 基礎知識準備
  2. Android MediaRecorder和AudioRecord 與 MediaPlayer 和 AudioTrack 的介紹
  3. PCM與WAV編碼介紹與轉化
  4. 例項 Android Audio Record 和 AudioTrack 的使用

基礎知識準備

音訊開發經常遇到的專業性詞語

(1) 取樣率

音訊取樣率” 是指錄音裝置在一秒鐘內對聲音訊號的取樣次數,取樣頻率越高聲音的還原就越真實越自然。常用的音訊取樣頻率有:8kHz、11.025kHz、22.05kHz、16kHz、37.8kHz、44.1kHz、48kHz、96kHz、192kHz等。在當今的主流採集卡上,取樣頻率一般共分為22.05KHz、44.1KHz、48KHz三個等級,22.05KHz只能達到FM廣播的聲音品質,44.1KHz則是理論上的CD音質界限,48KHz則更加精確一些。
通俗理解:每秒錄取聲音的次數。

(2) 量化精度(取樣位數)

取樣位數”越大表示的值的範圍也就越大
取樣位數“可以理解為採集卡處理聲音的解析度。這個數值越大,解析度就越高,錄製和回放的聲音就越真實。電腦中的聲音檔案是用數字0和1來表示的。連續的模擬訊號按一定的取樣頻率經數碼脈衝取樣後,每一個離散的脈衝訊號被以一定的量化精度量化成一串二進位制編碼流,這串編碼流的位數即為取樣位數,也稱為”量化精度“。
常見的位數為16bit32bit
通俗理解:每秒錄取聲音的精度,就像畫面的解析度,越高聲音越真實

(3) 聲道數

聲道數分別有:單聲道的聲道數為1個聲道雙聲道的聲道數為2個聲道;立體聲道的聲道數預設為2個聲道

;立體聲道(4聲道)的聲道數為4個聲道。
常見使用的是:單聲道(MONO) 和 雙聲道 (STEREO)
通俗理解:聲道數表示錄製或者播放音訊的聲音源

(4) PCM編碼與WAV格式

PCM(Pulse Code Modulation—-脈碼調製錄音)。所謂PCM錄音就是將聲音等模擬訊號變成符號化的脈衝列,再予以記錄。PCM訊號是由[1]、[0]等符號構成的數字訊號,而未經過任何編碼和壓縮處理。與模擬訊號比,它不易受傳送系統的雜波及失真的影響。動態範圍寬,可得到音質相當好的影響效果。也就是說,PCM就是沒有壓縮的編碼方式,PCM檔案就是採用PCM這種沒有壓縮的編碼方式編碼的音訊資料檔案。
PCM

約定俗成了無損編碼,因為PCM代表了數字音訊中最佳的保真水準,並不意味著PCM就能夠確保訊號絕對保真,PCM也只能做到最大程度的無限接近。

WAV為微軟公司(Microsoft)開發的一種聲音檔案格式,它符合RIFF(Resource Interchange File Format)檔案規範,用於儲存Windows平臺的音訊資訊資源,被Windows平臺及其應用程式所廣泛支援,該格式也支援MSADPCM,CCITT A LAW等多種壓縮運演算法,支援多種音訊數字,取樣頻率和聲道,標準格式化的WAV檔案和CD格式一樣,也是44.1K的取樣頻率,16位量化數字,因此在聲音檔案質量和CD相差無幾!
在Windows平臺下,基於PCM編碼的WAV是被支援得最好的音訊格式,所有音訊軟體都能完美支援,由於本身可以達到較高的音質的要求,因此,WAV也是音樂編輯創作的首選格式,適合儲存音樂素材。因此,基於PCM編碼的WAV被作為了一種中介的格式,常常使用在其他編碼的相互轉換之中,例如MP3轉換成WMA。

通俗理解:PCM是一種沒有壓縮且無損的編碼方式,WAV是微軟開發的一種無損的音訊檔案格式 , 而WAV是通過PCM資料的基礎上新增頭部資訊而生成的一種音訊格式,當然而可以基於其他如ADPCM編碼新增頭部資訊生成WAV

Android MediaRecorder和AudioRecordMediaPlayerAudioTrack 的介紹

官方提供兩種API用於音訊開發,分別為 MediaRecorderAudioRecord 用與音訊的採集,MediaPlayerAudioTrack 用於音訊的播放

API 作用 優點 缺點
AudioRecord 音訊採集 可以實時獲取音訊資料做到邊錄邊播,更偏向底層更加靈活,可以對獲取的音訊做出處理,如 壓縮、網路傳輸、演算法處理等 由於輸出的資料是原始資料PCM,播放器是不能識別播放的,需要通過AudioTrack處理播放
MediaRecorder 音訊採集 官方將音訊的錄製、編碼、壓縮等都封裝成API供使用、方便快捷 不能實時處理音訊、輸出的音訊格式不多,有AMR/ACC/VORBIS 而這些PCM都可以處理生成
MediaPlayer 播放音訊 MediaPlayer可以播放多種格式的聲音檔案,例如MP3,AAC,WAV,OGG,MIDI等。MediaPlayer會在framework層建立對應的音訊解碼器。 資源佔用量較高、延遲時間較長、不支援多個音訊同時播放等。這些缺點決定了MediaPlayer在某些場合的使用情況不會很理想,例如在對時間精準度要求相對較高的遊戲開發中。
AudioTrack 播放音訊 對於資料量小、延時要求高的音訊處理可以使用AudioTrack的MODE_STATIC傳輸模式 AudioTrack只能播放已經解碼的PCM流,如果是檔案的話只支援wav格式的音訊檔案,因為wav格式的音訊檔案大部分都是PCM流。AudioTrack不建立解碼器,所以只 能播放不需要解碼的wav檔案。

小知識點:
1. 在用MediaRecorder進行錄製音視訊時,最終還是會建立AudioRecord用來與AudioFlinger進行互動。
2. MediaPlayer在framework層還是會建立AudioTrack,把解碼後的PCM數流傳遞給AudioTrack,AudioTrack再傳遞給AudioFlinger進行混音,然後才傳遞給硬體播放。所以是MediaPlayer包含了AudioTRack。

PCM編碼轉化為WAV音訊格式

上面可知WAV是一種音訊格式,而所有的WAV格式都有特定檔案頭,檔案頭儲存著 RIFF檔案標誌、WAVE檔案標誌、取樣率、聲道數等資訊,PCM資料只需要加上WAV的檔案頭即可轉化為 WAV音訊格式
參考自文章作者 河北-寶哥
這裡給出的是關於下面例項的轉化WAV程式碼

   /**
     * 將pcm檔案轉化為可點選播放的wav檔案
     * @param inputPath pcm路徑
     * @param outPath wav存放路徑
     * @param data
     */
    private void PcmtoWav(String inputPath ,String outPath ,byte[] data){
        FileInputStream in;//讀取
        FileOutputStream out;
        try{
        in = new FileInputStream(inputPath);
        out = new FileOutputStream(outPath);
        //新增頭部資訊
        writeWavFileHeader(out,in.getChannel().size(),SAMPLE_RATE_HERTZ,CHANNEL_CONFIG);
        while(in.read(data)!= -1){
          out.write(data);
        }
        //關流
        in.close();
        out.close();
        }catch (IOException e){
            e.printStackTrace();
        }
    }

    /**
     * @param out            wav音訊檔案流
     * @param totalAudioLen  不包括header的音訊資料總長度
     * @param longSampleRate 取樣率,也就是錄製時使用的頻率
     * @param channels       audioRecord的頻道數量
     * @throws IOException 寫檔案錯誤
     */
    private void writeWavFileHeader(FileOutputStream out, long totalAudioLen, long longSampleRate,
                                    int channels) throws IOException {
        byte[] header = generateWavFileHeader(totalAudioLen, longSampleRate, channels);
        //寫頭
        out.write(header, 0, header.length);

    }

    /**
     * 任何一種檔案在頭部新增相應的標頭檔案才能夠確定的表示這種檔案的格式,
     * wave是RIFF檔案結構,每一部分為一個chunk,其中有RIFF WAVE chunk,
     * FMT Chunk,Fact chunk,Data chunk,其中Fact chunk是可以選擇的
     *
     * @param totalAudioLen  不包括header的音訊資料總長度
     * @param longSampleRate 取樣率,也就是錄製時使用的頻率
     * @param channels       audioRecord的頻道數量
     */
    private byte[] generateWavFileHeader(long totalAudioLen, long longSampleRate, int channels) {
        long totalDataLen = totalAudioLen + 36;
        long byteRate = longSampleRate * 2 * channels;
        byte[] header = new byte[44];
        header[0] = 'R'; // RIFF
        header[1] = 'I';
        header[2] = 'F';
        header[3] = 'F';
        header[4] = (byte) (totalDataLen & 0xff);//資料大小
        header[5] = (byte) ((totalDataLen >> 8) & 0xff);
        header[6] = (byte) ((totalDataLen >> 16) & 0xff);
        header[7] = (byte) ((totalDataLen >> 24) & 0xff);
        header[8] = 'W';//WAVE
        header[9] = 'A';
        header[10] = 'V';
        header[11] = 'E';
        //FMT Chunk
        header[12] = 'f'; // 'fmt '
        header[13] = 'm';
        header[14] = 't';
        header[15] = ' ';//過渡位元組
        //資料大小
        header[16] = 16; // 4 bytes: size of 'fmt ' chunk
        header[17] = 0;
        header[18] = 0;
        header[19] = 0;
        //編碼方式 10H為PCM編碼格式
        header[20] = 1; // format = 1
        header[21] = 0;
        //通道數
        header[22] = (byte) channels;
        header[23] = 0;
        //取樣率,每個通道的播放速度
        header[24] = (byte) (longSampleRate & 0xff);
        header[25] = (byte) ((longSampleRate >> 8) & 0xff);
        header[26] = (byte) ((longSampleRate >> 16) & 0xff);
        header[27] = (byte) ((longSampleRate >> 24) & 0xff);
        //音訊資料傳送速率,取樣率*通道數*取樣深度/8
        header[28] = (byte) (byteRate & 0xff);
        header[29] = (byte) ((byteRate >> 8) & 0xff);
        header[30] = (byte) ((byteRate >> 16) & 0xff);
        header[31] = (byte) ((byteRate >> 24) & 0xff);
        // 確定系統一次要處理多少個這樣位元組的資料,確定緩衝區,通道數*取樣位數
        header[32] = (byte) (2 * channels);
        header[33] = 0;
        //每個樣本的資料位數
        header[34] = 16;
        header[35] = 0;
        //Data chunk
        header[36] = 'd';//data
        header[37] = 'a';
        header[38] = 't';
        header[39] = 'a';
        header[40] = (byte) (totalAudioLen & 0xff);
        header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
        header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
        header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
        return header;
    }

例項Android AudioRecord 和 AudioTrack 的使用

首先給出我們將音訊的採集和播放封裝成一個AudioRecordManager音訊管理類 (程式碼有詳細註釋)

package com.example.medialearn.test2;

import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Build;
import android.os.Environment;
import android.support.annotation.RequiresApi;
import android.util.Log;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

/**
 * @author vveng
 * @version version 1.0.0
 * @date 2018/7/24 16:03.
 * @email [email protected]
 * @instructions 說明
 * @descirbe 描述
 * @features 功能
 */
public class AudioRecordManager {

    private static final String TAG = "AudioRecordManager";
    private static final String DIR_NAME = "arm";
    private static String AudioFolderFile; //音訊檔案路徑
    private static AudioRecordManager mAudioRecordManager;
    private File PcmFile = null ; //pcm音訊檔案
    private File WavFile = null;  //wav格式的音訊檔案
    private AudioRecordThread mAudioRecordThead; //錄製執行緒
    private AudioRecordPlayThead mAudioRecordPlayThead;//播放執行緒
    private boolean isRecord = false;
    /**
     * 取樣率,現在能夠保證在所有裝置上使用的取樣率是44100Hz, 但是其他的取樣率(22050, 16000, 11025)在一些裝置上也可以使用。
     */
    public static final int SAMPLE_RATE_HERTZ = 44100;

    /**
     * 聲道數。CHANNEL_IN_MONO and CHANNEL_IN_STEREO. 其中CHANNEL_IN_MONO是可以保證在所有裝置能夠使用的。
     */
    public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO;

    /**
     * 返回的音訊資料的格式。 ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, and ENCODING_PCM_FLOAT.
     */
    public static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;


    public static AudioRecordManager NewInstance() {
        if (mAudioRecordManager == null) {
            synchronized (AudioRecordManager.class) {
                if (mAudioRecordManager == null) {
                    mAudioRecordManager = new AudioRecordManager();
                }
            }
        }
        return mAudioRecordManager;
    }


    /**
     * 播放音訊
     */
    @RequiresApi(api = Build.VERSION_CODES.M)
    public synchronized void playRecord() {
        //可防止重複點選錄製
        if (true == isRecord) {
            Log.d(TAG, "無法開始播放,當前狀態為:" + isRecord);
            return;
        }
        isRecord = true;
        mAudioRecordPlayThead = new AudioRecordPlayThead(PcmFile);
        mAudioRecordPlayThead.start();
    }

    /**
     * 停止播放
     */
    public void stopPlayRecord() {
        if (null != mAudioRecordPlayThead) {
            mAudioRecordPlayThead.interrupt();
            mAudioRecordPlayThead = null;
        }
        isRecord = false;
    }

    /**
     * 播放音訊執行緒
     */
    private class AudioRecordPlayThead extends Thread {
        AudioTrack mAudioTrack;
        int BufferSize = 10240;
        File autoFile = null; //要播放的檔案

        @RequiresApi(api = Build.VERSION_CODES.M)
        AudioRecordPlayThead(File file) {
            setPriority(MAX_PRIORITY);
            autoFile = file;
            //播放緩衝的最小大小
            BufferSize = AudioTrack.getMinBufferSize(SAMPLE_RATE_HERTZ,
                    AudioFormat.CHANNEL_OUT_STEREO, AUDIO_FORMAT);
           // 建立用於播放的 AudioTrack
            mAudioTrack = new AudioTrack.Builder()
                    .setAudioAttributes(new AudioAttributes.Builder()
                            .setUsage(AudioAttributes.USAGE_ALARM)
                            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                            .build())
                    .setAudioFormat(new AudioFormat.Builder()
                            .setEncoding(AUDIO_FORMAT)
                            .setSampleRate(SAMPLE_RATE_HERTZ)
                            .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
                            .build())
                    .setBufferSizeInBytes(BufferSize)
                    .build();

        }

        @Override
        public void run() {

            Log.d(TAG, "播放開始");
            try {
                FileInputStream fis = new FileInputStream(autoFile);
                mAudioTrack.play();
                byte[] bytes = new byte[BufferSize];

                while(true == isRecord) {
                    int read = fis.read(bytes);
                    //若讀取有錯則跳過
                    if (AudioTrack.ERROR_INVALID_OPERATION == read
                            || AudioTrack.ERROR_BAD_VALUE == read) {
                        continue;
                    }

                    if (read != 0 && read != -1) {
                        mAudioTrack.write(bytes, 0, BufferSize);
                    }
                }
                mAudioTrack.stop();
                mAudioTrack.release();//釋放資源
                fis.close();//關流

            } catch (Exception e) {
                e.printStackTrace();
            }

            isRecord = false;
            Log.d(TAG, "播放停止");
        }
    }


    /**
     * 開始錄製
     */
    public synchronized void startRecord() {
        //可防止重複點選錄製
        if (true == isRecord) {
            Log.d(TAG, "無法開始錄製,當前狀態為:" + isRecord);
            return;
        }
        isRecord = true;
        SimpleDateFormat sdf = new SimpleDateFormat("yyMMdd_HHmmss", Locale.CHINA);
        //源pcm資料檔案
        PcmFile = new File(AudioFolderFile + File.separator + sdf.format(new Date())+".pcm");
        //wav檔案
        WavFile = new File(PcmFile.getPath().replace(".pcm",".wav"));

        Log.d(TAG, "PcmFile:"+ PcmFile.getName()+"WavFile:"+WavFile.getName());

        if (null != mAudioRecordThead) {
            //若執行緒不為空,則中斷執行緒
            mAudioRecordThead.interrupt();
            mAudioRecordThead = null;
        }
        mAudioRecordThead = new AudioRecordThread();
        mAudioRecordThead.start();
    }

    /**
     * 停止錄製
     */
    public synchronized void stopRecord() {
        if (null != mAudioRecordThead) {
            mAudioRecordThead.interrupt();
            mAudioRecordThead = null;
        }

        isRecord = false;
    }

    /**
     * 錄製執行緒
     */
    private class AudioRecordThread extends Thread {
        AudioRecord mAudioRecord;
        int BufferSize = 10240;

        AudioRecordThread() {
            /**
             * 獲取音訊緩衝最小的大小
             */
            BufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE_HERTZ,
                    CHANNEL_CONFIG, AUDIO_FORMAT);
            /**
             * 引數1:音訊源
             * 引數2:取樣率 主流是44100
             * 引數3:聲道設定 MONO單聲道 STEREO立體聲
             * 引數4:編碼格式和取樣大小 編碼格式為PCM,主流大小為16BIT
             * 引數5:採集資料需要的緩衝區大小
             */
            mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
                    SAMPLE_RATE_HERTZ, CHANNEL_CONFIG, AUDIO_FORMAT, BufferSize);
        }

        @Override
        public void run() {
            //將狀態置為錄製

            Log.d(TAG, "錄製開始");
            try {
                byte[] bytes = new byte[BufferSize];

                FileOutputStream PcmFos = new FileOutputStream(PcmFile);

                //開始錄製
                mAudioRecord.startRecording();

                while (true == isRecord && !isInterrupted()) {
                    int read = mAudioRecord.read(bytes, 0, bytes.length);
                    //若讀取資料沒有出現錯誤,將資料寫入檔案
                    if (AudioRecord.ERROR_INVALID_OPERATION != read) {
                        PcmFos.write(bytes, 0, read);
                        PcmFos.flush();
                    }
                }
                mAudioRecord.stop();//停止錄製
                PcmFos.close();//關流

            } catch (Exception e) {
                e.printStackTrace();

            }
            isRecord = false;
            //當錄製完成就將Pcm編碼資料轉化為wav檔案,也可以直接生成.wav
            PcmtoWav(PcmFile.getPath(),WavFile.getPath(),new byte[BufferSize]);
            Log.d(TAG, "錄製結束");
        }

    }


    /**
     * 初始化目錄
     */
    public static void init() {
        //檔案目錄
        AudioFolderFile = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath()
                + File.separator + DIR_NAME;
        File WavDir = new File(AudioFolderFile);
        if (!WavDir.exists()) {
            boolean flag = WavDir.mkdirs();
            Log.d(TAG,"檔案路徑:"+AudioFolderFile+"建立結果:"+flag);
        } else {
            Log.d(TAG,"檔案路徑:"+AudioFolderFile+"建立結果: 已存在");
        }
    }

    /**
     * 將pcm檔案轉化為可點選播放的wav檔案
     * @param inputPath pcm路徑
     * @param outPath wav存放路徑
     * @param data
     */
    private void PcmtoWav(String inputPath ,String outPath ,byte[] data){
        FileInputStream in;
        FileOutputStream out;
        try{
        in = new FileInputStream(inputPath);
        out = new FileOutputStream(outPath);
        //新增頭部資訊
        writeWavFileHeader(out,in.getChannel().size(),SAMPLE_RATE_HERTZ,CHANNEL_CONFIG);
        while(in.read(data)!= -1){
          out.write(data);
        }
        in.close();
        out.close();
        }catch (IOException e){
            e.printStackTrace();
        }
    }

    /**
     * @param out            wav音訊檔案流
     * @param totalAudioLen  不包括header的音訊資料總長度
     * @param longSampleRate 取樣率,也就是錄製時使用的頻率
     * @param channels       audioRecord的頻道數量
     * @throws IOException 寫檔案錯誤
     */
    private void writeWavFileHeader(FileOutputStream out, long totalAudioLen, long longSampleRate,
                                    int channels) throws IOException {
        byte[] header = generateWavFileHeader(totalAudioLen, longSampleRate, channels);
        out.write(header, 0, header.length);

    }

    /**
     * 任何一種檔案在頭部新增相應的標頭檔案才能夠確定的表示這種檔案的格式,
     * wave是RIFF檔案結構,每一部分為一個chunk,其中有RIFF WAVE chunk,
     * FMT Chunk,Fact chunk,Data chunk,其中Fact chunk是可以選擇的
     *
     * @param totalAudioLen  不包括header的音訊資料總長度
     * @param longSampleRate 取樣率,也就是錄製時使用的頻率
     * @param channels       audioRecord的頻道數量
     */
    private byte[] generateWavFileHeader(long totalAudioLen, long longSampleRate, int channels) {
        long totalDataLen = totalAudioLen + 36;
        long byteRate = longSampleRate * 2 * channels;
        byte[] header = new byte[44];
        header[0] = 'R'; // RIFF
        header[1] = 'I';
        header[2] = 'F';
        header[3] = 'F';
        header[4] = (byte) (totalDataLen & 0xff);//資料大小
        header[5] = (byte) ((totalDataLen >> 8) & 0xff);
        header[6] = (byte) ((totalDataLen >> 16) & 0xff);
        header[7] = (byte) ((totalDataLen >> 24) & 0xff);
        header[8] = 'W';//WAVE
        header[9] = 'A';
        header[10] = 'V';
        header[11] = 'E';
        //FMT Chunk
        header[12] = 'f'; // 'fmt '
        header[13] = 'm';
        header[14] = 't';
        header[15] = ' ';//過渡位元組
        //資料大小
        header[16] = 16; // 4 bytes: size of 'fmt ' chunk
        header[17] = 0;
        header[18] = 0;
        header[19] = 0;
        //編碼方式 10H為PCM編碼格式
        header[20] = 1; // format = 1
        header[21] = 0;
        //通道數
        header[22] = (byte) channels;
        header[23] = 0;
        //取樣率,每個通道的播放速度
        header[24] = (byte) (longSampleRate & 0xff);
        header[25] = (byte) ((longSampleRate >> 8) & 0xff);
        header[26] = (byte) ((longSampleRate >> 16) & 0xff);
        header[27] = (byte) ((longSampleRate >> 24) & 0xff);
        //音訊資料傳送速率,取樣率*通道數*取樣深度/8
        header[28] = (byte) (byteRate & 0xff);
        header[29] = (byte) ((byteRate >> 8) & 0xff);
        header[30] = (byte) ((byteRate >> 16) & 0xff);
        header[31] = (byte) ((byteRate >> 24) & 0xff);
        // 確定系統一次要處理多少個這樣位元組的資料,確定緩衝區,通道數*取樣位數
        header[32] = (byte) (2 * channels);
        header[33] = 0;
        //每個樣本的資料位數
        header[34] = 16;
        header[35] = 0;
        //Data chunk
        header[36] = 'd';//data
        header[37] = 'a';
        header[38] = 't';
        header[39] = 'a';
        header[40] = (byte) (totalAudioLen & 0xff);
        header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
        header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
        header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
        return header;
    }
}

使用AudioRecordActivity (由於佈局簡單就四個按鈕,這裡就不再給出)

注意在AndroidManifest.xml 新增相關許可權:

  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <!-- 錄音許可權 -->
    <uses-permission android:name="android.permission.RECORD_AUDIO" />

AudioRecordActivity:

package com.example.medialearn.test2;

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import com.example.medialearn.R;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

/**
 * @author vveng
 * @version version 1.0.0
 * @date 2018/7/24 16:03.
 * @email [email protected]
 * @instructions 說明
 * @descirbe 描述
 * @features 功能
 */
public class AudioRecordActivity extends AppCompatActivity
        implements View.OnClickListener {
    private String TAG = "AudioRecordActivity";
    private Button btn_start, btn_stop, btn_play, btn_onplay;
    private AudioRecordManager mManager;
    //申請許可權列表
    private int REQUEST_CODE = 1001;
    private String[] permissions = new String[]{
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.RECORD_AUDIO
    };
    //拒絕許可權列表
    private List<String> refusePermissions = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_audio_record);
        AudioRecordManager.init();//初始化目錄
        initView();
        checkPermission();
    }

    /**
     * 6.0以上要動態申請許可權
     */
    private void checkPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            for (int i = 0; i < permissions.length; i++) {
                if (ContextCompat.checkSelfPermission(this,
                        permissions[i]) != PackageManager.PERMISSION_GRANTED) {
                    refusePermissions.add(permissions[i]);
                }
            }
            if (!refusePermissions.isEmpty()) {
                String[] permissions = refusePermissions.toArray(new String[refusePermissions.size()]);
                ActivityCompat.requestPermissions(this, permissions, REQUEST_CODE);
            }
        }

    }

    /**
     * 許可權結果回撥
     * @param requestCode
     * @param permissions
     * @param grantResults
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == REQUEST_CODE) {
            if (grantResults.length > 0 && grantResults != null) {
                for(int i = 0 ; i<permissions.length;i++){
                    if(grantResults[i]!=PackageManager.PERMISSION_GRANTED){
                        Log.d(TAG,permissions[i]+"   被禁用");
                    }
                }
            }
        }
    }

    private void initView() {
        btn_start = findViewById(R.id.record_start);
        btn_stop = findViewById(R.id.record_stop);
        btn_play = findViewById(R.id.record_play);
        btn_onplay = findViewById(R.id.record_noplay);
        btn_start.setOnClickListener(this);
        btn_stop.setOnClickListener(this);
        btn_play.setOnClickListener(this);
        btn_onplay.setOnClickListener(this);
        //初始化
        mManager = AudioRecordManager.NewInstance();
    }

    @RequiresApi(api = Build.VERSION_CODES.M)
    @Override
    public void onClick(View view) {

        switch (view.getId()) {
            case R.id.record_start:
            //錄音
                mManager.startRecord();
                break;
            case R.id.record_stop:
            //停止
                mManager.stopRecord();
                break;
            case R.id.record_play:
            //播放
                mManager.playRecord();
                break;
            case R.id.record_noplay:
            //停止
                mManager.stopPlayRecord();
                break;
            default:
                break;
        }
    }
}
最後感謝你瀏覽到最後,例項程式碼已經全部給出。祝你

沉迷學習

相關推薦

Android視訊開發初探AudioRecordAudioTrack完成音訊採集播放

有陣子沒出文章,接下來爭取每週一更,將沉澱的東西記錄下來,廢話不多說 剛接觸了音視訊方面,趁熱乎記錄一下,歡迎大家指正 接下來會分為一下幾點來介紹: 基礎知識準備 Android MediaRecorder和AudioRecord 與 M

Android 視訊開發學習

  一直欠大家一篇音視訊入門之路的文章,這篇文章是我見過寫的最詳細的一篇了,今天算還了哈。作者從入門、進階、探究分別編寫了一系列文章。   Android 音視訊開發這塊目前的確沒有比較系統的教程或者書籍,網上的部落格文章也都是比較零散的。只能通過一點點的學習和積累把這塊的知識串聯積累起

視訊開發著作《Android視訊開發》終於發售了,先來一波簽名送書福利!

經歷了兩年多,我的著作終於和大家見面了,寫書是一件很磨練人耐力的事情,從打算寫一本書開始後,心裡無時不刻有一塊大石頭壓在頭頂。一來要保證專業性,二來要保證質量,同時還要兼具備怎麼表達,才能讓別人明白所說的意思。所以看起來沒有那麼簡單。近年來,直播,短視訊行業相關

Android 視訊開發(六): MediaCodec API 詳解

在學習了Android 音視訊的基本的相關知識,並整理了相關的API之後,我們應該對基本的音視訊有一定的輪廓了。下面開始接觸一個Android音視訊中相當重要的一個API: MediaCodec。 一、MediaCodec API介紹 MediaCodec可以處理具體的視

Android 視訊開發學習思路

Android 音視訊開發這塊目前的確沒有比較系統的教程或者書籍,網上的部落格文章也都是比較零散的。只能通過一點點的學習和積累把這塊的知識串聯積累起來。 初級入門篇: 初級入門篇主要是接觸Android多媒體展示相關的API,通過單獨的列舉和使用這些API,對Android音視訊處理有一個基本的輪廓

Android視訊開發入門指南

《Android 音視訊從入門到提高 —— 任務列表》 1. 在 Android 平臺繪製一張圖片,使用至少 3 種不同的 API,ImageView,SurfaceView,自定義 View 2. 在 Android 平臺使用 AudioRecord 和

即時通訊視訊開發(六):如何開始音訊編解碼技術的學習

前言 即時通訊應用中的實時音視訊技術,幾乎是IM開發中的最後一道高牆。原因在於:實時音視訊技術 = 音視訊處理技術 + 網路傳輸技術 的橫向技術應用集合體,而公共網際網路不是為了實時通訊設計的。 系列文章 《即時通訊音視訊開發(四):視訊編解碼之預測技術介紹》 《即時通訊音

即時通訊視訊開發(四):視訊編解碼預測技術介紹

前言 即時通訊應用中的實時音視訊技術,幾乎是IM開發中的最後一道高牆。原因在於:實時音視訊技術 = 音視訊處理技術 + 網路傳輸技術 的橫向技術應用集合體,而公共網際網路不是為了實時通訊設計的。 系列文章 《即時通訊音視訊開發(二):視訊編解碼之數字視訊介紹》 《即時通訊音

即時通訊視訊開發(三):視訊編解碼編碼基礎

前言 即時通訊應用中的實時音視訊技術,幾乎是IM開發中的最後一道高牆。原因在於:實時音視訊技術 = 音視訊處理技術 + 網路傳輸技術 的橫向技術應用集合體,而公共網際網路不是為了實時通訊設計的。 系列文章 《即時通訊音視訊開發(一):視訊編解碼之理論概述》 《即時通訊音視訊

即時通訊視訊開發(二):視訊編解碼數字視訊介紹

前言 即時通訊應用中的實時音視訊技術,幾乎是IM開發中的最後一道高牆。原因在於:實時音視訊技術 = 音視訊處理技術 + 網路傳輸技術 的橫向技術應用集合體,而公共網際網路不是為了實時通訊設計的。 系列文章 本文是系列文章中的第2篇,本系列文章的大綱如下:   《即時

即時通訊視訊開發(一):視訊編解碼理論概述

前言 即時通訊應用中的實時音視訊技術,幾乎是IM開發中的最後一道高牆。原因在於:實時音視訊技術 = 音視訊處理技術 + 網路傳輸技術 的橫向技術應用集合體,而公共網際網路不是為了實時通訊設計的。 系列文章 《即時通訊音視訊開發(二):視訊編解碼之數字視訊介紹》 《即時通訊音

中老年人計算機基礎應用培訓通知18個實時視訊開發中會用到開源專案

  實時音視訊的開發學習有很多可以參考的開源專案。一個實時音視訊應用共包括幾個環節:採集、編碼、前後處理、傳輸、解碼、緩衝、渲染等很多環節。每一個細分環節,還有更細分的技術模組。比如,前後處理環節有美顏、濾鏡、回聲消除、噪聲抑制等,採集有麥克風陣列等,編解碼有VP8、VP9、H.264、H.

Android視訊Android Onvif-IPC開發(一)——在Android端搭建伺服器模擬Onvif-IP-Camera

Android端實現Onvif IPC開發: 本篇內容簡介: 本篇是上一文章移植失敗採取的第二方案,通過在android搭建service,模擬成一個onvif協議對接的IPC端,在這之前,首先需要明白,onvif裝置對接的流程或者說方式,接下來的文章內容

Android視訊Android Onvif-IPC開發(一)——gSoap移植NDK嘗試

Android端實現Onvif IPC開發: 閱讀說明(必讀) 我在進行gSoap移植時暫時失敗了,而是採用方案二,在android端通過java搭建的server去模擬IPC,達到需求(實現Android端可供Onvif檢測的IPC),以下是我移植的步驟

Android WebRTC 視訊開發總結(二)

1 public void setTrace(boolean enable, VideoEngine.TraceLevel traceLevel) { 2 if (enable) { 3 vie.setTraceFile("/sdcard/trace.txt", f

android進階4step2:Android視訊處理——視訊錄製播放

錄音 MediaRecoder Android有一個內建的麥克風,通過它可以捕獲音訊和儲存,或在手機進行播放。 有很多方法可以做到這一點,但最常見的方法是通 過MediaRecorder類。 MediaRecoder常用方法 方法名 描述

手機Android視訊採集直播推送,實現單兵、移動監控類應用

      最新手機採集推送直播監控以及EasyDarwin開源流媒體平臺的版本及程式碼:恰逢2014 Google I/O大會,不難看出安卓在Google的推進以及本身的開放性作用下,已經快延生到生活

Android 視訊採集軟編碼總結

前言 本文總結了筆者在 Android 音視訊採集與軟編碼中的一些經驗與技巧,包括移植 FFmpeg、YUV 視訊幀處理、最新的 JNI 編寫技巧、 ndk 開發技巧等,為了不扯太遠本文不會對音視訊編碼的一些原理性東西進行剖析,也不會大量貼原始碼,更注重使用方法與

深入理解Android視訊同步機制(四)MediaSync的使用原理

MedaiSync是android M新加入的API,可以幫助應用視音訊的同步播放,如同官網介紹的 From Andriod M: MediaSync: class which helps applications to synchronously r

Android視訊學習第4章:視訊直播實現推送視訊

H.264標準學習 1.H264編碼框架 H264碼流檔案分為兩層: (1) VCL(Video Coding Layer)視訊編碼層: 負責高效的視訊內容表示,VCL 資料即編碼處理的輸出,它表示被壓縮編碼後的視訊資料序列。 (2)