commit 28/07/2025

This commit is contained in:
2025-07-28 13:55:58 +07:00
parent 9b1851aaa7
commit 901d553da9
4 changed files with 180 additions and 1 deletions

View File

@@ -191,7 +191,6 @@ class AudioPlayer (var samplingrate: Int) {
thread.isDaemon = true thread.isDaemon = true
thread.start() thread.start()
} }

View File

@@ -0,0 +1,153 @@
package audio
import audio.Bass.BASS_STREAMPROC_END
import audio.BassEnc.BASS_ENCODE_PCM
import org.tinylog.Logger
import java.io.PipedInputStream
import java.io.PipedOutputStream
import java.net.DatagramPacket
import java.net.DatagramSocket
import java.net.InetSocketAddress
import java.util.function.BiConsumer
import java.util.function.Consumer
@Suppress("unused")
/**
* UDPReceiverToFile is a class that listens for UDP packets on a specified address and port
* and writes the received data to a specified file.
* It is designed to run in a separate thread and can be stopped when no longer needed.
* @param listeningAddress The address to listen for incoming UDP packets.
* @param listeningPort The port to listen for incoming UDP packets.
* @param samplingrate The sampling rate for the audio data, default is 44,100 Hz.
* @param channel The number of audio channels, default is 1 (mono).
* @param outputFilePath The path to the file where the received data will be written.
* @param senderIP The IP address of the sender from which to accept packets.
*/
class UDPReceiverToFile(listeningAddress: String, listeningPort: Int, val samplingrate: Int=44100, val channel: Int=1, val outputFilePath: String, val senderIP: String) {
private var socket: DatagramSocket? = null
private val bass : Bass = Bass.Instance
private val bassenc : BassEnc = BassEnc.Instance
private var isReceiving: Boolean = false
private val pipeIn= PipedInputStream(16*1024) // 16K
var isReady: Boolean = false; private set
var bytesReceived: Long = 0; private set
var bytesWritten: Long = 0; private set
init {
try{
val socketaddress = InetSocketAddress(listeningAddress, listeningPort)
socket = DatagramSocket(socketaddress)
isReady = true
} catch (e : Exception) {
Logger.error {"Failed to create UDP socket: ${e.message}" }
}
}
private val streamProc = Bass.STREAMPROC { handle, buffer, length, user ->
try{
val dd = ByteArray(length)
val bytesread = pipeIn.read(dd)
if (bytesread>0){
buffer?.write(0, dd, 0, bytesread) // Write the data to the buffer
bytesWritten += bytesread
// Return the number of bytes read
bytesread
} else {
// if bytesread is 0, it means the pipe is empty, return BASS_STREAMPROC_END
BASS_STREAMPROC_END
}
} catch (e : Exception){
// If an error occurs, log it and return BASS_STREAMPROC_END
Logger.error { "STREAMPROC exception on UDPReceiverToFile $senderIP $outputFilePath" }
BASS_STREAMPROC_END
}
}
/**
* Starts receiving data from the UDP socket and writing it to the specified file.
* This method runs in a separate thread.
* @param callback A BiConsumer that accepts a Boolean indicating success or failure and a String message.
* @param udpIsReceiving A Consumer that accepts a Boolean indicating whether UDP is currently receiving
*/
fun startReceiving(callback : BiConsumer<Boolean, String>, udpIsReceiving: Consumer<Boolean>) {
var isReceiving = false
if (isReady){
val thread1 = Thread{
Logger.info { "UDPReceiverToFile started, listening on ${socket?.localSocketAddress} , saving to $outputFilePath" }
PipedOutputStream(pipeIn).use { pipeOut ->
while (isReceiving) {
try{
val xx = DatagramPacket(ByteArray(1500),1500)
socket?.receive(xx)
if (xx.address.hostAddress!= senderIP) continue
if (xx.length < 1) continue
pipeOut.write(xx.data, 0, xx.length)
bytesReceived += xx.length
if (!isReceiving){
isReceiving = true
udpIsReceiving.accept(true)
}
} catch (e : Exception){
Logger.error { "Error receiving UDP packet: ${e.message}" }
continue
}
}
}
Logger.info { "UDPReceiverToFile ended" }
}
thread1.name = "UDPReceiverToFile Thread1 $senderIP $outputFilePath"
thread1.isDaemon = true
thread1.start()
val thread2 = Thread{
bass.BASS_SetDevice(0) // Set to No Sound device, we are not playing audio
val streamhandle = bass.BASS_StreamCreate(samplingrate, channel, 0, streamProc, null)
if (streamhandle!=0){
bass.BASS_ChannelPlay(streamhandle,false)
val encodehandle = bassenc.BASS_Encode_Start(streamhandle, outputFilePath, BASS_ENCODE_PCM, null, null)
if (encodehandle!=0){
Logger.info { "UDPReceiverToFile started writing to $outputFilePath" }
callback.accept(true, "UDPReceiverToFile started successfully, writing to $outputFilePath")
while (isReceiving) {
try {
Thread.sleep(1000) // Sleep to avoid busy waiting
} catch (e: InterruptedException) {
Logger.error { "UDPReceiverToFile thread interrupted: ${e.message}" }
break
}
}
bassenc.BASS_Encode_Stop(encodehandle)
bass.BASS_StreamFree(streamhandle)
Logger.info { "UDPReceiverToFile stopped writing to $outputFilePath" }
callback.accept(false, "UDPReceiverToFile stopped successfully, written bytes: $bytesWritten")
} else {
callback.accept(false, "Failed to start encoding: ${bass.BASS_ErrorGetCode()}")
return@Thread
}
} else {
callback.accept(false, "Failed to create stream: ${bass.BASS_ErrorGetCode()}")
return@Thread
}
}
thread2.name = "UDPReceiverToFile Thread2 $senderIP $outputFilePath"
thread2.isDaemon = true
thread2.start()
} else callback.accept(false, "UDPReceiverToFile is not ready. Check if the socket was created successfully.")
}
/**
* Stop UDPReceiverToFile from receiving data.
*/
fun stopReceiving() {
isReceiving = false
}
}

14
src/codes/QuadConsumer.kt Normal file
View File

@@ -0,0 +1,14 @@
package codes
@Suppress("unused")
interface QuadConsumer<A,B,C,D> {
/**
* Performs this operation on the given arguments.
*
* @param a the first input argument
* @param b the second input argument
* @param c the third input argument
* @param d the fourth input argument
*/
fun accept(a: A, b: B, c: C, d: D)
}

13
src/codes/TriConsumer.kt Normal file
View File

@@ -0,0 +1,13 @@
package codes
@Suppress("unused")
interface TriConsumer<A,B,C> {
/**
* Performs this operation on the given arguments.
*
* @param a the first input argument
* @param b the second input argument
* @param c the third input argument
*/
fun accept(a: A, b: B, c: C)
}