commit 06/11/2025

This commit is contained in:
2025-11-06 11:31:02 +07:00
parent 72c509feec
commit b24c153615
34 changed files with 719 additions and 174 deletions

View File

@@ -16,9 +16,12 @@ import contentCache
import org.tinylog.Logger
@Suppress("unused")
class AudioPlayer (var samplingrate: Int) {
class AudioPlayer (var samplingrate: Int = 44100) {
val bass: Bass = Bass.Instance
val bassenc : BassEnc = BassEnc.Instance
val bassencmp3: BassEncMP3 = BassEncMP3.Instance
val bassencopus: BassEncOpus = BassEncOpus.Instance
val bassencogg : BassEncOGG = BassEncOGG.Instance
var initedDevice = -1
@@ -28,6 +31,9 @@ class AudioPlayer (var samplingrate: Int) {
if (samplingrate<1) samplingrate = 44100 // Default sampling rate
Logger.info {"Bass version ${Integer.toHexString(bass.BASS_GetVersion())}"}
Logger.info { "BassEnc version ${Integer.toHexString(bassenc.BASS_Encode_GetVersion())}" }
Logger.info { "BassEncMP3 version ${Integer.toHexString(bassencmp3.BASS_Encode_MP3_GetVersion())}" }
Logger.info { "BassEncOpus version ${Integer.toHexString(bassencopus.BASS_Encode_OPUS_GetVersion())}" }
Logger.info {" BassEncOGG version ${Integer.toHexString(bassencogg.BASS_Encode_OGG_GetVersion())}"}
InitAudio(0) // Audio 0 is No Sound, use for reading and writing wav silently
}

View File

@@ -88,7 +88,7 @@ public interface BassEnc extends Library {
* @param length number of bytes
* @param user the user pointer passed to BASS_Encode_Start
*/
void ENCODEPROC(int encoderhandle, int channelhandle, Memory encodedData, int length, Pointer user);
void ENCODEPROC(int encoderhandle, int channelhandle, Pointer encodedData, int length, Pointer user);
}
interface ENCODEPROCEX extends Callback {
@@ -101,7 +101,7 @@ public interface BassEnc extends Library {
* @param offset file offset of the data
* @param user the user pointer passed to BASS_Encode_Start
*/
void ENCODEPROCEX(int handle, int channel, Memory buffer, int length, long offset, Object user);
void ENCODEPROCEX(int handle, int channel, Pointer buffer, int length, long offset, Pointer user);
}
interface ENCODERPROC extends Callback {
@@ -115,7 +115,7 @@ public interface BassEnc extends Library {
* @param user the user pointer passed to BASS_Encode_Start
* @return the amount of encoded data (-1 = stop)
*/
int ENCODERPROC(int encoderHandle, int channelHandle, Memory encodedData, int length, int maxOut, Pointer user);
int ENCODERPROC(int encoderHandle, int channelHandle, Pointer encodedData, int length, int maxOut, Pointer user);
}

13
src/audio/BassEncMP3.java Normal file
View File

@@ -0,0 +1,13 @@
package audio;
import com.sun.jna.Library;
@SuppressWarnings("unused")
public interface BassEncMP3 extends Library {
BassEncMP3 Instance = (BassEncMP3) com.sun.jna.Native.load("bassenc_mp3", BassEncMP3.class);
int BASS_Encode_MP3_GetVersion();
int BASS_Encode_MP3_Start(int handle, String options, int flags, BassEnc.ENCODEPROCEX proc, Object user);
int BASS_Encode_MP3_StartFile(int handle, String options, int flags, String filename);
}

15
src/audio/BassEncOGG.java Normal file
View File

@@ -0,0 +1,15 @@
package audio;
import com.sun.jna.Library;
@SuppressWarnings("unused")
public interface BassEncOGG extends Library {
BassEncOGG Instance = (BassEncOGG) com.sun.jna.Native.load("bassenc_ogg", BassEncOGG.class);
int BASS_ENCODE_OGG_RESET = 0x1000000;
int BASS_Encode_OGG_GetVersion();
int BASS_Encode_OGG_Start(int handle, String options, int flags, BassEnc.ENCODEPROC proc, Object user);
int BASS_Encode_OGG_StartFile(int handle, String options, int flags, String filename);
boolean BASS_Encode_OGG_NewStream(int handle, String options, int flags);
}

View File

@@ -0,0 +1,18 @@
package audio;
import com.sun.jna.Library;
@SuppressWarnings("unused")
public interface BassEncOpus extends Library {
BassEncOpus Instance = (BassEncOpus) com.sun.jna.Native.load("bassenc_opus", BassEncOpus.class);
int BASS_ENCODE_OPUS_RESET = 0x1000000;
int BASS_ENCODE_OPUS_CTLONLY = 0x2000000;
int BASS_Encode_OPUS_GetVersion();
int BASS_Encode_OPUS_Start(int handle, String options, int flags, BassEnc.ENCODEPROC proc, Object user);
int BASS_Encode_OPUS_StartFile(int handle, String options, int flags, String filename);
boolean BASS_Encode_OPUS_NewStream(int handle, String options, int flags);
}

126
src/audio/Mp3Encoder.kt Normal file
View File

@@ -0,0 +1,126 @@
package audio
import audioPlayer
import com.sun.jna.Memory
import com.sun.jna.Pointer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import org.tinylog.Logger
import java.util.function.Consumer
@Suppress("unused")
class Mp3Encoder(val samplingrate: Int=44100, val channels: Int=1) {
var push_handle: Int = 0
var mp3_handle: Int = 0
private val bass = audioPlayer.bass
private val bassencmp3 = audioPlayer.bassencmp3
var callback : Consumer<ByteArray>? = null
/**
* Check if the encoder is started
* @return true if started, false otherwise
*/
fun isStarted() : Boolean {
return push_handle!=0 && mp3_handle!=0
}
val proc = BassEnc.ENCODEPROCEX{
enchandle, sourcehandle, buffer, length, offset, user ->
if (enchandle == mp3_handle) {
if (sourcehandle == push_handle) {
val data = ByteArray(length)
buffer.read(0, data, 0, length)
callback?.accept(data)
} else Logger.error { "MP3 Encoder callback called with unknown source handle: $sourcehandle" }
} else Logger.error { "MP3 Encoder callback called with unknown encoder handle: $enchandle" }
}
/**
* Start the MP3 encoder
* @param cb Function to receive encoded MP3 data
*/
fun Start(cb: Consumer<ByteArray>){
callback = null
push_handle = bass.BASS_StreamCreate(samplingrate, channels, Bass.BASS_STREAM_DECODE, Pointer(-1), null)
if (push_handle!=0){
Logger.info{"MP3 Encoder initialized with sampling rate $samplingrate Hz and $channels channel(s)" }
mp3_handle = bassencmp3.BASS_Encode_MP3_Start(push_handle, null, BassEnc.BASS_ENCODE_AUTOFREE,proc, null)
if (mp3_handle!=0){
callback = cb
CoroutineScope(Dispatchers.Default).launch {
val readsize = 4*1024 // 4 K buffer
Logger.info{"MP3 Encoder started successfully." }
while(isActive && push_handle!=0){
val p = Memory(readsize.toLong())
val read = bass.BASS_ChannelGetData(push_handle, p, readsize)
if (read==-1){
val err = bass.BASS_ErrorGetCode()
Logger.error{"Failed to read data from MP3 Encoder stream. BASS error code: $err" }
break
}
delay(2)
}
Logger.info{"MP3 Encoder finished successfully." }
}
} else {
val err = bass.BASS_ErrorGetCode()
Logger.error{"Failed to start MP3 Encoder. BASS error code: $err"}
}
} else {
val err = bass.BASS_ErrorGetCode()
Logger.error{"Failed to initialize MP3 Encoder. BASS error code: $err" }
}
}
/**
* Push PCM data to be encoded
* @param data PCM data in ByteArray
* @return Number of bytes written, or 0 if failed
*/
fun PushData(data: ByteArray): Int {
if (push_handle==0){
Logger.error{"MP3 Encoder is not started." }
return 0
}
val mem = Memory(data.size.toLong())
mem.write(0, data, 0, data.size)
val written = bass.BASS_StreamPutData(push_handle, mem, data.size)
if (written==-1){
val err = bass.BASS_ErrorGetCode()
Logger.error{"Failed to push data to MP3 Encoder. BASS error code: $err" }
return 0
}
//println("MP3 Encoder: Pushed $written bytes of PCM data.")
return written
}
/**
* Stop the MP3 encoder and free resources
*/
fun Stop(){
if (push_handle!=0){
val res = bass.BASS_StreamFree(push_handle)
if (res){
Logger.info{"MP3 Encoder stream freed successfully." }
} else {
val err = bass.BASS_ErrorGetCode()
Logger.error{"Failed to free MP3 Encoder stream. BASS error code: $err"}
}
push_handle = 0
}
// auto close by BASS_ENCODE_AUTOFREE
mp3_handle = 0
callback = null
println("MP3 Encoder: Stopped.")
}
}

127
src/audio/OpusEncoder.kt Normal file
View File

@@ -0,0 +1,127 @@
package audio
import audioPlayer
import com.sun.jna.Memory
import com.sun.jna.Pointer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import org.tinylog.Logger
import java.util.function.Consumer
@Suppress("unused")
class OpusEncoder(val samplingrate: Int=44100, val channels: Int=1) {
var push_handle: Int = 0
var opus_handle: Int = 0
private val bass = audioPlayer.bass
private val bassencopus = audioPlayer.bassencopus
var callback : Consumer<ByteArray>? = null
/**
* Check if the encoder is started
* @return true if started, false otherwise
*/
fun isStarted() : Boolean {
return push_handle!=0 && opus_handle!=0
}
val proc = BassEnc.ENCODEPROC{
enchandle, sourcehandle, buffer, length, user ->
if (enchandle == opus_handle) {
if (sourcehandle == push_handle) {
val data = ByteArray(length)
buffer.read(0, data, 0, length)
callback?.accept(data)
//println("MP3 Encoder callback: Sent $length bytes")
} else Logger.error { "Opus Encoder callback called with unknown source handle: $sourcehandle" }
} else Logger.error { "Opus Encoder callback called with unknown encoder handle: $enchandle" }
}
/**
* Start the Opus encoder
* @param cb Function to receive encoded MP3 data
*/
fun Start(cb: Consumer<ByteArray>){
callback = null
push_handle = bass.BASS_StreamCreate(samplingrate, channels, Bass.BASS_STREAM_DECODE, Pointer(-1), null)
if (push_handle!=0){
Logger.info{"Opus Encoder initialized with sampling rate $samplingrate Hz and $channels channel(s)" }
opus_handle = bassencopus.BASS_Encode_OPUS_Start(push_handle, null, BassEnc.BASS_ENCODE_AUTOFREE, proc, null)
if (opus_handle!=0){
callback = cb
CoroutineScope(Dispatchers.Default).launch {
val readsize = 8 * 1024 * 1024 // 8 MB buffer
Logger.info{"Opus Encoder started successfully." }
while(isActive && push_handle!=0){
delay(2)
val p = Memory(readsize.toLong())
val read = bass.BASS_ChannelGetData(push_handle, p, readsize)
if (read==-1){
val err = bass.BASS_ErrorGetCode()
Logger.error{"Failed to read data from Opus Encoder stream. BASS error code: $err" }
break
}
}
Logger.info{"Opus Encoder finished successfully." }
}
} else {
val err = bass.BASS_ErrorGetCode()
Logger.error{"Failed to start Opus Encoder. BASS error code: $err"}
}
} else {
val err = bass.BASS_ErrorGetCode()
Logger.error{"Failed to initialize Opus Encoder. BASS error code: $err" }
}
}
/**
* Push PCM data to be encoded
* @param data PCM data in ByteArray
* @return Number of bytes written, or 0 if failed
*/
fun PushData(data: ByteArray): Int {
if (push_handle==0){
Logger.error{"Opus Encoder is not started." }
return 0
}
val mem = Memory(data.size.toLong())
mem.write(0, data, 0, data.size)
val written = bass.BASS_StreamPutData(push_handle, mem, data.size)
if (written==-1){
val err = bass.BASS_ErrorGetCode()
Logger.error{"Failed to push data to Opus Encoder. BASS error code: $err" }
return 0
}
//println("Opus Encoder: Pushed $written bytes of PCM data.")
return written
}
/**
* Stop the Opus encoder and free resources
*/
fun Stop(){
if (push_handle!=0){
val res = bass.BASS_StreamFree(push_handle)
if (res){
Logger.info{"Opus Encoder stream freed successfully." }
} else {
val err = bass.BASS_ErrorGetCode()
Logger.error{"Failed to free Opus Encoder stream. BASS error code: $err"}
}
push_handle = 0
}
// auto close by BASS_ENCODE_AUTOFREE
opus_handle = 0
callback = null
println("Opus Encoder: Stopped.")
}
}