我正在开发一个基于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)
已使用,但显然它不是在这个版本
1条答案
按热度按时间rvpgvaaj1#
要保存本地站点音频,可以在创建AudioDeviceModule时使用setSamplesReadyCallback
示例代码在这里。
但是WebRTC不支持录制远程站点音频,你可以自己实现。