diff --git a/src/main/java/io/antmedia/AppSettings.java b/src/main/java/io/antmedia/AppSettings.java index 17272ad59..d4e24462a 100644 --- a/src/main/java/io/antmedia/AppSettings.java +++ b/src/main/java/io/antmedia/AppSettings.java @@ -2042,6 +2042,12 @@ public boolean isWriteStatsToDatastore() { */ @Value("${id3TagEnabled:false}") private boolean id3TagEnabled = false; + + /** + * Enables the SEI data for HLS + */ + @Value("${seiEnabled:false}") + private boolean seiEnabled = false; /** * Ant Media Server can get the audio level from incoming RTP Header in WebRTC streaming and send to the viewers. @@ -3584,6 +3590,14 @@ public void setId3TagEnabled(boolean id3TagEnabled) { this.id3TagEnabled = id3TagEnabled; } + public boolean isSeiEnabled() { + return seiEnabled; + } + + public void setSeiEnabled(boolean seiEnabled) { + this.seiEnabled = seiEnabled; + } + public boolean isSendAudioLevelToViewers() { return sendAudioLevelToViewers; } diff --git a/src/main/java/io/antmedia/muxer/HLSMuxer.java b/src/main/java/io/antmedia/muxer/HLSMuxer.java index fec52e1b6..728a35e94 100644 --- a/src/main/java/io/antmedia/muxer/HLSMuxer.java +++ b/src/main/java/io/antmedia/muxer/HLSMuxer.java @@ -2,8 +2,8 @@ import static org.bytedeco.ffmpeg.global.avcodec.*; import static org.bytedeco.ffmpeg.global.avformat.avformat_alloc_output_context2; -import static org.bytedeco.ffmpeg.global.avutil.AVMEDIA_TYPE_DATA; -import static org.bytedeco.ffmpeg.global.avutil.av_rescale_q; +import static org.bytedeco.ffmpeg.global.avutil.*; +import static org.bytedeco.ffmpeg.global.avutil.AV_OPT_SEARCH_CHILDREN; import java.io.File; import java.io.IOException; @@ -11,11 +11,9 @@ import java.nio.file.Files; import org.apache.commons.lang3.StringUtils; -import org.bytedeco.ffmpeg.avcodec.AVCodec; -import org.bytedeco.ffmpeg.avcodec.AVCodecContext; -import org.bytedeco.ffmpeg.avcodec.AVCodecParameters; -import org.bytedeco.ffmpeg.avcodec.AVPacket; +import org.bytedeco.ffmpeg.avcodec.*; import org.bytedeco.ffmpeg.avformat.AVFormatContext; +import org.bytedeco.ffmpeg.avformat.AVStream; import org.bytedeco.ffmpeg.avutil.AVRational; import org.bytedeco.ffmpeg.global.avcodec; import org.bytedeco.javacpp.BytePointer; @@ -28,6 +26,10 @@ public class HLSMuxer extends Muxer { + public static final String SEI_USER_DATA = "sei_user_data"; + + private static final String SEI_UUID = "086f3693-b7b3-4f2c-9653-21492feee5b8+"; + private static final String SEGMENT_SUFFIX_TS = "%0"+SEGMENT_INDEX_LENGTH+"d.ts"; protected static Logger logger = LoggerFactory.getLogger(HLSMuxer.class); @@ -68,6 +70,9 @@ public class HLSMuxer extends Muxer { private boolean id3Enabled = false; + private boolean seiEnabled = false; + + public HLSMuxer(Vertx vertx, StorageClient storageClient, String s3StreamsFolderPath, int uploadExtensionsToS3, String httpEndpoint, boolean addDateTimeToResourceName) { super(vertx); this.storageClient = storageClient; @@ -351,19 +356,58 @@ public synchronized boolean addStream(AVCodec codec, AVCodecContext codecContext logger.error("Cannot get codec parameters for {}", streamId); } - //call super directly because no need to add bit stream filter + //call super directly because no need to add bit stream filter return super.addStream(codecParameter, codecContext.time_base(), streamIndex); } + + @Override + public synchronized boolean addVideoStream(int width, int height, AVRational timebase, int codecId, int streamIndex, + boolean isAVC, AVCodecParameters codecpar) { + + boolean result = super.addVideoStream(width, height, timebase, codecId, streamIndex, isAVC, codecpar); + if (result && seiEnabled) + { + AVStream outStream = getOutputFormatContext().streams(inputOutputStreamIndexMap.get(streamIndex)); + + setBitstreamFilter("h264_metadata"); + + AVBSFContext avbsfContext = initVideoBitstreamFilter(getBitStreamFilter(), outStream.codecpar(), inputTimeBaseMap.get(streamIndex)); + + if (avbsfContext != null) { + int ret = avcodec_parameters_copy(outStream.codecpar(), avbsfContext.par_out()); + result = ret == 0; + } + + setSeiData("initial sei data"); + + logger.info("Adding video stream index:{} for stream:", streamIndex); + } + + return result; + } + + public void setSeiData(String data) { + int ret = av_opt_set(bsfFilterContextList.get(0).priv_data(), SEI_USER_DATA, SEI_UUID+data, AV_OPT_SEARCH_CHILDREN); + logError(ret, "Cannot set sei_user_data for {} and error is {}", streamId); + + + ret = av_bsf_init(bsfFilterContextList.get(0)); + logError(ret, "Cannot update sei_user_data for {} and error is {}", streamId); + + } + + public static void logError(int ret, String message, String streamId) { + if (ret < 0 && logger.isErrorEnabled()) { + logger.error(message, streamId, Muxer.getErrorDefinition(ret)); + } + } @Override public synchronized boolean addStream(AVCodecParameters codecParameters, AVRational timebase, int streamIndex) { - bsfVideoName = "h264_mp4toannexb"; - - boolean ret = super.addStream(codecParameters, timebase, streamIndex); - - return ret; + setBitstreamFilter("h264_mp4toannexb"); + return super.addStream(codecParameters, timebase, streamIndex); } public boolean addID3Stream() { @@ -419,7 +463,12 @@ public String getSegmentFilename() { public void setId3Enabled(boolean id3Enabled) { this.id3Enabled = id3Enabled; } - + + public void setSeiEnabled(boolean seiEnabled) { + this.seiEnabled = seiEnabled; + } + + @Override protected synchronized void clearResource() { super.clearResource(); diff --git a/src/main/java/io/antmedia/muxer/Mp4Muxer.java b/src/main/java/io/antmedia/muxer/Mp4Muxer.java index ed2012b8e..a90d3c39e 100644 --- a/src/main/java/io/antmedia/muxer/Mp4Muxer.java +++ b/src/main/java/io/antmedia/muxer/Mp4Muxer.java @@ -143,8 +143,8 @@ public synchronized boolean addVideoStream(int width, int height, AVRational tim */ @Override protected boolean prepareAudioOutStream(AVStream inStream, AVStream outStream) { - if (bsfVideoName != null) { - AVBitStreamFilter adtsToAscBsf = av_bsf_get_by_name(this.bsfVideoName); + if (getBitStreamFilter() != null) { + AVBitStreamFilter adtsToAscBsf = av_bsf_get_by_name(this.getBitStreamFilter()); bsfContext = new AVBSFContext(null); int ret = av_bsf_alloc(adtsToAscBsf, bsfContext); diff --git a/src/main/java/io/antmedia/muxer/MuxAdaptor.java b/src/main/java/io/antmedia/muxer/MuxAdaptor.java index ace306048..101c25f80 100644 --- a/src/main/java/io/antmedia/muxer/MuxAdaptor.java +++ b/src/main/java/io/antmedia/muxer/MuxAdaptor.java @@ -193,6 +193,16 @@ public boolean addID3Data(String data) { return false; } + public boolean addSEIData(String data) { + for (Muxer muxer : muxerList) { + if(muxer instanceof HLSMuxer) { + ((HLSMuxer)muxer).setSeiData(data); + return true; + } + } + return false; + } + public static class PacketTime { public final long packetTimeMs; public final long systemTimeMs; @@ -433,6 +443,7 @@ public boolean init(IScope scope, String streamId, boolean isAppend) { hlsMuxer.setHlsParameters( hlsListSize, hlsTime, hlsPlayListType, getAppSettings().getHlsflags(), getAppSettings().getHlsEncryptionKeyInfoFile(), getAppSettings().getHlsSegmentType()); hlsMuxer.setDeleteFileOnExit(deleteHLSFilesOnExit); hlsMuxer.setId3Enabled(appSettings.isId3TagEnabled()); + hlsMuxer.setSeiEnabled(appSettings.isSeiEnabled()); addMuxer(hlsMuxer); logger.info("adding HLS Muxer for {}", streamId); } diff --git a/src/main/java/io/antmedia/muxer/Muxer.java b/src/main/java/io/antmedia/muxer/Muxer.java index a03ebb2fa..c4e56fbfe 100644 --- a/src/main/java/io/antmedia/muxer/Muxer.java +++ b/src/main/java/io/antmedia/muxer/Muxer.java @@ -12,18 +12,8 @@ import static org.bytedeco.ffmpeg.global.avformat.avformat_open_input; import static org.bytedeco.ffmpeg.global.avformat.avformat_write_header; import static org.bytedeco.ffmpeg.global.avformat.avio_closep; -import static org.bytedeco.ffmpeg.global.avutil.AVMEDIA_TYPE_AUDIO; -import static org.bytedeco.ffmpeg.global.avutil.AVMEDIA_TYPE_DATA; -import static org.bytedeco.ffmpeg.global.avutil.AVMEDIA_TYPE_VIDEO; -import static org.bytedeco.ffmpeg.global.avutil.AV_NOPTS_VALUE; -import static org.bytedeco.ffmpeg.global.avutil.AV_PIX_FMT_YUV420P; -import static org.bytedeco.ffmpeg.global.avutil.AV_ROUND_NEAR_INF; -import static org.bytedeco.ffmpeg.global.avutil.AV_ROUND_PASS_MINMAX; -import static org.bytedeco.ffmpeg.global.avutil.av_dict_free; -import static org.bytedeco.ffmpeg.global.avutil.av_dict_set; -import static org.bytedeco.ffmpeg.global.avutil.av_rescale_q; -import static org.bytedeco.ffmpeg.global.avutil.av_rescale_q_rnd; -import static org.bytedeco.ffmpeg.global.avutil.av_strerror; +import static org.bytedeco.ffmpeg.global.avutil.*; +import static org.bytedeco.ffmpeg.global.avutil.AV_OPT_SEARCH_CHILDREN; import java.io.File; import java.io.IOException; @@ -38,8 +28,6 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import io.antmedia.FFmpegUtilities; import io.antmedia.rest.RestServiceBase; @@ -133,14 +121,14 @@ public abstract class Muxer { /** * Bitstream filter name that will be applied to packets */ - protected String bsfVideoName = null; + protected List bsfVideoNames = new ArrayList<>(); protected String streamId = null; protected Map inputTimeBaseMap = new ConcurrentHashMap<>(); - protected AVBSFContext videoBsfFilterContext = null; + protected List bsfFilterContextList = new ArrayList<>(); protected int videoWidth; protected int videoHeight; @@ -464,10 +452,10 @@ protected synchronized void clearResource() { audioPkt = null; } - if (videoBsfFilterContext != null) { + for (AVBSFContext videoBsfFilterContext: bsfFilterContextList) { av_bsf_free(videoBsfFilterContext); - videoBsfFilterContext = null; } + bsfFilterContextList.clear(); /* close output */ if (outputFormatContext != null && @@ -532,7 +520,7 @@ public void logPacketIssue(String format, Object... arguments) { /** * Write packets to the output. This function is used in transcoding. * Previously, It's the replacement of {link {@link #writePacket(AVPacket)} - * @param avpacket + * @param pkt * @param codecContext */ public synchronized void writePacket(AVPacket pkt, AVCodecContext codecContext) { @@ -569,11 +557,15 @@ public ByteBuffer getPacketBufferWithExtradata(byte[] extradata, AVPacket pkt){ public void setBitstreamFilter(String bsfName) { - this.bsfVideoName = bsfName; + bsfVideoNames.add(bsfName); } public String getBitStreamFilter() { - return bsfVideoName; + if(!bsfVideoNames.isEmpty()) + { + return bsfVideoNames.get(0); + } + return null; } public File getFile() { @@ -836,16 +828,18 @@ && isCodecSupported(codecParameters.codec_id()) && //if it's not running add to the list registeredStreamIndexList.add(streamIndex); - if (bsfVideoName != null && codecParameters.codec_type() == AVMEDIA_TYPE_VIDEO) + if (codecParameters.codec_type() == AVMEDIA_TYPE_VIDEO) { - AVBSFContext videoBitstreamFilter = initVideoBitstreamFilter(codecParameters, timebase); - if (videoBitstreamFilter != null) - { - codecParameters = videoBitstreamFilter.par_out(); - timebase = videoBitstreamFilter.time_base_out(); + for (String bsfVideoName: bsfVideoNames) { + AVBSFContext videoBitstreamFilter = initVideoBitstreamFilter(bsfVideoName, codecParameters, timebase); + if (videoBitstreamFilter != null) + { + codecParameters = videoBitstreamFilter.par_out(); + timebase = videoBitstreamFilter.time_base_out(); + } } - } + String codecType = "audio"; if (codecParameters.codec_type() == AVMEDIA_TYPE_VIDEO) { @@ -886,9 +880,9 @@ else if (codecParameters.codec_type() == AVMEDIA_TYPE_DATA) return result; } - public AVBSFContext initVideoBitstreamFilter(AVCodecParameters codecParameters, AVRational timebase) { + public AVBSFContext initVideoBitstreamFilter(String bsfVideoName, AVCodecParameters codecParameters, AVRational timebase) { AVBitStreamFilter bsfilter = av_bsf_get_by_name(bsfVideoName); - videoBsfFilterContext = new AVBSFContext(null); + AVBSFContext videoBsfFilterContext = new AVBSFContext(null); int ret = av_bsf_alloc(bsfilter, videoBsfFilterContext); if (ret < 0) { @@ -909,6 +903,8 @@ public AVBSFContext initVideoBitstreamFilter(AVCodecParameters codecParameters, return null; } + bsfFilterContextList.add(videoBsfFilterContext); + return videoBsfFilterContext; } @@ -1180,32 +1176,22 @@ public void addExtradataIfRequired(AVPacket pkt, boolean isKeyFrame) protected void writeVideoFrame(AVPacket pkt, AVFormatContext context) { int ret; - - if (videoBsfFilterContext != null) + for(AVBSFContext videoBsfFilterContext : bsfFilterContextList) { ret = av_bsf_send_packet(videoBsfFilterContext, pkt); if (ret < 0) { logger.warn("Cannot send packet to bit stream filter for stream:{}", streamId); return; } - while (av_bsf_receive_packet(videoBsfFilterContext, pkt) == 0) - { - logger.trace("write video packet pts:{} dts:{}", pkt.pts(), pkt.dts()); - ret = av_write_frame(context, tmpPacket); - if (ret < 0 && logger.isWarnEnabled()) { - logger.warn("cannot write video frame to muxer({}) av_bsf_receive_packet. Error is {} ", file.getName(), getErrorDefinition(ret)); - } - } + ret = av_bsf_receive_packet(videoBsfFilterContext, pkt); } - else - { - logger.trace("write video packet pts:{} dts:{}", pkt.pts(), pkt.dts()); - ret = av_write_frame(context, pkt); - if (ret < 0 && logger.isWarnEnabled()) { - //TODO: this is written for some muxers like HLS because normalized video time is coming from WebRTC - //WebRTCVideoForwarder#getVideoTime. Fix this problem when upgrading the webrtc stack - logger.warn("cannot write video frame to muxer({}). Pts: {} dts:{} Error is {} ", file.getName(), pkt.pts(), pkt.dts(), getErrorDefinition(ret)); - } + + logger.trace("write video packet pts:{} dts:{}", pkt.pts(), pkt.dts()); + ret = av_write_frame(context, pkt); + if (ret < 0 && logger.isWarnEnabled()) { + //TODO: this is written for some muxers like HLS because normalized video time is coming from WebRTC + //WebRTCVideoForwarder#getVideoTime. Fix this problem when upgrading the webrtc stack + logger.warn("cannot write video frame to muxer({}). Pts: {} dts:{} Error is {} ", file.getName(), pkt.pts(), pkt.dts(), getErrorDefinition(ret)); } } diff --git a/src/main/java/io/antmedia/muxer/RtmpMuxer.java b/src/main/java/io/antmedia/muxer/RtmpMuxer.java index 365f02bfe..94966cd9f 100644 --- a/src/main/java/io/antmedia/muxer/RtmpMuxer.java +++ b/src/main/java/io/antmedia/muxer/RtmpMuxer.java @@ -150,7 +150,7 @@ public synchronized boolean prepareIO() if (openIO()) { - if (videoBsfFilterContext == null) + if (bsfFilterContextList.isEmpty()) { writeHeader(); return; @@ -188,7 +188,7 @@ public synchronized boolean writeHeader() { long startTime = System.currentTimeMillis(); super.writeHeader(); long diff = System.currentTimeMillis() - startTime; - logger.info("write header takes {} for rtmp:{} the bitstream filter name is {}", diff, getOutputURL(), bsfVideoName); + logger.info("write header takes {} for rtmp:{} the bitstream filter name is {}", diff, getOutputURL(), getBitStreamFilter()); headerWritten = true; setStatus(IAntMediaStreamHandler.BROADCAST_STATUS_BROADCASTING); @@ -245,7 +245,7 @@ public synchronized boolean addVideoStream(int width, int height, AVRational tim setBitstreamFilter("extract_extradata"); - AVBSFContext avbsfContext = initVideoBitstreamFilter(outStream.codecpar(), inputTimeBaseMap.get(streamIndex)); + AVBSFContext avbsfContext = initVideoBitstreamFilter(getBitStreamFilter(), outStream.codecpar(), inputTimeBaseMap.get(streamIndex)); if (avbsfContext != null) { int ret = avcodec_parameters_copy(outStream.codecpar(), avbsfContext.par_out()); @@ -292,16 +292,16 @@ private synchronized void writeFrameInternal(AVPacket pkt, AVRational inputTimeb logger.error("Cannot copy packet for {}", file.getName()); return; } - if (videoBsfFilterContext != null) + if (!bsfFilterContextList.isEmpty() && bsfFilterContextList.get(0) != null) { - ret = av_bsf_send_packet(videoBsfFilterContext, getTmpPacket()); + ret = av_bsf_send_packet(bsfFilterContextList.get(0), getTmpPacket()); if (ret < 0) { setStatus(IAntMediaStreamHandler.BROADCAST_STATUS_ERROR); logger.warn("cannot send packet to the filter"); return; } - while (av_bsf_receive_packet(videoBsfFilterContext, getTmpPacket()) == 0) + while (av_bsf_receive_packet(bsfFilterContextList.get(0), getTmpPacket()) == 0) { if (!headerWritten) { diff --git a/src/main/java/io/antmedia/rest/BroadcastRestService.java b/src/main/java/io/antmedia/rest/BroadcastRestService.java index 140ebd222..229efdbbc 100644 --- a/src/main/java/io/antmedia/rest/BroadcastRestService.java +++ b/src/main/java/io/antmedia/rest/BroadcastRestService.java @@ -1953,4 +1953,23 @@ public Result addID3Data(@Parameter(description = "the id of the stream", requir return new Result(false, null, "Stream is not available"); } } + + @Operation(description = "Add SEI data to HLS stream at the moment") + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Path("/{stream_id}/sei") + @Produces(MediaType.APPLICATION_JSON) + public Result addSEIData(@Parameter(description = "the id of the stream", required = true) @PathParam("stream_id") String streamId, + @Parameter(description = "SEI data.", required = false) String data) { + if(!getAppSettings().isSeiEnabled()) { + return new Result(false, null, "SEI is not enabled"); + } + MuxAdaptor muxAdaptor = getMuxAdaptor(streamId); + if(muxAdaptor != null) { + return new Result(muxAdaptor.addSEIData(data)); + } + else { + return new Result(false, null, "Stream is not available"); + } + } } diff --git a/src/test/java/io/antmedia/test/AppSettingsUnitTest.java b/src/test/java/io/antmedia/test/AppSettingsUnitTest.java index 4b243dc43..f5b904920 100644 --- a/src/test/java/io/antmedia/test/AppSettingsUnitTest.java +++ b/src/test/java/io/antmedia/test/AppSettingsUnitTest.java @@ -543,6 +543,9 @@ public void testUnsetAppSettings(AppSettings appSettings) { assertEquals(0, appSettings.getWebhookRetryCount()); assertEquals(1000, appSettings.getWebhookRetryDelay()); + assertEquals(false, appSettings.isSeiEnabled()); + + assertFalse(appSettings.isSecureAnalyticEndpoint()); assertEquals("mpegts", appSettings.getHlsSegmentType()); @@ -551,7 +554,7 @@ public void testUnsetAppSettings(AppSettings appSettings) { //by also checking its default value. assertEquals("New field is added to settings. PAY ATTENTION: Please CHECK ITS DEFAULT VALUE and fix the number of fields.", - 179, numberOfFields); + 180, numberOfFields); } diff --git a/src/test/java/io/antmedia/test/MuxerUnitTest.java b/src/test/java/io/antmedia/test/MuxerUnitTest.java index 7de6f29a6..4a6ab0a6f 100644 --- a/src/test/java/io/antmedia/test/MuxerUnitTest.java +++ b/src/test/java/io/antmedia/test/MuxerUnitTest.java @@ -24,7 +24,6 @@ import static org.bytedeco.ffmpeg.global.avutil.AV_SAMPLE_FMT_FLTP; import static org.bytedeco.ffmpeg.global.avutil.av_channel_layout_default; import static org.bytedeco.ffmpeg.global.avutil.av_dict_get; -import static org.bytedeco.ffmpeg.global.avutil.av_get_default_channel_layout; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; @@ -59,7 +58,6 @@ 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.ConcurrentSkipListSet; import java.util.concurrent.TimeUnit; @@ -69,7 +67,6 @@ import org.apache.commons.lang3.RandomUtils; import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.buffer.SimpleBufferAllocator; -import org.apache.tika.io.IOUtils; import org.awaitility.Awaitility; import org.bytedeco.ffmpeg.avcodec.AVBSFContext; import org.bytedeco.ffmpeg.avcodec.AVCodecContext; @@ -107,8 +104,6 @@ import org.red5.server.api.scope.IScope; import org.red5.server.api.stream.IStreamCapableConnection; import org.red5.server.api.stream.IStreamPacket; -import org.red5.server.net.protocol.RTMPDecodeState; -import org.red5.server.net.rtmp.RTMPConnection; import org.red5.server.net.rtmp.RTMPMinaConnection; import org.red5.server.net.rtmp.codec.RTMP; import org.red5.server.net.rtmp.codec.RTMPProtocolDecoder; @@ -133,10 +128,8 @@ import io.antmedia.datastore.db.DataStore; import io.antmedia.datastore.db.DataStoreFactory; import io.antmedia.datastore.db.InMemoryDataStore; -import io.antmedia.datastore.db.MapDBStore; import io.antmedia.datastore.db.types.Broadcast; import io.antmedia.datastore.db.types.Endpoint; -import io.antmedia.datastore.db.types.StreamInfo; import io.antmedia.integration.AppFunctionalV2Test; import io.antmedia.integration.MuxingTest; import io.antmedia.muxer.HLSMuxer; @@ -159,7 +152,6 @@ import io.antmedia.storage.StorageClient; import io.antmedia.test.utils.VideoInfo; import io.antmedia.test.utils.VideoProber; -import io.netty.handler.codec.DecoderException; import io.vertx.core.Vertx; import org.springframework.test.util.ReflectionTestUtils; @@ -322,7 +314,6 @@ public void testAddAudioStream() { assertEquals(1, mp4Muxer.getOutputFormatContext().nb_streams()); } - @Test @@ -367,7 +358,7 @@ public void testInitVideoBitstreamFilter() { AVCodecParameters codecParameters = new AVCodecParameters(); codecParameters.codec_id(AV_CODEC_ID_H264); codecParameters.codec_type(AVMEDIA_TYPE_VIDEO); - AVBSFContext avbsfContext = mp4Muxer.initVideoBitstreamFilter(codecParameters, Muxer.avRationalTimeBase); + AVBSFContext avbsfContext = mp4Muxer.initVideoBitstreamFilter("h264_mp4toannexb", codecParameters, Muxer.avRationalTimeBase); assertNotNull(avbsfContext); @@ -527,19 +518,19 @@ public void testFFmpegReadPacket() { logger.info("codecpar.bit_rate(): {}\n" + - " codecpar.bits_per_coded_sample(): {} \n" + - " codecpar.bits_per_raw_sample(): {} \n" + - " codecpar.block_align(): {}\n" + - " codecpar.channel_layout(): {}\n" + - " codecpar.channels(): {}\n" + - " codecpar.codec_id(): {}\n" + - " codecpar.codec_tag(): {}\n" + - " codecpar.codec_type(): {} \n" + - " codecpar.format(): {}\n" + - " codecpar.frame_size():{} \n" + - " codecpar.level():{} \n" + - " codecpar.profile():{} \n" + - " codecpar.sample_rate(): {}", + " codecpar.bits_per_coded_sample(): {} \n" + + " codecpar.bits_per_raw_sample(): {} \n" + + " codecpar.block_align(): {}\n" + + " codecpar.channel_layout(): {}\n" + + " codecpar.channels(): {}\n" + + " codecpar.codec_id(): {}\n" + + " codecpar.codec_tag(): {}\n" + + " codecpar.codec_type(): {} \n" + + " codecpar.format(): {}\n" + + " codecpar.frame_size():{} \n" + + " codecpar.level():{} \n" + + " codecpar.profile():{} \n" + + " codecpar.sample_rate(): {}", codecpar.bit_rate(), codecpar.bits_per_coded_sample(), @@ -567,10 +558,10 @@ public void testFFmpegReadPacket() { logger.info(" pkt.duration():{} \n" + - " pkt.flags(): {} \n" + - " pkt.pos(): {}\n" + - " pkt.size(): {}\n" + - " pkt.stream_index():{} ", + " pkt.flags(): {} \n" + + " pkt.pos(): {}\n" + + " pkt.size(): {}\n" + + " pkt.stream_index():{} ", pkt.duration(), pkt.flags(), pkt.pos(), @@ -711,22 +702,22 @@ public void testStreamIndex() { rtmpMuxer.addStream(codecParameters, rat, 50); } - + @Test public void testRecordMuxerS3Prefix() { String s3Prefix = RecordMuxer.getS3Prefix("s3", null); assertEquals("s3/", s3Prefix); - + s3Prefix = RecordMuxer.getS3Prefix("s3", "test"); assertEquals("s3/test/", s3Prefix); - + s3Prefix = RecordMuxer.getS3Prefix("s3/", "test/"); assertEquals("s3/test/", s3Prefix); } - + @Test public void testHLSMuxerGetOutputURLAndSegmentFilename() { - + appScope = (WebScope) applicationContext.getBean("web.scope"); vertx = (Vertx) appScope.getContext().getApplicationContext().getBean(IAntMediaStreamHandler.VERTX_BEAN_NAME); @@ -735,86 +726,88 @@ public void testHLSMuxerGetOutputURLAndSegmentFilename() { HLSMuxer hlsMuxer = new HLSMuxer(vertx, Mockito.mock(StorageClient.class), "streams", 0, "http://example.com", false); String streamId = "streamId"; String subFolder = "subfolder"; - + hlsMuxer.init(appScope, streamId, 0, subFolder, 0); - - - assertEquals("http://example.com/"+subFolder+"/"+streamId+".m3u8", hlsMuxer.getOutputURL()); - assertEquals("http://example.com/"+subFolder+"/"+streamId+"%09d.ts", hlsMuxer.getSegmentFilename()); - + + + assertEquals("http://example.com/" + subFolder + "/" + streamId + ".m3u8", hlsMuxer.getOutputURL()); + assertEquals("http://example.com/" + subFolder + "/" + streamId + "%09d.ts", hlsMuxer.getSegmentFilename()); + } - + { //add trailer slash HLSMuxer hlsMuxer = new HLSMuxer(vertx, Mockito.mock(StorageClient.class), "streams", 0, "http://example.com/", false); String streamId = "streamId"; String subFolder = "subfolder/"; - + hlsMuxer.init(appScope, streamId, 0, subFolder, 0); - - - assertEquals("http://example.com/"+subFolder+streamId+".m3u8", hlsMuxer.getOutputURL()); - assertEquals("http://example.com/"+subFolder+streamId+"%09d.ts", hlsMuxer.getSegmentFilename()); - + + + assertEquals("http://example.com/" + subFolder + streamId + ".m3u8", hlsMuxer.getOutputURL()); + assertEquals("http://example.com/" + subFolder + streamId + "%09d.ts", hlsMuxer.getSegmentFilename()); + } - + { StorageClient storageClient = Mockito.mock(StorageClient.class); Mockito.when(storageClient.isEnabled()).thenReturn(true); - - + + HLSMuxer hlsMuxer = Mockito.spy(new HLSMuxer(vertx, storageClient, "streams/", 0b010, null, false)); String streamId = "streamId"; String subFolder = "subfolder/"; + hlsMuxer.setHlsParameters("1", "1", null, null, null, null); File[] file = new File[1]; file[0] = Mockito.mock(File.class); Mockito.when(file[0].exists()).thenReturn(true); Mockito.when(file[0].getName()).thenReturn(streamId + ".m3u8"); - + Mockito.doReturn(file).when(hlsMuxer).getHLSFilesInDirectory(Mockito.anyString()); - + hlsMuxer.init(appScope, streamId, 0, subFolder, 0); - - + + hlsMuxer.writeTrailer(); - + Mockito.verify(storageClient, Mockito.timeout(2000)).save("streams/subfolder/" + streamId + ".m3u8", file[0], true); - + } - + { StorageClient storageClient = Mockito.mock(StorageClient.class); Mockito.when(storageClient.isEnabled()).thenReturn(true); - - + + HLSMuxer hlsMuxer = Mockito.spy(new HLSMuxer(vertx, storageClient, "streams", 0b010, null, false)); String streamId = "streamId"; String subFolder = "subfolder"; + hlsMuxer.setHlsParameters("1", "1", null, null, null, null); File[] file = new File[1]; file[0] = Mockito.mock(File.class); Mockito.when(file[0].exists()).thenReturn(true); Mockito.when(file[0].getName()).thenReturn(streamId + ".m3u8"); - + Mockito.doReturn(file).when(hlsMuxer).getHLSFilesInDirectory(Mockito.anyString()); - + hlsMuxer.init(appScope, streamId, 0, subFolder, 0); - - + + hlsMuxer.writeTrailer(); - + Mockito.verify(storageClient, Mockito.timeout(2000)).save("streams/subfolder/" + streamId + ".m3u8", file[0], true); - + } - + { StorageClient storageClient = Mockito.mock(StorageClient.class); Mockito.when(storageClient.isEnabled()).thenReturn(true); - - + + HLSMuxer hlsMuxer = Mockito.spy(new HLSMuxer(vertx, storageClient, "streams", 0b010, null, false)); String streamId = "streamId"; hlsMuxer.setHlsParameters("1", "1", null, null, null, null); @@ -823,16 +816,16 @@ public void testHLSMuxerGetOutputURLAndSegmentFilename() { file[0] = Mockito.mock(File.class); Mockito.when(file[0].exists()).thenReturn(true); Mockito.when(file[0].getName()).thenReturn(streamId + ".m3u8"); - + Mockito.doReturn(file).when(hlsMuxer).getHLSFilesInDirectory(Mockito.anyString()); - + hlsMuxer.init(appScope, streamId, 0, null, 0); - - + + hlsMuxer.writeTrailer(); - + Mockito.verify(storageClient, Mockito.timeout(2000)).save("streams/" + streamId + ".m3u8", file[0], true); - + } } @@ -1051,7 +1044,7 @@ public void testAVWriteFrame() { //rtmpMuxer.set AVPacket pkt = av_packet_alloc(); - + appScope = (WebScope) applicationContext.getBean("web.scope"); rtmpMuxer.init(appScope, "", 0, "", 0); @@ -1254,71 +1247,72 @@ public void testRTMPWriteCrash() { //This is for testing writeHeader after writeTrailer. rtmpMuxer.writeHeader(); } - + @Test - public void testRtmpUrlWithoutAppName(){ + public void testRtmpUrlWithoutAppName() { { - RtmpMuxer rtmpMuxer = Mockito.spy(new RtmpMuxer("rtmp://a.rtmp.youtube.com/y8qd-42g5-1b53-fh15-2v0",vertx)); //RTMP URl without Appname + RtmpMuxer rtmpMuxer = Mockito.spy(new RtmpMuxer("rtmp://a.rtmp.youtube.com/y8qd-42g5-1b53-fh15-2v0", vertx)); //RTMP URl without Appname AVDictionary opt = rtmpMuxer.getOptionDictionary(); - AVDictionaryEntry optEntry = av_dict_get(opt,"rtmp_app",null,0); + AVDictionaryEntry optEntry = av_dict_get(opt, "rtmp_app", null, 0); assertEquals("rtmp_app", optEntry.key().getString()); assertEquals("", optEntry.value().getString()); } - - + + { - RtmpMuxer rtmpMuxer = Mockito.spy(new RtmpMuxer("rtmp://a.rtmp.youtube.com/y8qd-42g5-1b53-fh15-2v0/test",vertx)); //RTMP URl without Appname + RtmpMuxer rtmpMuxer = Mockito.spy(new RtmpMuxer("rtmp://a.rtmp.youtube.com/y8qd-42g5-1b53-fh15-2v0/test", vertx)); //RTMP URl without Appname AVDictionary opt = rtmpMuxer.getOptionDictionary(); - AVDictionaryEntry optEntry = av_dict_get(opt, "rtmp_app",null,0); - assertNull(optEntry); - - //if it's different from zero, it means no file is need to be open. + AVDictionaryEntry optEntry = av_dict_get(opt, "rtmp_app", null, 0); + assertNull(optEntry); + + //if it's different from zero, it means no file is need to be open. //If it's zero, Not "no file" and it means that file is need to be open . - assertEquals(0, rtmpMuxer.getOutputFormatContext().oformat().flags() & AVFMT_NOFILE); - - - rtmpMuxer.clearResource(); + assertEquals(0, rtmpMuxer.getOutputFormatContext().oformat().flags() & AVFMT_NOFILE); + + + rtmpMuxer.clearResource(); } - + { - RtmpMuxer rtmpMuxer = Mockito.spy(new RtmpMuxer("rtmps://a.rtmp.youtube.com/y8qd-42g5-1b53-fh15-2v0",vertx)); //RTMP URl without Appname + RtmpMuxer rtmpMuxer = Mockito.spy(new RtmpMuxer("rtmps://a.rtmp.youtube.com/y8qd-42g5-1b53-fh15-2v0", vertx)); //RTMP URl without Appname AVDictionary opt = rtmpMuxer.getOptionDictionary(); - AVDictionaryEntry optEntry = av_dict_get(opt,"rtmp_app",null,0); + AVDictionaryEntry optEntry = av_dict_get(opt, "rtmp_app", null, 0); assertEquals("rtmp_app", optEntry.key().getString()); assertEquals("", optEntry.value().getString()); - + } - + { - RtmpMuxer rtmpMuxer = Mockito.spy(new RtmpMuxer("rtmps://a.rtmp.youtube.com/y8qd-42g5-1b53-fh15-2v0/test",vertx)); //RTMP URl without Appname + RtmpMuxer rtmpMuxer = Mockito.spy(new RtmpMuxer("rtmps://a.rtmp.youtube.com/y8qd-42g5-1b53-fh15-2v0/test", vertx)); //RTMP URl without Appname AVDictionary opt = rtmpMuxer.getOptionDictionary(); - AVDictionaryEntry optEntry = av_dict_get(opt, "rtmp_app",null,0); - assertNull(optEntry); - - //if it's different from zero, it means no file is need to be open. + AVDictionaryEntry optEntry = av_dict_get(opt, "rtmp_app", null, 0); + assertNull(optEntry); + + //if it's different from zero, it means no file is need to be open. //If it's zero, Not "no file" and it means that file is need to be open . - assertEquals(0, rtmpMuxer.getOutputFormatContext().oformat().flags() & AVFMT_NOFILE); - - - rtmpMuxer.clearResource(); - + assertEquals(0, rtmpMuxer.getOutputFormatContext().oformat().flags() & AVFMT_NOFILE); + + + rtmpMuxer.clearResource(); + } - + { - RtmpMuxer rtmpMuxer = Mockito.spy(new RtmpMuxer("rtmps://live-api-s.facebook.com:443/rtmp/y8qd-42g5-1b53-fh15-2v0",vertx)); //RTMP URl without Appname + RtmpMuxer rtmpMuxer = Mockito.spy(new RtmpMuxer("rtmps://live-api-s.facebook.com:443/rtmp/y8qd-42g5-1b53-fh15-2v0", vertx)); //RTMP URl without Appname AVDictionary opt = rtmpMuxer.getOptionDictionary(); - AVDictionaryEntry optEntry = av_dict_get(opt, "rtmp_app",null,0); - assertNull(optEntry); - - //if it's different from zero, it means no file is need to be open. + AVDictionaryEntry optEntry = av_dict_get(opt, "rtmp_app", null, 0); + assertNull(optEntry); + + //if it's different from zero, it means no file is need to be open. //If it's zero, Not "no file" and it means that file is need to be open . - assertEquals(0, rtmpMuxer.getOutputFormatContext().oformat().flags() & AVFMT_NOFILE); - - - rtmpMuxer.clearResource(); + assertEquals(0, rtmpMuxer.getOutputFormatContext().oformat().flags() & AVFMT_NOFILE); + + + rtmpMuxer.clearResource(); } - + } + @Test public void testMp4MuxerDirectStreaming() { @@ -1803,9 +1797,9 @@ public void testApplicationStreamLimit() { long activeBroadcastCountFinal = activeBroadcastCount; Awaitility.await().atMost(5, TimeUnit.SECONDS).pollInterval(1, TimeUnit.SECONDS) - .until(() -> { - return activeBroadcastCountFinal + 2 == appAdaptor.getDataStore().getActiveBroadcastCount(); - }); + .until(() -> { + return activeBroadcastCountFinal + 2 == appAdaptor.getDataStore().getActiveBroadcastCount(); + }); if (activeBroadcastCount == 1) { Mockito.verify(appAdaptor, timeout(1000)).stopStreaming(Mockito.any()); @@ -1840,15 +1834,15 @@ public void testAbsoluteStartTimeMs() { when(stream.getAbsoluteStartTimeMs()).thenReturn(absoluteTimeMS); Awaitility.await().atMost(5, TimeUnit.SECONDS).pollInterval(1, TimeUnit.SECONDS) - .until(() -> - appAdaptor.getDataStore().get(streamId).getAbsoluteStartTimeMs() == absoluteTimeMS); + .until(() -> + appAdaptor.getDataStore().get(streamId).getAbsoluteStartTimeMs() == absoluteTimeMS); spyAdaptor.stopPublish(stream.getPublishedName()); Awaitility.await().atMost(5, TimeUnit.SECONDS).pollInterval(1, TimeUnit.SECONDS) - .until(() -> - appAdaptor.getDataStore().get(streamId) == null); + .until(() -> + appAdaptor.getDataStore().get(streamId) == null); } @@ -1989,7 +1983,7 @@ public void testMp4MuxingHighProfileDelayedVideo() { int finalDuration = 20000; Awaitility.await().atMost(10, TimeUnit.SECONDS).pollInterval(2, TimeUnit.SECONDS).until(() -> - MuxingTest.testFile(muxAdaptor.getMuxerList().get(0).getFile().getAbsolutePath(), finalDuration)); + MuxingTest.testFile(muxAdaptor.getMuxerList().get(0).getFile().getAbsolutePath(), finalDuration)); assertEquals(1640, MuxingTest.videoStartTimeMs); assertEquals(0, MuxingTest.audioStartTimeMs); @@ -2093,8 +2087,7 @@ public void testOrderedBufferedQueue() { @Test - public void testAddBufferQueue() - { + public void testAddBufferQueue() { appScope = (WebScope) applicationContext.getBean("web.scope"); MuxAdaptor muxAdaptor = Mockito.spy(MuxAdaptor.initializeMuxAdaptor(null, false, appScope)); @@ -2105,30 +2098,27 @@ public void testAddBufferQueue() muxAdaptor.setBuffering(true); - for (int i = 0; i <= 11; i++) - { + for (int i = 0; i <= 11; i++) { ITag tag = mock(ITag.class); - when(tag.getTimestamp()).thenReturn(i*100); + when(tag.getTimestamp()).thenReturn(i * 100); IStreamPacket pkt = new StreamPacket(tag); muxAdaptor.addBufferQueue(pkt); if (i < 11) { assertTrue(muxAdaptor.isBuffering()); - } - else if (i == 11) { + } else if (i == 11) { assertFalse(muxAdaptor.isBuffering()); } } - for (int i = 12; i <= 51; i++) - { + for (int i = 12; i <= 51; i++) { ITag tag = mock(ITag.class); - when(tag.getTimestamp()).thenReturn(i*100); + when(tag.getTimestamp()).thenReturn(i * 100); IStreamPacket pkt = new StreamPacket(tag); muxAdaptor.addBufferQueue(pkt); long bufferedDuration = muxAdaptor.getBufferQueue().last().getTimestamp() - muxAdaptor.getBufferQueue().first().getTimestamp(); if (i < 51) { - assertEquals(i*100, bufferedDuration); + assertEquals(i * 100, bufferedDuration); } } @@ -2414,7 +2404,7 @@ public File testMp4Muxing(String name, boolean shortVersion, boolean checkDurati if (checkDuration) { int finalDuration = duration; Awaitility.await().atMost(10, TimeUnit.SECONDS).pollInterval(2, TimeUnit.SECONDS).until(() -> - MuxingTest.testFile(muxAdaptor.getMuxerList().get(0).getFile().getAbsolutePath(), finalDuration)); + MuxingTest.testFile(muxAdaptor.getMuxerList().get(0).getFile().getAbsolutePath(), finalDuration)); } return muxAdaptor.getMuxerList().get(0).getFile(); } catch (Exception e) { @@ -2480,13 +2470,13 @@ public void updateStreamQualityParameters() { assertEquals(lastUpdateTime, broadcast2.getUpdateTime()); - Awaitility.await().pollDelay(MuxAdaptor.STAT_UPDATE_PERIOD_MS+1000, TimeUnit.MILLISECONDS) - .atMost(MuxAdaptor.STAT_UPDATE_PERIOD_MS*2, TimeUnit.MILLISECONDS).until(() -> { - muxAdaptor.updateStreamQualityParameters(streamId, null, 1.0123, 12120); - return true; - }); + Awaitility.await().pollDelay(MuxAdaptor.STAT_UPDATE_PERIOD_MS + 1000, TimeUnit.MILLISECONDS) + .atMost(MuxAdaptor.STAT_UPDATE_PERIOD_MS * 2, TimeUnit.MILLISECONDS).until(() -> { + muxAdaptor.updateStreamQualityParameters(streamId, null, 1.0123, 12120); + return true; + }); - Awaitility.await().atMost(MuxAdaptor.STAT_UPDATE_PERIOD_MS+1000, TimeUnit.MILLISECONDS).until(() -> { + Awaitility.await().atMost(MuxAdaptor.STAT_UPDATE_PERIOD_MS + 1000, TimeUnit.MILLISECONDS).until(() -> { Broadcast broadcastTmp = muxAdaptor.getDataStore().get(streamId); logger.info("speed: {}", broadcastTmp.getSpeed()); return "1.012".equals(Double.toString(broadcastTmp.getSpeed())); @@ -2767,10 +2757,10 @@ public void testMp4MuxingWithDirectParams() { mp4Muxer.writeTrailer(); Awaitility.await().atMost(20, TimeUnit.SECONDS) - .pollInterval(1, TimeUnit.SECONDS) - .until(() -> { - return MuxingTest.testFile("webapps/junit/streams/" + streamName + ".mp4", 10000); - }); + .pollInterval(1, TimeUnit.SECONDS) + .until(() -> { + return MuxingTest.testFile("webapps/junit/streams/" + streamName + ".mp4", 10000); + }); } @@ -2795,7 +2785,9 @@ public void testHLSMuxingWithDirectParams() { String streamName = "stream_name_" + (int) (Math.random() * 10000); //init hlsMuxer.init(appScope, streamName, 0, null, 0); - + + hlsMuxer.setId3Enabled(true); + //add stream int width = 640; int height = 480; @@ -3172,15 +3164,15 @@ public boolean accept(File dir, String name) { //wait to let hls muxer delete ts and m3u8 file Awaitility.await().atMost(hlsListSize * hlsTime * 1000 + 3000, TimeUnit.MILLISECONDS).pollInterval(1, TimeUnit.SECONDS) - .until(() -> { - File[] filesTmp = dir.listFiles(new FilenameFilter() { - @Override - public boolean accept(File dir, String name) { - return name.endsWith(".ts") || name.endsWith(".m3u8"); - } - }); - return 0 == filesTmp.length; - }); + .until(() -> { + File[] filesTmp = dir.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.endsWith(".ts") || name.endsWith(".m3u8"); + } + }); + return 0 == filesTmp.length; + }); assertFalse(hlsFile.exists()); @@ -3362,19 +3354,19 @@ public boolean accept(File dir, String name) { assertTrue(files.length > 0); assertTrue(files.length < (int) Integer.valueOf(hlsMuxer.getHlsListSize()) * (Integer.valueOf(hlsMuxer.getHlsTime()) + 1)); - + //wait to let hls muxer delete ts and m3u8 file Awaitility.await().atMost(hlsListSize * hlsTime * 1000 + 3000, TimeUnit.MILLISECONDS).pollInterval(1, TimeUnit.SECONDS) - .until(() -> { - File[] filesTmp = dir.listFiles(new FilenameFilter() { - @Override - public boolean accept(File dir, String name) { - return name.endsWith(".ts") || name.endsWith(".m3u8"); - } - }); - return 0 == filesTmp.length; - }); + .until(() -> { + File[] filesTmp = dir.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.endsWith(".ts") || name.endsWith(".m3u8"); + } + }); + return 0 == filesTmp.length; + }); } catch (Exception e) { @@ -3488,15 +3480,15 @@ public boolean accept(File dir, String name) { //wait to let hls muxer delete ts and m3u8 file Awaitility.await().atMost(hlsListSize * hlsTime * 1000 + 3000, TimeUnit.MILLISECONDS).pollInterval(1, TimeUnit.SECONDS) - .until(() -> { - File[] filesTmp = dir.listFiles(new FilenameFilter() { - @Override - public boolean accept(File dir, String name) { - return name.endsWith(".ts") || name.endsWith(".m3u8"); - } - }); - return 0 == filesTmp.length; - }); + .until(() -> { + File[] filesTmp = dir.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.endsWith(".ts") || name.endsWith(".m3u8"); + } + }); + return 0 == filesTmp.length; + }); assertFalse(hlsFile.exists()); @@ -3900,10 +3892,10 @@ public void testAnalyzeTime() { muxAdaptor.start(); Awaitility.await().atLeast(getAppSettings().getMaxAnalyzeDurationMS() * 2, TimeUnit.MILLISECONDS) - .atMost(getAppSettings().getMaxAnalyzeDurationMS() * 2 + 1000, TimeUnit.MILLISECONDS) - .until(() -> { - return muxAdaptor.isStopRequestExist(); - }); + .atMost(getAppSettings().getMaxAnalyzeDurationMS() * 2 + 1000, TimeUnit.MILLISECONDS) + .until(() -> { + return muxAdaptor.isStopRequestExist(); + }); Mockito.verify(muxAdaptor, Mockito.timeout(500)).closeRtmpConnection(); @@ -3947,14 +3939,13 @@ public void testMuxAdaptorPacketListener() { AVPacket pkt = new AVPacket(); pkt.flags(AV_PKT_FLAG_KEY); - + //inject time base to not encounter nullpointer for (Muxer muxer : muxAdaptor.getMuxerList()) { muxer.getInputTimeBaseMap().put(pkt.stream_index(), MuxAdaptor.TIME_BASE_FOR_MS); } - - - + + muxAdaptor.writePacket(stream, pkt); verify(listener, Mockito.times(1)).onVideoPacket(streamId, pkt); @@ -4262,7 +4253,7 @@ public void testID3Timing() { AVPacket pkt = argument.getValue(); BytePointer ptrData = pkt.data(); - byte [] id3Data = new byte[pkt.size()]; + byte[] id3Data = new byte[pkt.size()]; ptrData.get(id3Data); assertEquals("ID3", new String(id3Data, 0, 3)); @@ -4270,6 +4261,11 @@ public void testID3Timing() { assertEquals(lastPts, pkt.pts()); assertEquals(lastPts, pkt.dts()); + + + HLSMuxer.logError(-1, "test error message", "stream1"); + HLSMuxer.logError(0, "test error message", "stream1"); + HLSMuxer.logError(1, "test error message", "stream1"); } @@ -4291,14 +4287,48 @@ public void testMuxAdaptorPipeReader() { muxAdaptor.debugSetStopRequestExist(true); muxAdaptor.execute(); fail("It should throw exception"); - } - catch (Exception e) { + } catch (Exception e) { } assertFalse(muxAdaptor.getIsPipeReaderJobRunning().get()); + } + + @Test + public void testAddH264MetadataBSF() { + if (appScope == null) { + appScope = (WebScope) applicationContext.getBean("web.scope"); + logger.debug("Application / web scope: {}", appScope); + assertTrue(appScope.getDepth() == 1); + } + + HLSMuxer hlsMuxer = new HLSMuxer(vertx, Mockito.mock(StorageClient.class), "streams", 0, "http://example.com", false); + hlsMuxer.setHlsParameters(null, null, null, null, null); + hlsMuxer.init(appScope, "test", 0, null, 0); + + hlsMuxer.setSeiEnabled(true); + int width = 640; + int height = 480; + + boolean addStreamResult = hlsMuxer.addVideoStream(width, height, null, AV_CODEC_ID_H264, 0, false, null); + assertTrue(addStreamResult); + + assertEquals("h264_metadata", hlsMuxer.getBitStreamFilter()); + + } + + @Test + public void testSetSEIData() { + appScope = (WebScope) applicationContext.getBean("web.scope"); + ClientBroadcastStream clientBroadcastStream = new ClientBroadcastStream(); + MuxAdaptor muxAdaptorReal = MuxAdaptor.initializeMuxAdaptor(clientBroadcastStream, false, appScope); + HLSMuxer hlsMuxer = mock(HLSMuxer.class); + muxAdaptorReal.getMuxerList().add(hlsMuxer); + String data = "some data to put frame"; + muxAdaptorReal.addSEIData(data); + verify(hlsMuxer, times(1)).setSeiData(data); } } diff --git a/src/test/java/io/antmedia/test/rest/BroadcastRestServiceV2UnitTest.java b/src/test/java/io/antmedia/test/rest/BroadcastRestServiceV2UnitTest.java index 4af630820..ed87fd9fd 100644 --- a/src/test/java/io/antmedia/test/rest/BroadcastRestServiceV2UnitTest.java +++ b/src/test/java/io/antmedia/test/rest/BroadcastRestServiceV2UnitTest.java @@ -3507,6 +3507,7 @@ public void testBlockSubscriber(){ } + @Test public void testAddID3Tag() { DataStore store = new InMemoryDataStore("testdb"); restServiceReal.setDataStore(store); @@ -3525,6 +3526,29 @@ public void testAddID3Tag() { assertFalse(restServiceSpy.addID3Data("nonExistingStreamId", id3Data).isSuccess()); } + + @Test + public void testAddSEIData() { + DataStore store = new InMemoryDataStore("testdb"); + restServiceReal.setDataStore(store); + BroadcastRestService restServiceSpy = Mockito.spy(restServiceReal); + restServiceSpy.setAppSettings(new AppSettings()); + restServiceSpy.getAppSettings().setSeiEnabled(false); + + String seiData = "some data"; + doReturn(null).when(restServiceSpy).getMuxAdaptor("nonExistingStreamId"); + + MuxAdaptor muxadaptor = mock(MuxAdaptor.class); + doReturn(muxadaptor).when(restServiceSpy).getMuxAdaptor("existingStreamId"); + + when(muxadaptor.addSEIData(seiData)).thenReturn(true); + + assertFalse(restServiceSpy.addSEIData("existingStreamId", seiData).isSuccess()); + restServiceSpy.getAppSettings().setSeiEnabled(true); + assertTrue(restServiceSpy.addSEIData("existingStreamId", seiData).isSuccess()); + + assertFalse(restServiceSpy.addSEIData("nonExistingStreamId", seiData).isSuccess()); + } @Test public void testGetTOTP() {