Last active
March 16, 2025 00:39
-
-
Save LucasAlfare/5540075fdea7f4e94c4d4ba0bb59c253 to your computer and use it in GitHub Desktop.
Custom MP3 decoder from Scratch in Kotlin // Rascunho de decodificador de MP3 PRÓPRIO do ZERO
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
// TOTALMENTE feito com IA (até o momento) | |
@file:OptIn(ExperimentalUnsignedTypes::class) | |
package com.lucasalfare.flplayer.player.main | |
import com.lucasalfare.flbinary.Reader // tá no meu repositório! | |
import java.io.File | |
import javax.sound.sampled.AudioFormat | |
import javax.sound.sampled.AudioSystem | |
import javax.sound.sampled.DataLine | |
import javax.sound.sampled.SourceDataLine | |
fun main() { | |
val bytes = File("a.mp3").readBytes().toUByteArray() | |
val r = Reader(bytes) | |
// Pular ID3 tag se existir | |
if (r.position + 3 < bytes.size && bytes[0].toInt() == 0x49 && bytes[1].toInt() == 0x44 && bytes[2].toInt() == 0x33) { | |
println("ID3 tag encontrada. Pulando...") | |
val id3Size = | |
(bytes[6].toInt() shl 21) or (bytes[7].toInt() shl 14) or (bytes[8].toInt() shl 7) or (bytes[9].toInt()) | |
r.position += 10 + id3Size | |
} | |
// Lista para armazenar os dados PCM decodificados | |
val pcmData = mutableListOf<Byte>() | |
while (r.position < bytes.size - 4) { // Precisa de pelo menos 4 bytes para um header | |
val header = r.read4Bytes().toInt() | |
// Verificar sync (11 bits) | |
if ((header shr 21) and 0x7FF != 0x7FF) { | |
r.position -= 3 // Re-alinhar busca | |
continue | |
} | |
// Extrair campos do header | |
val mpegVersion = (header shr 19) and 0x3 | |
val layer = (header shr 17) and 0x3 | |
val bitrateIndex = (header shr 12) and 0xF | |
val sampleRateIndex = (header shr 10) and 0x3 | |
val padding = (header shr 9) and 0x1 | |
val crcPresent = (header shr 16) and 0x1 == 0 | |
// Validações | |
if (layer != 0x1 || bitrateIndex == 0 || bitrateIndex == 0xF || sampleRateIndex == 0x3) { | |
r.position -= 3 | |
continue | |
} | |
// Mapear bitrate | |
val bitrate = when (mpegVersion) { | |
0x3 -> when (bitrateIndex) { // MPEG1 | |
1 -> 32 | |
2 -> 40 | |
3 -> 48 | |
4 -> 56 | |
5 -> 64 | |
6 -> 80 | |
7 -> 96 | |
8 -> 112 | |
9 -> 128 | |
10 -> 160 | |
11 -> 192 | |
12 -> 224 | |
13 -> 256 | |
14 -> 320 | |
else -> 0 | |
} | |
0x2 -> when (bitrateIndex) { // MPEG2 | |
1 -> 8 | |
2 -> 16 | |
3 -> 24 | |
4 -> 32 | |
5 -> 40 | |
6 -> 48 | |
7 -> 56 | |
8 -> 64 | |
9 -> 80 | |
10 -> 96 | |
11 -> 112 | |
12 -> 128 | |
13 -> 144 | |
14 -> 160 | |
else -> 0 | |
} | |
0x0 -> when (bitrateIndex) { // MPEG2.5 | |
1 -> 8 | |
2 -> 16 | |
3 -> 24 | |
4 -> 32 | |
5 -> 40 | |
6 -> 48 | |
7 -> 56 | |
8 -> 64 | |
9 -> 80 | |
10 -> 96 | |
11 -> 112 | |
12 -> 128 | |
13 -> 144 | |
14 -> 160 | |
else -> 0 | |
} | |
else -> 0 | |
} | |
// Mapear sample rate | |
val sampleRate = when (mpegVersion) { | |
0x3 -> when (sampleRateIndex) { // MPEG1 | |
0 -> 44100 | |
1 -> 48000 | |
2 -> 32000 | |
else -> 0 | |
} | |
0x2 -> when (sampleRateIndex) { // MPEG2 | |
0 -> 22050 | |
1 -> 24000 | |
2 -> 16000 | |
else -> 0 | |
} | |
0x0 -> when (sampleRateIndex) { // MPEG2.5 | |
0 -> 11025 | |
1 -> 12000 | |
2 -> 8000 | |
else -> 0 | |
} | |
else -> 0 | |
} | |
// Calcular o tamanho do frame | |
val frameLength = ((144 * bitrate * 1000) / sampleRate) + padding | |
// Verificar CRC, se presente | |
if (crcPresent) { | |
val crc = r.read2Bytes() // Ler os 16 bits do CRC | |
val calculatedCRC = calculateCRC(bytes, r.position - 4 - 2, frameLength - 4 - 2) // Calcular CRC | |
if (crc != calculatedCRC) { | |
println("CRC inválido! Esperado: $calculatedCRC, Encontrado: $crc") | |
r.position -= 2 // Voltar para tentar realinhar | |
continue | |
} | |
} | |
println("Frame header válido encontrado em ${r.position - 4}!") | |
println("Frame length: $frameLength bytes") | |
println("Bitrate: $bitrate kbps") | |
println("Sample rate: $sampleRate Hz") | |
// Ler os dados do frame (excluindo o header e o CRC, se presente) | |
val frameData = bytes.copyOfRange(r.position, r.position + frameLength - 4 - if (crcPresent) 2 else 0) | |
// Processar o frame (aqui você implementará a decodificação) | |
val pcmFrame = decodeFrame( | |
frameData = frameData, | |
mpegVersion = mpegVersion, | |
layer = layer, | |
bitrate = bitrate, | |
sampleRate = sampleRate | |
) | |
// Adicionar os dados PCM decodificados à lista | |
pcmData.addAll(pcmFrame.toList()) | |
// Pular para o próximo frame | |
r.position += frameLength - 4 // Subtrair 4 bytes do header já lido | |
println() | |
} | |
// Converter a lista de PCM para um array de bytes | |
val pcmByteArray = pcmData.toByteArray() | |
// Agora você pode salvar ou reproduzir pcmByteArray | |
println("Decodificação concluída! Total de bytes PCM: ${pcmByteArray.size}") | |
// Reproduzir o PCM | |
playPCM(pcmByteArray, 44100f, 2) // Altere o sample rate e canais conforme necessário | |
} | |
// Função para reproduzir o PCM | |
fun playPCM(pcmByteArray: ByteArray, sampleRate: Float, channels: Int) { | |
val audioFormat = AudioFormat(sampleRate, 16, channels, true, false) | |
val info = DataLine.Info(SourceDataLine::class.java, audioFormat) | |
val audioLine = AudioSystem.getLine(info) as SourceDataLine | |
try { | |
audioLine.open(audioFormat) | |
audioLine.start() | |
audioLine.write(pcmByteArray, 0, pcmByteArray.size) | |
audioLine.drain() | |
audioLine.close() | |
} catch (e: Exception) { | |
e.printStackTrace() | |
} | |
} | |
// Função para calcular CRC-16 (implementação simplificada) | |
fun calculateCRC(data: UByteArray, start: Int, length: Int): Int { | |
var crc = 0xFFFF | |
for (i in start until start + length) { | |
crc = crc xor (data[i].toInt() shl 8) | |
for (j in 0 until 8) { | |
crc = if (crc and 0x8000 != 0) { | |
(crc shl 1) xor 0x1021 | |
} else { | |
crc shl 1 | |
} | |
} | |
} | |
return crc and 0xFFFF | |
} | |
fun decodeFrame(frameData: UByteArray, mpegVersion: Int, layer: Int, bitrate: Int, sampleRate: Int): ByteArray { | |
// 1. Extrair informações adicionais do frame (side info) | |
val sideInfo = extractSideInfo(frameData, mpegVersion, layer) | |
// 2. Decodificação Huffman | |
val coefficients = decodeHuffman(frameData, sideInfo) | |
// 3. Requantização | |
val scaleFactors = extractScaleFactors(frameData, sideInfo) // Função fictícia para extrair fatores de escala | |
val requantizedCoefficients = requantize(coefficients, scaleFactors) | |
// 4. Aplicar IMDCT | |
val timeDomainSamples = applyIMDCT(requantizedCoefficients) | |
// 5. Reordenação e síntese de sub-bandas | |
val pcmSamples = reorderAndSynthesize(timeDomainSamples) | |
// 6. Converter para PCM (16 bits) | |
val pcmByteArray = convertToPCM(pcmSamples) | |
return pcmByteArray | |
} | |
// Função fictícia para extrair side info (informações adicionais do frame) | |
fun extractSideInfo(frameData: UByteArray, mpegVersion: Int, layer: Int): Map<String, Any> { | |
// Implementação aqui | |
return mapOf() | |
} | |
// Função fictícia para extrair fatores de escala | |
fun extractScaleFactors(frameData: UByteArray, sideInfo: Map<String, Any>): FloatArray { | |
// Implementação aqui | |
return floatArrayOf() | |
} | |
// 1. Decodificação Huffman | |
/** | |
* Decodifica os dados Huffman do frame. | |
* @param frameData Os dados brutos do frame. | |
* @param sideInfo Informações adicionais do frame (como tabelas Huffman). | |
* @return Um array de coeficientes de frequência (ints). | |
*/ | |
fun decodeHuffman(frameData: UByteArray, sideInfo: Map<String, Any>): IntArray { | |
// Implementação aqui | |
return intArrayOf() | |
} | |
// 2. Requantização | |
/** | |
* Aplica a requantização nos coeficientes de frequência. | |
* @param coefficients Coeficientes de frequência decodificados. | |
* @param scaleFactors Fatores de escala para requantização. | |
* @return Coeficientes de frequência requantizados (floats). | |
*/ | |
fun requantize(coefficients: IntArray, scaleFactors: FloatArray): FloatArray { | |
// Implementação aqui | |
return floatArrayOf() | |
} | |
// 3. Transformada Inversa de Fourier (IMDCT) | |
/** | |
* Aplica a Transformada Inversa de Fourier (IMDCT) nos coeficientes de frequência. | |
* @param coefficients Coeficientes de frequência requantizados. | |
* @return Amostras no domínio do tempo (floats). | |
*/ | |
fun applyIMDCT(coefficients: FloatArray): FloatArray { | |
// Implementação aqui | |
return floatArrayOf() | |
} | |
// 4. Reordenação e Síntese de Sub-bandas | |
/** | |
* Reordena e sintetiza as sub-bandas para reconstruir o sinal PCM. | |
* @param timeDomainSamples Amostras no domínio do tempo. | |
* @return Amostras PCM reconstruídas (floats). | |
*/ | |
fun reorderAndSynthesize(timeDomainSamples: FloatArray): FloatArray { | |
// Implementação aqui | |
return floatArrayOf() | |
} | |
// 5. Conversão para PCM | |
/** | |
* Converte as amostras PCM de float para o formato de bytes (16 bits). | |
* @param pcmSamples Amostras PCM em float. | |
* @return Amostras PCM em formato de bytes (16 bits, little-endian). | |
*/ | |
fun convertToPCM(pcmSamples: FloatArray): ByteArray { | |
// Implementação aqui | |
return byteArrayOf() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment