最近在做一些跟傳感器相關的東西,有注意到以前騰訊微網誌以前出過一個吹一吹互動,雖然和傳感器無關,但是感覺也比較有興趣,就寫了一個拙劣的demo,因為接觸媒體檔案操作比較少,順帶寫了一個錄音和播放的例子,總結了一下一些小坑的地方,一并在此分享給大家。
主要思路和坑的地方
主要的思路是通過mediarecorder提供的getmaxamplitude()函數,擷取一段時間内輸入的音頻最大幅值來進行檢測,是以除了吹的動作,其他聲音也會被錄進來。
“吹”這個動作如果想和其他動作進行區分,其實本質在于吹的時候靠近聽筒,即便吹這個動作本身音量不大,但是麥克風看來它的分貝是很大的,是以我們可以通過檢測分貝來判斷這個動作是否是吹(如果其他聲音更大……那……算了不管了)。
這裡附上前人的一些參考資料:
<a href="http://www.jb51.net/article/64806.htm">http://www.jb51.net/article/6...</a>
一看到這個網站後面是htm,仿佛就明白了這個網站的架構…
這個東西坑的地方在于mediaplayer和mediarecorder這兩個東西stop和start的順序經常是嚴格被限制的,在退出時如果沒有成功釋放資源,有時候activity再啟動時,由于上次退出沒有stop,再重新start也會抛出異常。
權限添加
<uses-permission android:name="android.permission.read_external_storage"/>
<uses-permission android:name="android.permission.write_external_storage"/>
<uses-permission android:name="android.permission.mount_format_filesystems"/>
<uses-permission android:name="android.permission.record_audio" />
主要界面
大概想了一個簡單的界面,好吧其實是左下角的音響閃動,忘記修改文字描述了
布局檔案:
<?xml version="1.0" encoding="utf-8"?>
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_sound"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingbottom="@dimen/activity_vertical_margin"
android:paddingleft="@dimen/activity_horizontal_margin"
android:paddingright="@dimen/activity_horizontal_margin"
android:paddingtop="@dimen/activity_vertical_margin"
tools:context=".soundactivity">
<textview
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/introduction_of_sound"/>
<linearlayout
android:layout_width="match_parent"
android:orientation="vertical"
android:gravity="center"
android:layout_centerinparent="true">
<button
android:layout_width="70dp"
android:layout_height="70dp"
android:id="@+id/btn_start_record"
android:background="@drawable/ic_mic_none_black_24dp"
/>
<textview
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tv_record_tips"/>
android:id="@+id/btn_start_play"
android:background="@drawable/ic_play_circle_filled_black_24dp"
</linearlayout>
<imageview
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@drawable/ic_volume_mute_gray_24dp"
android:layout_alignparentbottom="true"
android:id="@+id/imv_sound"/>
</relativelayout>
主要代碼
import android.app.progressdialog;
import android.media.audiomanager;
import android.media.mediaplayer;
import android.media.mediarecorder;
import android.os.environment;
import android.os.handler;
import android.os.message;
import android.support.v7.app.appcompatactivity;
import android.os.bundle;
import android.view.view;
import android.widget.button;
import android.widget.imageview;
import android.widget.textview;
import android.widget.toast;
import java.io.ioexception;
import java.util.timer;
import java.util.timertask;
public class soundactivity extends appcompatactivity {
static final int recording = 1;
static final int playing = 2;
static final int pausing = 3;
static string tag = "soundactivity";
static int status = recording;
//用于音頻錄制
mediarecorder mediarecorder;
//用于音頻播放
mediaplayer mediaplayer;
//錄制按鈕
button btnrecord;
//播放按鈕
button btnplay;
//提示資訊
textview tvtips;
//吹一吹小音箱
imageview imvsound;
//播放進度條
static string path_name = environment.getexternalstoragedirectory().getabsolutepath() + "/sensordemorecorder.mp3";
@override
protected void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
setcontentview(r.layout.activity_sound);
init();
}
public void init(){
//控件初始化
btnplay = (button)findviewbyid(r.id.btn_start_play);
btnrecord = (button)findviewbyid(r.id.btn_start_record);
tvtips = (textview)findviewbyid(r.id.tv_record_tips);
imvsound = (imageview)findviewbyid(r.id.imv_sound);
mediaplayerpreparingdialog = new progressdialog(this);
btnplay.setonclicklistener(new view.onclicklistener() {
@override
public void onclick(view v) {
if (status == recording ){
//如果是在錄制,點選則停止錄制并且播放
stoprecording();
startplay();
}else if (status == pausing){
} else {
//如果是在播放,點選則暫停
pauseplay();
}
}
});
btnrecord.setonclicklistener(new view.onclicklistener() {
if (status == playing || status == pausing){
//如果是在播放或者暫停,點選開始錄制
startrecording();
}else {
//如果在錄制,點選開始播放
mediarecorder = new mediarecorder();
//設定到達最大錄制長度時重頭開始錄制
mediarecorder.setoninfolistener(new mediarecorder.oninfolistener() {
public void oninfo(mediarecorder mr, int what, int extra) {
switch (what){
case mediarecorder.media_recorder_error_unknown:
toast.maketext(soundactivity.this, "未知錯誤", toast.length_short).show();
finish();
break;
case mediarecorder.media_recorder_info_max_duration_reached:
toast.maketext(soundactivity.this, "已達到最大錄制長度,開始重新錄制", toast.length_short).show( );
startrecording();
case mediarecorder.media_recorder_info_max_filesize_reached:
toast.maketext(soundactivity.this, "空間不足,無法錄制", toast.length_short).show();
mediarecorder.stop();
//預設開始錄制
startrecording();
btnrecord.setbackgroundresource(r.drawable.ic_mic_black_24dp);
//預設開始吹一吹檢測以及播放進度檢測
startchecksound();
protected void ondestroy() {
super.ondestroy();
if (playing == status){
mediaplayer.stop();
mediaplayer.release();
}
if (recording == status){
mediarecorder.stop();
mediarecorder.release();
//為了防止activity結束後有時候這個timer還在定時執行任務(很坑)
timer.cancel();
public void startrecording(){
stopplay();
status = recording;
//設定為錄制狀态
tvtips.settext("正在錄制,點選播放按鈕或者麥克風停止錄制");
//開始錄制的設定
mediarecorder.reset(); // you can reuse the object by going back to setaudiosource() step
mediarecorder.setaudiosource(mediarecorder.audiosource.mic);
mediarecorder.setoutputformat(mediarecorder.outputformat.three_gpp);
mediarecorder.setaudioencoder(mediarecorder.audioencoder.amr_nb);
try{
mediarecorder.setoutputfile(path_name);
mediarecorder.prepare();
mediarecorder.start(); // recording is now started
}catch (ioexception e){
toast.maketext(this, "準備錄制檔案失敗", toast.length_short).show();
e.printstacktrace();
finish();
public void stoprecording(){
//說明正在錄制,設定停止資訊
tvtips.settext("已停止錄制,開始播放");
btnrecord.setbackgroundresource(r.drawable.ic_mic_none_black_24dp);
handler handler= new handler(new handler.callback() {
@override
public boolean handlemessage(message msg) {
if ((double)msg.obj > 70){
imvsound.setimageresource(r.drawable.ic_volume_mute_valid_24dp);
}else {
imvsound.setimageresource(r.drawable.ic_volume_mute_gray_24dp);
return false;
});
timer timer = new timer();
public void startchecksound(){
//定時檢測峰值,以及檢測播放進度
timer.schedule(new timertask() {
public void run() {
if (mediarecorder != null) {
double amplitude = (double)mediarecorder.getmaxamplitude();
double db = 0;
//計算分貝
if (amplitude > 1)
db = 20 * math.log10(amplitude);
message msg = new message();
msg.obj = db;
handler.sendmessage(msg);
//如果需要檢測播放進度可以使用
//mediaplayer.getcurrentposition()/mediaplayer.getduration();
},0,100);
progressdialog mediaplayerpreparingdialog;
public void startplay(){
//如果是從錄制狀态開始播放,則重新讀取新的錄制檔案
status = playing;
//設定音頻播放器
mediaplayer = new mediaplayer();
mediaplayer.setaudiostreamtype(audiomanager.stream_music);
mediaplayer.setoncompletionlistener(new mediaplayer.oncompletionlistener() {
@override
public void oncompletion(mediaplayer mp) {
//播放完設定
tvtips.settext("播放完畢,可點選麥克風重新錄制");
btnplay.setbackgroundresource(r.drawable.ic_play_circle_filled_black_24dp);
});
try {
mediaplayer.setdatasource(path_name);
mediaplayer.prepareasync();
} catch (ioexception e) {
e.printstacktrace();
toast.maketext(this, "錄音檔案已丢失", toast.length_short).show();
finish();
mediaplayerpreparingdialog.settitle("正在準備播放錄音");
mediaplayerpreparingdialog.show();
mediaplayer.setonpreparedlistener(new mediaplayer.onpreparedlistener() {
public void onprepared(mediaplayer mp) {
mediaplayerpreparingdialog.dismiss();
mediaplayer.start();
}else if(pausing == status){
//從暫停狀态開始播放則直接播放
mediaplayer.start();
//開始播放,設定按鈕為暫停
btnplay.setbackgroundresource(r.drawable.ic_pause_circle_filled_black_24dp);
public void pauseplay(){
if (playing == status) {
//暫停播放,設定按鈕為開始播放
mediaplayer.pause();
btnplay.setbackgroundresource(r.drawable.ic_play_circle_filled_black_24dp);
status = pausing;
public void stopplay(){
if (mediaplayer != null) mediaplayer.stop();
}
media和illegalstateexception
這個就是之前提到的由于沒有按順序釋放資源或者stop掉這兩個破玩意兒,可能會導緻的各種錯誤,是以我很無奈地設定了一個status變量,并且在activity的ondestoy裡對兩個東西進行了stop,其實一般還會使用release釋放掉資源…大家随意吧…
qcmediaplayer mediaplayer not present
!!!我就知道,如果你看到這個地方,一定也對這個錯誤感到莫名其妙。我記得好像上古時期,也就是上次我寫這個的時候也被坑了。
論壇上有人說這個東西在4.4以下的系統就容易出現,但是我也隻能感覺不明覺厲,我一開始用的是mediaplayer.create(this,uri.parse(path_name))來建立mediaplayer,于是換成了
mediaplayer = new mediaplayer();
mediaplayer.setaudiostreamtype(audiomanager.stream_music);
mediaplayer.setdatasource(path_name);
好吧,然後問題就解決了,我也是無語了。我覺得這個地方是一個很久遠的坑了,查原因一時也沒查到。我隻能推測大概因為create函數建立時沒有指定audiostreamtype導緻使用了預設的
private int mstreamtype = audiomanager.use_default_stream_type;
在某些裝置上可能不支援,于是就出了問題= =好吧,我也不知道還能說啥,就醬…
vector asset添加的圖示顔色不變化
如上,我的播放按鈕啊,音響啊,之類的圖示都是通過vector asset添加的,這也是一個比較久遠的坑了,但是以前也沒有記下來,即在android l以下的版本中,vector asset添加的圖示,修改顔色時不能使用顔色的引用,而要直接寫顔色,例如:
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportwidth="24.0"
android:viewportheight="24.0">
<path
android:fillcolor="#3f51b5"
android:pathdata="***"/>
</vector>
使用
android:fillcolor="@color/colorprimary"
則導緻顔色并不會修改,依然是黑色
作者:兩岸風景
來源:51cto