Skip to content

Instantly share code, notes, and snippets.

@shitpoet
Created August 25, 2019 10:21
Show Gist options
  • Save shitpoet/df50cf1407db2da374c7fd7bc8555458 to your computer and use it in GitHub Desktop.
Save shitpoet/df50cf1407db2da374c7fd7bc8555458 to your computer and use it in GitHub Desktop.
Low-latency capturing and sending, receiving and playing audio over UDP on Android 8.1. Converts a phone to an Wi-Fi UDP headset. This code is a prototype!
/* add to manifest:
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.INTERNET"/>
*/
package com.example.myapplication
import android.app.AlertDialog
import android.app.PendingIntent.getActivity
import androidx.appcompat.app.AppCompatActivity
import android.media.AudioFormat
import android.media.AudioRecord
import android.media.MediaRecorder
import android.view.View
import android.widget.Toast
import android.content.DialogInterface
import android.content.pm.PackageManager
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import android.os.AsyncTask.execute
import android.annotation.SuppressLint
import android.content.Context
import android.media.AudioManager
import android.os.*
import kotlinx.android.synthetic.main.activity_main.*
import java.net.DatagramPacket
import java.net.DatagramSocket
import java.net.InetAddress
import android.media.AudioTrack
class MainActivity : AppCompatActivity() {
var recording = false
private var ar: AudioRecord? = null
private var minSize: Int = 0
var playing = false
var at: AudioTrack? = null
fun startRecording() {
if (recording) return
if (ContextCompat.checkSelfPermission(
this,
android.Manifest.permission.RECORD_AUDIO
) != PackageManager.PERMISSION_GRANTED
) {
//msgbox("requesting record audio perm")
var perms = arrayOf(android.Manifest.permission.RECORD_AUDIO)
ActivityCompat.requestPermissions(
this,
perms,
1
);
return
}
val am = getSystemService(Context.AUDIO_SERVICE) as AudioManager
val sampleRateStr: String? = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE)
var sampleRate: Int = sampleRateStr?.let { str ->
Integer.parseInt(str).takeUnless { it == 0 }
} ?: 44100 // Use a default value if property not found
//msgbox(sampleRate.toString());
minSize = AudioRecord.getMinBufferSize(
sampleRate,
AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT
)
// msgbox("minSize" + minSize.toString());
var asrc = MediaRecorder.AudioSource.UNPROCESSED;
//MediaRecorder.AudioSource.MIC;
// MediaRecorder.AudioSource.MIC,
// MediaRecorder.AudioSource.VOICE_PERFORMANCE
/*if (AudioManager.getProperty(AudioManager.PROPERTY_SUPPORT_AUDIO_SOURCE_UNPROCESSED)) {
msgbox("unprocessed audio source is supported")
asrc = MediaRecorder.AudioSource.UNPROCESSED;
}*/
try {
ar = AudioRecord(
asrc,
sampleRate,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT,
minSize
)
ar!!.startRecording()
recording = true
val serverHost = "" +
editText.text + "." + editText2.text + "." +
editText3.text + "." + editText4.text
Thread(Runnable {
Looper.prepare()
val buffer = ByteArray(minSize)
val serverAddr = InetAddress.getByName(serverHost)
val serverPort = 20783;
var ds: DatagramSocket? = DatagramSocket()
while (recording) {
// val buffer = ShortArray(minSize)
ar!!.read(buffer, 0, minSize)
// var s = "udp-test-123 \r\n"
try {
val dp: DatagramPacket = DatagramPacket(
buffer, //buffer.toByteArray(),
minSize,
serverAddr,
serverPort
)
// ds!!.setBroadcast(true)
ds!!.send(dp)
// msgbox("udp packet sent")
} catch (e: Exception) {
e.printStackTrace()
// msgbox("udp send error")
} finally {
// if (ds != null) {
// ds!!.close()
// }
}
}
if (ds != null) ds!!.close()
}).start()
} catch (e: Exception) {
msgbox("can not create AudioRecord")
}
}
fun stopRecording() {
recording = false
if (ar != null) {
ar!!.stop()
}
}
fun startPlaying() {
if (playing) return
at = AudioTrack(
AudioManager.STREAM_VOICE_CALL,
48000, AudioFormat.CHANNEL_OUT_MONO,
AudioFormat.ENCODING_PCM_16BIT, 3840,
AudioTrack.MODE_STREAM
)
if (at == null) return
at!!.play()
playing = true
Thread(Runnable {
Looper.prepare()
val ds = DatagramSocket(20783);
try {
val buffer = ByteArray(3840)
val dp = DatagramPacket(buffer, buffer.size)
//disable timeout for testing
//ds.setSoTimeout(100000);
while (playing && !ds.isClosed) {
ds.receive(dp)
//System.out.println("playing: UDP packet received: " + dp.socketAddress.toString())
if (at != null) at!!.write(buffer, 0, buffer.size)
/*runOnUiThread(new Runnable() {
public void run() {
data.setText(lText);
}
});*/
}
} catch (e: Exception) {
e.printStackTrace();
playing = false
} finally {
if (ds != null) {
ds.close();
}
}
}).start()
}
fun stopPlaying() {
if (playing) {
if (at != null) {
at!!.stop()
at = null
}
playing = false
}
}
fun toast(s: String) {
Toast.makeText(getApplicationContext(), s, Toast.LENGTH_LONG).show();
}
fun msgbox(s: String) {
AlertDialog.Builder(this)
.setTitle("")
.setMessage(s)
// Specifying a listener allows you to take an action before dismissing the dialog.
// The dialog is automatically dismissed when a dialog button is clicked.
.setPositiveButton(android.R.string.ok, DialogInterface.OnClickListener { dialog, which ->
// Continue with delete operation
})
// A null listener allows the button to dismiss the dialog and take no further action.
// .setNegativeButton(android.R.string.no, null)
.show()
}
fun start(view: View) {
startRecording();
startPlaying();
msgbox("started");
}
fun stop(view: View) {
stopRecording();
stopPlaying();
msgbox("stopped");
}
fun test(view: View) {
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
//StrictMode.setThreadPolicy(policy)
if (ContextCompat.checkSelfPermission(
this,
android.Manifest.permission.INTERNET
) != PackageManager.PERMISSION_GRANTED
) {
var perms = arrayOf(android.Manifest.permission.INTERNET)
ActivityCompat.requestPermissions(
this,
perms,
2
);
} else {
//msgbox("already has internet perm")
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment