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?