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

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

package io.antmedia.muxer;


import static org.bytedeco.ffmpeg.global.avcodec.AV_PKT_FLAG_KEY;
import static org.bytedeco.ffmpeg.global.avcodec.avcodec_parameters_copy;
import static org.bytedeco.ffmpeg.global.avformat.avformat_alloc_output_context2;
import static org.bytedeco.ffmpeg.global.avutil.AVMEDIA_TYPE_VIDEO;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

import org.bytedeco.ffmpeg.avcodec.AVPacket;
import org.bytedeco.ffmpeg.avformat.AVFormatContext;
import org.bytedeco.ffmpeg.avformat.AVStream;
import org.red5.server.api.IContext;
import org.red5.server.api.scope.IScope;
import org.springframework.context.ApplicationContext;

import io.antmedia.AntMediaApplicationAdapter;
import io.antmedia.AppSettings;
import io.antmedia.storage.StorageClient;
import io.vertx.core.Vertx;

public abstract class RecordMuxer extends Muxer {

	protected File fileTmp;
	protected StorageClient storageClient = null;
	protected int resolution;
	
	protected boolean uploadMP4ToS3 = true;

	protected String previewPath;

	private String subFolder = null;

	private static final int S3_CONSTANT = 0b001;

	/**
	 * 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
	 */
	protected boolean firstKeyFrameReceivedChecked = false;

	private String s3FolderPath = "streams";

	/**
	 * Millisecond timestamp with record muxer initialization.
	 * It will be define when record muxer is called by anywhere
	 */
	private long startTime = 0;


	protected RecordMuxer(StorageClient storageClient, Vertx vertx, String s3FolderPath) {
		super(vertx);
		this.storageClient = storageClient;
		this.s3FolderPath = s3FolderPath;
		firstAudioDts = -1;
		firstVideoDts = -1;
	}

	protected int[] SUPPORTED_CODECS;

	public boolean isCodecSupported(int codecId) {
		for (int i=0; i< SUPPORTED_CODECS.length; i++) {
			if (codecId == SUPPORTED_CODECS[i]) {
				return true;
			}
		}
		return false;

	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void init(IScope scope, final String name, int resolutionHeight, String subFolder, int bitrate) {
		super.init(scope, name, resolutionHeight, false, subFolder, bitrate);

		this.streamId = name;
		this.resolution = resolutionHeight;
		this.subFolder = subFolder;

		this.startTime = System.currentTimeMillis();

	}



	@Override
	public AVFormatContext getOutputFormatContext() {
		if (outputFormatContext == null) 
		{
			outputFormatContext= new AVFormatContext(null);
			fileTmp = new File(file.getAbsolutePath() + TEMP_EXTENSION);
			int ret = avformat_alloc_output_context2(outputFormatContext, null, format, fileTmp.getAbsolutePath());
			if (ret < 0) {
				logger.info("Could not create output context for {}", streamId);
				return null;
			}
		}
		return outputFormatContext;
	}

	protected boolean prepareAudioOutStream(AVStream inStream, AVStream outStream) {
		int ret = avcodec_parameters_copy(outStream.codecpar(), inStream.codecpar());
		if (ret < 0) {
			logger.info("Cannot get codec parameters for {}", streamId);
			return false;
		}
		return true;
	}
	
	
	@Override
	public String getOutputURL() {
		return fileTmp.getAbsolutePath();
	}
		
	public void setPreviewPath(String path){
		this.previewPath = path;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public synchronized void writeTrailer() {

		super.writeTrailer();


		vertx.executeBlocking(l->{
			try {

				IContext context = RecordMuxer.this.scope.getContext();
				ApplicationContext appCtx = context.getApplicationContext();
				AntMediaApplicationAdapter adaptor = (AntMediaApplicationAdapter) appCtx.getBean(AntMediaApplicationAdapter.BEAN_NAME);

				AppSettings appSettings = (AppSettings) appCtx.getBean(AppSettings.BEAN_NAME);

				File f = getFinalFileName(appSettings.isS3RecordingEnabled());

				finalizeRecordFile(f);

				adaptor.muxingFinished(streamId, f, startTime, getDurationInMs(f,streamId), resolution, previewPath);

				logger.info("File: {} exist: {}", fileTmp.getAbsolutePath(), fileTmp.exists());


				if((appSettings.getUploadExtensionsToS3()&S3_CONSTANT) == 0){
					this.uploadMP4ToS3 = false;
				}

				if (appSettings.isS3RecordingEnabled() && this.uploadMP4ToS3 ) {
					logger.info("Storage client is available saving {} to storage", f.getName());

					saveToStorage(s3FolderPath + File.separator + (subFolder != null ? subFolder + File.separator : "" ), f, f.getName(), storageClient);
				}
			} catch (Exception e) {
				logger.error(e.getMessage());
			}
			l.complete();
		}, null);

	}


	public File getFinalFileName(boolean isS3Enabled)
	{
		String absolutePath = fileTmp.getAbsolutePath();
		String origFileName = absolutePath.replace(TEMP_EXTENSION, "");

		String prefix = s3FolderPath + File.separator + (subFolder != null ? subFolder + File.separator : "" );

		String fileName = getFile().getName();

		File f = new File(origFileName);

		if ( isS3Enabled && this.uploadMP4ToS3 && storageClient != null && doesFileExistInS3(storageClient, prefix+fileName)) 
		{
			int i = 0;

			do {
				i++;
				fileName = initialResourceNameWithoutExtension + "_" + i + extension;

				origFileName = origFileName.substring(0, origFileName.lastIndexOf("/") + 1) + fileName;
				f = new File(origFileName);
			} while (doesFileExistInS3(storageClient, prefix+fileName) || f.exists() || f.isDirectory());
		}
		return f;
	}

	private static boolean doesFileExistInS3(StorageClient storageClient, String name) {
		return storageClient.fileExist(name);
	}

	public static void saveToStorage(String prefix, File fileToUpload, String fileName, StorageClient storageClient) {
		saveToStorage(prefix, fileToUpload, fileName, storageClient, true);
	}

	public static void saveToStorage(String prefix, File fileToUpload, String fileName, StorageClient storageClient, boolean deleteLocalFile) {
		storageClient.save(prefix + fileName, fileToUpload, deleteLocalFile);
	}


	protected void finalizeRecordFile(final File file) throws IOException {
		Files.move(fileTmp.toPath(),file.toPath());
		logger.info("{} is ready", file.getName());
	}

	@Override
	public boolean checkToDropPacket(AVPacket pkt, int codecType) {
		if (!firstKeyFrameReceivedChecked && codecType == AVMEDIA_TYPE_VIDEO) 
		{
			if(firstVideoDts == -1) {
				firstVideoDts = pkt.dts();
			}

			int keyFrame = pkt.flags() & AV_PKT_FLAG_KEY;
			//we set start time here because we start recording with key frame and drop the other
			//setting here improves synch between audio and video
			if (keyFrame == 1) {
				firstKeyFrameReceivedChecked = true;
				logger.warn("First key frame received for stream: {}", streamId);
			} else {
				logger.info("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 true;

			}
		}
		//don't drop packet because it's either audio packet or key frame is received
		return false;
	}
	

	public boolean isUploadingToS3(){return uploadMP4ToS3;}


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy