← cd ../blog

Redarea MP3-urilor corupte pe Android: Dincolo de Expo

Redarea MP3-urilor corupte pe Android: Dincolo de Expo — imagine de copertă

Recent, în cadrul proiectului campus-book-player, m-am lovit de o problemă frustrantă: o colecție de audiobook-uri vechi refuza categoric să fie redată prin expo-av. Log-urile de sistem arătau erori de tip MEDIA_ERROR_DECODE sau MEDIA_ERROR_MALFORMED. După câteva ore de debugging, am realizat că headerele ID3v2 erau corupte, iar frame-urile VBR aveau un padding care inducea în eroare parserul nativ al Android-ului.

De ce eșuează ExpoPlayer (și MediaCodec)?

expo-av folosește în spate ExoPlayer (sau MediaCodec direct pe Android). Acestea sunt optimizate pentru viteză și eficiență, nu pentru toleranță la erori. Când întâlnesc un header ID3v2 malformat sau un sync word care nu se aliniază perfect, parserul abandonează operațiunea pentru a preveni crash-urile de memorie. Dacă fișierul are “garbage bytes” la început sau frame-uri VBR prost encodate, MediaCodec va refuza pur și simplu să inițializeze track-ul.

Soluția: Parsarea manuală și sincronizarea

Pentru a salva aceste fișiere, trebuie să sărim peste layer-ul de abstractizare și să găsim noi frame-urile MP3. Un fișier MP3 este, în esență, un stream de frame-uri care încep cu un “sync word” de 11 biți setat la 0xFFE0.

Iată cum scanez un buffer pentru a găsi punctul de intrare valid:

// Căutare sync word 0xFFE0 pentru a ignora corupția inițială
private int findSyncWord(ByteBuffer buffer) {
    while (buffer.remaining() >= 2) {
        int header = buffer.getShort() & 0xFFFF;
        if ((header & 0xFFE0) == 0xFFE0) {
            return buffer.position() - 2;
        }
    }
    return -1;
}

Pipeline-ul de fallback: Strategia în 3 pași

Nu putem renunța la eficiența expo-av pentru fișierele sănătoase. Am implementat un pipeline în campus-book-player care arată astfel:

  1. Expo-AV: Încercăm redarea standard. Dacă returnează onError, trecem la pasul 2.
  2. Native MediaCodec: Dacă fișierul este valid dar are header corupt, forțăm decodarea brută prin AudioTrack.
  3. Software Decoder (mpg123): Dacă totul eșuează, folosim libmpg123 via expo-module pentru decodare software.

Implementare nativă cu Expo Module

Pentru a integra mpg123 în React Native, am creat un modul nativ care extrage datele PCM și le trimite către un buffer AudioTrack:

// Inițializare AudioTrack după parsarea primului frame valid
val audioTrack = AudioTrack.Builder()
    .setAudioFormat(AudioFormat.Builder()
        .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
        .setSampleRate(44100)
        .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
        .build())
    .build()

audioTrack.play()
// Aici trimitem frame-urile decodate manual prin mpg123_decode

Benchmark: Nativ vs Software

Metodă Consum CPU Latență Stabilitate
Expo-AV (MediaCodec) Foarte mic Mică Scăzută (pe fișiere corupte)
Manual MediaCodec Mediu Medie Ridicată
mpg123 (Software) Ridicat Mare Maximă

Concluzie

Deși mpg123 consumă mai multă baterie, este “colacul de salvare” pentru fișierele vechi. Strategia mea a fost să rescriu headerele ID3v2 în memorie imediat ce am găsit primul frame valid, făcând fișierul compatibil cu player-ele standard pentru redările ulterioare. Dacă lucrezi la o aplicație de tip audio-player, nu te baza doar pe framework-urile de nivel înalt; un pic de control la nivel de byte îți va salva utilizatorii de la erori frustrante.