How to handle DTMF by Java Media Framework?

86 Views Asked by At

Im trying to make a simple SIP client who need to understand a voice RTP stream (like g711u, payload type 0) and RTP events with payload type 101. Its events is a DTMF by RFC2833. At this point i have to problems.

Im listen incoming RTP audio stream by RTPManager and Player (from JMF), audio from RTP stream play correct but when i get RTP event with the audio my player breaks and dont play anything more. When i get RTP event ReceiveStreamListener get a RemotePayloadChangeEvent. In JMF API guide i see the wah to handle payload change ivent but it doesnt work. When i get RemotePayloadChangeEvent from 101 payload i trying to restart player according to api guide but player cant start with error "Unable to handle format: 101". After that i get another one RemotePayloadChangeEvent from audio g711u traffic, trying to restart player again, but it doesnt play audio from rtp stream again.

public class RTPTransmitter implements ReceiveStreamListener {//ReceiveStreamListener,RemoteListener

    private final static Logger log = Logger.getLogger(RTPTransmitter.class);

    private String pathToFile;
    private SendStream stream;
    private Processor processor;
    private Player player;
    private DataSource rtpDataSource;
    private RtpEventListener rtpEventListener = new RtpEventListener();

    private String codecName;
    private int codecCode;
    private AudioFormat audioFormat;
    private MediaFormat rtpEventFormat;

    private String remoteAddressString;
    private int remotePort;
    private String sourceAddressString;
    private int sourcePort;

    private final static int DEFAULT_TTL_VALUE = 64;
    private final static Format RTP_EVENT_FORMAT = new Format("101");
    private final static int RTP_EVENT_PAYLOAD_TYPE = 101;



    public RTPTransmitter(String pathToFile) {
        MediaFormatFactoryImpl mediaFormatFactory = new MediaFormatFactoryImpl();
        long start = System.currentTimeMillis();
        this.pathToFile = pathToFile;
        initializeProcessor();
        System.err.println("Create RTPTransmitter: " + (System.currentTimeMillis() - start));
    }


    public void startTransmission() {
        try {
            long start = System.currentTimeMillis();
            log.info("Prepare to start");
            processor.start();
            stream.start();
            log.info("Transmission started");
            System.err.println("Session start: " + (System.currentTimeMillis() - start));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public void stopTransmission() {
        try {
            log.info("Prepare to stop");
            stream.stop();
            stream.close();
            processor.stop();
            processor.close();
            log.info("Transmission stopped");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public void initializeRtpSession(String remoteAddressString, int remotePort, String sourceAddressString, int sourcePort, String codecName, String codecCode) {
        this.codecName = codecName;
        this.codecCode = Integer.parseInt(codecCode);
        this.audioFormat = new AudioFormat(codecName);
        this.remoteAddressString = remoteAddressString;
        this.remotePort = remotePort;
        this.sourceAddressString = sourceAddressString;
        this.sourcePort = sourcePort;
        try {
            RTPManager rtpManager = createRTPManager();
            stream = createDataStream(rtpManager);
        } catch (InvalidSessionAddressException | UnsupportedFormatException | IOException e) {
            throw new RuntimeException(e);
        }
    }

    public void initializeRtpSession(String remoteAddressString, int remotePort, String sourceAddressString, int sourcePort, String codecName, int codecCode) {
        this.codecName = codecName;
        this.codecCode = codecCode;
        this.audioFormat = new AudioFormat(codecName);
        this.remoteAddressString = remoteAddressString;
        this.remotePort = remotePort;
        this.sourceAddressString = sourceAddressString;
        this.sourcePort = sourcePort;
        try {
            RTPManager rtpManager = createRTPManager();
            stream = createDataStream(rtpManager);
        } catch (InvalidSessionAddressException | UnsupportedFormatException | IOException e) {
            throw new RuntimeException(e);
        }
    }

    private void initializeProcessor() {
        try {
            processor = createProcessor();
            processor.configure();
            while (processor.getState() != Processor.Configured) {
                log.info("Configuring...");
            }
            trackControlSettings();
            processor.setContentDescriptor(new ContentDescriptor(ContentDescriptor.RAW_RTP));
            processor.realize();
            while (processor.getState() != Processor.Realized) {
                log.info("Realizing...");
            }

        } catch (NoProcessorException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private Processor createProcessor() throws IOException, NoProcessorException {
        File mediaFile = new File(pathToFile);
        URL mediaURL = mediaFile.toURI().toURL();
        MediaLocator mediaLocator = new MediaLocator(mediaURL);
        return Manager.createProcessor(mediaLocator);
    }

    private RTPManager createRTPManager() throws IOException, InvalidSessionAddressException {
        //This solution for fixing 4 second time to creation rtpManager.addTarget(remoteAddress);
        InetAddress temporaryRemoteAddress = InetAddress.getByName(remoteAddressString);
        byte[] bytesOfRemoteAddress = temporaryRemoteAddress.getAddress();
        InetAddress remoteIpAddress = InetAddress.getByAddress(InetAddress.getLocalHost().getHostName(), bytesOfRemoteAddress);
        SessionAddress remoteAddress = new SessionAddress(remoteIpAddress, remotePort, DEFAULT_TTL_VALUE);
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        InetAddress sourceIpAddress = InetAddress.getByName(sourceAddressString);
        SessionAddress sourceAddress = new SessionAddress(sourceIpAddress, sourcePort, DEFAULT_TTL_VALUE);
        RTPManager rtpManager = RTPManager.newInstance();
        rtpManager.addFormat(audioFormat, codecCode);
        /////////////////////////////////
        rtpManager.addFormat(RTP_EVENT_FORMAT, RTP_EVENT_PAYLOAD_TYPE);
        rtpManager.addReceiveStreamListener(this);
        //rtpManager.addRemoteListener(this);
        ////////////////////////////////
        rtpManager.initialize(sourceAddress);
        long start = System.currentTimeMillis();
        rtpManager.addTarget(remoteAddress);
        System.err.println("Creating RTPManager: " + (System.currentTimeMillis() - start));
        return rtpManager;
    }

    private SendStream createDataStream(RTPManager rtpManager) throws UnsupportedFormatException, IOException {
        long start = System.currentTimeMillis();
        DataSource output = processor.getDataOutput();
        SendStream sendStream = rtpManager.createSendStream(output, 0);
        System.err.println("Creating SendStream: " + (System.currentTimeMillis() - start));
        return sendStream;
    }

    private void trackControlSettings() {
        TrackControl[] tracks = processor.getTrackControls();

        if (tracks == null || tracks.length < 1) {
            System.out.println("Couldn't find tracks in processor");
            System.exit(1);
        }

        for (TrackControl track : tracks) {
            log.info("Format: " + track.getFormat());
        }

        Format supported[];
        Format chosen;
        boolean atLeastOneTrack = false;

        // Program the tracks.
        for (int i = 0; i < tracks.length; i++) {
            Format format = tracks[i].getFormat();

            System.out.println("Trenutni format je " +format.getEncoding());

            if (tracks[i].isEnabled()) {
                supported = tracks[i].getSupportedFormats();
                for (int n = 0; n < supported.length; n++)
                    System.out.println("Supported format: " + supported[n]);

                if (supported.length > 0) {
                    chosen = supported[0]; // this is where I tried changing formats
                    tracks[i].setFormat(chosen);
                    System.err.println("Track " + i + " is set to transmit as: " + chosen);
                    atLeastOneTrack = true;
                } else
                    tracks[i].setEnabled(false);
            } else
                tracks[i].setEnabled(false);
        }
    }

    @Override
    public synchronized void update(ReceiveStreamEvent receiveStreamEvent) {
        RTPControl[] controls = (RTPControl[]) receiveStreamEvent.getReceiveStream().getDataSource().getControls();
        Arrays.stream(controls).forEach(control -> System.err.println(control.getFormat()));
        if (receiveStreamEvent instanceof NewReceiveStreamEvent) {
            System.err.println("This is a NewReceiveStreamEvent: " + receiveStreamEvent.getClass());
            playReceivedRtpStream((NewReceiveStreamEvent) receiveStreamEvent);
        } else if (receiveStreamEvent instanceof StreamMappedEvent){
            System.err.println("This is a StreamMappedEvent: " + receiveStreamEvent.getClass());
        } else if (receiveStreamEvent instanceof RemotePayloadChangeEvent) {
            System.err.println("This is a RemotePayloadChangeEvent(" + ((RemotePayloadChangeEvent) receiveStreamEvent).getNewPayload() + "): " + receiveStreamEvent.getClass());
            changePayload((RemotePayloadChangeEvent) receiveStreamEvent);
        }
    }

    private void playReceivedRtpStream(NewReceiveStreamEvent event) {
        try {
            var receivedStream = event.getReceiveStream();
            rtpDataSource = receivedStream.getDataSource();
            ((RTPControl) rtpDataSource.getControls()[0]).addFormat(RTP_EVENT_FORMAT, RTP_EVENT_PAYLOAD_TYPE);
            player = Manager.createRealizedPlayer(rtpDataSource);
            player.addControllerListener(rtpEventListener);
            player.start();
        } catch (IOException | NoPlayerException | CannotRealizeException e) {
            throw new RuntimeException(e);
        }
    }



    private void changePayload(RemotePayloadChangeEvent event) {
        player.stop();

        while (player.getState() == Player.Started) {
            log.info(player.getState());
        }

        //player.removeControllerListener(rtpEventListener);
        player.close();

        while (player.getState() == Controller.Started) {
            log.info(player.getState());
        }

        try {
            rtpDataSource.connect();
            Player newPlayer = Manager.createPlayer(rtpDataSource);
            //newPlayer.addControllerListener(rtpEventListener);
            newPlayer.realize();
            player = newPlayer;
        } catch (IOException | NoPlayerException e) {
            throw new RuntimeException(e);
        }
    }
}

It possible to add to JMF realization of custom RTP payload types, but i couldnt find any guide or example. There is a g711u implementation in JMF but its a audio codeс which is a continuous stream, but i need to handle a single events. I read about FMJ, but there is the same as JMF. RTP payload 101 in not implemented by default. Also i read about Jitsi, but i couldnt find any guid of in except javadoc

So, maybe somebode see the guide about extending JMF with DTMF RTP events or exists any implementations of it? Or i should use other RTP framework who contains DTMF RTP handle by default?

0

There are 0 best solutions below