Created
October 31, 2024 16:50
-
-
Save sajjadyousefnia/5dfd60c372c655f6a1327e18fd517f40 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
package com.sajjady.recorder.Socket | |
import android.annotation.SuppressLint | |
import android.content.Context | |
import android.media.AudioFormat | |
import android.media.AudioRecord | |
import android.media.MediaCodec | |
import android.media.MediaFormat | |
import android.media.MediaRecorder | |
import io.socket.client.Socket | |
class AudioStreamer( | |
private val context: Context, | |
private val onLiveTextChangeListener: OnLiveTextChangeListener | |
/*, private val captureUUID: String*/ | |
) { | |
private var audioRecord: AudioRecord? = null | |
private var audioEncoder: MediaCodec? = null | |
private var socket: Socket? = null | |
// private val serverUrl = AppConstants.apiBaseURL | |
private val sampleRate = 16000 //44100 | |
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) { | |
} | |
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 | |
// thread(start = 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() | |
} | |
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, 16000 /*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) | |
val bufferInfo = MediaCodec.BufferInfo() | |
while (isStreaming) { | |
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() | |
/* encode( | |
dataToSend, | |
dataToSend.size, | |
bufferInfo | |
)*/ // Encodes before sending, if needed | |
// Clear accumulatedData and reset the timer | |
accumulatedData.clear() | |
lastSendTime = currentTime | |
// encode(inputBuffer, readResult, bufferInfo) | |
socketManager.emitAudioData(dataToSend!!) | |
} | |
} | |
} | |
audioEncoder?.signalEndOfInputStream() | |
releaseResources() | |
} | |
private fun releaseResources() { | |
audioRecord?.stop() | |
audioRecord?.release() | |
audioRecord = null | |
audioEncoder?.stop() | |
audioEncoder?.release() | |
audioEncoder = null | |
socket?.disconnect() | |
} | |
fun stopStreaming() { | |
isStreaming = false | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment