用ClipDrawable實現(xiàn)音頻錄制麥克風(fēng)講話效果

麥克風(fēng)

由于最近項目開發(fā)需要用到自定義SeekBar,于是又對android下的各種類型drawable進行了一個全面系統(tǒng)的認識,只能感慨drawable的功能還是很強大的。通過自定義SeekBar有感而發(fā),嘗試用ClipDrawable實現(xiàn)音頻錄制過程的一個麥克風(fēng)錄制效果:

在實現(xiàn)開發(fā)之前先讓我們一起認識兩種類型的Drawable:

LayerDrawable

LayerDrawable可以包含一個drawable數(shù)組,而且系統(tǒng)會根據(jù)drawable數(shù)組的前后順序來繪制所有的drawable,索引最大的drawable也就相應(yīng)的會被繪制在最上面。使用過PhotoShop的朋友應(yīng)該會比較容易理解,LayerDrawable和PhotoShop中圖層的概念很相似,這里drawable數(shù)組中的每一個drawable就相當(dāng)于PhotoShop中的一個圖層,上一個圖層會遮住之后所有圖層與之重疊的部分。

定義LayerDrawable對象的XML文件的根元素為<layer-list… />,該元素可以包含對個<item… />元素也就是一個drawable對象。

ClipDrawable

ClipDrawable,顧名思義這就是一個可以進行裁切的drawable,在XML文件中定義ClipDrawable對象使用的根元素是<clip… />元素,該元素包含以下幾個重要的屬性:

  • android:drawable:指定將要被截取的Drawable對象。
  • android:clipOrientation:指定Drawable對象的截取方向可以是水平和豎直方向。
  • android:gravity:表示Drawable對象的對齊方式,例如:left 可以理解為左邊部分為保留部分,右邊部分為剪切部分,則我們可以看到的就是截取的左邊部分。

注意,使用ClipDrawable對象時可以調(diào)用setLevel(int level)方法來控制截取區(qū)域的大小,而level的取值區(qū)間在0~10000之間,則level為0時,表示圖片截取部分為空,當(dāng)了level為10000時,截取整張圖片。

了解完畢,下面我們就要用這兩種Drawable結(jié)合使用開發(fā)我們今天的麥克風(fēng)說話效果:

首先,準(zhǔn)備兩張位圖

top drawable
bottom drawable

然后,在XML中新建一個擁有兩個Drawable的LayerDrawable文件layer-microphone.xml,在頂層顯示的是可以裁切的ClipDrawable,設(shè)置剪切方向為豎直方向,設(shè)置對其方式為bottom,底部的則是不通的Drawable作為背景。

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
    <item android:id="@android:id/background" android:drawable="@drawable/icon_microphone_normal" />
    <item android:id="@android:id/progress" >
        <clip android:drawable="@drawable/icon_microphone_recoding" android:gravity="bottom" android:clipOrientation="vertical" />
    </item>
</layer-list>

然后,再自定義一個PopupWindow用于音頻錄制過程顯示麥克風(fēng)動畫效果,關(guān)于自定義popupWindow有疑問的朋友可以參考我的上一篇文章"2016-05-10 淺談PopupWindow在Android開發(fā)中的使用"

窗口布局如下layout_microphone.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:gravity="center"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <LinearLayout
        android:background="@drawable/shape_window_background"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:orientation="vertical"
        android:gravity="center"
        android:padding="16dp" >

        <ImageView
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:id="@android:id/progress"
            android:src="@drawable/layer-microphone" />

        <TextView
            android:layout_height="wrap_content"
            android:layout_marginTop="6dp"
            android:id="@android:id/text1"
            android:layout_width="114dp"
            android:textColor="#FFFFFF"
            android:gravity="center"
            android:textSize="16sp"
            android:text="00:00" />
    </LinearLayout>
</LinearLayout>

從代碼中可以看出,我把ImageVie的資源設(shè)為layer-microphone.xml,我們獲取ImageView的Drawable對象并設(shè)置Level值就能實現(xiàn)想要的效果:

imageView.getDrawable().setLevel(5400);
icon_microphone_recoding11.png

從這里我們已經(jīng)可以看到裁切效果。最后一步,我們只要能夠?qū)崟r獲取音頻錄制過程中的分貝值的變化,再將分貝值變化轉(zhuǎn)換到相應(yīng)的Level值,就能實現(xiàn)音頻錄制說話效果啦,于是百度,在網(wǎng)上看到一篇文章"Android中實時獲取音量分貝值詳解" ,首先,感謝作者的分享,于是我也就照著方法寫了一個AudioRecoderUtil.java類,稍加改進,添加了一個監(jiān)聽事件,代碼如下:

package com.mariostudio.audiorecoder;

import java.io.File;
import java.io.IOException;

import android.media.MediaRecorder;
import android.os.Handler;
import android.util.Log;

/**
 * Created by MarioStudio on 2016/5/12.
 */

public class AudioRecoderUtils {

    private String filePath;
    private MediaRecorder mMediaRecorder;
    private final String TAG = "MediaRecord";
    public static final int MAX_LENGTH = 1000 * 60 * 10;// 最大錄音時長1000*60*10;

    private OnAudioStatusUpdateListener audioStatusUpdateListener;

    public AudioRecoderUtils(){
        this.filePath = "/dev/null";
    }

    public AudioRecoderUtils(File file) {
        this.filePath = file.getAbsolutePath();
    }

    private long startTime;
    private long endTime;

    /**
     * 開始錄音 使用amr格式
     *      錄音文件
     * @return
     */
    public void startRecord() {
        // 開始錄音
        /* ①Initial:實例化MediaRecorder對象 */
        if (mMediaRecorder == null)
            mMediaRecorder = new MediaRecorder();
        try {
            /* ②setAudioSource/setVedioSource */
            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 設(shè)置麥克風(fēng)
            /* ②設(shè)置音頻文件的編碼:AAC/AMR_NB/AMR_MB/Default 聲音的(波形)的采樣 */
            mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
            /*
             * ②設(shè)置輸出文件的格式:THREE_GPP/MPEG-4/RAW_AMR/Default THREE_GPP(3gp格式
             * ,H263視頻/ARM音頻編碼)、MPEG-4、RAW_AMR(只支持音頻且音頻編碼要求為AMR_NB)
             */
            mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
            /* ③準(zhǔn)備 */
            mMediaRecorder.setOutputFile(filePath);
            mMediaRecorder.setMaxDuration(MAX_LENGTH);
            mMediaRecorder.prepare();
            /* ④開始 */
            mMediaRecorder.start();
            // AudioRecord audioRecord.
            /* 獲取開始時間* */
            startTime = System.currentTimeMillis();
            updateMicStatus();
            Log.i("ACTION_START", "startTime" + startTime);
        } catch (IllegalStateException e) {
            Log.i(TAG, "call startAmr(File mRecAudioFile) failed!" + e.getMessage());
        } catch (IOException e) {
            Log.i(TAG, "call startAmr(File mRecAudioFile) failed!" + e.getMessage());
        }
    }

    /**
     * 停止錄音
     */
    public long stopRecord() {
        if (mMediaRecorder == null)
            return 0L;
        endTime = System.currentTimeMillis();
        Log.i("ACTION_END", "endTime" + endTime);
        mMediaRecorder.stop();
        mMediaRecorder.reset();
        mMediaRecorder.release();
        mMediaRecorder = null;
        Log.i("ACTION_LENGTH", "Time" + (endTime - startTime));
        return endTime - startTime;
    }

    private final Handler mHandler = new Handler();
    private Runnable mUpdateMicStatusTimer = new Runnable() {
        public void run() {
            updateMicStatus();
        }
    };

    /**
     * 更新話筒狀態(tài)
     */
    private int BASE = 1;
    private int SPACE = 100;// 間隔取樣時間

    public void setOnAudioStatusUpdateListener(OnAudioStatusUpdateListener audioStatusUpdateListener) {
        this.audioStatusUpdateListener = audioStatusUpdateListener;
    }

    private void updateMicStatus() {
        if (mMediaRecorder != null) {
            double ratio = (double)mMediaRecorder.getMaxAmplitude() / BASE;
            double db = 0;// 分貝
            if (ratio > 1) {
                db = 20 * Math.log10(ratio);
                if(null != audioStatusUpdateListener) {
                    audioStatusUpdateListener.onUpdate(db);
                }
            }
            mHandler.postDelayed(mUpdateMicStatusTimer, SPACE);
        }
    }

    public interface OnAudioStatusUpdateListener {
        public void onUpdate(double db);
    }
}

進行到這一步,已經(jīng)基本完成了這個效果,最后只需要在自定義PopupWindow的時候提供方法setLevel(int level)就可以輕松實現(xiàn)PopupWindow實時刷新分貝值啦??!

public void setLevel(int level) {
    imageView.getDrawable().setLevel(3000 + 6000 * level / 100);
}
動態(tài)效果圖

至于為什么設(shè)置level的時候要3000 + 6000 * level / 100以及計時效果,就都留給聰明的你去探索咯!!

Github項目Demo地址

作者申明:如果文中有表述不當(dāng)或闡述錯誤的地方,還望正在看文章的您可以幫忙指出,有疑惑也可以在評論區(qū)提問或者私信,期待您的意見和建議,歡迎關(guān)注交流。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容