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? = 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){ 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)" } val options = "lame -b 128 --cbr --write-xing 0 --nohist --noid3v2 - -" //val flag = BassEnc.BASS_ENCODE_AUTOFREE or BassEnc.BASS_ENCODE_NOHEAD val flag = BassEnc.BASS_ENCODE_AUTOFREE mp3_handle = bassencmp3.BASS_Encode_MP3_Start(push_handle, options, flag,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 } 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 } }