編輯:關於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");
}
}
}
}
AndrowListView實現(自定義游戲列表)android中不推薦的方法,要考慮向下兼容,用了推薦的新方法,可能不兼容舊版本系統的手機
activity類 package com.kane.listview; import java.util.ArrayList; import java.util.D
Android四大組件之Service精通
(一)概述本節,我們繼續來研究Service(服務)組件,本節將會學習下Android中的AIDL跨進程通信的一些 概念,並不深入到源碼層次,暫時知道是什麼,會用即可!(
android應用程序訪問Linux驅動第二步-實現並測試hardware層
不管是出於什麼樣地考慮,android系統終究是提供了hardware層來封裝了對Linux的驅動的訪問,同時為上層提供了一個統一的硬件接口和硬件形態。一.Hardwar
Android項目實現短信的發送、接收和對短信進行攔截
說實話,關於Android中對短信的一些相關操作是一個比較入門的東西。那我現在還要來寫這一篇博客的原因只是因為現在開發中有相關內容,而又想將這些東西分享給更多的人來學習,