Java FXML sound recorder can't drain target data line

Viewed 100

Edit: I found a solution by changing just a few lines, problem was only partly due to bad loops. This was the bulk of the issue:

'''

public void recordStop(){
    System.out.println("stop recording");
    recordingRunning = false;
    if (dataLine != null) {
        dataLine.flush();
        dataLine.close();
        System.out.println("close and drain line");
    }


}

'''

using .drain() kills the thread so just used .flush() instead and we chilling.

(TLDR - when I press a button to drain a target data line, the function called will try to drain the line in but the GUI will crash and no file is output)

I've been making an audio recorder with JavaFXML, using the java sound sampled class. I'm using a separate thread to run the recorder while the GUI is running but the issue comes when trying to stop the recording and save the recording. I've managed to get the thread to start okay and run on a button press. This is activated by this code:

The FXML event handler when button is pressed:

'''

    @FXML
    private void recordingButtonAction(ActionEvent event) throws LineUnavailableException {
    if(isRecording == false){
    label.setText("Recording");
    fileName = RT.setFileName();
   channelLocationTracker(channelSelectToggleGroup);
   try{
    TargetDataLine dataLine =AudioSystem.getTargetDataLine(audioFormat);
   }
   catch (LineUnavailableException ex) {
       ex.printStackTrace();
   }
   isRecording=true;
    }
    else{
        System.out.println("Already recording");
    }
}

'''

The code to start recording in seperate class where object "RT" comes from:

'''

    public TargetDataLine getDataLine() throws LineUnavailableException{
    audioFormat = getAudioFormat();
    DataLine.Info info = new DataLine.Info(TargetDataLine.class, audioFormat);
    if (!AudioSystem.isLineSupported(info)) {
        throw new LineUnavailableException(
                "Format Unsupported");
    }

    final TargetDataLine dataLine = AudioSystem.getTargetDataLine(audioFormat);;
    AudioSystem.getLine(info);
    return dataLine;
    }

''' '''

    public void recordStart(boolean isRecording) throws LineUnavailableException{

    isRecording = false;
    audioFormat = getAudioFormat();
    dataLine = getDataLine();
    dataLine.open(audioFormat);
    dataLine.start();

    System.out.println("open line");

    System.out.println("start recording");
    byte[] buffer = new byte[bufferSz];
    int bytesRead = 0;

    recordedBytes = new ByteArrayOutputStream();
    isRecording = true;

    while (isRecording) {
        bytesRead = getDataLine().read(buffer, 0, buffer.length);
        recordedBytes.write(buffer, 0, bytesRead);
    }
}

'''

And the thread itself:

'''

    private void startRecord(){
    Thread startRec = new Thread(new Runnable(){
        public void run(){
            try {
                isRecording=true;
                RT.recordStart(isRecording);
            } catch (LineUnavailableException ex) {
                Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex);
            }
         }
       });
      startRec.start();
     }

'''

Issue arises when I press the stop recording button, it will print drain line in the shell but the GUI will crash and recording stopped will not be printed. I think the issue comes from the .drain() method.

Once again here's the FXML:

'''

    @FXML
    private void stopButtonAction(ActionEvent event) throws IOException, LineUnavailableException, 
    UnsupportedAudioFileException {    
       RT.setFileName("recording"+".wav");
       recordedFileWAV = RT.getFile();
       if(isRecording == true){
       endRecord();
       }

    }

'''

Separate class for stopping recording:

'''

    public void recordStop(boolean isRecording){
    isRecording = false;
    System.out.println("drain line");
    dataLine.drain();
    System.out.println("stop recording");
    dataLine.close();


    }

'''

And the method in the recording class which let's you save:

'''

    public void recordSave(File wavFile) throws IOException {
    byte[] lineData = recordedBytes.toByteArray();
    ByteArrayInputStream bais = new ByteArrayInputStream(lineData);
    AudioInputStream AIS = new AudioInputStream(bais, audioFormat,lineData.length / 
    audioFormat.getFrameSize());

    AudioSystem.write(AIS, AudioFileFormat.Type.WAVE, wavFile);

    AIS.close();
    recordedBytes.close();
    }

Please let me know if you can figure out what's going wrong or if you can point me in the right direction :)

2 Answers

The following advice is more on general principals and best practices. I've not taken the time to inspect the details of your code, beyond reacting to what I see in your public methods for stopping and saving.

You have recording taking place in its own thread (as is should). The usual way to interact with another thread is to use loose coupling. Thus, in your public stop and save methods, limit yourself to setting a flag, such as isRecording. Then, have the code in the thread that is controlling the playback consult that boolean. For example, have an outer loop while(isRecording).

I've had success with this use of the loose-coupling design pattern while using JavaFX with javax.sound.sampled, for both controlling playback and for saving to file. Doing things this way helps clarify confusions and conflicts that can occur between the classes, instances, and threads.

Hopefully this points you in a direction that will unravel the problems you are having.

Solution:

'''

public void recordStop(){
System.out.println("stop recording");
recordingRunning = false;
if (dataLine != null) {
    dataLine.flush();
    dataLine.close();
    System.out.println("close and drain line");
}

}

'''

Related