diff --git a/.idea/misc.xml b/.idea/misc.xml index 9e3e273..aa61ddb 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -60,7 +60,7 @@ - + diff --git a/app/src/main/java/com/smarthomies/realtimetalk/RTTApp.java b/app/src/main/java/com/smarthomies/realtimetalk/RTTApp.java index f17270c..508f826 100644 --- a/app/src/main/java/com/smarthomies/realtimetalk/RTTApp.java +++ b/app/src/main/java/com/smarthomies/realtimetalk/RTTApp.java @@ -1,17 +1,27 @@ package com.smarthomies.realtimetalk; import android.app.Application; +import android.util.Log; +import com.smarthomies.realtimetalk.utils.MediaRecorderThread; +import com.smarthomies.realtimetalk.utils.MediaStreamServer; +import com.smarthomies.realtimetalk.utils.MediaStreamerThread; import com.smarthomies.realtimetalk.utils.RTTAppHelper; +import com.smarthomies.realtimetalk.utils.SocketAudioPlayer; +import com.smarthomies.realtimetalk.utils.SocketAudioRecorder; + +import java.net.Socket; import io.realm.Realm; /** * Created by ensar on 31/10/16. */ -public class RTTApp extends Application { +public class RTTApp extends Application implements MediaStreamServer.CallCallbacks { public static final String TAG = RTTApp.class.getSimpleName(); + private SocketAudioRecorder socketAudioRecorder; + @Override public void onCreate() { super.onCreate(); @@ -19,5 +29,46 @@ public class RTTApp extends Application { RTTAppHelper.getInstance().initWithContext(getApplicationContext()); Realm.init(this); + + MediaStreamServer.initWithContext(this); + + MediaStreamServer.getInstance().setCallListener(this); + MediaStreamServer.getInstance().startListeningToPort(); + + MediaStreamerThread.initWithContext(this); + MediaStreamerThread.getInstance().start(); + + MediaRecorderThread.initWithContext(this); + MediaRecorderThread.getInstance().start(); + } + + @Override + public void onError(int errorCode) { + Log.d(TAG, "onError: " + errorCode); + } + + @Override + public void onConnectionRequested(Socket sourceSocket) { + Log.d(TAG, "onConnectionRequested: "); + if(socketAudioRecorder != null) { + socketAudioRecorder.stop(); + } + socketAudioRecorder = MediaRecorderThread.getInstance().startStreamingAudio(sourceSocket.getInetAddress().toString().substring(1), 8087); + } + + @Override + public void onConnectionFinished() { + Log.d(TAG, "onConnectionFinished: "); + if(socketAudioRecorder != null) { + socketAudioRecorder.stop(); + } + } + + @Override + public void onTerminate() { + super.onTerminate(); + MediaStreamServer.getInstance().stop(); + MediaStreamerThread.getInstance().interrupt(); + MediaRecorderThread.getInstance().interrupt(); } } diff --git a/app/src/main/java/com/smarthomies/realtimetalk/utils/MediaRecorderThread.java b/app/src/main/java/com/smarthomies/realtimetalk/utils/MediaRecorderThread.java new file mode 100644 index 0000000..480f889 --- /dev/null +++ b/app/src/main/java/com/smarthomies/realtimetalk/utils/MediaRecorderThread.java @@ -0,0 +1,68 @@ +package com.smarthomies.realtimetalk.utils; + +import android.content.Context; +import android.os.Handler; +import android.os.HandlerThread; +import android.util.Log; + +import com.smarthomies.realtimetalk.R; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.Socket; +import java.util.Properties; +import java.util.concurrent.CountDownLatch; + +public class MediaRecorderThread extends HandlerThread { + private static final String TAG = MediaRecorderThread.class.getSimpleName(); + + private int frequency; + private int channelConfiguration; + private int audioEncoding; + + private static MediaRecorderThread instance; + + public static void initWithContext(Context context) { + instance = new MediaRecorderThread(context); + } + + private Handler mHandler; + + public static MediaRecorderThread getInstance() { + if(instance == null) { + throw new RuntimeException("Initialize server first with initWithContext"); + } + return instance; + } + + private MediaRecorderThread(Context ctx) { + super(TAG); + Properties prop = new Properties(); + try { + InputStream inputStream = ctx.getResources().openRawResource(R.raw.config); + prop.load(inputStream); + } catch (FileNotFoundException e) { + Log.d(TAG, "Can't find config"); + } catch (IOException e) { + Log.d(TAG, "Can't load config"); + } + + frequency = Integer.parseInt(prop.getProperty("frequency")); + channelConfiguration = Integer.parseInt(prop.getProperty("client_channel")); + audioEncoding = Integer.parseInt(prop.getProperty("audio_encoding")); + } + + @Override + protected void onLooperPrepared() { + super.onLooperPrepared(); + mHandler = new Handler(getLooper()); + } + + public SocketAudioRecorder startStreamingAudio(String ip, int port) { + SocketAudioRecorder recorder = new SocketAudioRecorder(ip, port, frequency, channelConfiguration, audioEncoding); + mHandler.post(recorder); + return recorder; + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/smarthomies/realtimetalk/utils/MediaStreamClient.java b/app/src/main/java/com/smarthomies/realtimetalk/utils/MediaStreamClient.java index 276c723..f94f394 100644 --- a/app/src/main/java/com/smarthomies/realtimetalk/utils/MediaStreamClient.java +++ b/app/src/main/java/com/smarthomies/realtimetalk/utils/MediaStreamClient.java @@ -4,6 +4,7 @@ import android.content.Context; import android.media.AudioFormat; import android.media.AudioRecord; import android.media.MediaRecorder; +import android.util.Log; import com.smarthomies.realtimetalk.R; import com.smarthomies.realtimetalk.views.activities.CallActivity; @@ -31,17 +32,19 @@ public class MediaStreamClient { InputStream inputStream = ctx.getResources().openRawResource(R.raw.config); prop.load(inputStream); } catch (FileNotFoundException e) { - System.out.println("Can't finde config"); + System.out.println("Can't find config"); } catch (IOException e) { System.out.println("Can't load config"); } final int frequency = Integer.parseInt(prop.getProperty("frequency")); - final int channelConfiguration = Integer.parseInt(prop.getProperty("channal_server")); + final int channelConfiguration = Integer.parseInt(prop.getProperty("client_channel")); final int audioEncoding = Integer.parseInt(prop.getProperty("audio_encoding")); - final int SERVERPORT = Integer.parseInt(prop.getProperty("serverport_server")); + final int SERVERPORT = Integer.parseInt(prop.getProperty("port_server")); + recBufSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding); + Log.d(TAG, "MediaStreamClient buffer size: " + recBufSize); //Log.v(TAG,String.valueOf(AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_CONFIGURATION_MONO , AudioFormat.ENCODING_PCM_16BIT))); audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, frequency, channelConfiguration, audioEncoding, recBufSize); @@ -70,7 +73,7 @@ public class MediaStreamClient { } } audioRecord.stop(); - //audioRecord.release(); + audioRecord.release(); try { connfd.close(); } catch (Exception e) { e.printStackTrace(); diff --git a/app/src/main/java/com/smarthomies/realtimetalk/utils/MediaStreamServer.java b/app/src/main/java/com/smarthomies/realtimetalk/utils/MediaStreamServer.java index 7a439fe..e2d6c1f 100644 --- a/app/src/main/java/com/smarthomies/realtimetalk/utils/MediaStreamServer.java +++ b/app/src/main/java/com/smarthomies/realtimetalk/utils/MediaStreamServer.java @@ -1,108 +1,172 @@ package com.smarthomies.realtimetalk.utils; import android.content.Context; -import android.content.Intent; -import android.media.AudioFormat; -import android.media.AudioManager; -import android.media.AudioTrack; import android.util.Log; import com.smarthomies.realtimetalk.R; -import com.smarthomies.realtimetalk.views.activities.CallActivity; -import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.Properties; +import java.util.concurrent.CountDownLatch; public class MediaStreamServer implements Runnable { + private static final String TAG = MediaStreamServer.class.getSimpleName(); - boolean isPlaying; - int playBufSize; - Socket connfd; - ServerSocket sockfd; - AudioTrack audioTrack; - private static final String TAG = "MyActivity"; - final int SERVERPORT; - Context ctx; + private boolean isListening; + private Socket socketConnection; + private ServerSocket serverSocket; + private final int SERVERPORT; - public MediaStreamServer(final Context ctx) { + private Thread serverThread; + + private CallCallbacks callListener; + + private static MediaStreamServer instance; + + public static void initWithContext(Context context) { + instance = new MediaStreamServer(context); + } + + public static MediaStreamServer getInstance() { + if(instance == null) { + throw new RuntimeException("Initialize server first with initWithContext"); + } + return instance; + } + + public void setCallListener(CallCallbacks callListener) { + this.callListener = callListener; + } + + public void removeCallListener() { + this.callListener = null; + } + + private MediaStreamServer(Context ctx) { Properties prop = new Properties(); try { InputStream inputStream = ctx.getResources().openRawResource(R.raw.config); prop.load(inputStream); } catch (FileNotFoundException e) { - System.out.println("Can't finde config"); + Log.d(TAG, "Can't find config"); } catch (IOException e) { - System.out.println("Can't load config"); + Log.d(TAG, "Can't load config"); } - final int frequency = Integer.parseInt(prop.getProperty("frequency")); - final int channelConfiguration = Integer.parseInt(prop.getProperty("channal_client")); - final int audioEncoding = Integer.parseInt(prop.getProperty("audio_encoding")); - SERVERPORT = Integer.parseInt(prop.getProperty("serverport_client")); - - - playBufSize=AudioTrack.getMinBufferSize(frequency, channelConfiguration, audioEncoding); - audioTrack = new AudioTrack(AudioManager.STREAM_VOICE_CALL, frequency, channelConfiguration, audioEncoding, playBufSize, AudioTrack.MODE_STREAM); - audioTrack.setStereoVolume(1f, 1f); - this.ctx=ctx; + SERVERPORT = Integer.parseInt(prop.getProperty("port_client")); } public void stop() { - isPlaying = false; - + isListening = false; + serverThread.interrupt(); } + public void startListeningToPort() { + if(serverThread == null) { + serverThread = new Thread(this); + } + serverThread.start(); + } - public void setVolume(float lvol, float rvol) { - audioTrack.setStereoVolume(lvol, rvol); + public void stopListeningToPort() { + isListening = false; } @Override public void run() { - byte[] buffer = new byte[playBufSize]; - try { sockfd = new ServerSocket(SERVERPORT); } - catch (Exception e) { + isListening = true; + try { + serverSocket = new ServerSocket(SERVERPORT); + Log.d(TAG, "Server socket open on port: " + SERVERPORT); + } + catch (IOException e) { e.printStackTrace(); - CallActivity.toast("Port unavailable",ctx); + notifyError(ERROR_PORT_UNAVAILABLE); + isListening = false; return; } - while(true) { + + while(isListening) { try { - connfd = sockfd.accept(); - } catch (Exception e) { + socketConnection = serverSocket.accept(); + Log.d(TAG, "Accepted socket connection!"); + Log.d(TAG, "Socket connection info: " + socketConnection); + notifyConnectionRequested(socketConnection); + } catch (IOException e) { e.printStackTrace(); - CallActivity.toast("Connection not accepted",ctx); + notifyError(ERROR_WHILE_ACCEPTING); continue; } - CallActivity.toast("Connected",ctx); - audioTrack.play(); - isPlaying = true; - while (isPlaying) { - int readSize = 0; - try { - readSize = connfd.getInputStream().read(buffer); - } catch (Exception e) { - Log.v(TAG, "palo"); - e.printStackTrace(); - CallActivity.toast("Closed stream by sender",ctx); - break; - } - audioTrack.write(buffer, 0, readSize); - } - audioTrack.stop(); - audioTrack.flush(); + + final CountDownLatch callTerminationLock = new CountDownLatch(1); + + Log.d(TAG, "Playing audio!"); + MediaStreamerThread.getInstance().playAudioFromSocket(socketConnection, callTerminationLock); + try { - connfd.close(); + callTerminationLock.await(); + Log.d(TAG, "Done playing!"); + } catch (InterruptedException ex) { + ex.printStackTrace(); + notifyError(ERROR_CALL_INTERRUPTED); + continue; + } + + try { + socketConnection.close(); + notifyConnectionFinished(); } catch (Exception e) { e.printStackTrace(); - CallActivity.toast("Can't close connection!",ctx); + notifyError(ERROR_WHILE_CLOSING_CONNECTION); } + + } + + try { + serverSocket.close(); + } + catch (IOException e) { + e.printStackTrace(); + notifyError(ERROR_WHILE_CLOSING); + isListening = false; + } + + } + + private void notifyError(int errorCode) { + if(callListener != null) { + callListener.onError(errorCode); } } + + private void notifyConnectionRequested(Socket sourceSocket) { + if(callListener != null) { + callListener.onConnectionRequested(sourceSocket); + } + } + + private void notifyConnectionFinished() { + if(callListener != null) { + callListener.onConnectionFinished(); + } + } + + public static final int ERROR_PORT_UNAVAILABLE = 0; + public static final int ERROR_WHILE_CLOSING = 1; + public static final int ERROR_WHILE_ACCEPTING = 2; + public static final int ERROR_WHILE_PLAYING = 3; + public static final int ERROR_WHILE_CLOSING_CONNECTION = 4; + public static final int ERROR_CALL_INTERRUPTED = 5; + + + public interface CallCallbacks { + void onError(int errorCode); + void onConnectionRequested(Socket sourceSocket); + void onConnectionFinished(); + } } \ No newline at end of file diff --git a/app/src/main/java/com/smarthomies/realtimetalk/utils/MediaStreamerThread.java b/app/src/main/java/com/smarthomies/realtimetalk/utils/MediaStreamerThread.java new file mode 100644 index 0000000..6f9dc5a --- /dev/null +++ b/app/src/main/java/com/smarthomies/realtimetalk/utils/MediaStreamerThread.java @@ -0,0 +1,72 @@ +package com.smarthomies.realtimetalk.utils; + +import android.content.Context; +import android.databinding.repacked.org.antlr.v4.codegen.model.Loop; +import android.media.AudioManager; +import android.media.AudioTrack; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.util.Log; + +import com.smarthomies.realtimetalk.R; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.Properties; +import java.util.concurrent.CountDownLatch; + +public class MediaStreamerThread extends HandlerThread { + private static final String TAG = MediaStreamerThread.class.getSimpleName(); + + private int frequency; + private int channelConfiguration; + private int audioEncoding; + + private static MediaStreamerThread instance; + + public static void initWithContext(Context context) { + instance = new MediaStreamerThread(context); + } + + private Handler mHandler; + + public static MediaStreamerThread getInstance() { + if(instance == null) { + throw new RuntimeException("Initialize server first with initWithContext"); + } + return instance; + } + + private MediaStreamerThread(Context ctx) { + super(TAG); + Properties prop = new Properties(); + try { + InputStream inputStream = ctx.getResources().openRawResource(R.raw.config); + prop.load(inputStream); + } catch (FileNotFoundException e) { + Log.d(TAG, "Can't find config"); + } catch (IOException e) { + Log.d(TAG, "Can't load config"); + } + + frequency = Integer.parseInt(prop.getProperty("frequency")); + channelConfiguration = Integer.parseInt(prop.getProperty("server_channel")); + audioEncoding = Integer.parseInt(prop.getProperty("audio_encoding")); + } + + @Override + protected void onLooperPrepared() { + super.onLooperPrepared(); + mHandler = new Handler(getLooper()); + } + + public void playAudioFromSocket(Socket socket, final CountDownLatch finishedLock) { + mHandler.post(new SocketAudioPlayer(socket, frequency, channelConfiguration, audioEncoding, finishedLock)); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/smarthomies/realtimetalk/utils/SocketAudioPlayer.java b/app/src/main/java/com/smarthomies/realtimetalk/utils/SocketAudioPlayer.java new file mode 100644 index 0000000..b5d0015 --- /dev/null +++ b/app/src/main/java/com/smarthomies/realtimetalk/utils/SocketAudioPlayer.java @@ -0,0 +1,73 @@ +package com.smarthomies.realtimetalk.utils; + +import android.media.AudioManager; +import android.media.AudioTrack; +import android.util.Log; + +import java.io.IOException; +import java.net.Socket; +import java.util.concurrent.CountDownLatch; + +public class SocketAudioPlayer implements Runnable { + private static final String TAG = SocketAudioPlayer.class.getSimpleName(); + + private int playBufSize; + private AudioTrack audioTrack; + + private boolean isPlaying; + + private Socket socket; + + private CountDownLatch completionLock; + + private int noDataCounter = 0; + + + public SocketAudioPlayer(Socket socket, int frequency, int channelConfiguration, int audioEncoding, final CountDownLatch countDownLatch) { + this.socket = socket; + playBufSize=AudioTrack.getMinBufferSize(frequency, channelConfiguration, audioEncoding); + audioTrack = new AudioTrack(AudioManager.STREAM_VOICE_CALL, frequency, channelConfiguration, audioEncoding, playBufSize, AudioTrack.MODE_STREAM); + audioTrack.setStereoVolume(1f, 1f); + this.completionLock = countDownLatch; + } + + @Override + public void run() { + try { + Log.d(TAG, "Start!"); + byte[] buffer = new byte[playBufSize]; + audioTrack.play(); + isPlaying = true; + while (isPlaying) { + int readSize = 0; + try { + readSize = socket.getInputStream().read(buffer); + + Log.d(TAG, "Received " + readSize + " bytes"); + + if(readSize <= 0) { + noDataCounter++; + } else { + noDataCounter = 0; + } + + if(noDataCounter == 10) { + break; + } + + } catch (IOException e) { + e.printStackTrace(); + isPlaying = false; + break; + } + audioTrack.write(buffer, 0, readSize); + } + audioTrack.stop(); + audioTrack.flush(); + } finally { + Log.d(TAG, "Done!"); + completionLock.countDown(); + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/smarthomies/realtimetalk/utils/SocketAudioRecorder.java b/app/src/main/java/com/smarthomies/realtimetalk/utils/SocketAudioRecorder.java new file mode 100644 index 0000000..a1fc1ee --- /dev/null +++ b/app/src/main/java/com/smarthomies/realtimetalk/utils/SocketAudioRecorder.java @@ -0,0 +1,80 @@ +package com.smarthomies.realtimetalk.utils; + +import android.media.AudioRecord; +import android.media.MediaRecorder; +import android.util.Log; + +import com.smarthomies.realtimetalk.views.activities.CallActivity; + +import java.io.IOException; +import java.net.Socket; +import java.util.concurrent.CountDownLatch; + +public class SocketAudioRecorder implements Runnable { + private static final String TAG = SocketAudioRecorder.class.getSimpleName(); + + private static int recBufSize; + private static AudioRecord audioRecord; + + private boolean isRecording; + + private Socket socket; + + private String ip; + private int port; + + public SocketAudioRecorder(String ip, int port, int frequency, int channelConfiguration, int audioEncoding) { + this.ip = ip; + this.port = port; + if(audioRecord == null || audioRecord.getState() == AudioRecord.STATE_UNINITIALIZED) { + recBufSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding); + Log.d(TAG, "MediaStreamClient buffer size: " + recBufSize); + //Log.v(TAG,String.valueOf(AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_CONFIGURATION_MONO , AudioFormat.ENCODING_PCM_16BIT))); + audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, frequency, channelConfiguration, audioEncoding, recBufSize); + } + } + + @Override + public void run() { + byte[] buffer = new byte[recBufSize]; + try { + socket = new Socket(ip, port); + Log.d(TAG, "Socket open!"); + Log.d(TAG, "Socket info: " + socket); + } catch (Exception e) { + e.printStackTrace(); + return; + } + audioRecord.startRecording(); + + Log.d(TAG, "Started recording"); + isRecording = true; + while (isRecording) { + + int readSize = audioRecord.read(buffer, 0, recBufSize); + + Log.d(TAG, "Recorded " + readSize + " bytes"); + + try { + socket.getOutputStream().write(buffer, 0, readSize); + Log.d(TAG, "Sent " + readSize + " bytes"); + } catch (Exception e) { + e.printStackTrace(); + break; + } + } + audioRecord.stop(); + Log.d(TAG, "Stopped recording!"); + try { + socket.close(); + Log.d(TAG, "Socket closed!"); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void stop() { + isRecording = false; + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/smarthomies/realtimetalk/views/activities/CallActivity.java b/app/src/main/java/com/smarthomies/realtimetalk/views/activities/CallActivity.java index 821d3b6..1cb68d9 100644 --- a/app/src/main/java/com/smarthomies/realtimetalk/views/activities/CallActivity.java +++ b/app/src/main/java/com/smarthomies/realtimetalk/views/activities/CallActivity.java @@ -12,20 +12,21 @@ import android.widget.TextView; import android.widget.Toast; import com.smarthomies.realtimetalk.R; +import com.smarthomies.realtimetalk.utils.MediaRecorderThread; import com.smarthomies.realtimetalk.utils.MediaStreamClient; import com.smarthomies.realtimetalk.utils.MediaStreamServer; +import com.smarthomies.realtimetalk.utils.SocketAudioRecorder; public class CallActivity extends AppCompatActivity { private TextView serverStatus; private EditText serverIp; private Button nazovi; - private MediaStreamClient mss; - private MediaStreamServer msc; // DESIGNATE A PORT boolean isRecording; private static Handler handler = new Handler(); - Thread t=null; + + private SocketAudioRecorder socketAudioRecorder; @Override protected void onCreate(Bundle savedInstanceState) { @@ -37,9 +38,6 @@ public class CallActivity extends AppCompatActivity { serverStatus=(TextView) findViewById(R.id.labela); nazovi.setOnTouchListener(nazoviL); isRecording=false; - msc=new MediaStreamServer(CallActivity.this); - t=new Thread(msc); - t.start(); } @@ -50,11 +48,15 @@ public class CallActivity extends AppCompatActivity { String ip=serverIp.getText().toString(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: - msc.stop(); - mss=new MediaStreamClient(CallActivity.this,ip); + if(socketAudioRecorder != null) { + socketAudioRecorder.stop(); + } + socketAudioRecorder=MediaRecorderThread.getInstance().startStreamingAudio(ip, 8087); break; case MotionEvent.ACTION_UP: - mss.stop(CallActivity.this); + if(socketAudioRecorder != null) { + socketAudioRecorder.stop(); + } break; } return false; diff --git a/app/src/main/res/raw/config.properties b/app/src/main/res/raw/config.properties index 68d11e5..5a526b3 100644 --- a/app/src/main/res/raw/config.properties +++ b/app/src/main/res/raw/config.properties @@ -1,6 +1,6 @@ frequency = 44100 -serverport_client = 8087 -serverport_server = 8083 +port_client = 8087 +port_server = 8083 audio_encoding=2 -channal_client=4 -channal_server=2 +client_channel=2 +server_channel=2