需要源码请点赞关注收藏后评论区留下QQ~~~

一、原始音频的录播

语音通话功能要求实时传输,如果使用MediaRecorder与MediaPlayer组合,那么只能整句话都录完并编码好了才能传给对方去播放,这个时效性太差。

此时用到音频录制器AudioRecord与音轨播放器AudioTrack,该组合的音频格式为原始的二进制音频数据,没有文件头和文件尾,故而可以实现边录边播的实时语音对话

下面是AudioRecord的录音方法

getMinBufferSize 根据采样频率 声道配置音频格式获得合适的缓冲区大小

startRecording  开始录音

read 从缓冲区读取音频数据

stop 停止录音

setNotificationMarkerPosition 设置需要通知的标记位置

setRecordPositionUpdataListener 设置需要通知的时间周期

下面是AudioTrack的播音方法

setStereoVolume 设置立体声的音量

play 开始播音

write 把缓冲区的音频数据写入音轨

实战效果如下

可以在下拉框中选择频率 类型 编码格式等等

连接真机测试更佳

代码如下

Java类

package com.example.audio;import android.media.AudioFormat;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;import androidx.appcompat.app.AppCompatActivity;import com.example.audio.task.AudioPlayTask;
import com.example.audio.task.AudioRecordTask;
import com.example.audio.util.DateUtil;public class AudioRawActivity extends AppCompatActivity implementsOnCheckedChangeListener, AudioRecordTask.OnRecordListener, AudioPlayTask.OnPlayListener {private static final String TAG = "AudioRawActivity";private TextView tv_audio_record; // 声明一个文本视图对象private CheckBox ck_audio_record; // 声明一个复选框对象private TextView tv_audio_play; // 声明一个文本视图对象private CheckBox ck_audio_play; // 声明一个复选框对象private int mFrequence; // 音频的采样频率private int mInChannel; // 音频的声道类型(录音时候)private int mOutChannel; // 音频的声道类型(播音时候)private int mFormat; // 音频的编码格式private String mRecordFilePath; // 录制文件的保存路径private AudioRecordTask mRecordTask; // 声明一个原始音频录制线程对象private AudioPlayTask mPlayTask; // 声明一个原始音频播放线程对象public void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_audio_raw);tv_audio_record = findViewById(R.id.tv_audio_record);ck_audio_record = findViewById(R.id.ck_audio_record);ck_audio_record.setOnCheckedChangeListener(this);tv_audio_play = findViewById(R.id.tv_audio_play);ck_audio_play = findViewById(R.id.ck_audio_play);ck_audio_play.setOnCheckedChangeListener(this);initFrequenceSpinner(); // 初始化采样频率的下拉框initChannelSpinner(); // 初始化声道类型的下拉框initFormatSpinner(); // 初始化编码格式的下拉框}// 初始化采样频率的下拉框private void initFrequenceSpinner() {ArrayAdapter<String> frequenceAdapter = new ArrayAdapter<>(this,R.layout.item_select, frequenceDescArray);Spinner sp_frequence = findViewById(R.id.sp_frequence);sp_frequence.setPrompt("请选择采样频率");sp_frequence.setAdapter(frequenceAdapter);sp_frequence.setOnItemSelectedListener(new FrequenceSelectedListener());sp_frequence.setSelection(0);}private String[] frequenceDescArray = {"16000赫兹", "8000赫兹"};private int[] frequenceArray = {16000, 8000};class FrequenceSelectedListener implements AdapterView.OnItemSelectedListener {public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {mFrequence = frequenceArray[arg2];}public void onNothingSelected(AdapterView<?> arg0) {}}// 初始化声道类型的下拉框private void initChannelSpinner() {ArrayAdapter<String> channelAdapter = new ArrayAdapter<>(this,R.layout.item_select, channelDescArray);Spinner sp_channel = findViewById(R.id.sp_channel);sp_channel.setPrompt("请选择声道类型");sp_channel.setAdapter(channelAdapter);sp_channel.setSelection(0);sp_channel.setOnItemSelectedListener(new ChannelSelectedListener());}private String[] channelDescArray = {"单声道", "立体声"};private int[] inChannelArray = {AudioFormat.CHANNEL_IN_MONO, AudioFormat.CHANNEL_IN_STEREO};private int[] outChannelArray = {AudioFormat.CHANNEL_OUT_MONO, AudioFormat.CHANNEL_OUT_STEREO};class ChannelSelectedListener implements AdapterView.OnItemSelectedListener {public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {mInChannel = inChannelArray[arg2];mOutChannel = outChannelArray[arg2];}public void onNothingSelected(AdapterView<?> arg0) {}}// 初始化编码格式的下拉框private void initFormatSpinner() {ArrayAdapter<String> formatAdapter = new ArrayAdapter<>(this,R.layout.item_select, formatDescArray);Spinner sp_format = findViewById(R.id.sp_format);sp_format.setPrompt("请选择编码格式");sp_format.setAdapter(formatAdapter);sp_format.setSelection(0);sp_format.setOnItemSelectedListener(new FormatSelectedListener());}private String[] formatDescArray = {"16位", "8位"};private int[] formatArray = {AudioFormat.ENCODING_PCM_16BIT, AudioFormat.ENCODING_PCM_8BIT};class FormatSelectedListener implements AdapterView.OnItemSelectedListener {public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {mFormat = formatArray[arg2];}public void onNothingSelected(AdapterView<?> arg0) {}}@Overridepublic void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {if (buttonView.getId() == R.id.ck_audio_record) {if (isChecked) { // 开始录音// 生成原始音频的文件路径mRecordFilePath = String.format("%s/%s.pcm",getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString(),DateUtil.getNowDateTime());ck_audio_record.setText("停止录音");int[] params = new int[] {mFrequence, mInChannel, mFormat};// 创建一个原始音频录制线程,并设置录制事件监听器mRecordTask = new AudioRecordTask(this, mRecordFilePath, params, this);mRecordTask.start(); // 启动原始音频录制线程} else { // 停止录音ck_audio_record.setText("开始录音");mRecordTask.cancel(); // 原始音频录制线程取消录音ck_audio_play.setVisibility(View.VISIBLE);}} else if (buttonView.getId() == R.id.ck_audio_play) {if (isChecked) { // 开始播音ck_audio_play.setText("暂停播音");int[] params = new int[] {mFrequence, mOutChannel, mFormat};// 创建一个原始音频播放线程,并设置播放事件监听器mPlayTask = new AudioPlayTask(this, mRecordFilePath, params, this);mPlayTask.start(); // 启动原始音频播放线程} else { // 停止播音ck_audio_play.setText("开始播音");mPlayTask.cancel(); // 原始音频播放线程取消播音}}}// 在录音进度更新时触发@Overridepublic void onRecordUpdate(int duration) {String desc = String.format("已录制%d秒", duration);tv_audio_record.setText(desc);}// 在录音完成时触发@Overridepublic void onRecordFinish() {ck_audio_record.setChecked(false);Toast.makeText(this, "已结束录音,音频文件路径为"+mRecordFilePath, Toast.LENGTH_LONG).show();}// 在播音进度更新时触发@Overridepublic void onPlayUpdate(int duration) {String desc = String.format("已播放%d秒", duration);tv_audio_play.setText(desc);}// 在播音完成时触发@Overridepublic void onPlayFinish() {ck_audio_play.setChecked(false);Toast.makeText(this, "已结束播音", Toast.LENGTH_LONG).show();}}

XML文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="40dp"android:paddingLeft="5dp"><TextViewandroid:layout_width="wrap_content"android:layout_height="match_parent"android:gravity="center"android:text="采样频率:"android:textColor="@color/black"android:textSize="17sp" /><Spinnerandroid:id="@+id/sp_frequence"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:gravity="left|center"android:spinnerMode="dialog" /></LinearLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="40dp"android:paddingLeft="5dp"><TextViewandroid:layout_width="wrap_content"android:layout_height="match_parent"android:gravity="center"android:text="声道类型:"android:textColor="@color/black"android:textSize="17sp" /><Spinnerandroid:id="@+id/sp_channel"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:gravity="left|center"android:spinnerMode="dialog" /></LinearLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="40dp"android:paddingLeft="5dp"><TextViewandroid:layout_width="wrap_content"android:layout_height="match_parent"android:gravity="center"android:text="编码格式:"android:textColor="@color/black"android:textSize="17sp" /><Spinnerandroid:id="@+id/sp_format"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:gravity="left|center"android:spinnerMode="dialog" /></LinearLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"android:padding="5dp"><CheckBoxandroid:id="@+id/ck_audio_record"style="@style/SwitchButton"android:layout_width="match_parent"android:checked="false"android:text="开始录音" /><TextViewandroid:id="@+id/tv_audio_record"android:layout_width="match_parent"android:layout_height="wrap_content"android:padding="5dp"android:gravity="center"android:textColor="@color/black"android:textSize="17sp" /><CheckBoxandroid:id="@+id/ck_audio_play"style="@style/SwitchButton"android:layout_width="match_parent"android:text="开始播音"android:textColor="@color/black"android:visibility="gone" /><TextViewandroid:id="@+id/tv_audio_play"android:layout_width="match_parent"android:layout_height="wrap_content"android:padding="5dp"android:gravity="center"android:textColor="@color/black"android:textSize="17sp" /></LinearLayout>
</LinearLayout>

二、自定义音频控制条

原始的拖动条十分简陋,我们设计一个全新的控件来实现以下三点功能

1:显示音频的总时长

2:显示音频的已播放时长

3:提供暂停播放与恢复播放功能

完整的播控功能至少包含以下三项

1:关联音频路径与音频控制条

2:控制条实时显示当前播放进度

3:进度条的拖动操作实时传给媒体播放器

效果如下 可实现如下的自动播放暂停 拖动功能 而且更加美观

代码如下

Java类

package com.example.audio;import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;import androidx.appcompat.app.AppCompatActivity;import com.example.audio.bean.AudioInfo;
import com.example.audio.util.MediaUtil;
import com.example.audio.widget.AudioController;public class AudioControllerActivity extends AppCompatActivity {private final static String TAG = "AudioControllerActivity";private LinearLayout ll_controller; // 声明一个线性视图对象private TextView tv_title; // 声明一个文本视图对象private AudioController ac_play; // 声明一个音频控制条对象private int CHOOSE_CODE = 3; // 只在音乐库挑选音频的请求码@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_audio_controller);ll_controller = findViewById(R.id.ll_controller);tv_title = findViewById(R.id.tv_title);ac_play = findViewById(R.id.ac_play);findViewById(R.id.btn_open).setOnClickListener(v -> {// ACTION_GET_CONTENT只可选择近期的音频Intent intent = new Intent(Intent.ACTION_GET_CONTENT);// ACTION_PICK可选择所有音频//Intent intent = new Intent(Intent.ACTION_PICK);intent.setType("audio/*"); // 类型为音频startActivityForResult(intent, CHOOSE_CODE); // 打开系统音频库});}@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent intent) {super.onActivityResult(requestCode, resultCode, intent);if (resultCode == RESULT_OK && requestCode == CHOOSE_CODE) { // 从音频库回来if (intent.getData() != null) {ll_controller.setVisibility(View.VISIBLE);// 从content://media/external/audio/media/这样的Uri中获取音频信息AudioInfo audio = MediaUtil.getPathFromContentUri(this, intent.getData());ac_play.prepare(audio.getAudio()); // 准备播放指定路径的音频ac_play.start(); // 开始播放String desc = String.format("%s的《%s》", audio.getArtist(), audio.getTitle());tv_title.setText("当前播放曲目名称:"+desc);}}}@Overrideprotected void onResume() {super.onResume();ac_play.resume(); // 恢复播放}@Overrideprotected void onPause() {super.onPause();ac_play.pause(); // 暂停播放}@Overrideprotected void onDestroy() {super.onDestroy();ac_play.release(); // 释放播放资源}}

XML文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><Buttonandroid:id="@+id/btn_open"android:layout_width="match_parent"android:layout_height="wrap_content"android:gravity="center"android:text="打开音频文件"android:textColor="@color/black"android:textSize="17sp" /><LinearLayoutandroid:id="@+id/ll_controller"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"android:visibility="gone"><TextViewandroid:id="@+id/tv_title"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginBottom="5dp"android:gravity="center"android:textColor="@color/black"android:textSize="17sp" /><com.example.audio.widget.AudioControllerandroid:id="@+id/ac_play"android:layout_width="match_parent"android:layout_height="60dp"android:background="#cccccc" /></LinearLayout>
</LinearLayout>

创作不易 觉得有帮助请点赞关注收藏~~~

Android 开发中原始音频的录播和和自定义音频控制条的讲解及实战(超详细 附源码)相关推荐

  1. Android开发音效中录制WAV音频和录制MP3音频的讲解及实战(超详细 附源码)

    需要源码请点赞关注收藏后评论区留下QQ~~~ 一.录制WAV音频 无论是MediaRecoredr录制的AMR和AAC音频,还是AudioRecord录制的PCM音频,都不能在计算机上直接播放,因为它 ...

  2. Android开发音效增强中铃声播放Ringtone及声音池调度SoundPool的讲解及实战(超详细 附源码)

    需要源码请点赞关注收藏后评论区留下QQ~~~ 一.铃声播放 虽然媒体播放器MediaPlayer既可用来播放视频,也可以用来播放音频,但是在具体的使用场合,MediaPlayer存在某些播音方面的不足 ...

  3. Android App开发实战项目之仿喜马拉雅的听说书App实现(超详细 附源码和演示视频)

    需要全部源码请点赞关注收藏后评论区留下QQ~~~ 一.需求分析 用户不仅能在平台上收听音频,还能成为内容创作者,总之长音频分享平台需要满足两种角色的使用:一种是作为内容创作者发布自己的音频,另一种是作 ...

  4. 【Android App】物联网中指南针、计步器、感光器、陀螺仪的讲解及实战演示(附源码 超详细必看)

    需要源码请点赞关注收藏后评论区留言~~~ 一.指南针-磁场传感器 顾名思义,指南针只要找到朝南的方向就好了. 可是在App中并非使用一个方向传感器这么简单,事实上单独的方向传感器已经弃用,取而代之的是 ...

  5. 【Android +Tensroflow Lite】实现从基于机器学习语音中识别指令讲解及实战(超详细 附源码和演示视频)

    需要源码和配置文件请点赞关注收藏后评论区留言~~~ 一.基于机器学习的语音推断 Tensorflow基于分层和模块化的设计思想,整个框架以C语言的编程接口为界,分为前端和后端两大部分 Tensorfl ...

  6. Android App开发语音处理之系统自带的语音引擎、文字转语音、语音识别的讲解及实战(超详细 附源码)

    需要源码请点赞关注收藏后评论区留下QQ~~~ 一.系统自带的语音引擎 语音播报的本质是将书面文字转换成自然语言的音频流,这个转换操作被称作语音合成,又称TTS(从文本到语音)在转换过程中,为了避免机械 ...

  7. Android 开发人脸识别之自动识别验证码功能讲解及实现(超详细 附源码)

    需要源码和图片集请点赞关注收藏后评论区留下QQ或者私信~~~ 一.自动识别验证码 验证码图片中最简单的是数字验证码,一张再普通不过的验证码拿到之后要进行以下步骤的处理 1:首先对图片适当裁剪,先去掉外 ...

  8. 【Android App】在线语音识别功能实现(使用云知声平台与WebSocket 超详细 附源码)

    需要源码和相关资源请点赞关注收藏后评论区留下QQ~~~ 一.在线语音识别 云知声的语音识别同样采用WebSocket接口,待识别的音频流支持MP3和PCM两种格式,对于在线语音识别来说,云知声使用JS ...

  9. 【Android App】实现在线语音合成功能(使用云知声平台和WebSocket 超详细 附源码)

    需要源码和Jar包请点赞关注收藏后评论区留下QQ~~~ 一.在线语音合成 虽然国产智能机大多集成了中文语音引擎,但是系统自带的语音工具无法满足商用要求,功能单一,所以势必引入第三方的语音引擎,依靠第三 ...

最新文章

  1. tensorflow2.0中valid_data的作用是在训练的过程对对比训练数据与测试数据的准确率 损失率,便于判断模型的训练效果:是过拟合还是欠拟合(过拟合)
  2. lua学习:使用Lua处理游戏数据
  3. 算法 - 快速排序(C#)
  4. PowerPoint中的LinkFormat对象
  5. mybatis工具类
  6. 神策数据宣布与微软进行深度合作 共拓大数据生态圈
  7. boost::log模块测试样板,用于检查每个公共标头是否都是独立的并且没有任何缺失的 #includes
  8. Android应用程序的组成部分和Manifest文件(转)
  9. 基于案例贯通 Spark Streaming 流计算框架的运行源码
  10. Incompatible JavaHL library loaded. Subversion 1.8.x required.
  11. C++难吗?好学吗?C++到底怎么样?
  12. 【慢慢学算法】:奇偶校验(bitset使用)
  13. Vue:vue项目npm安装animate.css后动画效果不起作用
  14. database如何管理超过4GB的文件
  15. C语言实现:素数的判断的多种方法
  16. 苹果开场铃声 android,‎App Store 上的“手机铃声制作 - 铃声设置助手大全”
  17. 计算机切换用户界面,win7系统登录界面切换用户的方法
  18. ArcBlock 赴美国华盛顿州议会作证支持区块链立法
  19. 【Mysql 第11章_数据处理之增删改】
  20. luogu3933 Chtholly Nota Seniorious

热门文章

  1. 计算机视觉与图形学-神经渲染专题-神经体渲染:NeRF与Beyond-I
  2. 计算机专业台式机好还是笔记本,科普:台式机显卡和笔记本显卡有啥不同?
  3. ajaxfileupload 文件上传
  4. codeblocks中的输出double数据乱码问题
  5. mysql 中 limit和offset用法
  6. 飞桨PaddleColorization-黑白照片着色
  7. android设置水平布局,Android布局左右对齐在水平布局
  8. 【Web前端】一文带你吃透HTML(上篇)
  9. 科大奥瑞物理实验——AD590温度特性测试与研究
  10. 进程管理工具——Supervisord简介及安装