public synchronized boolean waitForState(int state) { switch (state) { case Processor.Configured: p.configure(); break; case Controller.Realized: p.realize(); break; case Controller.Prefetched: p.prefetch(); break; case Controller.Started: p.start(); break; } while (p.getState() < state && !error) { try { wait(1000); } catch (Exception e) { } } // p.removeControllerListener(this); return !(error); }
private synchronized boolean waitForState(Processor p, int state) { p.addControllerListener(new StateListener()); failed = false; // Call the required method on the processor if (state == Processor.Configured) { p.configure(); } else if (state == Processor.Realized) { p.realize(); } // Wait until we get an event that confirms the // success of the method, or a failure event. // See StateListener inner class while (p.getState() < state && !failed) { synchronized (getStateLock()) { try { getStateLock().wait(); } catch (InterruptedException ie) { return false; } } } if (failed) return false; else return true; }
/** Stops the transmission if already started */ public void stop() { synchronized (this) { if (processor != null) { processor.stop(); processor.close(); processor = null; rtptransmitter.close(); rtptransmitter = null; } } }
/** * Restarts the recording for a specific SSRC. * * @param ssrc the SSRC for which to restart recording. RTP packet of the new recording). */ private void resetRecording(long ssrc, long timestamp) { ReceiveStreamDesc receiveStream = findReceiveStream(ssrc); // we only restart audio recordings if (receiveStream != null && receiveStream.format instanceof AudioFormat) { String newFilename = getNextFilename(path + "/" + ssrc, AUDIO_FILENAME_SUFFIX); // flush the buffer contained in the MP3 encoder String s = "trying to flush ssrc=" + ssrc; Processor p = receiveStream.processor; if (p != null) { s += " p!=null"; for (TrackControl tc : p.getTrackControls()) { Object o = tc.getControl(FlushableControl.class.getName()); if (o != null) ((FlushableControl) o).flush(); } } if (logger.isInfoEnabled()) { logger.info("Restarting recording for SSRC=" + ssrc + ". New filename: " + newFilename); } receiveStream.dataSink.close(); receiveStream.dataSink = null; // flush the FMJ jitter buffer // DataSource ds = receiveStream.receiveStream.getDataSource(); // if (ds instanceof net.sf.fmj.media.protocol.rtp.DataSource) // ((net.sf.fmj.media.protocol.rtp.DataSource)ds).flush(); receiveStream.filename = newFilename; try { receiveStream.dataSink = Manager.createDataSink( receiveStream.dataSource, new MediaLocator("file:" + newFilename)); } catch (NoDataSinkException ndse) { logger.warn("Could not reset recording for SSRC=" + ssrc + ": " + ndse); removeReceiveStream(receiveStream, false); } try { receiveStream.dataSink.open(); receiveStream.dataSink.start(); } catch (IOException ioe) { logger.warn("Could not reset recording for SSRC=" + ssrc + ": " + ioe); removeReceiveStream(receiveStream, false); } audioRecordingStarted(ssrc, timestamp); } }
/** * Finds the <tt>ReceiveStreamDesc</tt> with a particular <tt>Processor</tt> * * @param processor The <tt>Processor</tt> to match. * @return the <tt>ReceiveStreamDesc</tt> with a particular <tt>Processor</tt>, or <tt>null</tt>. */ private ReceiveStreamDesc findReceiveStream(Processor processor) { if (processor == null) return null; synchronized (receiveStreams) { for (ReceiveStreamDesc r : receiveStreams) if (processor.equals(r.processor)) return r; } return null; }
/** With the given processor info generated from matchTracks, build each of the processors. */ public boolean buildTracks(ProcInfo pInfo[]) { ContentDescriptor cd = new ContentDescriptor(ContentDescriptor.RAW); Processor p; for (int i = 0; i < pInfo.length; i++) { p = pInfo[i].p; p.setContentDescriptor(cd); // We are done with programming the processor. Let's just // realize the it. if (!waitForState(p, Controller.Realized)) { System.err.println("- Failed to realize the processor."); return false; } // Set the JPEG quality to .5. setJPEGQuality(p, 0.5f); PushBufferStream pbs[]; TrackInfo tInfo; int trackID; // Cheating. I should have checked the type of DataSource // returned. pInfo[i].ds = (PushBufferDataSource) p.getDataOutput(); pbs = pInfo[i].ds.getStreams(); // Find the matching data stream for the given track for audio. for (int type = AUDIO; type < MEDIA_TYPES; type++) { for (trackID = 0; trackID < pInfo[i].numTracksByType[type]; trackID++) { tInfo = pInfo[i].tracksByType[type][trackID]; tInfo.pbs = pbs[tInfo.idx]; } } } return true; }
/** * Starts the transmission. Returns null if transmission started ok. Otherwise it returns a string * with the reason why the setup failed. */ public synchronized String start() { String result; // Create a processor for the specified media locator // and program it to output JPEG/RTP result = createProcessor(); if (result != null) return result; // Create an RTP session to transmit the output of the // processor to the specified IP address and port no. result = createTransmitter(); if (result != null) { processor.close(); processor = null; return result; } // Start the transmission processor.start(); return null; }
/** Create the DataSink. */ DataSink createDataSink(Processor p, MediaLocator outML) { DataSource ds; if ((ds = p.getDataOutput()) == null) { System.err.println( "Something is really wrong: the processor does not have an output DataSource"); return null; } DataSink dsink; try { System.err.println("- Create DataSink for: " + outML); dsink = Manager.createDataSink(ds, outML); dsink.open(); } catch (Exception e) { System.err.println("Cannot create the DataSink: " + e); return null; } return dsink; }
/** * Implements {@link ControllerListener#controllerUpdate(ControllerEvent)}. Handles events from * the <tt>Processor</tt>s that this instance uses to transcode media. * * @param ev the event to handle. */ public void controllerUpdate(ControllerEvent ev) { if (ev == null || ev.getSourceController() == null) { return; } Processor processor = (Processor) ev.getSourceController(); ReceiveStreamDesc desc = findReceiveStream(processor); if (desc == null) { logger.warn("Event from an orphaned processor, ignoring: " + ev); return; } if (ev instanceof ConfigureCompleteEvent) { if (logger.isInfoEnabled()) { logger.info( "Configured processor for ReceiveStream ssrc=" + desc.ssrc + " (" + desc.format + ")" + " " + System.currentTimeMillis()); } boolean audio = desc.format instanceof AudioFormat; if (audio) { ContentDescriptor cd = processor.setContentDescriptor(AUDIO_CONTENT_DESCRIPTOR); if (!AUDIO_CONTENT_DESCRIPTOR.equals(cd)) { logger.error( "Failed to set the Processor content " + "descriptor to " + AUDIO_CONTENT_DESCRIPTOR + ". Actual result: " + cd); removeReceiveStream(desc, false); return; } } for (TrackControl track : processor.getTrackControls()) { Format trackFormat = track.getFormat(); if (audio) { final long ssrc = desc.ssrc; SilenceEffect silenceEffect; if (Constants.OPUS_RTP.equals(desc.format.getEncoding())) { silenceEffect = new SilenceEffect(48000); } else { // We haven't tested that the RTP timestamps survive // the journey through the chain when codecs other than // opus are in use, so for the moment we rely on FMJ's // timestamps for non-opus formats. silenceEffect = new SilenceEffect(); } silenceEffect.setListener( new SilenceEffect.Listener() { boolean first = true; @Override public void onSilenceNotInserted(long timestamp) { if (first) { first = false; // send event only audioRecordingStarted(ssrc, timestamp); } else { // change file and send event resetRecording(ssrc, timestamp); } } }); desc.silenceEffect = silenceEffect; AudioLevelEffect audioLevelEffect = new AudioLevelEffect(); audioLevelEffect.setAudioLevelListener( new SimpleAudioLevelListener() { @Override public void audioLevelChanged(int level) { activeSpeakerDetector.levelChanged(ssrc, level); } }); try { // We add an effect, which will insert "silence" in // place of lost packets. track.setCodecChain(new Codec[] {silenceEffect, audioLevelEffect}); } catch (UnsupportedPlugInException upie) { logger.warn("Failed to insert silence effect: " + upie); // But do go on, a recording without extra silence is // better than nothing ;) } } else { // transcode vp8/rtp to vp8 (i.e. depacketize vp8) if (trackFormat.matches(vp8RtpFormat)) track.setFormat(vp8Format); else { logger.error("Unsupported track format: " + trackFormat + " for ssrc=" + desc.ssrc); // we currently only support vp8 removeReceiveStream(desc, false); return; } } } processor.realize(); } else if (ev instanceof RealizeCompleteEvent) { desc.dataSource = processor.getDataOutput(); long ssrc = desc.ssrc; boolean audio = desc.format instanceof AudioFormat; String suffix = audio ? AUDIO_FILENAME_SUFFIX : VIDEO_FILENAME_SUFFIX; // XXX '\' on windows? String filename = getNextFilename(path + "/" + ssrc, suffix); desc.filename = filename; DataSink dataSink; if (audio) { try { dataSink = Manager.createDataSink(desc.dataSource, new MediaLocator("file:" + filename)); } catch (NoDataSinkException ndse) { logger.error("Could not create DataSink: " + ndse); removeReceiveStream(desc, false); return; } } else { dataSink = new WebmDataSink(filename, desc.dataSource); } if (logger.isInfoEnabled()) logger.info( "Created DataSink (" + dataSink + ") for SSRC=" + ssrc + ". Output filename: " + filename); try { dataSink.open(); } catch (IOException e) { logger.error("Failed to open DataSink (" + dataSink + ") for" + " SSRC=" + ssrc + ": " + e); removeReceiveStream(desc, false); return; } if (!audio) { final WebmDataSink webmDataSink = (WebmDataSink) dataSink; webmDataSink.setSsrc(ssrc); webmDataSink.setEventHandler(eventHandler); webmDataSink.setKeyFrameControl( new KeyFrameControlAdapter() { @Override public boolean requestKeyFrame(boolean urgent) { return requestFIR(webmDataSink); } }); } try { dataSink.start(); } catch (IOException e) { logger.error( "Failed to start DataSink (" + dataSink + ") for" + " SSRC=" + ssrc + ". " + e); removeReceiveStream(desc, false); return; } if (logger.isInfoEnabled()) logger.info("Started DataSink for SSRC=" + ssrc); desc.dataSink = dataSink; processor.start(); } else if (logger.isDebugEnabled()) { logger.debug( "Unhandled ControllerEvent from the Processor for ssrc=" + desc.ssrc + ": " + ev); } }
/** * Implements {@link ReceiveStreamListener#update(ReceiveStreamEvent)}. * * <p>{@link #rtpManager} will use this to notify us of <tt>ReceiveStreamEvent</tt>s. */ @Override public void update(ReceiveStreamEvent event) { if (event == null) return; ReceiveStream receiveStream = event.getReceiveStream(); if (event instanceof NewReceiveStreamEvent) { if (receiveStream == null) { logger.warn("NewReceiveStreamEvent: null"); return; } final long ssrc = getReceiveStreamSSRC(receiveStream); ReceiveStreamDesc receiveStreamDesc = findReceiveStream(ssrc); if (receiveStreamDesc != null) { String s = "NewReceiveStreamEvent for an existing SSRC. "; if (receiveStream != receiveStreamDesc.receiveStream) s += "(but different ReceiveStream object)"; logger.warn(s); return; } else receiveStreamDesc = new ReceiveStreamDesc(receiveStream); if (logger.isInfoEnabled()) logger.info("New ReceiveStream, ssrc=" + ssrc); // Find the format of the ReceiveStream DataSource dataSource = receiveStream.getDataSource(); if (dataSource instanceof PushBufferDataSource) { Format format = null; PushBufferDataSource pbds = (PushBufferDataSource) dataSource; for (PushBufferStream pbs : pbds.getStreams()) { if ((format = pbs.getFormat()) != null) break; } if (format == null) { logger.error("Failed to handle new ReceiveStream: " + "Failed to determine format"); return; } receiveStreamDesc.format = format; } else { logger.error("Failed to handle new ReceiveStream: " + "Unsupported DataSource"); return; } int rtpClockRate = -1; if (receiveStreamDesc.format instanceof AudioFormat) rtpClockRate = (int) ((AudioFormat) receiveStreamDesc.format).getSampleRate(); else if (receiveStreamDesc.format instanceof VideoFormat) rtpClockRate = 90000; getSynchronizer().setRtpClockRate(ssrc, rtpClockRate); // create a Processor and configure it Processor processor = null; try { processor = Manager.createProcessor(receiveStream.getDataSource()); } catch (NoProcessorException npe) { logger.error("Failed to create Processor: ", npe); return; } catch (IOException ioe) { logger.error("Failed to create Processor: ", ioe); return; } if (logger.isInfoEnabled()) logger.info("Created processor for SSRC=" + ssrc); processor.addControllerListener(this); receiveStreamDesc.processor = processor; final int streamCount; synchronized (receiveStreams) { receiveStreams.add(receiveStreamDesc); streamCount = receiveStreams.size(); } /* * XXX TODO IRBABOON * This is a terrible hack which works around a failure to realize() * some of the Processor-s for audio streams, when multiple streams * start nearly simultaneously. The cause of the problem is currently * unknown (and synchronizing all FMJ calls in RecorderRtpImpl * does not help). * XXX TODO NOOBABRI */ if (receiveStreamDesc.format instanceof AudioFormat) { final Processor p = processor; new Thread() { @Override public void run() { // delay configuring the processors for the different // audio streams to decrease the probability that they // run together. try { int ms = 450 * (streamCount - 1); logger.warn( "Sleeping for " + ms + "ms before" + " configuring processor for SSRC=" + ssrc + " " + System.currentTimeMillis()); Thread.sleep(ms); } catch (Exception e) { } p.configure(); } }.run(); } else { processor.configure(); } } else if (event instanceof TimeoutEvent) { if (receiveStream == null) { // TODO: we might want to get the list of ReceiveStream-s from // rtpManager and compare it to our list, to see if we should // remove a stream. logger.warn("TimeoutEvent: null."); return; } // FMJ silently creates new ReceiveStream instances, so we have to // recognize them by the SSRC. ReceiveStreamDesc receiveStreamDesc = findReceiveStream(getReceiveStreamSSRC(receiveStream)); if (receiveStreamDesc != null) { if (logger.isInfoEnabled()) { logger.info("ReceiveStream timeout, ssrc=" + receiveStreamDesc.ssrc); } removeReceiveStream(receiveStreamDesc, true); } } else if (event != null && logger.isInfoEnabled()) { logger.info("Unhandled ReceiveStreamEvent (" + event.getClass().getName() + "): " + event); } }
public static void main(String[] args) { // ---------------- CUT HERE START ----------------- // Format formats[] = new Format[2]; formats[0] = new AudioFormat(AudioFormat.IMA4); formats[1] = new VideoFormat(VideoFormat.CINEPAK); FileTypeDescriptor outputType = new FileTypeDescriptor(FileTypeDescriptor.QUICKTIME); Processor p = null; try { p = Manager.createRealizedProcessor(new ProcessorModel(formats, outputType)); } catch (IOException e) { System.exit(-1); } catch (NoProcessorException e) { System.exit(-1); } catch (CannotRealizeException e) { System.exit(-1); } // get the output of the processor DataSource source = p.getDataOutput(); // create a File protocol MediaLocator with the location of the file to // which bits are to be written MediaLocator dest = new MediaLocator("file://foo.mov"); // create a datasink to do the file writing & open the sink to make sure // we can write to it. DataSink filewriter = null; try { filewriter = Manager.createDataSink(source, dest); filewriter.open(); } catch (NoDataSinkException e) { System.exit(-1); } catch (IOException e) { System.exit(-1); } catch (SecurityException e) { System.exit(-1); } // now start the filewriter and processor try { filewriter.start(); } catch (IOException e) { System.exit(-1); } p.start(); // stop and close the processor when done capturing... // close the datasink when EndOfStream event is received... // ----------------- CUT HERE END ---------------- // try { Thread.currentThread().sleep(4000); } catch (InterruptedException ie) { } p.stop(); p.close(); try { Thread.currentThread().sleep(1000); } catch (InterruptedException ie) { } filewriter.close(); try { Thread.currentThread().sleep(4000); } catch (InterruptedException ie) { } System.exit(0); }
StateWaiter(Processor p) { this.p = p; p.addControllerListener(this); }
/** * Given an array of input media locators and an output locator, this method will concatenate the * input media files to generate a single concatentated output. */ public boolean doIt(MediaLocator inML[], MediaLocator outML) { // Guess the output content descriptor from the file extension. ContentDescriptor cd; if ((cd = fileExtToCD(outML.getRemainder())) == null) { System.err.println("Couldn't figure out from the file extension the type of output needed!"); return false; } // Build the ProcInfo data structure for each processor. ProcInfo pInfo[] = new ProcInfo[inML.length]; for (int i = 0; i < inML.length; i++) { pInfo[i] = new ProcInfo(); pInfo[i].ml = inML[i]; try { System.err.println("- Create processor for: " + inML[i]); pInfo[i].p = Manager.createProcessor(inML[i]); } catch (Exception e) { System.err.println("Yikes! Cannot create a processor from the given url: " + e); return false; } } // Try to match the tracks from different processors. if (!matchTracks(pInfo, cd)) { System.err.println("Failed to match the tracks."); return false; } // Program each processors to perform the necessary transcoding // to concatenate the tracks. if (!buildTracks(pInfo)) { System.err.println("Failed to build processors for the inputs."); return false; } // Generate a super glue data source from the processors. SuperGlueDataSource ds = new SuperGlueDataSource(pInfo); // Create the processor to generate the final output. Processor p; try { p = Manager.createProcessor(ds); } catch (Exception e) { System.err.println("Failed to create a processor to concatenate the inputs."); return false; } p.addControllerListener(this); // Put the Processor into configured state. if (!waitForState(p, Processor.Configured)) { System.err.println("Failed to configure the processor."); return false; } // Set the output content descriptor on the final processor. System.err.println("- Set output content descriptor to: " + cd); if ((p.setContentDescriptor(cd)) == null) { System.err.println("Failed to set the output content descriptor on the processor."); return false; } // We are done with programming the processor. Let's just // realize it. if (!waitForState(p, Controller.Realized)) { System.err.println("Failed to realize the processor."); return false; } // Now, we'll need to create a DataSink. DataSink dsink; if ((dsink = createDataSink(p, outML)) == null) { System.err.println("Failed to create a DataSink for the given output MediaLocator: " + outML); return false; } dsink.addDataSinkListener(this); fileDone = false; System.err.println("- Start concatenation..."); // OK, we can now start the actual concatenation. try { p.start(); dsink.start(); } catch (IOException e) { System.err.println("IO error during concatenation"); return false; } // Wait for EndOfStream event. waitForFileDone(); // Cleanup. try { dsink.close(); } catch (Exception e) { } p.removeControllerListener(this); System.err.println(" ...done concatenation."); return true; }
private String createProcessor() { if (locator == null) return "Locator is null"; DataSource ds; DataSource clone; try { ds = Manager.createDataSource(locator); } catch (Exception e) { return "Couldn't create DataSource"; } // Try to create a processor to handle the input media locator try { processor = Manager.createProcessor(ds); } catch (NoProcessorException npe) { return "Couldn't create processor"; } catch (IOException ioe) { return "IOException creating processor"; } // Wait for it to configure boolean result = waitForState(processor, Processor.Configured); if (result == false) return "Couldn't configure processor"; // Get the tracks from the processor TrackControl[] tracks = processor.getTrackControls(); // Do we have atleast one track? if (tracks == null || tracks.length < 1) return "Couldn't find tracks in processor"; boolean programmed = false; // Search through the tracks for a video track for (int i = 0; i < tracks.length; i++) { Format format = tracks[i].getFormat(); if (tracks[i].isEnabled() && format instanceof VideoFormat && !programmed) { // Found a video track. Try to program it to output JPEG/RTP // Make sure the sizes are multiple of 8's. Dimension size = ((VideoFormat) format).getSize(); float frameRate = ((VideoFormat) format).getFrameRate(); int w = (size.width % 8 == 0 ? size.width : (int) (size.width / 8) * 8); int h = (size.height % 8 == 0 ? size.height : (int) (size.height / 8) * 8); VideoFormat jpegFormat = new VideoFormat( VideoFormat.JPEG_RTP, new Dimension(w, h), Format.NOT_SPECIFIED, Format.byteArray, frameRate); tracks[i].setFormat(jpegFormat); System.err.println("Video transmitted as:"); System.err.println(" " + jpegFormat); // Assume succesful programmed = true; } else tracks[i].setEnabled(false); } if (!programmed) return "Couldn't find video track"; // Set the output content descriptor to RAW_RTP ContentDescriptor cd = new ContentDescriptor(ContentDescriptor.RAW_RTP); processor.setContentDescriptor(cd); // Realize the processor. This will internally create a flow // graph and attempt to create an output datasource for JPEG/RTP // video frames. result = waitForState(processor, Controller.Realized); if (result == false) return "Couldn't realize processor"; // Set the JPEG quality to .5. setJPEGQuality(processor, 0.5f); // Get the output data source of the processor dataOutput = processor.getDataOutput(); return null; }