天天看點

Android簡易“吹一吹實作”以及錄音和播放示例

最近在做一些跟傳感器相關的東西,有注意到以前騰訊微網誌以前出過一個吹一吹互動,雖然和傳感器無關,但是感覺也比較有興趣,就寫了一個拙劣的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也會抛出異常。

權限添加

&lt;uses-permission android:name="android.permission.read_external_storage"/&gt; 

&lt;uses-permission android:name="android.permission.write_external_storage"/&gt; 

&lt;uses-permission android:name="android.permission.mount_format_filesystems"/&gt; 

&lt;uses-permission android:name="android.permission.record_audio" /&gt; 

主要界面

大概想了一個簡單的界面,好吧其實是左下角的音響閃動,忘記修改文字描述了

Android簡易“吹一吹實作”以及錄音和播放示例

布局檔案:

&lt;?xml version="1.0" encoding="utf-8"?&gt; 

&lt;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"&gt; 

&lt;textview 

    android:layout_width="wrap_content" 

    android:layout_height="wrap_content" 

    android:text="@string/introduction_of_sound"/&gt; 

&lt;linearlayout 

    android:layout_width="match_parent" 

    android:orientation="vertical" 

    android:gravity="center" 

    android:layout_centerinparent="true"&gt; 

    &lt;button 

        android:layout_width="70dp" 

        android:layout_height="70dp" 

        android:id="@+id/btn_start_record" 

        android:background="@drawable/ic_mic_none_black_24dp" 

        /&gt; 

    &lt;textview 

        android:layout_width="wrap_content" 

        android:layout_height="wrap_content" 

        android:id="@+id/tv_record_tips"/&gt; 

        android:id="@+id/btn_start_play" 

        android:background="@drawable/ic_play_circle_filled_black_24dp" 

&lt;/linearlayout&gt; 

&lt;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"/&gt; 

&lt;/relativelayout&gt;  

主要代碼

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 &gt; 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 &gt; 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添加的圖示,修改顔色時不能使用顔色的引用,而要直接寫顔色,例如:

&lt;vector xmlns:android="http://schemas.android.com/apk/res/android" 

    android:width="24dp" 

    android:height="24dp" 

    android:viewportwidth="24.0" 

    android:viewportheight="24.0"&gt; 

&lt;path 

    android:fillcolor="#3f51b5" 

    android:pathdata="***"/&gt; 

&lt;/vector&gt;  

使用

    android:fillcolor="@color/colorprimary" 

則導緻顔色并不會修改,依然是黑色

作者:兩岸風景

來源:51cto

繼續閱讀