五、通过udp实现局域网语音通话(未编码)

目录

一、前言

二、创建udp socket发送接收线程

三、效果展示

四、优化后的版本


一、前言

本部分将AudioRecorder(封装AudioRecord)采集的pcm数据通过局域网传输,并通过局域网接收pcm数据,之后送给AudioPlayer(封装AudioTrack)进行播放。

二、创建udp socket发送接收线程

启动两个线程,一个线程将AudioRecorder采集的pcm数据通过udp socket发送,另一个线程监听udp socket,达到数据后将数据送给AudioPlayer进行播放。

发送线程:

new Thread(new Runnable() {
            @Override
            public void run() {
                final int bufferSize = AudioRecorder.BUFFER_SIZE;
                byte[] buffer = new byte[bufferSize];

                try {
                    // 创建一个数据报套接字对象
                    DatagramSocket socket = new DatagramSocket();
                    // 创建一个InetAddress对象,用于指定目标IP地址
                    InetAddress IPAddress = InetAddress.getByName(remoteIp);

                    while (calling) {
                        int bytesRead = audioRecorder.readRecord(buffer, bufferSize);
                        if(bytesRead > 0){
                            //将数据写到缓存
                            //writeToRecorderBuffer(buffer, bytesRead);
                            // 发送数据
                            // 将要发送的数据打包
                            //String data = "Hello, UDP Server!";
                            //byte[] sendData = data.getBytes();
                            // 创建一个DatagramPacket对象,包含了要发送的信息
                            int numPacket = bytesRead / 1000;
                            for(int i = 0; i < numPacket; i++){
                                byte[] sendData = new byte[1000];
                                System.arraycopy(buffer, i*1000, sendData, 0, 1000);
                                DatagramPacket sendPacket = new DatagramPacket(sendData, 1000, IPAddress, 9999);
                                socket.send(sendPacket);
                            }
                            int remainLength = bytesRead % 1000;
                            if(remainLength > 0){
                                byte[] sendData = new byte[remainLength];
                                System.arraycopy(buffer, numPacket*1000, sendData, 0, remainLength);
                                DatagramPacket sendPacket = new DatagramPacket(sendData, remainLength, IPAddress, 9999);
                                socket.send(sendPacket);
                            }
                        }
                    }
                    // 关闭套接字
                    socket.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();

接收线程:

new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    DatagramSocket socket = new DatagramSocket(9999);
                    byte[] receiveData = new byte[1024];
                    // 创建一个数据包对象,用于接收数据
                    DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
                    while (calling) {
                        // 接收数据
                        socket.receive(receivePacket);
                        byte[] pcmData = receivePacket.getData();
                        if (pcmData != null) {
                            int pcmLength = receivePacket.getLength();
                            audioPlayer.play(pcmData, pcmLength);
                        }

                        //从缓存中获取数据
                        //byte[] pcmData = getFromRecorderBuffer();
                        //if (pcmData != null) {
                        //    audioPlayer.play(pcmData, pcmData.length);
                        //}
                    }
                    socket.close();
                }catch (Exception e){
                    e.printStackTrace();
                }

            }
        }).start();

三、效果展示

四、优化后的版本

将udp socket封装成一个类(UdpIo),并在类内开启发送、接收线程,同时在AudioRecorder和AudioPlayer开启线程进行采集和发送。每个线程的数据出入都有缓存,且通过锁保证线程安全。该方式不是最好的方式,目的只是暂时为了架构清晰一些,实际使用可设计更好的架构。

UdpIo:

package com.example.ffmpegtestprj;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.sql.PreparedStatement;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.ReentrantLock;

public class UdpIo {
    private DatagramSocket socketRecv;
    private DatagramSocket socketSend;
    private String remoteIp = "127.0.0.1";
    private int remotePort = 9999;
    private int localPort = 9999;
    private InetAddress remoteIpAddress;
    private boolean recving = false;
    private boolean sending = false;

    private ReentrantLock queueSendLock = new ReentrantLock();
    private Queue<byte[]> sendQueue = new LinkedList<>();

    private ReentrantLock queueRecvLock = new ReentrantLock();
    private Queue<byte[]> recvQueue = new LinkedList<>();

    private void initReceiver(int port){
        localPort = port;
        try{
            // 创建一个数据报套接字对象
            socketRecv = new DatagramSocket(localPort);
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void initSender(String ip, int port){
        remoteIp = ip;
        remotePort = port;
        try{
            // 创建一个数据报套接字对象
            socketSend = new DatagramSocket();
            // 创建一个InetAddress对象,用于指定目标IP地址
            remoteIpAddress = InetAddress.getByName(remoteIp);
        }catch (Exception e) {
            e.printStackTrace();
        }

    }

    public void startRecving(int port){
        if(recving){
            return;
        }else {
            recving = true;
            initReceiver(port);
        }

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    byte[] receiveData = new byte[1024];
                    // 创建一个数据包对象,用于接收数据
                    DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
                    while (recving) {
                        // 接收数据
                        socketRecv.receive(receivePacket);
                        byte[] data = receivePacket.getData();
                        if (data != null) {
                            int pcmLength = receivePacket.getLength();
                            writeToRecvQueue(data, pcmLength);
                        }
                    }
                    socketRecv.close();
                }catch (Exception e){
                    e.printStackTrace();
                }

            }
        }).start();
    }

    public void stopRecving(){
        recving = false;
    }

    public void startSending(String ip, int port){
        if(sending){
            return;
        }else {
            sending = true;
            initSender(ip, port);
        }

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    while (sending) {
                        //1、从缓存中获取rtp包
                        byte[] dataToSend = getFromSendQueue();
                        if(dataToSend != null){
                            if(dataToSend.length > 1000){
                                int pktNum = dataToSend.length / 1000;
                                int remainDataLength = dataToSend.length % 1000;
                                for(int i = 0; i < pktNum; ++i){
                                    byte[] tmpData = new byte[1000];
                                    System.arraycopy(dataToSend, i * 1000, tmpData, 0, 1000);
                                    DatagramPacket sendPacket = new DatagramPacket(tmpData, tmpData.length, remoteIpAddress, remotePort);
                                    socketSend.send(sendPacket);
                                }

                                if(remainDataLength > 0){
                                    byte[] tmpData = new byte[1000];
                                    System.arraycopy(dataToSend, pktNum * 1000, tmpData, 0, remainDataLength);
                                    DatagramPacket sendPacket = new DatagramPacket(tmpData, tmpData.length, remoteIpAddress, remotePort);
                                    socketSend.send(sendPacket);
                                }
                            }
                            else{
                                // 创建一个DatagramPacket对象,包含了要发送的信息
                                DatagramPacket sendPacket = new DatagramPacket(dataToSend, dataToSend.length, remoteIpAddress, remotePort);
                                socketSend.send(sendPacket);
                            }

                        }
                    }
                    // 关闭套接字
                    socketSend.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    public void stopSending(){
        sending = false;
    }

    private void writeToRecvQueue(byte[] data, int length){
        //锁
        byte[] dataToSave = new byte[length];
        System.arraycopy(data, 0, dataToSave, 0, length);

        queueRecvLock.lock();
        recvQueue.offer(dataToSave);
        queueRecvLock.unlock();
    }
    public byte[] getFromRecvQueue(){
        //锁
        queueRecvLock.lock();
        if(!recvQueue.isEmpty()){
            byte[] data = recvQueue.poll();
            queueRecvLock.unlock();
            return data;
        }
        queueRecvLock.unlock();
        return null;
    }

    public void writeToSendQueue(byte[] data){
        //锁
        queueSendLock.lock();
        sendQueue.offer(data);
        queueSendLock.unlock();
    }
    private byte[] getFromSendQueue(){
        //锁
        queueSendLock.lock();
        if(!sendQueue.isEmpty()){
            byte[] data = sendQueue.poll();
            queueSendLock.unlock();
            return data;
        }
        queueSendLock.unlock();
        return null;
    }
}

AudioRecorder:

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;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.ReentrantLock;

public class AudioRecorder {
    private ReentrantLock pcmQueueLock = new ReentrantLock();
    private Queue<byte[]> pcmQueue = new LinkedList<>();
    private boolean recording = false;
    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);
    private void initRecording() {
        audioRecord = new AudioRecord(
                MediaRecorder.AudioSource.MIC,
                SAMPLE_RATE,
                CHANNEL_CONFIG,
                AUDIO_FORMAT,
                BUFFER_SIZE);

        if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
            Log.e(TAG, "音频录制器初始化失败");
            return;
        }
    }

    private void startRecording() {
        audioRecord.startRecording();
    }

    public void start(){
        if(!recording){
            recording = true;
        }else{
            return;
        }
        initRecording();
        startRecording();
        new Thread(new Runnable() {
            @Override
            public void run() {
                final int bufferSize = AudioRecorder.BUFFER_SIZE;
                try {
                    while (recording) {
                        byte[] buffer = new byte[bufferSize];
                        int bytesRead = audioRecord.read(buffer, 0, bufferSize);
                        if(bytesRead > 0){
                            //将数据写到缓存
                            writeToRecorderBuffer(buffer, bytesRead);
                        }
                        //睡眠10ms
                        //Thread.sleep(10);
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    public void stop() {
        if(recording){
            recording = false;
            audioRecord.stop();
            audioRecord.release();
        }
    }

    private void writeToRecorderBuffer(byte[] buffer, int size){
        byte[] pcmData = new byte[size];
        System.arraycopy(buffer, 0, pcmData, 0, size);
        //锁
        pcmQueueLock.lock();
        pcmQueue.offer(pcmData);
        pcmQueueLock.unlock();
    }

    public byte[] getFromRecorderBuffer() {
        //锁
        pcmQueueLock.lock();
        if(!pcmQueue.isEmpty()){
            byte[] pcmData = pcmQueue.poll();
            pcmQueueLock.unlock();
            return pcmData;
        }
        pcmQueueLock.unlock();
        return null;
    }
}

AudioPlayer:

package com.example.ffmpegtestprj;

import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.media.MediaRecorder;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.ReentrantLock;

public class AudioPlayer {
    private boolean inited = false;
    private boolean playing = false;
    private ReentrantLock pcmQueueLock = new ReentrantLock();
    private Queue<byte[]> pcmQueue = new LinkedList<>();
    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;
    private 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(!inited){
            inited = true;
            init();
        }

        if (audioTrack.getState() != AudioTrack.STATE_UNINITIALIZED) {
            audioTrack.play();
        }

        if(!playing){
            playing = true;
        }else{
            return;
        }

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (playing) {
                    try {
                        byte[] pcmData = getFromRecorderBuffer();
                        if (pcmData != null) {
                            play(pcmData, pcmData.length);
                        }
                        //Thread.sleep(10);//睡眠10ms
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

    public void stop() {
        playing = false;
        try {
            if (audioTrack.getState() != AudioTrack.STATE_UNINITIALIZED) {
                audioTrack.stop();
                audioTrack.release();
                inited = false;
            }
        } catch (IllegalStateException e) {
            e.printStackTrace();
        }
    }

    public void writeToRecorderBuffer(byte[] buffer, int size){
        byte[] pcmData = new byte[size];
        System.arraycopy(buffer, 0, pcmData, 0, size);
        //锁
        pcmQueueLock.lock();
        pcmQueue.offer(pcmData);
        pcmQueueLock.unlock();
    }

    private byte[] getFromRecorderBuffer() {
        //锁
        pcmQueueLock.lock();
        if(!pcmQueue.isEmpty()){
            byte[] pcmData = pcmQueue.poll();
            pcmQueueLock.unlock();
            return pcmData;
        }
        pcmQueueLock.unlock();
        return null;
    }

    private void play(byte[] buffer, int size){
        if(size > 0 ) {
            audioTrack.write(buffer, 0, size);
        }
    }
}

MainActivity:

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.EditText;
import android.widget.TextView;
import android.view.View;

import com.example.ffmpegtestprj.databinding.ActivityMainBinding;

import java.io.File;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
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 UdpIo udpIo;
    private boolean calling = false;
    EditText ipText;
    private String remoteIp = "127.0.0.1";

    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();
        udpIo = new UdpIo();
    }

    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){
            ipText = findViewById(R.id.editText);
            remoteIp = ipText.getText().toString();
            startCall(remoteIp, 9999);
        }
        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(String ip, int port){
        if(calling == true) {
            return;
        }
        calling = true;
        audioPlayer.start();
        udpIo.startSending(ip, port);
        udpIo.startRecving(port);
        audioRecorder.start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                final int bufferSize = AudioRecorder.BUFFER_SIZE;
                try {
                    while (calling) {
                        byte[] pcmRecord = audioRecorder.getFromRecorderBuffer();
                        if(pcmRecord != null){
                            udpIo.writeToSendQueue(pcmRecord);
                        }

                        byte[] pcmRecv = udpIo.getFromRecvQueue();
                        if(pcmRecv != null){
                            audioPlayer.writeToRecorderBuffer(pcmRecv, pcmRecv.length);
                        }
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    private void stopCall(){
        if(calling == true) {
            calling = false;
            audioRecorder.stop();
            udpIo.stopRecving();
            udpIo.stopSending();
            audioPlayer.stop();
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值