/** * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ * * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below). * * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free Software * Foundation; either version 3.0 of the License, or (at your option) any later * version. * * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License along * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. * */ package org.bigbluebutton.voiceconf.red5.media; import java.net.DatagramSocket; import org.apache.mina.core.buffer.IoBuffer; import org.bigbluebutton.voiceconf.red5.media.transcoder.SipToFlashTranscoder; import org.bigbluebutton.voiceconf.red5.media.transcoder.TranscodedAudioDataListener; import org.red5.logging.Red5LoggerFactory; import org.red5.server.api.IContext; import org.red5.server.api.scope.IScope; import org.red5.server.net.rtmp.event.AudioData; import org.red5.server.net.rtmp.event.Notify; import org.red5.server.net.rtmp.message.Constants; import org.red5.server.scope.Scope; import org.red5.server.stream.IProviderService; import org.slf4j.Logger; public class SipToFlashAudioStream implements TranscodedAudioDataListener, RtpStreamReceiverListener { private static final Logger log = Red5LoggerFactory.getLogger(SipToFlashAudioStream.class, "sip"); private AudioBroadcastStream audioBroadcastStream; private IScope scope; private final String listenStreamName; private RtpStreamReceiver rtpStreamReceiver; private StreamObserver observer; private SipToFlashTranscoder transcoder; private boolean sentMetadata = false; private IoBuffer mBuffer; private AudioData audioData; private final byte[] fakeMetadata = new byte[] { 0x02, 0x00, 0x0a, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x44, 0x61, 0x74, 0x61, 0x08, 0x00, 0x00, 0x00, 0x06, 0x00, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x40, 0x31, (byte)0xaf, 0x5c, 0x28, (byte)0xf5, (byte)0xc2, (byte)0x8f, 0x00, 0x0f, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x61, 0x74, 0x65, 0x00, 0x40, (byte)0xe5, (byte)0x88, (byte)0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x69, 0x7a, 0x65, 0x00, 0x40, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x73, 0x74, 0x65, 0x72, 0x65, 0x6f, 0x01, 0x00, 0x00, 0x0c, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x63, 0x6f, 0x64, 0x65, 0x63, 0x69, 0x64, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x66, 0x69, 0x6c, 0x65, (byte)0xc8, 0x73, 0x69, 0x7a, 0x65, 0x00, 0x40, (byte)0xf3, (byte)0xf5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; public SipToFlashAudioStream(IScope scope, SipToFlashTranscoder transcoder, DatagramSocket socket) { this.scope = scope; this.transcoder = transcoder; rtpStreamReceiver = new RtpStreamReceiver(socket, transcoder.getIncomingEncodedFrameSize()); rtpStreamReceiver.setRtpStreamReceiverListener(this); listenStreamName = "speaker_" + System.currentTimeMillis(); mBuffer = IoBuffer.allocate(1024); mBuffer = mBuffer.setAutoExpand(true); audioData = new AudioData(); transcoder.setTranscodedAudioListener(this); } public String getStreamName() { return listenStreamName; } public void addListenStreamObserver(StreamObserver o) { observer = o; } public void stop() { if (log.isDebugEnabled()) log.debug("Stopping stream for {}", listenStreamName); transcoder.stop(); rtpStreamReceiver.stop(); if (log.isDebugEnabled()) log.debug("Stopped RTP Stream Receiver for {}", listenStreamName); if (audioBroadcastStream != null) { audioBroadcastStream.stop(); if (log.isDebugEnabled()) log.debug("Stopped audioBroadcastStream for {}", listenStreamName); audioBroadcastStream.close(); if (log.isDebugEnabled()) log.debug("Closed audioBroadcastStream for {}", listenStreamName); } else { if (log.isDebugEnabled()) log.debug("audioBroadcastStream is null, couldn't stop"); } if (log.isDebugEnabled()) log.debug("Stream(s) stopped"); if (observer != null) observer.onStreamStopped(); } public void start() { if (log.isDebugEnabled()) log.debug("started publishing stream in scope=[" + scope.getName() + "] path=[" + scope.getPath() + "]"); audioBroadcastStream = new AudioBroadcastStream(listenStreamName); audioBroadcastStream.setPublishedName(listenStreamName); audioBroadcastStream.setScope(scope); IContext context = scope.getContext(); IProviderService providerService = (IProviderService) context.getBean(IProviderService.BEAN_NAME); if (providerService.registerBroadcastStream(scope, listenStreamName, audioBroadcastStream)){ // Do nothing. Successfully registered a live broadcast stream. (ralam Sept. 4, 2012) } else{ log.error("could not register broadcast stream"); throw new RuntimeException("could not register broadcast stream"); } audioBroadcastStream.start(); transcoder.start(); rtpStreamReceiver.start(); } @Override public void onStoppedReceiving() { if (observer != null) observer.onStreamStopped(); } @Override public void onAudioDataReceived(byte[] audioData, int offset, int len) { transcoder.handleData(audioData, offset, len); } @Override public void handleTranscodedAudioData(byte[] audioData, long timestamp) { if (audioData != null) { pushAudio(audioData, timestamp); } else { log.warn("Transcoded audio is null. Discarding."); } } private void sendFakeMetadata(long timestamp) { if (!sentMetadata) { /* * Flash Player 10.1 requires us to send metadata for it to play audio. * We create a fake one here to get it going. Red5 should do this automatically * but for Red5 0.91, doesn't yet. (ralam Sept 24, 2010). */ mBuffer.clear(); mBuffer.put(fakeMetadata); mBuffer.flip(); Notify notifyData = new Notify(mBuffer); notifyData.setTimestamp((int)timestamp); notifyData.setSourceType(Constants.SOURCE_TYPE_LIVE); audioBroadcastStream.dispatchEvent(notifyData); notifyData.release(); sentMetadata = true; } } private void pushAudio(byte[] audio, long timestamp) { sendFakeMetadata(timestamp); mBuffer.clear(); mBuffer.put((byte) transcoder.getCodecId()); mBuffer.put(audio); mBuffer.flip(); audioData.setSourceType(Constants.SOURCE_TYPE_LIVE); /* * Use timestamp increments passed in by codecs (i.e. 32 for nelly). This will force * Flash Player to playback audio at proper timestamp. If we calculate timestamp using * System.currentTimeMillis() - startTimestamp, the audio has tendency to drift and * introduce delay. (ralam dec 14, 2010) */ audioData.setTimestamp((int)(timestamp)); audioData.setData(mBuffer); audioBroadcastStream.dispatchEvent(audioData); audioData.release(); } }