最近在做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