Created
November 5, 2024 18:29
-
-
Save sajjadyousefnia/70d0f98f877b4615d84eb9d4406aba47 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
////*************************************************************************************************************************************************** | |
////*************************************************************************************************************************************************** | |
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