編輯:關於Android編程
音頻這方面很博大精深,我這裡肯定講不了什麼高級的東西,最多也只是一些基礎類知識,首先,我們要介紹一下Android他提供的錄音類,實際上他有兩個,一個是MediaRecorder,還有一個就是我們今天要用到的AudioRecord,那他們有什麼區別呢?
一.區別
MediaRecorder和AudioRecord都可以錄制音頻,區別是MediaRecorder錄制的音頻文件是經過壓縮後的,需要設置編碼器。並且錄制的音頻文件可以用系統自帶的Music播放器播放。
而AudioRecord錄制的是PCM格式的音頻文件,需要用AudioTrack來播放,AudioTrack更接近底層。
PCM可能更加可以理解為音頻的源文件
二.優缺點
AudioRecord
主要是實現邊錄邊播以及對音頻的實時處理,這個特性讓他更適合在語音方面有優勢
優點:語音的實時處理,可以用代碼實現各種音頻的封裝
缺點:輸出是PCM格式文件,如果保存成音頻文件,是不能夠被播放器播放的,所以必須先寫代碼實現數據編碼以及壓縮
MediaRecorder
已經集成了錄音、編碼、壓縮等,支持少量的錄音音頻格式,大概有,aac,amr,3gp等
優點:集成,直接調用相關接口即可,代碼量小
缺點:無法實時處理音頻;輸出的音頻格式不是很多,例如沒有輸出mp3格式文件
三.准備工作
我們要實現的是一個實時的去錄音,播放,停止等功能的測試案例,那我們肯定要准備點什麼,比如說,我這裡先創建一個項目——PCMSample
然後寫個布局
layout_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="10dp"> <Button android:id="@+id/startAudio" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/button_bg" android:text="開始錄音" android:textColor="@android:color/white"/> <Button android:id="@+id/stopAudio" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="10dp" android:layout_marginTop="5dp" android:background="@drawable/button_bg" android:enabled="false" android:text="停止錄音" android:textColor="@android:color/white"/> <Button android:id="@+id/playAudio" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/button_bg" android:enabled="false" android:text="播放音頻" android:textColor="@android:color/white"/> <Button android:id="@+id/deleteAudio" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="5dp" android:background="@drawable/button_bg" android:text="刪除PCM" android:textColor="@android:color/white"/> <ScrollView android:id="@+id/mScrollView" android:layout_width="match_parent" android:layout_height="0dp" android:layout_marginTop="5dp" android:layout_weight="1"> <TextView android:id="@+id/tv_audio_succeess" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="初始化完成...." android:textColor="@color/colorAccent"/> </ScrollView> </LinearLayout>
可以預覽一下
這裡我給按鈕加了一個扁平的效果,實際上寫了一個xml,很簡單
button_bg.xml
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_pressed="true"> <shape> <corners android:radius="30dp"/> <solid android:color="@color/colorPrimary"/> </shape> </item> <item android:state_pressed="false"> <shape> <corners android:radius="30dp"/> <solid android:color="@color/colorPrimaryDark"/> </shape> </item> </selector>
好的,回到正題,我們這裡有四個按鈕,分別是開始。停止,播放,和刪除,我們就是要實現這四個功能,在此之前,我們還需要做的事情就是添加權限,因為我們要錄音和寫內存卡文件,所有需要這兩個權限即可
<!--錄音--> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <!--讀取SD卡--> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
這裡初始化什麼的就不說了,我們直接進入正題
四.開始錄音
開始錄音的話,這裡,我們定義一個變量isRecording去控制,這樣就比較好結束了,而且要注意的是,錄音是不能放在UI線程的,你懂的,所以我們可以寫一個開始錄音的方法
//開始錄音 public void StartRecord() { Log.i(TAG,"開始錄音"); //16K采集率 int frequency = 16000; //格式 int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO; //16Bit int audioEncoding = AudioFormat.ENCODING_PCM_16BIT; //生成PCM文件 file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/reverseme.pcm"); Log.i(TAG,"生成文件"); //如果存在,就先刪除再創建 if (file.exists()) file.delete(); Log.i(TAG,"刪除文件"); try { file.createNewFile(); Log.i(TAG,"創建文件"); } catch (IOException e) { Log.i(TAG,"未能創建"); throw new IllegalStateException("未能創建" + file.toString()); } try { //輸出流 OutputStream os = new FileOutputStream(file); BufferedOutputStream bos = new BufferedOutputStream(os); DataOutputStream dos = new DataOutputStream(bos); int bufferSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding); AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, frequency, channelConfiguration, audioEncoding, bufferSize); short[] buffer = new short[bufferSize]; audioRecord.startRecording(); Log.i(TAG, "開始錄音"); isRecording = true; while (isRecording) { int bufferReadResult = audioRecord.read(buffer, 0, bufferSize); for (int i = 0; i < bufferReadResult; i++) { dos.writeShort(buffer[i]); } } audioRecord.stop(); dos.close(); } catch (Throwable t) { Log.e(TAG, "錄音失敗"); } }
首先,這裡我們了解一下采樣率,編碼,音頻流等基本的概念,剩下的大多是讀寫流的操作了,我們通過創建一個AudioRecord去寫pcm文件,定義一個while循環,用我們剛才定義的isRecording控制,所以,我們的點擊事件就
case R.id.startAudio: Thread thread = new Thread(new Runnable() { @Override public void run() { StartRecord(); Log.e(TAG,"start"); } }); thread.start(); printLog("開始錄音"); ButtonEnabled(false, true, false); break;
這裡要注意一下thread.start();開啟線程,同時打印出log,具體代碼如下
//打印log private void printLog(final String resultString) { tv_audio_succeess.post(new Runnable() { @Override public void run() { tv_audio_succeess.append(resultString + "\n"); mScrollView.fullScroll(ScrollView.FOCUS_DOWN); } }); }
這裡,我為了防止ANR,所以控制了一下按鈕的焦點
//獲取/失去焦點 private void ButtonEnabled(boolean start, boolean stop, boolean play) { startAudio.setEnabled(start); stopAudio.setEnabled(stop); playAudio.setEnabled(play); }
好的,我們運行一下
看起來沒什麼變化,但是你去內存卡中就會發現多了一個pcm文件
當然,你只是點擊啟動錄音是不會生成這個pcm文件的,你需要點擊停止停止錄音的按鈕
五.停止錄音
停止錄音很簡單,我們控制通過改變寫入流就好了
case R.id.stopAudio: isRecording = false; ButtonEnabled(true, false, true); printLog("停止錄音"); break;
這樣才會生成PCM
六播放音頻
現在有了PCM我們可以試著去播放了,寫一個播放的方法
//播放文件 public void PlayRecord() { if(file == null){ return; } //讀取文件 int musicLength = (int) (file.length() / 2); short[] music = new short[musicLength]; try { InputStream is = new FileInputStream(file); BufferedInputStream bis = new BufferedInputStream(is); DataInputStream dis = new DataInputStream(bis); int i = 0; while (dis.available() > 0) { music[i] = dis.readShort(); i++; } dis.close(); AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 16000, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, musicLength * 2, AudioTrack.MODE_STREAM); audioTrack.play(); audioTrack.write(music, 0, musicLength); audioTrack.stop(); } catch (Throwable t) { Log.e(TAG, "播放失敗"); } }
正如上面所說,我們播放需要用到AudioTrack,調用他的play方法以及設置一些參數即可
七.刪除音頻
刪除音頻只需要刪除這個pcm文件就行
//刪除文件 private void deleFile() { if(file == null){ return; } file.delete(); printLog("文件刪除成功"); }
這就是大致的錄音邏輯,雖然看起來很簡單,但是這正是現在很多語音和音頻的最基礎部分,特別是語音,如果你從事語音的工作,我相信你會感謝我的!
好了,最後放上完整的代碼:
MainActivity
package com.liuguilin.pcmsample; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioRecord; import android.media.AudioTrack; import android.media.MediaRecorder; import android.os.Bundle; import android.os.Environment; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.ScrollView; import android.widget.TextView; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; public class MainActivity extends AppCompatActivity implements View.OnClickListener { public static final String TAG = "PCMSample"; //是否在錄制 private boolean isRecording = false; //開始錄音 private Button startAudio; //結束錄音 private Button stopAudio; //播放錄音 private Button playAudio; //刪除文件 private Button deleteAudio; private ScrollView mScrollView; private TextView tv_audio_succeess; //pcm文件 private File file; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); } //初始化View private void initView() { mScrollView = (ScrollView) findViewById(R.id.mScrollView); tv_audio_succeess = (TextView) findViewById(R.id.tv_audio_succeess); printLog("初始化成功"); startAudio = (Button) findViewById(R.id.startAudio); startAudio.setOnClickListener(this); stopAudio = (Button) findViewById(R.id.stopAudio); stopAudio.setOnClickListener(this); playAudio = (Button) findViewById(R.id.playAudio); playAudio.setOnClickListener(this); deleteAudio = (Button) findViewById(R.id.deleteAudio); deleteAudio.setOnClickListener(this); } //點擊事件 @Override public void onClick(View v) { switch (v.getId()) { case R.id.startAudio: Thread thread = new Thread(new Runnable() { @Override public void run() { StartRecord(); Log.e(TAG,"start"); } }); thread.start(); printLog("開始錄音"); ButtonEnabled(false, true, false); break; case R.id.stopAudio: isRecording = false; ButtonEnabled(true, false, true); printLog("停止錄音"); break; case R.id.playAudio: PlayRecord(); ButtonEnabled(true, false, false); printLog("播放錄音"); break; case R.id.deleteAudio: deleFile(); break; } } //打印log private void printLog(final String resultString) { tv_audio_succeess.post(new Runnable() { @Override public void run() { tv_audio_succeess.append(resultString + "\n"); mScrollView.fullScroll(ScrollView.FOCUS_DOWN); } }); } //獲取/失去焦點 private void ButtonEnabled(boolean start, boolean stop, boolean play) { startAudio.setEnabled(start); stopAudio.setEnabled(stop); playAudio.setEnabled(play); } //開始錄音 public void StartRecord() { Log.i(TAG,"開始錄音"); //16K采集率 int frequency = 16000; //格式 int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO; //16Bit int audioEncoding = AudioFormat.ENCODING_PCM_16BIT; //生成PCM文件 file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/reverseme.pcm"); Log.i(TAG,"生成文件"); //如果存在,就先刪除再創建 if (file.exists()) file.delete(); Log.i(TAG,"刪除文件"); try { file.createNewFile(); Log.i(TAG,"創建文件"); } catch (IOException e) { Log.i(TAG,"未能創建"); throw new IllegalStateException("未能創建" + file.toString()); } try { //輸出流 OutputStream os = new FileOutputStream(file); BufferedOutputStream bos = new BufferedOutputStream(os); DataOutputStream dos = new DataOutputStream(bos); int bufferSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding); AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, frequency, channelConfiguration, audioEncoding, bufferSize); short[] buffer = new short[bufferSize]; audioRecord.startRecording(); Log.i(TAG, "開始錄音"); isRecording = true; while (isRecording) { int bufferReadResult = audioRecord.read(buffer, 0, bufferSize); for (int i = 0; i < bufferReadResult; i++) { dos.writeShort(buffer[i]); } } audioRecord.stop(); dos.close(); } catch (Throwable t) { Log.e(TAG, "錄音失敗"); } } //播放文件 public void PlayRecord() { if(file == null){ return; } //讀取文件 int musicLength = (int) (file.length() / 2); short[] music = new short[musicLength]; try { InputStream is = new FileInputStream(file); BufferedInputStream bis = new BufferedInputStream(is); DataInputStream dis = new DataInputStream(bis); int i = 0; while (dis.available() > 0) { music[i] = dis.readShort(); i++; } dis.close(); AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 16000, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, musicLength * 2, AudioTrack.MODE_STREAM); audioTrack.play(); audioTrack.write(music, 0, musicLength); audioTrack.stop(); } catch (Throwable t) { Log.e(TAG, "播放失敗"); } } //刪除文件 private void deleFile() { if(file == null){ return; } file.delete(); printLog("文件刪除成功"); } }
如果你想去調試這些pcm文件做音頻測試的話,我推薦使用Audacity這個軟件,可以看到,我直接點擊左上角的file-導入-源文件,然後設置16K
這樣就可以調試了
最後,放一張完整的截圖
以上所述是小編給大家介紹的Android音頻處理之通過AudioRecord去保存PCM文件進行錄制,播放,停止,刪除功能,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對本站網站的支持!
Android上讓人頭疼的莫過於從網絡上獲取圖片,然後顯示圖片,最後還要考慮到圖片的回收問題,這之間只要有任何一個環節有問題都可能直接OOM。尤其在需要展示圖片的列表頁面
大家好,我是M1ko。在互聯網時代的今天,如果一個App不接入互聯網,那麼這個App一定不會有長時間的生命周期,因此Android網絡編程是每一個Android開發者必備
一.概述因為之前項目有動態熱修復的功能,在修復的過程中會從服務器上下載一個新的dex文件來替換老的dex文件,所以就牽扯到文件身份效驗的問題.通常接口會下發一個MD5值,
前面講到Vitamio可以支持一些流媒體,在這裡就用Vitamio來播放網絡上的一些流媒體,如:mms、rtsp、http,參考前輩的一些文章來寫一個網絡收音機程序,對於