Skip to content

Instantly share code, notes, and snippets.

@sajjadyousefnia
Created November 5, 2024 18:29
Show Gist options
  • Save sajjadyousefnia/70d0f98f877b4615d84eb9d4406aba47 to your computer and use it in GitHub Desktop.
Save sajjadyousefnia/70d0f98f877b4615d84eb9d4406aba47 to your computer and use it in GitHub Desktop.
////***************************************************************************************************************************************************
////***************************************************************************************************************************************************
private var continueToRun: Boolean = true
private var minimumBufferSize: Int = -1
private var audioRecord: AudioRecord? = null
private var audioEncoder: MediaCodec? = null
private var socket: Socket? = null
// private val serverUrl = AppConstants.apiBaseURL
private val sampleRate = 44100 // 16000
private val channelConfig = AudioFormat.CHANNEL_IN_MONO
private val audioFormat = AudioFormat.ENCODING_PCM_16BIT
private val bufferSize = 1024
//AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat)
var lastSendTime = System.currentTimeMillis()
val accumulatedData = mutableListOf<Byte>() // Temporary storage for 500ms of audio data
// private val deviceName = "android"
private lateinit var socketManager: SocketManager
@Volatile
private var isStreaming = false
init {
initializeSocket()
}
// initialize and start socket and
private fun initializeSocket() {
socketManager = SocketManager()
socketManager.setSocketLiveListener(object : SocketLiveListener {
override fun onStartMicrophone() {
startStreaming()
}
override fun onMicrophoneBlob() {
}
override fun onReceived(data: String) {
Handler(Looper.getMainLooper()).post {
Toast.makeText(context, data, Toast.LENGTH_SHORT).show()
}
}
override fun onStopMicrophone() {
}
override fun onReceiveFault() {
}
override fun onFinish() {
}
})
socketManager.connect()
socketManager.startLiveStreaming()
}
@android.annotation.SuppressLint("MissingPermission")
fun startStreaming() {
setupAudioRecord()
// setupAudioEncoder()
audioRecord?.startRecording()
audioEncoder?.start()
isStreaming = true
captureAndEncodeLoop()
}
@SuppressLint("MissingPermission")
private fun setupAudioRecord() {
/* audioRecord = AudioRecord.Builder()
.setAudioSource(MediaRecorder.AudioSource.VOICE_RECOGNITION)
.setAudioFormat(
AudioFormat.Builder()
.setEncoding(audioFormat)
.setSampleRate(sampleRate)
.setChannelMask(channelConfig)
.build()
)
.setBufferSizeInBytes(bufferSize)
.build()*/
minimumBufferSize = AudioRecord.getMinBufferSize(
sampleRate,
AudioFormat.CHANNEL_IN_MONO, audioFormat
)
audioRecord = AudioRecord(
MediaRecorder.AudioSource.MIC,
sampleRate, channelConfig,
audioFormat, bufferSize
)
}
/*
private fun setupAudioEncoder() {
val format = MediaFormat.createAudioFormat(
MediaFormat.MIMETYPE_AUDIO_OPUS*/
/*MIMETYPE_AUDIO_AAC*//*
,
sampleRate,
1
)
// format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC)
format.setInteger(MediaFormat.KEY_BIT_RATE, 64000)
audioEncoder =
MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_OPUS*/
/*MIMETYPE_AUDIO_AAC*//*
)
audioEncoder?.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
}
*/
private fun captureAndEncodeLoop() {
// FFmpeg command setup
val inputBuffer = ByteArray(bufferSize)
while (isStreaming && continueToRun) {
val readResult = audioRecord?.read(inputBuffer, 0, inputBuffer.size) ?: 0
if (readResult > 0) {
// Add read data to accumulatedData
accumulatedData.addAll(inputBuffer.take(readResult))
// Check if 500ms has passed
val currentTime = System.currentTimeMillis()
if (currentTime - lastSendTime >= 500) {
// Encode and send the data
val dataToSend = accumulatedData.toByteArray()
val finalData = processAudio(dataToSend)
// Encodes before sending, if needed
// Clear accumulatedData and reset the timer
accumulatedData.clear()
lastSendTime = currentTime
// processAudio(dataToSend)
// encode(inputBuffer, readResult, bufferInfo)
socketManager.emitAudioData(finalData!!)
}
}
}
// audioEncoder?.signalEndOfInputStream()
releaseResources()
}
fun processAudio(byteArray: ByteArray): ByteArray? {
val channels = 1 // Replace with desired CHANNELS
val rate = 16000 // Replace with desired RATE
val tempInputFile = File(context.cacheDir, "input.raw")
// Temporary output file to store processed WebM data
val tempOutputFile = File(context.cacheDir, "output.webm")
// Clean up temporary files
tempInputFile.delete()
tempOutputFile.delete()
// Create temporary input file from ByteArray (as FFmpeg requires a file input)
tempInputFile.apply {
writeBytes(byteArray)
}
// Run FFmpeg command to process the audio
val command =
"-f s16le -ar $rate -ac $channels -i ${tempInputFile.absolutePath} -c:a libopus ${tempOutputFile.absolutePath}"
FFmpegKit.execute(command).also { session ->
if (session.returnCode.isValueSuccess) {
Timber.d("FFmpeg", "Audio processed and saved at ${tempOutputFile.absolutePath}")
// Convert output file to ByteArray
return tempOutputFile.inputStream().use { it.readBytes() }
} else {
Timber.e("FFmpeg", "Error in FFmpeg processing: ${session.returnCode}")
}
}
return null
}
private fun releaseResources() {
audioRecord?.stop()
audioRecord?.release()
audioRecord = null
audioEncoder?.stop()
audioEncoder?.release()
audioEncoder = null
socketManager?.disconnect()
}
fun stopStreaming() {
isStreaming = false
}
fun stopRecording() {
continueToRun = false
socketManager.disconnect()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment