diff --git a/io/src/main/java/org/red5/io/matroska/ParserUtils.java b/io/src/main/java/org/red5/io/matroska/ParserUtils.java index 40f5a80d7971cf25b77f940022285747c8f06a7c..89dfbc6ace43b5636a616e93153027f29e35ef79 100644 --- a/io/src/main/java/org/red5/io/matroska/ParserUtils.java +++ b/io/src/main/java/org/red5/io/matroska/ParserUtils.java @@ -7,6 +7,12 @@ */ package org.red5.io.matroska; +import static org.red5.io.matroska.VINT.MASK_BYTE_1; +import static org.red5.io.matroska.VINT.MASK_BYTE_2; +import static org.red5.io.matroska.VINT.MASK_BYTE_3; +import static org.red5.io.matroska.VINT.MASK_BYTE_4; +import static org.red5.io.matroska.VINT.MASK_BYTE_8; + import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -17,14 +23,12 @@ import java.util.ArrayList; import org.red5.io.matroska.dtd.CompoundTag; import org.red5.io.matroska.dtd.Tag; import org.red5.io.matroska.dtd.TagFactory; - -import static org.red5.io.matroska.VINT.MASK_BYTE_1; -import static org.red5.io.matroska.VINT.MASK_BYTE_2; -import static org.red5.io.matroska.VINT.MASK_BYTE_3; -import static org.red5.io.matroska.VINT.MASK_BYTE_4; -import static org.red5.io.matroska.VINT.MASK_BYTE_8; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class ParserUtils { + + private static Logger log = LoggerFactory.getLogger(ParserUtils.class); public static final int BIT_IN_BYTE = 8; @@ -40,15 +44,15 @@ public class ParserUtils { * - in case of IO error */ public static long parseInteger(InputStream inputStream, final int size) throws IOException { + log.debug("parseInteger inputStream: {} size: {}", inputStream, size); byte[] buffer = new byte[size]; int numberOfReadsBytes = inputStream.read(buffer, 0, size); - assert numberOfReadsBytes == size; - + log.debug("numberOfReadsBytes: {}", numberOfReadsBytes); + //assert numberOfReadsBytes == size; long value = buffer[0] & (long) 0xff; for (int i = 1; i < size; ++i) { value = (value << BIT_IN_BYTE) | ((long) buffer[i] & (long) 0xff); } - return value; } diff --git a/io/src/main/java/org/red5/io/matroska/dtd/TagFactory.java b/io/src/main/java/org/red5/io/matroska/dtd/TagFactory.java index c6709588838d8e3818741879c86a2d1483f82bb0..32d48d2a7a83dd60d438057d0efc6ccfaf73b652 100644 --- a/io/src/main/java/org/red5/io/matroska/dtd/TagFactory.java +++ b/io/src/main/java/org/red5/io/matroska/dtd/TagFactory.java @@ -18,9 +18,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * factory for creating matroska tags, it use property file - matroska_type_definition_config.properties with structure: long id = "name provided specification","java class representing tag data" + * https://www.matroska.org/technical/tagging.html + * + * factory for creating matroska tags, it use property file - matroska_type_definition_config.properties with structure: + * long id = "name provided specification","java class representing tag data" */ public class TagFactory { + private static Logger log = LoggerFactory.getLogger(TagFactory.class); private static final Map<Long, NameTag> tagsById = new Hashtable<>(); @@ -47,17 +51,19 @@ public class TagFactory { } public static Tag createTag(VINT id, VINT size, InputStream inputStream) throws ConverterException { + Tag tag = null; NameTag nt = tagsById.get(id.getBinary()); - if (null == nt) { - throw new ConverterException("not supported matroska tag: " + id.getBinary()); - } - try { - return (Tag) nt.clazz.getConstructor(String.class, VINT.class, VINT.class, InputStream.class).newInstance(nt.name, id, size, inputStream); - } catch (Exception e) { - log.error("Unexpected exception while creating tag", e); + if (nt != null) { + try { + tag = (Tag) nt.clazz.getConstructor(String.class, VINT.class, VINT.class, InputStream.class).newInstance(nt.name, id, size, inputStream); + } catch (Exception e) { + log.error("Unexpected exception while creating tag", e); + } + } else { + log.info("Unsupported matroska tag: {} {}", id, id.getBinary()); + //throw new ConverterException("not supported matroska tag: " + id.getBinary()); } - - return null; + return tag; } public static Tag createTag(String tagName) throws ConverterException { diff --git a/io/src/main/java/org/red5/io/matroska/parser/TagCrawler.java b/io/src/main/java/org/red5/io/matroska/parser/TagCrawler.java index aa1057ff3836c01655ec44a89e7af36a9a7ac554..59eb10223067088b7601b08d00c7e121dece6087 100644 --- a/io/src/main/java/org/red5/io/matroska/parser/TagCrawler.java +++ b/io/src/main/java/org/red5/io/matroska/parser/TagCrawler.java @@ -73,7 +73,7 @@ public class TagCrawler { * @return - this for chaining */ public TagHandler getHandler(Tag tag) { - if (handlers.containsKey(tag.getName())) { + if (tag != null && handlers.containsKey(tag.getName())) { return handlers.get(tag.getName()); } return null; diff --git a/io/src/main/java/org/red5/io/mp4/impl/MP4.java b/io/src/main/java/org/red5/io/mp4/impl/MP4.java index 368812481751559e046b7074f1ac547ec1ecb825..ba4d8d9464b41a3792cd4dd55c5454c3dadcd399 100644 --- a/io/src/main/java/org/red5/io/mp4/impl/MP4.java +++ b/io/src/main/java/org/red5/io/mp4/impl/MP4.java @@ -12,7 +12,6 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.util.Map; -import org.apache.mina.core.buffer.IoBuffer; import org.red5.io.ITagReader; import org.red5.io.ITagWriter; import org.red5.io.flv.meta.IMetaData; @@ -118,14 +117,10 @@ public class MP4 implements IMP4 { @Override public ITagReader getReader() throws IOException { MP4Reader reader = null; - IoBuffer fileData = null; String fileName = file.getName(); if (file.exists()) { log.debug("File name: {} size: {}", fileName, file.length()); reader = new MP4Reader(file); - // get a ref to the mapped byte buffer - fileData = reader.getFileData(); - log.trace("File data size: {}", fileData); } else { log.info("Creating new file: {}", file); file.createNewFile(); @@ -146,6 +141,8 @@ public class MP4 implements IMP4 { */ @Override public ITagWriter getWriter() throws IOException { + log.info("getWriter: {}", file); + //return new MP4Writer(file.toPath(), false); return null; } diff --git a/io/src/main/java/org/red5/io/mp4/impl/MP4Reader.java b/io/src/main/java/org/red5/io/mp4/impl/MP4Reader.java index 1a4bf5208bc95b1cb40b240e79300166089fa9b3..0efcff18937ac5e43c84b619e333956809db8b9c 100644 --- a/io/src/main/java/org/red5/io/mp4/impl/MP4Reader.java +++ b/io/src/main/java/org/red5/io/mp4/impl/MP4Reader.java @@ -32,7 +32,6 @@ import org.jcodec.common.io.SeekableByteChannel; import org.jcodec.containers.mp4.MP4TrackType; import org.jcodec.containers.mp4.MP4Util; import org.jcodec.containers.mp4.MP4Util.Movie; -import org.jcodec.containers.mp4.boxes.AVC1Box; import org.jcodec.containers.mp4.boxes.AudioSampleEntry; import org.jcodec.containers.mp4.boxes.Box; import org.jcodec.containers.mp4.boxes.ChunkOffsets64Box; @@ -65,7 +64,6 @@ import org.red5.io.IoConstants; import org.red5.io.amf.Output; import org.red5.io.flv.IKeyFrameDataAnalyzer; import org.red5.io.flv.impl.Tag; -import org.red5.io.isobmff.atom.HVC1Box; import org.red5.io.isobmff.atom.ShortEsdsBox; import org.red5.io.mp4.MP4Frame; import org.red5.io.utils.HexDump; @@ -763,19 +761,6 @@ public class MP4Reader implements IoConstants, ITagReader, IKeyFrameDataAnalyzer return hasVideo; } - /** - * Returns the file buffer. - * - * @return File contents as byte buffer - */ - public IoBuffer getFileData() { - // TODO as of now, return null will disable cache - // we need to redesign the cache architecture so that - // the cache is layered underneath FLVReader not above it, - // thus both tag cache and file cache are feasible. - return null; - } - /** * {@inheritDoc} */ @@ -1040,20 +1025,20 @@ public class MP4Reader implements IoConstants, ITagReader, IKeyFrameDataAnalyzer log.debug("Playback #{} {}", currentFrame, frame); int sampleSize = frame.getSize(); int time = (int) Math.round(frame.getTime() * 1000.0); - //log.debug("Read tag - dst: {} base: {} time: {}", new Object[]{frameTs, baseTs, time}); - //long samplePos = frame.getOffset(); - //log.debug("Read tag - samplePos {}", samplePos); - //determine frame type and packet body padding + log.debug("Read tag - time: {}", time); + long samplePos = frame.getOffset(); + log.debug("Read tag - samplePos {}", samplePos); + // determine frame type and packet body padding byte type = frame.getType(); - //assume video type + // assume video type int pad = 5; if (type == TYPE_AUDIO) { pad = 2; } - //create a byte buffer of the size of the sample + // create a byte buffer of the size of the sample ByteBuffer data = ByteBuffer.allocate(sampleSize + pad); try { - //prefix is different for keyframes + // prefix is different for keyframes if (type == TYPE_VIDEO) { if (frame.isKeyFrame()) { //log.debug("Writing keyframe prefix"); @@ -1085,7 +1070,7 @@ public class MP4Reader implements IoConstants, ITagReader, IKeyFrameDataAnalyzer audioCount++; } // do we need to add the mdat offset to the sample position? - //dataSource.position(samplePos); + dataSource.setPosition(samplePos); // read from the channel dataSource.read(data); } catch (IOException e) { @@ -1095,7 +1080,7 @@ public class MP4Reader implements IoConstants, ITagReader, IKeyFrameDataAnalyzer IoBuffer payload = IoBuffer.wrap(data.array()); // create the tag tag = new Tag(type, time, payload.limit(), payload, prevFrameSize); - //log.debug("Read tag - type: {} body size: {}", (type == TYPE_AUDIO ? "Audio" : "Video"), tag.getBodySize()); + log.debug("Read tag - type: {} body size: {}", (type == TYPE_AUDIO ? "Audio" : "Video"), tag.getBodySize()); // increment the frame number currentFrame++; // set the frame / tag size @@ -1229,7 +1214,7 @@ public class MP4Reader implements IoConstants, ITagReader, IKeyFrameDataAnalyzer int size = 0; // if we have no samples, skip size check as its probably not aac if (audioSamples.length > 0) { - //update sample size + // update sample size size = (int) audioSamples[sample - 1]; // skip empty AAC data which is 6 bytes long log.trace("Audio sample - size: {} pos: {}", size, pos); @@ -1281,7 +1266,7 @@ public class MP4Reader implements IoConstants, ITagReader, IKeyFrameDataAnalyzer } } } - //sort the frames + // sort the frames Collections.sort(frames); log.debug("Frames count: {}", frames.size()); //log.debug("Frames: {}", frames); diff --git a/io/src/main/java/org/red5/io/webm/WebmReader.java b/io/src/main/java/org/red5/io/webm/WebmReader.java index e394482120a15160a30460f2f20145117e319202..51b342b833b0ee4b97e51507e2407c3fe4b0c052 100644 --- a/io/src/main/java/org/red5/io/webm/WebmReader.java +++ b/io/src/main/java/org/red5/io/webm/WebmReader.java @@ -28,7 +28,6 @@ import org.slf4j.LoggerFactory; */ public class WebmReader implements Closeable { - @SuppressWarnings("unused") private static Logger log = LoggerFactory.getLogger(WebmReader.class); private FileInputStream fis = null; @@ -54,9 +53,13 @@ public class WebmReader implements Closeable { final TagHandler valueTagHandler = new TagHandler() { @Override public void handle(Tag tag, InputStream input) throws IOException, ConverterException { - //log.debug("Tag found: " + tag.getName()); - tag.parse(); - WebmReader.this.processor.consume(tag); + if (tag != null) { + log.debug("Tag found: {}", tag); + tag.parse(); + WebmReader.this.processor.consume(tag); + } else { + log.trace("Tag is null"); + } } }; diff --git a/io/src/test/java/org/red5/io/mp4/impl/MP4ReaderTest.java b/io/src/test/java/org/red5/io/mp4/impl/MP4ReaderTest.java index d4c0f6a5179c9157127c3fecbaa1e9639dd868ca..31eaa420b836897776270e1a7b336344944cc3f9 100644 --- a/io/src/test/java/org/red5/io/mp4/impl/MP4ReaderTest.java +++ b/io/src/test/java/org/red5/io/mp4/impl/MP4ReaderTest.java @@ -27,19 +27,11 @@ public class MP4ReaderTest extends TestCase { public void testCtor() throws Exception { // use for the internal unit tests //File file = new File("target/test-classes/fixtures/bbb.mp4"); // non-avc1 h264 video / aac audio + //File file = new File("target/test-classes/fixtures/sample.mp4"); // non-avc1 h264 video / aac audio File file = new File("target/test-classes/fixtures/mov_h264.mp4"); // avc1 h264 video / aac audio //File file = new File("target/test-classes/fixtures/mov_h265.mp4"); // hev1 h265 video / aac audio - //File file = new File("/media/mondain/terrorbyte/Videos/bbb_sunflower_2160p_60fps_normal.mp4"); // h264 video / ac-3 audio - //File file = new File("target/test-classes/fixtures/sample.mp4"); // non-avc1 h264 video / aac audio //File file = new File("target/test-classes/fixtures/MOV1.MOV"); // hcv1 h265 video / aac audio - // test clips for issues/bugs - // https://code.google.com/p/red5/issues/detail?id=141 - //File file = new File("target/test-classes/fixtures/test_480_aac.f4v"); - //File file = new File("M:/backup/media/test_clips/ratatouille.mp4"); - //File file = new File("M:/backup/media/test_clips/0608071221.3g2"); - //File file = new File("M:/backup/media/test_clips/test1.3gp"); - //File file = new File("M:/backup/media/test_clips/ANewHope.mov"); - //File file = new File("M:/Movies/Hitman.m4v"); // has pasp atom + //File file = new File("/media/mondain/terrorbyte/Videos/bbb_sunflower_2160p_60fps_normal.mp4"); // h264 video / ac-3 and mp3 audio MP4Reader reader = new MP4Reader(file); diff --git a/io/src/test/java/org/red5/io/webm/WebmTest.java b/io/src/test/java/org/red5/io/webm/WebmTest.java index 0d1aec34f2f6fb83c5e3bce6bc1e21ea0475d15c..cbf77042c0dff0e5267569582c318a88f800b0a0 100644 --- a/io/src/test/java/org/red5/io/webm/WebmTest.java +++ b/io/src/test/java/org/red5/io/webm/WebmTest.java @@ -26,25 +26,30 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class WebmTest { + private static Logger log = LoggerFactory.getLogger(WebmTest.class); private static final String WEBM_FILE_PROPERTY = "webm.file.path"; + private static String webmTestFilePath; + /** - * This check will cancel further tests in case there is no system property "webm.file.path" specified with path to the test web file. + * This check will cancel further tests in case there is no system property + * "webm.file.path" specified with path to the test web file. */ @Before public void before() { - assumeTrue(System.getProperties().containsKey(WEBM_FILE_PROPERTY)); + webmTestFilePath = System.getProperty(WEBM_FILE_PROPERTY, "target/webm_sample.webm"); } /** - * This test checks if test webm file can be read till the end with no exceptions + * This test checks if test webm file can be read till the end with no + * exceptions * * @throws IOException - * - in case of any IO exception + * - in case of any IO exception * @throws ConverterException - * - in case of any conversion exception + * - in case of any conversion exception */ @Test public void crawl() throws IOException, ConverterException { @@ -60,7 +65,7 @@ public class WebmTest { return logHandle; } }; - File webmF = new File(System.getProperty(WEBM_FILE_PROPERTY)); + File webmF = new File(webmTestFilePath); if (webmF.exists() && webmF.isFile()) { try (FileInputStream fis = new FileInputStream(webmF)) { crawler.process(fis); @@ -70,16 +75,17 @@ public class WebmTest { } /** - * This test checks if test webm file can be read and then be written with no exceptions + * This test checks if test webm file can be read and then be written with no + * exceptions * * @throws IOException - * - in case of any IO exception + * - in case of any IO exception * @throws ConverterException - * - in case of any conversion exception + * - in case of any conversion exception */ @Test public void testReaderWriter() throws IOException, ConverterException { - File webmF = new File(System.getProperty(WEBM_FILE_PROPERTY)); + File webmF = new File(webmTestFilePath); assertTrue("Invalid webM file is specified", webmF.exists() && webmF.isFile()); File out = File.createTempFile("webmwriter", ".webm"); try (WebmWriter w = new WebmWriter(out, false); WebmReader r = new WebmReader(webmF, w);) { @@ -88,4 +94,24 @@ public class WebmTest { log.debug("Temporary file was created: " + out.getAbsolutePath()); assertEquals("", webmF.length(), out.length()); } + + @Test + public void testReader() throws IOException, ConverterException { + // https://www.matroska.org/technical/tagging.html + //File webmF = new File("/media/mondain/terrorbyte/Videos/bbb-fullhd.webm"); + File webmF = new File("/media/mondain/terrorbyte/Videos/BladeRunner2049.webm"); + assertTrue("Invalid webM file is specified", webmF.exists() && webmF.isFile()); + File out = File.createTempFile("webmwriter", ".webm"); + try (WebmReader r = new WebmReader(webmF, new TagConsumer() { + @Override + public void consume(Tag tag) { + log.debug("Tag found: " + tag.getName()); + } + });) { + r.process(); + } + log.debug("Temporary file was created: " + out.getAbsolutePath()); + assertEquals("", webmF.length(), out.length()); + } + } diff --git a/server/src/main/server/conf/red5-common.xml b/server/src/main/server/conf/red5-common.xml index 91fff799f5e9599b3fbd3691b712f762edc9d799..7a2f59f344439880778f9816e688bdd7d21ee758 100644 --- a/server/src/main/server/conf/red5-common.xml +++ b/server/src/main/server/conf/red5-common.xml @@ -47,9 +47,12 @@ <bean id="flvFileService" class="org.red5.server.service.flv.impl.FLVService"> <property name="generateMetadata" value="true"/> </bean> - <bean id="mp3FileService" class="org.red5.server.service.mp3.impl.MP3Service"/> <bean id="mp4FileService" class="org.red5.server.service.mp4.impl.MP4Service"/> + <!-- available separately as of 1.3.32 --> + <!-- + <bean id="mp3FileService" class="org.red5.server.service.mp3.impl.MP3Service"/> <bean id="m4aFileService" class="org.red5.server.service.m4a.impl.M4AService"/> + --> </list> </property> </bean> @@ -156,11 +159,13 @@ <bean id="mp4reader.impl" class="org.red5.io.mp4.impl.MP4Reader"/> <!-- Use injection to set the keyframe cache for MP3 files --> + <!-- <bean id="mp3reader.impl" class="org.red5.io.mp3.impl.MP3Reader"> <property name="frameCache"> <ref bean="keyframe.cache"/> </property> - </bean> + </bean> + --> <!-- Use injection to set the buffer type for reading FLV files --> <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">