# AAC und MP3 in Ringpuffer und Lücke finden



## Seppl22 (12. Jul 2018)

Hallo zusammen,
in meinem Webradio können Nutzer Titel zu einer Aufnahmeliste hinzufügen. Das Programm hört dann auf einer gewissen Anzahl Sender mit (per Shoutcast-Stream). Da diese Informationen aber zeitverzögert sind, muss ich für jeden 'hörenden' Sender die Aufnahme puffern und falls sich dann ein Match mit einem der Nutzerwünsche ergibt, die Lücke zwischen diesen zwei Teilen finden.
So sieht mein Code bisher aus:

```
package de.dhbw.webradio.recording;

import de.dhbw.webradio.WebradioPlayer;
import de.dhbw.webradio.enumerations.FileExtension;
import de.dhbw.webradio.m3uparser.FileExtensionParser;
import de.dhbw.webradio.models.ScheduledRecord;
import de.dhbw.webradio.models.Station;
import de.dhbw.webradio.radioplayer.MetainformationReader;
import de.dhbw.webradio.radioplayer.SimpleIcyInputStreamReader;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

public class RecorderController {
    private static RecorderController recorderController = new RecorderController();
    private Recorder actualRecordNowRecorder;
    private List<ScheduledRecord> scheduledRecordList = new ArrayList<>();
    private List<Recorder> listeningRecorders = new ArrayList<>();
    private int maximumRecords = 5;

    private RecorderController() {
        initializeScheduledRecords();
    }

    public static RecorderController getInstance() {
        return recorderController;
    }

    public Recorder recordNow(URL url) {
        FileExtensionParser p = new FileExtensionParser();
        FileExtension mediaType = p.parseFileExtension(url);

        if (mediaType.equals(FileExtension.MP3)) {
            return new MP3Recorder();
        }
        if (mediaType.equals(FileExtension.AAC)) {
            return new AACRecorder();
        }
        return actualRecordNowRecorder;
    }

    private void initializeScheduledRecords() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                int i = 0;
                while (scheduledRecordList.size() <= maximumRecords && i < WebradioPlayer.getStationList().size()) {
                    Station s = WebradioPlayer.getStationList().get(i);
                    MetainformationReader r = null;
                    try {
                        r = new SimpleIcyInputStreamReader(s.getStationURL());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    Thread t = new Thread(r);
                    t.start();
                    waitToFetchIcy();
                    if (!(r.getActualMusicTitle().equalsIgnoreCase("keine informationen verfügbar"))) {
                        Recorder recorder = RecorderController.getInstance().recordNow(s.getStationURL());
                        recorder.setMetaInformationReader(r);
                        listeningRecorders.add(recorder);
                    }
                    System.err.println(i);
                    i++;
                }
                //complete initialization and start recorders
                listeningRecorders.forEach(recorder -> recorder.recordByTitle());
            }
        }).start();
    }

    private void waitToFetchIcy() {
        try {
            Thread.sleep(30000);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public Recorder getActualRecordNowRecorder() {
        return actualRecordNowRecorder;
    }

    public void setActualRecordNowRecorder(Recorder r) {
        this.actualRecordNowRecorder = r;
    }

    public List<ScheduledRecord> getScheduledRecordList() {
        return scheduledRecordList;
    }

    public void addScheduledRecord(ScheduledRecord r) {
        scheduledRecordList.add(r);
    }

    public void removeScheduledRecord(ScheduledRecord r) {
        scheduledRecordList.remove(r);
    }
}
```
Das ist der RecorderController, der alles initialisiert. Hier werden die besagten 'Zuhörer' erstellt und gestartet. In einem Recorder sieht es dann folgendermaßen aus:

```
package de.dhbw.webradio.recording;

import de.dhbw.webradio.WebradioPlayer;
import de.dhbw.webradio.gui.Gui;
import de.dhbw.webradio.id3.ID3;
import de.dhbw.webradio.id3.ID3v1;
import de.dhbw.webradio.id3.ID3v1Builder;
import de.dhbw.webradio.logger.Logger;
import de.dhbw.webradio.radioplayer.MetainformationReader;
import de.dhbw.webradio.radioplayer.PlayerFactory;
import de.dhbw.webradio.utilities.FileUtilitie;

import javax.sound.sampled.LineUnavailableException;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Calendar;
import java.util.Optional;

public class MP3Recorder implements Recorder, Runnable {
    private boolean recording = false;
    private File recorderDirectory = WebradioPlayer.getSettings().getGeneralSettings().getRecordingDirectory();
    private MetainformationReader reader = null;

    @Override
    public void recordNow(URL url) throws LineUnavailableException, IOException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                if (url == null) {
                    throw new IllegalArgumentException("URL was not valid");
                }
                File f = null;
                try {
                    HttpURLConnection con = (HttpURLConnection) url.openConnection();
                    InputStream in = con.getInputStream();
                    f = new File(recorderDirectory + "/" + generateFileName() + ".mp3");
                    Path path = Paths.get(f.toURI());
                    recording = true;
                    OutputStream out = new FileOutputStream(path.toFile());
                    byte[] buffer = new byte[4096];
                    int len;
                    long t = System.currentTimeMillis();
                    while ((len = in.read(buffer)) > 0 && (recording)) {
                        out.write(buffer, 0, len);
                    }
                    out.close();

                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (MalformedURLException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                Logger.logInfo("writing id3v1.1");
                ID3 id3 = new ID3v1Builder(createTitle().orElse("Webradio-Aufnahme"), createArtist().orElse("Kein Interpret"))
                        .setYear(String.valueOf(Calendar.getInstance().get(Calendar.YEAR)).toString())
                        .setAlbum(WebradioPlayer.getPlayer().getIcyReader().getStationName())
                        .setGenre(1)
                        .build();
                id3.writeId3Info();
                id3.writeId3ToFile(f);
                Logger.logInfo("Wrote ID3: " + id3.toString());
                Logger.logInfo("finished writing id3 for file " + f.toString());
            }
        }).start();
    }

    private Optional<String> createTitle() {
        String s = WebradioPlayer.getPlayer().getIcyReader().getActualMusicTitle();
        String title = null;
        if (s.contains("/")) {
            title = s.split("/")[0];
        } else if (s.contains("-")) {
            title = s.split("-")[0];
        }
        return Optional.ofNullable(title);
    }

    private Optional<String> createArtist() {
        String s = WebradioPlayer.getPlayer().getIcyReader().getActualMusicTitle();
        String artist = null;
        if (s.contains("/")) {
            artist = s.split("/")[1];
        } else if (s.contains("-")) {
            artist = s.split("-")[1];
        }
        return Optional.ofNullable(artist);
    }

    private String generateFileName() {
        //remove everything but characters and numbers
        return FileUtilitie.generateFileNameForRecording();
    }

    @Override
    public void recordByTitle() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                recordToBuffer();
            }
        }).start();
    }

    private void recordToBuffer() {
        /*
        while ( ! this.reader.getActualMusicTitle().equalsIgnoreCase(RecorderController.getInstance.getScheduledRecords.foreach() {
  recordIntoBuffer()    
      }
        findGapAndSaveToFile();

     */
    }

    @Override
    public void stop() {
        this.recording = false;
    }

    @Override
    public void run() {

    }

    @Override
    public boolean isRecording() {
        return recording;
    }

    @Override
    public void setMetaInformationReader(MetainformationReader r) {
        this.reader = r;
    }
}
```
Das dazugehörige Interface stelle ich hier nicht ein, alle Overrides kommen aus dem Recorder-Interface. Neben dem hier gezeigten MP3-Recorder gibt es auch noch einen AAC-Recorder, der leichte Unterschiede bei den Tags hat.
Nun stelle ich mir für mein Problem so etwas ähnliches vor, wie in der Methode recordToBuffer() beschrieben ist.
Anmerkung: Ja, die Threads mache ich im Refactoring noch über einen ExecutorService sauber : )
Wichtig: Die Audiodaten werden nicht wie bei der Wiedergabe in PCM_Unsigned dekodiert sondern direkt als MP3 bzw. AAC gespeichert. Ich denke dass das einen Einfluss auf das FInden der Lücke hat?

Kann mir bei diesem Problem jemand helfen bzw. hat sowas schonmal jemand gemacht?

Vielen Dank und viele Grüße


----------

