[Red5devs] [red5 commit] r3064 - in java/server/branches/paulg_mp4: conf src/org/red5/io/mp4 src/org/red5/io/mp4/impl src/...
codesite-noreply at google.com
codesite-noreply at google.com
Wed Sep 17 16:56:00 PDT 2008
Author: mondain
Date: Wed Sep 17 16:55:08 2008
New Revision: 3064
Modified:
java/server/branches/paulg_mp4/conf/logback.xml
java/server/branches/paulg_mp4/src/org/red5/io/mp4/MP4Atom.java
java/server/branches/paulg_mp4/src/org/red5/io/mp4/MP4Frame.java
java/server/branches/paulg_mp4/src/org/red5/io/mp4/impl/MP4Reader.java
java/server/branches/paulg_mp4/src/org/red5/server/net/rtmp/codec/RTMPProtocolDecoder.java
java/server/branches/paulg_mp4/src/org/red5/server/net/rtmp/codec/RTMPProtocolEncoder.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
java/server/branches/paulg_mp4/test/org/red5/server/io/mp4/MP4FrameTest.java
Log:
Timestamps and frame collection sort are now working and match Izumi
exactly. Updated timestamps to use doubles as well as audio sample rate.
Added debug sections for RTMP packet checking as this seems to be a
difference between us and Izumi.
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 Wed Sep 17 16:55:08 2008
@@ -49,7 +49,7 @@
<level value="DEBUG" />
</logger>
<logger name="org.red5.io.mp4.MP4Atom">
- <level value="WARN" />
+ <level value="DEBUG" />
</logger>
<logger name="org.red5.io.mp4.MP4Descriptor">
<level value="WARN" />
@@ -117,6 +117,12 @@
</logger>
<logger name="org.red5.server.net.rtmp.codec">
<level value="INFO" />
+ </logger>
+ <logger name="org.red5.server.net.rtmp.codec.RTMPProtocolDecoder">
+ <level value="DEBUG" />
+ </logger>
+ <logger name="org.red5.server.net.rtmp.codec.RTMPProtocolEncoder">
+ <level value="DEBUG" />
</logger>
<logger name="org.red5.server.net.rtmp.status">
<level value="INFO" />
Modified: java/server/branches/paulg_mp4/src/org/red5/io/mp4/MP4Atom.java
==============================================================================
--- java/server/branches/paulg_mp4/src/org/red5/io/mp4/MP4Atom.java
(original)
+++ java/server/branches/paulg_mp4/src/org/red5/io/mp4/MP4Atom.java Wed Sep
17 16:55:08 2008
@@ -19,26 +19,21 @@
*/
/**
- This software module was originally developed by Apple Computer, Inc.
- in the course of development of MPEG-4.
- This software module is an implementation of a part of one or
- more MPEG-4 tools as specified by MPEG-4.
- ISO/IEC gives users of MPEG-4 free license to this
- software module or modifications thereof for use in hardware
- or software products claiming conformance to MPEG-4.
- Those intending to use this software module in hardware or software
- products are advised that its use may infringe existing patents.
- The original developer of this software module and his/her company,
- the subsequent editors and their companies, and ISO/IEC have no
- liability for use of this software module or modifications thereof
- in an implementation.
- Copyright is not released for non MPEG-4 conforming
- products. Apple Computer, Inc. retains full right to use the code for its
own
- purpose, assign or donate the code to a third party and to
- inhibit third parties from using the code for non
- MPEG-4 conforming products.
- This copyright notice must be included in all copies or
- derivative works. Copyright (c) 1999.
+ This software module was originally developed by Apple Computer, Inc. in
the
+ course of development of MPEG-4. This software module is an
implementation of
+ a part of one or more MPEG-4 tools as specified by MPEG-4. ISO/IEC gives
users
+ of MPEG-4 free license to this software module or modifications thereof
for
+ use in hardware or software products claiming conformance to MPEG-4. Those
+ intending to use this software module in hardware or software products are
+ advised that its use may infringe existing patents. The original
developer of
+ this software module and his/her company, the subsequent editors and their
+ companies, and ISO/IEC have no liability for use of this software module
or
+ modifications thereof in an implementation. Copyright is not released for
non
+ MPEG-4 conforming products. Apple Computer, Inc. retains full right to
use the
+ code for its own purpose, assign or donate the code to a third party and
to
+ inhibit third parties from using the code for non MPEG-4 conforming
products.
+ This copyright notice must be included in all copies or derivative works.
+ Copyright (c) 1999.
*/
import java.io.IOException;
@@ -786,7 +781,7 @@
log.debug("V Resolution: {}", verticalRez);
bitstream.skipBytes(4);
int frameCount = (int) bitstream.readBytes(2);
- log.debug("Frame count: {}", frameCount);
+ log.debug("Frame to sample count: {}", frameCount);
int stringLen = (int) bitstream.readBytes(1);
log.debug("String length (cpname): {}", stringLen);
String compressorName = bitstream.readString(31);
Modified: java/server/branches/paulg_mp4/src/org/red5/io/mp4/MP4Frame.java
==============================================================================
--- java/server/branches/paulg_mp4/src/org/red5/io/mp4/MP4Frame.java
(original)
+++ java/server/branches/paulg_mp4/src/org/red5/io/mp4/MP4Frame.java Wed
Sep 17 16:55:08 2008
@@ -20,7 +20,7 @@
*/
/**
- * Represents an MP4 frame.
+ * Represents an MP4 frame / chunk sample
*
* @author Paul Gregoire (mondain at gmail.com)
*/
@@ -32,7 +32,7 @@
private int size;
- private int time;
+ private double time;
private boolean keyFrame;
@@ -80,11 +80,11 @@
*
* @return
*/
- public int getTime() {
+ public double getTime() {
return time;
}
- public void setTime(int time) {
+ public void setTime(double time) {
this.time = time;
}
@@ -101,6 +101,46 @@
this.keyFrame = keyFrame;
}
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (int) (offset ^ (offset >>> 32));
+ result = prime * result + type;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ MP4Frame other = (MP4Frame) obj;
+ if (offset != other.offset)
+ return false;
+ if (type != other.type)
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("MP4Frame type=");
+ sb.append(type);
+ sb.append(", time=");
+ sb.append(time);
+ sb.append(", size=");
+ sb.append(size);
+ sb.append(", offset=");
+ sb.append(offset);
+ sb.append(", keyframe=");
+ sb.append(keyFrame);
+ return sb.toString();
+ }
+
/**
* The frames are expected to be sorted by their timestamp
*/
@@ -110,6 +150,10 @@
ret = 1;
} else if (this.time < that.getTime()) {
ret = -1;
+ } else if (this.time == that.getTime() && this.offset >
that.getOffset()) {
+ ret = 1;
+ } else if (this.time == that.getTime() && this.offset <
that.getOffset()) {
+ 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
Wed Sep 17 16:55:08 2008
@@ -135,10 +135,11 @@
private int timeScale;
private int width;
private int height;
- private int audioSampleRate;
+ private double audioSampleRate;
private int audioChannels;
private int videoSampleCount;
private double fps;
+ private double videoSampleRate = 2997.0; //not sure where to get this
value from?
private int avcLevel;
private int avcProfile;
private String formattedDuration;
@@ -170,6 +171,8 @@
private long audioCount;
private long videoCount;
+ private double baseTs = 0f;
+
/**
* Container for metadata and any other tags that should
* be sent prior to media data.
@@ -208,7 +211,7 @@
//build the keyframe meta data
analyzeKeyFrames();
//add meta data
- firstTags.add(createFileMeta());
+ //firstTags.add(createFileMeta());
//create / add the pre-streaming tags
createPreStreamingTags();
}
@@ -350,9 +353,9 @@
setAudioCodecId(MP4Atom.intToType(mp4a.getType()));
//log.debug("{}",
ToStringBuilder.reflectionToString(mp4a));
log.debug("Sample size: {}", mp4a.getSampleSize());
- audioSampleRate = mp4a.getTimeScale();
+ audioSampleRate = mp4a.getTimeScale() * 1.0;
audioChannels = mp4a.getChannelCount();
- log.debug("Sample rate: {}", audioSampleRate);
+ log.debug("Sample rate (time scale): {}",
audioSampleRate);
log.debug("Channels: {}", audioChannels);
/* no data we care about right now
//mp4a: esds
@@ -465,6 +468,7 @@
MP4Atom avc1 = stsd.getChildren().get(0);
//could set the video codec here
setVideoCodecId(MP4Atom.intToType(avc1.getType()));
+ log.debug("Sample rate (time scale): {}", videoSampleRate);
//
MP4Atom avcC = avc1.lookup(MP4Atom.typeToInt("avcC"), 0);
if (avcC != null) {
@@ -545,6 +549,7 @@
//real duration
StringBuilder sb = new StringBuilder();
double videoTime = ((double) duration / (double) timeScale);
+ log.debug("Video time: {}", videoTime);
int minutes = (int) (videoTime / 60);
if (minutes > 0) {
sb.append(minutes);
@@ -851,6 +856,13 @@
(byte) 0x8c, (byte) 0x18, (byte) 0x9c, (byte) 0x01, (byte) 0,
(byte) 0x04, (byte) 0x68,
(byte) 0xce, (byte) 0x3c, (byte) 0x80});
+ //fake avcc
+ //(byte) 0x01, (byte) 0x4D, (byte) 0x40, (byte) 0x1F, (byte) 0xFF,
(byte) 0xE1, (byte) 0x00,
+ //(byte) 0x14, (byte) 0x27, (byte) 0x4D, (byte) 0x40, (byte) 0x1F,
(byte) 0xA9, (byte) 0x18,
+ //(byte) 0x0A, (byte) 0x00, (byte) 0x8B, (byte) 0x60, (byte) 0x0D,
(byte) 0x41, (byte) 0x80,
+ //(byte) 0x41, (byte) 0x8C, (byte) 0x2B, (byte) 0x5E, (byte) 0xF7,
(byte) 0xC0, (byte) 0x40,
+ //(byte) 0x01, (byte) 0x00, (byte) 0x04, (byte) 0x28, (byte) 0xCE,
(byte) 0x09, (byte) 0xC8
+
body.flip();
tag.setBody(body);
@@ -882,21 +894,22 @@
// Return first tags before media data
return firstTags.removeFirst();
}
- log.debug("Read tag - currentSample {}, prevFrameSize {}", new
Object[]{currentSample, prevFrameSize});
+ log.debug("Read tag - sample {} prevFrameSize {} audio: {} video: {}",
new Object[]{currentSample, prevFrameSize, audioCount, videoCount});
//get the current frame
MP4Frame frame = frames.get(currentSample - 1);
+ log.debug("Playback {}", frame);
+
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)});
+
+ double frameTs = (frame.getTime() - baseTs) * 1000.0;
+ int time = (int) Math.round(frameTs);
+ log.debug("Read tag - dst: {} base: {} time: {}", new Object[]{frameTs,
baseTs, time});
+ //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);
+ //log.debug("Read tag - samplePos {}", samplePos);
//determine frame type and packet body padding
byte type = frame.getType();
@@ -937,7 +950,7 @@
ByteBuffer payload = getChunkedPayload(data.array());
//create the tag
- ITag tag = new Tag(type, ts, payload.limit(), payload, prevFrameSize);
+ ITag tag = new Tag(type, time, payload.limit(), payload, prevFrameSize);
log.debug("Read tag - type: {} body size: {}", (type ==
TYPE_AUDIO ? "Audio" : "Video"), tag.getBodySize());
//increment the sample number
@@ -945,12 +958,14 @@
//set the frame / tag size
prevFrameSize = tag.getBodySize();
+ baseTs += frameTs / 1000.0;
//log.debug("Tag: {}", tag);
return tag;
}
/**
- * Performs frame analysis and generates metadata for use in seeking.
+ * Performs frame analysis and generates metadata for use in seeking.
The
+ * method name is a little misleading since it analyzes all the frames.
*
* @return Keyframe metadata
*/
@@ -960,13 +975,13 @@
return keyframeMeta;
}
log.debug("Analyzing key frames");
-
+
//key frame sample numbers are stored in the syncSamples collection
int keyframeCount = syncSamples.size();
// Lists of video positions and timestamps
List<Long> positionList = new ArrayList<Long>(keyframeCount);
- List<Integer> timestampList = new
ArrayList<Integer>(keyframeCount);
+ List<Float> timestampList = new ArrayList<Float>(keyframeCount);
// Maps positions to tags
posTagMap = new HashMap<Long, Integer>();
samplePosMap = new HashMap<Integer, Long>();
@@ -981,18 +996,18 @@
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);
+ double ts = (videoSampleDuration * (sample - 1)) / videoSampleRate;
//check to see if the sample is a keyframe
boolean keyframe = syncSamples.contains(sample);
if (keyframe) {
log.debug("Keyframe - sample: {}", sample);
positionList.add(pos);
//log.debug("Keyframe - timestamp: {}", ts);
- timestampList.add(ts);
+ //timestampList.add(ts);
}
int size = ((Integer) videoSamples.get(sample - 1)).intValue();
@@ -1005,6 +1020,8 @@
frame.setType(TYPE_VIDEO);
frames.add(frame);
+ log.debug("Sample #{} {}", sample, frame);
+
//inc and dec stuff
pos += size;
sampleCount--;
@@ -1012,10 +1029,10 @@
}
}
- log.debug("Position Tag Map size: {}", posTagMap.size());
- log.debug("Keyframe position list size: {}", positionList.size());
-
- //add the audio frames / samples / chunks
+ log.debug("Position map size: {} keyframe list size: {}",
posTagMap.size(), positionList.size());
+ log.debug("Sample position map (video): {}", samplePosMap);
+
+ //add the audio frames / samples / chunks
sample = 1;
records = audioSamplesToChunks.elements();
while (records.hasMoreElements()) {
@@ -1026,7 +1043,7 @@
pos = (Long) audioChunkOffsets.elementAt(firstChunk - 1);
while (sampleCount > 0) {
//calculate ts
- int ts = ((int) audioSampleDuration * sample);
+ double ts = (audioSampleDuration * (sample - 1)) / audioSampleRate;
//sample size
int size = ((Integer) audioSamples.get(sample - 1)).intValue();
//create a frame
@@ -1037,6 +1054,8 @@
frame.setType(TYPE_AUDIO);
frames.add(frame);
+ log.debug("Sample #{} {}", sample, frame);
+
//inc and dec stuff
pos += size;
sampleCount--;
@@ -1044,25 +1063,31 @@
}
}
+ records = null;
+
//sort the frames
Collections.sort(frames);
+ log.debug("Frames count (expect 16042 for backcountry): {}",
frames.size());
+ log.debug("Frames: {}", frames);
+
keyframeMeta = new KeyFrameMeta();
keyframeMeta.duration = duration;
+ /*
posTimeMap = new HashMap<Long, Long>();
keyframeMeta.positions = new long[positionList.size()];
- keyframeMeta.timestamps = new int[timestampList.size()];
+ keyframeMeta.timestamps = new float[timestampList.size()];
for (int i = 0; i < keyframeMeta.positions.length; i++) {
keyframeMeta.positions[i] = positionList.get(i);
keyframeMeta.timestamps[i] = timestampList.get(i);
- posTimeMap.put((long) positionList.get(i), (long) timestampList
+ posTimeMap.put((long) positionList.get(i), (float) timestampList
.get(i));
}
if (keyframeCache != null) {
keyframeCache.saveKeyFrameMeta(file, keyframeMeta);
}
-
+ */
return keyframeMeta;
}
@@ -1073,9 +1098,9 @@
* @return
*/
private ByteBuffer getChunkedPayload(byte[] payload) {
- log.debug("Get chunked payload");
+ //log.debug("Get chunked payload");
int len = payload.length;
- log.debug("Payload length: {}", len);
+ //log.debug("Payload length: {}", len);
int chunkLen = 0;
int offset = 0;
//extra bytes needed for chunk markers and such
@@ -1086,12 +1111,12 @@
ret.setAutoExpand(true);
while (len > 0) {
chunkLen = Math.min(len, 4096);
- log.debug("Chunk len: {}", chunkLen);
+ //log.debug("Chunk len: {}", chunkLen);
ret.put(payload, offset, chunkLen);
- log.debug("read: {}", ret.position());
+ //log.debug("read: {}", ret.position());
offset += chunkLen;
len -= chunkLen;
- log.debug("len: {}", len);
+ //log.debug("len: {}", len);
if (len > 0) {
ret.put(CHUNK_MARKER);
}
Modified:
java/server/branches/paulg_mp4/src/org/red5/server/net/rtmp/codec/RTMPProtocolDecoder.java
==============================================================================
---
java/server/branches/paulg_mp4/src/org/red5/server/net/rtmp/codec/RTMPProtocolDecoder.java
(original)
+++
java/server/branches/paulg_mp4/src/org/red5/server/net/rtmp/codec/RTMPProtocolDecoder.java
Wed Sep 17 16:55:08 2008
@@ -441,6 +441,7 @@
public Header decodeHeader(ByteBuffer in, Header lastHeader) {
byte headerByte = in.get();
+ log.debug("Header byte: {}", headerByte);
int headerValue;
int byteCount = 1;
if ((headerByte & 0x3f) == 0) {
@@ -530,10 +531,11 @@
message = decodeInvoke(in, rtmp);
break;
case TYPE_NOTIFY:
- if (header.getStreamId() == 0)
+ if (header.getStreamId() == 0) {
message = decodeNotify(in, header, rtmp);
- else
+ } else {
message = decodeStreamMetadata(in);
+ }
break;
case TYPE_PING:
message = decodePing(in);
@@ -770,11 +772,13 @@
private boolean isStreamCommand(String action) {
return (ACTION_CREATE_STREAM.equals(action)
|| ACTION_DELETE_STREAM.equals(action)
- || ACTION_PUBLISH.equals(action) || ACTION_PLAY.equals(action)
- || ACTION_SEEK.equals(action) || ACTION_PAUSE.equals(action)
+ || ACTION_PUBLISH.equals(action)
+ || ACTION_PLAY.equals(action)
+ || ACTION_SEEK.equals(action)
+ || ACTION_PAUSE.equals(action)
|| ACTION_CLOSE_STREAM.equals(action)
- || ACTION_RECEIVE_VIDEO.equals(action) || ACTION_RECEIVE_AUDIO
- .equals(action));
+ || ACTION_RECEIVE_VIDEO.equals(action)
+ || ACTION_RECEIVE_AUDIO.equals(action));
}
/**
Modified:
java/server/branches/paulg_mp4/src/org/red5/server/net/rtmp/codec/RTMPProtocolEncoder.java
==============================================================================
---
java/server/branches/paulg_mp4/src/org/red5/server/net/rtmp/codec/RTMPProtocolEncoder.java
(original)
+++
java/server/branches/paulg_mp4/src/org/red5/server/net/rtmp/codec/RTMPProtocolEncoder.java
Wed Sep 17 16:55:08 2008
@@ -236,6 +236,7 @@
*/
public void encodeHeader(Header header, Header lastHeader, ByteBuffer
buf) {
final byte headerType = getHeaderType(header, lastHeader);
+ log.debug("Header byte (type): {}", headerType);
RTMPUtils.encodeHeaderByte(buf, headerType, header
.getChannelId());
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
Wed Sep 17 16:55:08 2008
@@ -131,7 +131,7 @@
frameType = (FrameType) in.readObject();
byte[] byteBuf = (byte[]) in.readObject();
if (byteBuf != null) {
- data = ByteBuffer.allocate(0);
+ data = ByteBuffer.allocate(byteBuf.length);
data.setAutoExpand(true);
SerializeUtils.ByteArrayToByteBuffer(byteBuf, data);
}
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
Wed Sep 17 16:55:08 2008
@@ -351,6 +351,7 @@
if (stream != null && stream.getCodecInfo() != null) {
IVideoStreamCodec videoCodec = stream.getCodecInfo()
.getVideoCodec();
+ log.debug("Stream video codec: {}", videoCodec);
if (videoCodec != null) {
ByteBuffer keyFrame = videoCodec.getKeyframe();
if (keyFrame != null) {
@@ -372,6 +373,7 @@
videoFrameDropper.reset();
} finally {
video.release();
+ video = null;
}
}
}
@@ -439,13 +441,11 @@
if (msg == null) {
break;
}
- if (!(msg instanceof RTMPMessage)) {
- continue;
+ if (msg instanceof RTMPMessage) {
+ body = ((RTMPMessage) msg).getBody();
}
- body = ((RTMPMessage) msg).getBody();
}
}
-
if (body != null) {
// Adjust timestamp when playing lists
body.setTimestamp(body.getTimestamp() + timestampOffset);
Modified:
java/server/branches/paulg_mp4/test/org/red5/server/io/mp4/MP4FrameTest.java
==============================================================================
---
java/server/branches/paulg_mp4/test/org/red5/server/io/mp4/MP4FrameTest.java
(original)
+++
java/server/branches/paulg_mp4/test/org/red5/server/io/mp4/MP4FrameTest.java
Wed Sep 17 16:55:08 2008
@@ -24,23 +24,43 @@
//create some frames
MP4Frame frame1 = new MP4Frame();
frame1.setTime(1);
+ frame1.setOffset(1);
frames.add(frame1);
MP4Frame frame2 = new MP4Frame();
frame2.setTime(6);
+ frame2.setOffset(6);
frames.add(frame2);
MP4Frame frame3 = new MP4Frame();
frame3.setTime(660);
+ frame3.setOffset(660);
frames.add(frame3);
MP4Frame frame4 = new MP4Frame();
frame4.setTime(3);
+ frame4.setOffset(3);
frames.add(frame4);
MP4Frame frame5 = new MP4Frame();
frame5.setTime(400);
+ frame5.setOffset(400);
frames.add(frame5);
+
+ MP4Frame frame6 = new MP4Frame();
+ frame6.setTime(1000);
+ frame6.setOffset(1010);
+ frames.add(frame6);
+
+ MP4Frame frame7 = new MP4Frame();
+ frame7.setTime(1000);
+ frame7.setOffset(1000);
+ frames.add(frame7);
+
+ MP4Frame frame8 = new MP4Frame();
+ frame8.setTime(1000);
+ frame8.setOffset(900);
+ frames.add(frame8);
System.out.printf("Frame 1 - time: %d (should be 660)\n",
frames.get(2).getTime());
@@ -50,7 +70,7 @@
int f = 1;
for (MP4Frame frame : frames) {
- System.out.printf("Frame %d - time: %d\n", f++, frame.getTime());
+ System.out.printf("Frame %d - time: %d offset: %d\n", f++,
frame.getTime(), frame.getOffset());
}
}
More information about the Red5devs
mailing list