安卓錄音技術整合

先列個題目,待整理好了再搬到這裏,嘻嘻!最近正在學習當中,慢慢整理到這裏來,希望對大家有幫助吧!

 

Android多媒體框架包含獲取和編碼多種音頻格式的支持,所以你可以輕鬆地把音頻合併到你的應用中.如果設備支持,你可以使用MediaRecorder APIs 進行錄音.

本章向你展示如何寫一個應用從設備上的microphone獲取音頻,然後保存並回放.

注:Android模擬器不具有錄音的能力,但是真實的設備一般都具有此功能.

 

執行音頻獲取
從設備獲取音頻比回放音頻或視頻要複雜一點,但是也還算簡單:


創建一個android.media.MediaRecorder的新實例.
使用MediaRecorder.setAudioSource()設置音頻源,一般要使用MediaRecorder.AudioSource.MIC.
使用MediaRecorder.setOutputFormat()設置輸出文件的格式.
使用MediaRecorder.setOutputFile()設置輸出文件的名字.
使用MediaRecorder.setAudioEncoder()設置音頻編碼.
調用MediaRecorder 實例的MediaRecorder.prepare().
MediaRecorder.start()開始獲取音頻.
調用MediaRecorder.stop().停止.
當你用完MediaRecorder實例後,調用MediaRecorder.release(),就會立即釋放資源.

 

示例:錄音


package com.ppmeet;
import java.io.IOException;
import Android.app.Activity;
import android.graphics.PixelFormat;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
/**
* class name:TestBasicAudio<BR>
* class description:Basic Record Audio Demo<BR>
*
* @version 1.00 2011/12/01
* @author CODYY)peijiangping
*/
public class TestBasicAudio extends Activity {
private Button button_start;
private Button button_stop;
private MediaRecorder recorder;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFormat(PixelFormat.TRANSLUCENT);// 讓界面橫屏
requestWindowFeature(Window.FEATURE_NO_TITLE);// 去掉界面標題
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
// 重新設置界面大小
setContentView(R.layout.main);
init();
}
private void init() {
button_start = (Button) this.findViewById(R.id.start);
button_stop = (Button) this.findViewById(R.id.stop);
button_stop.setOnClickListener(new AudioListerner());
button_start.setOnClickListener(new AudioListerner());
}
class AudioListerner implements OnClickListener {
@Override
public void onClick(View v) {
if (v == button_start) {
initializeAudio();
}
if (v == button_stop) {
recorder.stop();// 停止刻錄
// recorder.reset(); // 重新啓動MediaRecorder.
recorder.release(); // 刻錄完成一定要釋放資源
// recorder = null;
}
}
private void initializeAudio() {
recorder = new MediaRecorder();// new出MediaRecorder對象
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
// 設置MediaRecorder的音頻源爲麥克風
recorder.setOutputFormat(MediaRecorder.OutputFormat.RAW_AMR);
// 設置MediaRecorder錄製的音頻格式
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
// 設置MediaRecorder錄製音頻的編碼爲amr.貌似android就支持amr編碼。
recorder.setOutputFile("/sdcard/peipei.amr");
// 設置錄製好的音頻文件保存路徑
try {
recorder.prepare();// 準備錄製
recorder.start();// 開始錄製
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
AndroidMainfest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ppmeet"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="8" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:name=".TestBasicAudio"
android:screenOrientation="landscape" >
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<!-- 聯網權限 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 往SDCard寫入數據權限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- 錄音權限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- 在SDCard中創建與刪除文件權限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
</manifest>

示例:錄音並回放錄音

下面的例子演示瞭如何設置,開始以及停止音頻獲取,以及回放錄製的文件.

 

/*
* 如果輸出文件被寫入外部存儲,
* 本應用需要具有寫外部存儲的權限,
* 還要具有錄音的權限.這些權限必須
* 在AndroidManifest.xml 文件中聲明,像這樣:
*
* <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
* <uses-permission android:name="android.permission.RECORD_AUDIO" />
*
*/
package com.android.audiorecordtest;
import android.app.Activity;
import android.widget.LinearLayout;
import android.os.Bundle;
import android.os.Environment;
import android.view.ViewGroup;
import android.widget.Button;
import android.view.View;
import android.view.View.OnClickListener;
import android.content.Context;
import android.util.Log;
import android.media.MediaRecorder;
import android.media.MediaPlayer;
import java.io.IOException;
public class AudioRecordTest extends Activity
{
private static final String LOG_TAG = "AudioRecordTest";
private static String mFileName = null;
   //錄音按鈕
private RecordButton mRecordButton = null;
private MediaRecorder mRecorder = null;
   //回放按鈕
private PlayButton   mPlayButton = null;
private MediaPlayer   mPlayer = null;
   //當錄音按鈕被click時調用此方法,開始或停止錄音
private void onRecord(boolean start) {
if (start) {
startRecording();
} else {
stopRecording();
}
}
   //當播放按鈕被click時調用此方法,開始或停止播放
private void onPlay(boolean start) {
if (start) {
startPlaying();
} else {
stopPlaying();
}
}
private void startPlaying() {
mPlayer = new MediaPlayer();
try {
    //設置要播放的文件
mPlayer.setDataSource(mFileName);
mPlayer.prepare();
    //播放之
mPlayer.start();
} catch (IOException e) {
Log.e(LOG_TAG, "prepare() failed");
}
}
//停止播放
private void stopPlaying() {
mPlayer.release();
mPlayer = null;
}
private void startRecording() {
mRecorder = new MediaRecorder();
//設置音源爲Micphone
mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
//設置封裝格式
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mRecorder.setOutputFile(mFileName);
//設置編碼格式
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
try {
mRecorder.prepare();
} catch (IOException e) {
Log.e(LOG_TAG, "prepare() failed");
}
mRecorder.start();
}
private void stopRecording() {
mRecorder.stop();
mRecorder.release();
mRecorder = null;
}
//定義錄音按鈕
class RecordButton extends Button {
boolean mStartRecording = true;
OnClickListener clicker = new OnClickListener() {
public void onClick(View v) {
onRecord(mStartRecording);
if (mStartRecording) {
setText("Stop recording");
} else {
setText("Start recording");
}
mStartRecording = !mStartRecording;
}
};
public RecordButton(Context ctx) {
super(ctx);
setText("Start recording");
setOnClickListener(clicker);
}
}
//定義播放按鈕
class PlayButton extends Button {
boolean mStartPlaying = true;
OnClickListener clicker = new OnClickListener() {
public void onClick(View v) {
onPlay(mStartPlaying);
if (mStartPlaying) {
setText("Stop playing");
} else {
setText("Start playing");
}
mStartPlaying = !mStartPlaying;
}
};
public PlayButton(Context ctx) {
super(ctx);
setText("Start playing");
setOnClickListener(clicker);
}
}
//構造方法
public AudioRecordTest() {
mFileName = Environment.getExternalStorageDirectory().getAbsolutePath();
mFileName += "/audiorecordtest.3gp";
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
//構造界面
LinearLayout ll = new LinearLayout(this);
mRecordButton = new RecordButton(this);
ll.addView(mRecordButton,
new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
0));
mPlayButton = new PlayButton(this);
ll.addView(mPlayButton,
new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
0));
setContentView(ll);
}
@Override
public void onPause() {
super.onPause();
//Activity暫停時釋放錄音和播放對象
if (mRecorder != null) {
mRecorder.release();
mRecorder = null;
} www.2cto.com
if (mPlayer != null) {
mPlayer.release();
mPlayer = null;
}
}
}

接下來我們進行更深入的瞭解,關於Android平臺SoundPool 和 MediaPlayer

下面轉自轉自:http://wjlgryx.iteye.com/blog/1114928

Android平臺中關於音頻播放有以下兩種方式:
1. SoundPool —— 適合短促且對反應速度比較高的情況(遊戲音效或按鍵聲等)
2. MediaPlayer —— 適合比較長且對時間要求不高的情況

-------------------------------------------------------------------------------------------
SoundPool

1. 創建一個SoundPool
public SoundPool(int maxStream, int streamType, int srcQuality)
maxStream —— 同時播放的流的最大數量
streamType —— 流的類型,一般爲STREAM_MUSIC(具體在AudioManager類中列出)
srcQuality —— 採樣率轉化質量,當前無效果,使用0作爲默認值

eg.
SoundPool soundPool = new SoundPool(3, AudioManager.STREAM_MUSIC, 0);
創建了一個最多支持3個流同時播放的,類型標記爲音樂的SoundPool。

2. 加載音頻資源
可以通過四種途徑來記載一個音頻資源:
int load(AssetFileDescriptor afd, int priority)
通過一個AssetFileDescriptor對象
int load(Context context, int resId, int priority)
通過一個資源ID
int load(String path, int priority)
通過指定的路徑加載
int load(FileDescriptor fd, long offset, long length, int priority)
通過FileDescriptor加載

*API中指出,其中的priority參數目前沒有效果,建議設置爲1。

一個SoundPool能同時管理多個音頻,所以可以通過多次調用load函數來記載,如果記載成功將返回一個非0的soundID ,用於播放時指定特定的音頻。

eg. 
int soundID1 = soundPool.load(this, R.raw.sound1, 1);
if(soundID1 ==0){
// 記載失敗
}else{
// 加載成功
}
int soundID2 = soundPool.load(this, R.raw.sound2, 1);
...

這裏加載了兩個流,並分別記錄了返回的soundID 。

需要注意的是,
流的加載過程是一個將音頻解壓爲原始16位PCM數據的過程,由一個後臺線程來進行處理異步,所以初始化後不能立即播放,需要等待一點時間。

3. 播放控制
有以下幾個函數可用於控制播放:
final int play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate)
播放指定音頻的音效,並返回一個streamID 。
priority —— 流的優先級,值越大優先級高,影響當同時播放數量超出了最大支持數時SoundPool對該流的處理;
loop —— 循環播放的次數,0爲值播放一次,-1爲無限循環,其他值爲播放loop+1次(例如,3爲一共播放4次).
rate —— 播放的速率,範圍0.5-2.0(0.5爲一半速率,1.0爲正常速率,2.0爲兩倍速率)
final void pause(int streamID)
暫停指定播放流的音效(streamID 應通過play()返回)。
final void resume(int streamID)
繼續播放指定播放流的音效(streamID 應通過play()返回)。
final void stop(int streamID)
終止指定播放流的音效(streamID 應通過play()返回)。

這裏需要注意的是,
1.play()函數傳遞的是一個load()返回的soundID——指向一個被記載的音頻資源 ,如果播放成功則返回一個非0的streamID——指向一個成功播放的流 ;同一個soundID 可以通過多次調用play()而獲得多個不同的streamID (只要不超出同時播放的最大數量);
2.pause()、resume()和stop()是針對播放流操作的,傳遞的是play()返回的streamID ;
3.play()中的priority參數,只在同時播放的流的數量超過了預先設定的最大數量是起作用,管理器將自動終止優先級低的播放流。如果存在多個同樣優先級的流,再進一步根據其創建事件來處理,新創建的流的年齡是最小的,將被終止;
4.無論如何,程序退出時,手動終止播放並釋放資源是必要的。

eg.
//這裏對soundID1的音效進行播放——優先級爲0(最低),無限循環,正常速率。
int streamID = soundPool.play(soundID1 , 1.0, 1.0, 0, -1, 1.0);
if(streamID ==0){
// 播放失敗
}else{
// 播放成功
}
...
// 暫停soundID1的播放
soundPool.pause(streamID );
... 
// 恢復soundID1的播放
soundPool.resume(streamID );
...
// 終止播放,記住循環爲-1時必須手動停止
soundPool.stop(streamID );


*API中指出,即使使用無效的soundID /streamID (操作失敗或指向無效的資源)來調用相關函數也不會導致錯誤,這樣能減輕邏輯的處理。

4. 更多屬性設置
其實就是paly()中的一些參數的獨立設置:
final void setLoop(int streamID, int loop)
設置指定播放流的循環.
final void setVolume(int streamID, float leftVolume, float rightVolume)
設置指定播放流的音量.
final void setPriority(int streamID, int priority)
設置指定播放流的優先級,上面已說明priority的作用.
final void setRate(int streamID, float rate)
設置指定播放流的速率,0.5-2.0.

5. 釋放資源
可操作的函數有:
final boolean unload(int soundID)
卸載一個指定的音頻資源.
final void release()
釋放SoundPool中的所有音頻資源.

-彙總-
一個SoundPool可以:
1.管理多個音頻資源,通過load()函數,成功則返回非0的soundID;
2.同時播放多個音頻,通過play()函數,成功則返回非0的streamID;
3.pause()、resume()和stop()等操作是針對streamID(播放流)的;
4.當設置爲無限循環時,需要手動調用stop()來終止播放;
5.播放流的優先級(play()中的priority參數),只在同時播放數超過設定的最大數時起作用;
6.程序中不用考慮(play觸發的)播放流的生命週期,無效的soundID/streamID不會導致程序錯誤。

-------------------------------------------------------------------------------------------

MediaPlayer

你可以通過new或便捷的靜態create函數組來創建一個MediaPlayer對象。

兩種方式的比較:
new MediaPlayer()
1.成功調用後,MediaPlayer將處於Idle狀態;
2.setDataSource提供了對String(path)、Uri和FileDescriptor格式的資源路徑的支持;
3.後續需要手動調用prepare()才能進行播放。

MediaPlayer.create(...)
1.成功調用後,MediaPlayer將處於Prepared狀態;
2.create提供了對int(resID)和Uri格式的資源路徑的支持;
3.無需(也不能)再次調用prepare()就能直接播放。

MediaPlayer的狀態圖:




橢圓 代表一個MediaPlayer可能處於的狀態。
圓弧 代表(驅動對象狀態轉變的)播放控制的操作。
>>箭頭有兩種形態:
單箭 頭代表同步函數的調用;
雙箭 頭代表異步的函數調用。

函數在不同狀態下的有效性:

Method NameValid SatesInvalid StatesComments
attachAuxEffect{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted}{Idle, Error}This method must be called after setDataSource. Calling it does not change the object state.
getAudioSessionIdany{}This method can be called in any state and calling it does not change the object state.
getCurrentPosition{Idle, Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted}{Error}Successful invoke of this method in a valid state does not change the state. Calling this method in an invalid state transfers the object to theErrorstate.
getDuration{Prepared, Started, Paused, Stopped, PlaybackCompleted}{Idle, Initialized, Error}Successful invoke of this method in a valid state does not change the state. Calling this method in an invalid state transfers the object to theErrorstate.
getVideoHeight{Idle, Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted}{Error}Successful invoke of this method in a valid state does not change the state. Calling this method in an invalid state transfers the object to theErrorstate.
getVideoWidth{Idle, Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted}{Error}Successful invoke of this method in a valid state does not change the state. Calling this method in an invalid state transfers the object to theErrorstate.
isPlaying{Idle, Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted}{Error}Successful invoke of this method in a valid state does not change the state. Calling this method in an invalid state transfers the object to theErrorstate.
pause{Started, Paused}{Idle, Initialized, Prepared, Stopped, PlaybackCompleted, Error}Successful invoke of this method in a valid state transfers the object to thePausedstate. Calling this method in an invalid state transfers the object to theErrorstate.
prepare{Initialized, Stopped}{Idle, Prepared, Started, Paused, PlaybackCompleted, Error}Successful invoke of this method in a valid state transfers the object to thePreparedstate. Calling this method in an invalid state throws an IllegalStateException.
prepareAsync{Initialized, Stopped}{Idle, Prepared, Started, Paused, PlaybackCompleted, Error}Successful invoke of this method in a valid state transfers the object to thePreparingstate. Calling this method in an invalid state throws an IllegalStateException.
releaseany{}Afterrelease(), the object is no longer available.
reset{Idle, Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted, Error}{}Afterreset(), the object is like being just created.
seekTo{Prepared, Started, Paused, PlaybackCompleted}{Idle, Initialized, Stopped, Error}Successful invoke of this method in a valid state does not change the state. Calling this method in an invalid state transfers the object to theErrorstate.
setAudioSessionId{Idle}{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted, Error}This method must be called in idle state as the audio session ID must be known before calling setDataSource. Calling it does not change the object state.
setAudioStreamType{Idle, Initialized, Stopped, Prepared, Started, Paused, PlaybackCompleted}{Error}Successful invoke of this method does not change the state. In order for the target audio stream type to become effective, this method must be called before prepare() or prepareAsync().
setAuxEffectSendLevelany{}Calling this method does not change the object state.
setDataSource{Idle}{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted, Error}Successful invoke of this method in a valid state transfers the object to theInitializedstate. Calling this method in an invalid state throws an IllegalStateException.
setDisplayany{}This method can be called in any state and calling it does not change the object state.
setLooping{Idle, Initialized, Stopped, Prepared, Started, Paused, PlaybackCompleted}{Error}Successful invoke of this method in a valid state does not change the state. Calling this method in an invalid state transfers the object to theErrorstate.
isLoopingany{}This method can be called in any state and calling it does not change the object state.
setOnBufferingUpdateListenerany{}This method can be called in any state and calling it does not change the object state.
setOnCompletionListenerany{}This method can be called in any state and calling it does not change the object state.
setOnErrorListenerany{}This method can be called in any state and calling it does not change the object state.
setOnPreparedListenerany{}This method can be called in any state and calling it does not change the object state.
setOnSeekCompleteListenerany{}This method can be called in any state and calling it does not change the object state.
setScreenOnWhilePlayingany{}This method can be called in any state and calling it does not change the object state.
setVolume{Idle, Initialized, Stopped, Prepared, Started, Paused, PlaybackCompleted}{Error}Successful invoke of this method does not change the state.
setWakeModeany{}This method can be called in any state and calling it does not change the object state.
start{Prepared, Started, Paused, PlaybackCompleted}{Idle, Initialized, Stopped, Error}Successful invoke of this method in a valid state transfers the object to theStartedstate. Calling this method in an invalid state transfers the object to theErrorstate.
stop{Prepared, Started, Stopped, Paused, PlaybackCompleted}{Idle, Initialized, Error}Successful invoke of this method in a valid state transfers the object to theStoppedstate. Calling this method in an invalid state transfers the object to theErrorstate.


要點:
1.如果由於錯誤的操作(參照上圖)導致MediaPlayer處於Error狀態,可通過reset()函數來使其恢復到Idle狀態,再重新執行setDataSource等初始化操作(ps:如果是通過create函數綁定資源ID創建的就鬱悶了...);
2.API中指出雖然reset後的MediaPlayer就像相當於新new的一樣,但存在微妙的差異的:
在這兩種情況下播放器處於Idle狀態,此時調用getCurrentPosition(), getDuration(),getVideoHeight(),getVideoWidth(), setAudioStreamType(int),setLooping(boolean), setVolume(float, float), pause(), start(), stop(),seekTo(int), prepare() 或 prepareAsync() 等函數都屬與編程錯誤。當在MediaPlayer剛創建後調用這些函數,用戶指定的OnErrorListener.onError() 回調函數不會被internal player engine(內部播放引擎)調用,並且播放器的狀態依然未變;但如果是在調用reset() 函數之後,用戶指定的OnErrorListener.onError() 回調函數將會被internal player engine(內部播放引擎)調用,並且播放器的狀態將轉變爲Error(錯誤)狀態。
3.使用完畢後應該立即調用release()函數來釋放資源,如果操作成功,MediaPlayer對象將處於End狀態,此時無法再進行任何操作,除非重新創建MediaPlayer對象。

更多的細節通過一個用new方式來創建的示例說明:

 

// 通過new創建後的player處於Idle狀態
MediaPlayer mp = new MediaPlayer();
if(mp==null){
// new創建有可能會返回null值,檢測是好的習慣
return;
}
// 設置資源路徑,成功執行的話player將處於Initialized狀態
try {
mp.setDataSource("/sdcard/test.mp3"); // 直接傳URL也是可以的,將自動處理緩衝
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
// 如果在非Idle狀態下調用setDataSource就會導致該異常
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
// 設置必要的監聽器
mp.setOnPreparedListener(new OnPreparedListener(){
@Override
public void onPrepared(MediaPlayer mp) {
// 這時能確保player處於Prepared狀態,觸發start是最合適的
mp.start();
}
});
mp.setOnCompletionListener(new OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
// 正常播放結束,可以觸發播放下一首
}
});
mp.setOnErrorListener(new OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, intextra) {
// 操作錯誤或其他原因導致的錯誤會在這裏被通知
return true;
}
});
// 連接並加載資源
try {
mp.prepare();
//                mp.prepareAsync() 這也是可以的,這是異步處理,上面的是同步處理,實際加載完畢以OnPreparedListener.onPrepared()爲準。
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
//          mp.start(); // 建議在OnPreparedListener.onPrepared()回調中觸發該函數,特別是使用異步加載時
/**
* ... 你的其他操作 ...
*/
// 終止播放並釋放資源
try{
mp.stop(); // 這是必要的,如果你設置了循環播放,否則程序退出了音樂仍在後臺繼續播...
mp.release();
}catch(IllegalStateException e){
e.printStackTrace();
}

播放控制上基本與SoundPool相同有:
start()、pause()、stop()、seekTo()、setLooping()...

需要注意的是, 循環播放設置上與SoundPool不同,不能指定確定的循環次數,而是一個布爾值,指定是否循環播放...

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章