-1

(sorry, not an english speaker, expect lots of grammatical/syntactical error)

I'm developing a piece of software to manage D-Link Ip Cam (DCS-xxxx series and other). Because this camera expose an audio stream (some model even have a speaker for bidirectional communication), i would like to play it at user request.

All entry point are behind a http basic authentication (but weirdly enough i cant use http:\USER:PASS@192.168.1.100, because i get a 401).

I use the javax.sound.* package to do that, but for some reason the audio start playing after 15 to 20 seconds, with a total delay of 30-40 seconds EDIT: 45 seconds in average, but the audio is played from the beginning, so its even worse.

This is the class (bare minimum, just for testing purpose)

import java.io.IOException;
import java.net.Authenticator;
import java.net.MalformedURLException;
import java.net.PasswordAuthentication;
import java.net.URL;

import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;

public class AudioPlayer implements Runnable{

    private URL URL;
    private String USERNAME;
    private String PASSWORD;

    private volatile boolean stop = false;

    public AudioPlayer(String url, String user, String pass) throws MalformedURLException{
        this.URL = new URL(url);
        this.USERNAME = user;
        this.PASSWORD = pass;
    }

    public void shutdown() {
        stop = true;
    }

    @Override
    public void run() {
        Authenticator.setDefault (new Authenticator() {
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication (USERNAME, PASSWORD.toCharArray());
            }
        });

        try {
            Clip clip = AudioSystem.getClip();
            AudioInputStream inputStream = AudioSystem.getAudioInputStream(URL);
            clip.open(inputStream);
            clip.start();
            while(!stop && clip.isRunning()) {}
            clip.stop();
            System.err.println("AUDIO PLAYER STOPPED");
        } catch (LineUnavailableException | IOException | UnsupportedAudioFileException e) {
            e.printStackTrace();
        }
    }

}

The Authenticator part is needed because ipcam use basic http autentication.

I've read somewhere that the AudioSystem make several pass with different algotithm to get the right one, then will reset the stream to the beginning and only then start to play. So, because of this, maybe AudioSystem have some problem to realize what type of codec to use (maybe need some kind of header) and spent quite some time before start playing the audio.

It's worth to know that even VLC struggle to keep up with the streaming, losing up to 8 seconds before playing (8 seconds is way better than 20). The IpCam is on a local network.

There is something wrong with my code? Some method i don't see?

Really don't know where to look with this one.

I was unable to find any meaningful answer here or elsewhere.

PRiM
  • 104
  • 6
  • you could check where the delay happens is it to get the audioinputstream? the clip? – gpasch Apr 13 '19 at 21:20
  • It's even worse now idk why: it take **3 second** to execute `AudioSystem.getAudioInputStream`, and an average of **45 seconds** to execute `clip.open(inputStream)`. – PRiM Apr 13 '19 at 22:04

1 Answers1

0

After fiddling with one answer, i've found the solution, wich provide 1 to 2 seconds delay (wich is the same delay of the official app or the web page configuration, so pretty much perfect).

private void playStreamedURL() throws IOException {

        //to avoid 401 error
        Authenticator.setDefault (new Authenticator() {
            protected PasswordAuthentication getPasswordAuthentication() {
                //USERNAME and PASSWORD are defined in the class
                return new PasswordAuthentication (USERNAME, PASSWORD.toCharArray()); 
            }
        });

        AudioInputStream AIS = null;
        SourceDataLine line = null;

        try {
//get the input stream
            AIS = AudioSystem.getAudioInputStream(this.URL);

//get the format, Very Important!
            AudioFormat format = AIS.getFormat();
            DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);

//create the output line
            line = (SourceDataLine) AudioSystem.getLine(info);
//open the line with the specified format (other solution manually create the format
//and thats is a big problem because things like sampleRate aren't standard
//For example, the IpCam i use for testing use 11205 as sample rate.
            line.open(format);

            int framesize = format.getFrameSize();

//NOT_SPECIFIED is -1, wich create problem with the buffer definition, so it's revalued if necessary
            if(framesize == AudioSystem.NOT_SPECIFIED)
                framesize = 1;

//the buffer used to read and write bytes from stream to audio line
            byte[] buffer = new byte[4 * 1024 * framesize];
            int total = 0;

            boolean playing = false;
            int r, towrite, remaining;
            while( (r = AIS.read(buffer, total, buffer.length - total)) >= 0 ) { //or !=-1
                total += r;

//avoid start the line more than one time
                if (!playing) {
                    line.start();
                    playing = true;
                }

//actually play the sound (the frames in the buffer)
                towrite = (total / framesize) * framesize;
                line.write(buffer, 0, towrite);

//if some byte remain, overwrite them into the buffer and change the total
                remaining = total - towrite;
                if (remaining > 0)
                    System.arraycopy(buffer, towrite, buffer, 0, remaining);
                total = remaining;
            }

//line.drain() can be used, but it will consume the rest of the buffer.
            line.stop();
            line.flush();
        } catch (UnsupportedAudioFileException | IOException | LineUnavailableException e) {
            e.printStackTrace();
        } finally {
            if (line != null)
                line.close();
            if (AIS != null)
                AIS.close();
        }

    }

Still, some optimization can be done, but it work.

PRiM
  • 104
  • 6