今天來寫個關於聲級器(噪音計)的APP吧~
最近因為手頭上沒什麼新的開發案,所以上班閒著無聊就來研究怎麼寫噪音計(/・0・)
對,然後就真的給我玩出了點名堂了ɷ◡ɷ
好啦不鬧,總之還真的讓我做出來了沒錯
我後面也打算要把這個包裝成一個APP做上架...不過應該也是一個月後的事了(;´д`)ゞ
因為我可能還會加一些外部功能,讓APP完整度更高這樣
OK,那就進入正題
看範例
還有Github
->https://github.com/thumbb13555/NoiseRecoder
Go~
1. 畫介面&取得權限
1-1 畫介面
介面其實也沒有什麼內容
直接給快些
<?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的部分
輸入以下粉底白字的部分
<?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. 完整程式碼
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~
掰~
留言列表