Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android錄音實現——使用AtudioRecord

Android錄音實現——使用AtudioRecord

編輯:關於Android編程

最近在做android中錄音錄屏的功能,以前也是從未接觸多媒體這塊,然後從不會到一點點的摸索,參考大神們的代碼,到現在算是入門了,今天就總結一下android中的錄音部分,後面總結錄屏。

在android中實現錄音共有三種方式:

通過意圖捕獲音頻。這是android中最簡單的一種方式,就是通過一個意圖利用已有的、提供錄制功能的應用程序。android系統中都會再帶一個錄音程序,我們可以通過意圖來調用這個錄音程序,從而實現錄音功能。MediaRecorder類實現錄音。MediaRecorder類是android中用來捕獲音頻和視頻的多媒體類,通過這種方式錄制音頻也是比較簡單的。通過設置音頻源,輸出格式,設置音頻編解碼器進行編解碼,最後將音頻輸出到文件中。AudioRecord錄制原始音頻。這種方式相對於前兩種比較麻煩,需要我們自己處理的事情較多,因此也是最靈活的。AudioRecord允許訪問原始音頻流,這種音頻流是不能直接進行播放的,需要使用AudioTrack來進行播放原始音頻。如果要使用這種方式來將音頻保存到文件中,並可像MP3文件一樣直接打開的話,就需要對原始音頻進行編解碼,然後用混合器(Muxer)進行輸出到文件中。

我所用到的是第三種方式來實現錄音的,因為我需要將音頻添加到視頻中去,因此得選用第三中最靈活的方式。

使用AudioRecord錄制音頻並不難,但是要將原始音頻進行編碼並輸出到一個可播放的文件中就有點麻煩。先說說實現這個功能的大概思路:我們需要兩個任務,一個任務用來進行采集音頻數據,在采集音頻數據的同時將音頻數據不斷的發送給編碼的任務,將這些數據按照指定格式進行編碼,然後輸出到文件中。

具體實現:

使用AudioRecord捕獲原始音頻流。采集工作很簡單,我們只需要構造一個AudioRecord對象,然後傳入各種不同配置的參數即可。

1.音頻源:我們可以使用麥克風作為采集音頻的數據源。

2.采樣率:一秒鐘對聲音數據的采樣次數,采樣率越高,音質越好。

3.音頻通道:單聲道,雙聲道等,

4.音頻格式:一般選用PCM格式,即原始的音頻樣本。

5.緩沖區大小:音頻數據寫入緩沖區的總數,可以通過AudioRecord.getMinBufferSize獲取最小的緩沖區。(將音頻采集到緩沖區中然後再從緩沖區中讀取)。

 

package com.creativeboy.audioandvideocapture.encoder;

import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.media.MediaRecorder;
import android.os.Environment;
import android.util.Log;

import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Created by heshaokang on 2015/5/6.
 */
public class AudioRecorder {
    private static final String TAG = "AudioRecorder";
    private static final int SAMPLE_RATE = 44100; //采樣率(CD音質)
    private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO; //音頻通道(單聲道)
    private static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT; //音頻格式
    private static final int AUDIO_SOURCE = MediaRecorder.AudioSource.MIC;  //音頻源(麥克風)
    private static boolean is_recording = false;
    public static File recordFile ;
    private AudioEncoder audioEncoder;
    private static AudioRecorder instance;
    private RecorderTask recorderTask = new RecorderTask();
    private AudioRecorder(File file){
       recordFile = file;
    }
    public static AudioRecorder getInstance(File file) {
           return new AudioRecorder(file);

    }

    public void setAudioEncoder(AudioEncoder audioEncoder) {
        this.audioEncoder = audioEncoder;
    }

    /*
        開始錄音
     */
    public void startAudioRecording() {

        new Thread(recorderTask).start();
    }

    /*
        停止錄音
     */
    public void stopAudioRecording() {
        is_recording = false;
    }
    class RecorderTask implements Runnable {
        int bufferReadResult = 0;
        public int samples_per_frame = 2048;
        @Override
        public void run() {
                long audioPresentationTimeNs; //音頻時間戳 pts
                //獲取最小緩沖區大小
                int bufferSizeInBytes = AudioRecord.getMinBufferSize(SAMPLE_RATE,CHANNEL_CONFIG,AUDIO_FORMAT);
                AudioRecord audioRecord = new AudioRecord(
                        AUDIO_SOURCE,   //音頻源
                        SAMPLE_RATE,    //采樣率
                        CHANNEL_CONFIG,  //音頻通道
                        AUDIO_FORMAT,    //音頻格式
                        bufferSizeInBytes //緩沖區
                );
                audioRecord.startRecording();
                is_recording = true;

            Log.v(TAG, "recordFile.getAbsolutepath---" + recordFile.getAbsolutePath());

            while(is_recording) {
                  byte[] buffer = new byte[samples_per_frame];
                  audioPresentationTimeNs = System.nanoTime();
                    //從緩沖區中讀取數據,存入到buffer字節數組數組中
                    bufferReadResult = audioRecord.read(buffer,0,samples_per_frame);
                    //判斷是否讀取成功
                    if(bufferReadResult == AudioRecord.ERROR_BAD_VALUE || bufferReadResult == AudioRecord.ERROR_INVALID_OPERATION)
                        Log.e(TAG, "Read error");
                    if(audioRecord!=null) {
                        audioEncoder.offerAudioEncoder(buffer,audioPresentationTimeNs);
                    }

                }
                if(audioRecord!=null) {
                    audioRecord.setRecordPositionUpdateListener(null);
                    audioRecord.stop();
                    audioRecord.release();
                    audioRecord = null;
                }


        }
    }




}

 

使用MediaCodec進行音頻編碼.

MediaCodec是android的一個編解碼類。將獲取到的原始音頻流先進行特定的解碼,然後進行數據處理,再編碼為指定的格式。具體可參考這篇博客:http://blog.csdn.net/mouse_1894/article/details/27311099

MediaMuxer 這是一個混合器,用來將編碼好的音頻數據輸出到文件。

 

package com.creativeboy.audioandvideocapture.encoder;

import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.util.Log;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by heshaokang on 2015/5/9.
 *  對音頻數據進行編碼
 */
public class AudioEncoder {
    private static final String TAG = "AudioEncoder";
    //編碼
    private MediaCodec mAudioCodec;     //音頻編解碼器
    private MediaFormat mAudioFormat;
    private static final String AUDIO_MIME_TYPE = "audio/mp4a-latm"; //音頻類型
    private static final int SAMPLE_RATE = 44100; //采樣率(CD音質)
    private TrackIndex mAudioTrackIndex = new TrackIndex();
    private MediaMuxer mMediaMuxer;     //混合器
    private boolean mMuxerStart = false; //混合器啟動的標志
    private MediaCodec.BufferInfo mAudioBufferInfo;
    private static long audioBytesReceived = 0;        //接收到的音頻數據 用來設置錄音起始時間的
    private long audioStartTime;
    private String recordFile ;
    private boolean eosReceived = false;  //終止錄音的標志
    private ExecutorService encodingService = Executors.newSingleThreadExecutor(); //序列化線程任務
    //枚舉值 一個用來標志編碼 一個標志編碼完成
    enum EncoderTaskType {ENCODE_FRAME,FINALIZE_ENCODER};

    public AudioEncoder() {
        recordFile = AudioRecorder.recordFile.getAbsolutePath();
        prepareEncoder();
    }

    class TrackIndex {
        int index = 0;
    }
    public void prepareEncoder() {
        eosReceived = false;
        audioBytesReceived = 0;
        mAudioBufferInfo = new MediaCodec.BufferInfo();
        mAudioFormat = new MediaFormat();
        mAudioFormat.setString(MediaFormat.KEY_MIME, AUDIO_MIME_TYPE);
        mAudioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
        mAudioFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE,SAMPLE_RATE);
        mAudioFormat.setInteger(MediaFormat.KEY_BIT_RATE, 128000);
        mAudioFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
        mAudioFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE,16384);
        try {
            mAudioCodec = MediaCodec.createEncoderByType(AUDIO_MIME_TYPE);
            mAudioCodec.configure(mAudioFormat,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE);
            mAudioCodec.start();
            mMediaMuxer = new MediaMuxer(recordFile,MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    //此方法 由AudioRecorder任務調用 開啟編碼任務
    public void offerAudioEncoder(byte[] input,long presentationTimeStampNs) {
       if(!encodingService.isShutdown()) {
//           Log.d(TAG,"encodingService--submit");
           encodingService.submit(new AudioEncodeTask(this,input,presentationTimeStampNs));
       }

    }

    //發送音頻數據和時間進行編碼
    public void _offerAudioEncoder(byte[] input,long pts) {
        if(audioBytesReceived==0) {
            audioStartTime = pts;
        }
        audioBytesReceived+=input.length;
        drainEncoder(mAudioCodec,mAudioBufferInfo,mAudioTrackIndex,false);
        try {
            ByteBuffer[] inputBuffers = mAudioCodec.getInputBuffers();
            int inputBufferIndex = mAudioCodec.dequeueInputBuffer(-1);
//        Log.d(TAG,"inputBufferIndex--"+inputBufferIndex);
            if(inputBufferIndex>=0) {
                ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
                inputBuffer.clear();
                inputBuffer.put(input);

                //錄音時長
                long presentationTimeUs = (pts - audioStartTime)/1000;
                Log.d("hsk","presentationTimeUs--"+presentationTimeUs);
                if(eosReceived) {
                    mAudioCodec.queueInputBuffer(inputBufferIndex, 0, input.length, presentationTimeUs, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                    closeEncoder(mAudioCodec, mAudioBufferInfo, mAudioTrackIndex);
                    closeMuxer();
                    encodingService.shutdown();

                }else {
                    mAudioCodec.queueInputBuffer(inputBufferIndex,0,input.length,presentationTimeUs,0);
                }
            }

        }catch (Throwable t) {
            Log.e(TAG, "_offerAudioEncoder exception");
        }

    }

    public void drainEncoder(MediaCodec encoder,MediaCodec.BufferInfo bufferInfo,TrackIndex trackIndex ,boolean endOfStream) {
        final int TIMEOUT_USEC = 100;
        ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers();
        while(true) {
            int encoderIndex = encoder.dequeueOutputBuffer(bufferInfo,TIMEOUT_USEC);
            Log.d("hsk","encoderIndex---"+encoderIndex);
            if(encoderIndex==MediaCodec.INFO_TRY_AGAIN_LATER) {
                //沒有可進行混合的輸出流數據 但還沒有結束錄音 此時退出循環
                Log.d(TAG,"info_try_again_later");
                if(!endOfStream)
                    break;
                else
                    Log.d(TAG, "no output available, spinning to await EOS");
            }else if(encoderIndex== MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                //只會在第一次接收數據前 調用一次
                if(mMuxerStart)
                    throw new RuntimeException("format 在muxer啟動後發生了改變");
                MediaFormat newFormat = encoder.getOutputFormat();
                trackIndex.index = mMediaMuxer.addTrack(newFormat);
                mMediaMuxer.start();
                mMuxerStart = true;
            }else if(encoderIndex<0) {
                Log.w(TAG,"encoderIndex 非法"+encoderIndex);
            }else {
                ByteBuffer encodeData = encoderOutputBuffers[encoderIndex];
                if (encodeData==null) {
                    throw new RuntimeException("編碼數據為空");
                }
                if(bufferInfo.size!=0) {
                    if(!mMuxerStart) {
                        throw new RuntimeException("混合器未開啟");
                    }
                    encodeData.position(bufferInfo.offset);
                    encodeData.limit(bufferInfo.offset + bufferInfo.size);
                    mMediaMuxer.writeSampleData(trackIndex.index,encodeData,bufferInfo);
                }

                encoder.releaseOutputBuffer(encoderIndex,false);
                //退出循環
                if((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM)!=0) {
                    break;
                }


            }
        }

    }



    /**
     * 關閉編碼
     * @param encoder
     * @param bufferInfo
     *
     */
    public void closeEncoder(MediaCodec encoder,MediaCodec.BufferInfo bufferInfo,TrackIndex trackIndex) {
        drainEncoder(encoder,bufferInfo,trackIndex,true);
        encoder.stop();
        encoder.release();
        encoder = null;

    }

    /**
     * 關閉混合器
     */
    public void closeMuxer() {
        mMediaMuxer.stop();
        mMediaMuxer.release();
        mMediaMuxer = null;
        mMuxerStart = false;
    }

    //發送終止編碼信息
    public void stop() {
        if(!encodingService.isShutdown()) {
            encodingService.submit(new AudioEncodeTask(this,EncoderTaskType.FINALIZE_ENCODER));
        }
    }

    //終止編碼
    public void _stop() {
        eosReceived = true;
        Log.d(TAG,"停止編碼");
    }


    /**
     * 音頻編碼任務
     */
    class AudioEncodeTask implements Runnable {
        private static final String TAG = "AudioEncoderTask";
        private boolean is_initialized = false;
        private AudioEncoder encoder;
        private byte[] audio_data;
        long pts;
        private EncoderTaskType type;

        //進行編碼任務時 調用此構造方法
        public AudioEncodeTask(AudioEncoder encoder,byte[] audio_data,long pts) {
            this.encoder = encoder;
            this.audio_data = audio_data;
            this.pts = pts;
            is_initialized = true;
            this.type = EncoderTaskType.ENCODE_FRAME;
            //這裡是有數據的
//            Log.d(TAG,"AudioData--"+audio_data);
//            Log.d(TAG,"pts--"+pts);
        }
        //當要停止編碼任務時 調用此構造方法
        public AudioEncodeTask(AudioEncoder encoder,EncoderTaskType type) {
            this.type = type;

            if(type==EncoderTaskType.FINALIZE_ENCODER) {
                this.encoder = encoder;
                is_initialized = true;
            }
            Log.d(TAG,"完成...");

        }
        ////編碼
        private void encodeFrame() {
            Log.d(TAG,"audio_data---encoder--"+audio_data+" "+encoder);
            if(audio_data!=null && encoder!=null) {
                encoder._offerAudioEncoder(audio_data,pts);
                audio_data = null;
            }

        }

        //終止編碼
        private void finalizeEncoder() {
            encoder._stop();
        }

        @Override
        public void run() {
            Log.d(TAG,"is_initialized--"+is_initialized);
            if(is_initialized) {
                switch(type) {
                    case ENCODE_FRAME:
                        //進行編碼
                        encodeFrame();
                        break;
                    case FINALIZE_ENCODER:
                        //完成編碼
                        finalizeEncoder();
                        break;
                }
                is_initialized = false;
            }else {
                //打印錯誤日志
                Log.e(TAG,"AudioEncoderTask is not initiallized");
            }
        }
    }



}

具體代碼下載:https://github.com/hsk256/RecordAudioAndVideo

 

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved