Android錄制mp3格式文件
前言
最近做一個即時通信類的項目,由于要保證pc端,iOS端和Android端的通用性,最終統(tǒng)一為MP3格式,一直擔(dān)心MP3格式會不會很大,但是實測還是可以接受的。下面來看看具體步驟:
工具
MP3格式是用一個開源項目轉(zhuǎn)的,MP3lame,由于該項目用到了jni,所以需要大家配置好ndk環(huán)境,環(huán)境配置在此就不多說了,大家可以自行百度,最新的應(yīng)該很好配置。
創(chuàng)建jni
拷貝文件
下載好后(我下載的是3.98.4版本)打開,找到libmp3lame文件,將里面的.h和.c拷貝下來,在自己的工程里創(chuàng)建jni文件夾,在jni文件夾下新建一個文件夾(我的命名為lame-3.98.4_libmp3lame,后面會用到),將剛才拷貝的文件復(fù)制進去,然后再把include文件夾里的lame.h也拷貝進去。
創(chuàng)建Android.mk
在jni中創(chuàng)建文件,Android.mk
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LAME_LIBMP3_DIR := lame-3.98.4_libmp3lame LOCAL_MODULE := mp3lame LOCAL_SRC_FILES := $(LAME_LIBMP3_DIR)/bitstream.c $(LAME_LIBMP3_DIR)/fft.c $(LAME_LIBMP3_DIR)/id3tag.c $(LAME_LIBMP3_DIR)/mpglib_interface.c $(LAME_LIBMP3_DIR)/presets.c $(LAME_LIBMP3_DIR)/quantize.c $(LAME_LIBMP3_DIR)/reservoir.c $(LAME_LIBMP3_DIR)/tables.c $(LAME_LIBMP3_DIR)/util.c $(LAME_LIBMP3_DIR)/VbrTag.c $(LAME_LIBMP3_DIR)/encoder.c $(LAME_LIBMP3_DIR)/gain_analysis.c $(LAME_LIBMP3_DIR)/lame.c $(LAME_LIBMP3_DIR)/newmdct.c $(LAME_LIBMP3_DIR)/psymodel.c $(LAME_LIBMP3_DIR)/quantize_pvt.c $(LAME_LIBMP3_DIR)/set_get.c $(LAME_LIBMP3_DIR)/takehiro.c $(LAME_LIBMP3_DIR)/vbrquantize.c $(LAME_LIBMP3_DIR)/version.c com_maxi_mp3record_MP3Recorder.c include $(BUILD_SHARED_LIBRARY)
**注意:**LAME_LIBMP3_DIR := lame-3.98.4_libmp3lame 需要將其改為你的項目中的文件名,即上面說的jni下新建的文件夾。
大家應(yīng)該看到了最后一句的com_maxi_mp3record_MP3Recorder.c
很明顯這是我自己創(chuàng)建的.c文件。用來調(diào)用mp3lame中的接口的,對應(yīng)著我java中的com.maxi.mp3record.MP3Recorder.java。咱們先創(chuàng)建java文件。
創(chuàng)建MP3Recorder.java
對應(yīng)你的包名建一個MP3Recorder.java文件,該文件是java文件對應(yīng)你的包名建立即可。
package cn.ctvonline.android.modules.project.widget;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Handler;
/**
* <b>類功能描述:</b><div style="margin-left:40px;margin-top:-10px">
* MP3實時錄制功能,可暫停,注意因踩用Native開發(fā),不能混淆
*/
public class MP3Recorder {
private String filePath;
private int sampleRate;
private boolean isRecording = false;
private boolean isPause = false;
private Handler handler;
/**
* 開始錄音
*/
public static final int MSG_REC_STARTED = 1;
/**
* 結(jié)束錄音
*/
public static final int MSG_REC_STOPPED = 2;
/**
* 暫停錄音
*/
public static final int MSG_REC_PAUSE = 3;
/**
* 繼續(xù)錄音
*/
public static final int MSG_REC_RESTORE = 4;
/**
* 緩沖區(qū)掛了,采樣率手機不支持
*/
public static final int MSG_ERROR_GET_MIN_BUFFERSIZE = -1;
/**
* 創(chuàng)建文件時撲街了
*/
public static final int MSG_ERROR_CREATE_FILE = -2;
/**
* 初始化錄音器時撲街了
*/
public static final int MSG_ERROR_REC_START = -3;
/**
* 錄緊音的時候出錯
*/
public static final int MSG_ERROR_AUDIO_RECORD = -4;
/**
* 編碼時掛了
*/
public static final int MSG_ERROR_AUDIO_ENCODE = -5;
/**
* 寫文件時掛了
*/
public static final int MSG_ERROR_WRITE_FILE = -6;
/**
* 沒法關(guān)閉文件流
*/
public static final int MSG_ERROR_CLOSE_FILE = -7;
public MP3Recorder(int sampleRate) {
this.sampleRate = sampleRate;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
/**
* 開片
*/
public void start() {
if (isRecording) {
return;
}
new Thread() {
@Override
public void run() {
android.os.Process
.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
// 根據(jù)定義好的幾個配置,來獲取合適的緩沖大小
final int minBufferSize = AudioRecord.getMinBufferSize(
sampleRate, AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT);
if (minBufferSize < 0) {
if (handler != null) {
handler.sendEmptyMessage(MSG_ERROR_GET_MIN_BUFFERSIZE);
}
return;
}
AudioRecord audioRecord = new AudioRecord(
MediaRecorder.AudioSource.MIC, sampleRate,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT, minBufferSize * 2);
// 5秒的緩沖
short[] buffer = new short[sampleRate * (16 / 8) * 1 * 5];
byte[] mp3buffer = new byte[(int) (7200 + buffer.length * 2 * 1.25)];
FileOutputStream output = null;
try {
File file = createSDFile(filePath);
output = new FileOutputStream(file);
} catch (FileNotFoundException e) {
if (handler != null) {
handler.sendEmptyMessage(MSG_ERROR_CREATE_FILE);
}
return;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
MP3Recorder.init(sampleRate, 1, sampleRate, 32);
isRecording = true; // 錄音狀態(tài)
isPause = false; // 錄音狀態(tài)
try {
try {
audioRecord.startRecording(); // 開啟錄音獲取音頻數(shù)據(jù)
if (mListener != null) {
mListener.wellPrepared();
}
} catch (IllegalStateException e) {
// 不給錄音...
if (handler != null) {
handler.sendEmptyMessage(MSG_ERROR_REC_START);
}
return;
}
try {
// 開始錄音
if (handler != null) {
handler.sendEmptyMessage(MSG_REC_STARTED);
}
int readSize = 0;
boolean pause = false;
while (isRecording) {
/*--暫停--*/
if (isPause) {
if (!pause) {
handler.sendEmptyMessage(MSG_REC_PAUSE);
pause = true;
}
continue;
}
if (pause) {
handler.sendEmptyMessage(MSG_REC_RESTORE);
pause = false;
}
/*--End--*/
/*--實時錄音寫數(shù)據(jù)--*/
readSize = audioRecord.read(buffer, 0,
minBufferSize);
voiceLevel = getVoiceSize(readSize, buffer);
if (readSize < 0) {
if (handler != null) {
handler.sendEmptyMessage(MSG_ERROR_AUDIO_RECORD);
}
break;
} else if (readSize == 0) {
;
} else {
int encResult = MP3Recorder.encode(buffer,
buffer, readSize, mp3buffer);
if (encResult < 0) {
if (handler != null) {
handler.sendEmptyMessage(MSG_ERROR_AUDIO_ENCODE);
}
break;
}
if (encResult != 0) {
try {
output.write(mp3buffer, 0, encResult);
} catch (IOException e) {
if (handler != null) {
handler.sendEmptyMessage(MSG_ERROR_WRITE_FILE);
}
break;
}
}
}
/*--End--*/
}
/*--錄音完--*/
int flushResult = MP3Recorder.flush(mp3buffer);
if (flushResult < 0) {
if (handler != null) {
handler.sendEmptyMessage(MSG_ERROR_AUDIO_ENCODE);
}
}
if (flushResult != 0) {
try {
output.write(mp3buffer, 0, flushResult);
} catch (IOException e) {
if (handler != null) {
handler.sendEmptyMessage(MSG_ERROR_WRITE_FILE);
}
}
}
try {
output.close();
} catch (IOException e) {
if (handler != null) {
handler.sendEmptyMessage(MSG_ERROR_CLOSE_FILE);
}
}
/*--End--*/
} finally {
audioRecord.stop();
audioRecord.release();
}
} finally {
MP3Recorder.close();
isRecording = false;
}
if (handler != null) {
handler.sendEmptyMessage(MSG_REC_STOPPED);
}
}
}.start();
}
public void stop() {
isRecording = false;
}
public void pause() {
isPause = true;
}
public void restore() {
isPause = false;
}
public boolean isRecording() {
return isRecording;
}
public boolean isPaus() {
if (!isRecording) {
return false;
}
return isPause;
}
// 獲得聲音的level
public int getVoiceSize(int r, short[] buffer) {
if (isRecording) {
try {
long v = 0;
// 將 buffer 內(nèi)容取出,進行平方和運算
for (int i = 0; i < buffer.length; i++) {
v += buffer[i] * buffer[i];
}
// 平方和除以數(shù)據(jù)總長度,得到音量大小。
double mean = v / (double) r;
double volume = 10 * Math.log10(mean);
return (((int) volume / 10) - 1);
} catch (Exception e) {
// TODO Auto-generated catch block
}
}
return 1;
}
/**
* 在SD卡上創(chuàng)建文件
*
* @throws IOException
*/
public static File createSDFile(String fileName) throws IOException {
File file = new File(fileName);
if (!isFileExists(file))
if (file.isDirectory()) {
file.mkdirs();
} else {
file.createNewFile();
}
return file;
}
private int voiceLevel;
public int getVoiceLevel() {
return voiceLevel;
}
public interface AudioStageListener {
void wellPrepared();
}
public AudioStageListener mListener;
public void setOnAudioStageListener(AudioStageListener listener) {
mListener = listener;
}
/**
* 錄音狀態(tài)管理
*
* @see RecMicToMp3#MSG_REC_STARTED
* @see RecMicToMp3#MSG_REC_STOPPED
* @see RecMicToMp3#MSG_REC_PAUSE
* @see RecMicToMp3#MSG_REC_RESTORE
* @see RecMicToMp3#MSG_ERROR_GET_MIN_BUFFERSIZE
* @see RecMicToMp3#MSG_ERROR_CREATE_FILE
* @see RecMicToMp3#MSG_ERROR_REC_START
* @see RecMicToMp3#MSG_ERROR_AUDIO_RECORD
* @see RecMicToMp3#MSG_ERROR_AUDIO_ENCODE
* @see RecMicToMp3#MSG_ERROR_WRITE_FILE
* @see RecMicToMp3#MSG_ERROR_CLOSE_FILE
*/
public void setHandle(Handler handler) {
this.handler = handler;
}
/*--以下為Native部分--*/
static {
System.loadLibrary("mp3lame");
}
/**
* 初始化錄制參數(shù)
*/
public static void init(int inSamplerate, int outChannel,
int outSamplerate, int outBitrate) {
init(inSamplerate, outChannel, outSamplerate, outBitrate, 7);
}
/**
* 初始化錄制參數(shù) quality:0=很好很慢 9=很差很快
*/
public native static void init(int inSamplerate, int outChannel,
int outSamplerate, int outBitrate, int quality);
/**
* 音頻數(shù)據(jù)編碼(PCM左進,PCM右進,MP3輸出)
*/
public native static int encode(short[] buffer_l, short[] buffer_r,
int samples, byte[] mp3buf);
/**
* 據(jù)說錄完之后要刷干凈緩沖區(qū)
*/
public native static int flush(byte[] mp3buf);
/**
* 結(jié)束編碼
*/
public native static void close();
}
創(chuàng)建c文件
在創(chuàng)建c文件,創(chuàng)建在jni下,命名就按你的java文件所在的包名命名”.”替換為“_”。例如:com_maxi_mp3record_MP3Recorder.c。當(dāng)然還得有頭文件:com_maxi_mp3record_MP3Recorder.h。
com_maxi_mp3record_MP3Recorder.c
#include "lame-3.98.4_libmp3lame/lame.h"
#include "com_maxi_mp3record_MP3Recorder.h"
static lame_global_flags *glf = NULL;
JNIEXPORT void JNICALL Java_com_maxi_mp3record_MP3Recorder_init(
JNIEnv *env, jclass cls, jint inSamplerate, jint outChannel,
jint outSamplerate, jint outBitrate, jint quality) {
if (glf != NULL) {
lame_close(glf);
glf = NULL;
}
glf = lame_init();
lame_set_in_samplerate(glf, inSamplerate);
lame_set_num_channels(glf, outChannel);
lame_set_out_samplerate(glf, outSamplerate);
lame_set_brate(glf, outBitrate);
lame_set_quality(glf, quality);
lame_init_params(glf);
}
JNIEXPORT jint JNICALL Java_com_maxi_mp3record_MP3Recorder_encode(
JNIEnv *env, jclass cls, jshortArray buffer_l, jshortArray buffer_r,
jint samples, jbyteArray mp3buf) {
jshort* j_buffer_l = (*env)->GetShortArrayElements(env, buffer_l, NULL);
jshort* j_buffer_r = (*env)->GetShortArrayElements(env, buffer_r, NULL);
const jsize mp3buf_size = (*env)->GetArrayLength(env, mp3buf);
jbyte* j_mp3buf = (*env)->GetByteArrayElements(env, mp3buf, NULL);
int result = lame_encode_buffer(glf, j_buffer_l, j_buffer_r,
samples, j_mp3buf, mp3buf_size);
(*env)->ReleaseShortArrayElements(env, buffer_l, j_buffer_l, 0);
(*env)->ReleaseShortArrayElements(env, buffer_r, j_buffer_r, 0);
(*env)->ReleaseByteArrayElements(env, mp3buf, j_mp3buf, 0);
return result;
}
JNIEXPORT jint JNICALL Java_com_maxi_mp3record_MP3Recorder_flush(
JNIEnv *env, jclass cls, jbyteArray mp3buf) {
const jsize mp3buf_size = (*env)->GetArrayLength(env, mp3buf);
jbyte* j_mp3buf = (*env)->GetByteArrayElements(env, mp3buf, NULL);
int result = lame_encode_flush(glf, j_mp3buf, mp3buf_size);
(*env)->ReleaseByteArrayElements(env, mp3buf, j_mp3buf, 0);
return result;
}
JNIEXPORT void JNICALL Java_com_maxi_mp3record_MP3Recorder_close(
JNIEnv *env, jclass cls) {
lame_close(glf);
glf = NULL;
}
com_maxi_mp3record_MP3Recorder.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_maxi_mp3record_MP3Recorder */
#ifndef _Included_com_maxi_mp3record_MP3Recorder
#define _Included_com_maxi_mp3record_MP3Recorder
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_maxi_mp3record_MP3Recorder
* Method: init
* Signature: (IIIII)V
*/
JNIEXPORT void JNICALL Java_com_maxi_mp3record_MP3Recorder_init
(JNIEnv *, jclass, jint, jint, jint, jint, jint);
/*
* Class: com_maxi_mp3record_MP3Recorder
* Method: encode
* Signature: ([S[SI[B)I
*/
JNIEXPORT jint JNICALL Java_com_maxi_mp3record_MP3Recorder_encode
(JNIEnv *, jclass, jshortArray, jshortArray, jint, jbyteArray);
/*
* Class: com_maxi_mp3record_MP3Recorder
* Method: flush
* Signature: ([B)I
*/
JNIEXPORT jint JNICALL Java_com_maxi_mp3record_MP3Recorder_flush
(JNIEnv *, jclass, jbyteArray);
/*
* Class: com_maxi_mp3record_MP3Recorder
* Method: close
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_maxi_mp3record_MP3Recorder_close
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
這倆文件別只復(fù)制不看內(nèi)容啊,里面可都是連接java和c的接口,所以不能有差錯。舉個例子吧,#ifndef _Included_com_maxi_mp3record_MP3Recorder,這個就得替換成你對應(yīng)的包名,里面所有都得替換成你自己的程序?qū)?yīng)的包名。
編譯ndk
創(chuàng)建Application.mk在你的項目文件的jni下,在里面寫入:
APP_ABI := all
如果不加,NDK只會編譯“armeabi”,然而安卓有很多不同類型的處理器,所以我們不止需要arm。相信你們搞到現(xiàn)在了肯定ndk都配置好了,然后打開終端,進入到你的項目(找到你的ndk目錄下的ndk-bulid,最好把它添加到環(huán)境變量里,對于以后編譯比較方便,在此默認你沒添加環(huán)境變量),執(zhí)行ndk-bulid。稍等片刻你會發(fā)現(xiàn)你的項目里多了一個obj文件夾,obj文件夾下會生成”arm64-v8a”、”armeabi”、”armeabi-v7a”、”mips”、”mips64”、”x86”、”x86_64”。打開它,各個文件夾下會有一個libmp3lame.so。ok,沒錯那就是你要的“滑板鞋”。將它放入你的libs文件下,沒有自行創(chuàng)建,各個平臺便都可以加載了。
使用方法
MP3Recorder recorder = new MP3Recorder(8000); recorder.setFilePath(voicePath);//錄音保存目錄 recorder.start();//開始錄音 recorder.stop();//錄音結(jié)束 recorder.getVoiceLevel()//這是我封裝的獲取音頻振幅接口,大家可以用來錄音的時候顯示聲音大小,數(shù)據(jù)自行調(diào)節(jié)。
總結(jié)
之前一直用MediaRecorder錄音,發(fā)現(xiàn)錄出來的只能是amr、acc等格式,用lame轉(zhuǎn)MP3感覺是不可行的。我試了沒能成功,不知道具體是什么原因,所以大家有時間可以研究研究,沒時間就不要嘗試了。
Mp3lame錄制出來的聲音還是挺靠譜的(不過據(jù)聽說iOS就有些莎莎聲),然后錄制出來的大小還是可以接受的,五秒鐘的音頻大概在20k左右的樣子吧。使用還是很方便的。如果有什么疑問或建議請留言哈。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
基于Android實現(xiàn)的文件同步設(shè)計方案
隨著用戶對自身數(shù)據(jù)保護意識的加強,讓用戶自己維護自己的數(shù)據(jù)也成了獨立開發(fā)產(chǎn)品時的一個賣點,若只針對少量的文件進行同步,則實現(xiàn)起來比較簡單,當(dāng)針對一個多層級目錄同步時,情況就復(fù)雜多了,本文我分享下我的設(shè)計思路2023-10-10
android使用ViewPager組件實現(xiàn)app引導(dǎo)查看頁面
這篇文章主要為大家詳細介紹了android使用ViewPager組件實現(xiàn)app引導(dǎo)查看頁面,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-07-07
Android自定義View基礎(chǔ)開發(fā)之圖片加載進度條
這篇文章主要介紹了Android自定義View基礎(chǔ)開發(fā)之圖片加載進度條,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-08-08
詳解Android Studio實現(xiàn)用戶登陸界面demo(xml實現(xiàn))
這篇文章主要介紹了詳解Android Studio實現(xiàn)用戶登陸界面demo,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05
Flutter基于Dart Unwrapping Multiple Optional小技巧
這篇文章主要為大家介紹了Flutter Unwrapping Multiple Optional打開多個選項小技巧示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12
Android使用setCustomTitle()方法自定義對話框標(biāo)題
Android有自帶的對話框標(biāo)題,但是不太美觀,如果要給彈出的對話框設(shè)置一個自定義的標(biāo)題,使用AlertDialog.Builder的setCustomTitle()方法非常方便,接下來通過本文給大家介紹Android使用setCustomTitle()方法自定義對話框標(biāo)題,感興趣的朋友一起學(xué)習(xí)吧2016-02-02

