Android开发之PCM音频流采集| 音频流录制与PCM音频流播放的实现方法
生活随笔
收集整理的這篇文章主要介紹了
Android开发之PCM音频流采集| 音频流录制与PCM音频流播放的实现方法
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
在android中如果需要錄制PCM流需要用到AudioRecord這個類,然后播放的話需要用AudioTrack
先看下效果圖:
好了我們先看下如何錄制PCM,看下核心代碼
try {//輸出流OutputStream os = new FileOutputStream(recordFile);BufferedOutputStream bos = new BufferedOutputStream(os);DataOutputStream dos = new DataOutputStream(bos);/*** android.media.AudioRecord public static int getMinBufferSize(int sampleRateInHz,int channelConfig,int audioFormat)返回成功創建 AudioRecord 對象所需的最小緩沖區大小,以字節為單位。 請注意,此大小 不能保證在負載下順利錄制,應根據 AudioRecord 實例輪詢新數據的預期頻率選擇更高的 值。 有關有效配置值的更多信息,請參閱AudioRecord(int, int, int, int, int) 。參數:sampleRateInHz – 以赫茲表示的采樣率。 AudioFormat.SAMPLE_RATE_UNSPECIFIED是不允許的。channelConfig – 描述音頻通道的配置。 請參閱AudioFormat.CHANNEL_IN_MONO和 AudioFormat.CHANNEL_IN_STEREOaudioFormat – 表示音頻數據的格式。 請參閱AudioFormat.ENCODING_PCM_16BIT ?;貓?#xff1a;ERROR_BAD_VALUE如果硬件不支持錄制參數,或者傳遞了無效參數,或者如果實現無法查詢 硬件以獲取其輸入屬性或以字節表示的最小緩沖區大小,則為ERROR */int bufferSize = AudioRecord.getMinBufferSize(sampleRateInHz, AudioFormat.CHANNEL_IN_STEREO, audioEncoding);AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRateInHz, AudioFormat.CHANNEL_IN_STEREO, audioEncoding, bufferSize);short[] buffer = new short[bufferSize];audioRecord.startRecording();Log.e(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, "錄音失敗");showToast("錄音失敗");}再看下播放pcm,分兩種
1.一次性讀取所有pcm數據后在播放這種適合數據小的pcm流
2.一邊讀取pcm流一邊播放,這種適合比較大的數據流
先看一次性讀取的代碼
int musicLength = (int) (recordFile.length() / 2);short[] music = new short[musicLength];try {InputStream is = new FileInputStream(recordFile);BufferedInputStream bis = new BufferedInputStream(is);DataInputStream dis = new DataInputStream(bis);int i = 0;while (dis.available() > 0) {music[i] = dis.readShort();i++;}dis.close();/**android.media.AudioTrack public static int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat)返回在MODE_STREAM模式下創建的 AudioTrack 對象所需的估計最小緩沖區大小。 大小是一個 估計值,因為它既不考慮路由也不考慮匯,因為兩者都不知道。 請注意,此大小并不能保證在 負載下流暢播放,應根據緩沖區重新填充要播放的其他數據的預期頻率選擇更高的值。 例如, 如果您打算將 AudioTrack 的源采樣率動態設置為高于初始源采樣率的值,請務必根據計劃的 最高采樣率配置緩沖區大小。參數:sampleRateInHz – 以 Hz 表示的源采樣率。 不允許 AudioFormat.SAMPLE_RATE_UNSPECIFIED 。channelConfig – 描述音頻通道的配置。 請參閱AudioFormat.CHANNEL_OUT_MONO和 AudioFormat.CHANNEL_OUT_STEREOaudioFormat – 表示音頻數據的格式。 請參閱AudioFormat.ENCODING_PCM_16BIT和 AudioFormat.ENCODING_PCM_8BIT和AudioFormat.ENCODING_PCM_FLOAT 。返回:如果傳遞了無效參數,則為ERROR_BAD_VALUE如果無法查詢輸出屬性,則為ERROR ,或者以字 節為單位表示的 最小緩沖區大小**/AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRateInHz, channelConfiguration, audioEncoding, musicLength * 2, AudioTrack.MODE_STREAM);audioTrack.play();audioTrack.write(music, 0, musicLength);audioTrack.stop();} catch (Throwable t) {Log.e(TAG, "播放失敗");showToast("播放失敗");}在看下一邊讀一邊播放
try { // recordFile = new File("/storage/emulated/0/Android/data/com.yhsh.recordpcm/cache/audio_cache/music.wav");//從音頻文件中讀取聲音DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(recordFile)));//最小緩存區int bufferSizeInBytes = AudioTrack.getMinBufferSize(sampleRateInHz, AudioFormat.CHANNEL_OUT_STEREO, audioEncoding);//創建AudioTrack對象 依次傳入 :流類型、采樣率(與采集的要一致)、音頻通道(采集是IN 播放時OUT)、量化位數、最小緩沖區、模式AudioTrack player = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRateInHz, AudioFormat.CHANNEL_OUT_STEREO, audioEncoding, bufferSizeInBytes, AudioTrack.MODE_STREAM);short[] data = new short[bufferSizeInBytes];//byte[] data = new byte[bufferSizeInBytes];//開始播放player.play();while (true) {int i = 0;while (dis.available() > 0 && i < data.length) {//錄音時write Byte 那么讀取時就該為readByte要相互對應data[i] = dis.readShort();//data[i] = dis.readByte();i++;}player.write(data, 0, data.length);//表示讀取完了if (i != bufferSizeInBytes) {player.stop();//停止播放player.release();//釋放資源dis.close();showToast("播放完成了!!!");break;}}} catch (Exception e) {Log.e(TAG, "播放異常: " + e.getMessage());showToast("播放異常!!!!");e.printStackTrace();}好了如果看著亂,我貼下完整代碼
先看MainActivity.java
package com.yhsh.recordpcm;import android.annotation.SuppressLint; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.ScrollView; import android.widget.TextView;import androidx.appcompat.app.AppCompatActivity;import java.io.File; import java.lang.ref.WeakReference; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit;/*** @author DELL*/ public class MainActivity extends AppCompatActivity implements View.OnClickListener {private static final String TAG = "MainActivity";private TextView tvAudioSuccess;private ScrollView mScrollView;private Button startAudio;private Button stopAudio;private Button playAudio;ThreadPoolExecutor mExecutorService = new ThreadPoolExecutor(3, 5, 1, TimeUnit.MINUTES, new LinkedBlockingDeque<>(10));private boolean isChecked;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);startAudio = findViewById(R.id.startAudio);startAudio.setOnClickListener(this);stopAudio = findViewById(R.id.stopAudio);stopAudio.setOnClickListener(this);CheckBox cbTogetherPlay = findViewById(R.id.cb_together_play);cbTogetherPlay.setOnCheckedChangeListener((buttonView, isChecked) -> MainActivity.this.isChecked = isChecked);playAudio = findViewById(R.id.playAudio);playAudio.setOnClickListener(this);Button deleteAudio = findViewById(R.id.deleteAudio);deleteAudio.setOnClickListener(this);tvAudioSuccess = findViewById(R.id.tv_audio_succeess);mScrollView = findViewById(R.id.mScrollView);}@SuppressLint("NonConstantResourceId")@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.startAudio:mExecutorService.execute(() -> {PlayManagerUtils.getInstance().startRecord(new WeakReference<>(getApplicationContext()));Log.e(TAG, "start");});printLog("開始錄音");buttonEnabled(false, true, false);break;case R.id.stopAudio:PlayManagerUtils.getInstance().setRecord(false);buttonEnabled(true, false, true);printLog("停止錄音");break;case R.id.playAudio:mExecutorService.execute(() -> PlayManagerUtils.getInstance().playPcm(isChecked));buttonEnabled(true, false, false);printLog("播放錄音");break;case R.id.deleteAudio:deleteFile();break;default:break;}}/*** 打印log** @param resultString 返回數據*/private void printLog(final String resultString) {tvAudioSuccess.post(new Runnable() {@Overridepublic void run() {tvAudioSuccess.append(resultString + "\n");mScrollView.fullScroll(ScrollView.FOCUS_DOWN);}});}/*** 獲取/失去焦點** @param start 是否可點擊* @param stop 是否可點擊* @param play 是否可點擊*/private void buttonEnabled(boolean start, boolean stop, boolean play) {startAudio.setEnabled(start);stopAudio.setEnabled(stop);playAudio.setEnabled(play);}/*** 刪除文件*/private void deleteFile() {File recordFile = PlayManagerUtils.getInstance().getRecordFile();if (recordFile == null) {return;}recordFile.delete();printLog("文件刪除成功");} }在看下布局文件activit_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"><Buttonandroid: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" /><Buttonandroid:id="@+id/stopAudio"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="5dp"android:layout_marginBottom="10dp"android:background="@drawable/button_bg"android:enabled="false"android:text="停止錄音"android:textColor="@android:color/white" /><CheckBoxandroid:id="@+id/cb_together_play"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="錄制兩次一起播放" /><Buttonandroid: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" /><Buttonandroid: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" /><ScrollViewandroid:id="@+id/mScrollView"android:layout_width="match_parent"android:layout_height="0dp"android:layout_marginTop="5dp"android:layout_weight="1"><TextViewandroid:id="@+id/tv_audio_succeess"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="初始化完成...."android:textColor="@color/purple_700" /></ScrollView></LinearLayout>在看下錄制與播放工具類
PlayManagerUtils.java package com.yhsh.recordpcm;import android.content.Context; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioRecord; import android.media.AudioTrack; import android.media.MediaRecorder; import android.os.Handler; import android.os.Looper; import android.text.format.DateFormat; import android.util.Log; import android.widget.Toast;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; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.Locale; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit;/*** @author xiayiye5* @date 2021/12/17 18:10*/ public class PlayManagerUtils {private static final String TAG = "PlayManagerUtils";private WeakReference<Context> weakReference;private File recordFile;private boolean isRecording;/*** 最多只能存2條記錄*/private final List<File> filePathList = new ArrayList<>(2);/*** 16K采集率*/int sampleRateInHz = 16000;/*** 格式*/ // int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;int channelConfiguration = AudioFormat.CHANNEL_OUT_STEREO; // int channelConfiguration = AudioFormat.CHANNEL_IN_STEREO;/*** 16Bit*/int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;private PlayManagerUtils() {}private static final PlayManagerUtils PLAY_MANAGER_UTILS = new PlayManagerUtils();public static PlayManagerUtils getInstance() {return PLAY_MANAGER_UTILS;}private final Handler handler = new Handler(Looper.getMainLooper());ThreadPoolExecutor mExecutorService = new ThreadPoolExecutor(3, 5, 1, TimeUnit.MINUTES, new LinkedBlockingDeque<>(10));public void startRecord(WeakReference<Context> weakReference) {this.weakReference = weakReference;Log.e(TAG, "開始錄音");//生成PCM文件String fileName = DateFormat.format("yyyyMMdd_HHmmss", Calendar.getInstance(Locale.getDefault())) + "_xiayiye5.pcm";File file = new File(weakReference.get().getExternalCacheDir(), "audio_cache");if (!file.exists()) {file.mkdir();}String audioSaveDir = file.getAbsolutePath();Log.e(TAG, audioSaveDir);recordFile = new File(audioSaveDir, fileName);Log.e(TAG, "生成文件" + recordFile);//如果存在,就先刪除再創建if (recordFile.exists()) {recordFile.delete();Log.e(TAG, "刪除文件");}try {recordFile.createNewFile();Log.e(TAG, "創建文件");} catch (IOException e) {Log.e(TAG, "未能創建");throw new IllegalStateException("未能創建" + recordFile.toString());}if (filePathList.size() == 2) {filePathList.clear();}filePathList.add(recordFile);try {//輸出流OutputStream os = new FileOutputStream(recordFile);BufferedOutputStream bos = new BufferedOutputStream(os);DataOutputStream dos = new DataOutputStream(bos);int bufferSize = AudioRecord.getMinBufferSize(sampleRateInHz, AudioFormat.CHANNEL_IN_STEREO, audioEncoding);AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRateInHz, AudioFormat.CHANNEL_IN_STEREO, audioEncoding, bufferSize);short[] buffer = new short[bufferSize];audioRecord.startRecording();Log.e(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, "錄音失敗");showToast("錄音失敗");}}/*** 播放pcm流的方法,一次性讀取所有Pcm流,讀完后在開始播放*/public void playAllRecord() {if (recordFile == null) {return;} // recordFile = new File("/storage/emulated/0/Android/data/com.yhsh.recordpcm/cache/audio_cache/music.wav");//讀取文件int musicLength = (int) (recordFile.length() / 2);short[] music = new short[musicLength];try {InputStream is = new FileInputStream(recordFile);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, sampleRateInHz, channelConfiguration, audioEncoding, musicLength * 2, AudioTrack.MODE_STREAM);audioTrack.play();audioTrack.write(music, 0, musicLength);audioTrack.stop();} catch (Throwable t) {Log.e(TAG, "播放失敗");showToast("播放失敗");}}public void playPcm(boolean isChecked) {if (isChecked) {//兩首一起播放for (File recordFiles : filePathList) {mExecutorService.execute(() -> playPcmData(recordFiles));}} else {//只播放最后一次錄音playPcmData(recordFile);}}/*** 播放Pcm流,邊讀取邊播*/private void playPcmData(File recordFiles) {Log.e(TAG, "打印線程" + Thread.currentThread().getName());try { // recordFile = new File("/storage/emulated/0/Android/data/com.yhsh.recordpcm/cache/audio_cache/music.wav");DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(recordFiles)));//最小緩存區int bufferSizeInBytes = AudioTrack.getMinBufferSize(sampleRateInHz, AudioFormat.CHANNEL_OUT_STEREO, audioEncoding);AudioTrack player = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRateInHz, AudioFormat.CHANNEL_OUT_STEREO, audioEncoding, bufferSizeInBytes, AudioTrack.MODE_STREAM);short[] data = new short[bufferSizeInBytes];//開始播放player.play();while (true) {int i = 0;while (dis.available() > 0 && i < data.length) {data[i] = dis.readShort();i++;}player.write(data, 0, data.length);//表示讀取完了if (i != bufferSizeInBytes) {player.stop();//停止播放player.release();//釋放資源dis.close();showToast("播放完成了!!!");break;}}} catch (Exception e) {Log.e(TAG, "播放異常: " + e.getMessage());showToast("播放異常!!!!");e.printStackTrace();}}public File getRecordFile() {return recordFile;}public void setRecord(boolean isRecording) {this.isRecording = isRecording;}private void showToast(String msg) {handler.post(() -> Toast.makeText(weakReference.get(), msg, Toast.LENGTH_LONG).show());} }如果還是看著不明白,請下載源碼查看
gitee源碼下載:??????PCM音頻流錄制與播放源碼下載
在此非常感謝兩位博主:博主直達錄音與播放
博主直達pcm播放
總結
以上是生活随笔為你收集整理的Android开发之PCM音频流采集| 音频流录制与PCM音频流播放的实现方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 删除文件时显示被另一程序打开怎么解决?
- 下一篇: 我的世界床的五个用法 教你玩转世界床