四、使用AudioRecord、AudioTrack进行采集和播放

目录

一、前言

二、封装AudioRecord

三、封装AudioTrack

四、开启线程进行语音采集和播放


一、前言

想要通过网络进行语音通话,首先需要进行语音采集和播放。这里直接使用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;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值