目录
一、前言
想要通过网络进行语音通话,首先需要进行语音采集和播放。这里直接使用AudioRecord、AudioTrack。
二、封装AudioRecord
为了方便使用AudioRecord,这里封装一下AudioRecord:
package com.example.ffmpegtestprj;
import android.content.pm.PackageManager;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.util.Log;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.io.IOException;
public class AudioRecorder {
private AudioRecord audioRecord;
private static final String TAG = "AudioCapture";
private static final int PERMISSION_REQUEST_CODE = 200;
private static final int SAMPLE_RATE = 44100;
private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO;
private static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
public static final int BUFFER_SIZE = AudioRecord.getMinBufferSize(
SAMPLE_RATE,
CHANNEL_CONFIG,
AUDIO_FORMAT);
public void init() {
audioRecord = new AudioRecord(
MediaRecorder.AudioSource.MIC,
SAMPLE_RATE,
CHANNEL_CONFIG,
AUDIO_FORMAT,
BUFFER_SIZE);
if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
Log.e(TAG, "音频录制器初始化失败");
return;
}
}
public void start() {
audioRecord.startRecording();
}
public void stop() {
audioRecord.stop();
audioRecord.release();
}
public int readRecord(byte[] buffer, int bufferSize){
int bytesRead = audioRecord.read(buffer, 0, bufferSize);
return bytesRead;
}
}
三、封装AudioTrack
同样,封装一下AudioTrack:
package com.example.ffmpegtestprj;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import java.io.IOException;
public class AudioPlayer {
private int sampleRateInHz = 44100;
private int channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
private int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
private int bufferSizeInBytes = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
private AudioTrack audioTrack;
public void init() {
audioTrack = new AudioTrack(
AudioManager.STREAM_MUSIC, // 指定音频流类型
sampleRateInHz, // 采样率,例如44100Hz
channelConfig, // 声道配置,例如AudioFormat.CHANNEL_OUT_MONO
audioFormat, // 数据格式,例如AudioFormat.ENCODING_PCM_16BIT
bufferSizeInBytes, // 缓冲区大小,例如1024字节
AudioTrack.MODE_STREAM // 播放模式,使用流模式**
);
}
public void start() {
if ( audioTrack.getState() != AudioTrack.STATE_UNINITIALIZED) {
audioTrack.play();
}
}
public void play(byte[] buffer, int size){
if(size > 0) {
audioTrack.write(buffer, 0, size);
}
}
public void stop() {
try {
if (audioTrack.getState() != AudioTrack.STATE_UNINITIALIZED) {
audioTrack.stop();
audioTrack.release();
}
} catch (IllegalStateException e) {
e.printStackTrace();
}
}
}
四、开启线程进行语音采集和播放
package com.example.ffmpegtestprj;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
import android.view.View;
import com.example.ffmpegtestprj.databinding.ActivityMainBinding;
import java.io.File;
import java.sql.Struct;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.ReentrantLock;
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
// Used to load the 'ffmpegtestprj' library on application startup.
static {
System.loadLibrary("ffmpegtestprj");
}
/**
* A native method that is implemented by the 'ffmpegtestprj' native library,
* which is packaged with this application.
*/
public native String fetchPcmFromMp4();
public native int getPcm(byte[] data, int tmpPcmLen);
public native void mp4ToMp3(String url);
private ActivityMainBinding binding;
private AudioRecorder audioRecorder;
private AudioPlayer audioPlayer;
private boolean calling = false;
private ReentrantLock queueLock = new ReentrantLock();
private Queue<byte[]> pcmQueue = new LinkedList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
initDevice();
initView();
}
private void initDevice(){
audioRecorder = new AudioRecorder();
audioPlayer = new AudioPlayer();
}
private void initView(){
//初始化控件
Button btnDecode = (Button) findViewById(R.id.btnGetPcm);
Button btnPlay = (Button) findViewById(R.id.btnPlayPcm);
Button btnCall = (Button) findViewById(R.id.btnCall);
Button btnHangUp = (Button) findViewById(R.id.btnHangUp);
//注册按钮事件监听器
btnDecode.setOnClickListener(this);
btnPlay.setOnClickListener(this);
btnCall.setOnClickListener(this);
btnHangUp.setOnClickListener(this);
}
@Override
public void onClick(View view) {
if(view.getId() == R.id.btnGetPcm){
decodeAudio();
}
if(view.getId() == R.id.btnPlayPcm){
startPlayPcm();
}
if(view.getId() == R.id.btnCall){
startCall();
}
if(view.getId() == R.id.btnHangUp){
stopCall();
}
}
private void decodeAudio(){
AlertDialog.Builder dialog = new AlertDialog.Builder(MainActivity.this);
dialog.setTitle("HINT!!!");
String str = fetchPcmFromMp4();
dialog.setMessage("find mp4 file OK, get audio:"+str);
dialog.show();
}
private void startPlayPcm(){
int sampleRateInHz = 44100;
int channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
int bufferSizeInBytes = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
AudioTrack audioTrack = new AudioTrack(
AudioManager.STREAM_MUSIC, // 指定音频流类型
sampleRateInHz, // 采样率,例如44100Hz
channelConfig, // 声道配置,例如AudioFormat.CHANNEL_OUT_MONO
audioFormat, // 数据格式,例如AudioFormat.ENCODING_PCM_16BIT
bufferSizeInBytes, // 缓冲区大小,例如1024字节
AudioTrack.MODE_STREAM // 播放模式,使用流模式**
);
if ( audioTrack.getState() != AudioTrack.STATE_UNINITIALIZED) {
audioTrack.play();
}
byte[] data = new byte[bufferSizeInBytes];
while(true){
int pcmLen = getPcm(data,bufferSizeInBytes);
if(pcmLen > 0){
audioTrack.write(data, 0, pcmLen);
}else{
break;
}
}
if (audioTrack.getState() != AudioTrack.STATE_UNINITIALIZED) {
audioTrack.stop();
audioTrack.release();
}
}
private void startCall(){
if(calling == true) {
return;
}
audioRecorder.init();
audioPlayer.init();
audioRecorder.start();
audioPlayer.start();
calling = true;
new Thread(new Runnable() {
@Override
public void run() {
final int bufferSize = AudioRecorder.BUFFER_SIZE;
byte[] buffer = new byte[bufferSize];
while (calling) {
int bytesRead = audioRecorder.readRecord(buffer, bufferSize);
//将数据写到缓存
if(bytesRead > 0){
writeToRecorderBuffer(buffer, bytesRead);
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (calling) {
byte[] pcmData = getFromRecorderBuffer();
if(pcmData != null){
audioPlayer.play(pcmData, pcmData.length);
}
}
}
}).start();
}
private void stopCall(){
if(calling == true) {
calling = false;
audioPlayer.stop();
audioRecorder.stop();
}
}
private void writeToRecorderBuffer(byte[] buffer, int size){
byte[] pcmData = new byte[size];
System.arraycopy(buffer, 0, pcmData, 0, size);
//锁
queueLock.lock();
pcmQueue.offer(pcmData);
queueLock.unlock();
}
public byte[] getFromRecorderBuffer() {
//锁
queueLock.lock();
if(!pcmQueue.isEmpty()){
byte[] pcmData = pcmQueue.poll();
queueLock.unlock();
return pcmData;
}
queueLock.unlock();
return null;
}
}