package org.red5.server.plugin.icy.stream;
/*
* RED5 Open Source Flash Server - http://www.osflash.org/red5
*
* Copyright (c) 2006-2009 by respective authors (see below). All rights reserved.
*
* This library 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 2.1 of the License, or (at your option) any later
* version.
*
* This library 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 this library; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import org.apache.mina.core.buffer.IoBuffer;
import org.red5.codec.AACAudio;
import org.red5.codec.IAudioStreamCodec;
import org.red5.codec.IStreamCodecInfo;
import org.red5.codec.StreamCodecInfo;
import org.red5.server.api.event.IEvent;
import org.red5.server.api.scope.IScope;
import org.red5.server.api.statistics.IClientBroadcastStreamStatistics;
import org.red5.server.api.statistics.support.StatisticsCounter;
import org.red5.server.api.stream.IBroadcastStream;
import org.red5.server.api.stream.IStreamListener;
import org.red5.server.api.stream.IStreamPacket;
import org.red5.server.api.stream.IVideoStreamCodec;
import org.red5.server.api.stream.ResourceExistException;
import org.red5.server.api.stream.ResourceNotFoundException;
import org.red5.server.icy.IICYEventSink;
import org.red5.server.messaging.IConsumer;
import org.red5.server.messaging.IMessageComponent;
import org.red5.server.messaging.IPipe;
import org.red5.server.messaging.IPipeConnectionListener;
import org.red5.server.messaging.IProvider;
import org.red5.server.messaging.OOBControlMessage;
import org.red5.server.messaging.PipeConnectionEvent;
import org.red5.server.net.rtmp.event.AudioData;
import org.red5.server.net.rtmp.event.IRTMPEvent;
import org.red5.server.net.rtmp.event.Notify;
import org.red5.server.net.rtmp.message.Header;
import org.red5.server.plugin.icy.marshal.transpose.AudioFramer;
import org.red5.server.plugin.icy.marshal.transpose.VideoFramer;
import org.red5.server.stream.IStreamData;
import org.red5.server.stream.PlayEngine;
import org.red5.server.stream.message.RTMPMessage;
/**
* Output stream used to pipe the icy/nsv content.
*
* @author Wittawas Nakkasem (vittee@hotmail.com)
* @author Andy Shaules (bowljoman@hotmail.com)
*/
public class ICYStream implements IBroadcastStream, IProvider, IPipeConnectionListener, IICYEventSink,
IClientBroadcastStreamStatistics {
private Set<IStreamListener> mListeners = new CopyOnWriteArraySet<IStreamListener>();
private String mPublishedName;
private IPipe mLivePipe;
private IScope mScope;
private StreamCodecInfo mCodecInfo;
private List<IConsumer> newComsumers = new ArrayList<IConsumer>();
private StatisticsCounter subscriberStats = new StatisticsCounter();
private int audioTime;
private long bytesReceived = 0;
private long creationTime;
private IAudioStreamCodec audioReader;
private IVideoStreamCodec videoReader;
public AudioFramer audioFramer;
public VideoFramer videoFramer;
private IRTMPEvent _metaDataEvent;
public ICYStream(String name, boolean video, boolean audio) {
mPublishedName = name;
mLivePipe = null;
mCodecInfo = new StreamCodecInfo();
mCodecInfo.setHasAudio(audio);
mCodecInfo.setHasVideo(video);
}
@Override
public void addStreamListener(IStreamListener listener) {
//log.debug("addStreamListener(listener: {})", listener);
mListeners.add(listener);
}
@Override
public IProvider getProvider() {
//log.debug("getProvider()");
return this;
}
@Override
public String getPublishedName() {
return mPublishedName;
}
@Override
public String getSaveFilename() {
throw new Error("unimplemented method");
}
@Override
public Collection<IStreamListener> getStreamListeners() {
return mListeners;
}
@Override
public void removeStreamListener(IStreamListener listener) {
mListeners.remove(listener);
}
@Override
public void saveAs(String arg0, boolean arg1) throws IOException, ResourceNotFoundException, ResourceExistException {
throw new Error("unimplemented method");
}
@Override
public void setPublishedName(String name) {
//log.debug("setPublishedName(name:{})", name);
mPublishedName = name;
}
@Override
public void close() {
// log.debug("close()");
}
@Override
public IStreamCodecInfo getCodecInfo() {
return mCodecInfo;
}
@Override
public String getName() {
return mPublishedName;
}
@Override
public IScope getScope() {
return mScope;
}
public void setScope(IScope scope) {
mScope = scope;
}
@Override
public void start() {
bytesReceived = 0;
audioTime = 0;
creationTime = System.currentTimeMillis();
}
@Override
public void stop() {
}
@Override
public void onOOBControlMessage(IMessageComponent arg0, IPipe arg1, OOBControlMessage arg2) {
}
public void setAudioReader(IAudioStreamCodec codecReader) {
this.audioReader = codecReader;
}
public IAudioStreamCodec getCodecReader() {
return audioReader;
}
public void setVideoReader(IVideoStreamCodec codecReader) {
this.videoReader = codecReader;
}
public IVideoStreamCodec getVideoReader() {
return videoReader;
}
@SuppressWarnings("unused")
@Override
public void onPipeConnectionEvent(PipeConnectionEvent event) {
switch (event.getType()) {
case PipeConnectionEvent.PROVIDER_CONNECT_PUSH:
if ((event.getProvider() == this) && (event.getParamMap() == null)) {
mLivePipe = (IPipe) event.getSource();
//log.info("mLivePipe {}", mLivePipe);
for (IConsumer consumer : mLivePipe.getConsumers()) {
subscriberStats.increment();
}
}
break;
case PipeConnectionEvent.PROVIDER_DISCONNECT:
if (mLivePipe == event.getSource()) {
mLivePipe = null;
}
break;
case PipeConnectionEvent.CONSUMER_CONNECT_PUSH:
if (mLivePipe != null) {
List<IConsumer> consumers = mLivePipe.getConsumers();
int count = consumers.size();
if (count > 0) {
newComsumers.add(consumers.get(count - 1));
}
subscriberStats.increment();
}
break;
case PipeConnectionEvent.CONSUMER_DISCONNECT:
subscriberStats.decrement();
break;
default:
break;
}
}
private void sendConfig() {
while (newComsumers.size() > 0) {
IConsumer consumer = newComsumers.remove(0);
if (consumer instanceof PlayEngine) {
if (audioReader instanceof AACAudio) { // Audio pay-load
IoBuffer buffer = IoBuffer.allocate(10);
buffer.setAutoExpand(true);
buffer.put((byte) 0xaf);
buffer.put((byte) 0x00);
buffer.put(audioFramer.getAACSpecificConfig());
//buffer.put((byte) 0x06);
buffer.flip();
AudioData data = new AudioData(buffer);
data.setHeader(new Header());
RTMPMessage msg = RTMPMessage.build(data);
// msg.setBody(data);
try {
((PlayEngine) consumer).pushMessage(null, msg);
} catch (IOException e) {
}
}
if (_metaDataEvent != null) {
dispatchEvent(_metaDataEvent);
}
}
}
}
public void dispatchEvent(IEvent event) {
if (event instanceof IRTMPEvent) {
IRTMPEvent rtmpEvent = (IRTMPEvent) event;
int eventTime = 0;
IoBuffer buf = null;
if (rtmpEvent instanceof IStreamData && (buf = ((IStreamData) rtmpEvent).getData()) != null) {
bytesReceived += buf.limit();
}
if (rtmpEvent instanceof AudioData) {
audioTime += rtmpEvent.getTimestamp();
eventTime = audioTime;
sendConfig();
} else {
eventTime = audioTime;
}
if (mLivePipe != null) {
RTMPMessage msg = RTMPMessage.build(rtmpEvent);
msg.getBody().setTimestamp(eventTime);
try {
mLivePipe.pushMessage(msg);
} catch (Exception e) {
//log.info("dispatchEvent {}, error: {}", event, e);
}
}
// Notify listeners about received packet
if (rtmpEvent instanceof IStreamPacket) {
for (IStreamListener listener : getStreamListeners()) {
try {
listener.packetReceived(this, (IStreamPacket) rtmpEvent);
} catch (Exception e) {
//log.info("Error while notifying listener {}", listener, e);
}
}
}
}
}
@Override
public int getActiveSubscribers() {
return subscriberStats.getCurrent();
}
@Override
public long getBytesReceived() {
return bytesReceived;
}
@Override
public int getMaxSubscribers() {
return subscriberStats.getMax();
}
@Override
public int getTotalSubscribers() {
return subscriberStats.getTotal();
}
@Override
public int getCurrentTimestamp() {
return audioTime;
}
@Override
public long getCreationTime() {
return creationTime;
}
@Override
public void reset() {
newComsumers.addAll(mLivePipe.getConsumers());
}
public void setMetaDataEvent(IRTMPEvent event) {
_metaDataEvent = event;
}
@Override
public Notify getMetaData() {
// TODO Auto-generated method stub
return null;
}
}