android平臺下基於MediaRecorder和AudioRecord實現錄製AAC、PCM音訊資料
阿新 • • 發佈:2018-12-03
音視訊實踐學習
- android全平臺編譯ffmpeg以及x264與fdk-aac實踐
- ubuntu下使用nginx和nginx-rtmp-module配置直播推流伺服器
- android全平臺編譯ffmpeg合併為單個庫實踐
- android-studio使用cmake編譯ffmpeg實踐
- android全平臺下基於ffmpeg解碼MP4視訊檔案為YUV檔案
- android全平臺編譯ffmpeg支援命令列實踐
- android全平臺基於ffmpeg解碼本地MP4視訊推流到RTMP伺服器
- android平臺下音訊編碼之編譯LAME庫轉碼PCM為MP3
- ubuntu平臺下編譯vlc-android視訊播放器實踐
- 圖解YU12、I420、YV12、NV12、NV21、YUV420P、YUV420SP、YUV422P、YUV444P的區別
- 圖解RGB565、RGB555、RGB16、RGB24、RGB32、ARGB32等格式的區別
- YUV420P、YUV420SP、NV12、NV21和RGB互相轉換並存儲為JPEG以及PNG圖片
- android全平臺編譯libyuv庫實現YUV和RGB的轉換
- android平臺下基於ffmpeg對相機採集的NV21資料編碼為MP4視訊檔案
- android平臺下基於ffmpeg採集Camera資料編碼成H.264推流到RTMP伺服器
- android平臺下基於ffmpeg和ANativeWindow實現簡單的視訊播放器
- android平臺下基於ffmpeg實現對相機預覽截圖的功能(NV21資料編碼為JPEG檔案)
- android平臺下基於ffmpeg的swscale模組實現對YUV和RGB資料進行轉換
- android平臺下基於MediaRecorder和AudioRecord實現錄製AAC、PCM音訊資料
概述
在android sdk
中提供了兩種方式來實現音訊的採集:MediaRecorder
和AudioRecord
,其中的MediaRecorder
處於更上層,它可以對音訊錄製的資料編碼成AMR
MP3
等格式,並存儲為檔案,而AudioRecord
則更靈活,因為它可以錄製最原始的PCM流資料
,這個在直播中很常見,
在android ndk
中還有一種是基於OpenSLES
的方式來處理音訊
,但是對API的版本要求最低為23以上,本文暫時不作說明,後續有時間再更新相關的處理操作
錄製介面
使用不同的方式處理音訊,API肯定有所不同,這裡暫時先抽象一個介面:
public interface IAudioRecorder {
interface RECORD_STATE {
int STATE_RECORDING = 0;
int STATE_SUCCESS = 1;
int STATE_ERROR = 2;
}
/**
* 初始化
*/
void initRecorder();
/**
* 開始錄製音訊
*/
int recordStart();
/**
* 停止錄製音訊
*/
void recordStop();
}
基於MediaRecorder
系統都已經給你提供好了的輪子,我們只需要設定幾個引數就可以了:
public class MediaRecordRecorder implements IAudioRecorder {
/**
* 基於MediaRecorder錄製音訊
*/
private MediaRecorder mMediaRecorder;
/**
* 是否正在錄製
*/
private boolean isRecord = false;
private String filePath;
public MediaRecordRecorder(String filePath){
this.filePath = filePath;
}
@Override
public void initRecorder() {
//例項化MediaRecorder物件
mMediaRecorder = new MediaRecorder();
//從麥克風採集聲音資料
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
//設定輸出格式為MP4
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
//設定取樣頻率44100 頻率越高,音質越好,檔案越大
mMediaRecorder.setAudioSamplingRate(44100);
//設定聲音資料編碼格式,音訊通用格式是AAC
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
//設定編碼頻率
mMediaRecorder.setAudioEncodingBitRate(96000);
//設定輸出檔案
mMediaRecorder.setOutputFile(filePath);
}
@Override
public int recordStart() {
//判斷是否有外部儲存裝置sdcard
if (isRecord) {
return RECORD_STATE.STATE_RECORDING;
} else {
if (mMediaRecorder != null) {
try {
mMediaRecorder.prepare();
mMediaRecorder.start();
// 讓錄製狀態為true
isRecord = true;
return RECORD_STATE.STATE_SUCCESS;
} catch (IOException e) {
e.printStackTrace();
}
}
return RECORD_STATE.STATE_ERROR;
}
}
@Override
public void recordStop() {
if (mMediaRecorder != null) {
isRecord = false;
mMediaRecorder.stop();
mMediaRecorder.release();
mMediaRecorder = null;
}
}
}
筆者這裡只是作演示操作,因此部分引數直接硬編碼,實際過程最好以引數的形式對外提供
基於AudioRecord
這種方式需要我們自己線上程中,迴圈讀取音訊資料:
public class AudioRecordRecorder implements IAudioRecorder {
private static final String TAG = "AudioRecordRecorder";
private int AUDIO_SOURCE = MediaRecorder.AudioSource.MIC;
/**
* 音訊取樣率
*/
public static int SAMPLE_RATE = 44100;
/**
* 單聲道
*/
public final static int CHANNEL = AudioFormat.CHANNEL_IN_STEREO;
/**
* 16位元
*/
public final static int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
/**
* 音訊錄製例項
*/
protected AudioRecord audioRecord;
/**
* 錄製執行緒
*/
private Thread recordThread;
/**
* 輸出的檔案路徑
*/
private String pcmPath;
/**
* 緩衝區大小
*/
private int bufferSize = 0;
/**
* 是否正在錄製
*/
private boolean isRecording = false;
/**
* 回撥原始的PCM資料
*/
private OnAudioRecordListener mOnAudioRecordListener;
public AudioRecordRecorder(String filePath) {
this.pcmPath = filePath;
}
@Override
public void initRecorder() {
if (null != audioRecord) {
audioRecord.release();
}
try {
bufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL, AUDIO_FORMAT);
audioRecord = new AudioRecord(AUDIO_SOURCE, SAMPLE_RATE, CHANNEL, AUDIO_FORMAT, bufferSize);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public int recordStart() {
if (isRecording) {
return RECORD_STATE.STATE_RECORDING;
} else if (audioRecord != null && audioRecord.getState() == AudioRecord.STATE_INITIALIZED) {
try {
audioRecord.startRecording();
isRecording = true;
recordThread = new Thread(new AudioRecordRunnable());
recordThread.start();
return RECORD_STATE.STATE_SUCCESS;
} catch (Exception e) {
e.printStackTrace();
}
}
return RECORD_STATE.STATE_ERROR;
}
/**
* 停止音訊錄製
*/
@Override
public void recordStop() {
try {
if (audioRecord != null) {
isRecording = false;
try {
if (recordThread != null) {
recordThread.join();
recordThread = null;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
//釋放資源
recordRelease();
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void recordRelease() {
if (audioRecord != null) {
if (audioRecord.getState() == AudioRecord.STATE_INITIALIZED) {
audioRecord.stop();
}
audioRecord.release();
audioRecord = null;
}
}
private class AudioRecordRunnable implements Runnable {
private FileOutputStream outputStream = null;
@Override
public void run() {
try {
if (!TextUtils.isEmpty(pcmPath)) {
outputStream = new FileOutputStream(pcmPath);
}
byte[] audioBuffer = new byte[bufferSize];
while (isRecording && audioRecord != null) {
int audioSampleSize = audioRecord.read(audioBuffer, 0, bufferSize);
if (audioSampleSize > 0) {
if (outputStream != null) {
outputStream.write(audioBuffer);
}
if (mOnAudioRecordListener != null) {
mOnAudioRecordListener.onAudioBuffer(audioBuffer, audioBuffer.length);
}
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
close(outputStream);
outputStream = null;
}
}
}
private void close(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void setOnAudioRecordListener(OnAudioRecordListener onAudioRecordListener) {
this.mOnAudioRecordListener = onAudioRecordListener;
}
public interface OnAudioRecordListener {
void onAudioBuffer(byte[] buffer, int length);
}
}
工程實踐
新建工程ffmpeg-audio-encode
,在應用的點選事件中處理如下:
/**
* 點選開始錄製按鈕
* @param view
*/
public void onRecordStart(View view) {
File targetDir = getExternalFilesDir(null);
if (audioRecorder == null) {
audioRecorder = new MediaRecordRecorder(targetDir.getAbsolutePath()+ File.separator+"output.aac");
//audioRecorder = new AudioRecordRecorder(targetDir.getAbsolutePath()+ File.separator+"output.pcm");
}
audioRecorder.initRecorder();
audioRecorder.recordStart();
//更新按鈕狀態
mBtnStart.setEnabled(false);
mBtnStop.setEnabled(true);
}
/**
* 點選停止按鈕
* @param view
*/
public void onRecordStop(View view) {
if (audioRecorder != null) {
audioRecorder.recordStop();
audioRecorder = null;
}
//更新按鈕狀態
mBtnStart.setEnabled(true);
mBtnStop.setEnabled(false);
}
基於MediaRecorder
方式我們可以直接得到output.aac
檔案,這個直接使用系統的音訊播放器
即可播放
基於AudioRecord
方式我們可以直接得到output.ocm
檔案,需要匯出到電腦上,使用ffplay播放器
進行播放
ffplay -ar 44100 -channels 2 -f s16le -i output.pcm
專案地址:ffmpeg-audio-encode
https://github.com/byhook/ffmpeg4android