All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.antmedia.muxer.MuxAdaptor Maven / Gradle / Ivy

There is a newer version: 2.3.3.1
Show newest version
package io.antmedia.muxer;

import static org.bytedeco.javacpp.avcodec.AV_PKT_FLAG_KEY;
import static org.bytedeco.javacpp.avcodec.av_packet_free;
import static org.bytedeco.javacpp.avcodec.av_packet_unref;
import static org.bytedeco.javacpp.avformat.av_dump_format;
import static org.bytedeco.javacpp.avformat.av_read_frame;
import static org.bytedeco.javacpp.avformat.avformat_close_input;
import static org.bytedeco.javacpp.avformat.avformat_find_stream_info;
import static org.bytedeco.javacpp.avformat.avformat_open_input;
import static org.bytedeco.javacpp.avformat.avio_alloc_context;
import static org.bytedeco.javacpp.avutil.AVMEDIA_TYPE_VIDEO;
import static org.bytedeco.javacpp.avutil.AV_LOG_INFO;
import static org.bytedeco.javacpp.avutil.av_free;
import static org.bytedeco.javacpp.avutil.av_log_get_level;
import static org.bytedeco.javacpp.avutil.av_rescale_q;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.bytedeco.javacpp.BytePointer;
import org.bytedeco.javacpp.Pointer;
import org.bytedeco.javacpp.avcodec;
import org.bytedeco.javacpp.avcodec.AVPacket;
import org.bytedeco.javacpp.avformat;
import org.bytedeco.javacpp.avformat.AVFormatContext;
import org.bytedeco.javacpp.avformat.AVIOContext;
import org.bytedeco.javacpp.avformat.AVStream;
import org.bytedeco.javacpp.avformat.Read_packet_Pointer_BytePointer_int;
import org.bytedeco.javacpp.avutil;
import org.bytedeco.javacpp.avutil.AVDictionary;
import org.bytedeco.javacpp.avutil.AVRational;
import org.red5.io.utils.IOUtils;
import org.red5.server.api.IConnection;
import org.red5.server.api.IContext;
import org.red5.server.api.scheduling.IScheduledJob;
import org.red5.server.api.scheduling.ISchedulingService;
import org.red5.server.api.scope.IScope;
import org.red5.server.api.stream.IBroadcastStream;
import org.red5.server.api.stream.IStreamPacket;
import org.red5.server.scheduling.QuartzSchedulingService;
import org.red5.server.stream.ClientBroadcastStream;
import org.red5.server.stream.IRecordingListener;
import org.red5.server.stream.consumer.FileConsumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;

import io.antmedia.AppSettings;
import io.antmedia.EncoderSettings;
import io.antmedia.datastore.db.IDataStore;
import io.antmedia.datastore.db.IDataStoreFactory;
import io.antmedia.datastore.db.types.Broadcast;
import io.antmedia.storage.StorageClient;

public class MuxAdaptor implements IRecordingListener, IScheduledJob {

	private static final byte[] DEFAULT_STREAM_ID = new byte[] { (byte) (0 & 0xff), (byte) (0 & 0xff),
			(byte) (0 & 0xff) };
	private static final int HEADER_LENGTH = 9;
	private static final int TAG_HEADER_LENGTH = 11;
	public static final String ADAPTIVE_SUFFIX = "_adaptive";
	protected QuartzSchedulingService scheduler;
	private static Logger logger = LoggerFactory.getLogger(MuxAdaptor.class);
	protected String packetFeederJobName = null;
	protected ConcurrentLinkedQueue inputQueue = new ConcurrentLinkedQueue<>();
	protected AtomicBoolean isPipeReaderJobRunning = new AtomicBoolean(false);
	protected AVIOContext avio_alloc_context;
	protected AVFormatContext inputFormatContext;

	protected ArrayList muxerList = new ArrayList<>();
	protected boolean deleteHLSFilesOnExit = true;
	protected int receivedPacketCount;
	protected boolean previewOverwrite = false;
	public static class InputContext {
		public Queue queue;
		volatile boolean isHeaderWritten = false;
		volatile boolean stopRequestExist = false;
		public AtomicInteger queueSize = new AtomicInteger(0);

		public InputContext(ConcurrentLinkedQueue queue) {
			this.queue = queue;
		}
	}
	protected static Map queueReferences = new ConcurrentHashMap<>();
	protected static final int BUFFER_SIZE = 4096;
	public static final String QUALITY_GOOD = "good";
	public static final String QUALITY_AVERAGE  = "average";
	public static final String QUALITY_POOR ="poor";
	public static final String QUALITY_NA ="NA";
	public static final int MP4_ENABLED_FOR_STREAM = 1;
	public static final int MP4_DISABLED_FOR_STREAM = -1;
	public static final int MP4_NO_SET_FOR_STREAM = 0;
	protected boolean isRecording = false;
	protected ClientBroadcastStream broadcastStream;
	protected boolean mp4MuxingEnabled;
	protected boolean addDateTimeToMp4FileName;
	protected boolean hlsMuxingEnabled;
	protected boolean objectDetectionEnabled;
	protected boolean webRTCEnabled = false;
	protected StorageClient storageClient;
	protected String hlsTime;
	protected String hlsListSize;
	protected String hlsPlayListType;
	List adaptiveResolutionList = null;
	protected AVPacket pkt = avcodec.av_packet_alloc();
	protected IDataStore dataStore;

	/**
	 * By default first video key frame should be checked 
	 * and below flag should be set to true
	 * If first video key frame should not be checked, 
	 * then below should be flag in advance   
	 */
	private boolean firstKeyFrameReceivedChecked = false;
	protected String streamId;
	protected long startTime;

	protected IScope scope;

	private String oldQuality;
	private AVRational timeBaseForMS;
	private InputContext inputContext;
	private IAntMediaStreamHandler appAdapter;

	private String mp4Filtername;
	protected List encoderSettingsList;
	protected long timeDiffBetweenVideoandElapsed;
	protected long elapsedTime;
	protected static boolean isStreamSource=false;

	private int previewCreatePeriod;
	private double oldspeed;
	private long firstPacketTime = -1;
	private boolean audioOnly= false;
	private long lastQualityUpdateTime = 0;
	private Broadcast broadcast;



	private static Read_packet_Pointer_BytePointer_int readCallback = new Read_packet_Pointer_BytePointer_int() {

		@Override
		public int call(Pointer opaque, BytePointer buf, int bufSize) {
			int length = -1;
			try {
				InputContext inputContext = queueReferences.get(opaque);
				if (inputContext.isHeaderWritten) {
					byte[] packet = null;

					if (inputContext.queue != null) {
						while ((packet = inputContext.queue.poll()) == null) {
							if (inputContext.stopRequestExist) {
								logger.info("stop request ");
								break;
							}
							Thread.sleep(5);
						}
						inputContext.queueSize.decrementAndGet();

					} else {
						logger.error("input queue null");
					}

					if (packet != null) {
						// ** this setting critical..
						length = packet.length;
						buf.put(packet, 0, length);
					} 
					else {
						logger.info("packet is null and return length is {}", length);
					}
				} else {
					inputContext.isHeaderWritten = true;
					logger.info("writing header");
					byte[] flvHeader = getFLVHeader();
					length = flvHeader.length;

					buf.put(flvHeader, 0, length);
				}

			} catch (Exception e) {
				logger.error("Exception handling queue", e);
			}

			return length;
		}

	};

	public static MuxAdaptor initializeMuxAdaptor(ClientBroadcastStream clientBroadcastStream, boolean isSource, IScope scope) {
		MuxAdaptor muxAdaptor = null;
		ApplicationContext applicationContext = scope.getContext().getApplicationContext();
		boolean tryEncoderAdaptor = false;
		if (applicationContext.containsBean(AppSettings.BEAN_NAME)) 
		{
			AppSettings appSettings = (AppSettings)applicationContext.getBean(AppSettings.BEAN_NAME);
			List list = appSettings.getAdaptiveResolutionList();
			if ((list != null && !list.isEmpty()) || appSettings.isWebRTCEnabled()) 
			{
				/*
				 * enable encoder adaptor if webrtc enabled because we're supporting forwarding video to end user
				 * without transcoding. We need encoder adaptor because we need to transcode audio
				 */
				tryEncoderAdaptor = true;
			}
		}

		if (tryEncoderAdaptor) {
			//if adaptive bitrate enabled, take a look at encoder adaptor exists
			//if it is not enabled, then initialize only mux adaptor
			try {
				Class transraterClass = Class.forName("io.antmedia.enterprise.adaptive.EncoderAdaptor");

				muxAdaptor = (MuxAdaptor) transraterClass.getConstructor(ClientBroadcastStream.class)
						.newInstance(clientBroadcastStream);

			} catch (Exception e) {
				logger.error(e.getMessage());
			} 
		}
		if (muxAdaptor == null) {
			muxAdaptor = new MuxAdaptor(clientBroadcastStream);
		}
		muxAdaptor.setStreamSource(isSource);

		return muxAdaptor;
	}


	protected MuxAdaptor(ClientBroadcastStream clientBroadcastStream) {

		this.broadcastStream = clientBroadcastStream;

		timeBaseForMS = new AVRational();
		timeBaseForMS.num(1);
		timeBaseForMS.den(1000);
		inputContext = new InputContext(inputQueue);
	}

	public void addMuxer(Muxer muxer) {
		muxerList.add(muxer);
	}

	@Override
	public boolean init(IConnection conn, String name, boolean isAppend) {

		return init(conn.getScope(), name, isAppend);
	}

	protected void enableSettings() {
		AppSettings appSettings = getAppSettings();
		hlsMuxingEnabled = appSettings.isHlsMuxingEnabled();
		mp4MuxingEnabled = appSettings.isMp4MuxingEnabled();
		objectDetectionEnabled = appSettings.isObjectDetectionEnabled();

		addDateTimeToMp4FileName = getAppSettings().isAddDateTimeToMp4FileName();
		mp4Filtername = null;
		webRTCEnabled = getAppSettings().isWebRTCEnabled();
		deleteHLSFilesOnExit = appSettings.isDeleteHLSFilesOnExit();
		hlsListSize = appSettings.getHlsListSize();
		hlsTime = appSettings.getHlsTime();
		hlsPlayListType = appSettings.getHlsPlayListType();
		previewOverwrite = appSettings.isPreviewOverwrite();
		encoderSettingsList = appSettings.getAdaptiveResolutionList();
		previewCreatePeriod = appSettings.getCreatePreviewPeriod();
	}

	public void initStorageClient() {
		if (scope.getContext().getApplicationContext().containsBean(StorageClient.BEAN_NAME)) {
			storageClient = (StorageClient) scope.getContext().getApplicationContext().getBean(StorageClient.BEAN_NAME);
		}
	}

	protected void initScheduler() {
		scheduler = (QuartzSchedulingService) scope.getParent().getContext().getBean(QuartzSchedulingService.BEAN_NAME);

	}

	@Override
	public boolean init(IScope scope, String name, boolean isAppend) {

		this.streamId = name;
		this.scope = scope;
		initScheduler();
		if (scheduler == null) {
			logger.warn("scheduler is not available in beans for {}", name);
			return false;
		}

		initializeDataStore();
		enableSettings();
		initStorageClient();
		enableMp4Setting();

		if (mp4MuxingEnabled) {
			Mp4Muxer mp4Muxer = new Mp4Muxer(storageClient, scheduler);
			mp4Muxer.setAddDateTimeToSourceName(addDateTimeToMp4FileName);
			mp4Muxer.setBitstreamFilter(mp4Filtername);
			addMuxer(mp4Muxer);
			logger.info("adding MP4 Muxer, add datetime to file name {}", addDateTimeToMp4FileName);
		} 

		if (hlsMuxingEnabled) {
			HLSMuxer hlsMuxer = new HLSMuxer(scheduler, hlsListSize, hlsTime, hlsPlayListType, getAppSettings().getHlsFlags());
			hlsMuxer.setDeleteFileOnExit(deleteHLSFilesOnExit);
			addMuxer(hlsMuxer);
			logger.info("adding HLS Muxer for {}", name);
		}

		for (Muxer muxer : muxerList) {
			muxer.init(scope, name, 0);
		}
		return true;
	}


	protected void enableMp4Setting() {
		broadcast = getBroadcast();
		
		if (broadcast != null) {
			if (broadcast.getMp4Enabled() == MP4_DISABLED_FOR_STREAM) {
				// if stream specific mp4 setting is disabled 
				mp4MuxingEnabled = false;
			}
			else if ( broadcast.getMp4Enabled() == MP4_ENABLED_FOR_STREAM) {
				// if stream specific mp4 setting is enabled 
				mp4MuxingEnabled = true;
			}
		}		
	}


	public boolean prepare() throws Exception {

		inputFormatContext = avformat.avformat_alloc_context();
		if (inputFormatContext == null) {
			logger.info("cannot allocate input context");
			return false;
		}

		avio_alloc_context = avio_alloc_context(new BytePointer(avutil.av_malloc(BUFFER_SIZE)), BUFFER_SIZE, 0,
				inputFormatContext, getReadCallback(), null, null);

		inputFormatContext.pb(avio_alloc_context);


		queueReferences.put(inputFormatContext, inputContext);

		int ret;
		logger.debug("before avformat_open_input for stream {}", streamId);

		if (avformat_open_input(inputFormatContext, (String) null, avformat.av_find_input_format("flv"),
				(AVDictionary) null) < 0) {
			logger.error("cannot open input context for stream: {}", streamId);
			return false;
		}

		logger.debug("after avformat_open_input for stream {}", streamId);
		long startFindStreamInfoTime = System.currentTimeMillis();

		ret = avformat_find_stream_info(inputFormatContext, (AVDictionary) null);
		if (ret < 0) {
			logger.info("Could not find stream information for stream {}", streamId);
			return false;
		}
		logger.info("avformat_find_stream_info takes {}ms", System.currentTimeMillis() - startFindStreamInfoTime);


		logger.info("after avformat_find_sream_info for stream: {}", streamId);

		return prepareInternal(inputFormatContext);
	}

	public boolean prepareInternal(AVFormatContext inputFormatContext) throws Exception{
		return prepareMuxers(inputFormatContext);
	}

	public boolean prepareMuxers(AVFormatContext inputFormatContext) throws Exception {

		if (av_log_get_level() >= AV_LOG_INFO) {
			// Dump information about file onto standard error
			av_dump_format(inputFormatContext, 0, streamId, 0);
		}

		Iterator iterator = muxerList.iterator();
		while (iterator.hasNext()) {
			Muxer muxer = iterator.next();
			if (!muxer.prepare(inputFormatContext)) {
				iterator.remove();
				logger.warn("muxer prepare returns false {}",  muxer.getFormat());
			}
		}

		if (muxerList.isEmpty()) {
			return false;
		}

		startTime = System.currentTimeMillis();

		return true;
	}

	protected Read_packet_Pointer_BytePointer_int getReadCallback() {
		return readCallback;
	}

	/**
	 * 
	 * @param streamId id of the stream
	 * @param quality, quality string
	 * @param packetTime, time of the packet in milliseconds
	 * @param duration, the total elapsed time in milliseconds
	 * @param inputQueueSize, input queue size of the packets that is waiting to be processed
	 */
	public void changeStreamQualityParameters(String streamId, String quality, double speed, int inputQueueSize) {
		long now = System.currentTimeMillis();
		if((now - lastQualityUpdateTime) > 1000 &&
				((quality != null && !quality.equals(oldQuality)) || oldspeed == 0 || Math.abs(speed - oldspeed) > 0.05)) {

			lastQualityUpdateTime = now;
			getStreamHandler().setQualityParameters(streamId, quality, speed, inputQueueSize);
			oldQuality = quality;
			oldspeed = speed;
		}
	}

	private IAntMediaStreamHandler getStreamHandler() {
		if (appAdapter == null) {

			IContext context = MuxAdaptor.this.scope.getContext(); 
			ApplicationContext appCtx = context.getApplicationContext(); 
			appAdapter = (IAntMediaStreamHandler)appCtx.getBean("web.handler");

		}
		return appAdapter;
	}

	public AppSettings getAppSettings() {

		AppSettings appSettings = null;
		if(scope.getContext().getApplicationContext().containsBean(AppSettings.BEAN_NAME)) {
			appSettings = (AppSettings) scope.getContext().getApplicationContext().getBean(AppSettings.BEAN_NAME);
		}
		if (appSettings == null) {
			logger.warn("No app settings in context, returning default AppSettings for {}", streamId);
			appSettings = new AppSettings();
		}

		return appSettings;

	}

	public IDataStore initializeDataStore() {
		if(dataStore == null) {

			IDataStoreFactory dsf = (IDataStoreFactory) scope.getContext().getBean(IDataStoreFactory.BEAN_NAME);
			dataStore = dsf.getDataStore();
		}
		return dataStore;
	}

	@Override
	public void execute(ISchedulingService service) throws CloneNotSupportedException {

		if (isPipeReaderJobRunning.compareAndSet(false, true)) {
			while (true) {
				if (inputFormatContext == null) {
					break;
				}
				int ret = av_read_frame(inputFormatContext, pkt);

				if (ret >= 0) {
					writePacket(inputFormatContext.streams(pkt.stream_index()), pkt);

					av_packet_unref(pkt);
				} 
				else {
					closeResources();
				}
				// if there is not element in the qeueue,
				// break the loop
				if (inputQueue.peek() == null || inputFormatContext == null) {
					break;
				}
			}

			isPipeReaderJobRunning.compareAndSet(true, false);
		}

	}


	public void writePacket(AVStream stream, AVPacket pkt) {

		long currentTime = System.currentTimeMillis();
		long packetTime = av_rescale_q(pkt.pts(), stream.time_base(), timeBaseForMS);

		if (firstPacketTime == -1) {
			firstPacketTime = packetTime;
			logger.info("first packet time {}", firstPacketTime);
		}

		timeDiffBetweenVideoandElapsed = (currentTime - startTime) - packetTime;
		elapsedTime = currentTime - startTime;

		double speed = 0L;
		if (elapsedTime > 0) {
			speed= (double)(packetTime - firstPacketTime)/elapsedTime;
			if (Double.isNaN(speed) && logger.isWarnEnabled()) {
				logger.warn("speed is NaN, packetTime: {}, first packetTime: {}, elapsedTime:{}", packetTime, firstPacketTime, elapsedTime);
			}
		}
		String quality = QUALITY_POOR;

		if(timeDiffBetweenVideoandElapsed < 1800) 
		{
			quality = QUALITY_GOOD;
		}
		else if(timeDiffBetweenVideoandElapsed>1799) 
		{
			quality = QUALITY_AVERAGE;
		}

		receivedPacketCount++;
		int inputQueueSize = getInputQueueSize();
		if (receivedPacketCount % 500 == 0) {
			logger.info("Number of items in the queue {}", inputQueueSize);
		}

		changeStreamQualityParameters(this.streamId, quality, speed, inputQueueSize);

		if (!firstKeyFrameReceivedChecked && stream.codec().codec_type() == AVMEDIA_TYPE_VIDEO) {
			int keyFrame = pkt.flags() & AV_PKT_FLAG_KEY;
			if (keyFrame == 1) {
				firstKeyFrameReceivedChecked = true;
			} else {
				logger.warn("First video packet is not key frame. It will drop for direct muxing. Stream {}" , streamId);
				// return if firstKeyFrameReceived is not received
				// below return is important otherwise it does not work with like some encoders(vidiu)
				return;
			}
		}

		for (Muxer muxer : muxerList) {
			muxer.writePacket(pkt, stream);
		}

	}

	public void writeTrailer(AVFormatContext inputFormatContext) 
	{
		for (Muxer muxer : muxerList) {
			muxer.writeTrailer();
		}
		//This is allocated and needs to free for every case
		av_packet_free(pkt);

		if (timeBaseForMS != null) {
			timeBaseForMS.close();
			timeBaseForMS = null;
		}
	}


	public void closeResources() {
		logger.info("close resources for streamId -> {}", streamId);

		if (packetFeederJobName != null) {
			logger.info("removing scheduled job {} ", packetFeederJobName);
			scheduler.removeScheduledJob(packetFeederJobName);
		}

		writeTrailer(inputFormatContext);

		queueReferences.remove(inputFormatContext);

		avformat_close_input(inputFormatContext);

		if (avio_alloc_context != null) {
			if (avio_alloc_context.buffer() != null) {
				av_free(avio_alloc_context.buffer());
				avio_alloc_context.buffer(null);
			}
			av_free(avio_alloc_context);
			avio_alloc_context = null;
		}

		inputFormatContext = null;
		isRecording = false;

		changeStreamQualityParameters(this.streamId, QUALITY_NA, 0, getInputQueueSize());

	}




	public static byte[] getFLVFrame(IStreamPacket packet) throws IOException {
		/**
		 * Tag header = 11 bytes |-|---|----|---| 0 = type 1-3 = data size 4-7 =
		 * timestamp 8-10 = stream id (always 0) Tag data = variable bytes
		 * Previous tag = 4 bytes (tag header size + tag data size)
		 * 
		 * ITag tag = new Tag(); tag.setDataType(packet.getDataType());
		 * tag.setBodySize(data.limit());
		 * tag.setTimestamp(packet.getTimestamp());
		 */
		// skip tags with no data
		int bodySize = packet.getData().limit();
		// ensure that the channel is still open
		// get the data type
		byte dataType = packet.getDataType(); 
		// if we're writing non-meta tags do seeking and tag size update

		// set a var holding the entire tag size including the previous tag
		// length
		int totalTagSize = TAG_HEADER_LENGTH + bodySize + 4;
		// resize
		// create a buffer for this tag
		ByteBuffer tagBuffer = ByteBuffer.allocate(totalTagSize);
		// get the timestamp
		int timestamp = packet.getTimestamp(); 
		// allow for empty tag bodies
		byte[] bodyBuf = null;
		if (bodySize > 0) {
			// create an array big enough
			bodyBuf = new byte[bodySize];
			// put the bytes into the array
			packet.getData().position(0);
			packet.getData().get(bodyBuf);
			// get the audio or video codec identifier
			packet.getData().position(0);

		}

		// Data Type
		IOUtils.writeUnsignedByte(tagBuffer, dataType); // 1
		// Body Size - Length of the message. Number of bytes after StreamID to
		// end of tag
		// (Equal to length of the tag - 11)
		IOUtils.writeMediumInt(tagBuffer, bodySize); // 3
		// Timestamp
		IOUtils.writeExtendedMediumInt(tagBuffer, timestamp); // 4
		// Stream id
		tagBuffer.put(DEFAULT_STREAM_ID); // 3
		// get the body if we have one
		if (bodyBuf != null) {
			tagBuffer.put(bodyBuf);
		}
		// we add the tag size
		tagBuffer.putInt(TAG_HEADER_LENGTH + bodySize);
		// flip so we can process from the beginning
		tagBuffer.flip();
		// write the tag

		return tagBuffer.array();

	}

	public static byte[] getFLVHeader() {
		org.red5.io.flv.FLVHeader flvHeader = new org.red5.io.flv.FLVHeader();
		flvHeader.setFlagVideo(true);
		flvHeader.setFlagAudio(true);
		// create a buffer
		ByteBuffer header = ByteBuffer.allocate(HEADER_LENGTH + 4); // FLVHeader
		// (9 bytes)
		// +
		// PreviousTagSize0
		// (4 bytes)
		flvHeader.write(header);
		return header.array();
	}

	@Override
	public void start() {
		isRecording = false;
		logger.info("Number of items in the queue while adaptor is being started to prepare is {}", getInputQueueSize());
		scheduler.addScheduledOnceJob(0, new IScheduledJob() {

			@Override
			public void execute(ISchedulingService service) throws CloneNotSupportedException {
				logger.info("before prepare for {}", streamId);
				try {
					if (prepare()) {
						logger.info("after prepare for {}", streamId);
						isRecording = true;
						startTime = System.currentTimeMillis();
						packetFeederJobName = scheduler.addScheduledJob(10, MuxAdaptor.this);
						logger.info("Number of items in the queue while adaptor is scheduled to process incoming packets is {}", getInputQueueSize());

						logger.info("Packet Feeder Job Name {}", packetFeederJobName);
					} else {
						logger.warn("input format context cannot be created for stream -> {}", streamId);
						if (broadcastStream != null) {
							broadcastStream.removeStreamListener(MuxAdaptor.this);
						}
						logger.warn("closing adaptor for {}", streamId);
						closeResources();
						logger.warn("closed adaptor for {}", streamId);
					}
				} catch (Exception e) {
					logger.error(ExceptionUtils.getStackTrace(e));
				}
			}
		});

	}

	@Override
	public void stop() {
		logger.info("Calling stop for {}", streamId);
		if (inputFormatContext == null) {
			logger.warn("Mux adaptor stopped returning for {}", streamId);
			return;
		}
		InputContext inputContextRef = queueReferences.get(inputFormatContext);
		if (inputContextRef != null) {
			inputContextRef.stopRequestExist = true;
		}
	}

	@Override
	public void packetReceived(IBroadcastStream stream, IStreamPacket packet) {

		byte[] flvFrame;
		try {
			flvFrame = getFLVFrame(packet);

			if (flvFrame.length <= BUFFER_SIZE) {
				inputQueue.add(flvFrame);
				inputContext.queueSize.incrementAndGet();
			} else {
				int numberOfBytes = flvFrame.length;
				int startIndex = 0;
				int copySize = 0;
				while (numberOfBytes != 0) {
					if (numberOfBytes > BUFFER_SIZE) {
						copySize = BUFFER_SIZE;
					} else {
						copySize = numberOfBytes;
					}
					byte[] data = Arrays.copyOfRange(flvFrame, startIndex, startIndex + copySize);
					inputQueue.add(data);
					inputContext.queueSize.incrementAndGet();
					numberOfBytes -= copySize;
					startIndex += copySize;
				}
			}

		} catch (IOException e) {
			logger.error(e.getMessage());
		}
	}

	@Override
	public boolean isRecording() {
		return isRecording;
	}

	@Override
	public boolean isAppending() {
		return false;
	}

	@Override
	public FileConsumer getFileConsumer() {
		return null;
	}

	@Override
	public void setFileConsumer(FileConsumer recordingConsumer) {
		//No need to implement
	}

	@Override
	public String getFileName() {
		return null;
	}

	@Override
	public void setFileName(String fileName) {
		//No need to implement
	}

	public List getMuxerList() {
		return muxerList;
	}


	public void setStorageClient(StorageClient storageClient) {
		this.storageClient = storageClient;
	}

	public boolean isWebRTCEnabled() {
		return webRTCEnabled;
	}

	public void setWebRTCEnabled(boolean webRTCEnabled) {
		this.webRTCEnabled = webRTCEnabled;
	}

	public void setHLSFilesDeleteOnExit(boolean deleteHLSFilesOnExit) {
		this.deleteHLSFilesOnExit = deleteHLSFilesOnExit;
	}

	public int getInputQueueSize() {
		return inputContext.queueSize.get();
	}

	public void setPreviewOverwrite(boolean overwrite) {
		this.previewOverwrite  = overwrite;
	}


	public boolean isPreviewOverwrite() {
		return previewOverwrite;
	}

	public long getStartTime() {
		return startTime;
	}


	public void setStartTime(long startTime) {
		this.startTime = startTime;
	}

	public List getEncoderSettingsList() {
		return encoderSettingsList;
	}

	public void setEncoderSettingsList(List encoderSettingsList) {
		this.encoderSettingsList = encoderSettingsList;
	}

	public boolean isStreamSource() {
		return isStreamSource;
	}

	public void setStreamSource(boolean isStreamSource) {
		this.isStreamSource = isStreamSource;
	}

	public boolean isObjectDetectionEnabled() {
		return objectDetectionEnabled;
	}

	public void setObjectDetectionEnabled(Boolean objectDetectionEnabled) {
		this.objectDetectionEnabled = objectDetectionEnabled;
	}

	public int getPreviewCreatePeriod() {
		return previewCreatePeriod;
	}

	public void setPreviewCreatePeriod(int previewCreatePeriod) {
		this.previewCreatePeriod = previewCreatePeriod;
	}

	public String getStreamId() {
		return streamId;
	}

	public void setStreamId(String streamId) {
		this.streamId = streamId;
	}

	public long getFirstPacketTime() {
		return firstPacketTime;
	}

	public StorageClient getStorageClient() {
		return storageClient;
	}

	/**
	 * Setter for {@link #firstKeyFrameReceivedChecked}
	 * @param firstKeyFrameReceivedChecked
	 */
	public void setFirstKeyFrameReceivedChecked(boolean firstKeyFrameReceivedChecked) {
		this.firstKeyFrameReceivedChecked = firstKeyFrameReceivedChecked;
	}

	public Broadcast getBroadcast() {

		if(broadcast == null) {

			broadcast = dataStore.get(this.streamId);
		}
		return broadcast;
	}

	// this is for test cases
	public void setBroadcast(Broadcast broadcast) {
		this.broadcast = broadcast;
	}

}






© 2015 - 2024 Weber Informatics LLC | Privacy Policy