From b000825fc49bcc7b070956c8a8f7f1be90e96149 Mon Sep 17 00:00:00 2001 From: Deepak Rathi Date: Tue, 25 Nov 2014 03:31:26 -0800 Subject: [PATCH 1/2] Audio Call module created Made changes in WebRtcClient to add setAudio function for audio call Tested audio call successfully --- AndroidManifest.xml | 1 + project.properties | 2 +- src/deepak/rathi/RTCAudioActivity.java | 165 ++++++++++++++++++++++ src/fr/pchab/AndroidRTC/RTCActivity.java | 3 +- src/fr/pchab/AndroidRTC/WebRtcClient.java | 65 ++++++++- 5 files changed, 230 insertions(+), 6 deletions(-) create mode 100644 src/deepak/rathi/RTCAudioActivity.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index a1a7ccd..60a0a46 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -34,4 +34,5 @@ + diff --git a/project.properties b/project.properties index 6d7a78e..1086562 100644 --- a/project.properties +++ b/project.properties @@ -1,3 +1,3 @@ # This file is automatically generated by IntelliJ IDEA # Project target. -target=android-17 \ No newline at end of file +target=android-19 diff --git a/src/deepak/rathi/RTCAudioActivity.java b/src/deepak/rathi/RTCAudioActivity.java new file mode 100644 index 0000000..d4242c9 --- /dev/null +++ b/src/deepak/rathi/RTCAudioActivity.java @@ -0,0 +1,165 @@ +package deepak.rathi; + +import fr.pchab.AndroidRTC.R; +import fr.pchab.AndroidRTC.VideoStreamsView; +import fr.pchab.AndroidRTC.WebRtcClient; +import android.app.Activity; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.graphics.Point; +import android.os.Bundle; +import android.view.Window; +import android.widget.Toast; +import org.json.JSONException; +import org.webrtc.MediaStream; +import org.webrtc.PeerConnectionFactory; +//import org.webrtc.VideoRenderer; + +import java.util.List; + +public class RTCAudioActivity extends Activity implements WebRtcClient.RTCListener{ + private final static int AUDIO_CALL_SENT = 666; + private VideoStreamsView vsv; + private WebRtcClient client; + private String mSocketAddress; + private String callerId; + + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_NO_TITLE); + mSocketAddress = "http://" + getResources().getString(R.string.host); + mSocketAddress += (":"+getResources().getString(R.string.port)+"/"); + + PeerConnectionFactory.initializeAndroidGlobals(this); + + // Camera display view + Point displaySize = new Point(); + getWindowManager().getDefaultDisplay().getSize(displaySize); + vsv = new VideoStreamsView(this, displaySize); + client = new WebRtcClient(this, mSocketAddress); + + final Intent intent = getIntent(); + final String action = intent.getAction(); + + if (Intent.ACTION_VIEW.equals(action)) { + final List segments = intent.getData().getPathSegments(); + callerId = segments.get(0); + //callerId = "R9lEjFqo4sb1ZbeM9r0i"; static room id for testing + } + } + + public void onConfigurationChanged(Configuration newConfig) + { + super.onConfigurationChanged(newConfig); + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + } + + @Override + public void onPause() { + super.onPause(); + vsv.onPause(); + } + + @Override + public void onResume() { + super.onResume(); + vsv.onResume(); + } + + @Override + public void onCallReady(String callId) { + //callerId = "R9lEjFqo4sb1ZbeM9r0i"; a static room id for testing + if(callerId != null) { + try { + answer(callerId); + } catch (JSONException e) { + e.printStackTrace(); + } + } else { + call(callId); + } + } + + public void answer(String callerId) throws JSONException { + client.sendMessage(callerId, "init", null); + startCam(); + } + + public void call(String callId) { + Intent msg = new Intent(Intent.ACTION_SEND); + msg.putExtra(Intent.EXTRA_TEXT, mSocketAddress + callId); + msg.setType("text/plain"); + startActivityForResult(Intent.createChooser(msg, "Call someone :"), AUDIO_CALL_SENT); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if(requestCode == AUDIO_CALL_SENT) { + startCam(); + } + } + + public void startCam() { + setContentView(vsv); + // Camera settings + //client.setCamera("front", "640", "480"); + client.setAudio(); + client.start("android_test", true); + } + + @Override + public void onStatusChanged(final String newStatus) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(getApplicationContext(), newStatus, Toast.LENGTH_SHORT).show(); + } + }); + } + + @Override + public void onLocalStream(MediaStream localStream) { + //localStream.videoTracks.get(0).addRenderer(new VideoRenderer(new VideoCallbacks(vsv, 0))); + } + + @Override + public void onAddRemoteStream(MediaStream remoteStream, int endPoint) { + //remoteStream.videoTracks.get(0).addRenderer(new VideoRenderer(new VideoCallbacks(vsv, endPoint))); + //vsv.shouldDraw[endPoint] = true; + } + + @Override + public void onRemoveRemoteStream(MediaStream remoteStream, int endPoint) { + //remoteStream.videoTracks.get(0).dispose(); + //vsv.shouldDraw[endPoint] = false; + } + + // Implementation detail: bridge the VideoRenderer.Callbacks interface to the + // VideoStreamsView implementation. +// private class VideoCallbacks implements VideoRenderer.Callbacks { +// private final VideoStreamsView view; +// private final int stream; +// +// public VideoCallbacks(VideoStreamsView view, int stream) { +// this.view = view; +// this.stream = stream; +// } +// +// @Override +// public void setSize(final int width, final int height) { +// view.queueEvent(new Runnable() { +// public void run() { +// view.setSize(stream, width, height); +// } +// }); +// } +// +// @Override +// public void renderFrame(VideoRenderer.I420Frame frame) { +// view.queueFrame(stream, frame); +// } +// } +} diff --git a/src/fr/pchab/AndroidRTC/RTCActivity.java b/src/fr/pchab/AndroidRTC/RTCActivity.java index c55aee4..a34e9d1 100644 --- a/src/fr/pchab/AndroidRTC/RTCActivity.java +++ b/src/fr/pchab/AndroidRTC/RTCActivity.java @@ -12,7 +12,6 @@ import org.webrtc.MediaStream; import org.webrtc.PeerConnectionFactory; import org.webrtc.VideoRenderer; - import java.util.List; public class RTCActivity extends Activity implements WebRtcClient.RTCListener{ @@ -133,7 +132,7 @@ public void onRemoveRemoteStream(MediaStream remoteStream, int endPoint) { // Implementation detail: bridge the VideoRenderer.Callbacks interface to the // VideoStreamsView implementation. - private class VideoCallbacks implements VideoRenderer.Callbacks { + public class VideoCallbacks implements VideoRenderer.Callbacks { private final VideoStreamsView view; private final int stream; diff --git a/src/fr/pchab/AndroidRTC/WebRtcClient.java b/src/fr/pchab/AndroidRTC/WebRtcClient.java index 3cc4ef8..5e08a7d 100644 --- a/src/fr/pchab/AndroidRTC/WebRtcClient.java +++ b/src/fr/pchab/AndroidRTC/WebRtcClient.java @@ -2,7 +2,8 @@ import java.util.HashMap; import java.util.LinkedList; - +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -25,7 +26,7 @@ import com.koushikdutta.async.http.socketio.EventCallback; import com.koushikdutta.async.http.socketio.SocketIOClient; -class WebRtcClient { +public class WebRtcClient { private final static int MAX_PEER = 2; private boolean[] endPoints = new boolean[MAX_PEER]; private PeerConnectionFactory factory; @@ -161,8 +162,11 @@ private class Peer implements SdpObserver, PeerConnection.Observer{ private int endPoint; @Override - public void onCreateSuccess(final SessionDescription sdp) { + public void onCreateSuccess(final SessionDescription origSdp) { try { + //changed default audio codec from OPUCS(ossy audio codec format) to iSAC + SessionDescription sdp = new SessionDescription(origSdp.type, + preferISAC(origSdp.description)); JSONObject payload = new JSONObject(); payload.put("type", sdp.type.canonicalForm()); payload.put("sdp", sdp.description); @@ -282,6 +286,12 @@ public void setCamera(String cameraFacing, String height, String width){ mListener.onLocalStream(lMS); } + + public void setAudio() { + lMS = factory.createLocalMediaStream("ARDAMS"); + lMS.addTrack(factory.createAudioTrack("ARDAMSa0")); + mListener.onLocalStream(lMS); + } private int findEndPoint() { for(int i = 0; i < MAX_PEER; i++) { @@ -335,4 +345,53 @@ private void removePeer(String id) { endPoints[peer.endPoint] = false; } + +//Mangle SDP to prefer ISAC/16000 over any other audio codec. + private String preferISAC(String sdpDescription) { + String[] lines = sdpDescription.split("\n"); + int mLineIndex = -1; + String isac16kRtpMap = null; + Pattern isac16kPattern = Pattern + .compile("^a=rtpmap:(\\d+) ISAC/16000[\r]?$"); + for (int i = 0; (i < lines.length) + && (mLineIndex == -1 || isac16kRtpMap == null); ++i) { + if (lines[i].startsWith("m=audio ")) { + mLineIndex = i; + continue; + } + Matcher isac16kMatcher = isac16kPattern.matcher(lines[i]); + if (isac16kMatcher.matches()) { + isac16kRtpMap = isac16kMatcher.group(1); + continue; + } + } + if (mLineIndex == -1) { + Log.d(TAG, "No m=audio line, so can't prefer iSAC"); + return sdpDescription; + } + if (isac16kRtpMap == null) { + Log.d(TAG, "No ISAC/16000 line, so can't prefer iSAC"); + return sdpDescription; + } + String[] origMLineParts = lines[mLineIndex].split(" "); + StringBuilder newMLine = new StringBuilder(); + int origPartIndex = 0; + // Format is: m= ... + newMLine.append(origMLineParts[origPartIndex++]).append(" "); + newMLine.append(origMLineParts[origPartIndex++]).append(" "); + newMLine.append(origMLineParts[origPartIndex++]).append(" "); + newMLine.append(isac16kRtpMap).append(" "); + for (; origPartIndex < origMLineParts.length; ++origPartIndex) { + if (!origMLineParts[origPartIndex].equals(isac16kRtpMap)) { + newMLine.append(origMLineParts[origPartIndex]).append(" "); + } + } + lines[mLineIndex] = newMLine.toString(); + StringBuilder newSdpDescription = new StringBuilder(); + for (String line : lines) { + newSdpDescription.append(line).append("\n"); + } + return newSdpDescription.toString(); + } + } From 811bae738435c4c8ececd014951dbddca1409053 Mon Sep 17 00:00:00 2001 From: Deepak Rathi Date: Tue, 25 Nov 2014 03:42:53 -0800 Subject: [PATCH 2/2] Added Instructions/Steps followed to create Audio call module Removed unwanted comments --- src/deepak/rathi/RTCAudioActivity.java | 62 +++++++++++++------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/src/deepak/rathi/RTCAudioActivity.java b/src/deepak/rathi/RTCAudioActivity.java index d4242c9..cffd521 100644 --- a/src/deepak/rathi/RTCAudioActivity.java +++ b/src/deepak/rathi/RTCAudioActivity.java @@ -1,5 +1,30 @@ package deepak.rathi; +/** + * Copyright Deepak Rathi (https://github.com/deepak-rathi) + * + * STEPS FOLLOWED TO CREATE AUDIO CALL + * - In WebRtcClient : +create a new method that does the same thing as setCamera but without video +: + + public void setAudio(){ + lMS = factory.createLocalMediaStream("ARDAMS"); + lMS.addTrack(factory.createAudioTrack("ARDAMSa0")); + + mListener.onLocalStream(lMS); + } + +- In RTCactivity : +in startCam() call setAudio() instead of setCamera(...) + +At this point, you should still have audio working and a green (or black) +screen. +You can then : +- Remove the videoStreamsView and videoRenderer from RTCactivity +- Change the PeerConnection Constraints "OfferToReceiveVideo" to false + */ + import fr.pchab.AndroidRTC.R; import fr.pchab.AndroidRTC.VideoStreamsView; import fr.pchab.AndroidRTC.WebRtcClient; @@ -104,8 +129,8 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { public void startCam() { setContentView(vsv); - // Camera settings - //client.setCamera("front", "640", "480"); + // Audio call settings + client.setAudio(); client.start("android_test", true); } @@ -122,44 +147,17 @@ public void run() { @Override public void onLocalStream(MediaStream localStream) { - //localStream.videoTracks.get(0).addRenderer(new VideoRenderer(new VideoCallbacks(vsv, 0))); + } @Override public void onAddRemoteStream(MediaStream remoteStream, int endPoint) { - //remoteStream.videoTracks.get(0).addRenderer(new VideoRenderer(new VideoCallbacks(vsv, endPoint))); - //vsv.shouldDraw[endPoint] = true; + } @Override public void onRemoveRemoteStream(MediaStream remoteStream, int endPoint) { - //remoteStream.videoTracks.get(0).dispose(); - //vsv.shouldDraw[endPoint] = false; + } - // Implementation detail: bridge the VideoRenderer.Callbacks interface to the - // VideoStreamsView implementation. -// private class VideoCallbacks implements VideoRenderer.Callbacks { -// private final VideoStreamsView view; -// private final int stream; -// -// public VideoCallbacks(VideoStreamsView view, int stream) { -// this.view = view; -// this.stream = stream; -// } -// -// @Override -// public void setSize(final int width, final int height) { -// view.queueEvent(new Runnable() { -// public void run() { -// view.setSize(stream, width, height); -// } -// }); -// } -// -// @Override -// public void renderFrame(VideoRenderer.I420Frame frame) { -// view.queueFrame(stream, frame); -// } -// } }