如何在Android中使用webrtc保存视频和音频文件

dwbf0jvd  于 2023-04-10  发布在  Android
关注(0)|答案(1)|浏览(1522)

我正在开发一个基于WebRTC的视频聊天应用程序,目前视频通话正在工作,但我想在客户端记录通话;
我用的是webrc的这个版本
implementation 'org.webrtc:google-webrtc:1.0.32006'
在Android端,使用这个类:

import static org.webrtc.SessionDescription.Type.ANSWER;
import static org.webrtc.SessionDescription.Type.OFFER;
import static io.socket.client.Socket.EVENT_CONNECT;
import static io.socket.client.Socket.EVENT_CONNECT_ERROR;
import static io.socket.client.Socket.EVENT_DISCONNECT;

import android.Manifest;
import android.app.Activity;
import android.media.MediaRecorder;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.util.Log;

import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;

import com.google.firebase.firestore.FirebaseFirestore;
import com.google.gson.Gson;

import org.json.JSONException;
import org.json.JSONObject;
import org.webrtc.AudioSource;
import org.webrtc.AudioTrack;
import org.webrtc.Camera1Enumerator;
import org.webrtc.Camera2Enumerator;
import org.webrtc.CameraEnumerator;
import org.webrtc.DataChannel;
import org.webrtc.EglBase;
import org.webrtc.EglRenderer;
import org.webrtc.IceCandidate;
import org.webrtc.MediaConstraints;
import org.webrtc.MediaStream;
import org.webrtc.PeerConnection;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.SessionDescription;
import org.webrtc.SurfaceViewRenderer;
import org.webrtc.VideoCapturer;
import org.webrtc.VideoRenderer;
import org.webrtc.VideoSource;
import org.webrtc.VideoTrack;

import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;

import io.socket.client.IO;
import io.socket.client.Socket;
import ir.dehkade.markazi.core.Models.AppSetting;
import ir.dehkade.markazi.core.Models.socketModel;
import ir.dehkade.markazi.core.datarecourd.MoveEncoder;
import ir.dehkade.markazi.core.datasource.Room.Entity.SettingData;
import ir.dehkade.markazi.interfaceclass.socketcallback;
import pub.devrel.easypermissions.EasyPermissions;

public class mSocket {

    private static final String TAG = "my Socket";
    private static final int RC_CALL = 111;
    public static final String VIDEO_TRACK_ID = "ARDAMSv0";
    public static final int VIDEO_RESOLUTION_WIDTH = 1280;
    public static final int VIDEO_RESOLUTION_HEIGHT = 720;
    public static final int FPS = 30;

    private static Socket socket;
    private EglBase rootEglBase;
    private static mSocket Msoket;
    public Socket socket()
    {
        return socket;
    }
    private boolean isInitiator=true;
    private boolean isChannelReady=true;
    public boolean isUserRinging=true;
    private boolean isStarted;

    MoveEncoder recording = null;
    SurfaceViewRenderer surfaceView,surfaceView2;
    MediaConstraints audioConstraints;
  //  MediaConstraints videoConstraints;
  //  MediaConstraints sdpConstraints;
 //   VideoSource videoSource;
 //   VideoTrack localVideoTrack;
    AudioSource audioSource;
    AudioTrack localAudioTrack;

    private PeerConnection peerConnection;

    private PeerConnectionFactory factory;
    private VideoTrack videoTrackFromCamera;

    public   boolean ringFromOther=false;
    AppSetting settingData ;
    //Firestore
 //   FirebaseFirestore db = FirebaseFirestore.getInstance();
    AppCompatActivity activity ;
    socketcallback call;
    public  static mSocket getInstance()
    {
        return Msoket;
    }
    public mSocket()
    {

    }
    public mSocket(AppCompatActivity activity,
                   SurfaceViewRenderer surfaceView,
                   SurfaceViewRenderer surfaceView2,
                   EglBase eglBase,
                   socketcallback call, AppSetting sd)
    {
        settingData=sd;
        this.call = call;
        this.activity = activity;
        this.surfaceView = surfaceView2;
        this.surfaceView2 = surfaceView;
        this. rootEglBase = eglBase;
        start(activity);
        Msoket = this;
    }

    private void start(Activity activityCompat) {

        String[] perms = {Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO};
        if (EasyPermissions.hasPermissions(activityCompat, perms)) {

            connectToSignallingServer();

        } else {
            EasyPermissions.requestPermissions(activityCompat, "Need some permissions", RC_CALL, perms);
        }
    }

    public   void startcall()
    {

        initializePeerConnectionFactory();

        createVideoTrackFromCameraAndShowIt(activity);

        initializePeerConnections();

        startStreamingVideo();


    }

    private void connectToSignallingServer() {
        try {
           
            // $ hostname -I
            String URL = "http://192.168.1.39:3030/";
            Log.e(TAG, "REPLACE ME: IO Socket:" + URL);
            IO.Options ops = new IO.Options();

            socket = IO.socket(URL);

            socket.on(EVENT_CONNECT, args -> {
                Log.d(TAG, "connectToSignallingServer: connect");
                socket.emit("create or join", "kavosh"+settingData.MyDeviceID);
                socket.emit("Create or join ward", settingData.WardName);
            }).on(EVENT_CONNECT_ERROR,args -> {
                Log.d(TAG, "connectToSignallingServer: connect");
            }).on("bye", args -> {
                handleRemoteHangup();
                Log.d(TAG, "connectToSignallingServer: ipaddr");
            }).on("ipaddr", args -> {
                Log.d(TAG, "connectToSignallingServer: ipaddr");
            }).on("created", args -> {
                Log.d(TAG, "connectToSignallingServer: created");
                isInitiator = true;
            }).on("full", args -> {
                Log.d(TAG, "connectToSignallingServer: full");
            }).on("join", args -> {
                Log.d(TAG, "connectToSignallingServer: join");
                Log.d(TAG, "connectToSignallingServer: Another peer made a request to join room");
                Log.d(TAG, "connectToSignallingServer: This peer is the initiator of room");
                isChannelReady = true;
            }).on("joined", args -> {
                Log.d(TAG, "connectToSignallingServer: joined");
                isChannelReady = true;
            }).on("log", args -> {
                for (Object arg : args) {
                    Log.d(TAG, "connectToSignallingServer: " + String.valueOf(arg));
                }
            }).on("ring",args -> {
             
                Gson js = new Gson();
                socketModel x = js.fromJson((String) args[0],socketModel.class);
                call.ONRinging(x);
                //isUserRinging = true;
            }).on("message", args -> {
                Log.d(TAG, "connectToSignallingServer: got a message");
            }).on("message", args -> {
                try {
                    if (args[0] instanceof String) {
                        String message = (String) args[0];
                        if (message.equals("got user media")) {
                            maybeStart();
                        }
                    } else {
                        JSONObject message = (JSONObject) args[0];
                        Log.d(TAG, "connectToSignallingServer: got message " + message);
                        if (message.getString("type").equals("offer")) {
                            Log.d(TAG, "connectToSignallingServer: received an offer " + isInitiator + " " + isStarted);
                            if (!isInitiator && !isStarted) {
                                maybeStart();
                            }
                            peerConnection.setRemoteDescription(new SimpleSdpObserver(), new SessionDescription(OFFER, message.getString("sdp")));
                            doAnswer();
                        } else if (message.getString("type").equals("answer") && isStarted) {
                            peerConnection.setRemoteDescription(new SimpleSdpObserver(), new SessionDescription(ANSWER, message.getString("sdp")));
                        } else if (message.getString("type").equals("candidate") && isStarted) {
                            Log.d(TAG, "connectToSignallingServer: receiving candidates");
                            IceCandidate candidate = new IceCandidate(message.getString("id"), message.getInt("label"), message.getString("candidate"));
                            peerConnection.addIceCandidate(candidate);
                        }
                        // else if (message.equals("bye") && isStarted) {
                        // handleRemoteHangup();
                        // }
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }).on(EVENT_DISCONNECT, args -> {
                Log.d(TAG, "connectToSignallingServer: disconnect");
            });
            socket.connect();
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
    }

    public void handleRemoteHangup() {
        peerConnection.close();
        call.OnBye();
       // this.finish();
    }

    //MirtDPM4
    private void doAnswer() {
        peerConnection.createAnswer(new SimpleSdpObserver() {
            @Override
            public void onCreateSuccess(SessionDescription sessionDescription) {
                peerConnection.setLocalDescription(new SimpleSdpObserver(), sessionDescription);
                JSONObject message = new JSONObject();
                try {
                    message.put("type", "answer");
                    message.put("sdp", sessionDescription.description);
                    sendMessage(message);
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        }, new MediaConstraints());
    }

    private void maybeStart() {
        Log.d(TAG, "maybeStart: " + isStarted + " " + isChannelReady);
        if (!isStarted && isChannelReady & isUserRinging) {
            isStarted = true;
            if (isInitiator) {
                doCall();
            }
        }
    }

    private void doCall() {
        MediaConstraints sdpMediaConstraints = new MediaConstraints();

        sdpMediaConstraints.mandatory.add(
                new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));
        sdpMediaConstraints.mandatory.add(
                new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));
        peerConnection.createOffer(new SimpleSdpObserver() {
            @Override
            public void onCreateSuccess(SessionDescription sessionDescription) {
                Log.d(TAG, "onCreateSuccess: ");
                peerConnection.setLocalDescription(new SimpleSdpObserver(), sessionDescription);
                JSONObject message = new JSONObject();
                try {
                    message.put("type", "offer");
                    message.put("sdp", sessionDescription.description);
                    sendMessage(message);
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        }, sdpMediaConstraints);
    }

    private void sendMessage(Object message) {
        socket.emit("message", message);
    }


    private void initializePeerConnectionFactory() {
        PeerConnectionFactory.initializeAndroidGlobals(activity, true, true, true);
        factory = new PeerConnectionFactory(null);
        factory.setVideoHwAccelerationOptions(rootEglBase.getEglBaseContext(), rootEglBase.getEglBaseContext());

    }

    private void createVideoTrackFromCameraAndShowIt(Activity activity) {
        audioConstraints = new MediaConstraints();
        VideoCapturer videoCapturer = createVideoCapturer(activity);
        VideoSource videoSource = factory.createVideoSource(videoCapturer);
        videoCapturer.startCapture(VIDEO_RESOLUTION_WIDTH, VIDEO_RESOLUTION_HEIGHT, FPS);

        videoTrackFromCamera = factory.createVideoTrack(VIDEO_TRACK_ID, videoSource);
        videoTrackFromCamera.setEnabled(true);
        videoTrackFromCamera.addRenderer(new VideoRenderer(surfaceView));

        //create an AudioSource instance
        audioSource = factory.createAudioSource(audioConstraints);
        localAudioTrack = factory.createAudioTrack("101", audioSource);

    }

    private void initializePeerConnections() {
        peerConnection = createPeerConnection(factory);

    }
private void SaveAudio(AudioTrack input,AudioTrack outPut)
{

}
    private void startStreamingVideo() {
        MediaStream mediaStream = factory.createLocalMediaStream("ARDAMS");
        mediaStream.addTrack(videoTrackFromCamera);
        mediaStream.addTrack(localAudioTrack);

        peerConnection.addStream(mediaStream);

        sendMessage("got user media");
    }

    private PeerConnection createPeerConnection(PeerConnectionFactory factory) {
        ArrayList<PeerConnection.IceServer> iceServers = new ArrayList<>();
        String URL = "stun:stun.l.google.com:19302";
        iceServers.add(new PeerConnection.IceServer(URL));

        PeerConnection.RTCConfiguration rtcConfig = new PeerConnection.RTCConfiguration(iceServers);
        MediaConstraints pcConstraints = new MediaConstraints();

        PeerConnection.Observer pcObserver = new PeerConnection.Observer() {
            @Override
            public void onSignalingChange(PeerConnection.SignalingState signalingState) {
                Log.d(TAG, "onSignalingChange: ");
            }

            @Override
            public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
                Log.d(TAG, "onIceConnectionChange: ");
            }

            @Override
            public void onIceConnectionReceivingChange(boolean b) {
                Log.d(TAG, "onIceConnectionReceivingChange: ");
            }

            @Override
            public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
                Log.d(TAG, "onIceGatheringChange: ");
            }

            @Override
            public void onIceCandidate(IceCandidate iceCandidate) {
                Log.d(TAG, "onIceCandidate: ");
                JSONObject message = new JSONObject();

                try {
                    message.put("type", "candidate");
                    message.put("label", iceCandidate.sdpMLineIndex);
                    message.put("id", iceCandidate.sdpMid);
                    message.put("candidate", iceCandidate.sdp);

                    Log.d(TAG, "onIceCandidate: sending candidate " + message);
                    sendMessage(message);
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onIceCandidatesRemoved(IceCandidate[] iceCandidates) {
                Log.d(TAG, "onIceCandidatesRemoved: ");
            }

            @Override
            public void onAddStream(MediaStream mediaStream) {
                Log.d(TAG, "onAddStream: " + mediaStream.videoTracks.size());
                VideoTrack remoteVideoTrack = mediaStream.videoTracks.get(0);
                AudioTrack remoteAudioTrack = mediaStream.audioTracks.get(0);
                remoteAudioTrack.setEnabled(true);
                remoteVideoTrack.setEnabled(true);
                remoteVideoTrack.addRenderer(new VideoRenderer(surfaceView2));

            }

            @Override
            public void onRemoveStream(MediaStream mediaStream) {
                Log.d(TAG, "onRemoveStream: ");
            }

            @Override
            public void onDataChannel(DataChannel dataChannel) {
                Log.d(TAG, "onDataChannel: ");
            }

            @Override
            public void onRenegotiationNeeded() {
                Log.d(TAG, "onRenegotiationNeeded: ");
            }
        };

        return factory.createPeerConnection(rtcConfig, pcConstraints, pcObserver);
    }

    private VideoCapturer createVideoCapturer(Activity activity) {
        VideoCapturer videoCapturer;
        if (useCamera2(activity)) {
            videoCapturer = createCameraCapturer(new Camera2Enumerator(activity));
        } else {
            videoCapturer = createCameraCapturer(new Camera1Enumerator(true));
        }
        return videoCapturer;
    }

    private VideoCapturer createCameraCapturer(CameraEnumerator enumerator) {
        final String[] deviceNames = enumerator.getDeviceNames();

        for (String deviceName : deviceNames) {
            if (enumerator.isFrontFacing(deviceName)) {
                VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);

                if (videoCapturer != null) {
                    return videoCapturer;
                }
            }
        }

        for (String deviceName : deviceNames) {
            if (!enumerator.isFrontFacing(deviceName)) {
                VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);

                if (videoCapturer != null) {
                    return videoCapturer;
                }
            }
        }

        return null;
    }

    private boolean useCamera2(Activity activity) {
        return Camera2Enumerator.isSupported(activity);
    }

}

服务器在<node.js>

'use strict';

var os = require('os');
var nodeStatic = require('node-static');
var http = require('http');
var socketIO = require('socket.io');
const { Console } = require('console');
const port = process.env.PORT || 3030;

var fileServer = new(nodeStatic.Server)();
var app = http.createServer(function(req, res) {
  fileServer.serve(req, res);
}).listen(port);

var io = socketIO.listen(app);
io.sockets.on('connection', function(socket) {
console.log("user connected")
  // convenience function to log server messages on the client
  function log() {
    var array = ['Message from server:'];
    array.push.apply(array, arguments);
    socket.emit('log', array);
  }

  socket.on('ring', function(data) {
   console.log('Client ring ');
   socket.broadcast.emit('ring', data);
    // for a real app, would be room-only (not broadcast)
    //socket.broadcast.in(ward).emit('Clientring', data);
  });

  socket.on('status', function(data) {
    console.log('status ');
    socket.broadcast.emit('status', data);
     // for a real app, would be room-only (not broadcast)
     //socket.broadcast.in(ward).emit('Clientring', data);
   });

   socket.on('code', function(data) {
    console.log('code ');
    socket.broadcast.emit('code', data);
     // for a real app, would be room-only (not broadcast)
     //socket.broadcast.in(ward).emit('Clientring', data);
   });
   
  
  socket.on('message', function(message) {
    log('Client said: ', message);
    // for a real app, would be room-only (not broadcast)
    //io.sockets.connected["socketid"].emit("message")
    socket.broadcast.emit('message', message);
  });

  socket.on('Create or join ward',function(ward)
  {
    var clientsInRoom = io.sockets.adapter.rooms[ward];
    var numClients = clientsInRoom ? Object.keys(clientsInRoom.sockets).length : 0;
    if (numClients === 0) {
      socket.join(ward);
      log('Client ID ' + socket.id + ' created room ' + ward);
      socket.emit('wardcreated', ward, socket.id);
      console.log('wardcreated',ward,socket.id)

    } else if (numClients < 50) {
      log('Client ID ' + socket.id + ' joined room ' + room);
      io.sockets.in(ward).emit('joinward', ward);
      socket.join(ward);
      socket.emit('joined', ward, socket.id);
      io.sockets.in(ward).emit('ready');
      
      console.log('joined',ward,socket.id)
    } else { // max two clients
      socket.emit('full', ward);
    }

  })

  socket.on('create or join', function(room) {
    
    log('Received request to create or join room ' + room);

    var clientsInRoom = io.sockets.adapter.rooms[room];
    var numClients = clientsInRoom ? Object.keys(clientsInRoom.sockets).length : 0;
    log('Room ' + room + ' now has ' + numClients + ' client(s)');

    if (numClients === 0) {
      socket.join(room);
      log('Client ID ' + socket.id + ' created room ' + room);
      socket.emit('created', room, socket.id);
      console.log('created',room,socket.id)

    } else if (numClients === 1) {
      log('Client ID ' + socket.id + ' joined room ' + room);
      io.sockets.in(room).emit('join', room);
      socket.join(room);
      socket.emit('joined', room, socket.id);
      io.sockets.in(room).emit('ready');
      
      console.log('joined',room,socket.id)
    } else { // max two clients
      socket.emit('full', room);
    }
  });
  socket.on('disconnect', function()
  {
    console.log('Disconnected from:',  socket.id);
  });
  socket.on('ipaddr', function() {
    var ifaces = os.networkInterfaces();
    for (var dev in ifaces) {
      ifaces[dev].forEach(function(details) {
        if (details.family === 'IPv4' && details.address !== '127.0.0.1' && details.address !== '192.168.1.39') {
          socket.emit('ipaddr', details.address);
        }
      });
    }
  });

  socket.on('bye', function(data){
    console.log('received bye');
    socket.broadcast.emit('bye',data);
  });

});

这些代码工作正常。但是我不知道如何在客户端保存对话的语音,这是一个Android应用程序。有人有什么建议吗?
我看到的方式,从stream.videoTracks.get(0).addSink(remoteRenderer)已使用,但显然它不是在这个版本

rvpgvaaj

rvpgvaaj1#

要保存本地站点音频,可以在创建AudioDeviceModule时使用setSamplesReadyCallback

AudioDeviceModule adm = 
JavaAudioDeviceModule.builder(appContext)
        .setSamplesReadyCallback(saveRecordedAudioToFile)
        .createAudioDeviceModule();

factory = PeerConnectionFactory.builder()
                  .setOptions(options)
                  .setAudioDeviceModule(adm) 
                  .createPeerConnectionFactory();

示例代码在这里。
但是WebRTC不支持录制远程站点音频,你可以自己实现。

相关问题