Android录音实现——使用AtudioRecord

最近在做android中录音录屏的功能,以前也是从未接触多媒体这块,然后从不会到一点点的摸索,参考大神们的代码,到现在算是入门了,今天就总结一下android中的录音部分,后面总结录屏。

在android中实现录音共有三种方式:

  • 通过意图捕获音频。这是android中最简单的一种方式,就是通过一个意图利用已有的、提供录制功能的应用程序。android系统中都会再带一个录音程序,我们可以通过意图来调用这个录音程序,从而实现录音功能。
  • MediaRecorder类实现录音。MediaRecorder类是android中用来捕获音频和视频的多媒体类,通过这种方式录制音频也是比较简单的。通过设置音频源,输出格式,设置音频编解码器进行编解码,最后将音频输出到文件中。
  • AudioRecord录制原始音频。这种方式相对于前两种比较麻烦,需要我们自己处理的事情较多,因此也是最灵活的。AudioRecord允许访问原始音频流,这种音频流是不能直接进行播放的,需要使用AudioTrack来进行播放原始音频。如果要使用这种方式来将音频保存到文件中,并可像MP3文件一样直接打开的话,就需要对原始音频进行编解码,然后用混合器(Muxer)进行输出到文件中。

我所用到的是第三种方式来实现录音的,因为我需要将音频添加到视频中去,因此得选用第三中最灵活的方式。

使用AudioRecord录制音频并不难,但是要将原始音频进行编码并输出到一个可播放的文件中就有点麻烦。先说说实现这个功能的大概思路:我们需要两个任务,一个任务用来进行采集音频数据,在采集音频数据的同时将音频数据不断的发送给编码的任务,将这些数据按照指定格式进行编码,然后输出到文件中。

具体实现:

  • 使用AudioRecord捕获原始音频流。采集工作很简单,我们只需要构造一个AudioRecord对象,然后传入各种不同配置的参数即可。

1.音频源:我们可以使用麦克风作为采集音频的数据源。

2.采样率:一秒钟对声音数据的采样次数,采样率越高,音质越好。

3.音频通道:单声道,双声道等,

4.音频格式:一般选用PCM格式,即原始的音频样本。

5.缓冲区大小:音频数据写入缓冲区的总数,可以通过AudioRecord.getMinBufferSize获取最小的缓冲区。(将音频采集到缓冲区中然后再从缓冲区中读取)。

<span style="font-family:Courier New;font-size:14px;">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;
                }

        }
    }

}</span>
  • 使用MediaCodec进行音频编码.

MediaCodec是android的一个编解码类。将获取到的原始音频流先进行特定的解码,然后进行数据处理,再编码为指定的格式。具体可参考这篇博客:http://blog.csdn.net/mouse_1894/article/details/27311099

  • MediaMuxer 这是一个混合器,用来将编码好的音频数据输出到文件。

<span style="font-family:Courier New;font-size:14px;">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");
            }
        }
    }

</span>
}

具体代码下载:https://github.com/hsk256/RecordAudioAndVideo

时间: 2024-08-01 14:48:54

Android录音实现——使用AtudioRecord的相关文章

Android录音--AudioRecord、MediaRecorder

Android提供了两个API用于实现录音功能:android.media.AudioRecord.android.media.MediaRecorder. 网上有很多谈论这两个类的资料.现在大致总结下: 1.AudioRecord 主要是实现边录边播(AudioRecord+AudioTrack)以及对音频的实时处理(如会说话的汤姆猫.语音) 优点:语音的实时处理,可以用代码实现各种音频的封装 缺点:输出是PCM语音数据,如果保存成音频文件,是不能够被播放器播放的,所以必须先写代码实现数据编码

通过Android录音进行简单音频分析

Android录音有MediaRecorder和AudioRecord两种方式,前者使用方便,可以直接生成录音文件,但是录音格式为aac和amr等等,都经过压缩处理,不方便进行音频分析. 而用AudioRecord可以得到PCM编码的原音频数据,可以用FFT对数据进行处理,简单分析声音的频率. 1.AndroidRecord录音 private static final String FILE_NAME = "MainMicRecord"; private static final i

android录音相关

android的麦克风在现在的生活中发挥着很大的作用,打电话,视频聊天,语音识别等等. android sdk的api里提供了很方便的调用方法,下面写一个小的DEMO. 五个按钮:开始录音,停止,播放,删除录音,保存(令存为) recordBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { try{ _recordAudioFile=File.createTempFile("

Android 录音(MediaRecorder)与播放(MediaPlayer)

通过MediaRecorder和MediaPlayer实现声音的录制和播放,代码比较简单,直接贴代码.xml文件里面只有四个按钮就不贴了. UI 代码: VoiceActivity.class package com.zy.ione; import com.zy.media.UPlayer; import com.zy.media.URecorder; import android.app.Activity; import android.os.Bundle; import android.os

Android录音,拍照,摄像

import java.io.File; import java.text.SimpleDateFormat; import java.util.Date; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.provider.MediaStore; import android.app.Activity; import android.content.In

Android 录音

MediaRecorder类具体解释 移动电话通常具有一个麦克风和照相机,和Android该系统将能够利用这些硬件的优点来记录的音频和视频. 添加支持的音频和视频录制,Android该系统提供了MediaRecorder的类.该类的使用也很easy.以下让我们来了解一下这个类 一.结构 java.lang.Object->android.media.MediaRecorder 二.类概述: 用于录制音频和视频的一个类. 三.状态图: 说明: 与MediaPlayer类很相似MediaRecord

Android 录音器

Android自带的mediarecoder录音器不含pause暂停功能,解决方法:录制多个音频片段,最后合成一个文件. 参照 : http://blog.csdn.net/a601445984/article/details/44239717

Android 录音和摄像头权限适配【转】

本文转载自:http://blog.csdn.net/self_study/article/details/52965045 最近在研究权限适配的相关内容,整理以前的权限博客如下:  android permission权限与安全机制解析(上)  android permission权限与安全机制解析(下)  Android 悬浮窗权限各机型各系统适配大全  这篇博客主要是介绍录音权限和摄像头权限的适配,android permission权限与安全机制解析(下)这篇博客中我介绍到了 6.0 之

Android 录音getMaxAmplitude()

这个方法是用来获取在前一次调用此方法之后录音中出现的最大振幅,文档解释如下: Returns the maximum absolute amplitude that was sampled since the last call to this method. Call this only after the setAudioSource(). 很多人遇到问题,说是返回值为0,文档中解释如下:the maximum absolute amplitude measured since the la