Android實(shí)現(xiàn)邊錄邊播功能
Android可以通過(guò)MediaRecorder和AudioRecord這兩個(gè)工具來(lái)實(shí)現(xiàn)錄音,MediaRecorder直接把麥克風(fēng)的數(shù)據(jù)存到文件,并且能夠直接進(jìn)行編碼(如AMR,MP3等),而AudioRecord則是讀取麥克風(fēng)的音頻流。本文使用AudioRecord讀取音頻流,使用AudioTrack播放音頻流,通過(guò)“邊讀邊播放”以及增大音量的方式來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的助聽(tīng)器程序。
PS:由于目前的Android模擬器還不支持AudioRecord,因此本程序需要編譯之后放到真機(jī)運(yùn)行。
Activity_layout:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <Button android:id="@+id/bt_yinpinhuilu_testing_exit" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Close" /> </LinearLayout>
MainActivity:
import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import android.Manifest; import android.content.pm.PackageManager; 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.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; import java.util.LinkedList; public class MainActivity extends AppCompatActivity implements View.OnClickListener { private static final String TAG = "ktv"; /** * 按鈕 */ private Button bt_exit; /** * AudioRecord 寫(xiě)入緩沖區(qū)大小 */ protected int m_in_buf_size; /** * 錄制音頻對(duì)象 */ private AudioRecord m_in_rec; /** * 錄入的字節(jié)數(shù)組 */ private byte[] m_in_bytes; /** * 存放錄入字節(jié)數(shù)組的大小 */ private LinkedList<byte[]> m_in_q; /** * AudioTrack 播放緩沖大小 */ private int m_out_buf_size; /** * 播放音頻對(duì)象 */ private AudioTrack m_out_trk; /** * 播放的字節(jié)數(shù)組 */ private byte[] m_out_bytes; /** * 錄制音頻線(xiàn)程 */ private Thread record; /** * 播放音頻線(xiàn)程 */ private Thread play; /** * 讓線(xiàn)程停止的標(biāo)志 */ private boolean flag = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); this.setTitle("音頻回路"); requestAllPower(); Log.d("dfdfd", "333333333333"); init(); record = new Thread(new recordSound()); play = new Thread(new playRecord()); // 啟動(dòng)錄制線(xiàn)程 record.start(); // 啟動(dòng)播放線(xiàn)程 play.start(); } private void init() { bt_exit = findViewById(R.id.bt_yinpinhuilu_testing_exit); Log.i(TAG, "bt_exit====" + bt_exit); bt_exit.setOnClickListener(this); // AudioRecord 得到錄制最小緩沖區(qū)的大小 m_in_buf_size = AudioRecord.getMinBufferSize(8000, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT); // 實(shí)例化播放音頻對(duì)象 m_in_rec = new AudioRecord(MediaRecorder.AudioSource.MIC, 8000, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, m_in_buf_size); // 實(shí)例化一個(gè)字節(jié)數(shù)組,長(zhǎng)度為最小緩沖區(qū)的長(zhǎng)度 m_in_bytes = new byte[m_in_buf_size]; // 實(shí)例化一個(gè)鏈表,用來(lái)存放字節(jié)組數(shù) m_in_q = new LinkedList<byte[]>(); // AudioTrack 得到播放最小緩沖區(qū)的大小 m_out_buf_size = AudioTrack.getMinBufferSize(8000, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT); // 實(shí)例化播放音頻對(duì)象 m_out_trk = new AudioTrack(AudioManager.STREAM_MUSIC, 8000, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, m_out_buf_size, AudioTrack.MODE_STREAM); // 實(shí)例化一個(gè)長(zhǎng)度為播放最小緩沖大小的字節(jié)數(shù)組 m_out_bytes = new byte[m_out_buf_size]; } public void onClick(View v) { // TODO Auto-generated method stub switch (v.getId()) { case R.id.bt_yinpinhuilu_testing_exit: flag = false; m_in_rec.stop(); m_in_rec = null; m_out_trk.stop(); m_out_trk = null; this.finish(); } } /** * 錄音線(xiàn)程 */ class recordSound implements Runnable { @Override public void run() { Log.i(TAG, "........recordSound run()......"); byte[] bytes_pkg; // 開(kāi)始錄音 m_in_rec.startRecording(); while (flag) { m_in_rec.read(m_in_bytes, 0, m_in_buf_size); bytes_pkg = m_in_bytes.clone(); Log.i(TAG, "........recordSound bytes_pkg==" + bytes_pkg.length); if (m_in_q.size() >= 2) { m_in_q.removeFirst(); } m_in_q.add(bytes_pkg); } } } /** * 播放線(xiàn)程 */ class playRecord implements Runnable { @Override public void run() { // TODO Auto-generated method stub Log.i(TAG, "........playRecord run()......"); byte[] bytes_pkg = null; // 開(kāi)始播放 m_out_trk.play(); while (flag) { try { m_out_bytes = m_in_q.getFirst(); bytes_pkg = m_out_bytes.clone(); m_out_trk.write(bytes_pkg, 0, bytes_pkg.length); } catch (Exception e) { e.printStackTrace(); } } } } /** * 錄音哦動(dòng)態(tài)權(quán)限 */ public void requestAllPower() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.RECORD_AUDIO)) { } else { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO, Manifest.permission.READ_EXTERNAL_STORAGE}, 1); } } } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == 1) { for (int i = 0; i < permissions.length; i++) { Toast.makeText(this, "" + "權(quán)限" + permissions[i] + "申請(qǐng)成功", Toast.LENGTH_SHORT).show(); } } } }
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.test.speex"> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <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>
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android Canvas和Bitmap結(jié)合繪圖詳解流程
在 Android Canvas 上繪圖非常難,在繪圖時(shí)需要理解許多不同的類(lèi)和概念。這篇文章中,將介紹 Android 框架中可用的一些類(lèi),它們可以讓畫(huà)布使用時(shí)更輕松2021-11-11Android中驗(yàn)證碼倒計(jì)時(shí)的簡(jiǎn)單實(shí)現(xiàn)方法示例
最近開(kāi)發(fā)中在注冊(cè)獲取驗(yàn)證碼時(shí)候需要一個(gè)倒計(jì)時(shí)按鈕,找了相關(guān)的資料終于實(shí)現(xiàn)了,所以現(xiàn)在分享給大家,下面這篇文章主要給大家介紹了關(guān)于A(yíng)ndroid中驗(yàn)證碼倒計(jì)時(shí)簡(jiǎn)單實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考借鑒。2017-06-06Templates實(shí)戰(zhàn)之更優(yōu)雅實(shí)現(xiàn)自定義View構(gòu)造方法詳解
本篇文章介紹如何利用Android Studio提供的Live Templates更優(yōu)雅實(shí)現(xiàn)自定義View的構(gòu)造方法,說(shuō)句人話(huà)就是:簡(jiǎn)化自定義View構(gòu)造參數(shù)模板代碼的編寫(xiě),實(shí)現(xiàn)自動(dòng)生成,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09Recyclerview添加頭布局和尾布局、item點(diǎn)擊事件詳解
這篇文章主要為大家詳細(xì)介紹了Recyclerview添加頭布局和尾布局、item點(diǎn)擊事件的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08Android使用RecyclerView實(shí)現(xiàn)自定義列表、點(diǎn)擊事件以及下拉刷新
這篇文章主要介紹了Android使用RecyclerView實(shí)現(xiàn)自定義列表、點(diǎn)擊事件以及下拉刷新的相關(guān)資料,需要的朋友可以參考下2016-04-04Flutter開(kāi)發(fā)Mac桌面應(yīng)用實(shí)現(xiàn)自動(dòng)提取生成視頻字幕文件
這篇文章主要為大家介紹了Flutter開(kāi)發(fā)Mac桌面應(yīng)用實(shí)現(xiàn)自動(dòng)提取生成視頻字幕文件示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03Android Handler內(nèi)存泄漏詳解及其解決方案
在android開(kāi)發(fā)過(guò)程中,我們可能會(huì)遇到過(guò)令人奔潰的OOM異常,這篇文章主要介紹了Android Handler內(nèi)存泄漏詳解及其解決方案,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-08-08