[Red5devs] [red5 commit] r3061 - in java/server/branches/paulg_mp4: . conf src/org/red5/io/mp4 src/org/red5/io/mp4/impl sr...
codesite-noreply at google.com
codesite-noreply at google.com
Mon Sep 15 23:43:35 PDT 2008
Author: mondain
Date: Mon Sep 15 23:42:07 2008
New Revision: 3061
Added:
java/server/branches/paulg_mp4/src/org/red5/io/mp4/MP4Frame.java
java/server/branches/paulg_mp4/test/org/red5/server/io/mp4/MP4FrameTest.java
Modified:
java/server/branches/paulg_mp4/conf/logback.xml
java/server/branches/paulg_mp4/conf/red5-common.xml
java/server/branches/paulg_mp4/conf/red5.properties
java/server/branches/paulg_mp4/conf/web.xml
java/server/branches/paulg_mp4/java6_build_dist.bat
java/server/branches/paulg_mp4/src/org/red5/io/mp4/impl/MP4Reader.java
java/server/branches/paulg_mp4/src/org/red5/server/api/Red5.java
java/server/branches/paulg_mp4/src/org/red5/server/net/rtmp/RTMPHandler.java
java/server/branches/paulg_mp4/src/org/red5/server/net/rtmp/event/FLVData.java
java/server/branches/paulg_mp4/src/org/red5/server/net/rtmp/event/VideoData.java
java/server/branches/paulg_mp4/src/org/red5/server/stream/PlayEngine.java
Log:
Refactoring mp4 handling to more closely match Izumi style of media handling
Modified: java/server/branches/paulg_mp4/conf/logback.xml
==============================================================================
--- java/server/branches/paulg_mp4/conf/logback.xml (original)
+++ java/server/branches/paulg_mp4/conf/logback.xml Mon Sep 15 23:42:07 2008
@@ -1,232 +1,229 @@
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
- <consolePlugin/>
- <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+ <appender name="CONSOLE"
+ class="ch.qos.logback.core.ConsoleAppender">
+ <filter class="ch.qos.logback.classic.filter.LevelFilter">
+ <level>WARN</level>
+ <onMatch>ACCEPT</onMatch>
+ <onMismatch>DENY</onMismatch>
+ </filter>
<layout class="ch.qos.logback.classic.PatternLayout">
- <Pattern>[%p] [%thread] %logger - %msg%n</Pattern>
+ <Pattern>[%p] %d{ISO8601} %t:( %c.%M ) %m%n</Pattern>
</layout>
</appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
- <File>../log/red5.log</File>
+ <File>log/red5.log</File>
<Append>false</Append>
- <Encoding>UTF-8</Encoding>
<BufferedIO>false</BufferedIO>
<ImmediateFlush>true</ImmediateFlush>
<layout class="ch.qos.logback.classic.PatternLayout">
- <Pattern>%d{ISO8601} [%thread] %-5level %logger{35} - %msg%n</Pattern>
+ <Pattern>[%p] %d{ISO8601} [%thread] %logger{21} - %msg%n</Pattern>
</layout>
</appender>
<appender name="ERRORFILE" class="ch.qos.logback.core.FileAppender">
- <File>../log/error.log</File>
+ <File>log/error.log</File>
<Append>false</Append>
- <Encoding>UTF-8</Encoding>
<BufferedIO>false</BufferedIO>
<ImmediateFlush>true</ImmediateFlush>
- <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
- <level>WARN</level>
- </filter>
+ <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+ <level>WARN</level>
+ </filter>
<layout class="ch.qos.logback.classic.PatternLayout">
- <Pattern>%d{ISO8601} [%thread] %-5level %logger{35} - %msg%n</Pattern>
+ <Pattern>[%p] %d{ISO8601} [%thread] %logger{35} - %msg%n</Pattern>
</layout>
</appender>
<root>
- <level value="DEBUG"/>
- <appender-ref ref="CONSOLE"/>
- <appender-ref ref="FILE"/>
- <appender-ref ref="ERRORFILE"/>
+ <level value="DEBUG" />
+ <appender-ref ref="ERRORFILE" />
+ <appender-ref ref="FILE" />
+ <appender-ref ref="CONSOLE" />
</root>
+ <logger name="com.infrared5">
+ <level value="DEBUG" />
+ </logger>
<!-- Red5 -->
-
<logger name="org.red5.io">
- <level value="DEBUG"/>
+ <level value="INFO" />
</logger>
-
- <logger name="org.red5.io.object">
- <level value="WARN"/>
+ <logger name="org.red5.io.mp4">
+ <level value="DEBUG" />
</logger>
+ <logger name="org.red5.io.mp4.MP4Atom">
+ <level value="WARN" />
+ </logger>
+ <logger name="org.red5.io.mp4.MP4Descriptor">
+ <level value="WARN" />
+ </logger>
+ <logger name="org.red5.server.net.rtmp.status.StatusObject">
+ <level value="DEBUG" />
+ </logger>
<logger name="org.red5.server">
- <level value="WARN"/>
+ <level value="INFO" />
</logger>
<logger name="org.red5.server.Client">
- <level value="INFO"/>
+ <level value="DEBUG" />
+ </logger>
+ <logger name="org.red5.server.CoreHandler">
+ <level value="DEBUG" />
+ </logger>
+ <logger name="org.red5.server.Scope">
+ <level value="INFO" />
</logger>
<logger name="org.red5.server.jetty">
- <level value="INFO"/>
+ <level value="DEBUG" />
</logger>
<logger name="org.red5.server.Standalone">
- <level value="INFO"/>
+ <level value="INFO" />
</logger>
<logger name="org.red5.server.tomcat">
- <level value="INFO"/>
+ <level value="INFO" />
</logger>
<logger name="org.red5.server.api.stream.support">
- <level value="INFO"/>
+ <level value="INFO" />
</logger>
<logger name="org.red5.server.cache">
- <level value="WARN"/>
+ <level value="WARN" />
</logger>
- <logger name="org.red5.server.jetty.Red5WebPropertiesConfiguration">
- <level value="WARN"/>
+ <logger
+ name="org.red5.server.jetty.Red5WebPropertiesConfiguration">
+ <level value="WARN" />
</logger>
<logger name="org.red5.server.jmx">
- <level value="INFO"/>
+ <level value="OFF" />
</logger>
<logger name="org.red5.server.messaging.InMemoryPushPushPipe">
- <level value="INFO"/>
+ <level value="INFO" />
</logger>
<logger name="org.red5.server.net">
- <level value="INFO"/>
+ <level value="INFO" />
</logger>
<logger name="org.red5.server.net.servlet.RTMPTServlet">
- <level value="WARN"/>
- </logger>
+ <level value="WARN" />
+ </logger>
<logger name="org.red5.server.net.servlet">
- <level value="WARN"/>
+ <level value="WARN" />
</logger>
<logger name="org.red5.server.net.proxy">
- <level value="INFO"/>
+ <level value="INFO" />
</logger>
<logger name="org.red5.server.net.remoting">
- <level value="WARN"/>
+ <level value="WARN" />
</logger>
<logger name="org.red5.server.net.rtmp">
- <level value="WARN"/>
- </logger>
- <logger name="org.red5.server.net.rtmp.RTMPHandler">
- <level value="OFF"/>
- </logger>
- <logger name="org.red5.server.net.rtmp.BaseRTMPHandler">
- <level value="OFF"/>
- </logger>
+ <level value="WARN" />
+ </logger>
+ <logger name="org.red5.server.net.rtmp.RTMPMinaTransport">
+ <level value="DEBUG" />
+ </logger>
<logger name="org.red5.server.net.rtmp.codec">
- <level value="INFO"/>
+ <level value="INFO" />
</logger>
- <logger name="org.red5.server.net.rtmp.RTMPMinaIoHandler">
- <level value="OFF"/>
+ <logger name="org.red5.server.net.rtmp.status">
+ <level value="INFO" />
</logger>
- <logger name="org.red5.server.net.rtmp.RTMPMinaTransport">
- <level value="INFO"/>
+ <logger name="org.red5.server.net.mrtmp">
+ <level value="DEBUG" />
</logger>
- <logger name="org.red5.server.net.rtmp.status">
- <level value="INFO"/>
+ <logger name="org.red5.server.net.mrtmp.codec">
+ <level value="DEBUG" />
</logger>
<logger name="org.red5.server.net.rtmpt">
- <level value="WARN"/>
+ <level value="WARN" />
</logger>
<logger name="org.red5.server.persistence">
- <level value="WARN"/>
+ <level value="WARN" />
</logger>
<logger name="org.red5.server.pooling.ThreadObjectFactory">
- <level value="WARN"/>
+ <level value="WARN" />
</logger>
<logger name="org.red5.server.script">
- <level value="WARN"/>
- </logger>
+ <level value="WARN" />
+ </logger>
<logger name="org.red5.server.service">
- <level value="INFO"/>
+ <level value="INFO" />
</logger>
<logger name="org.red5.server.so">
- <level value="WARN"/>
+ <level value="WARN" />
</logger>
<logger name="org.red5.server.stream">
- <level value="INFO"/>
+ <level value="DEBUG" />
</logger>
-
<logger name="org.red5.server.stream.ProviderService">
- <level value="DEBUG"/>
- </logger>
- <logger name="org.red5.server.stream.provider">
- <level value="DEBUG"/>
+ <level value="DEBUG" />
</logger>
-
<logger name="org.red5.server.stream.consumer">
- <level value="WARN"/>
+ <level value="WARN" />
</logger>
- <logger name="org.red5.server.net.mrtmp">
- <level value="WARN"/>
- </logger>
- <logger name="org.red5.server.net.mrtmp.codec">
- <level value="WARN"/>
- </logger>
- <!-- Mina -->
+ <!-- Red5 demos -->
+ <logger name="org.red5.server.webapp.oflaDemo">
+ <level value="DEBUG" />
+ </logger>
+ <!-- Mina -->
<logger name="org.apache.mina">
- <level value="WARN"/>
- </logger>
+ <level value="WARN" />
+ </logger>
<logger name="org.apache.mina.filter">
- <level value="WARN"/>
+ <level value="WARN" />
</logger>
<logger name="org.apache.mina.filter.thread.ThreadPoolFilter">
- <level value="WARN"/>
+ <level value="WARN" />
</logger>
<!-- Apache commons -->
- <logger name="org.apache.commons">
- <level value="WARN"/>
- </logger>
- <logger name="org.apache.commons.modeler">
- <level value="WARN"/>
- </logger>
- <logger name="org.apache.commons.beanutils">
- <level value="WARN"/>
- </logger>
- <logger name="org.apache.commons.digester">
- <level value="WARN"/>
+ <logger name="org.apache">
+ <level value="INFO" />
</logger>
- <logger name="httpclient">
- <level value="WARN"/>
+ <logger name="org.apache.tomcat.util.net">
+ <level value="INFO" />
</logger>
- <!-- Apache catalina / tomcat -->
- <logger name="org.apache.catalina">
- <level value="INFO"/>
+ <logger name="org.apache.catalina.deploy.SecurityCollection">
+ <level value="INFO" />
</logger>
- <logger name="org.apache.catalina.authenticator">
- <level value="INFO"/>
+ <logger name="org.apache.catalina.loader.WebappClassLoader">
+ <level value="INFO" />
</logger>
<logger name="org.apache.catalina.realm">
- <level value="WARN"/>
- </logger>
- <logger name="org.apache.catalina.session">
- <level value="WARN"/>
- </logger>
- <logger name="org.apache.jasper">
- <level value="INFO"/>
+ <level value="WARN" />
</logger>
- <logger name="org.apache.tomcat">
- <level value="INFO"/>
+ <logger name="org.apache.tomcat.util.modeler">
+ <level value="WARN" />
</logger>
- <logger name="org.apache.tomcat.util.net">
- <level value="WARN"/>
+ <logger name="org.apache.commons.digester">
+ <level value="WARN" />
</logger>
<!-- Jetty -->
<logger name="org.mortbay">
- <level value="WARN"/>
+ <level value="INFO" />
</logger>
<logger name="org.mortbay.log">
- <level value="INFO"/>
+ <level value="WARN" />
</logger>
<!-- Spring -->
<logger name="org.springframework">
- <level value="INFO"/>
+ <level value="INFO" />
</logger>
<logger name="org.springframework.beans.factory">
- <level value="INFO"/>
- </logger>
+ <level value="INFO" />
+ </logger>
<logger name="org.springframework.beans.factory.xml">
- <level value="WARN"/>
+ <level value="WARN" />
</logger>
<logger name="org.springframework.ui.context.support">
- <level value="WARN"/>
+ <level value="WARN" />
</logger>
<logger name="org.springframework.web.context">
- <level value="INFO"/>
+ <level value="INFO" />
</logger>
<logger name="org.springframework.web.context.support">
- <level value="WARN"/>
+ <level value="WARN" />
</logger>
<logger name="org.quartz">
- <level value="WARN"/>
+ <level value="WARN" />
</logger>
<!-- Caching -->
<logger name="net.sf.ehcache">
- <level value="INFO"/>
- </logger>
+ <level value="INFO" />
+ </logger>
+ <logger name="org.hibernate">
+ <level value="WARN" />
+ </logger>
</configuration>
Modified: java/server/branches/paulg_mp4/conf/red5-common.xml
==============================================================================
--- java/server/branches/paulg_mp4/conf/red5-common.xml (original)
+++ java/server/branches/paulg_mp4/conf/red5-common.xml Mon Sep 15 23:42:07
2008
@@ -1,9 +1,12 @@
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:lang="http://www.springframework.org/schema/lang"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/lang
http://www.springframework.org/schema/lang/spring-lang-2.0.xsd">
- <bean id="placeholderConfig"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
- <property name="location" value="classpath:/red5.properties"/>
- </bean>
+
+ <bean id="placeholderConfig"
+
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
+ <property name="location" value="classpath:/red5.properties" />
+ </bean>
+
<!-- This context is shared between all child contexts. -->
<!-- Server bean -->
<bean id="red5.server" class="org.red5.server.Server"/>
@@ -13,11 +16,16 @@
</bean>
<bean id="jmxAgent" class="org.red5.server.jmx.JMXAgent"
init-method="init">
<!-- The RMI adapter allows remote connections to the MBeanServer -->
-
<property name="enableRmiAdapter" value="true"/>
<property name="rmiAdapterPort" value="${jmx.rmi.port.registry}"/>
<property name="rmiAdapterRemotePort"
value="${jmx.rmi.port.remoteobjects}"/>
<property name="rmiAdapterHost" value="${jmx.rmi.host}"/>
+ <!-- SSL
+ To use jmx with ssl you must also supply the location of the keystore
and its password
+ when starting the server with the following JVM options:
+ -Djavax.net.ssl.keyStore=keystore
+ -Djavax.net.ssl.keyStorePassword=password
+ -->
<property name="enableSsl" value="${jmx.rmi.ssl}"/>
<!-- Starts a registry if it doesnt exist -->
<property name="startRegistry" value="true"/>
@@ -94,7 +102,7 @@
</bean>
<!-- High level access to streams -->
<bean id="streamService" class="org.red5.server.stream.StreamService"/>
- <!-- High level access to broadcasted streams -->
+ <!-- Hight level access to broadcasted streams -->
<bean id="providerService"
class="org.red5.server.stream.ProviderService"/>
<!-- Provides output to consumers -->
<bean id="consumerService"
class="org.red5.server.stream.ConsumerService"/>
@@ -208,7 +216,7 @@
the client buffer filled.
-->
<bean id="streamExecutor"
class="java.util.concurrent.ScheduledThreadPoolExecutor">
- <constructor-arg value="4"/>
+ <constructor-arg value="2"/>
<property name="maximumPoolSize" value="32"/>
</bean>
<!-- ClientBroadcastStream and PlaylistSubscriberStream
Modified: java/server/branches/paulg_mp4/conf/red5.properties
==============================================================================
--- java/server/branches/paulg_mp4/conf/red5.properties (original)
+++ java/server/branches/paulg_mp4/conf/red5.properties Mon Sep 15 23:42:07
2008
@@ -1,12 +1,10 @@
# Socket policy
policy.host=0.0.0.0
policy.port=843
-
# HTTP
http.host=0.0.0.0
-http.port=5080
+http.port=80
https.port=443
-
# RTMP
rtmp.host=0.0.0.0
rtmp.port=1935
Modified: java/server/branches/paulg_mp4/conf/web.xml
==============================================================================
--- java/server/branches/paulg_mp4/conf/web.xml (original)
+++ java/server/branches/paulg_mp4/conf/web.xml Mon Sep 15 23:42:07 2008
@@ -554,6 +554,10 @@
<mime-type>text/css</mime-type>
</mime-mapping>
<mime-mapping>
+ <extension>dcr</extension>
+ <mime-type>application/x-director</mime-type>
+ </mime-mapping>
+ <mime-mapping>
<extension>dib</extension>
<mime-type>image/bmp</mime-type>
</mime-mapping>
@@ -1034,6 +1038,10 @@
<mime-type>application/x-visio</mime-type>
</mime-mapping>
<mime-mapping>
+ <extension>w3d</extension>
+ <mime-type>application/octet-stream</mime-type>
+ </mime-mapping>
+ <mime-mapping>
<!-- Wireless Bitmap -->
<extension>wbmp</extension>
<mime-type>image/vnd.wap.wbmp</mime-type>
@@ -1153,4 +1161,4 @@
<auth-constraint/>
</security-constraint>
-</web-app>
\ No newline at end of file
+</web-app>
Modified: java/server/branches/paulg_mp4/java6_build_dist.bat
==============================================================================
--- java/server/branches/paulg_mp4/java6_build_dist.bat (original)
+++ java/server/branches/paulg_mp4/java6_build_dist.bat Mon Sep 15 23:42:07
2008
@@ -1,7 +1,10 @@
+SETLOCAL
SET JAVA_HOME=c:\dev\java6
SET PATH=c:\dev\java6\bin;%PATH%
-ant clean dist
+ant dist
+
+ENDLOCAL
pause
Added: java/server/branches/paulg_mp4/src/org/red5/io/mp4/MP4Frame.java
==============================================================================
--- (empty file)
+++ java/server/branches/paulg_mp4/src/org/red5/io/mp4/MP4Frame.java Mon
Sep 15 23:42:07 2008
@@ -0,0 +1,117 @@
+package org.red5.io.mp4;
+
+/*
+ * RED5 Open Source Flash Server - http://www.osflash.org/red5
+ *
+ * Copyright (c) 2006-2007 by respective authors (see below). All rights
reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
under the
+ * terms of the GNU Lesser General Public License as published by the Free
Software
+ * Foundation; either version 2.1 of the License, or (at your option) any
later
+ * version.
+ *
+ * This library is distributed in the hope that it will be useful, but
WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
License along
+ * with this library; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/**
+ * Represents an MP4 frame.
+ *
+ * @author Paul Gregoire (mondain at gmail.com)
+ */
+public class MP4Frame implements Comparable<MP4Frame> {
+
+ private byte type;
+
+ private long offset;
+
+ private int size;
+
+ private int time;
+
+ private boolean keyFrame;
+
+ /**
+ * Returns the data type, being audio or video.
+ *
+ * @return
+ */
+ public byte getType() {
+ return type;
+ }
+
+ public void setType(byte type) {
+ this.type = type;
+ }
+
+ /**
+ * Returns the offset of the data chunk in the media source.
+ *
+ * @return
+ */
+ public long getOffset() {
+ return offset;
+ }
+
+ public void setOffset(long offset) {
+ this.offset = offset;
+ }
+
+ /**
+ * Returns the size of the data chunk.
+ *
+ * @return
+ */
+ public int getSize() {
+ return size;
+ }
+
+ public void setSize(int size) {
+ this.size = size;
+ }
+
+ /**
+ * Returns the timestamp.
+ *
+ * @return
+ */
+ public int getTime() {
+ return time;
+ }
+
+ public void setTime(int time) {
+ this.time = time;
+ }
+
+ /**
+ * Returns whether or not this chunk represents a key frame.
+ *
+ * @return
+ */
+ public boolean isKeyFrame() {
+ return keyFrame;
+ }
+
+ public void setKeyFrame(boolean keyFrame) {
+ this.keyFrame = keyFrame;
+ }
+
+ /**
+ * The frames are expected to be sorted by their timestamp
+ */
+ public int compareTo(MP4Frame that) {
+ int ret = 0;
+ if (this.time > that.getTime()) {
+ ret = 1;
+ } else if (this.time < that.getTime()) {
+ ret = -1;
+ }
+ return ret;
+ }
+
+}
Modified:
java/server/branches/paulg_mp4/src/org/red5/io/mp4/impl/MP4Reader.java
==============================================================================
--- java/server/branches/paulg_mp4/src/org/red5/io/mp4/impl/MP4Reader.java
(original)
+++ java/server/branches/paulg_mp4/src/org/red5/io/mp4/impl/MP4Reader.java
Mon Sep 15 23:42:07 2008
@@ -26,6 +26,7 @@
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
@@ -45,26 +46,27 @@
import org.red5.io.flv.impl.Tag;
import org.red5.io.mp4.MP4Atom;
import org.red5.io.mp4.MP4DataStream;
+import org.red5.io.mp4.MP4Frame;
import org.red5.io.object.Serializer;
-import org.red5.server.api.IConnection;
-import org.red5.server.api.Red5;
-import org.red5.server.net.rtmp.Channel;
-import org.red5.server.net.rtmp.event.Invoke;
-import org.red5.server.service.PendingCall;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
- * A Reader is used to read the contents of a MP4 file.
- * NOTE: This class is not implemented as threading-safe. The caller
- * should make sure the threading-safety.
+ * This reader is used to read the contents of an MP4 file.
+ *
+ * NOTE: This class is not implemented as thread-safe, the caller
+ * should ensure the thread-safety.
* <p>
* New NetStream notifications
* <br />
* Two new notifications facilitate the implementation of the playback
components:
* <ul>
- * <li>NetStream.Play.FileStructureInvalid: This event is sent if the
player detects an MP4 with an invalid file structure. Flash Player cannot
play files that have invalid file structures.</li>
- * <li>NetStream.Play.NoSupportedTrackFound: This event is sent if the
player does not detect any supported tracks. If there aren't any supported
video, audio or data tracks found, Flash Player does not play the file.</li>
+ * <li>NetStream.Play.FileStructureInvalid: This event is sent if the
player detects
+ * an MP4 with an invalid file structure. Flash Player cannot play files
that have
+ * invalid file structures.</li>
+ * <li>NetStream.Play.NoSupportedTrackFound: This event is sent if the
player does not
+ * detect any supported tracks. If there aren't any supported video, audio
or data
+ * tracks found, Flash Player does not play the file.</li>
* </ul>
* </p>
*
@@ -86,6 +88,8 @@
/** Video packet prefix for standard frames (interframe)*/
private final static byte[] PREFIX_VIDEO_FRAME = new byte[]{(byte) 0x27,
(byte) 0x01, (byte) 0, (byte) 0, (byte) 0};
+
+ private final static byte[] CHUNK_MARKER = new byte[]{(byte) 0xc5};
/**
* File
@@ -118,7 +122,9 @@
private static IKeyFrameMetaCache keyframeCache;
/** Whether or not the clip contains a video track */
- private boolean hasVideo = false;
+ private boolean hasVideo = false;
+ /** Whether or not the clip contains an audio track */
+ private boolean hasAudio = false;
//
private String videoCodecId = "avc1";
@@ -155,10 +161,14 @@
private int audioSampleDuration = 1024;
//keep track of current sample
- private int currentSample = 0;
+ private int currentSample = 1;
+
+ private int prevFrameSize = 0;
- private long firstAudioTag;
- private long firstVideoTag;
+ private List<MP4Frame> frames = new ArrayList<MP4Frame>();
+
+ private long audioCount;
+ private long videoCount;
/**
* Container for metadata and any other tags that should
@@ -193,7 +203,7 @@
this.file = f;
this.fis = new MP4DataStream(new FileInputStream(f));
channel = fis.getChannel();
- //decode all the info from the atoms
+ //decode all the info that we want from the atoms
decodeHeader();
//build the keyframe meta data
analyzeKeyFrames();
@@ -204,8 +214,8 @@
}
/**
- * Currently this expects the moov atom at the beginning of the file,
later we will
- * have to handle the mdat at the beginning instead.
+ * This handles the moov atom being at the beginning or end of the file,
so the mdat may also
+ * be before or after the moov atom.
*/
public void decodeHeader() {
try {
@@ -282,8 +292,11 @@
// soun or vide
log.debug("Handler type: {}", MP4Atom
.intToType(hdlr.getHandlerType()));
- if (MP4Atom.intToType(hdlr.getHandlerType()) == "vide") {
+ String hdlrType = MP4Atom.intToType(hdlr.getHandlerType());
+ if ("vide".equals(hdlrType)) {
hasVideo = true;
+ } else if ("soun".equals(hdlrType)) {
+ hasAudio = true;
}
i++;
}
@@ -391,8 +404,8 @@
//vector full of integers
audioChunkOffsets = stco.getChunks();
log.debug("Chunk count: {}", audioChunkOffsets.size());
- //set the first video offset
- firstAudioTag = (Long) audioChunkOffsets.get(0);
+ //set the first audio offset
+ //firstAudioChunkOffset = (Long) audioChunkOffsets.get(0);
}
//stts - has TimeSampleRecords
MP4Atom stts = stbl.lookup(MP4Atom.typeToInt("stts"), 0);
@@ -404,6 +417,7 @@
log.debug("Record data: Consecutive samples={}
Duration={}", rec.getConsecutiveSamples(), rec.getSampleDuration());
//if we have 1 record then all samples have the same
duration
if (records.size() > 1) {
+ //TODO: handle audio samples with varying durations
log.warn("Audio samples have differing durations, audio
playback may fail");
}
audioSampleDuration = rec.getSampleDuration();
@@ -442,7 +456,7 @@
// stco - chunk offset
// ctts - (composition) time to sample
// stss - sync sample
- // sdtp - independent and disposible samples
+ // sdtp - independent and disposable samples
//stsd - has codec child
MP4Atom stsd = stbl.lookup(MP4Atom.typeToInt("stsd"), 0);
@@ -479,8 +493,8 @@
//if sample size is 0 then the table must be checked due
//to variable sample sizes
log.debug("Sample size: {}", stsz.getSampleSize());
- log.debug("Sample count: {}", videoSamples.size());
videoSampleCount = videoSamples.size();
+ log.debug("Sample count: {}", videoSampleCount);
}
//stco - has Chunks
MP4Atom stco = stbl.lookup(MP4Atom.typeToInt("stco"), 0);
@@ -490,7 +504,7 @@
videoChunkOffsets = stco.getChunks();
log.debug("Chunk count: {}", videoChunkOffsets.size());
//set the first video offset
- firstVideoTag = (Long) videoChunkOffsets.get(0);
+ //firstVideoChunkOffset = (Long) videoChunkOffsets.get(0);
}
//stss - has Sync - no sync means all samples are keyframes
MP4Atom stss = stbl.lookup(MP4Atom.typeToInt("stss"), 0);
@@ -510,6 +524,7 @@
log.debug("Record data: Consecutive samples={}
Duration={}", rec.getConsecutiveSamples(), rec.getSampleDuration());
//if we have 1 record then all samples have the same
duration
if (records.size() > 1) {
+ //TODO: handle video samples with varying durations
log.warn("Video samples have differing durations, video
playback may fail");
}
videoSampleDuration = rec.getSampleDuration();
@@ -549,7 +564,7 @@
MP4Atom mdat = atom;
dataSize = mdat.getSize();
log.debug("{}", ToStringBuilder.reflectionToString(mdat));
- mdatOffset = fis.getOffset() - mdat.getSize();
+ mdatOffset = fis.getOffset() - dataSize;
log.debug("File size: {} mdat size: {}", file.length(), dataSize);
break;
@@ -561,7 +576,7 @@
}
}
- //the tag name to the offsets
+ //add the tag name (size) to the offsets
moovOffset += 8;
mdatOffset += 8;
log.debug("Offsets moov: {} mdat: {}", moovOffset, mdatOffset);
@@ -599,6 +614,11 @@
return channel.size();
} catch (Exception e) {
log.error("Error getTotalBytes", e);
+ }
+ if (file != null) {
+ //just return the file size
+ return file.length();
+ } else {
return 0;
}
}
@@ -678,7 +698,7 @@
/** {@inheritDoc}
*/
public boolean hasMoreTags() {
- return currentSample < videoSampleCount;
+ return currentSample < frames.size();
}
/**
@@ -801,131 +821,128 @@
* Video - ts=0 size=5 bytes {17 02 00 00 00}
* Video - ts=0 size=2 bytes {52 01}
* Audio - ts=0 size=4 bytes {af 00 12 10}
- * Audio - ts=0 size=9 bytes {af 01 20 00 00 00 00 00 0e}
- * Regular packets follow - prefix video with 5 bytes {17 01 00 00 00}
+ * Audio - ts=0 size=9 bytes {af 01 20 00 00 00 00 00 0e}
*
* Packet prefixes:
- * 17 00 00 00 00 = Video config?
+ * 17 00 00 00 00 = Video extra data (first video packet)
* 17 01 00 00 00 = Keyframe
* 27 01 00 00 00 = Interframe
- * af 00 = Audio config?
+ * af 00 = Audio extra data (first audio packet)
* af 01 = Audio
*
- * Audio config:
- * af 00 12 10 = AAC LC
+ * Audio extra data(s):
+ * af 00 12 10 06 = AAC LC
* af 00 13 90 56 e5 a5 48 00 = HE-AAC
*/
private void createPreStreamingTags() {
log.debug("Creating pre-streaming tags");
- //video tag #1
- ITag tag = new Tag(IoConstants.TYPE_VIDEO, 0, 2, null, 0);
- ByteBuffer body = ByteBuffer.allocate(tag.getBodySize());
- body.put(new byte[]{(byte) 0x52, (byte) 0});
- body.flip();
- tag.setBody(body);
-
- //add tag
- firstTags.add(tag);
- //clear body for re-use
- //body.clear();
-
- //video tag #2
- tag = new Tag(IoConstants.TYPE_VIDEO, 0, 5, null, tag.getBodySize());
- body = ByteBuffer.allocate(tag.getBodySize());
- body.put(new byte[]{(byte) 0x17, (byte) 0x02, (byte) 00, (byte) 00,
(byte) 00});
- body.flip();
- tag.setBody(body);
-
- //add tag
- firstTags.add(tag);
- //clear body for re-use
- //body.clear();
-
- //video tag #3
- tag = new Tag(IoConstants.TYPE_VIDEO, 0, 2, null, tag.getBodySize());
- body = ByteBuffer.allocate(tag.getBodySize());
- body.put(new byte[]{(byte) 0x52, (byte) 0x01});
- body.flip();
- tag.setBody(body);
-
- //add tag
- firstTags.add(tag);
- //clear body for re-use
- //body.clear();
-
- //audio tag #1
- tag = new Tag(IoConstants.TYPE_AUDIO, 0, 4, null, tag.getBodySize());
- body = ByteBuffer.allocate(tag.getBodySize());
- body.put(new byte[]{(byte) 0xaf, (byte) 00, (byte) 0x12, (byte) 0x10});
- body.flip();
- tag.setBody(body);
-
- //add tag
- firstTags.add(tag);
- //clear body for re-use
- //body.clear();
-
- //audio tag #2
- tag = new Tag(IoConstants.TYPE_AUDIO, 0, 9, null, tag.getBodySize());
- body = ByteBuffer.allocate(tag.getBodySize());
- body.put(new byte[]{(byte) 0xaf, (byte) 0x01, (byte) 0x20, (byte) 00,
(byte) 00, (byte) 00, (byte) 00, (byte) 00, (byte) 0x0e});
- body.flip();
- tag.setBody(body);
-
- //add tag
- firstTags.add(tag);
- //clear body for release
- //body.clear();
-
- //body.release();
- }
+ ITag tag = null;
+ ByteBuffer body = null;
+
+ if (hasVideo) {
+ //video tag #1
+ tag = new Tag(IoConstants.TYPE_VIDEO, 0, 43, null, 0);
+ body = ByteBuffer.allocate(tag.getBodySize());
+ body.put(new byte[]{(byte) 0x17, (byte) 0, (byte) 0, (byte) 0,
(byte) 0,
+ (byte) 0x01, (byte) 0x4d, (byte) 0x40, (byte) 0x33, (byte) 0xff,
(byte) 0xff, (byte) 0,
+ (byte) 0x17, (byte) 0x67, (byte) 0x4d, (byte) 0x40, (byte) 0x33,
(byte) 0x9a, (byte) 0x76,
+ (byte) 0x02, (byte) 0x80, (byte) 0x2d, (byte) 0xd0, (byte) 0x80,
(byte) 0, (byte) 0,
+ (byte) 0x03, (byte) 0, (byte) 0x80, (byte) 0, (byte) 0,
(byte) 0x19, (byte) 0x47,
+ (byte) 0x8c, (byte) 0x18, (byte) 0x9c, (byte) 0x01, (byte) 0,
(byte) 0x04, (byte) 0x68,
+ (byte) 0xce, (byte) 0x3c, (byte) 0x80});
+
+ body.flip();
+ tag.setBody(body);
- private int prevFrameSize = 0;
+ //add tag
+ firstTags.add(tag);
+ }
+
+ if (hasAudio) {
+ //audio tag #1
+ tag = new Tag(IoConstants.TYPE_AUDIO, 0, 5, null, tag.getBodySize());
+ body = ByteBuffer.allocate(tag.getBodySize());
+ body.put(new byte[]{(byte) 0xaf, (byte) 0, (byte) 0x12, (byte) 0x10,
(byte) 0x06});
+ body.flip();
+ tag.setBody(body);
+
+ //add tag
+ firstTags.add(tag);
+ }
+ }
/**
*
*/
public synchronized ITag readTag() {
- log.debug("Read tag - currentSample {}, prevFrameSize {}", new
Object[]{currentSample, prevFrameSize});
+ log.debug("Read tag");
//empty-out the pre-streaming tags first
if (!firstTags.isEmpty()) {
log.debug("Returning pre-tag");
// Return first tags before media data
return firstTags.removeFirst();
}
-
- int sampleSize = (Integer) videoSamples.get(currentSample) + 5;
- int ts = videoSampleDuration * currentSample;
//Math.round((currentSample * timeScale) / videoSampleDuration);
+ log.debug("Read tag - currentSample {}, prevFrameSize {}", new
Object[]{currentSample, prevFrameSize});
+
+ //get the current frame
+ MP4Frame frame = frames.get(currentSample - 1);
+ int sampleSize = frame.getSize();
+ //int sampleSize = (Integer) videoSamples.get(currentSample) + 5;
+ int ts = frame.getTime();
+ //int ts = videoSampleDuration * currentSample;
+ log.debug("Read tag - sampleSize {} ts {}", new Object[]{sampleSize,
ts});
log.debug("Read tag - sample dur / scale {}", new
Object[]{((currentSample * timeScale) / videoSampleDuration)});
+
+ long samplePos = frame.getOffset();
+ //long samplePos = samplePosMap.get(currentSample);
+ log.debug("Read tag - samplePos {}", samplePos);
log.debug("Sample position map: {}", samplePosMap);
- long samplePos = samplePosMap.get(currentSample);
- log.debug("Read tag - sampleSize {} ts {}", new Object[]{sampleSize,
ts});
- //once video works we will try adding audio
- //tag = new Tag(IoConstants.TYPE_AUDIO, ts, sampleSize, null,
prevFrameSize);
- ITag tag = new Tag(IoConstants.TYPE_VIDEO, ts, sampleSize, null,
prevFrameSize);
- log.debug("Read tag - body size: {}", tag.getBodySize());
- ByteBuffer body = ByteBuffer.allocate(tag.getBodySize());
- log.debug("Read tag - current pos {} sample pos {}",
getCurrentPosition(), samplePos);
- //prefix is different for keyframes
- if (posTimeMap.containsKey(samplePos)) {
- log.debug("Writing keyframe prefix");
- body.put(PREFIX_VIDEO_KEYFRAME);
- } else {
- log.debug("Writing interframe prefix");
- body.put(PREFIX_VIDEO_FRAME);
+ //determine frame type and packet body padding
+ byte type = frame.getType();
+ //assume video type
+ int pad = 5;
+ if (type == TYPE_AUDIO) {
+ pad = 2;
}
+
+ //create a byte buffer of the size of the sample
+ java.nio.ByteBuffer data = java.nio.ByteBuffer.allocate(sampleSize +
pad);
try {
+ //prefix is different for keyframes
+ if (type == TYPE_VIDEO) {
+ if (frame.isKeyFrame()) {
+ log.debug("Writing keyframe prefix");
+ data.put(PREFIX_VIDEO_KEYFRAME);
+ } else {
+ log.debug("Writing interframe prefix");
+ data.put(PREFIX_VIDEO_FRAME);
+ }
+
+ videoCount++;
+ } else {
+ log.debug("Writing audio prefix");
+ data.put(PREFIX_AUDIO_FRAME);
+
+ audioCount++;
+ }
//do we need to add the mdat offset to the sample position?
channel.position(samplePos);
- channel.read(body.buf());
+ channel.read(data);
} catch (IOException e) {
- log.error("Error handling position", e);
+ log.error("Error on channel position / read", e);
}
- body.flip();
- tag.setBody(body);
- currentSample++;
+ //chunk the data
+ ByteBuffer payload = getChunkedPayload(data.array());
+
+ //create the tag
+ ITag tag = new Tag(type, ts, payload.limit(), payload, prevFrameSize);
+ log.debug("Read tag - type: {} body size: {}", (type ==
TYPE_AUDIO ? "Audio" : "Video"), tag.getBodySize());
+
+ //increment the sample number
+ currentSample++;
+ //set the frame / tag size
prevFrameSize = tag.getBodySize();
//log.debug("Tag: {}", tag);
@@ -933,8 +950,7 @@
}
/**
- * Key frames analysis may be used as a utility method so
- * synchronize it.
+ * Performs frame analysis and generates metadata for use in seeking.
*
* @return Keyframe metadata
*/
@@ -955,37 +971,82 @@
posTagMap = new HashMap<Long, Integer>();
samplePosMap = new HashMap<Integer, Long>();
// tag == sample
- int sample = 0;
+ int sample = 1;
Long pos = null;
Enumeration records = videoSamplesToChunks.elements();
while (records.hasMoreElements()) {
MP4Atom.Record record = (MP4Atom.Record) records.nextElement();
int firstChunk = record.getFirstChunk();
int sampleCount = record.getSamplesPerChunk();
- //log.debug("First chunk: {} count:{}", firstChunk, sampleCount);
+ log.debug("Video first chunk: {} count:{}", firstChunk, sampleCount);
pos = (Long) videoChunkOffsets.elementAt(firstChunk - 1);
while (sampleCount > 0) {
- //log.debug("Position: {}", pos);
+ log.debug("Position: {}", pos);
posTagMap.put(pos, sample);
samplePosMap.put(sample, pos);
+ //calculate ts
+ int ts = ((int) videoSampleDuration * sample);
//check to see if the sample is a keyframe
- if (syncSamples.contains(sample)) {
- //log.debug("Keyframe - sample: {}", sample);
+ boolean keyframe = syncSamples.contains(sample);
+ if (keyframe) {
+ log.debug("Keyframe - sample: {}", sample);
positionList.add(pos);
- //need to calculate ts
- Integer ts = ((int) videoSampleDuration * (sample));
//log.debug("Keyframe - timestamp: {}", ts);
timestampList.add(ts);
}
- pos = pos + (Integer) videoSamples.get(sample);
+ int size = ((Integer) videoSamples.get(sample - 1)).intValue();
+
+ //create a frame
+ MP4Frame frame = new MP4Frame();
+ frame.setKeyFrame(keyframe);
+ frame.setOffset(pos);
+ frame.setSize(size);
+ frame.setTime(ts);
+ frame.setType(TYPE_VIDEO);
+ frames.add(frame);
+
+ //inc and dec stuff
+ pos += size;
sampleCount--;
- sample++;
+ sample++;
}
}
log.debug("Position Tag Map size: {}", posTagMap.size());
log.debug("Keyframe position list size: {}", positionList.size());
+ //add the audio frames / samples / chunks
+ sample = 1;
+ records = audioSamplesToChunks.elements();
+ while (records.hasMoreElements()) {
+ MP4Atom.Record record = (MP4Atom.Record) records.nextElement();
+ int firstChunk = record.getFirstChunk();
+ int sampleCount = record.getSamplesPerChunk();
+ log.debug("Audio first chunk: {} count:{}", firstChunk, sampleCount);
+ pos = (Long) audioChunkOffsets.elementAt(firstChunk - 1);
+ while (sampleCount > 0) {
+ //calculate ts
+ int ts = ((int) audioSampleDuration * sample);
+ //sample size
+ int size = ((Integer) audioSamples.get(sample - 1)).intValue();
+ //create a frame
+ MP4Frame frame = new MP4Frame();
+ frame.setOffset(pos);
+ frame.setSize(size);
+ frame.setTime(ts);
+ frame.setType(TYPE_AUDIO);
+ frames.add(frame);
+
+ //inc and dec stuff
+ pos += size;
+ sampleCount--;
+ sample++;
+ }
+ }
+
+ //sort the frames
+ Collections.sort(frames);
+
keyframeMeta = new KeyFrameMeta();
keyframeMeta.duration = duration;
posTimeMap = new HashMap<Long, Long>();
@@ -1005,6 +1066,40 @@
return keyframeMeta;
}
+ /**
+ * Handler for data chunks.
+ *
+ * @param payload
+ * @return
+ */
+ private ByteBuffer getChunkedPayload(byte[] payload) {
+ log.debug("Get chunked payload");
+ int len = payload.length;
+ log.debug("Payload length: {}", len);
+ int chunkLen = 0;
+ int offset = 0;
+ //extra bytes needed for chunk markers and such
+ int extra = Math.max(0, Math.round(len / 4096));
+ //size the return array as good as possible to prevent resize
+ ByteBuffer ret = ByteBuffer.allocate(len + extra);
+ //allow resize
+ ret.setAutoExpand(true);
+ while (len > 0) {
+ chunkLen = Math.min(len, 4096);
+ log.debug("Chunk len: {}", chunkLen);
+ ret.put(payload, offset, chunkLen);
+ log.debug("read: {}", ret.position());
+ offset += chunkLen;
+ len -= chunkLen;
+ log.debug("len: {}", len);
+ if (len > 0) {
+ ret.put(CHUNK_MARKER);
+ }
+ }
+ ret.flip();
+ return ret;
+ }
+
/**
* Put the current position to pos.
* The caller must ensure the pos is a valid one
Modified: java/server/branches/paulg_mp4/src/org/red5/server/api/Red5.java
==============================================================================
--- java/server/branches/paulg_mp4/src/org/red5/server/api/Red5.java
(original)
+++ java/server/branches/paulg_mp4/src/org/red5/server/api/Red5.java Mon
Sep 15 23:42:07 2008
@@ -59,7 +59,12 @@
/**
* Current server version with revision
*/
- public static final String VERSION = "Red5 Server 0.7.1-dev $Revision:
3020 $";
+ public static final String VERSION = "Red5 Server 0.7.2-dev $Revision:
3020 $";
+
+ /**
+ * Current server version for fmsVer requests
+ */
+ public static final String FMS_VERSION = "RED5/0,7,2,0";
/**
* Server start time
@@ -152,6 +157,15 @@
*/
public static String getVersion() {
return VERSION;
+ }
+
+ /**
+ * Returns the current version for fmsVer requests
+ *
+ * @return String fms version
+ */
+ public static String getFMSVersion() {
+ return FMS_VERSION;
}
/**
Modified:
java/server/branches/paulg_mp4/src/org/red5/server/net/rtmp/RTMPHandler.java
==============================================================================
---
java/server/branches/paulg_mp4/src/org/red5/server/net/rtmp/RTMPHandler.java
(original)
+++
java/server/branches/paulg_mp4/src/org/red5/server/net/rtmp/RTMPHandler.java
Mon Sep 15 23:42:07 2008
@@ -32,6 +32,7 @@
import org.red5.server.api.IScope;
import org.red5.server.api.IScopeHandler;
import org.red5.server.api.IServer;
+import org.red5.server.api.Red5;
import org.red5.server.api.ScopeUtils;
import org.red5.server.api.IConnection.Encoding;
import org.red5.server.api.service.IPendingServiceCall;
@@ -306,7 +307,7 @@
IPendingServiceCall pc = (IPendingServiceCall) call;
//send fmsver and capabilities
StatusObject result = getStatus(NC_CONNECT_SUCCESS);
- result.setAdditional("fmsVer", "RED5/0,7,1,0");
+ result.setAdditional("fmsVer", Red5.getFMSVersion());
result.setAdditional("capabilities", Integer.valueOf(31));
pc.setResult(result);
}
Modified:
java/server/branches/paulg_mp4/src/org/red5/server/net/rtmp/event/FLVData.java
==============================================================================
---
java/server/branches/paulg_mp4/src/org/red5/server/net/rtmp/event/FLVData.java
(original)
+++
java/server/branches/paulg_mp4/src/org/red5/server/net/rtmp/event/FLVData.java
Mon Sep 15 23:42:07 2008
@@ -37,7 +37,7 @@
* soundSize (byte & 0x02) == 1 0: 8-bit, 2: 16-bit
* soundRate (byte & 0x0C) == 2 0: 5.5kHz, 1: 11kHz, 2: 22kHz, 3: 44kHz
* soundFormat (byte & 0xf0) == 4 0: Uncompressed, 1: ADPCM, 2: MP3,
- * 5: Nellymoser 8kHz mono, 6: Nellymoser
+ * 5: Nellymoser 8kHz mono, 6: Nellymoser, 11: Speex
*/
/*
@@ -53,7 +53,7 @@
/**
* Getter for disposable state
*
- * @return <code>true</code> if FVL data is disposable,
<code>false</code> otherwise
+ * @return <code>true</code> if FLV data is disposable,
<code>false</code> otherwise
*/
public boolean isDisposable() {
return false;
@@ -116,6 +116,10 @@
*/
public static final int AUDIO_NELLYMOOSER = 6;
/**
+ * Speex encoded data
+ */
+ public static final int AUDIO_SPEEX = 11;
+ /**
* Sound size when 8 khz quality marker
*/
public static final int SOUND_SIZE_8_BIT = 0;
Modified:
java/server/branches/paulg_mp4/src/org/red5/server/net/rtmp/event/VideoData.java
==============================================================================
---
java/server/branches/paulg_mp4/src/org/red5/server/net/rtmp/event/VideoData.java
(original)
+++
java/server/branches/paulg_mp4/src/org/red5/server/net/rtmp/event/VideoData.java
Mon Sep 15 23:42:07 2008
@@ -22,10 +22,13 @@
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
+
import org.apache.mina.common.ByteBuffer;
import org.red5.io.IoConstants;
import org.red5.server.api.stream.IStreamPacket;
import org.red5.server.stream.IStreamData;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Video data event
@@ -33,7 +36,10 @@
public class VideoData extends BaseEvent implements IoConstants,
IStreamData, IStreamPacket {
private static final long serialVersionUID = 5538859593815804830L;
- /**
+
+ private static Logger log = LoggerFactory.getLogger(VideoData.class);
+
+ /**
* Videoframe type
*/
public static enum FrameType {
@@ -65,15 +71,20 @@
if (data != null && data.limit() > 0) {
int oldPos = data.position();
int firstByte = (data.get()) & 0xff;
+ log.debug("old pos: {} first byte: {}", oldPos, firstByte);
data.position(oldPos);
int frameType = (firstByte & MASK_VIDEO_FRAMETYPE) >> 4;
if (frameType == FLAG_FRAMETYPE_KEYFRAME) {
+ log.debug("Frame type = Keyframe");
this.frameType = FrameType.KEYFRAME;
} else if (frameType == FLAG_FRAMETYPE_INTERFRAME) {
+ log.debug("Frame type = Interframe");
this.frameType = FrameType.INTERFRAME;
} else if (frameType == FLAG_FRAMETYPE_DISPOSABLE) {
+ log.debug("Frame type = Disposable interframe");
this.frameType = FrameType.DISPOSABLE_INTERFRAME;
} else {
+ log.debug("Frame type = Unknown");
this.frameType = FrameType.UNKNOWN;
}
}
Modified:
java/server/branches/paulg_mp4/src/org/red5/server/stream/PlayEngine.java
==============================================================================
---
java/server/branches/paulg_mp4/src/org/red5/server/stream/PlayEngine.java
(original)
+++
java/server/branches/paulg_mp4/src/org/red5/server/stream/PlayEngine.java
Mon Sep 15 23:42:07 2008
@@ -889,9 +889,11 @@
}
}
lastMessage = message.getBody();
- if (lastMessage instanceof IStreamData) {
- bytesSent += ((IStreamData) lastMessage).getData().limit();
- }
+ //XXX Paul: bytesSent is updated in the doPushMessage() so I assume we
dont
+ //also want to do it here?
+ //if (lastMessage instanceof IStreamData) {
+ // bytesSent += ((IStreamData) lastMessage).getData().limit();
+ //}
doPushMessage(message);
}
Added:
java/server/branches/paulg_mp4/test/org/red5/server/io/mp4/MP4FrameTest.java
==============================================================================
--- (empty file)
+++
java/server/branches/paulg_mp4/test/org/red5/server/io/mp4/MP4FrameTest.java
Mon Sep 15 23:42:07 2008
@@ -0,0 +1,60 @@
+package org.red5.server.io.mp4;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.junit.Test;
+import org.red5.io.mp4.MP4Frame;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class MP4FrameTest extends TestCase {
+
+ private static Logger log = LoggerFactory.getLogger(MP4FrameTest.class);
+
+ @Test
+ public void testSort() {
+
+ List<MP4Frame> frames = new ArrayList<MP4Frame>(6);
+
+ //create some frames
+ MP4Frame frame1 = new MP4Frame();
+ frame1.setTime(1);
+ frames.add(frame1);
+
+ MP4Frame frame2 = new MP4Frame();
+ frame2.setTime(6);
+ frames.add(frame2);
+
+ MP4Frame frame3 = new MP4Frame();
+ frame3.setTime(660);
+ frames.add(frame3);
+
+ MP4Frame frame4 = new MP4Frame();
+ frame4.setTime(3);
+ frames.add(frame4);
+
+ MP4Frame frame5 = new MP4Frame();
+ frame5.setTime(400);
+ frames.add(frame5);
+
+ System.out.printf("Frame 1 - time: %d (should be 660)\n",
frames.get(2).getTime());
+
+ Collections.sort(frames);
+
+ System.out.println("After sorting");
+
+ int f = 1;
+ for (MP4Frame frame : frames) {
+ System.out.printf("Frame %d - time: %d\n", f++, frame.getTime());
+ }
+
+ }
+
+
+
+}
More information about the Red5devs
mailing list