今天來寫個關於聲級器(噪音計)的APP吧~

最近因為手頭上沒什麼新的開發案,所以上班閒著無聊就來研究怎麼寫噪音計(/・0・)

對,然後就真的給我玩出了點名堂了ɷ◡ɷ

 

好啦不鬧,總之還真的讓我做出來了沒錯

我後面也打算要把這個包裝成一個APP做上架...不過應該也是一個月後的事了(;´д`)ゞ

因為我可能還會加一些外部功能,讓APP完整度更高這樣

 

OK,那就進入正題

看範例

 

 

還有Github

->https://github.com/thumbb13555/NoiseRecoder

 

Go~

 


 

1. 畫介面&取得權限

 

1-1 畫介面

介面其實也沒有什麼內容

直接給快些

 

activity_main.xml

截圖 2021-02-27 下午7.42.03

 

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView_Result"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="0 db"
        android:textAppearance="@style/TextAppearance.AppCompat.Large"
        android:textSize="30sp"
        app:layout_constraintBottom_toTopOf="@+id/button_Start"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button_Start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="開始偵測"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button_Stop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="結束偵測"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button_Start" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

就兩個按鈕跟一個TextView,真的沒什麼!ˋ3ˊ

 

1-2 取得麥克風權限

 

好ㄌ,接下來才是比較重要的部分

所謂權限,就是當你要取用某些手機上的某些功能時,必須要在程式中向使用者通知的一個機制

這某種程度上很像是一種法律證明,就像是“個資取用合約書”是一樣的道理

那麼麥克風就是其中之一,由於麥克風可以搜集一些聲音資訊,所以必須向使用者徵求權限

那麼就來吧!

 

首先,進入到AndroidManifest.xml的部分

輸入以下粉底白字的部分

 

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.noahliu.noiserecoder">
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

 

接著,我們還要動態申請權限

來到MainActivity.java

 

輸入以下內容

public class MainActivity extends AppCompatActivity {
    private boolean hasPermission = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //取德麥克風使用權限
        checkPermission();
        
    }
    /**確認是否有麥克風使用權限*/
    private void checkPermission(){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
                ActivityCompat.checkSelfPermission(this
                        , Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(this
                    ,new String[]{Manifest.permission.RECORD_AUDIO},100);
        }else hasPermission = true;
    }
    /**取得權限回傳*/
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == 100 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
            hasPermission = true;
        }
    }
}

 

接著執行,就可以看到詢問權限的畫面囉

 


 

2. 開始偵測

 

接著是開始偵測的部分

他的上頭還有一個按鈕點擊的部分,也會一併貼(・ωー)~☆

 

首先是開始偵測的部分

 

public class MainActivity extends AppCompatActivity {
    private boolean hasPermission = false, isRecoding;
    private MediaRecorder mediaRecorder;
    private TextView tvResult;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //取德麥克風使用權限
        checkPermission();
        Button btStart,btStop;
        btStart = findViewById(R.id.button_Start);
        btStop = findViewById(R.id.button_Stop);
        tvResult = findViewById(R.id.textView_Result);
        btStart.setOnClickListener(v->{startMeasure();});
        btStop.setOnClickListener(v->{});

    }
    /**確認是否有麥克風使用權限*/
    private void checkPermission(){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
                ActivityCompat.checkSelfPermission(this
                        , Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(this
                    ,new String[]{Manifest.permission.RECORD_AUDIO},100);
        }else hasPermission = true;
    }
    /**取得權限回傳*/
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == 100 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
            hasPermission = true;
        }
    }
    /**開啟檢測*/
    private void startMeasure(){
        if (!hasPermission || isRecoding)return;
        mediaRecorder = new MediaRecorder();
        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
        mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
        mediaRecorder.setOutputFile("/dev/null");
        try {
            mediaRecorder.prepare();
            mediaRecorder.start();
            isRecoding = true;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

重點在這一段

/**開啟檢測*/
private void startMeasure(){
    if (!hasPermission || isRecoding)return;
    mediaRecorder = new MediaRecorder();
    mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
    mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
    mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
    mediaRecorder.setOutputFile("/dev/null");
    try {
        mediaRecorder.prepare();
        mediaRecorder.start();
        isRecoding = true;
    } catch (IOException e) {
        e.printStackTrace();
    }
}

 

以上四個重要參數由上至下為

setAudioSource→設置聲音來源

setOutputFormat→設置輸出格式(.3gp)

setAudioEncoder→設置編碼方式

setOutputFile→設置輸出資料夾路徑

 

⚠️以上四個是一定要初始化的,缺一不可喔⚠️

 


 

3. 持續偵測

 

再來是持續偵測的部分

這個部分Android的套件並沒有一個監聽器可以用,所以持續Do something這件事要自己來

這部分我將利用Handler+task來完成

我之前有寫過相關文章,各位可以去看看

碼農日常-『Android studio』Android背景執行之Thread、Handler及AsyncTask的基礎用法

 

那就繼續往下貼

public class MainActivity extends AppCompatActivity {
    private boolean hasPermission = false, isRecoding;
    private MediaRecorder mediaRecorder;
    private TextView tvResult;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //取德麥克風使用權限
        checkPermission();
        Button btStart,btStop;
        btStart = findViewById(R.id.button_Start);
        btStop = findViewById(R.id.button_Stop);
        tvResult = findViewById(R.id.textView_Result);
        btStart.setOnClickListener(v->{startMeasure();});
        btStop.setOnClickListener(v->{stopMeasure();});

    }
    /**確認是否有麥克風使用權限*/
    private void checkPermission(){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
                ActivityCompat.checkSelfPermission(this
                        , Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(this
                    ,new String[]{Manifest.permission.RECORD_AUDIO},100);
        }else hasPermission = true;
    }
    /**取得權限回傳*/
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == 100 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
            hasPermission = true;
        }
    }
    /**開啟檢測*/
    private void startMeasure(){
        if (!hasPermission || isRecoding)return;
        mediaRecorder = new MediaRecorder();
        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
        mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
        mediaRecorder.setOutputFile("/dev/null");
        try {
            mediaRecorder.prepare();
            mediaRecorder.start();
            handlerMeasure.post(taskMeasure);
            isRecoding = true;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**透過HandlerTask 取得檢測結果*/
    @SuppressLint("HandlerLeak")
    private Handler handlerMeasure = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            switch (msg.what){
                case 1:
                    int amp = mediaRecorder.getMaxAmplitude();
                    //公式:Gdb = 20log10(V1/V0)
                    //Google已提供方法幫你取得麥克風的檢測電壓(V1)以及參考電壓(V0)
                    double db = 20*(Math.log10(Math.abs(amp)));
                    //if -Infinity
                    if (Math.round(db) == -9223372036854775808.0) tvResult.setText("0 db");
                    else tvResult.setText(Math.round(db)+" db");
                    break;
            }
            super.handleMessage(msg);

        }
    };
    private Runnable taskMeasure = new Runnable() {
        @Override
        public void run() {
            handlerMeasure.sendEmptyMessage(1);
            //每500毫秒抓取一次檢測結果
            handlerMeasure.postDelayed(this,500);
        }
    };
}

 

說一下重點

在開啟Recoder完成後,如果你呼叫一次mediaRecorder.getMaxAmplitude()的話

那麼他會回你一次瞬間的值

不過這個東西他是連接著手機本身的麥克風的

所以他在第一次呼叫的時候會回傳-Infinity

也因此我使用Handler讓他一直執行

我們在開啟的程式內呼叫發起Handler

handlerMeasure.post(taskMeasure);

再來就是task+Handler的配合了

/**透過HandlerTask 取得檢測結果*/
@SuppressLint("HandlerLeak")
private Handler handlerMeasure = new Handler(){
    @Override
    public void handleMessage(@NonNull Message msg) {
        switch (msg.what){
            case 1:
                int amp = mediaRecorder.getMaxAmplitude();
                //公式:Gdb = 20log10(V1/V0)
                //Google已提供方法幫你取得麥克風的檢測電壓(V1)以及參考電壓(V0)
                double db = 20*(Math.log10(Math.abs(amp)));
                //if -Infinity
                if (Math.round(db) == -9223372036854775808.0) tvResult.setText("0 db");
                else tvResult.setText(Math.round(db)+" db");
                break;
        }
        super.handleMessage(msg);

    }
};
private Runnable taskMeasure = new Runnable() {
    @Override
    public void run() {
        handlerMeasure.sendEmptyMessage(1);
        //每500毫秒抓取一次檢測結果
        handlerMeasure.postDelayed(this,500);
    }
};

 

最後他的方法會回傳一個震幅,這時再套用計算公式

db = 20log10(震幅)

即求出響度值

 


 

4. 結束偵測

 

最後,結束偵測(粉底白字)

public class MainActivity extends AppCompatActivity {
    private boolean hasPermission = false, isRecoding;
    private MediaRecorder mediaRecorder;
    private TextView tvResult;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //取德麥克風使用權限
        checkPermission();
        Button btStart,btStop;
        btStart = findViewById(R.id.button_Start);
        btStop = findViewById(R.id.button_Stop);
        tvResult = findViewById(R.id.textView_Result);
        btStart.setOnClickListener(v->{startMeasure();});
        btStop.setOnClickListener(v->{stopMeasure();});

    }
    /**確認是否有麥克風使用權限*/
    private void checkPermission(){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
                ActivityCompat.checkSelfPermission(this
                        , Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(this
                    ,new String[]{Manifest.permission.RECORD_AUDIO},100);
        }else hasPermission = true;
    }
    /**取得權限回傳*/
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == 100 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
            hasPermission = true;
        }
    }
    /**開啟檢測*/
    private void startMeasure(){
        if (!hasPermission || isRecoding)return;
        mediaRecorder = new MediaRecorder();
        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
        mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
        mediaRecorder.setOutputFile("/dev/null");
        try {
            mediaRecorder.prepare();
            mediaRecorder.start();
            handlerMeasure.post(taskMeasure);
            isRecoding = true;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**關閉檢測*/
    private void stopMeasure(){
        if (!hasPermission || !isRecoding)return;
        handlerMeasure.removeCallbacks(taskMeasure);
        try {
            mediaRecorder.release();
            mediaRecorder.stop();
        }catch (IllegalStateException e){
            e.printStackTrace();
        }
        isRecoding = false;
    }
    /**透過HandlerTask 取得檢測結果*/
    @SuppressLint("HandlerLeak")
    private Handler handlerMeasure = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            switch (msg.what){
                case 1:
                    int amp = mediaRecorder.getMaxAmplitude();
                    //公式:Gdb = 20log10(V1/V0)
                    //Google已提供方法幫你取得麥克風的檢測電壓(V1)以及參考電壓(V0)
                    double db = 20*(Math.log10(Math.abs(amp)));
                    //if -Infinity
                    if (Math.round(db) == -9223372036854775808.0) tvResult.setText("0 db");
                    else tvResult.setText(Math.round(db)+" db");
                    break;
            }
            super.handleMessage(msg);

        }
    };
    private Runnable taskMeasure = new Runnable() {
        @Override
        public void run() {
            handlerMeasure.sendEmptyMessage(1);
            //每500毫秒抓取一次檢測結果
            handlerMeasure.postDelayed(this,500);
        }
    };

    @Override
    protected void onStop() {
        super.onStop();
        stopMeasure();
    }
}

 


 

5. 完整程式碼

 

MainActivity.java

public class MainActivity extends AppCompatActivity {
    private boolean hasPermission = false, isRecoding;
    private MediaRecorder mediaRecorder;
    private TextView tvResult;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //取德麥克風使用權限
        checkPermission();
        Button btStart,btStop;
        btStart = findViewById(R.id.button_Start);
        btStop = findViewById(R.id.button_Stop);
        tvResult = findViewById(R.id.textView_Result);
        btStart.setOnClickListener(v->{startMeasure();});
        btStop.setOnClickListener(v->{stopMeasure();});

    }
    /**確認是否有麥克風使用權限*/
    private void checkPermission(){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
                ActivityCompat.checkSelfPermission(this
                        , Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(this
                    ,new String[]{Manifest.permission.RECORD_AUDIO},100);
        }else hasPermission = true;
    }
    /**取得權限回傳*/
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == 100 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
            hasPermission = true;
        }
    }
    /**開啟檢測*/
    private void startMeasure(){
        if (!hasPermission || isRecoding)return;
        mediaRecorder = new MediaRecorder();
        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
        mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
        mediaRecorder.setOutputFile("/dev/null");
        try {
            mediaRecorder.prepare();
            mediaRecorder.start();
            handlerMeasure.post(taskMeasure);
            isRecoding = true;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**關閉檢測*/
    private void stopMeasure(){
        if (!hasPermission || !isRecoding)return;
        handlerMeasure.removeCallbacks(taskMeasure);
        try {
            mediaRecorder.release();
            mediaRecorder.stop();
        }catch (IllegalStateException e){
            e.printStackTrace();
        }
        isRecoding = false;
    }
    /**透過HandlerTask 取得檢測結果*/
    @SuppressLint("HandlerLeak")
    private Handler handlerMeasure = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            switch (msg.what){
                case 1:
                    int amp = mediaRecorder.getMaxAmplitude();
                    //公式:Gdb = 20log10(V1/V0)
                    //Google已提供方法幫你取得麥克風的檢測電壓(V1)以及參考電壓(V0)
                    double db = 20*(Math.log10(Math.abs(amp)));
                    //if -Infinity
                    if (Math.round(db) == -9223372036854775808.0) tvResult.setText("0 db");
                    else tvResult.setText(Math.round(db)+" db");
                    break;
            }
            super.handleMessage(msg);

        }
    };
    private Runnable taskMeasure = new Runnable() {
        @Override
        public void run() {
            handlerMeasure.sendEmptyMessage(1);
            //每500毫秒抓取一次檢測結果
            handlerMeasure.postDelayed(this,500);
        }
    };

    @Override
    protected void onStop() {
        super.onStop();
        stopMeasure();
    }
}

 


 

總算寫完了(哈欠)

最近不知道為何,非~~~常沒有動力寫網誌

也非~~~常沒有動力唸書

每天上班也非~~~常沒有動力

就是很難集中精神做事的那種

實際上最近也很頭痛,其實我今年網誌進度已經欠兩篇了ヾ(´¬`)ノ

雖然其中一篇是因為補班日的緣故沒寫,不過去年的我就算是補班日也是會找點文章寫,也不至於都會放掉

哎~而且我最近也發現好多人的網誌自過年後的更新率也變低了

難道是因為天氣轉暖的影響嗎Σ(・口・)

好啦,總之雖然是這種日子這種天氣

但還是祝福各位的程式沒有Bug~

掰~

TK

arrow
arrow

    碼農日常 發表在 痞客邦 留言(19) 人氣()