需要源码请点赞关注收藏后评论区留下QQ~~~
语音通话功能要求实时传输,如果使用MediaRecorder与MediaPlayer组合,那么只能整句话都录完并编码好了才能传给对方去播放,这个时效性太差。
此时用到音频录制器AudioRecord与音轨播放器AudioTrack,该组合的音频格式为原始的二进制音频数据,没有文件头和文件尾,故而可以实现边录边播的实时语音对话
下面是AudioRecord的录音方法
getMinBufferSize 根据采样频率 声道配置音频格式获得合适的缓冲区大小
startRecording 开始录音
read 从缓冲区读取音频数据
stop 停止录音
setNotificationMarkerPosition 设置需要通知的标记位置
setRecordPositionUpdataListener 设置需要通知的时间周期
下面是AudioTrack的播音方法
setStereoVolume 设置立体声的音量
play 开始播音
write 把缓冲区的音频数据写入音轨
实战效果如下
可以在下拉框中选择频率 类型 编码格式等等
连接真机测试更佳

代码如下
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 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 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 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();}}
原始的拖动条十分简陋,我们设计一个全新的控件来实现以下三点功能
1:显示音频的总时长
2:显示音频的已播放时长
3:提供暂停播放与恢复播放功能
完整的播控功能至少包含以下三项
1:关联音频路径与音频控制条
2:控制条实时显示当前播放进度
3:进度条的拖动操作实时传给媒体播放器
效果如下 可实现如下的自动播放暂停 拖动功能 而且更加美观


代码如下
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(); // 释放播放资源}}
创作不易 觉得有帮助请点赞关注收藏~~~
上一篇:如何评价魏晋南北朝的诗歌