private InputStream getInputStream( String urlStr, Format outputFormat, ContentDescriptor outputContentDescriptor) throws Exception { final ProcessorModel processorModel = new ProcessorModel( new MediaLocator(urlStr), outputFormat == null ? null : new Format[] {outputFormat}, outputContentDescriptor); final Processor processor = Manager.createRealizedProcessor(processorModel); final DataSource ds = processor.getDataOutput(); final DataSink[] streamDataSinkHolder = new DataSink[] {null}; // connect the data output of the processor to a StreamDataSink, which // will make the data available to PipedInputStream, which we return. final PipedInputStream in = new PipedInputStream() { // override close to clean up everything when the media has been // served. @Override public void close() throws IOException { super.close(); logger.fine("Closed input stream"); logger.fine("Stopping processor"); processor.stop(); logger.fine("Closing processor"); processor.close(); logger.fine("Deallocating processor"); processor.deallocate(); if (streamDataSinkHolder[0] != null) { logger.fine("Closing StreamDataSink"); streamDataSinkHolder[0].close(); } } }; final PipedOutputStream out = new PipedOutputStream(in); final DataSink streamDataSink = new StreamDataSink(out); streamDataSinkHolder[0] = streamDataSink; streamDataSink.setSource(ds); streamDataSink.open(); streamDataSink.start(); logger.info("Starting processor"); processor.start(); // TODO: if there is an error, make sure we clean up. // for example, if the client breaks the connection. // we need a controller listener to listen for errors. return in; }
/** * Finds the <tt>ReceiveStreamDesc</tt> with a particular <tt>DataSink</tt> * * @param dataSink The <tt>DataSink</tt> to match. * @return the <tt>ReceiveStreamDesc</tt> with a particular <tt>DataSink</tt>, or <tt>null</tt>. */ private ReceiveStreamDesc findReceiveStream(DataSink dataSink) { if (dataSink == null) return null; synchronized (receiveStreams) { for (ReceiveStreamDesc r : receiveStreams) if (dataSink.equals(r.dataSink)) return r; } return null; }
/** Stops the transmission if already started */ public void stop() { synchronized (this) { if (processor != null) { processor.stop(); processor.close(); processor = null; rtptransmitter.close(); rtptransmitter = null; } } }
// Creates an RTP transmit data sink. This is the easiest way to create // an RTP transmitter. The other way is to use the RTPSessionManager API. // Using an RTP session manager gives you more control if you wish to // fine tune your transmission and set other parameters. private String createTransmitter() { // Create a media locator for the RTP data sink. // For example: // rtp://129.130.131.132:42050/video String rtpURL = "rtp://" + ipAddress + ":" + port + "/video"; MediaLocator outputLocator = new MediaLocator(rtpURL); // Create a data sink, open it and start transmission. It will wait // for the processor to start sending data. So we need to start the // output data source of the processor. We also need to start the // processor itself, which is done after this method returns. try { rtptransmitter = Manager.createDataSink(dataOutput, outputLocator); rtptransmitter.open(); rtptransmitter.start(); dataOutput.start(); } catch (MediaException me) { return "Couldn't create RTP data sink"; } catch (IOException ioe) { return "Couldn't create RTP data sink"; } return null; }
/** * 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); } }
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); }