summaryrefslogtreecommitdiff
path: root/isoparser/src/main/java/com/googlecode/mp4parser/authoring
diff options
context:
space:
mode:
Diffstat (limited to 'isoparser/src/main/java/com/googlecode/mp4parser/authoring')
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/all-wcprops41
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/entries244
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/text-base/AbstractTrack.java.svn-base60
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/text-base/DateHelper.java.svn-base44
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/text-base/Movie.java.svn-base91
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/text-base/Mp4TrackImpl.java.svn-base219
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/text-base/Track.java.svn-base60
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/text-base/TrackMetaData.java.svn-base130
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/AbstractTrack.java60
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/DateHelper.java44
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/Movie.java91
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/Mp4TrackImpl.java219
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/Track.java60
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/TrackMetaData.java130
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/all-wcprops47
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/entries266
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/text-base/AbstractManifestWriter.java.svn-base126
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/text-base/AudioQuality.java.svn-base29
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/text-base/FlatManifestWriterImpl.java.svn-base643
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/text-base/FlatPackageWriterImpl.java.svn-base197
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/text-base/ManifestWriter.java.svn-base31
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/text-base/PackageWriter.java.svn-base27
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/text-base/VideoQuality.java.svn-base25
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/AbstractManifestWriter.java126
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/AudioQuality.java29
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/FlatManifestWriterImpl.java643
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/FlatPackageWriterImpl.java197
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/ManifestWriter.java31
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/PackageWriter.java27
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/VideoQuality.java25
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/all-wcprops47
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/entries266
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/ByteBufferHelper.java.svn-base50
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/DefaultMp4Builder.java.svn-base576
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/FragmentIntersectionFinder.java.svn-base34
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/FragmentedMp4Builder.java.svn-base742
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/Mp4Builder.java.svn-base35
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/SyncSampleIntersectFinderImpl.java.svn-base334
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/TwoSecondIntersectionFinder.java.svn-base86
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/ByteBufferHelper.java50
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/DefaultMp4Builder.java576
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/FragmentIntersectionFinder.java34
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/FragmentedMp4Builder.java742
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/Mp4Builder.java35
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/SyncSampleIntersectFinderImpl.java334
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/TwoSecondIntersectionFinder.java86
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/container/.svn/all-wcprops5
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/container/.svn/entries31
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/container/mp4/.svn/all-wcprops11
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/container/mp4/.svn/entries62
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/container/mp4/.svn/text-base/MovieCreator.java.svn-base40
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/container/mp4/MovieCreator.java40
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/all-wcprops89
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/entries504
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/AACTrackImpl.java.svn-base292
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/AC3TrackImpl.java.svn-base513
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/Amf0Track.java.svn-base116
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/AppendTrack.java.svn-base348
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/ChangeTimeScaleTrack.java.svn-base203
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/CroppedTrack.java.svn-base151
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/DivideTimeScaleTrack.java.svn-base126
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/EC3TrackImpl.java.svn-base436
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/H264TrackImpl.java.svn-base740
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/MultiplyTimeScaleTrack.java.svn-base130
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/QuicktimeTextTrackImpl.java.svn-base165
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/ReplaceSampleTrack.java.svn-base104
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/SilenceTrackImpl.java.svn-base98
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/TextTrackImpl.java.svn-base165
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/AACTrackImpl.java292
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/AC3TrackImpl.java513
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/Amf0Track.java116
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/AppendTrack.java348
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/ChangeTimeScaleTrack.java203
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/CroppedTrack.java151
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/DivideTimeScaleTrack.java126
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/EC3TrackImpl.java436
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/H264TrackImpl.java740
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/MultiplyTimeScaleTrack.java130
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/QuicktimeTextTrackImpl.java165
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/ReplaceSampleTrack.java104
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/SilenceTrackImpl.java98
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/TextTrackImpl.java165
82 files changed, 15945 insertions, 0 deletions
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/all-wcprops b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/all-wcprops
new file mode 100644
index 0000000..89054c9
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/all-wcprops
@@ -0,0 +1,41 @@
+K 25
+svn:wc:ra_dav:version-url
+V 82
+/svn/!svn/ver/776/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring
+END
+Movie.java
+K 25
+svn:wc:ra_dav:version-url
+V 93
+/svn/!svn/ver/514/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/Movie.java
+END
+Track.java
+K 25
+svn:wc:ra_dav:version-url
+V 93
+/svn/!svn/ver/686/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/Track.java
+END
+TrackMetaData.java
+K 25
+svn:wc:ra_dav:version-url
+V 101
+/svn/!svn/ver/745/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/TrackMetaData.java
+END
+Mp4TrackImpl.java
+K 25
+svn:wc:ra_dav:version-url
+V 100
+/svn/!svn/ver/765/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/Mp4TrackImpl.java
+END
+AbstractTrack.java
+K 25
+svn:wc:ra_dav:version-url
+V 101
+/svn/!svn/ver/418/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/AbstractTrack.java
+END
+DateHelper.java
+K 25
+svn:wc:ra_dav:version-url
+V 98
+/svn/!svn/ver/418/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/DateHelper.java
+END
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/entries b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/entries
new file mode 100644
index 0000000..7d2b29e
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/entries
@@ -0,0 +1,244 @@
+10
+
+dir
+778
+http://mp4parser.googlecode.com/svn/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring
+http://mp4parser.googlecode.com/svn
+
+
+
+2012-09-10T14:34:23.574807Z
+776
+sebastian.annies@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+7decde4b-c250-0410-a0da-51896bc88be6
+
+Movie.java
+file
+
+
+
+
+2012-09-14T17:27:50.517219Z
+e3a56133cfdfacb92ed0a54177e847b4
+2012-04-22T10:09:06.632613Z
+514
+Sebastian.Annies@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+2558
+
+container
+dir
+
+Track.java
+file
+
+
+
+
+2012-09-14T17:27:50.517219Z
+9537fa79b71fe26727e56e84e94bbdb8
+2012-06-24T19:52:05.961412Z
+686
+Sebastian.Annies@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1607
+
+TrackMetaData.java
+file
+
+
+
+
+2012-09-14T17:27:50.527219Z
+cbb770cca0ee421026eec0a2f40d2376
+2012-08-14T19:18:50.777750Z
+745
+Sebastian.Annies@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+2948
+
+builder
+dir
+
+adaptivestreaming
+dir
+
+tracks
+dir
+
+Mp4TrackImpl.java
+file
+
+
+
+
+2012-09-14T17:27:50.527219Z
+c57930172e9d0da9e881d8dc8ecf2924
+2012-08-29T08:26:56.932482Z
+765
+michael.stattmann@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+10958
+
+AbstractTrack.java
+file
+
+
+
+
+2012-09-14T17:27:50.527219Z
+973f4f354fb6f575dd1a0c8a68d54653
+2012-03-11T20:54:45.638478Z
+418
+Sebastian.Annies@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1481
+
+DateHelper.java
+file
+
+
+
+
+2012-09-14T17:27:50.527219Z
+765e3f37d7bb369f569aa91e326a90b8
+2012-03-11T20:54:45.638478Z
+418
+Sebastian.Annies@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1349
+
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/text-base/AbstractTrack.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/text-base/AbstractTrack.java.svn-base
new file mode 100644
index 0000000..fb0e224
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/text-base/AbstractTrack.java.svn-base
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.googlecode.mp4parser.authoring;
+
+/**
+ *
+ */
+public abstract class AbstractTrack implements Track {
+ private boolean enabled = true;
+ private boolean inMovie = true;
+ private boolean inPreview = true;
+ private boolean inPoster = true;
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public boolean isInMovie() {
+ return inMovie;
+ }
+
+ public boolean isInPreview() {
+ return inPreview;
+ }
+
+ public boolean isInPoster() {
+ return inPoster;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public void setInMovie(boolean inMovie) {
+ this.inMovie = inMovie;
+ }
+
+ public void setInPreview(boolean inPreview) {
+ this.inPreview = inPreview;
+ }
+
+ public void setInPoster(boolean inPoster) {
+ this.inPoster = inPoster;
+ }
+
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/text-base/DateHelper.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/text-base/DateHelper.java.svn-base
new file mode 100644
index 0000000..0252859
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/text-base/DateHelper.java.svn-base
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring;
+
+import java.util.Date;
+
+/**
+ * Converts ISO Dates (seconds since 1/1/1904) to Date and vice versa.
+ */
+public class DateHelper {
+ /**
+ * Converts a long value with seconds since 1/1/1904 to Date.
+ *
+ * @param secondsSince seconds since 1/1/1904
+ * @return date the corresponding <code>Date</code>
+ */
+ static public Date convert(long secondsSince) {
+ return new Date((secondsSince - 2082844800L) * 1000L);
+ }
+
+
+ /**
+ * Converts a date as long to a mac date as long
+ *
+ * @param date date to convert
+ * @return date in mac format
+ */
+ static public long convert(Date date) {
+ return (date.getTime() / 1000L) + 2082844800L;
+ }
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/text-base/Movie.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/text-base/Movie.java.svn-base
new file mode 100644
index 0000000..0658682
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/text-base/Movie.java.svn-base
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ *
+ */
+public class Movie {
+ List<Track> tracks = new LinkedList<Track>();
+
+ public List<Track> getTracks() {
+ return tracks;
+ }
+
+ public void setTracks(List<Track> tracks) {
+ this.tracks = tracks;
+ }
+
+ public void addTrack(Track nuTrack) {
+ // do some checking
+ // perhaps the movie needs to get longer!
+ if (getTrackByTrackId(nuTrack.getTrackMetaData().getTrackId()) != null) {
+ // We already have a track with that trackId. Create a new one
+ nuTrack.getTrackMetaData().setTrackId(getNextTrackId());
+ }
+ tracks.add(nuTrack);
+ }
+
+
+ @Override
+ public String toString() {
+ String s = "Movie{ ";
+ for (Track track : tracks) {
+ s += "track_" + track.getTrackMetaData().getTrackId() + " (" + track.getHandler() + ") ";
+ }
+
+ s += '}';
+ return s;
+ }
+
+ public long getNextTrackId() {
+ long nextTrackId = 0;
+ for (Track track : tracks) {
+ nextTrackId = nextTrackId < track.getTrackMetaData().getTrackId() ? track.getTrackMetaData().getTrackId() : nextTrackId;
+ }
+ return ++nextTrackId;
+ }
+
+
+ public Track getTrackByTrackId(long trackId) {
+ for (Track track : tracks) {
+ if (track.getTrackMetaData().getTrackId() == trackId) {
+ return track;
+ }
+ }
+ return null;
+ }
+
+
+ public long getTimescale() {
+ long timescale = this.getTracks().iterator().next().getTrackMetaData().getTimescale();
+ for (Track track : this.getTracks()) {
+ timescale = gcd(track.getTrackMetaData().getTimescale(), timescale);
+ }
+ return timescale;
+ }
+
+ public static long gcd(long a, long b) {
+ if (b == 0) {
+ return a;
+ }
+ return gcd(b, a % b);
+ }
+
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/text-base/Mp4TrackImpl.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/text-base/Mp4TrackImpl.java.svn-base
new file mode 100644
index 0000000..3bff1a5
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/text-base/Mp4TrackImpl.java.svn-base
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring;
+
+import com.coremedia.iso.boxes.*;
+import com.coremedia.iso.boxes.fragment.*;
+import com.coremedia.iso.boxes.mdat.SampleList;
+
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import static com.googlecode.mp4parser.util.CastUtils.l2i;
+
+/**
+ * Represents a single track of an MP4 file.
+ */
+public class Mp4TrackImpl extends AbstractTrack {
+ private List<ByteBuffer> samples;
+ private SampleDescriptionBox sampleDescriptionBox;
+ private List<TimeToSampleBox.Entry> decodingTimeEntries;
+ private List<CompositionTimeToSample.Entry> compositionTimeEntries;
+ private long[] syncSamples = new long[0];
+ private List<SampleDependencyTypeBox.Entry> sampleDependencies;
+ private TrackMetaData trackMetaData = new TrackMetaData();
+ private String handler;
+ private AbstractMediaHeaderBox mihd;
+
+ public Mp4TrackImpl(TrackBox trackBox) {
+ final long trackId = trackBox.getTrackHeaderBox().getTrackId();
+ samples = new SampleList(trackBox);
+ SampleTableBox stbl = trackBox.getMediaBox().getMediaInformationBox().getSampleTableBox();
+ handler = trackBox.getMediaBox().getHandlerBox().getHandlerType();
+
+ mihd = trackBox.getMediaBox().getMediaInformationBox().getMediaHeaderBox();
+ decodingTimeEntries = new LinkedList<TimeToSampleBox.Entry>();
+ compositionTimeEntries = new LinkedList<CompositionTimeToSample.Entry>();
+ sampleDependencies = new LinkedList<SampleDependencyTypeBox.Entry>();
+
+ decodingTimeEntries.addAll(stbl.getTimeToSampleBox().getEntries());
+ if (stbl.getCompositionTimeToSample() != null) {
+ compositionTimeEntries.addAll(stbl.getCompositionTimeToSample().getEntries());
+ }
+ if (stbl.getSampleDependencyTypeBox() != null) {
+ sampleDependencies.addAll(stbl.getSampleDependencyTypeBox().getEntries());
+ }
+ if (stbl.getSyncSampleBox() != null) {
+ syncSamples = stbl.getSyncSampleBox().getSampleNumber();
+ }
+
+
+ sampleDescriptionBox = stbl.getSampleDescriptionBox();
+ final List<MovieExtendsBox> movieExtendsBoxes = trackBox.getParent().getBoxes(MovieExtendsBox.class);
+ if (movieExtendsBoxes.size() > 0) {
+ for (MovieExtendsBox mvex : movieExtendsBoxes) {
+ final List<TrackExtendsBox> trackExtendsBoxes = mvex.getBoxes(TrackExtendsBox.class);
+ for (TrackExtendsBox trex : trackExtendsBoxes) {
+ if (trex.getTrackId() == trackId) {
+ List<Long> syncSampleList = new LinkedList<Long>();
+
+ long sampleNumber = 1;
+ for (MovieFragmentBox movieFragmentBox : trackBox.getIsoFile().getBoxes(MovieFragmentBox.class)) {
+ List<TrackFragmentBox> trafs = movieFragmentBox.getBoxes(TrackFragmentBox.class);
+ for (TrackFragmentBox traf : trafs) {
+ if (traf.getTrackFragmentHeaderBox().getTrackId() == trackId) {
+ List<TrackRunBox> truns = traf.getBoxes(TrackRunBox.class);
+ for (TrackRunBox trun : truns) {
+ final TrackFragmentHeaderBox tfhd = ((TrackFragmentBox) trun.getParent()).getTrackFragmentHeaderBox();
+ boolean first = true;
+ for (TrackRunBox.Entry entry : trun.getEntries()) {
+ if (trun.isSampleDurationPresent()) {
+ if (decodingTimeEntries.size() == 0 ||
+ decodingTimeEntries.get(decodingTimeEntries.size() - 1).getDelta() != entry.getSampleDuration()) {
+ decodingTimeEntries.add(new TimeToSampleBox.Entry(1, entry.getSampleDuration()));
+ } else {
+ TimeToSampleBox.Entry e = decodingTimeEntries.get(decodingTimeEntries.size() - 1);
+ e.setCount(e.getCount() + 1);
+ }
+ } else {
+ if (tfhd.hasDefaultSampleDuration()) {
+ decodingTimeEntries.add(new TimeToSampleBox.Entry(1, tfhd.getDefaultSampleDuration()));
+ } else {
+ decodingTimeEntries.add(new TimeToSampleBox.Entry(1, trex.getDefaultSampleDuration()));
+ }
+ }
+
+ if (trun.isSampleCompositionTimeOffsetPresent()) {
+ if (compositionTimeEntries.size() == 0 ||
+ compositionTimeEntries.get(compositionTimeEntries.size() - 1).getOffset() != entry.getSampleCompositionTimeOffset()) {
+ compositionTimeEntries.add(new CompositionTimeToSample.Entry(1, l2i(entry.getSampleCompositionTimeOffset())));
+ } else {
+ CompositionTimeToSample.Entry e = compositionTimeEntries.get(compositionTimeEntries.size() - 1);
+ e.setCount(e.getCount() + 1);
+ }
+ }
+ final SampleFlags sampleFlags;
+ if (trun.isSampleFlagsPresent()) {
+ sampleFlags = entry.getSampleFlags();
+ } else {
+ if (first && trun.isFirstSampleFlagsPresent()) {
+ sampleFlags = trun.getFirstSampleFlags();
+ } else {
+ if (tfhd.hasDefaultSampleFlags()) {
+ sampleFlags = tfhd.getDefaultSampleFlags();
+ } else {
+ sampleFlags = trex.getDefaultSampleFlags();
+ }
+ }
+ }
+ if (sampleFlags != null && !sampleFlags.isSampleIsDifferenceSample()) {
+ //iframe
+ syncSampleList.add(sampleNumber);
+ }
+ sampleNumber++;
+ first = false;
+ }
+ }
+ }
+ }
+ }
+ // Warning: Crappy code
+ long[] oldSS = syncSamples;
+ syncSamples = new long[syncSamples.length + syncSampleList.size()];
+ System.arraycopy(oldSS, 0, syncSamples, 0, oldSS.length);
+ final Iterator<Long> iterator = syncSampleList.iterator();
+ int i = oldSS.length;
+ while (iterator.hasNext()) {
+ Long syncSampleNumber = iterator.next();
+ syncSamples[i++] = syncSampleNumber;
+ }
+ }
+ }
+ }
+ }
+ MediaHeaderBox mdhd = trackBox.getMediaBox().getMediaHeaderBox();
+ TrackHeaderBox tkhd = trackBox.getTrackHeaderBox();
+
+ setEnabled(tkhd.isEnabled());
+ setInMovie(tkhd.isInMovie());
+ setInPoster(tkhd.isInPoster());
+ setInPreview(tkhd.isInPreview());
+
+ trackMetaData.setTrackId(tkhd.getTrackId());
+ trackMetaData.setCreationTime(DateHelper.convert(mdhd.getCreationTime()));
+ trackMetaData.setLanguage(mdhd.getLanguage());
+/* System.err.println(mdhd.getModificationTime());
+ System.err.println(DateHelper.convert(mdhd.getModificationTime()));
+ System.err.println(DateHelper.convert(DateHelper.convert(mdhd.getModificationTime())));
+ System.err.println(DateHelper.convert(DateHelper.convert(DateHelper.convert(mdhd.getModificationTime()))));*/
+
+ trackMetaData.setModificationTime(DateHelper.convert(mdhd.getModificationTime()));
+ trackMetaData.setTimescale(mdhd.getTimescale());
+ trackMetaData.setHeight(tkhd.getHeight());
+ trackMetaData.setWidth(tkhd.getWidth());
+ trackMetaData.setLayer(tkhd.getLayer());
+ }
+
+ public List<ByteBuffer> getSamples() {
+ return samples;
+ }
+
+
+ public SampleDescriptionBox getSampleDescriptionBox() {
+ return sampleDescriptionBox;
+ }
+
+ public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
+ return decodingTimeEntries;
+ }
+
+ public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
+ return compositionTimeEntries;
+ }
+
+ public long[] getSyncSamples() {
+ return syncSamples;
+ }
+
+ public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
+ return sampleDependencies;
+ }
+
+ public TrackMetaData getTrackMetaData() {
+ return trackMetaData;
+ }
+
+ public String getHandler() {
+ return handler;
+ }
+
+ public AbstractMediaHeaderBox getMediaHeaderBox() {
+ return mihd;
+ }
+
+ public SubSampleInformationBox getSubsampleInformationBox() {
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "Mp4TrackImpl{" +
+ "handler='" + handler + '\'' +
+ '}';
+ }
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/text-base/Track.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/text-base/Track.java.svn-base
new file mode 100644
index 0000000..1f4b363
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/text-base/Track.java.svn-base
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring;
+
+import com.coremedia.iso.boxes.*;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+/**
+ * Represents a Track. A track is a timed sequence of related samples.
+ * <p/>
+ * <b>NOTE: </b><br/
+ * For media data, a track corresponds to a sequence of images or sampled audio; for hint tracks, a track
+ * corresponds to a streaming channel.
+ */
+public interface Track {
+
+ SampleDescriptionBox getSampleDescriptionBox();
+
+ List<TimeToSampleBox.Entry> getDecodingTimeEntries();
+
+ List<CompositionTimeToSample.Entry> getCompositionTimeEntries();
+
+ long[] getSyncSamples();
+
+ List<SampleDependencyTypeBox.Entry> getSampleDependencies();
+
+ TrackMetaData getTrackMetaData();
+
+ String getHandler();
+
+ boolean isEnabled();
+
+ boolean isInMovie();
+
+ boolean isInPreview();
+
+ boolean isInPoster();
+
+ List<ByteBuffer> getSamples();
+
+ public Box getMediaHeaderBox();
+
+ public SubSampleInformationBox getSubsampleInformationBox();
+
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/text-base/TrackMetaData.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/text-base/TrackMetaData.java.svn-base
new file mode 100644
index 0000000..c262309
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/.svn/text-base/TrackMetaData.java.svn-base
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring;
+
+import java.util.Date;
+
+/**
+ *
+ */
+public class TrackMetaData implements Cloneable {
+ private String language;
+ private long timescale;
+ private Date modificationTime = new Date();
+ private Date creationTime = new Date();
+ private double width;
+ private double height;
+ private float volume;
+ private long trackId = 1; // zero is not allowed
+ private int group = 0;
+
+
+ /**
+ * specifies the front-to-back ordering of video tracks; tracks with lower
+ * numbers are closer to the viewer. 0 is the normal value, and -1 would be
+ * in front of track 0, and so on.
+ */
+ int layer;
+
+ public String getLanguage() {
+ return language;
+ }
+
+ public void setLanguage(String language) {
+ this.language = language;
+ }
+
+ public long getTimescale() {
+ return timescale;
+ }
+
+ public void setTimescale(long timescale) {
+ this.timescale = timescale;
+ }
+
+ public Date getModificationTime() {
+ return modificationTime;
+ }
+
+ public void setModificationTime(Date modificationTime) {
+ this.modificationTime = modificationTime;
+ }
+
+ public Date getCreationTime() {
+ return creationTime;
+ }
+
+ public void setCreationTime(Date creationTime) {
+ this.creationTime = creationTime;
+ }
+
+ public double getWidth() {
+ return width;
+ }
+
+ public void setWidth(double width) {
+ this.width = width;
+ }
+
+ public double getHeight() {
+ return height;
+ }
+
+ public void setHeight(double height) {
+ this.height = height;
+ }
+
+ public long getTrackId() {
+ return trackId;
+ }
+
+ public void setTrackId(long trackId) {
+ this.trackId = trackId;
+ }
+
+ public int getLayer() {
+ return layer;
+ }
+
+ public void setLayer(int layer) {
+ this.layer = layer;
+ }
+
+ public float getVolume() {
+ return volume;
+ }
+
+ public void setVolume(float volume) {
+ this.volume = volume;
+ }
+
+ public int getGroup() {
+ return group;
+ }
+
+ public void setGroup(int group) {
+ this.group = group;
+ }
+
+ public Object clone() {
+ try {
+ return super.clone();
+ } catch (CloneNotSupportedException e) {
+ return null;
+ }
+ }
+
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/AbstractTrack.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/AbstractTrack.java
new file mode 100644
index 0000000..fb0e224
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/AbstractTrack.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.googlecode.mp4parser.authoring;
+
+/**
+ *
+ */
+public abstract class AbstractTrack implements Track {
+ private boolean enabled = true;
+ private boolean inMovie = true;
+ private boolean inPreview = true;
+ private boolean inPoster = true;
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public boolean isInMovie() {
+ return inMovie;
+ }
+
+ public boolean isInPreview() {
+ return inPreview;
+ }
+
+ public boolean isInPoster() {
+ return inPoster;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public void setInMovie(boolean inMovie) {
+ this.inMovie = inMovie;
+ }
+
+ public void setInPreview(boolean inPreview) {
+ this.inPreview = inPreview;
+ }
+
+ public void setInPoster(boolean inPoster) {
+ this.inPoster = inPoster;
+ }
+
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/DateHelper.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/DateHelper.java
new file mode 100644
index 0000000..0252859
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/DateHelper.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring;
+
+import java.util.Date;
+
+/**
+ * Converts ISO Dates (seconds since 1/1/1904) to Date and vice versa.
+ */
+public class DateHelper {
+ /**
+ * Converts a long value with seconds since 1/1/1904 to Date.
+ *
+ * @param secondsSince seconds since 1/1/1904
+ * @return date the corresponding <code>Date</code>
+ */
+ static public Date convert(long secondsSince) {
+ return new Date((secondsSince - 2082844800L) * 1000L);
+ }
+
+
+ /**
+ * Converts a date as long to a mac date as long
+ *
+ * @param date date to convert
+ * @return date in mac format
+ */
+ static public long convert(Date date) {
+ return (date.getTime() / 1000L) + 2082844800L;
+ }
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/Movie.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/Movie.java
new file mode 100644
index 0000000..0658682
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/Movie.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ *
+ */
+public class Movie {
+ List<Track> tracks = new LinkedList<Track>();
+
+ public List<Track> getTracks() {
+ return tracks;
+ }
+
+ public void setTracks(List<Track> tracks) {
+ this.tracks = tracks;
+ }
+
+ public void addTrack(Track nuTrack) {
+ // do some checking
+ // perhaps the movie needs to get longer!
+ if (getTrackByTrackId(nuTrack.getTrackMetaData().getTrackId()) != null) {
+ // We already have a track with that trackId. Create a new one
+ nuTrack.getTrackMetaData().setTrackId(getNextTrackId());
+ }
+ tracks.add(nuTrack);
+ }
+
+
+ @Override
+ public String toString() {
+ String s = "Movie{ ";
+ for (Track track : tracks) {
+ s += "track_" + track.getTrackMetaData().getTrackId() + " (" + track.getHandler() + ") ";
+ }
+
+ s += '}';
+ return s;
+ }
+
+ public long getNextTrackId() {
+ long nextTrackId = 0;
+ for (Track track : tracks) {
+ nextTrackId = nextTrackId < track.getTrackMetaData().getTrackId() ? track.getTrackMetaData().getTrackId() : nextTrackId;
+ }
+ return ++nextTrackId;
+ }
+
+
+ public Track getTrackByTrackId(long trackId) {
+ for (Track track : tracks) {
+ if (track.getTrackMetaData().getTrackId() == trackId) {
+ return track;
+ }
+ }
+ return null;
+ }
+
+
+ public long getTimescale() {
+ long timescale = this.getTracks().iterator().next().getTrackMetaData().getTimescale();
+ for (Track track : this.getTracks()) {
+ timescale = gcd(track.getTrackMetaData().getTimescale(), timescale);
+ }
+ return timescale;
+ }
+
+ public static long gcd(long a, long b) {
+ if (b == 0) {
+ return a;
+ }
+ return gcd(b, a % b);
+ }
+
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/Mp4TrackImpl.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/Mp4TrackImpl.java
new file mode 100644
index 0000000..3bff1a5
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/Mp4TrackImpl.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring;
+
+import com.coremedia.iso.boxes.*;
+import com.coremedia.iso.boxes.fragment.*;
+import com.coremedia.iso.boxes.mdat.SampleList;
+
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import static com.googlecode.mp4parser.util.CastUtils.l2i;
+
+/**
+ * Represents a single track of an MP4 file.
+ */
+public class Mp4TrackImpl extends AbstractTrack {
+ private List<ByteBuffer> samples;
+ private SampleDescriptionBox sampleDescriptionBox;
+ private List<TimeToSampleBox.Entry> decodingTimeEntries;
+ private List<CompositionTimeToSample.Entry> compositionTimeEntries;
+ private long[] syncSamples = new long[0];
+ private List<SampleDependencyTypeBox.Entry> sampleDependencies;
+ private TrackMetaData trackMetaData = new TrackMetaData();
+ private String handler;
+ private AbstractMediaHeaderBox mihd;
+
+ public Mp4TrackImpl(TrackBox trackBox) {
+ final long trackId = trackBox.getTrackHeaderBox().getTrackId();
+ samples = new SampleList(trackBox);
+ SampleTableBox stbl = trackBox.getMediaBox().getMediaInformationBox().getSampleTableBox();
+ handler = trackBox.getMediaBox().getHandlerBox().getHandlerType();
+
+ mihd = trackBox.getMediaBox().getMediaInformationBox().getMediaHeaderBox();
+ decodingTimeEntries = new LinkedList<TimeToSampleBox.Entry>();
+ compositionTimeEntries = new LinkedList<CompositionTimeToSample.Entry>();
+ sampleDependencies = new LinkedList<SampleDependencyTypeBox.Entry>();
+
+ decodingTimeEntries.addAll(stbl.getTimeToSampleBox().getEntries());
+ if (stbl.getCompositionTimeToSample() != null) {
+ compositionTimeEntries.addAll(stbl.getCompositionTimeToSample().getEntries());
+ }
+ if (stbl.getSampleDependencyTypeBox() != null) {
+ sampleDependencies.addAll(stbl.getSampleDependencyTypeBox().getEntries());
+ }
+ if (stbl.getSyncSampleBox() != null) {
+ syncSamples = stbl.getSyncSampleBox().getSampleNumber();
+ }
+
+
+ sampleDescriptionBox = stbl.getSampleDescriptionBox();
+ final List<MovieExtendsBox> movieExtendsBoxes = trackBox.getParent().getBoxes(MovieExtendsBox.class);
+ if (movieExtendsBoxes.size() > 0) {
+ for (MovieExtendsBox mvex : movieExtendsBoxes) {
+ final List<TrackExtendsBox> trackExtendsBoxes = mvex.getBoxes(TrackExtendsBox.class);
+ for (TrackExtendsBox trex : trackExtendsBoxes) {
+ if (trex.getTrackId() == trackId) {
+ List<Long> syncSampleList = new LinkedList<Long>();
+
+ long sampleNumber = 1;
+ for (MovieFragmentBox movieFragmentBox : trackBox.getIsoFile().getBoxes(MovieFragmentBox.class)) {
+ List<TrackFragmentBox> trafs = movieFragmentBox.getBoxes(TrackFragmentBox.class);
+ for (TrackFragmentBox traf : trafs) {
+ if (traf.getTrackFragmentHeaderBox().getTrackId() == trackId) {
+ List<TrackRunBox> truns = traf.getBoxes(TrackRunBox.class);
+ for (TrackRunBox trun : truns) {
+ final TrackFragmentHeaderBox tfhd = ((TrackFragmentBox) trun.getParent()).getTrackFragmentHeaderBox();
+ boolean first = true;
+ for (TrackRunBox.Entry entry : trun.getEntries()) {
+ if (trun.isSampleDurationPresent()) {
+ if (decodingTimeEntries.size() == 0 ||
+ decodingTimeEntries.get(decodingTimeEntries.size() - 1).getDelta() != entry.getSampleDuration()) {
+ decodingTimeEntries.add(new TimeToSampleBox.Entry(1, entry.getSampleDuration()));
+ } else {
+ TimeToSampleBox.Entry e = decodingTimeEntries.get(decodingTimeEntries.size() - 1);
+ e.setCount(e.getCount() + 1);
+ }
+ } else {
+ if (tfhd.hasDefaultSampleDuration()) {
+ decodingTimeEntries.add(new TimeToSampleBox.Entry(1, tfhd.getDefaultSampleDuration()));
+ } else {
+ decodingTimeEntries.add(new TimeToSampleBox.Entry(1, trex.getDefaultSampleDuration()));
+ }
+ }
+
+ if (trun.isSampleCompositionTimeOffsetPresent()) {
+ if (compositionTimeEntries.size() == 0 ||
+ compositionTimeEntries.get(compositionTimeEntries.size() - 1).getOffset() != entry.getSampleCompositionTimeOffset()) {
+ compositionTimeEntries.add(new CompositionTimeToSample.Entry(1, l2i(entry.getSampleCompositionTimeOffset())));
+ } else {
+ CompositionTimeToSample.Entry e = compositionTimeEntries.get(compositionTimeEntries.size() - 1);
+ e.setCount(e.getCount() + 1);
+ }
+ }
+ final SampleFlags sampleFlags;
+ if (trun.isSampleFlagsPresent()) {
+ sampleFlags = entry.getSampleFlags();
+ } else {
+ if (first && trun.isFirstSampleFlagsPresent()) {
+ sampleFlags = trun.getFirstSampleFlags();
+ } else {
+ if (tfhd.hasDefaultSampleFlags()) {
+ sampleFlags = tfhd.getDefaultSampleFlags();
+ } else {
+ sampleFlags = trex.getDefaultSampleFlags();
+ }
+ }
+ }
+ if (sampleFlags != null && !sampleFlags.isSampleIsDifferenceSample()) {
+ //iframe
+ syncSampleList.add(sampleNumber);
+ }
+ sampleNumber++;
+ first = false;
+ }
+ }
+ }
+ }
+ }
+ // Warning: Crappy code
+ long[] oldSS = syncSamples;
+ syncSamples = new long[syncSamples.length + syncSampleList.size()];
+ System.arraycopy(oldSS, 0, syncSamples, 0, oldSS.length);
+ final Iterator<Long> iterator = syncSampleList.iterator();
+ int i = oldSS.length;
+ while (iterator.hasNext()) {
+ Long syncSampleNumber = iterator.next();
+ syncSamples[i++] = syncSampleNumber;
+ }
+ }
+ }
+ }
+ }
+ MediaHeaderBox mdhd = trackBox.getMediaBox().getMediaHeaderBox();
+ TrackHeaderBox tkhd = trackBox.getTrackHeaderBox();
+
+ setEnabled(tkhd.isEnabled());
+ setInMovie(tkhd.isInMovie());
+ setInPoster(tkhd.isInPoster());
+ setInPreview(tkhd.isInPreview());
+
+ trackMetaData.setTrackId(tkhd.getTrackId());
+ trackMetaData.setCreationTime(DateHelper.convert(mdhd.getCreationTime()));
+ trackMetaData.setLanguage(mdhd.getLanguage());
+/* System.err.println(mdhd.getModificationTime());
+ System.err.println(DateHelper.convert(mdhd.getModificationTime()));
+ System.err.println(DateHelper.convert(DateHelper.convert(mdhd.getModificationTime())));
+ System.err.println(DateHelper.convert(DateHelper.convert(DateHelper.convert(mdhd.getModificationTime()))));*/
+
+ trackMetaData.setModificationTime(DateHelper.convert(mdhd.getModificationTime()));
+ trackMetaData.setTimescale(mdhd.getTimescale());
+ trackMetaData.setHeight(tkhd.getHeight());
+ trackMetaData.setWidth(tkhd.getWidth());
+ trackMetaData.setLayer(tkhd.getLayer());
+ }
+
+ public List<ByteBuffer> getSamples() {
+ return samples;
+ }
+
+
+ public SampleDescriptionBox getSampleDescriptionBox() {
+ return sampleDescriptionBox;
+ }
+
+ public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
+ return decodingTimeEntries;
+ }
+
+ public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
+ return compositionTimeEntries;
+ }
+
+ public long[] getSyncSamples() {
+ return syncSamples;
+ }
+
+ public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
+ return sampleDependencies;
+ }
+
+ public TrackMetaData getTrackMetaData() {
+ return trackMetaData;
+ }
+
+ public String getHandler() {
+ return handler;
+ }
+
+ public AbstractMediaHeaderBox getMediaHeaderBox() {
+ return mihd;
+ }
+
+ public SubSampleInformationBox getSubsampleInformationBox() {
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "Mp4TrackImpl{" +
+ "handler='" + handler + '\'' +
+ '}';
+ }
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/Track.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/Track.java
new file mode 100644
index 0000000..1f4b363
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/Track.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring;
+
+import com.coremedia.iso.boxes.*;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+/**
+ * Represents a Track. A track is a timed sequence of related samples.
+ * <p/>
+ * <b>NOTE: </b><br/
+ * For media data, a track corresponds to a sequence of images or sampled audio; for hint tracks, a track
+ * corresponds to a streaming channel.
+ */
+public interface Track {
+
+ SampleDescriptionBox getSampleDescriptionBox();
+
+ List<TimeToSampleBox.Entry> getDecodingTimeEntries();
+
+ List<CompositionTimeToSample.Entry> getCompositionTimeEntries();
+
+ long[] getSyncSamples();
+
+ List<SampleDependencyTypeBox.Entry> getSampleDependencies();
+
+ TrackMetaData getTrackMetaData();
+
+ String getHandler();
+
+ boolean isEnabled();
+
+ boolean isInMovie();
+
+ boolean isInPreview();
+
+ boolean isInPoster();
+
+ List<ByteBuffer> getSamples();
+
+ public Box getMediaHeaderBox();
+
+ public SubSampleInformationBox getSubsampleInformationBox();
+
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/TrackMetaData.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/TrackMetaData.java
new file mode 100644
index 0000000..c262309
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/TrackMetaData.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring;
+
+import java.util.Date;
+
+/**
+ *
+ */
+public class TrackMetaData implements Cloneable {
+ private String language;
+ private long timescale;
+ private Date modificationTime = new Date();
+ private Date creationTime = new Date();
+ private double width;
+ private double height;
+ private float volume;
+ private long trackId = 1; // zero is not allowed
+ private int group = 0;
+
+
+ /**
+ * specifies the front-to-back ordering of video tracks; tracks with lower
+ * numbers are closer to the viewer. 0 is the normal value, and -1 would be
+ * in front of track 0, and so on.
+ */
+ int layer;
+
+ public String getLanguage() {
+ return language;
+ }
+
+ public void setLanguage(String language) {
+ this.language = language;
+ }
+
+ public long getTimescale() {
+ return timescale;
+ }
+
+ public void setTimescale(long timescale) {
+ this.timescale = timescale;
+ }
+
+ public Date getModificationTime() {
+ return modificationTime;
+ }
+
+ public void setModificationTime(Date modificationTime) {
+ this.modificationTime = modificationTime;
+ }
+
+ public Date getCreationTime() {
+ return creationTime;
+ }
+
+ public void setCreationTime(Date creationTime) {
+ this.creationTime = creationTime;
+ }
+
+ public double getWidth() {
+ return width;
+ }
+
+ public void setWidth(double width) {
+ this.width = width;
+ }
+
+ public double getHeight() {
+ return height;
+ }
+
+ public void setHeight(double height) {
+ this.height = height;
+ }
+
+ public long getTrackId() {
+ return trackId;
+ }
+
+ public void setTrackId(long trackId) {
+ this.trackId = trackId;
+ }
+
+ public int getLayer() {
+ return layer;
+ }
+
+ public void setLayer(int layer) {
+ this.layer = layer;
+ }
+
+ public float getVolume() {
+ return volume;
+ }
+
+ public void setVolume(float volume) {
+ this.volume = volume;
+ }
+
+ public int getGroup() {
+ return group;
+ }
+
+ public void setGroup(int group) {
+ this.group = group;
+ }
+
+ public Object clone() {
+ try {
+ return super.clone();
+ } catch (CloneNotSupportedException e) {
+ return null;
+ }
+ }
+
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/all-wcprops b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/all-wcprops
new file mode 100644
index 0000000..7d70c40
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/all-wcprops
@@ -0,0 +1,47 @@
+K 25
+svn:wc:ra_dav:version-url
+V 100
+/svn/!svn/ver/773/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming
+END
+VideoQuality.java
+K 25
+svn:wc:ra_dav:version-url
+V 118
+/svn/!svn/ver/760/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/VideoQuality.java
+END
+FlatPackageWriterImpl.java
+K 25
+svn:wc:ra_dav:version-url
+V 127
+/svn/!svn/ver/760/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/FlatPackageWriterImpl.java
+END
+ManifestWriter.java
+K 25
+svn:wc:ra_dav:version-url
+V 120
+/svn/!svn/ver/755/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/ManifestWriter.java
+END
+AbstractManifestWriter.java
+K 25
+svn:wc:ra_dav:version-url
+V 128
+/svn/!svn/ver/757/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/AbstractManifestWriter.java
+END
+PackageWriter.java
+K 25
+svn:wc:ra_dav:version-url
+V 119
+/svn/!svn/ver/755/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/PackageWriter.java
+END
+AudioQuality.java
+K 25
+svn:wc:ra_dav:version-url
+V 118
+/svn/!svn/ver/760/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/AudioQuality.java
+END
+FlatManifestWriterImpl.java
+K 25
+svn:wc:ra_dav:version-url
+V 128
+/svn/!svn/ver/773/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/FlatManifestWriterImpl.java
+END
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/entries b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/entries
new file mode 100644
index 0000000..619b17c
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/entries
@@ -0,0 +1,266 @@
+10
+
+dir
+778
+http://mp4parser.googlecode.com/svn/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming
+http://mp4parser.googlecode.com/svn
+
+
+
+2012-09-01T21:55:19.768646Z
+773
+michael.stattmann@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+7decde4b-c250-0410-a0da-51896bc88be6
+
+VideoQuality.java
+file
+
+
+
+
+2012-09-14T17:27:50.317216Z
+356fcadf80f684d83b5f30afd5cb26e4
+2012-08-17T15:20:10.783404Z
+760
+Sebastian.Annies@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+807
+
+FlatPackageWriterImpl.java
+file
+
+
+
+
+2012-09-14T17:27:50.317216Z
+f38a8b91e1b8abd48e1ae26b23b060fa
+2012-08-17T15:20:10.783404Z
+760
+Sebastian.Annies@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+8285
+
+ManifestWriter.java
+file
+
+
+
+
+2012-09-14T17:27:50.317216Z
+4fc006c7919c1ab4ed498340dfa133b3
+2012-08-17T01:13:17.213046Z
+755
+michael.stattmann@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+992
+
+AbstractManifestWriter.java
+file
+
+
+
+
+2012-09-14T17:27:50.317216Z
+1ce766c781ae825fb0620a61eb2b2e1c
+2012-08-17T05:55:12.215481Z
+757
+michael.stattmann@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+5030
+
+PackageWriter.java
+file
+
+
+
+
+2012-09-14T17:27:50.317216Z
+ffdb02efc14eeadf6c1ba9c5e500e76c
+2012-08-17T01:13:17.213046Z
+755
+michael.stattmann@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+878
+
+AudioQuality.java
+file
+
+
+
+
+2012-09-14T17:27:50.317216Z
+c2b5ada192ff228aac261452067773fd
+2012-08-17T15:20:10.783404Z
+760
+Sebastian.Annies@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+887
+
+FlatManifestWriterImpl.java
+file
+
+
+
+
+2012-09-14T17:27:50.317216Z
+d45a45107db5f4c43765d95708382310
+2012-09-01T21:55:19.768646Z
+773
+michael.stattmann@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+30095
+
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/text-base/AbstractManifestWriter.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/text-base/AbstractManifestWriter.java.svn-base
new file mode 100644
index 0000000..6ee4ffa
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/text-base/AbstractManifestWriter.java.svn-base
@@ -0,0 +1,126 @@
+package com.googlecode.mp4parser.authoring.adaptivestreaming;
+
+import com.coremedia.iso.boxes.OriginalFormatBox;
+import com.coremedia.iso.boxes.TimeToSampleBox;
+import com.coremedia.iso.boxes.sampleentry.SampleEntry;
+import com.googlecode.mp4parser.authoring.Movie;
+import com.googlecode.mp4parser.authoring.Track;
+import com.googlecode.mp4parser.authoring.builder.FragmentIntersectionFinder;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.logging.Logger;
+
+import static com.googlecode.mp4parser.util.CastUtils.l2i;
+
+/**
+ * Created with IntelliJ IDEA.
+ * User: mstattma
+ * Date: 17.08.12
+ * Time: 02:51
+ * To change this template use File | Settings | File Templates.
+ */
+public abstract class AbstractManifestWriter implements ManifestWriter {
+ private static final Logger LOG = Logger.getLogger(AbstractManifestWriter.class.getName());
+
+ private FragmentIntersectionFinder intersectionFinder;
+ protected long[] audioFragmentsDurations;
+ protected long[] videoFragmentsDurations;
+
+ protected AbstractManifestWriter(FragmentIntersectionFinder intersectionFinder) {
+ this.intersectionFinder = intersectionFinder;
+ }
+
+ /**
+ * Calculates the length of each fragment in the given <code>track</code> (as part of <code>movie</code>).
+ *
+ * @param track target of calculation
+ * @param movie the <code>track</code> must be part of this <code>movie</code>
+ * @return the duration of each fragment in track timescale
+ */
+ public long[] calculateFragmentDurations(Track track, Movie movie) {
+ long[] startSamples = intersectionFinder.sampleNumbers(track, movie);
+ long[] durations = new long[startSamples.length];
+ int currentFragment = 0;
+ int currentSample = 1; // sync samples start with 1 !
+
+ for (TimeToSampleBox.Entry entry : track.getDecodingTimeEntries()) {
+ for (int max = currentSample + l2i(entry.getCount()); currentSample < max; currentSample++) {
+ // in this loop we go through the entry.getCount() samples starting from current sample.
+ // the next entry.getCount() samples have the same decoding time.
+ if (currentFragment != startSamples.length - 1 && currentSample == startSamples[currentFragment + 1]) {
+ // we are not in the last fragment && the current sample is the start sample of the next fragment
+ currentFragment++;
+ }
+ durations[currentFragment] += entry.getDelta();
+
+
+ }
+ }
+ return durations;
+
+ }
+
+ public long getBitrate(Track track) {
+ long bitrate = 0;
+ for (ByteBuffer sample : track.getSamples()) {
+ bitrate += sample.limit();
+ }
+ bitrate *= 8; // from bytes to bits
+ bitrate /= ((double) getDuration(track)) / track.getTrackMetaData().getTimescale(); // per second
+ return bitrate;
+ }
+
+ protected static long getDuration(Track track) {
+ long duration = 0;
+ for (TimeToSampleBox.Entry entry : track.getDecodingTimeEntries()) {
+ duration += entry.getCount() * entry.getDelta();
+ }
+ return duration;
+ }
+
+ protected long[] checkFragmentsAlign(long[] referenceTimes, long[] checkTimes) throws IOException {
+
+ if (referenceTimes == null || referenceTimes.length == 0) {
+ return checkTimes;
+ }
+ long[] referenceTimesMinusLast = new long[referenceTimes.length - 1];
+ System.arraycopy(referenceTimes, 0, referenceTimesMinusLast, 0, referenceTimes.length - 1);
+ long[] checkTimesMinusLast = new long[checkTimes.length - 1];
+ System.arraycopy(checkTimes, 0, checkTimesMinusLast, 0, checkTimes.length - 1);
+
+ if (!Arrays.equals(checkTimesMinusLast, referenceTimesMinusLast)) {
+ String log = "";
+ log += (referenceTimes.length);
+ log += ("Reference : [");
+ for (long l : referenceTimes) {
+ log += (String.format("%10d,", l));
+ }
+ log += ("]");
+ LOG.warning(log);
+ log = "";
+
+ log += (checkTimes.length);
+ log += ("Current : [");
+ for (long l : checkTimes) {
+ log += (String.format("%10d,", l));
+ }
+ log += ("]");
+ LOG.warning(log);
+ throw new IOException("Track does not have the same fragment borders as its predecessor.");
+
+ } else {
+ return checkTimes;
+ }
+ }
+
+ protected String getFormat(SampleEntry se) {
+ String type = se.getType();
+ if (type.equals("encv") || type.equals("enca") || type.equals("encv")) {
+ OriginalFormatBox frma = se.getBoxes(OriginalFormatBox.class, true).get(0);
+ type = frma.getDataFormat();
+ }
+ return type;
+ }
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/text-base/AudioQuality.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/text-base/AudioQuality.java.svn-base
new file mode 100644
index 0000000..39e115f
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/text-base/AudioQuality.java.svn-base
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.adaptivestreaming;
+
+
+public class AudioQuality {
+ String fourCC;
+ long bitrate;
+ int audioTag;
+ long samplingRate;
+ int channels;
+ int bitPerSample;
+ int packetSize;
+ String language;
+ String codecPrivateData;
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/text-base/FlatManifestWriterImpl.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/text-base/FlatManifestWriterImpl.java.svn-base
new file mode 100644
index 0000000..5cc9be9
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/text-base/FlatManifestWriterImpl.java.svn-base
@@ -0,0 +1,643 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.adaptivestreaming;
+
+import com.coremedia.iso.Hex;
+import com.coremedia.iso.boxes.SampleDescriptionBox;
+import com.coremedia.iso.boxes.SoundMediaHeaderBox;
+import com.coremedia.iso.boxes.VideoMediaHeaderBox;
+import com.coremedia.iso.boxes.h264.AvcConfigurationBox;
+import com.coremedia.iso.boxes.sampleentry.AudioSampleEntry;
+import com.coremedia.iso.boxes.sampleentry.VisualSampleEntry;
+import com.googlecode.mp4parser.Version;
+import com.googlecode.mp4parser.authoring.Movie;
+import com.googlecode.mp4parser.authoring.Track;
+import com.googlecode.mp4parser.authoring.builder.FragmentIntersectionFinder;
+import com.googlecode.mp4parser.boxes.DTSSpecificBox;
+import com.googlecode.mp4parser.boxes.EC3SpecificBox;
+import com.googlecode.mp4parser.boxes.mp4.ESDescriptorBox;
+import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.AudioSpecificConfig;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.*;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.nio.ByteBuffer;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.logging.Logger;
+
+public class FlatManifestWriterImpl extends AbstractManifestWriter {
+ private static final Logger LOG = Logger.getLogger(FlatManifestWriterImpl.class.getName());
+
+ protected FlatManifestWriterImpl(FragmentIntersectionFinder intersectionFinder) {
+ super(intersectionFinder);
+ }
+
+ /**
+ * Overwrite this method in subclasses to add your specialities.
+ *
+ * @param manifest the original manifest
+ * @return your customized version of the manifest
+ */
+ protected Document customizeManifest(Document manifest) {
+ return manifest;
+ }
+
+ public String getManifest(Movie movie) throws IOException {
+
+ LinkedList<VideoQuality> videoQualities = new LinkedList<VideoQuality>();
+ long videoTimescale = -1;
+
+ LinkedList<AudioQuality> audioQualities = new LinkedList<AudioQuality>();
+ long audioTimescale = -1;
+
+ for (Track track : movie.getTracks()) {
+ if (track.getMediaHeaderBox() instanceof VideoMediaHeaderBox) {
+ videoFragmentsDurations = checkFragmentsAlign(videoFragmentsDurations, calculateFragmentDurations(track, movie));
+ SampleDescriptionBox stsd = track.getSampleDescriptionBox();
+ videoQualities.add(getVideoQuality(track, (VisualSampleEntry) stsd.getSampleEntry()));
+ if (videoTimescale == -1) {
+ videoTimescale = track.getTrackMetaData().getTimescale();
+ } else {
+ assert videoTimescale == track.getTrackMetaData().getTimescale();
+ }
+ }
+ if (track.getMediaHeaderBox() instanceof SoundMediaHeaderBox) {
+ audioFragmentsDurations = checkFragmentsAlign(audioFragmentsDurations, calculateFragmentDurations(track, movie));
+ SampleDescriptionBox stsd = track.getSampleDescriptionBox();
+ audioQualities.add(getAudioQuality(track, (AudioSampleEntry) stsd.getSampleEntry()));
+ if (audioTimescale == -1) {
+ audioTimescale = track.getTrackMetaData().getTimescale();
+ } else {
+ assert audioTimescale == track.getTrackMetaData().getTimescale();
+ }
+
+ }
+ }
+ DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder documentBuilder;
+ try {
+ documentBuilder = documentBuilderFactory.newDocumentBuilder();
+ } catch (ParserConfigurationException e) {
+ throw new IOException(e);
+ }
+ Document document = documentBuilder.newDocument();
+
+
+ Element smoothStreamingMedia = document.createElement("SmoothStreamingMedia");
+ document.appendChild(smoothStreamingMedia);
+ smoothStreamingMedia.setAttribute("MajorVersion", "2");
+ smoothStreamingMedia.setAttribute("MinorVersion", "1");
+// silverlight ignores the timescale attr smoothStreamingMedia.addAttribute(new Attribute("TimeScale", Long.toString(movieTimeScale)));
+ smoothStreamingMedia.setAttribute("Duration", "0");
+
+ smoothStreamingMedia.appendChild(document.createComment(Version.VERSION));
+ Element videoStreamIndex = document.createElement("StreamIndex");
+ videoStreamIndex.setAttribute("Type", "video");
+ videoStreamIndex.setAttribute("TimeScale", Long.toString(videoTimescale)); // silverlight ignores the timescale attr
+ videoStreamIndex.setAttribute("Chunks", Integer.toString(videoFragmentsDurations.length));
+ videoStreamIndex.setAttribute("Url", "video/{bitrate}/{start time}");
+ videoStreamIndex.setAttribute("QualityLevels", Integer.toString(videoQualities.size()));
+ smoothStreamingMedia.appendChild(videoStreamIndex);
+
+ for (int i = 0; i < videoQualities.size(); i++) {
+ VideoQuality vq = videoQualities.get(i);
+ Element qualityLevel = document.createElement("QualityLevel");
+ qualityLevel.setAttribute("Index", Integer.toString(i));
+ qualityLevel.setAttribute("Bitrate", Long.toString(vq.bitrate));
+ qualityLevel.setAttribute("FourCC", vq.fourCC);
+ qualityLevel.setAttribute("MaxWidth", Long.toString(vq.width));
+ qualityLevel.setAttribute("MaxHeight", Long.toString(vq.height));
+ qualityLevel.setAttribute("CodecPrivateData", vq.codecPrivateData);
+ qualityLevel.setAttribute("NALUnitLengthField", Integer.toString(vq.nalLength));
+ videoStreamIndex.appendChild(qualityLevel);
+ }
+
+ for (int i = 0; i < videoFragmentsDurations.length; i++) {
+ Element c = document.createElement("c");
+ c.setAttribute("n", Integer.toString(i));
+ c.setAttribute("d", Long.toString(videoFragmentsDurations[i]));
+ videoStreamIndex.appendChild(c);
+ }
+
+ if (audioFragmentsDurations != null) {
+ Element audioStreamIndex = document.createElement("StreamIndex");
+ audioStreamIndex.setAttribute("Type", "audio");
+ audioStreamIndex.setAttribute("TimeScale", Long.toString(audioTimescale)); // silverlight ignores the timescale attr
+ audioStreamIndex.setAttribute("Chunks", Integer.toString(audioFragmentsDurations.length));
+ audioStreamIndex.setAttribute("Url", "audio/{bitrate}/{start time}");
+ audioStreamIndex.setAttribute("QualityLevels", Integer.toString(audioQualities.size()));
+ smoothStreamingMedia.appendChild(audioStreamIndex);
+
+ for (int i = 0; i < audioQualities.size(); i++) {
+ AudioQuality aq = audioQualities.get(i);
+ Element qualityLevel = document.createElement("QualityLevel");
+ qualityLevel.setAttribute("Index", Integer.toString(i));
+ qualityLevel.setAttribute("FourCC", aq.fourCC);
+ qualityLevel.setAttribute("Bitrate", Long.toString(aq.bitrate));
+ qualityLevel.setAttribute("AudioTag", Integer.toString(aq.audioTag));
+ qualityLevel.setAttribute("SamplingRate", Long.toString(aq.samplingRate));
+ qualityLevel.setAttribute("Channels", Integer.toString(aq.channels));
+ qualityLevel.setAttribute("BitsPerSample", Integer.toString(aq.bitPerSample));
+ qualityLevel.setAttribute("PacketSize", Integer.toString(aq.packetSize));
+ qualityLevel.setAttribute("CodecPrivateData", aq.codecPrivateData);
+ audioStreamIndex.appendChild(qualityLevel);
+ }
+ for (int i = 0; i < audioFragmentsDurations.length; i++) {
+ Element c = document.createElement("c");
+ c.setAttribute("n", Integer.toString(i));
+ c.setAttribute("d", Long.toString(audioFragmentsDurations[i]));
+ audioStreamIndex.appendChild(c);
+ }
+ }
+
+ document.setXmlStandalone(true);
+ Source source = new DOMSource(document);
+ StringWriter stringWriter = new StringWriter();
+ Result result = new StreamResult(stringWriter);
+ TransformerFactory factory = TransformerFactory.newInstance();
+ Transformer transformer;
+ try {
+ transformer = factory.newTransformer();
+ transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+ transformer.transform(source, result);
+ } catch (TransformerConfigurationException e) {
+ throw new IOException(e);
+ } catch (TransformerException e) {
+ throw new IOException(e);
+ }
+ return stringWriter.getBuffer().toString();
+
+
+ }
+
+ private AudioQuality getAudioQuality(Track track, AudioSampleEntry ase) {
+ if (getFormat(ase).equals("mp4a")) {
+ return getAacAudioQuality(track, ase);
+ } else if (getFormat(ase).equals("ec-3")) {
+ return getEc3AudioQuality(track, ase);
+ } else if (getFormat(ase).startsWith("dts")) {
+ return getDtsAudioQuality(track, ase);
+ } else {
+ throw new InternalError("I don't know what to do with audio of type " + getFormat(ase));
+ }
+
+ }
+
+ private AudioQuality getAacAudioQuality(Track track, AudioSampleEntry ase) {
+ AudioQuality l = new AudioQuality();
+ final ESDescriptorBox esDescriptorBox = ase.getBoxes(ESDescriptorBox.class).get(0);
+ final AudioSpecificConfig audioSpecificConfig = esDescriptorBox.getEsDescriptor().getDecoderConfigDescriptor().getAudioSpecificInfo();
+ if (audioSpecificConfig.getSbrPresentFlag() == 1) {
+ l.fourCC = "AACH";
+ } else if (audioSpecificConfig.getPsPresentFlag() == 1) {
+ l.fourCC = "AACP"; //I'm not sure if that's what MS considers as AAC+ - because actually AAC+ and AAC-HE should be the same...
+ } else {
+ l.fourCC = "AACL";
+ }
+ l.bitrate = getBitrate(track);
+ l.audioTag = 255;
+ l.samplingRate = ase.getSampleRate();
+ l.channels = ase.getChannelCount();
+ l.bitPerSample = ase.getSampleSize();
+ l.packetSize = 4;
+ l.codecPrivateData = getAudioCodecPrivateData(audioSpecificConfig);
+ //Index="0" Bitrate="103000" AudioTag="255" SamplingRate="44100" Channels="2" BitsPerSample="16" packetSize="4" CodecPrivateData=""
+ return l;
+ }
+
+ private AudioQuality getEc3AudioQuality(Track track, AudioSampleEntry ase) {
+ final EC3SpecificBox ec3SpecificBox = ase.getBoxes(EC3SpecificBox.class).get(0);
+ if (ec3SpecificBox == null) {
+ throw new RuntimeException("EC-3 track misses EC3SpecificBox!");
+ }
+
+ short nfchans = 0; //full bandwidth channels
+ short lfechans = 0;
+ byte dWChannelMaskFirstByte = 0;
+ byte dWChannelMaskSecondByte = 0;
+ for (EC3SpecificBox.Entry entry : ec3SpecificBox.getEntries()) {
+ /*
+ Table 4.3: Audio coding mode
+ acmod Audio coding mode Nfchans Channel array ordering
+ 000 1 + 1 2 Ch1, Ch2
+ 001 1/0 1 C
+ 010 2/0 2 L, R
+ 011 3/0 3 L, C, R
+ 100 2/1 3 L, R, S
+ 101 3/1 4 L, C, R, S
+ 110 2/2 4 L, R, SL, SR
+ 111 3/2 5 L, C, R, SL, SR
+
+ Table F.2: Chan_loc field bit assignments
+ Bit Location
+ 0 Lc/Rc pair
+ 1 Lrs/Rrs pair
+ 2 Cs
+ 3 Ts
+ 4 Lsd/Rsd pair
+ 5 Lw/Rw pair
+ 6 Lvh/Rvh pair
+ 7 Cvh
+ 8 LFE2
+ */
+ switch (entry.acmod) {
+ case 0: //1+1; Ch1, Ch2
+ nfchans += 2;
+ throw new RuntimeException("Smooth Streaming doesn't support DDP 1+1 mode");
+ case 1: //1/0; C
+ nfchans += 1;
+ if (entry.num_dep_sub > 0) {
+ DependentSubstreamMask dependentSubstreamMask = new DependentSubstreamMask(dWChannelMaskFirstByte, dWChannelMaskSecondByte, entry).process();
+ dWChannelMaskFirstByte |= dependentSubstreamMask.getdWChannelMaskFirstByte();
+ dWChannelMaskSecondByte |= dependentSubstreamMask.getdWChannelMaskSecondByte();
+ } else {
+ dWChannelMaskFirstByte |= 0x20;
+ }
+ break;
+ case 2: //2/0; L, R
+ nfchans += 2;
+ if (entry.num_dep_sub > 0) {
+ DependentSubstreamMask dependentSubstreamMask = new DependentSubstreamMask(dWChannelMaskFirstByte, dWChannelMaskSecondByte, entry).process();
+ dWChannelMaskFirstByte |= dependentSubstreamMask.getdWChannelMaskFirstByte();
+ dWChannelMaskSecondByte |= dependentSubstreamMask.getdWChannelMaskSecondByte();
+ } else {
+ dWChannelMaskFirstByte |= 0xC0;
+ }
+ break;
+ case 3: //3/0; L, C, R
+ nfchans += 3;
+ if (entry.num_dep_sub > 0) {
+ DependentSubstreamMask dependentSubstreamMask = new DependentSubstreamMask(dWChannelMaskFirstByte, dWChannelMaskSecondByte, entry).process();
+ dWChannelMaskFirstByte |= dependentSubstreamMask.getdWChannelMaskFirstByte();
+ dWChannelMaskSecondByte |= dependentSubstreamMask.getdWChannelMaskSecondByte();
+ } else {
+ dWChannelMaskFirstByte |= 0xE0;
+ }
+ break;
+ case 4: //2/1; L, R, S
+ nfchans += 3;
+ if (entry.num_dep_sub > 0) {
+ DependentSubstreamMask dependentSubstreamMask = new DependentSubstreamMask(dWChannelMaskFirstByte, dWChannelMaskSecondByte, entry).process();
+ dWChannelMaskFirstByte |= dependentSubstreamMask.getdWChannelMaskFirstByte();
+ dWChannelMaskSecondByte |= dependentSubstreamMask.getdWChannelMaskSecondByte();
+ } else {
+ dWChannelMaskFirstByte |= 0xC0;
+ dWChannelMaskSecondByte |= 0x80;
+ }
+ break;
+ case 5: //3/1; L, C, R, S
+ nfchans += 4;
+ if (entry.num_dep_sub > 0) {
+ DependentSubstreamMask dependentSubstreamMask = new DependentSubstreamMask(dWChannelMaskFirstByte, dWChannelMaskSecondByte, entry).process();
+ dWChannelMaskFirstByte |= dependentSubstreamMask.getdWChannelMaskFirstByte();
+ dWChannelMaskSecondByte |= dependentSubstreamMask.getdWChannelMaskSecondByte();
+ } else {
+ dWChannelMaskFirstByte |= 0xE0;
+ dWChannelMaskSecondByte |= 0x80;
+ }
+ break;
+ case 6: //2/2; L, R, SL, SR
+ nfchans += 4;
+ if (entry.num_dep_sub > 0) {
+ DependentSubstreamMask dependentSubstreamMask = new DependentSubstreamMask(dWChannelMaskFirstByte, dWChannelMaskSecondByte, entry).process();
+ dWChannelMaskFirstByte |= dependentSubstreamMask.getdWChannelMaskFirstByte();
+ dWChannelMaskSecondByte |= dependentSubstreamMask.getdWChannelMaskSecondByte();
+ } else {
+ dWChannelMaskFirstByte |= 0xCC;
+ }
+ break;
+ case 7: //3/2; L, C, R, SL, SR
+ nfchans += 5;
+ if (entry.num_dep_sub > 0) {
+ DependentSubstreamMask dependentSubstreamMask = new DependentSubstreamMask(dWChannelMaskFirstByte, dWChannelMaskSecondByte, entry).process();
+ dWChannelMaskFirstByte |= dependentSubstreamMask.getdWChannelMaskFirstByte();
+ dWChannelMaskSecondByte |= dependentSubstreamMask.getdWChannelMaskSecondByte();
+ } else {
+ dWChannelMaskFirstByte |= 0xEC;
+ }
+ break;
+ }
+ if (entry.lfeon == 1) {
+ lfechans ++;
+ dWChannelMaskFirstByte |= 0x10;
+ }
+ }
+
+ final ByteBuffer waveformatex = ByteBuffer.allocate(22);
+ waveformatex.put(new byte[]{0x00, 0x06}); //1536 wSamplesPerBlock - little endian
+ waveformatex.put(dWChannelMaskFirstByte);
+ waveformatex.put(dWChannelMaskSecondByte);
+ waveformatex.put(new byte[]{0x00, 0x00}); //pad dwChannelMask to 32bit
+ waveformatex.put(new byte[]{(byte)0xAF, (byte)0x87, (byte)0xFB, (byte)0xA7, 0x02, 0x2D, (byte)0xFB, 0x42, (byte)0xA4, (byte)0xD4, 0x05, (byte)0xCD, (byte)0x93, (byte)0x84, 0x3B, (byte)0xDD}); //SubFormat - Dolby Digital Plus GUID
+
+ final ByteBuffer dec3Content = ByteBuffer.allocate((int) ec3SpecificBox.getContentSize());
+ ec3SpecificBox.getContent(dec3Content);
+
+ AudioQuality l = new AudioQuality();
+ l.fourCC = "EC-3";
+ l.bitrate = getBitrate(track);
+ l.audioTag = 65534;
+ l.samplingRate = ase.getSampleRate();
+ l.channels = nfchans + lfechans;
+ l.bitPerSample = 16;
+ l.packetSize = track.getSamples().get(0).limit(); //assuming all are same size
+ l.codecPrivateData = Hex.encodeHex(waveformatex.array()) + Hex.encodeHex(dec3Content.array()); //append EC3SpecificBox (big endian) at the end of waveformatex
+ return l;
+ }
+
+ private AudioQuality getDtsAudioQuality(Track track, AudioSampleEntry ase) {
+ final DTSSpecificBox dtsSpecificBox = ase.getBoxes(DTSSpecificBox.class).get(0);
+ if (dtsSpecificBox == null) {
+ throw new RuntimeException("DTS track misses DTSSpecificBox!");
+ }
+
+ final ByteBuffer waveformatex = ByteBuffer.allocate(22);
+ final int frameDuration = dtsSpecificBox.getFrameDuration();
+ short samplesPerBlock = 0;
+ switch (frameDuration) {
+ case 0:
+ samplesPerBlock = 512;
+ break;
+ case 1:
+ samplesPerBlock = 1024;
+ break;
+ case 2:
+ samplesPerBlock = 2048;
+ break;
+ case 3:
+ samplesPerBlock = 4096;
+ break;
+ }
+ waveformatex.put((byte) (samplesPerBlock & 0xff));
+ waveformatex.put((byte) (samplesPerBlock >>> 8));
+ final int dwChannelMask = getNumChannelsAndMask(dtsSpecificBox)[1];
+ waveformatex.put((byte) (dwChannelMask & 0xff));
+ waveformatex.put((byte) (dwChannelMask >>> 8));
+ waveformatex.put((byte) (dwChannelMask >>> 16));
+ waveformatex.put((byte) (dwChannelMask >>> 24));
+ waveformatex.put(new byte[]{(byte)0xAE, (byte)0xE4, (byte)0xBF, (byte)0x5E, (byte)0x61, (byte)0x5E, (byte)0x41, (byte)0x87, (byte)0x92, (byte)0xFC, (byte)0xA4, (byte)0x81, (byte)0x26, (byte)0x99, (byte)0x02, (byte)0x11}); //DTS-HD GUID
+
+ final ByteBuffer dtsCodecPrivateData = ByteBuffer.allocate(8);
+ dtsCodecPrivateData.put((byte) dtsSpecificBox.getStreamConstruction());
+
+ final int channelLayout = dtsSpecificBox.getChannelLayout();
+ dtsCodecPrivateData.put((byte) (channelLayout & 0xff));
+ dtsCodecPrivateData.put((byte) (channelLayout >>> 8));
+ dtsCodecPrivateData.put((byte) (channelLayout >>> 16));
+ dtsCodecPrivateData.put((byte) (channelLayout >>> 24));
+
+ byte dtsFlags = (byte) (dtsSpecificBox.getMultiAssetFlag() << 1);
+ dtsFlags |= dtsSpecificBox.getLBRDurationMod();
+ dtsCodecPrivateData.put(dtsFlags);
+ dtsCodecPrivateData.put(new byte[]{0x00, 0x00}); //reserved
+
+ AudioQuality l = new AudioQuality();
+ l.fourCC = getFormat(ase);
+ l.bitrate = dtsSpecificBox.getAvgBitRate();
+ l.audioTag = 65534;
+ l.samplingRate = dtsSpecificBox.getDTSSamplingFrequency();
+ l.channels = getNumChannelsAndMask(dtsSpecificBox)[0];
+ l.bitPerSample = 16;
+ l.packetSize = track.getSamples().get(0).limit(); //assuming all are same size
+ l.codecPrivateData = Hex.encodeHex(waveformatex.array()) + Hex.encodeHex(dtsCodecPrivateData.array());
+ return l;
+
+ }
+
+ /* dwChannelMask
+ L SPEAKER_FRONT_LEFT 0x00000001
+ R SPEAKER_FRONT_RIGHT 0x00000002
+ C SPEAKER_FRONT_CENTER 0x00000004
+ LFE1 SPEAKER_LOW_FREQUENCY 0x00000008
+ Ls or Lsr* SPEAKER_BACK_LEFT 0x00000010
+ Rs or Rsr* SPEAKER_BACK_RIGHT 0x00000020
+ Lc SPEAKER_FRONT_LEFT_OF_CENTER 0x00000040
+ Rc SPEAKER_FRONT_RIGHT_OF_CENTER 0x00000080
+ Cs SPEAKER_BACK_CENTER 0x00000100
+ Lss SPEAKER_SIDE_LEFT 0x00000200
+ Rss SPEAKER_SIDE_RIGHT 0x00000400
+ Oh SPEAKER_TOP_CENTER 0x00000800
+ Lh SPEAKER_TOP_FRONT_LEFT 0x00001000
+ Ch SPEAKER_TOP_FRONT_CENTER 0x00002000
+ Rh SPEAKER_TOP_FRONT_RIGHT 0x00004000
+ Lhr SPEAKER_TOP_BACK_LEFT 0x00008000
+ Chf SPEAKER_TOP_BACK_CENTER 0x00010000
+ Rhr SPEAKER_TOP_BACK_RIGHT 0x00020000
+ SPEAKER_RESERVED 0x80000000
+
+ * if Lss, Rss exist, then this position is equivalent to Lsr, Rsr respectively
+ */
+ private int[] getNumChannelsAndMask(DTSSpecificBox dtsSpecificBox) {
+ final int channelLayout = dtsSpecificBox.getChannelLayout();
+ int numChannels = 0;
+ int dwChannelMask = 0;
+ if ((channelLayout & 0x0001) == 0x0001) {
+ //0001h Center in front of listener 1
+ numChannels += 1;
+ dwChannelMask |= 0x00000004; //SPEAKER_FRONT_CENTER
+ }
+ if ((channelLayout & 0x0002) == 0x0002) {
+ //0002h Left/Right in front 2
+ numChannels += 2;
+ dwChannelMask |= 0x00000001; //SPEAKER_FRONT_LEFT
+ dwChannelMask |= 0x00000002; //SPEAKER_FRONT_RIGHT
+ }
+ if ((channelLayout & 0x0004) == 0x0004) {
+ //0004h Left/Right surround on side in rear 2
+ numChannels += 2;
+ //* if Lss, Rss exist, then this position is equivalent to Lsr, Rsr respectively
+ dwChannelMask |= 0x00000010; //SPEAKER_BACK_LEFT
+ dwChannelMask |= 0x00000020; //SPEAKER_BACK_RIGHT
+ }
+ if ((channelLayout & 0x0008) == 0x0008) {
+ //0008h Low frequency effects subwoofer 1
+ numChannels += 1;
+ dwChannelMask |= 0x00000008; //SPEAKER_LOW_FREQUENCY
+ }
+ if ((channelLayout & 0x0010) == 0x0010) {
+ //0010h Center surround in rear 1
+ numChannels += 1;
+ dwChannelMask |= 0x00000100; //SPEAKER_BACK_CENTER
+ }
+ if ((channelLayout & 0x0020) == 0x0020) {
+ //0020h Left/Right height in front 2
+ numChannels += 2;
+ dwChannelMask |= 0x00001000; //SPEAKER_TOP_FRONT_LEFT
+ dwChannelMask |= 0x00004000; //SPEAKER_TOP_FRONT_RIGHT
+ }
+ if ((channelLayout & 0x0040) == 0x0040) {
+ //0040h Left/Right surround in rear 2
+ numChannels += 2;
+ dwChannelMask |= 0x00000010; //SPEAKER_BACK_LEFT
+ dwChannelMask |= 0x00000020; //SPEAKER_BACK_RIGHT
+ }
+ if ((channelLayout & 0x0080) == 0x0080) {
+ //0080h Center Height in front 1
+ numChannels += 1;
+ dwChannelMask |= 0x00002000; //SPEAKER_TOP_FRONT_CENTER
+ }
+ if ((channelLayout & 0x0100) == 0x0100) {
+ //0100h Over the listener’s head 1
+ numChannels += 1;
+ dwChannelMask |= 0x00000800; //SPEAKER_TOP_CENTER
+ }
+ if ((channelLayout & 0x0200) == 0x0200) {
+ //0200h Between left/right and center in front 2
+ numChannels += 2;
+ dwChannelMask |= 0x00000040; //SPEAKER_FRONT_LEFT_OF_CENTER
+ dwChannelMask |= 0x00000080; //SPEAKER_FRONT_RIGHT_OF_CENTER
+ }
+ if ((channelLayout & 0x0400) == 0x0400) {
+ //0400h Left/Right on side in front 2
+ numChannels += 2;
+ dwChannelMask |= 0x00000200; //SPEAKER_SIDE_LEFT
+ dwChannelMask |= 0x00000400; //SPEAKER_SIDE_RIGHT
+ }
+ if ((channelLayout & 0x0800) == 0x0800) {
+ //0800h Left/Right surround on side 2
+ numChannels += 2;
+ //* if Lss, Rss exist, then this position is equivalent to Lsr, Rsr respectively
+ dwChannelMask |= 0x00000010; //SPEAKER_BACK_LEFT
+ dwChannelMask |= 0x00000020; //SPEAKER_BACK_RIGHT
+ }
+ if ((channelLayout & 0x1000) == 0x1000) {
+ //1000h Second low frequency effects subwoofer 1
+ numChannels += 1;
+ dwChannelMask |= 0x00000008; //SPEAKER_LOW_FREQUENCY
+ }
+ if ((channelLayout & 0x2000) == 0x2000) {
+ //2000h Left/Right height on side 2
+ numChannels += 2;
+ dwChannelMask |= 0x00000010; //SPEAKER_BACK_LEFT
+ dwChannelMask |= 0x00000020; //SPEAKER_BACK_RIGHT
+ }
+ if ((channelLayout & 0x4000) == 0x4000) {
+ //4000h Center height in rear 1
+ numChannels += 1;
+ dwChannelMask |= 0x00010000; //SPEAKER_TOP_BACK_CENTER
+ }
+ if ((channelLayout & 0x8000) == 0x8000) {
+ //8000h Left/Right height in rear 2
+ numChannels += 2;
+ dwChannelMask |= 0x00008000; //SPEAKER_TOP_BACK_LEFT
+ dwChannelMask |= 0x00020000; //SPEAKER_TOP_BACK_RIGHT
+ }
+ if ((channelLayout & 0x10000) == 0x10000) {
+ //10000h Center below in front
+ numChannels += 1;
+ }
+ if ((channelLayout & 0x20000) == 0x20000) {
+ //20000h Left/Right below in front
+ numChannels += 2;
+ }
+ return new int[]{numChannels, dwChannelMask};
+ }
+
+ private String getAudioCodecPrivateData(AudioSpecificConfig audioSpecificConfig) {
+ byte[] configByteArray = audioSpecificConfig.getConfigBytes();
+ return Hex.encodeHex(configByteArray);
+ }
+
+ private VideoQuality getVideoQuality(Track track, VisualSampleEntry vse) {
+ VideoQuality l;
+ if ("avc1".equals(getFormat(vse))) {
+ AvcConfigurationBox avcConfigurationBox = vse.getBoxes(AvcConfigurationBox.class).get(0);
+ l = new VideoQuality();
+ l.bitrate = getBitrate(track);
+ l.codecPrivateData = Hex.encodeHex(getAvcCodecPrivateData(avcConfigurationBox));
+ l.fourCC = "AVC1";
+ l.width = vse.getWidth();
+ l.height = vse.getHeight();
+ l.nalLength = avcConfigurationBox.getLengthSizeMinusOne() + 1;
+ } else {
+ throw new InternalError("I don't know how to handle video of type " + getFormat(vse));
+ }
+ return l;
+ }
+
+ private byte[] getAvcCodecPrivateData(AvcConfigurationBox avcConfigurationBox) {
+ List<byte[]> sps = avcConfigurationBox.getSequenceParameterSets();
+ List<byte[]> pps = avcConfigurationBox.getPictureParameterSets();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ baos.write(new byte[]{0, 0, 0, 1});
+
+ for (byte[] sp : sps) {
+ baos.write(sp);
+ }
+ baos.write(new byte[]{0, 0, 0, 1});
+ for (byte[] pp : pps) {
+ baos.write(pp);
+ }
+ } catch (IOException ex) {
+ throw new RuntimeException("ByteArrayOutputStream do not throw IOException ?!?!?");
+ }
+ return baos.toByteArray();
+ }
+
+ private class DependentSubstreamMask {
+ private byte dWChannelMaskFirstByte;
+ private byte dWChannelMaskSecondByte;
+ private EC3SpecificBox.Entry entry;
+
+ public DependentSubstreamMask(byte dWChannelMaskFirstByte, byte dWChannelMaskSecondByte, EC3SpecificBox.Entry entry) {
+ this.dWChannelMaskFirstByte = dWChannelMaskFirstByte;
+ this.dWChannelMaskSecondByte = dWChannelMaskSecondByte;
+ this.entry = entry;
+ }
+
+ public byte getdWChannelMaskFirstByte() {
+ return dWChannelMaskFirstByte;
+ }
+
+ public byte getdWChannelMaskSecondByte() {
+ return dWChannelMaskSecondByte;
+ }
+
+ public DependentSubstreamMask process() {
+ switch (entry.chan_loc) {
+ case 0:
+ dWChannelMaskFirstByte |= 0x3;
+ break;
+ case 1:
+ dWChannelMaskFirstByte |= 0xC;
+ break;
+ case 2:
+ dWChannelMaskSecondByte |= 0x80;
+ break;
+ case 3:
+ dWChannelMaskSecondByte |= 0x8;
+ break;
+ case 6:
+ dWChannelMaskSecondByte |= 0x5;
+ break;
+ case 7:
+ dWChannelMaskSecondByte |= 0x2;
+ break;
+ }
+ return this;
+ }
+ }
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/text-base/FlatPackageWriterImpl.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/text-base/FlatPackageWriterImpl.java.svn-base
new file mode 100644
index 0000000..3e3847c
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/text-base/FlatPackageWriterImpl.java.svn-base
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.adaptivestreaming;
+
+import com.coremedia.iso.IsoFile;
+import com.coremedia.iso.boxes.Box;
+import com.coremedia.iso.boxes.SoundMediaHeaderBox;
+import com.coremedia.iso.boxes.VideoMediaHeaderBox;
+import com.coremedia.iso.boxes.fragment.MovieFragmentBox;
+import com.googlecode.mp4parser.authoring.Movie;
+import com.googlecode.mp4parser.authoring.Track;
+import com.googlecode.mp4parser.authoring.builder.*;
+import com.googlecode.mp4parser.authoring.tracks.ChangeTimeScaleTrack;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.channels.FileChannel;
+import java.util.Iterator;
+import java.util.logging.Logger;
+
+public class FlatPackageWriterImpl implements PackageWriter {
+ private static Logger LOG = Logger.getLogger(FlatPackageWriterImpl.class.getName());
+ long timeScale = 10000000;
+
+ private File outputDirectory;
+ private boolean debugOutput;
+ private FragmentedMp4Builder ismvBuilder;
+ ManifestWriter manifestWriter;
+
+ public FlatPackageWriterImpl() {
+ ismvBuilder = new FragmentedMp4Builder();
+ FragmentIntersectionFinder intersectionFinder = new SyncSampleIntersectFinderImpl();
+ ismvBuilder.setIntersectionFinder(intersectionFinder);
+ manifestWriter = new FlatManifestWriterImpl(intersectionFinder);
+ }
+
+ /**
+ * Creates a factory for a smooth streaming package. A smooth streaming package is
+ * a collection of files that can be served by a webserver as a smooth streaming
+ * stream.
+ * @param minFragmentDuration the smallest allowable duration of a fragment (0 == no restriction).
+ */
+ public FlatPackageWriterImpl(int minFragmentDuration) {
+ ismvBuilder = new FragmentedMp4Builder();
+ FragmentIntersectionFinder intersectionFinder = new SyncSampleIntersectFinderImpl(minFragmentDuration);
+ ismvBuilder.setIntersectionFinder(intersectionFinder);
+ manifestWriter = new FlatManifestWriterImpl(intersectionFinder);
+ }
+
+ public void setOutputDirectory(File outputDirectory) {
+ assert outputDirectory.isDirectory();
+ this.outputDirectory = outputDirectory;
+
+ }
+
+ public void setDebugOutput(boolean debugOutput) {
+ this.debugOutput = debugOutput;
+ }
+
+ public void setIsmvBuilder(FragmentedMp4Builder ismvBuilder) {
+ this.ismvBuilder = ismvBuilder;
+ this.manifestWriter = new FlatManifestWriterImpl(ismvBuilder.getFragmentIntersectionFinder());
+ }
+
+ public void setManifestWriter(ManifestWriter manifestWriter) {
+ this.manifestWriter = manifestWriter;
+ }
+
+ /**
+ * Writes the movie given as <code>qualities</code> flattened into the
+ * <code>outputDirectory</code>.
+ *
+ * @param source the source movie with all qualities
+ * @throws IOException
+ */
+ public void write(Movie source) throws IOException {
+
+ if (debugOutput) {
+ outputDirectory.mkdirs();
+ DefaultMp4Builder defaultMp4Builder = new DefaultMp4Builder();
+ IsoFile muxed = defaultMp4Builder.build(source);
+ File muxedFile = new File(outputDirectory, "debug_1_muxed.mp4");
+ FileOutputStream muxedFileOutputStream = new FileOutputStream(muxedFile);
+ muxed.getBox(muxedFileOutputStream.getChannel());
+ muxedFileOutputStream.close();
+ }
+ Movie cleanedSource = removeUnknownTracks(source);
+ Movie movieWithAdjustedTimescale = correctTimescale(cleanedSource);
+
+ if (debugOutput) {
+ DefaultMp4Builder defaultMp4Builder = new DefaultMp4Builder();
+ IsoFile muxed = defaultMp4Builder.build(movieWithAdjustedTimescale);
+ File muxedFile = new File(outputDirectory, "debug_2_timescale.mp4");
+ FileOutputStream muxedFileOutputStream = new FileOutputStream(muxedFile);
+ muxed.getBox(muxedFileOutputStream.getChannel());
+ muxedFileOutputStream.close();
+ }
+ IsoFile isoFile = ismvBuilder.build(movieWithAdjustedTimescale);
+ if (debugOutput) {
+ File allQualities = new File(outputDirectory, "debug_3_fragmented.mp4");
+ FileOutputStream allQualis = new FileOutputStream(allQualities);
+ isoFile.getBox(allQualis.getChannel());
+ allQualis.close();
+ }
+
+
+ for (Track track : movieWithAdjustedTimescale.getTracks()) {
+ String bitrate = Long.toString(manifestWriter.getBitrate(track));
+ long trackId = track.getTrackMetaData().getTrackId();
+ Iterator<Box> boxIt = isoFile.getBoxes().iterator();
+ File mediaOutDir;
+ if (track.getMediaHeaderBox() instanceof SoundMediaHeaderBox) {
+ mediaOutDir = new File(outputDirectory, "audio");
+
+ } else if (track.getMediaHeaderBox() instanceof VideoMediaHeaderBox) {
+ mediaOutDir = new File(outputDirectory, "video");
+ } else {
+ System.err.println("Skipping Track with handler " + track.getHandler() + " and " + track.getMediaHeaderBox().getClass().getSimpleName());
+ continue;
+ }
+ File bitRateOutputDir = new File(mediaOutDir, bitrate);
+ bitRateOutputDir.mkdirs();
+ LOG.finer("Created : " + bitRateOutputDir.getCanonicalPath());
+
+ long[] fragmentTimes = manifestWriter.calculateFragmentDurations(track, movieWithAdjustedTimescale);
+ long startTime = 0;
+ int currentFragment = 0;
+ while (boxIt.hasNext()) {
+ Box b = boxIt.next();
+ if (b instanceof MovieFragmentBox) {
+ assert ((MovieFragmentBox) b).getTrackCount() == 1;
+ if (((MovieFragmentBox) b).getTrackNumbers()[0] == trackId) {
+ FileOutputStream fos = new FileOutputStream(new File(bitRateOutputDir, Long.toString(startTime)));
+ startTime += fragmentTimes[currentFragment++];
+ FileChannel fc = fos.getChannel();
+ Box mdat = boxIt.next();
+ assert mdat.getType().equals("mdat");
+ b.getBox(fc); // moof
+ mdat.getBox(fc); // mdat
+ fc.truncate(fc.position());
+ fc.close();
+ }
+ }
+
+ }
+ }
+ FileWriter fw = new FileWriter(new File(outputDirectory, "Manifest"));
+ fw.write(manifestWriter.getManifest(movieWithAdjustedTimescale));
+ fw.close();
+
+ }
+
+ private Movie removeUnknownTracks(Movie source) {
+ Movie nuMovie = new Movie();
+ for (Track track : source.getTracks()) {
+ if ("vide".equals(track.getHandler()) || "soun".equals(track.getHandler())) {
+ nuMovie.addTrack(track);
+ } else {
+ LOG.fine("Removed track " + track);
+ }
+ }
+ return nuMovie;
+ }
+
+
+ /**
+ * Returns a new <code>Movie</code> in that all tracks have the timescale 10000000. CTS & DTS are modified
+ * in a way that even with more than one framerate the fragments exactly begin at the same time.
+ *
+ * @param movie
+ * @return a movie with timescales suitable for smooth streaming manifests
+ */
+ public Movie correctTimescale(Movie movie) {
+ Movie nuMovie = new Movie();
+ for (Track track : movie.getTracks()) {
+ nuMovie.addTrack(new ChangeTimeScaleTrack(track, timeScale, ismvBuilder.getFragmentIntersectionFinder().sampleNumbers(track, movie)));
+ }
+ return nuMovie;
+
+ }
+
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/text-base/ManifestWriter.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/text-base/ManifestWriter.java.svn-base
new file mode 100644
index 0000000..2b2ba7d
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/text-base/ManifestWriter.java.svn-base
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.adaptivestreaming;
+
+
+import com.googlecode.mp4parser.authoring.Movie;
+import com.googlecode.mp4parser.authoring.Track;
+
+import java.io.IOException;
+
+public interface ManifestWriter {
+ String getManifest(Movie inputs) throws IOException;
+
+ long getBitrate(Track track);
+
+ long[] calculateFragmentDurations(Track track, Movie movie);
+
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/text-base/PackageWriter.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/text-base/PackageWriter.java.svn-base
new file mode 100644
index 0000000..0d97fc5
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/text-base/PackageWriter.java.svn-base
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.adaptivestreaming;
+
+import com.googlecode.mp4parser.authoring.Movie;
+
+import java.io.IOException;
+
+/**
+ * Writes the whole package.
+ */
+public interface PackageWriter {
+ public void write(Movie qualities) throws IOException;
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/text-base/VideoQuality.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/text-base/VideoQuality.java.svn-base
new file mode 100644
index 0000000..4a70e47
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/.svn/text-base/VideoQuality.java.svn-base
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.adaptivestreaming;
+
+class VideoQuality {
+ long bitrate;
+ String fourCC;
+ int width;
+ int height;
+ String codecPrivateData;
+ int nalLength;
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/AbstractManifestWriter.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/AbstractManifestWriter.java
new file mode 100644
index 0000000..6ee4ffa
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/AbstractManifestWriter.java
@@ -0,0 +1,126 @@
+package com.googlecode.mp4parser.authoring.adaptivestreaming;
+
+import com.coremedia.iso.boxes.OriginalFormatBox;
+import com.coremedia.iso.boxes.TimeToSampleBox;
+import com.coremedia.iso.boxes.sampleentry.SampleEntry;
+import com.googlecode.mp4parser.authoring.Movie;
+import com.googlecode.mp4parser.authoring.Track;
+import com.googlecode.mp4parser.authoring.builder.FragmentIntersectionFinder;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.logging.Logger;
+
+import static com.googlecode.mp4parser.util.CastUtils.l2i;
+
+/**
+ * Created with IntelliJ IDEA.
+ * User: mstattma
+ * Date: 17.08.12
+ * Time: 02:51
+ * To change this template use File | Settings | File Templates.
+ */
+public abstract class AbstractManifestWriter implements ManifestWriter {
+ private static final Logger LOG = Logger.getLogger(AbstractManifestWriter.class.getName());
+
+ private FragmentIntersectionFinder intersectionFinder;
+ protected long[] audioFragmentsDurations;
+ protected long[] videoFragmentsDurations;
+
+ protected AbstractManifestWriter(FragmentIntersectionFinder intersectionFinder) {
+ this.intersectionFinder = intersectionFinder;
+ }
+
+ /**
+ * Calculates the length of each fragment in the given <code>track</code> (as part of <code>movie</code>).
+ *
+ * @param track target of calculation
+ * @param movie the <code>track</code> must be part of this <code>movie</code>
+ * @return the duration of each fragment in track timescale
+ */
+ public long[] calculateFragmentDurations(Track track, Movie movie) {
+ long[] startSamples = intersectionFinder.sampleNumbers(track, movie);
+ long[] durations = new long[startSamples.length];
+ int currentFragment = 0;
+ int currentSample = 1; // sync samples start with 1 !
+
+ for (TimeToSampleBox.Entry entry : track.getDecodingTimeEntries()) {
+ for (int max = currentSample + l2i(entry.getCount()); currentSample < max; currentSample++) {
+ // in this loop we go through the entry.getCount() samples starting from current sample.
+ // the next entry.getCount() samples have the same decoding time.
+ if (currentFragment != startSamples.length - 1 && currentSample == startSamples[currentFragment + 1]) {
+ // we are not in the last fragment && the current sample is the start sample of the next fragment
+ currentFragment++;
+ }
+ durations[currentFragment] += entry.getDelta();
+
+
+ }
+ }
+ return durations;
+
+ }
+
+ public long getBitrate(Track track) {
+ long bitrate = 0;
+ for (ByteBuffer sample : track.getSamples()) {
+ bitrate += sample.limit();
+ }
+ bitrate *= 8; // from bytes to bits
+ bitrate /= ((double) getDuration(track)) / track.getTrackMetaData().getTimescale(); // per second
+ return bitrate;
+ }
+
+ protected static long getDuration(Track track) {
+ long duration = 0;
+ for (TimeToSampleBox.Entry entry : track.getDecodingTimeEntries()) {
+ duration += entry.getCount() * entry.getDelta();
+ }
+ return duration;
+ }
+
+ protected long[] checkFragmentsAlign(long[] referenceTimes, long[] checkTimes) throws IOException {
+
+ if (referenceTimes == null || referenceTimes.length == 0) {
+ return checkTimes;
+ }
+ long[] referenceTimesMinusLast = new long[referenceTimes.length - 1];
+ System.arraycopy(referenceTimes, 0, referenceTimesMinusLast, 0, referenceTimes.length - 1);
+ long[] checkTimesMinusLast = new long[checkTimes.length - 1];
+ System.arraycopy(checkTimes, 0, checkTimesMinusLast, 0, checkTimes.length - 1);
+
+ if (!Arrays.equals(checkTimesMinusLast, referenceTimesMinusLast)) {
+ String log = "";
+ log += (referenceTimes.length);
+ log += ("Reference : [");
+ for (long l : referenceTimes) {
+ log += (String.format("%10d,", l));
+ }
+ log += ("]");
+ LOG.warning(log);
+ log = "";
+
+ log += (checkTimes.length);
+ log += ("Current : [");
+ for (long l : checkTimes) {
+ log += (String.format("%10d,", l));
+ }
+ log += ("]");
+ LOG.warning(log);
+ throw new IOException("Track does not have the same fragment borders as its predecessor.");
+
+ } else {
+ return checkTimes;
+ }
+ }
+
+ protected String getFormat(SampleEntry se) {
+ String type = se.getType();
+ if (type.equals("encv") || type.equals("enca") || type.equals("encv")) {
+ OriginalFormatBox frma = se.getBoxes(OriginalFormatBox.class, true).get(0);
+ type = frma.getDataFormat();
+ }
+ return type;
+ }
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/AudioQuality.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/AudioQuality.java
new file mode 100644
index 0000000..39e115f
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/AudioQuality.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.adaptivestreaming;
+
+
+public class AudioQuality {
+ String fourCC;
+ long bitrate;
+ int audioTag;
+ long samplingRate;
+ int channels;
+ int bitPerSample;
+ int packetSize;
+ String language;
+ String codecPrivateData;
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/FlatManifestWriterImpl.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/FlatManifestWriterImpl.java
new file mode 100644
index 0000000..5cc9be9
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/FlatManifestWriterImpl.java
@@ -0,0 +1,643 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.adaptivestreaming;
+
+import com.coremedia.iso.Hex;
+import com.coremedia.iso.boxes.SampleDescriptionBox;
+import com.coremedia.iso.boxes.SoundMediaHeaderBox;
+import com.coremedia.iso.boxes.VideoMediaHeaderBox;
+import com.coremedia.iso.boxes.h264.AvcConfigurationBox;
+import com.coremedia.iso.boxes.sampleentry.AudioSampleEntry;
+import com.coremedia.iso.boxes.sampleentry.VisualSampleEntry;
+import com.googlecode.mp4parser.Version;
+import com.googlecode.mp4parser.authoring.Movie;
+import com.googlecode.mp4parser.authoring.Track;
+import com.googlecode.mp4parser.authoring.builder.FragmentIntersectionFinder;
+import com.googlecode.mp4parser.boxes.DTSSpecificBox;
+import com.googlecode.mp4parser.boxes.EC3SpecificBox;
+import com.googlecode.mp4parser.boxes.mp4.ESDescriptorBox;
+import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.AudioSpecificConfig;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.*;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.nio.ByteBuffer;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.logging.Logger;
+
+public class FlatManifestWriterImpl extends AbstractManifestWriter {
+ private static final Logger LOG = Logger.getLogger(FlatManifestWriterImpl.class.getName());
+
+ protected FlatManifestWriterImpl(FragmentIntersectionFinder intersectionFinder) {
+ super(intersectionFinder);
+ }
+
+ /**
+ * Overwrite this method in subclasses to add your specialities.
+ *
+ * @param manifest the original manifest
+ * @return your customized version of the manifest
+ */
+ protected Document customizeManifest(Document manifest) {
+ return manifest;
+ }
+
+ public String getManifest(Movie movie) throws IOException {
+
+ LinkedList<VideoQuality> videoQualities = new LinkedList<VideoQuality>();
+ long videoTimescale = -1;
+
+ LinkedList<AudioQuality> audioQualities = new LinkedList<AudioQuality>();
+ long audioTimescale = -1;
+
+ for (Track track : movie.getTracks()) {
+ if (track.getMediaHeaderBox() instanceof VideoMediaHeaderBox) {
+ videoFragmentsDurations = checkFragmentsAlign(videoFragmentsDurations, calculateFragmentDurations(track, movie));
+ SampleDescriptionBox stsd = track.getSampleDescriptionBox();
+ videoQualities.add(getVideoQuality(track, (VisualSampleEntry) stsd.getSampleEntry()));
+ if (videoTimescale == -1) {
+ videoTimescale = track.getTrackMetaData().getTimescale();
+ } else {
+ assert videoTimescale == track.getTrackMetaData().getTimescale();
+ }
+ }
+ if (track.getMediaHeaderBox() instanceof SoundMediaHeaderBox) {
+ audioFragmentsDurations = checkFragmentsAlign(audioFragmentsDurations, calculateFragmentDurations(track, movie));
+ SampleDescriptionBox stsd = track.getSampleDescriptionBox();
+ audioQualities.add(getAudioQuality(track, (AudioSampleEntry) stsd.getSampleEntry()));
+ if (audioTimescale == -1) {
+ audioTimescale = track.getTrackMetaData().getTimescale();
+ } else {
+ assert audioTimescale == track.getTrackMetaData().getTimescale();
+ }
+
+ }
+ }
+ DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder documentBuilder;
+ try {
+ documentBuilder = documentBuilderFactory.newDocumentBuilder();
+ } catch (ParserConfigurationException e) {
+ throw new IOException(e);
+ }
+ Document document = documentBuilder.newDocument();
+
+
+ Element smoothStreamingMedia = document.createElement("SmoothStreamingMedia");
+ document.appendChild(smoothStreamingMedia);
+ smoothStreamingMedia.setAttribute("MajorVersion", "2");
+ smoothStreamingMedia.setAttribute("MinorVersion", "1");
+// silverlight ignores the timescale attr smoothStreamingMedia.addAttribute(new Attribute("TimeScale", Long.toString(movieTimeScale)));
+ smoothStreamingMedia.setAttribute("Duration", "0");
+
+ smoothStreamingMedia.appendChild(document.createComment(Version.VERSION));
+ Element videoStreamIndex = document.createElement("StreamIndex");
+ videoStreamIndex.setAttribute("Type", "video");
+ videoStreamIndex.setAttribute("TimeScale", Long.toString(videoTimescale)); // silverlight ignores the timescale attr
+ videoStreamIndex.setAttribute("Chunks", Integer.toString(videoFragmentsDurations.length));
+ videoStreamIndex.setAttribute("Url", "video/{bitrate}/{start time}");
+ videoStreamIndex.setAttribute("QualityLevels", Integer.toString(videoQualities.size()));
+ smoothStreamingMedia.appendChild(videoStreamIndex);
+
+ for (int i = 0; i < videoQualities.size(); i++) {
+ VideoQuality vq = videoQualities.get(i);
+ Element qualityLevel = document.createElement("QualityLevel");
+ qualityLevel.setAttribute("Index", Integer.toString(i));
+ qualityLevel.setAttribute("Bitrate", Long.toString(vq.bitrate));
+ qualityLevel.setAttribute("FourCC", vq.fourCC);
+ qualityLevel.setAttribute("MaxWidth", Long.toString(vq.width));
+ qualityLevel.setAttribute("MaxHeight", Long.toString(vq.height));
+ qualityLevel.setAttribute("CodecPrivateData", vq.codecPrivateData);
+ qualityLevel.setAttribute("NALUnitLengthField", Integer.toString(vq.nalLength));
+ videoStreamIndex.appendChild(qualityLevel);
+ }
+
+ for (int i = 0; i < videoFragmentsDurations.length; i++) {
+ Element c = document.createElement("c");
+ c.setAttribute("n", Integer.toString(i));
+ c.setAttribute("d", Long.toString(videoFragmentsDurations[i]));
+ videoStreamIndex.appendChild(c);
+ }
+
+ if (audioFragmentsDurations != null) {
+ Element audioStreamIndex = document.createElement("StreamIndex");
+ audioStreamIndex.setAttribute("Type", "audio");
+ audioStreamIndex.setAttribute("TimeScale", Long.toString(audioTimescale)); // silverlight ignores the timescale attr
+ audioStreamIndex.setAttribute("Chunks", Integer.toString(audioFragmentsDurations.length));
+ audioStreamIndex.setAttribute("Url", "audio/{bitrate}/{start time}");
+ audioStreamIndex.setAttribute("QualityLevels", Integer.toString(audioQualities.size()));
+ smoothStreamingMedia.appendChild(audioStreamIndex);
+
+ for (int i = 0; i < audioQualities.size(); i++) {
+ AudioQuality aq = audioQualities.get(i);
+ Element qualityLevel = document.createElement("QualityLevel");
+ qualityLevel.setAttribute("Index", Integer.toString(i));
+ qualityLevel.setAttribute("FourCC", aq.fourCC);
+ qualityLevel.setAttribute("Bitrate", Long.toString(aq.bitrate));
+ qualityLevel.setAttribute("AudioTag", Integer.toString(aq.audioTag));
+ qualityLevel.setAttribute("SamplingRate", Long.toString(aq.samplingRate));
+ qualityLevel.setAttribute("Channels", Integer.toString(aq.channels));
+ qualityLevel.setAttribute("BitsPerSample", Integer.toString(aq.bitPerSample));
+ qualityLevel.setAttribute("PacketSize", Integer.toString(aq.packetSize));
+ qualityLevel.setAttribute("CodecPrivateData", aq.codecPrivateData);
+ audioStreamIndex.appendChild(qualityLevel);
+ }
+ for (int i = 0; i < audioFragmentsDurations.length; i++) {
+ Element c = document.createElement("c");
+ c.setAttribute("n", Integer.toString(i));
+ c.setAttribute("d", Long.toString(audioFragmentsDurations[i]));
+ audioStreamIndex.appendChild(c);
+ }
+ }
+
+ document.setXmlStandalone(true);
+ Source source = new DOMSource(document);
+ StringWriter stringWriter = new StringWriter();
+ Result result = new StreamResult(stringWriter);
+ TransformerFactory factory = TransformerFactory.newInstance();
+ Transformer transformer;
+ try {
+ transformer = factory.newTransformer();
+ transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+ transformer.transform(source, result);
+ } catch (TransformerConfigurationException e) {
+ throw new IOException(e);
+ } catch (TransformerException e) {
+ throw new IOException(e);
+ }
+ return stringWriter.getBuffer().toString();
+
+
+ }
+
+ private AudioQuality getAudioQuality(Track track, AudioSampleEntry ase) {
+ if (getFormat(ase).equals("mp4a")) {
+ return getAacAudioQuality(track, ase);
+ } else if (getFormat(ase).equals("ec-3")) {
+ return getEc3AudioQuality(track, ase);
+ } else if (getFormat(ase).startsWith("dts")) {
+ return getDtsAudioQuality(track, ase);
+ } else {
+ throw new InternalError("I don't know what to do with audio of type " + getFormat(ase));
+ }
+
+ }
+
+ private AudioQuality getAacAudioQuality(Track track, AudioSampleEntry ase) {
+ AudioQuality l = new AudioQuality();
+ final ESDescriptorBox esDescriptorBox = ase.getBoxes(ESDescriptorBox.class).get(0);
+ final AudioSpecificConfig audioSpecificConfig = esDescriptorBox.getEsDescriptor().getDecoderConfigDescriptor().getAudioSpecificInfo();
+ if (audioSpecificConfig.getSbrPresentFlag() == 1) {
+ l.fourCC = "AACH";
+ } else if (audioSpecificConfig.getPsPresentFlag() == 1) {
+ l.fourCC = "AACP"; //I'm not sure if that's what MS considers as AAC+ - because actually AAC+ and AAC-HE should be the same...
+ } else {
+ l.fourCC = "AACL";
+ }
+ l.bitrate = getBitrate(track);
+ l.audioTag = 255;
+ l.samplingRate = ase.getSampleRate();
+ l.channels = ase.getChannelCount();
+ l.bitPerSample = ase.getSampleSize();
+ l.packetSize = 4;
+ l.codecPrivateData = getAudioCodecPrivateData(audioSpecificConfig);
+ //Index="0" Bitrate="103000" AudioTag="255" SamplingRate="44100" Channels="2" BitsPerSample="16" packetSize="4" CodecPrivateData=""
+ return l;
+ }
+
+ private AudioQuality getEc3AudioQuality(Track track, AudioSampleEntry ase) {
+ final EC3SpecificBox ec3SpecificBox = ase.getBoxes(EC3SpecificBox.class).get(0);
+ if (ec3SpecificBox == null) {
+ throw new RuntimeException("EC-3 track misses EC3SpecificBox!");
+ }
+
+ short nfchans = 0; //full bandwidth channels
+ short lfechans = 0;
+ byte dWChannelMaskFirstByte = 0;
+ byte dWChannelMaskSecondByte = 0;
+ for (EC3SpecificBox.Entry entry : ec3SpecificBox.getEntries()) {
+ /*
+ Table 4.3: Audio coding mode
+ acmod Audio coding mode Nfchans Channel array ordering
+ 000 1 + 1 2 Ch1, Ch2
+ 001 1/0 1 C
+ 010 2/0 2 L, R
+ 011 3/0 3 L, C, R
+ 100 2/1 3 L, R, S
+ 101 3/1 4 L, C, R, S
+ 110 2/2 4 L, R, SL, SR
+ 111 3/2 5 L, C, R, SL, SR
+
+ Table F.2: Chan_loc field bit assignments
+ Bit Location
+ 0 Lc/Rc pair
+ 1 Lrs/Rrs pair
+ 2 Cs
+ 3 Ts
+ 4 Lsd/Rsd pair
+ 5 Lw/Rw pair
+ 6 Lvh/Rvh pair
+ 7 Cvh
+ 8 LFE2
+ */
+ switch (entry.acmod) {
+ case 0: //1+1; Ch1, Ch2
+ nfchans += 2;
+ throw new RuntimeException("Smooth Streaming doesn't support DDP 1+1 mode");
+ case 1: //1/0; C
+ nfchans += 1;
+ if (entry.num_dep_sub > 0) {
+ DependentSubstreamMask dependentSubstreamMask = new DependentSubstreamMask(dWChannelMaskFirstByte, dWChannelMaskSecondByte, entry).process();
+ dWChannelMaskFirstByte |= dependentSubstreamMask.getdWChannelMaskFirstByte();
+ dWChannelMaskSecondByte |= dependentSubstreamMask.getdWChannelMaskSecondByte();
+ } else {
+ dWChannelMaskFirstByte |= 0x20;
+ }
+ break;
+ case 2: //2/0; L, R
+ nfchans += 2;
+ if (entry.num_dep_sub > 0) {
+ DependentSubstreamMask dependentSubstreamMask = new DependentSubstreamMask(dWChannelMaskFirstByte, dWChannelMaskSecondByte, entry).process();
+ dWChannelMaskFirstByte |= dependentSubstreamMask.getdWChannelMaskFirstByte();
+ dWChannelMaskSecondByte |= dependentSubstreamMask.getdWChannelMaskSecondByte();
+ } else {
+ dWChannelMaskFirstByte |= 0xC0;
+ }
+ break;
+ case 3: //3/0; L, C, R
+ nfchans += 3;
+ if (entry.num_dep_sub > 0) {
+ DependentSubstreamMask dependentSubstreamMask = new DependentSubstreamMask(dWChannelMaskFirstByte, dWChannelMaskSecondByte, entry).process();
+ dWChannelMaskFirstByte |= dependentSubstreamMask.getdWChannelMaskFirstByte();
+ dWChannelMaskSecondByte |= dependentSubstreamMask.getdWChannelMaskSecondByte();
+ } else {
+ dWChannelMaskFirstByte |= 0xE0;
+ }
+ break;
+ case 4: //2/1; L, R, S
+ nfchans += 3;
+ if (entry.num_dep_sub > 0) {
+ DependentSubstreamMask dependentSubstreamMask = new DependentSubstreamMask(dWChannelMaskFirstByte, dWChannelMaskSecondByte, entry).process();
+ dWChannelMaskFirstByte |= dependentSubstreamMask.getdWChannelMaskFirstByte();
+ dWChannelMaskSecondByte |= dependentSubstreamMask.getdWChannelMaskSecondByte();
+ } else {
+ dWChannelMaskFirstByte |= 0xC0;
+ dWChannelMaskSecondByte |= 0x80;
+ }
+ break;
+ case 5: //3/1; L, C, R, S
+ nfchans += 4;
+ if (entry.num_dep_sub > 0) {
+ DependentSubstreamMask dependentSubstreamMask = new DependentSubstreamMask(dWChannelMaskFirstByte, dWChannelMaskSecondByte, entry).process();
+ dWChannelMaskFirstByte |= dependentSubstreamMask.getdWChannelMaskFirstByte();
+ dWChannelMaskSecondByte |= dependentSubstreamMask.getdWChannelMaskSecondByte();
+ } else {
+ dWChannelMaskFirstByte |= 0xE0;
+ dWChannelMaskSecondByte |= 0x80;
+ }
+ break;
+ case 6: //2/2; L, R, SL, SR
+ nfchans += 4;
+ if (entry.num_dep_sub > 0) {
+ DependentSubstreamMask dependentSubstreamMask = new DependentSubstreamMask(dWChannelMaskFirstByte, dWChannelMaskSecondByte, entry).process();
+ dWChannelMaskFirstByte |= dependentSubstreamMask.getdWChannelMaskFirstByte();
+ dWChannelMaskSecondByte |= dependentSubstreamMask.getdWChannelMaskSecondByte();
+ } else {
+ dWChannelMaskFirstByte |= 0xCC;
+ }
+ break;
+ case 7: //3/2; L, C, R, SL, SR
+ nfchans += 5;
+ if (entry.num_dep_sub > 0) {
+ DependentSubstreamMask dependentSubstreamMask = new DependentSubstreamMask(dWChannelMaskFirstByte, dWChannelMaskSecondByte, entry).process();
+ dWChannelMaskFirstByte |= dependentSubstreamMask.getdWChannelMaskFirstByte();
+ dWChannelMaskSecondByte |= dependentSubstreamMask.getdWChannelMaskSecondByte();
+ } else {
+ dWChannelMaskFirstByte |= 0xEC;
+ }
+ break;
+ }
+ if (entry.lfeon == 1) {
+ lfechans ++;
+ dWChannelMaskFirstByte |= 0x10;
+ }
+ }
+
+ final ByteBuffer waveformatex = ByteBuffer.allocate(22);
+ waveformatex.put(new byte[]{0x00, 0x06}); //1536 wSamplesPerBlock - little endian
+ waveformatex.put(dWChannelMaskFirstByte);
+ waveformatex.put(dWChannelMaskSecondByte);
+ waveformatex.put(new byte[]{0x00, 0x00}); //pad dwChannelMask to 32bit
+ waveformatex.put(new byte[]{(byte)0xAF, (byte)0x87, (byte)0xFB, (byte)0xA7, 0x02, 0x2D, (byte)0xFB, 0x42, (byte)0xA4, (byte)0xD4, 0x05, (byte)0xCD, (byte)0x93, (byte)0x84, 0x3B, (byte)0xDD}); //SubFormat - Dolby Digital Plus GUID
+
+ final ByteBuffer dec3Content = ByteBuffer.allocate((int) ec3SpecificBox.getContentSize());
+ ec3SpecificBox.getContent(dec3Content);
+
+ AudioQuality l = new AudioQuality();
+ l.fourCC = "EC-3";
+ l.bitrate = getBitrate(track);
+ l.audioTag = 65534;
+ l.samplingRate = ase.getSampleRate();
+ l.channels = nfchans + lfechans;
+ l.bitPerSample = 16;
+ l.packetSize = track.getSamples().get(0).limit(); //assuming all are same size
+ l.codecPrivateData = Hex.encodeHex(waveformatex.array()) + Hex.encodeHex(dec3Content.array()); //append EC3SpecificBox (big endian) at the end of waveformatex
+ return l;
+ }
+
+ private AudioQuality getDtsAudioQuality(Track track, AudioSampleEntry ase) {
+ final DTSSpecificBox dtsSpecificBox = ase.getBoxes(DTSSpecificBox.class).get(0);
+ if (dtsSpecificBox == null) {
+ throw new RuntimeException("DTS track misses DTSSpecificBox!");
+ }
+
+ final ByteBuffer waveformatex = ByteBuffer.allocate(22);
+ final int frameDuration = dtsSpecificBox.getFrameDuration();
+ short samplesPerBlock = 0;
+ switch (frameDuration) {
+ case 0:
+ samplesPerBlock = 512;
+ break;
+ case 1:
+ samplesPerBlock = 1024;
+ break;
+ case 2:
+ samplesPerBlock = 2048;
+ break;
+ case 3:
+ samplesPerBlock = 4096;
+ break;
+ }
+ waveformatex.put((byte) (samplesPerBlock & 0xff));
+ waveformatex.put((byte) (samplesPerBlock >>> 8));
+ final int dwChannelMask = getNumChannelsAndMask(dtsSpecificBox)[1];
+ waveformatex.put((byte) (dwChannelMask & 0xff));
+ waveformatex.put((byte) (dwChannelMask >>> 8));
+ waveformatex.put((byte) (dwChannelMask >>> 16));
+ waveformatex.put((byte) (dwChannelMask >>> 24));
+ waveformatex.put(new byte[]{(byte)0xAE, (byte)0xE4, (byte)0xBF, (byte)0x5E, (byte)0x61, (byte)0x5E, (byte)0x41, (byte)0x87, (byte)0x92, (byte)0xFC, (byte)0xA4, (byte)0x81, (byte)0x26, (byte)0x99, (byte)0x02, (byte)0x11}); //DTS-HD GUID
+
+ final ByteBuffer dtsCodecPrivateData = ByteBuffer.allocate(8);
+ dtsCodecPrivateData.put((byte) dtsSpecificBox.getStreamConstruction());
+
+ final int channelLayout = dtsSpecificBox.getChannelLayout();
+ dtsCodecPrivateData.put((byte) (channelLayout & 0xff));
+ dtsCodecPrivateData.put((byte) (channelLayout >>> 8));
+ dtsCodecPrivateData.put((byte) (channelLayout >>> 16));
+ dtsCodecPrivateData.put((byte) (channelLayout >>> 24));
+
+ byte dtsFlags = (byte) (dtsSpecificBox.getMultiAssetFlag() << 1);
+ dtsFlags |= dtsSpecificBox.getLBRDurationMod();
+ dtsCodecPrivateData.put(dtsFlags);
+ dtsCodecPrivateData.put(new byte[]{0x00, 0x00}); //reserved
+
+ AudioQuality l = new AudioQuality();
+ l.fourCC = getFormat(ase);
+ l.bitrate = dtsSpecificBox.getAvgBitRate();
+ l.audioTag = 65534;
+ l.samplingRate = dtsSpecificBox.getDTSSamplingFrequency();
+ l.channels = getNumChannelsAndMask(dtsSpecificBox)[0];
+ l.bitPerSample = 16;
+ l.packetSize = track.getSamples().get(0).limit(); //assuming all are same size
+ l.codecPrivateData = Hex.encodeHex(waveformatex.array()) + Hex.encodeHex(dtsCodecPrivateData.array());
+ return l;
+
+ }
+
+ /* dwChannelMask
+ L SPEAKER_FRONT_LEFT 0x00000001
+ R SPEAKER_FRONT_RIGHT 0x00000002
+ C SPEAKER_FRONT_CENTER 0x00000004
+ LFE1 SPEAKER_LOW_FREQUENCY 0x00000008
+ Ls or Lsr* SPEAKER_BACK_LEFT 0x00000010
+ Rs or Rsr* SPEAKER_BACK_RIGHT 0x00000020
+ Lc SPEAKER_FRONT_LEFT_OF_CENTER 0x00000040
+ Rc SPEAKER_FRONT_RIGHT_OF_CENTER 0x00000080
+ Cs SPEAKER_BACK_CENTER 0x00000100
+ Lss SPEAKER_SIDE_LEFT 0x00000200
+ Rss SPEAKER_SIDE_RIGHT 0x00000400
+ Oh SPEAKER_TOP_CENTER 0x00000800
+ Lh SPEAKER_TOP_FRONT_LEFT 0x00001000
+ Ch SPEAKER_TOP_FRONT_CENTER 0x00002000
+ Rh SPEAKER_TOP_FRONT_RIGHT 0x00004000
+ Lhr SPEAKER_TOP_BACK_LEFT 0x00008000
+ Chf SPEAKER_TOP_BACK_CENTER 0x00010000
+ Rhr SPEAKER_TOP_BACK_RIGHT 0x00020000
+ SPEAKER_RESERVED 0x80000000
+
+ * if Lss, Rss exist, then this position is equivalent to Lsr, Rsr respectively
+ */
+ private int[] getNumChannelsAndMask(DTSSpecificBox dtsSpecificBox) {
+ final int channelLayout = dtsSpecificBox.getChannelLayout();
+ int numChannels = 0;
+ int dwChannelMask = 0;
+ if ((channelLayout & 0x0001) == 0x0001) {
+ //0001h Center in front of listener 1
+ numChannels += 1;
+ dwChannelMask |= 0x00000004; //SPEAKER_FRONT_CENTER
+ }
+ if ((channelLayout & 0x0002) == 0x0002) {
+ //0002h Left/Right in front 2
+ numChannels += 2;
+ dwChannelMask |= 0x00000001; //SPEAKER_FRONT_LEFT
+ dwChannelMask |= 0x00000002; //SPEAKER_FRONT_RIGHT
+ }
+ if ((channelLayout & 0x0004) == 0x0004) {
+ //0004h Left/Right surround on side in rear 2
+ numChannels += 2;
+ //* if Lss, Rss exist, then this position is equivalent to Lsr, Rsr respectively
+ dwChannelMask |= 0x00000010; //SPEAKER_BACK_LEFT
+ dwChannelMask |= 0x00000020; //SPEAKER_BACK_RIGHT
+ }
+ if ((channelLayout & 0x0008) == 0x0008) {
+ //0008h Low frequency effects subwoofer 1
+ numChannels += 1;
+ dwChannelMask |= 0x00000008; //SPEAKER_LOW_FREQUENCY
+ }
+ if ((channelLayout & 0x0010) == 0x0010) {
+ //0010h Center surround in rear 1
+ numChannels += 1;
+ dwChannelMask |= 0x00000100; //SPEAKER_BACK_CENTER
+ }
+ if ((channelLayout & 0x0020) == 0x0020) {
+ //0020h Left/Right height in front 2
+ numChannels += 2;
+ dwChannelMask |= 0x00001000; //SPEAKER_TOP_FRONT_LEFT
+ dwChannelMask |= 0x00004000; //SPEAKER_TOP_FRONT_RIGHT
+ }
+ if ((channelLayout & 0x0040) == 0x0040) {
+ //0040h Left/Right surround in rear 2
+ numChannels += 2;
+ dwChannelMask |= 0x00000010; //SPEAKER_BACK_LEFT
+ dwChannelMask |= 0x00000020; //SPEAKER_BACK_RIGHT
+ }
+ if ((channelLayout & 0x0080) == 0x0080) {
+ //0080h Center Height in front 1
+ numChannels += 1;
+ dwChannelMask |= 0x00002000; //SPEAKER_TOP_FRONT_CENTER
+ }
+ if ((channelLayout & 0x0100) == 0x0100) {
+ //0100h Over the listener’s head 1
+ numChannels += 1;
+ dwChannelMask |= 0x00000800; //SPEAKER_TOP_CENTER
+ }
+ if ((channelLayout & 0x0200) == 0x0200) {
+ //0200h Between left/right and center in front 2
+ numChannels += 2;
+ dwChannelMask |= 0x00000040; //SPEAKER_FRONT_LEFT_OF_CENTER
+ dwChannelMask |= 0x00000080; //SPEAKER_FRONT_RIGHT_OF_CENTER
+ }
+ if ((channelLayout & 0x0400) == 0x0400) {
+ //0400h Left/Right on side in front 2
+ numChannels += 2;
+ dwChannelMask |= 0x00000200; //SPEAKER_SIDE_LEFT
+ dwChannelMask |= 0x00000400; //SPEAKER_SIDE_RIGHT
+ }
+ if ((channelLayout & 0x0800) == 0x0800) {
+ //0800h Left/Right surround on side 2
+ numChannels += 2;
+ //* if Lss, Rss exist, then this position is equivalent to Lsr, Rsr respectively
+ dwChannelMask |= 0x00000010; //SPEAKER_BACK_LEFT
+ dwChannelMask |= 0x00000020; //SPEAKER_BACK_RIGHT
+ }
+ if ((channelLayout & 0x1000) == 0x1000) {
+ //1000h Second low frequency effects subwoofer 1
+ numChannels += 1;
+ dwChannelMask |= 0x00000008; //SPEAKER_LOW_FREQUENCY
+ }
+ if ((channelLayout & 0x2000) == 0x2000) {
+ //2000h Left/Right height on side 2
+ numChannels += 2;
+ dwChannelMask |= 0x00000010; //SPEAKER_BACK_LEFT
+ dwChannelMask |= 0x00000020; //SPEAKER_BACK_RIGHT
+ }
+ if ((channelLayout & 0x4000) == 0x4000) {
+ //4000h Center height in rear 1
+ numChannels += 1;
+ dwChannelMask |= 0x00010000; //SPEAKER_TOP_BACK_CENTER
+ }
+ if ((channelLayout & 0x8000) == 0x8000) {
+ //8000h Left/Right height in rear 2
+ numChannels += 2;
+ dwChannelMask |= 0x00008000; //SPEAKER_TOP_BACK_LEFT
+ dwChannelMask |= 0x00020000; //SPEAKER_TOP_BACK_RIGHT
+ }
+ if ((channelLayout & 0x10000) == 0x10000) {
+ //10000h Center below in front
+ numChannels += 1;
+ }
+ if ((channelLayout & 0x20000) == 0x20000) {
+ //20000h Left/Right below in front
+ numChannels += 2;
+ }
+ return new int[]{numChannels, dwChannelMask};
+ }
+
+ private String getAudioCodecPrivateData(AudioSpecificConfig audioSpecificConfig) {
+ byte[] configByteArray = audioSpecificConfig.getConfigBytes();
+ return Hex.encodeHex(configByteArray);
+ }
+
+ private VideoQuality getVideoQuality(Track track, VisualSampleEntry vse) {
+ VideoQuality l;
+ if ("avc1".equals(getFormat(vse))) {
+ AvcConfigurationBox avcConfigurationBox = vse.getBoxes(AvcConfigurationBox.class).get(0);
+ l = new VideoQuality();
+ l.bitrate = getBitrate(track);
+ l.codecPrivateData = Hex.encodeHex(getAvcCodecPrivateData(avcConfigurationBox));
+ l.fourCC = "AVC1";
+ l.width = vse.getWidth();
+ l.height = vse.getHeight();
+ l.nalLength = avcConfigurationBox.getLengthSizeMinusOne() + 1;
+ } else {
+ throw new InternalError("I don't know how to handle video of type " + getFormat(vse));
+ }
+ return l;
+ }
+
+ private byte[] getAvcCodecPrivateData(AvcConfigurationBox avcConfigurationBox) {
+ List<byte[]> sps = avcConfigurationBox.getSequenceParameterSets();
+ List<byte[]> pps = avcConfigurationBox.getPictureParameterSets();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ baos.write(new byte[]{0, 0, 0, 1});
+
+ for (byte[] sp : sps) {
+ baos.write(sp);
+ }
+ baos.write(new byte[]{0, 0, 0, 1});
+ for (byte[] pp : pps) {
+ baos.write(pp);
+ }
+ } catch (IOException ex) {
+ throw new RuntimeException("ByteArrayOutputStream do not throw IOException ?!?!?");
+ }
+ return baos.toByteArray();
+ }
+
+ private class DependentSubstreamMask {
+ private byte dWChannelMaskFirstByte;
+ private byte dWChannelMaskSecondByte;
+ private EC3SpecificBox.Entry entry;
+
+ public DependentSubstreamMask(byte dWChannelMaskFirstByte, byte dWChannelMaskSecondByte, EC3SpecificBox.Entry entry) {
+ this.dWChannelMaskFirstByte = dWChannelMaskFirstByte;
+ this.dWChannelMaskSecondByte = dWChannelMaskSecondByte;
+ this.entry = entry;
+ }
+
+ public byte getdWChannelMaskFirstByte() {
+ return dWChannelMaskFirstByte;
+ }
+
+ public byte getdWChannelMaskSecondByte() {
+ return dWChannelMaskSecondByte;
+ }
+
+ public DependentSubstreamMask process() {
+ switch (entry.chan_loc) {
+ case 0:
+ dWChannelMaskFirstByte |= 0x3;
+ break;
+ case 1:
+ dWChannelMaskFirstByte |= 0xC;
+ break;
+ case 2:
+ dWChannelMaskSecondByte |= 0x80;
+ break;
+ case 3:
+ dWChannelMaskSecondByte |= 0x8;
+ break;
+ case 6:
+ dWChannelMaskSecondByte |= 0x5;
+ break;
+ case 7:
+ dWChannelMaskSecondByte |= 0x2;
+ break;
+ }
+ return this;
+ }
+ }
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/FlatPackageWriterImpl.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/FlatPackageWriterImpl.java
new file mode 100644
index 0000000..3e3847c
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/FlatPackageWriterImpl.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.adaptivestreaming;
+
+import com.coremedia.iso.IsoFile;
+import com.coremedia.iso.boxes.Box;
+import com.coremedia.iso.boxes.SoundMediaHeaderBox;
+import com.coremedia.iso.boxes.VideoMediaHeaderBox;
+import com.coremedia.iso.boxes.fragment.MovieFragmentBox;
+import com.googlecode.mp4parser.authoring.Movie;
+import com.googlecode.mp4parser.authoring.Track;
+import com.googlecode.mp4parser.authoring.builder.*;
+import com.googlecode.mp4parser.authoring.tracks.ChangeTimeScaleTrack;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.channels.FileChannel;
+import java.util.Iterator;
+import java.util.logging.Logger;
+
+public class FlatPackageWriterImpl implements PackageWriter {
+ private static Logger LOG = Logger.getLogger(FlatPackageWriterImpl.class.getName());
+ long timeScale = 10000000;
+
+ private File outputDirectory;
+ private boolean debugOutput;
+ private FragmentedMp4Builder ismvBuilder;
+ ManifestWriter manifestWriter;
+
+ public FlatPackageWriterImpl() {
+ ismvBuilder = new FragmentedMp4Builder();
+ FragmentIntersectionFinder intersectionFinder = new SyncSampleIntersectFinderImpl();
+ ismvBuilder.setIntersectionFinder(intersectionFinder);
+ manifestWriter = new FlatManifestWriterImpl(intersectionFinder);
+ }
+
+ /**
+ * Creates a factory for a smooth streaming package. A smooth streaming package is
+ * a collection of files that can be served by a webserver as a smooth streaming
+ * stream.
+ * @param minFragmentDuration the smallest allowable duration of a fragment (0 == no restriction).
+ */
+ public FlatPackageWriterImpl(int minFragmentDuration) {
+ ismvBuilder = new FragmentedMp4Builder();
+ FragmentIntersectionFinder intersectionFinder = new SyncSampleIntersectFinderImpl(minFragmentDuration);
+ ismvBuilder.setIntersectionFinder(intersectionFinder);
+ manifestWriter = new FlatManifestWriterImpl(intersectionFinder);
+ }
+
+ public void setOutputDirectory(File outputDirectory) {
+ assert outputDirectory.isDirectory();
+ this.outputDirectory = outputDirectory;
+
+ }
+
+ public void setDebugOutput(boolean debugOutput) {
+ this.debugOutput = debugOutput;
+ }
+
+ public void setIsmvBuilder(FragmentedMp4Builder ismvBuilder) {
+ this.ismvBuilder = ismvBuilder;
+ this.manifestWriter = new FlatManifestWriterImpl(ismvBuilder.getFragmentIntersectionFinder());
+ }
+
+ public void setManifestWriter(ManifestWriter manifestWriter) {
+ this.manifestWriter = manifestWriter;
+ }
+
+ /**
+ * Writes the movie given as <code>qualities</code> flattened into the
+ * <code>outputDirectory</code>.
+ *
+ * @param source the source movie with all qualities
+ * @throws IOException
+ */
+ public void write(Movie source) throws IOException {
+
+ if (debugOutput) {
+ outputDirectory.mkdirs();
+ DefaultMp4Builder defaultMp4Builder = new DefaultMp4Builder();
+ IsoFile muxed = defaultMp4Builder.build(source);
+ File muxedFile = new File(outputDirectory, "debug_1_muxed.mp4");
+ FileOutputStream muxedFileOutputStream = new FileOutputStream(muxedFile);
+ muxed.getBox(muxedFileOutputStream.getChannel());
+ muxedFileOutputStream.close();
+ }
+ Movie cleanedSource = removeUnknownTracks(source);
+ Movie movieWithAdjustedTimescale = correctTimescale(cleanedSource);
+
+ if (debugOutput) {
+ DefaultMp4Builder defaultMp4Builder = new DefaultMp4Builder();
+ IsoFile muxed = defaultMp4Builder.build(movieWithAdjustedTimescale);
+ File muxedFile = new File(outputDirectory, "debug_2_timescale.mp4");
+ FileOutputStream muxedFileOutputStream = new FileOutputStream(muxedFile);
+ muxed.getBox(muxedFileOutputStream.getChannel());
+ muxedFileOutputStream.close();
+ }
+ IsoFile isoFile = ismvBuilder.build(movieWithAdjustedTimescale);
+ if (debugOutput) {
+ File allQualities = new File(outputDirectory, "debug_3_fragmented.mp4");
+ FileOutputStream allQualis = new FileOutputStream(allQualities);
+ isoFile.getBox(allQualis.getChannel());
+ allQualis.close();
+ }
+
+
+ for (Track track : movieWithAdjustedTimescale.getTracks()) {
+ String bitrate = Long.toString(manifestWriter.getBitrate(track));
+ long trackId = track.getTrackMetaData().getTrackId();
+ Iterator<Box> boxIt = isoFile.getBoxes().iterator();
+ File mediaOutDir;
+ if (track.getMediaHeaderBox() instanceof SoundMediaHeaderBox) {
+ mediaOutDir = new File(outputDirectory, "audio");
+
+ } else if (track.getMediaHeaderBox() instanceof VideoMediaHeaderBox) {
+ mediaOutDir = new File(outputDirectory, "video");
+ } else {
+ System.err.println("Skipping Track with handler " + track.getHandler() + " and " + track.getMediaHeaderBox().getClass().getSimpleName());
+ continue;
+ }
+ File bitRateOutputDir = new File(mediaOutDir, bitrate);
+ bitRateOutputDir.mkdirs();
+ LOG.finer("Created : " + bitRateOutputDir.getCanonicalPath());
+
+ long[] fragmentTimes = manifestWriter.calculateFragmentDurations(track, movieWithAdjustedTimescale);
+ long startTime = 0;
+ int currentFragment = 0;
+ while (boxIt.hasNext()) {
+ Box b = boxIt.next();
+ if (b instanceof MovieFragmentBox) {
+ assert ((MovieFragmentBox) b).getTrackCount() == 1;
+ if (((MovieFragmentBox) b).getTrackNumbers()[0] == trackId) {
+ FileOutputStream fos = new FileOutputStream(new File(bitRateOutputDir, Long.toString(startTime)));
+ startTime += fragmentTimes[currentFragment++];
+ FileChannel fc = fos.getChannel();
+ Box mdat = boxIt.next();
+ assert mdat.getType().equals("mdat");
+ b.getBox(fc); // moof
+ mdat.getBox(fc); // mdat
+ fc.truncate(fc.position());
+ fc.close();
+ }
+ }
+
+ }
+ }
+ FileWriter fw = new FileWriter(new File(outputDirectory, "Manifest"));
+ fw.write(manifestWriter.getManifest(movieWithAdjustedTimescale));
+ fw.close();
+
+ }
+
+ private Movie removeUnknownTracks(Movie source) {
+ Movie nuMovie = new Movie();
+ for (Track track : source.getTracks()) {
+ if ("vide".equals(track.getHandler()) || "soun".equals(track.getHandler())) {
+ nuMovie.addTrack(track);
+ } else {
+ LOG.fine("Removed track " + track);
+ }
+ }
+ return nuMovie;
+ }
+
+
+ /**
+ * Returns a new <code>Movie</code> in that all tracks have the timescale 10000000. CTS & DTS are modified
+ * in a way that even with more than one framerate the fragments exactly begin at the same time.
+ *
+ * @param movie
+ * @return a movie with timescales suitable for smooth streaming manifests
+ */
+ public Movie correctTimescale(Movie movie) {
+ Movie nuMovie = new Movie();
+ for (Track track : movie.getTracks()) {
+ nuMovie.addTrack(new ChangeTimeScaleTrack(track, timeScale, ismvBuilder.getFragmentIntersectionFinder().sampleNumbers(track, movie)));
+ }
+ return nuMovie;
+
+ }
+
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/ManifestWriter.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/ManifestWriter.java
new file mode 100644
index 0000000..2b2ba7d
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/ManifestWriter.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.adaptivestreaming;
+
+
+import com.googlecode.mp4parser.authoring.Movie;
+import com.googlecode.mp4parser.authoring.Track;
+
+import java.io.IOException;
+
+public interface ManifestWriter {
+ String getManifest(Movie inputs) throws IOException;
+
+ long getBitrate(Track track);
+
+ long[] calculateFragmentDurations(Track track, Movie movie);
+
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/PackageWriter.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/PackageWriter.java
new file mode 100644
index 0000000..0d97fc5
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/PackageWriter.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.adaptivestreaming;
+
+import com.googlecode.mp4parser.authoring.Movie;
+
+import java.io.IOException;
+
+/**
+ * Writes the whole package.
+ */
+public interface PackageWriter {
+ public void write(Movie qualities) throws IOException;
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/VideoQuality.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/VideoQuality.java
new file mode 100644
index 0000000..4a70e47
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/adaptivestreaming/VideoQuality.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.adaptivestreaming;
+
+class VideoQuality {
+ long bitrate;
+ String fourCC;
+ int width;
+ int height;
+ String codecPrivateData;
+ int nalLength;
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/all-wcprops b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/all-wcprops
new file mode 100644
index 0000000..9204edf
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/all-wcprops
@@ -0,0 +1,47 @@
+K 25
+svn:wc:ra_dav:version-url
+V 90
+/svn/!svn/ver/776/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder
+END
+FragmentIntersectionFinder.java
+K 25
+svn:wc:ra_dav:version-url
+V 122
+/svn/!svn/ver/455/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/FragmentIntersectionFinder.java
+END
+TwoSecondIntersectionFinder.java
+K 25
+svn:wc:ra_dav:version-url
+V 123
+/svn/!svn/ver/658/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/TwoSecondIntersectionFinder.java
+END
+FragmentedMp4Builder.java
+K 25
+svn:wc:ra_dav:version-url
+V 116
+/svn/!svn/ver/707/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/FragmentedMp4Builder.java
+END
+Mp4Builder.java
+K 25
+svn:wc:ra_dav:version-url
+V 106
+/svn/!svn/ver/672/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/Mp4Builder.java
+END
+SyncSampleIntersectFinderImpl.java
+K 25
+svn:wc:ra_dav:version-url
+V 125
+/svn/!svn/ver/774/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/SyncSampleIntersectFinderImpl.java
+END
+DefaultMp4Builder.java
+K 25
+svn:wc:ra_dav:version-url
+V 113
+/svn/!svn/ver/776/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/DefaultMp4Builder.java
+END
+ByteBufferHelper.java
+K 25
+svn:wc:ra_dav:version-url
+V 112
+/svn/!svn/ver/530/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/ByteBufferHelper.java
+END
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/entries b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/entries
new file mode 100644
index 0000000..2c6e266
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/entries
@@ -0,0 +1,266 @@
+10
+
+dir
+778
+http://mp4parser.googlecode.com/svn/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder
+http://mp4parser.googlecode.com/svn
+
+
+
+2012-09-10T14:34:23.574807Z
+776
+sebastian.annies@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+7decde4b-c250-0410-a0da-51896bc88be6
+
+FragmentIntersectionFinder.java
+file
+
+
+
+
+2012-09-14T17:27:50.237215Z
+979df582987d3797c42ae315b3d2555a
+2012-04-10T10:20:46.357991Z
+455
+Sebastian.Annies@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1160
+
+TwoSecondIntersectionFinder.java
+file
+
+
+
+
+2012-09-14T17:27:50.237215Z
+47aa683919ce24da51b82236e8f27426
+2012-06-06T10:36:10.590512Z
+658
+Sebastian.Annies@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+2817
+
+FragmentedMp4Builder.java
+file
+
+
+
+
+2012-09-14T17:27:50.237215Z
+f81cbeb22aee5ab0405ad38e67b9e4e1
+2012-07-10T16:32:53.757675Z
+707
+michael.stattmann@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+31005
+
+Mp4Builder.java
+file
+
+
+
+
+2012-09-14T17:27:50.237215Z
+ea548a5ace2a4480472b90d9f820bceb
+2012-06-11T22:10:18.183835Z
+672
+michael.stattmann@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1156
+
+SyncSampleIntersectFinderImpl.java
+file
+
+
+
+
+2012-09-14T17:27:50.237215Z
+d49823bb95a5920f2df6899195c28a72
+2012-09-06T12:33:39.419429Z
+774
+michael.stattmann@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+14915
+
+DefaultMp4Builder.java
+file
+
+
+
+
+2012-09-14T17:27:50.237215Z
+15d93ea29e24e257604301ad5e73d623
+2012-09-10T14:34:23.574807Z
+776
+sebastian.annies@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+22096
+
+ByteBufferHelper.java
+file
+
+
+
+
+2012-09-14T17:27:50.237215Z
+a103511eeddf1e0d1962704ae23ba4a5
+2012-04-27T09:17:25.544414Z
+530
+hoemmagnus@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+2344
+
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/ByteBufferHelper.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/ByteBufferHelper.java.svn-base
new file mode 100644
index 0000000..ad21b11
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/ByteBufferHelper.java.svn-base
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.builder;
+
+import java.nio.ByteBuffer;
+import java.nio.MappedByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Used to merge adjacent byte buffers.
+ */
+public class ByteBufferHelper {
+ public static List<ByteBuffer> mergeAdjacentBuffers(List<ByteBuffer> samples) {
+ ArrayList<ByteBuffer> nuSamples = new ArrayList<ByteBuffer>(samples.size());
+ for (ByteBuffer buffer : samples) {
+ int lastIndex = nuSamples.size() - 1;
+ if (lastIndex >= 0 && buffer.hasArray() && nuSamples.get(lastIndex).hasArray() && buffer.array() == nuSamples.get(lastIndex).array() &&
+ nuSamples.get(lastIndex).arrayOffset() + nuSamples.get(lastIndex).limit() == buffer.arrayOffset()) {
+ ByteBuffer oldBuffer = nuSamples.remove(lastIndex);
+ ByteBuffer nu = ByteBuffer.wrap(buffer.array(), oldBuffer.arrayOffset(), oldBuffer.limit() + buffer.limit()).slice();
+ // We need to slice here since wrap([], offset, length) just sets position and not the arrayOffset.
+ nuSamples.add(nu);
+ } else if (lastIndex >= 0 &&
+ buffer instanceof MappedByteBuffer && nuSamples.get(lastIndex) instanceof MappedByteBuffer &&
+ nuSamples.get(lastIndex).limit() == nuSamples.get(lastIndex).capacity() - buffer.capacity()) {
+ // This can go wrong - but will it?
+ ByteBuffer oldBuffer = nuSamples.get(lastIndex);
+ oldBuffer.limit(buffer.limit() + oldBuffer.limit());
+ } else {
+ buffer.rewind();
+ nuSamples.add(buffer);
+ }
+ }
+ return nuSamples;
+ }
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/DefaultMp4Builder.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/DefaultMp4Builder.java.svn-base
new file mode 100644
index 0000000..9bd1ca6
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/DefaultMp4Builder.java.svn-base
@@ -0,0 +1,576 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.builder;
+
+import com.coremedia.iso.BoxParser;
+import com.coremedia.iso.IsoFile;
+import com.coremedia.iso.IsoTypeWriter;
+import com.coremedia.iso.boxes.Box;
+import com.coremedia.iso.boxes.CompositionTimeToSample;
+import com.coremedia.iso.boxes.ContainerBox;
+import com.coremedia.iso.boxes.DataEntryUrlBox;
+import com.coremedia.iso.boxes.DataInformationBox;
+import com.coremedia.iso.boxes.DataReferenceBox;
+import com.coremedia.iso.boxes.FileTypeBox;
+import com.coremedia.iso.boxes.HandlerBox;
+import com.coremedia.iso.boxes.MediaBox;
+import com.coremedia.iso.boxes.MediaHeaderBox;
+import com.coremedia.iso.boxes.MediaInformationBox;
+import com.coremedia.iso.boxes.MovieBox;
+import com.coremedia.iso.boxes.MovieHeaderBox;
+import com.coremedia.iso.boxes.SampleDependencyTypeBox;
+import com.coremedia.iso.boxes.SampleSizeBox;
+import com.coremedia.iso.boxes.SampleTableBox;
+import com.coremedia.iso.boxes.SampleToChunkBox;
+import com.coremedia.iso.boxes.StaticChunkOffsetBox;
+import com.coremedia.iso.boxes.SyncSampleBox;
+import com.coremedia.iso.boxes.TimeToSampleBox;
+import com.coremedia.iso.boxes.TrackBox;
+import com.coremedia.iso.boxes.TrackHeaderBox;
+import com.googlecode.mp4parser.authoring.DateHelper;
+import com.googlecode.mp4parser.authoring.Movie;
+import com.googlecode.mp4parser.authoring.Track;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.GatheringByteChannel;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import static com.googlecode.mp4parser.util.CastUtils.l2i;
+
+/**
+ * Creates a plain MP4 file from a video. Plain as plain can be.
+ */
+public class DefaultMp4Builder implements Mp4Builder {
+
+ public int STEPSIZE = 64;
+ Set<StaticChunkOffsetBox> chunkOffsetBoxes = new HashSet<StaticChunkOffsetBox>();
+ private static Logger LOG = Logger.getLogger(DefaultMp4Builder.class.getName());
+
+ HashMap<Track, List<ByteBuffer>> track2Sample = new HashMap<Track, List<ByteBuffer>>();
+ HashMap<Track, long[]> track2SampleSizes = new HashMap<Track, long[]>();
+ private FragmentIntersectionFinder intersectionFinder = new TwoSecondIntersectionFinder();
+
+ public void setIntersectionFinder(FragmentIntersectionFinder intersectionFinder) {
+ this.intersectionFinder = intersectionFinder;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public IsoFile build(Movie movie) {
+ LOG.fine("Creating movie " + movie);
+ for (Track track : movie.getTracks()) {
+ // getting the samples may be a time consuming activity
+ List<ByteBuffer> samples = track.getSamples();
+ putSamples(track, samples);
+ long[] sizes = new long[samples.size()];
+ for (int i = 0; i < sizes.length; i++) {
+ sizes[i] = samples.get(i).limit();
+ }
+ putSampleSizes(track, sizes);
+ }
+
+ IsoFile isoFile = new IsoFile();
+ // ouch that is ugly but I don't know how to do it else
+ List<String> minorBrands = new LinkedList<String>();
+ minorBrands.add("isom");
+ minorBrands.add("iso2");
+ minorBrands.add("avc1");
+
+ isoFile.addBox(new FileTypeBox("isom", 0, minorBrands));
+ isoFile.addBox(createMovieBox(movie));
+ InterleaveChunkMdat mdat = new InterleaveChunkMdat(movie);
+ isoFile.addBox(mdat);
+
+ /*
+ dataOffset is where the first sample starts. In this special mdat the samples always start
+ at offset 16 so that we can use the same offset for large boxes and small boxes
+ */
+ long dataOffset = mdat.getDataOffset();
+ for (StaticChunkOffsetBox chunkOffsetBox : chunkOffsetBoxes) {
+ long[] offsets = chunkOffsetBox.getChunkOffsets();
+ for (int i = 0; i < offsets.length; i++) {
+ offsets[i] += dataOffset;
+ }
+ }
+
+
+ return isoFile;
+ }
+
+ public FragmentIntersectionFinder getFragmentIntersectionFinder() {
+ throw new UnsupportedOperationException("No fragment intersection finder in default MP4 builder!");
+ }
+
+ protected long[] putSampleSizes(Track track, long[] sizes) {
+ return track2SampleSizes.put(track, sizes);
+ }
+
+ protected List<ByteBuffer> putSamples(Track track, List<ByteBuffer> samples) {
+ return track2Sample.put(track, samples);
+ }
+
+ private MovieBox createMovieBox(Movie movie) {
+ MovieBox movieBox = new MovieBox();
+ MovieHeaderBox mvhd = new MovieHeaderBox();
+
+ mvhd.setCreationTime(DateHelper.convert(new Date()));
+ mvhd.setModificationTime(DateHelper.convert(new Date()));
+
+ long movieTimeScale = getTimescale(movie);
+ long duration = 0;
+
+ for (Track track : movie.getTracks()) {
+ long tracksDuration = getDuration(track) * movieTimeScale / track.getTrackMetaData().getTimescale();
+ if (tracksDuration > duration) {
+ duration = tracksDuration;
+ }
+
+
+ }
+
+ mvhd.setDuration(duration);
+ mvhd.setTimescale(movieTimeScale);
+ // find the next available trackId
+ long nextTrackId = 0;
+ for (Track track : movie.getTracks()) {
+ nextTrackId = nextTrackId < track.getTrackMetaData().getTrackId() ? track.getTrackMetaData().getTrackId() : nextTrackId;
+ }
+ mvhd.setNextTrackId(++nextTrackId);
+ if (mvhd.getCreationTime() >= 1l << 32 ||
+ mvhd.getModificationTime() >= 1l << 32 ||
+ mvhd.getDuration() >= 1l << 32) {
+ mvhd.setVersion(1);
+ }
+
+ movieBox.addBox(mvhd);
+ for (Track track : movie.getTracks()) {
+ movieBox.addBox(createTrackBox(track, movie));
+ }
+ // metadata here
+ Box udta = createUdta(movie);
+ if (udta != null) {
+ movieBox.addBox(udta);
+ }
+ return movieBox;
+
+ }
+
+ /**
+ * Override to create a user data box that may contain metadata.
+ *
+ * @return a 'udta' box or <code>null</code> if none provided
+ */
+ protected Box createUdta(Movie movie) {
+ return null;
+ }
+
+ private TrackBox createTrackBox(Track track, Movie movie) {
+
+ LOG.info("Creating Mp4TrackImpl " + track);
+ TrackBox trackBox = new TrackBox();
+ TrackHeaderBox tkhd = new TrackHeaderBox();
+ int flags = 0;
+ if (track.isEnabled()) {
+ flags += 1;
+ }
+
+ if (track.isInMovie()) {
+ flags += 2;
+ }
+
+ if (track.isInPreview()) {
+ flags += 4;
+ }
+
+ if (track.isInPoster()) {
+ flags += 8;
+ }
+ tkhd.setFlags(flags);
+
+ tkhd.setAlternateGroup(track.getTrackMetaData().getGroup());
+ tkhd.setCreationTime(DateHelper.convert(track.getTrackMetaData().getCreationTime()));
+ // We need to take edit list box into account in trackheader duration
+ // but as long as I don't support edit list boxes it is sufficient to
+ // just translate media duration to movie timescale
+ tkhd.setDuration(getDuration(track) * getTimescale(movie) / track.getTrackMetaData().getTimescale());
+ tkhd.setHeight(track.getTrackMetaData().getHeight());
+ tkhd.setWidth(track.getTrackMetaData().getWidth());
+ tkhd.setLayer(track.getTrackMetaData().getLayer());
+ tkhd.setModificationTime(DateHelper.convert(new Date()));
+ tkhd.setTrackId(track.getTrackMetaData().getTrackId());
+ tkhd.setVolume(track.getTrackMetaData().getVolume());
+ if (tkhd.getCreationTime() >= 1l << 32 ||
+ tkhd.getModificationTime() >= 1l << 32 ||
+ tkhd.getDuration() >= 1l << 32) {
+ tkhd.setVersion(1);
+ }
+
+ trackBox.addBox(tkhd);
+
+/*
+ EditBox edit = new EditBox();
+ EditListBox editListBox = new EditListBox();
+ editListBox.setEntries(Collections.singletonList(
+ new EditListBox.Entry(editListBox, (long) (track.getTrackMetaData().getStartTime() * getTimescale(movie)), -1, 1)));
+ edit.addBox(editListBox);
+ trackBox.addBox(edit);
+*/
+
+ MediaBox mdia = new MediaBox();
+ trackBox.addBox(mdia);
+ MediaHeaderBox mdhd = new MediaHeaderBox();
+ mdhd.setCreationTime(DateHelper.convert(track.getTrackMetaData().getCreationTime()));
+ mdhd.setDuration(getDuration(track));
+ mdhd.setTimescale(track.getTrackMetaData().getTimescale());
+ mdhd.setLanguage(track.getTrackMetaData().getLanguage());
+ mdia.addBox(mdhd);
+ HandlerBox hdlr = new HandlerBox();
+ mdia.addBox(hdlr);
+
+ hdlr.setHandlerType(track.getHandler());
+
+ MediaInformationBox minf = new MediaInformationBox();
+ minf.addBox(track.getMediaHeaderBox());
+
+ // dinf: all these three boxes tell us is that the actual
+ // data is in the current file and not somewhere external
+ DataInformationBox dinf = new DataInformationBox();
+ DataReferenceBox dref = new DataReferenceBox();
+ dinf.addBox(dref);
+ DataEntryUrlBox url = new DataEntryUrlBox();
+ url.setFlags(1);
+ dref.addBox(url);
+ minf.addBox(dinf);
+ //
+
+ SampleTableBox stbl = new SampleTableBox();
+
+ stbl.addBox(track.getSampleDescriptionBox());
+
+ List<TimeToSampleBox.Entry> decodingTimeToSampleEntries = track.getDecodingTimeEntries();
+ if (decodingTimeToSampleEntries != null && !track.getDecodingTimeEntries().isEmpty()) {
+ TimeToSampleBox stts = new TimeToSampleBox();
+ stts.setEntries(track.getDecodingTimeEntries());
+ stbl.addBox(stts);
+ }
+
+ List<CompositionTimeToSample.Entry> compositionTimeToSampleEntries = track.getCompositionTimeEntries();
+ if (compositionTimeToSampleEntries != null && !compositionTimeToSampleEntries.isEmpty()) {
+ CompositionTimeToSample ctts = new CompositionTimeToSample();
+ ctts.setEntries(compositionTimeToSampleEntries);
+ stbl.addBox(ctts);
+ }
+
+ long[] syncSamples = track.getSyncSamples();
+ if (syncSamples != null && syncSamples.length > 0) {
+ SyncSampleBox stss = new SyncSampleBox();
+ stss.setSampleNumber(syncSamples);
+ stbl.addBox(stss);
+ }
+
+ if (track.getSampleDependencies() != null && !track.getSampleDependencies().isEmpty()) {
+ SampleDependencyTypeBox sdtp = new SampleDependencyTypeBox();
+ sdtp.setEntries(track.getSampleDependencies());
+ stbl.addBox(sdtp);
+ }
+ HashMap<Track, int[]> track2ChunkSizes = new HashMap<Track, int[]>();
+ for (Track current : movie.getTracks()) {
+ track2ChunkSizes.put(current, getChunkSizes(current, movie));
+ }
+ int[] tracksChunkSizes = track2ChunkSizes.get(track);
+
+ SampleToChunkBox stsc = new SampleToChunkBox();
+ stsc.setEntries(new LinkedList<SampleToChunkBox.Entry>());
+ long lastChunkSize = Integer.MIN_VALUE; // to be sure the first chunks hasn't got the same size
+ for (int i = 0; i < tracksChunkSizes.length; i++) {
+ // The sample description index references the sample description box
+ // that describes the samples of this chunk. My Tracks cannot have more
+ // than one sample description box. Therefore 1 is always right
+ // the first chunk has the number '1'
+ if (lastChunkSize != tracksChunkSizes[i]) {
+ stsc.getEntries().add(new SampleToChunkBox.Entry(i + 1, tracksChunkSizes[i], 1));
+ lastChunkSize = tracksChunkSizes[i];
+ }
+ }
+ stbl.addBox(stsc);
+
+ SampleSizeBox stsz = new SampleSizeBox();
+ stsz.setSampleSizes(track2SampleSizes.get(track));
+
+ stbl.addBox(stsz);
+ // The ChunkOffsetBox we create here is just a stub
+ // since we haven't created the whole structure we can't tell where the
+ // first chunk starts (mdat box). So I just let the chunk offset
+ // start at zero and I will add the mdat offset later.
+ StaticChunkOffsetBox stco = new StaticChunkOffsetBox();
+ this.chunkOffsetBoxes.add(stco);
+ long offset = 0;
+ long[] chunkOffset = new long[tracksChunkSizes.length];
+ // all tracks have the same number of chunks
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Calculating chunk offsets for track_" + track.getTrackMetaData().getTrackId());
+ }
+
+
+ for (int i = 0; i < tracksChunkSizes.length; i++) {
+ // The filelayout will be:
+ // chunk_1_track_1,... ,chunk_1_track_n, chunk_2_track_1,... ,chunk_2_track_n, ... , chunk_m_track_1,... ,chunk_m_track_n
+ // calculating the offsets
+ if (LOG.isLoggable(Level.FINER)) {
+ LOG.finer("Calculating chunk offsets for track_" + track.getTrackMetaData().getTrackId() + " chunk " + i);
+ }
+ for (Track current : movie.getTracks()) {
+ if (LOG.isLoggable(Level.FINEST)) {
+ LOG.finest("Adding offsets of track_" + current.getTrackMetaData().getTrackId());
+ }
+ int[] chunkSizes = track2ChunkSizes.get(current);
+ long firstSampleOfChunk = 0;
+ for (int j = 0; j < i; j++) {
+ firstSampleOfChunk += chunkSizes[j];
+ }
+ if (current == track) {
+ chunkOffset[i] = offset;
+ }
+ for (int j = l2i(firstSampleOfChunk); j < firstSampleOfChunk + chunkSizes[i]; j++) {
+ offset += track2SampleSizes.get(current)[j];
+ }
+ }
+ }
+ stco.setChunkOffsets(chunkOffset);
+ stbl.addBox(stco);
+ minf.addBox(stbl);
+ mdia.addBox(minf);
+
+ return trackBox;
+ }
+
+ private class InterleaveChunkMdat implements Box {
+ List<Track> tracks;
+ List<ByteBuffer> samples = new ArrayList<ByteBuffer>();
+ ContainerBox parent;
+
+ long contentSize = 0;
+
+ public ContainerBox getParent() {
+ return parent;
+ }
+
+ public void setParent(ContainerBox parent) {
+ this.parent = parent;
+ }
+
+ public void parse(ReadableByteChannel readableByteChannel, ByteBuffer header, long contentSize, BoxParser boxParser) throws IOException {
+ }
+
+ private InterleaveChunkMdat(Movie movie) {
+
+ tracks = movie.getTracks();
+ Map<Track, int[]> chunks = new HashMap<Track, int[]>();
+ for (Track track : movie.getTracks()) {
+ chunks.put(track, getChunkSizes(track, movie));
+ }
+
+ for (int i = 0; i < chunks.values().iterator().next().length; i++) {
+ for (Track track : tracks) {
+
+ int[] chunkSizes = chunks.get(track);
+ long firstSampleOfChunk = 0;
+ for (int j = 0; j < i; j++) {
+ firstSampleOfChunk += chunkSizes[j];
+ }
+
+ for (int j = l2i(firstSampleOfChunk); j < firstSampleOfChunk + chunkSizes[i]; j++) {
+
+ ByteBuffer s = DefaultMp4Builder.this.track2Sample.get(track).get(j);
+ contentSize += s.limit();
+ samples.add((ByteBuffer) s.rewind());
+ }
+
+ }
+
+ }
+
+ }
+
+ public long getDataOffset() {
+ Box b = this;
+ long offset = 16;
+ while (b.getParent() != null) {
+ for (Box box : b.getParent().getBoxes()) {
+ if (b == box) {
+ break;
+ }
+ offset += box.getSize();
+ }
+ b = b.getParent();
+ }
+ return offset;
+ }
+
+
+ public String getType() {
+ return "mdat";
+ }
+
+ public long getSize() {
+ return 16 + contentSize;
+ }
+
+ private boolean isSmallBox(long contentSize) {
+ return (contentSize + 8) < 4294967296L;
+ }
+
+
+ public void getBox(WritableByteChannel writableByteChannel) throws IOException {
+ ByteBuffer bb = ByteBuffer.allocate(16);
+ long size = getSize();
+ if (isSmallBox(size)) {
+ IsoTypeWriter.writeUInt32(bb, size);
+ } else {
+ IsoTypeWriter.writeUInt32(bb, 1);
+ }
+ bb.put(IsoFile.fourCCtoBytes("mdat"));
+ if (isSmallBox(size)) {
+ bb.put(new byte[8]);
+ } else {
+ IsoTypeWriter.writeUInt64(bb, size);
+ }
+ bb.rewind();
+ writableByteChannel.write(bb);
+ if (writableByteChannel instanceof GatheringByteChannel) {
+ List<ByteBuffer> nuSamples = unifyAdjacentBuffers(samples);
+
+
+ for (int i = 0; i < Math.ceil((double) nuSamples.size() / STEPSIZE); i++) {
+ List<ByteBuffer> sublist = nuSamples.subList(
+ i * STEPSIZE, // start
+ (i + 1) * STEPSIZE < nuSamples.size() ? (i + 1) * STEPSIZE : nuSamples.size()); // end
+ ByteBuffer sampleArray[] = sublist.toArray(new ByteBuffer[sublist.size()]);
+ do {
+ ((GatheringByteChannel) writableByteChannel).write(sampleArray);
+ } while (sampleArray[sampleArray.length - 1].remaining() > 0);
+ }
+ //System.err.println(bytesWritten);
+ } else {
+ for (ByteBuffer sample : samples) {
+ sample.rewind();
+ writableByteChannel.write(sample);
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Gets the chunk sizes for the given track.
+ *
+ * @param track
+ * @param movie
+ * @return
+ */
+ int[] getChunkSizes(Track track, Movie movie) {
+
+ long[] referenceChunkStarts = intersectionFinder.sampleNumbers(track, movie);
+ int[] chunkSizes = new int[referenceChunkStarts.length];
+
+
+ for (int i = 0; i < referenceChunkStarts.length; i++) {
+ long start = referenceChunkStarts[i] - 1;
+ long end;
+ if (referenceChunkStarts.length == i + 1) {
+ end = track.getSamples().size();
+ } else {
+ end = referenceChunkStarts[i + 1] - 1;
+ }
+
+ chunkSizes[i] = l2i(end - start);
+ // The Stretch makes sure that there are as much audio and video chunks!
+ }
+ assert DefaultMp4Builder.this.track2Sample.get(track).size() == sum(chunkSizes) : "The number of samples and the sum of all chunk lengths must be equal";
+ return chunkSizes;
+
+
+ }
+
+
+ private static long sum(int[] ls) {
+ long rc = 0;
+ for (long l : ls) {
+ rc += l;
+ }
+ return rc;
+ }
+
+ protected static long getDuration(Track track) {
+ long duration = 0;
+ for (TimeToSampleBox.Entry entry : track.getDecodingTimeEntries()) {
+ duration += entry.getCount() * entry.getDelta();
+ }
+ return duration;
+ }
+
+ public long getTimescale(Movie movie) {
+ long timescale = movie.getTracks().iterator().next().getTrackMetaData().getTimescale();
+ for (Track track : movie.getTracks()) {
+ timescale = gcd(track.getTrackMetaData().getTimescale(), timescale);
+ }
+ return timescale;
+ }
+
+ public static long gcd(long a, long b) {
+ if (b == 0) {
+ return a;
+ }
+ return gcd(b, a % b);
+ }
+
+ public List<ByteBuffer> unifyAdjacentBuffers(List<ByteBuffer> samples) {
+ ArrayList<ByteBuffer> nuSamples = new ArrayList<ByteBuffer>(samples.size());
+ for (ByteBuffer buffer : samples) {
+ int lastIndex = nuSamples.size() - 1;
+ if (lastIndex >= 0 && buffer.hasArray() && nuSamples.get(lastIndex).hasArray() && buffer.array() == nuSamples.get(lastIndex).array() &&
+ nuSamples.get(lastIndex).arrayOffset() + nuSamples.get(lastIndex).limit() == buffer.arrayOffset()) {
+ ByteBuffer oldBuffer = nuSamples.remove(lastIndex);
+ ByteBuffer nu = ByteBuffer.wrap(buffer.array(), oldBuffer.arrayOffset(), oldBuffer.limit() + buffer.limit()).slice();
+ // We need to slice here since wrap([], offset, length) just sets position and not the arrayOffset.
+ nuSamples.add(nu);
+ } else if (lastIndex >= 0 &&
+ buffer instanceof MappedByteBuffer && nuSamples.get(lastIndex) instanceof MappedByteBuffer &&
+ nuSamples.get(lastIndex).limit() == nuSamples.get(lastIndex).capacity() - buffer.capacity()) {
+ // This can go wrong - but will it?
+ ByteBuffer oldBuffer = nuSamples.get(lastIndex);
+ oldBuffer.limit(buffer.limit() + oldBuffer.limit());
+ } else {
+ nuSamples.add(buffer);
+ }
+ }
+ return nuSamples;
+ }
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/FragmentIntersectionFinder.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/FragmentIntersectionFinder.java.svn-base
new file mode 100644
index 0000000..1224bbf
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/FragmentIntersectionFinder.java.svn-base
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.builder;
+
+import com.googlecode.mp4parser.authoring.Movie;
+import com.googlecode.mp4parser.authoring.Track;
+
+/**
+ *
+ */
+public interface FragmentIntersectionFinder {
+ /**
+ * Gets the ordinal number of the samples which will be the first sample
+ * in each fragment.
+ *
+ * @param track concerned track
+ * @param movie the context of the track
+ * @return an array containing the ordinal of each fragment's first sample
+ */
+ public long[] sampleNumbers(Track track, Movie movie);
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/FragmentedMp4Builder.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/FragmentedMp4Builder.java.svn-base
new file mode 100644
index 0000000..c65ff1c
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/FragmentedMp4Builder.java.svn-base
@@ -0,0 +1,742 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.builder;
+
+import com.coremedia.iso.BoxParser;
+import com.coremedia.iso.IsoFile;
+import com.coremedia.iso.IsoTypeWriter;
+import com.coremedia.iso.boxes.*;
+import com.coremedia.iso.boxes.fragment.*;
+import com.googlecode.mp4parser.authoring.DateHelper;
+import com.googlecode.mp4parser.authoring.Movie;
+import com.googlecode.mp4parser.authoring.Track;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.GatheringByteChannel;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+import java.util.*;
+import java.util.logging.Logger;
+
+import static com.googlecode.mp4parser.util.CastUtils.l2i;
+
+/**
+ * Creates a fragmented MP4 file.
+ */
+public class FragmentedMp4Builder implements Mp4Builder {
+ private static final Logger LOG = Logger.getLogger(FragmentedMp4Builder.class.getName());
+
+ protected FragmentIntersectionFinder intersectionFinder;
+
+ public FragmentedMp4Builder() {
+ this.intersectionFinder = new SyncSampleIntersectFinderImpl();
+ }
+
+ public List<String> getAllowedHandlers() {
+ return Arrays.asList("soun", "vide");
+ }
+
+ public Box createFtyp(Movie movie) {
+ List<String> minorBrands = new LinkedList<String>();
+ minorBrands.add("isom");
+ minorBrands.add("iso2");
+ minorBrands.add("avc1");
+ return new FileTypeBox("isom", 0, minorBrands);
+ }
+
+ /**
+ * Some formats require sorting of the fragments. E.g. Ultraviolet CFF files are required
+ * to contain the fragments size sort:
+ * <ul>
+ * <li>video[1].getBytes().length < audio[1].getBytes().length < subs[1].getBytes().length</li>
+ * <li> audio[2].getBytes().length < video[2].getBytes().length < subs[2].getBytes().length</li>
+ * </ul>
+ *
+ * make this fragment:
+ *
+ * <ol>
+ * <li>video[1]</li>
+ * <li>audio[1]</li>
+ * <li>subs[1]</li>
+ * <li>audio[2]</li>
+ * <li>video[2]</li>
+ * <li>subs[2]</li>
+ * </ol>
+ *
+ * @param tracks the list of tracks to returned sorted
+ * @param cycle current fragment (sorting may vary between the fragments)
+ * @param intersectionMap a map from tracks to their fragments' first samples.
+ * @return the list of tracks in order of appearance in the fragment
+ */
+ protected List<Track> sortTracksInSequence(List<Track> tracks, final int cycle, final Map<Track, long[]> intersectionMap) {
+ tracks = new LinkedList<Track>(tracks);
+ Collections.sort(tracks, new Comparator<Track>() {
+ public int compare(Track o1, Track o2) {
+ long[] startSamples1 = intersectionMap.get(o1);
+ long startSample1 = startSamples1[cycle];
+ // one based sample numbers - the first sample is 1
+ long endSample1 = cycle + 1 < startSamples1.length ? startSamples1[cycle + 1] : o1.getSamples().size() + 1;
+ long[] startSamples2 = intersectionMap.get(o2);
+ long startSample2 = startSamples2[cycle];
+ // one based sample numbers - the first sample is 1
+ long endSample2 = cycle + 1 < startSamples2.length ? startSamples2[cycle + 1] : o2.getSamples().size() + 1;
+ List<ByteBuffer> samples1 = o1.getSamples().subList(l2i(startSample1) - 1, l2i(endSample1) - 1);
+ List<ByteBuffer> samples2 = o2.getSamples().subList(l2i(startSample2) - 1, l2i(endSample2) - 1);
+ int size1 = 0;
+ for (ByteBuffer byteBuffer : samples1) {
+ size1 += byteBuffer.limit();
+ }
+ int size2 = 0;
+ for (ByteBuffer byteBuffer : samples2) {
+ size2 += byteBuffer.limit();
+ }
+ return size1 - size2;
+ }
+ });
+ return tracks;
+ }
+
+ protected List<Box> createMoofMdat(final Movie movie) {
+ List<Box> boxes = new LinkedList<Box>();
+ HashMap<Track, long[]> intersectionMap = new HashMap<Track, long[]>();
+ int maxNumberOfFragments = 0;
+ for (Track track : movie.getTracks()) {
+ long[] intersects = intersectionFinder.sampleNumbers(track, movie);
+ intersectionMap.put(track, intersects);
+ maxNumberOfFragments = Math.max(maxNumberOfFragments, intersects.length);
+ }
+
+
+ int sequence = 1;
+ // this loop has two indices:
+
+ for (int cycle = 0; cycle < maxNumberOfFragments; cycle++) {
+
+ final List<Track> sortedTracks = sortTracksInSequence(movie.getTracks(), cycle, intersectionMap);
+
+ for (Track track : sortedTracks) {
+ if (getAllowedHandlers().isEmpty() || getAllowedHandlers().contains(track.getHandler())) {
+ long[] startSamples = intersectionMap.get(track);
+ //some tracks may have less fragments -> skip them
+ if (cycle < startSamples.length) {
+
+ long startSample = startSamples[cycle];
+ // one based sample numbers - the first sample is 1
+ long endSample = cycle + 1 < startSamples.length ? startSamples[cycle + 1] : track.getSamples().size() + 1;
+
+ // if startSample == endSample the cycle is empty!
+ if (startSample != endSample) {
+ boxes.add(createMoof(startSample, endSample, track, sequence));
+ boxes.add(createMdat(startSample, endSample, track, sequence++));
+ }
+ }
+ }
+ }
+ }
+ return boxes;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public IsoFile build(Movie movie) {
+ LOG.fine("Creating movie " + movie);
+ IsoFile isoFile = new IsoFile();
+
+
+ isoFile.addBox(createFtyp(movie));
+ isoFile.addBox(createMoov(movie));
+
+ for (Box box : createMoofMdat(movie)) {
+ isoFile.addBox(box);
+ }
+ isoFile.addBox(createMfra(movie, isoFile));
+
+ return isoFile;
+ }
+
+ protected Box createMdat(final long startSample, final long endSample, final Track track, final int i) {
+
+ class Mdat implements Box {
+ ContainerBox parent;
+
+ public ContainerBox getParent() {
+ return parent;
+ }
+
+ public void setParent(ContainerBox parent) {
+ this.parent = parent;
+ }
+
+ public long getSize() {
+ long size = 8; // I don't expect 2gig fragments
+ for (ByteBuffer sample : getSamples(startSample, endSample, track, i)) {
+ size += sample.limit();
+ }
+ return size;
+ }
+
+ public String getType() {
+ return "mdat";
+ }
+
+ public void getBox(WritableByteChannel writableByteChannel) throws IOException {
+ List<ByteBuffer> bbs = getSamples(startSample, endSample, track, i);
+ final List<ByteBuffer> samples = ByteBufferHelper.mergeAdjacentBuffers(bbs);
+ ByteBuffer header = ByteBuffer.allocate(8);
+ IsoTypeWriter.writeUInt32(header, l2i(getSize()));
+ header.put(IsoFile.fourCCtoBytes(getType()));
+ header.rewind();
+ writableByteChannel.write(header);
+ if (writableByteChannel instanceof GatheringByteChannel) {
+
+ int STEPSIZE = 1024;
+ // This is required to prevent android from crashing
+ // it seems that {@link GatheringByteChannel#write(java.nio.ByteBuffer[])}
+ // just handles up to 1024 buffers
+ for (int i = 0; i < Math.ceil((double) samples.size() / STEPSIZE); i++) {
+ List<ByteBuffer> sublist = samples.subList(
+ i * STEPSIZE, // start
+ (i + 1) * STEPSIZE < samples.size() ? (i + 1) * STEPSIZE : samples.size()); // end
+ ByteBuffer sampleArray[] = sublist.toArray(new ByteBuffer[sublist.size()]);
+ do {
+ ((GatheringByteChannel) writableByteChannel).write(sampleArray);
+ } while (sampleArray[sampleArray.length - 1].remaining() > 0);
+ }
+ //System.err.println(bytesWritten);
+ } else {
+ for (ByteBuffer sample : samples) {
+ sample.rewind();
+ writableByteChannel.write(sample);
+ }
+ }
+
+ }
+
+ public void parse(ReadableByteChannel readableByteChannel, ByteBuffer header, long contentSize, BoxParser boxParser) throws IOException {
+
+ }
+ }
+
+ return new Mdat();
+ }
+
+ protected Box createTfhd(long startSample, long endSample, Track track, int sequenceNumber) {
+ TrackFragmentHeaderBox tfhd = new TrackFragmentHeaderBox();
+ SampleFlags sf = new SampleFlags();
+
+ tfhd.setDefaultSampleFlags(sf);
+ tfhd.setBaseDataOffset(-1);
+ tfhd.setTrackId(track.getTrackMetaData().getTrackId());
+ return tfhd;
+ }
+
+ protected Box createMfhd(long startSample, long endSample, Track track, int sequenceNumber) {
+ MovieFragmentHeaderBox mfhd = new MovieFragmentHeaderBox();
+ mfhd.setSequenceNumber(sequenceNumber);
+ return mfhd;
+ }
+
+ protected Box createTraf(long startSample, long endSample, Track track, int sequenceNumber) {
+ TrackFragmentBox traf = new TrackFragmentBox();
+ traf.addBox(createTfhd(startSample, endSample, track, sequenceNumber));
+ for (Box trun : createTruns(startSample, endSample, track, sequenceNumber)) {
+ traf.addBox(trun);
+ }
+
+ return traf;
+ }
+
+
+ /**
+ * Gets the all samples starting with <code>startSample</code> (one based -> one is the first) and
+ * ending with <code>endSample</code> (exclusive).
+ *
+ * @param startSample low endpoint (inclusive) of the sample sequence
+ * @param endSample high endpoint (exclusive) of the sample sequence
+ * @param track source of the samples
+ * @param sequenceNumber the fragment index of the requested list of samples
+ * @return a <code>List&lt;ByteBuffer></code> of raw samples
+ */
+ protected List<ByteBuffer> getSamples(long startSample, long endSample, Track track, int sequenceNumber) {
+ // since startSample and endSample are one-based substract 1 before addressing list elements
+ return track.getSamples().subList(l2i(startSample) - 1, l2i(endSample) - 1);
+ }
+
+ /**
+ * Gets the sizes of a sequence of samples-
+ *
+ * @param startSample low endpoint (inclusive) of the sample sequence
+ * @param endSample high endpoint (exclusive) of the sample sequence
+ * @param track source of the samples
+ * @param sequenceNumber the fragment index of the requested list of samples
+ * @return
+ */
+ protected long[] getSampleSizes(long startSample, long endSample, Track track, int sequenceNumber) {
+ List<ByteBuffer> samples = getSamples(startSample, endSample, track, sequenceNumber);
+
+ long[] sampleSizes = new long[samples.size()];
+ for (int i = 0; i < sampleSizes.length; i++) {
+ sampleSizes[i] = samples.get(i).limit();
+ }
+ return sampleSizes;
+ }
+
+ /**
+ * Creates one or more track run boxes for a given sequence.
+ *
+ * @param startSample low endpoint (inclusive) of the sample sequence
+ * @param endSample high endpoint (exclusive) of the sample sequence
+ * @param track source of the samples
+ * @param sequenceNumber the fragment index of the requested list of samples
+ * @return the list of TrackRun boxes.
+ */
+ protected List<? extends Box> createTruns(long startSample, long endSample, Track track, int sequenceNumber) {
+ TrackRunBox trun = new TrackRunBox();
+ long[] sampleSizes = getSampleSizes(startSample, endSample, track, sequenceNumber);
+
+ trun.setSampleDurationPresent(true);
+ trun.setSampleSizePresent(true);
+ List<TrackRunBox.Entry> entries = new ArrayList<TrackRunBox.Entry>(l2i(endSample - startSample));
+
+
+ Queue<TimeToSampleBox.Entry> timeQueue = new LinkedList<TimeToSampleBox.Entry>(track.getDecodingTimeEntries());
+ long left = startSample - 1;
+ long curEntryLeft = timeQueue.peek().getCount();
+ while (left > curEntryLeft) {
+ left -= curEntryLeft;
+ timeQueue.remove();
+ curEntryLeft = timeQueue.peek().getCount();
+ }
+ curEntryLeft -= left;
+
+
+ Queue<CompositionTimeToSample.Entry> compositionTimeQueue =
+ track.getCompositionTimeEntries() != null && track.getCompositionTimeEntries().size() > 0 ?
+ new LinkedList<CompositionTimeToSample.Entry>(track.getCompositionTimeEntries()) : null;
+ long compositionTimeEntriesLeft = compositionTimeQueue != null ? compositionTimeQueue.peek().getCount() : -1;
+
+
+ trun.setSampleCompositionTimeOffsetPresent(compositionTimeEntriesLeft > 0);
+
+ // fast forward composition stuff
+ for (long i = 1; i < startSample; i++) {
+ if (compositionTimeQueue != null) {
+ //trun.setSampleCompositionTimeOffsetPresent(true);
+ if (--compositionTimeEntriesLeft == 0 && compositionTimeQueue.size() > 1) {
+ compositionTimeQueue.remove();
+ compositionTimeEntriesLeft = compositionTimeQueue.element().getCount();
+ }
+ }
+ }
+
+ boolean sampleFlagsRequired = (track.getSampleDependencies() != null && !track.getSampleDependencies().isEmpty() ||
+ track.getSyncSamples() != null && track.getSyncSamples().length != 0);
+
+ trun.setSampleFlagsPresent(sampleFlagsRequired);
+
+ for (int i = 0; i < sampleSizes.length; i++) {
+ TrackRunBox.Entry entry = new TrackRunBox.Entry();
+ entry.setSampleSize(sampleSizes[i]);
+ if (sampleFlagsRequired) {
+ //if (false) {
+ SampleFlags sflags = new SampleFlags();
+
+ if (track.getSampleDependencies() != null && !track.getSampleDependencies().isEmpty()) {
+ SampleDependencyTypeBox.Entry e = track.getSampleDependencies().get(i);
+ sflags.setSampleDependsOn(e.getSampleDependsOn());
+ sflags.setSampleIsDependedOn(e.getSampleIsDependentOn());
+ sflags.setSampleHasRedundancy(e.getSampleHasRedundancy());
+ }
+ if (track.getSyncSamples() != null && track.getSyncSamples().length > 0) {
+ // we have to mark non-sync samples!
+ if (Arrays.binarySearch(track.getSyncSamples(), startSample + i) >= 0) {
+ sflags.setSampleIsDifferenceSample(false);
+ sflags.setSampleDependsOn(2);
+ } else {
+ sflags.setSampleIsDifferenceSample(true);
+ sflags.setSampleDependsOn(1);
+ }
+ }
+ // i don't have sample degradation
+ entry.setSampleFlags(sflags);
+
+ }
+
+ entry.setSampleDuration(timeQueue.peek().getDelta());
+ if (--curEntryLeft == 0 && timeQueue.size() > 1) {
+ timeQueue.remove();
+ curEntryLeft = timeQueue.peek().getCount();
+ }
+
+ if (compositionTimeQueue != null) {
+ entry.setSampleCompositionTimeOffset(compositionTimeQueue.peek().getOffset());
+ if (--compositionTimeEntriesLeft == 0 && compositionTimeQueue.size() > 1) {
+ compositionTimeQueue.remove();
+ compositionTimeEntriesLeft = compositionTimeQueue.element().getCount();
+ }
+ }
+ entries.add(entry);
+ }
+
+ trun.setEntries(entries);
+
+ return Collections.singletonList(trun);
+ }
+
+ /**
+ * Creates a 'moof' box for a given sequence of samples.
+ *
+ * @param startSample low endpoint (inclusive) of the sample sequence
+ * @param endSample high endpoint (exclusive) of the sample sequence
+ * @param track source of the samples
+ * @param sequenceNumber the fragment index of the requested list of samples
+ * @return the list of TrackRun boxes.
+ */
+ protected Box createMoof(long startSample, long endSample, Track track, int sequenceNumber) {
+ MovieFragmentBox moof = new MovieFragmentBox();
+ moof.addBox(createMfhd(startSample, endSample, track, sequenceNumber));
+ moof.addBox(createTraf(startSample, endSample, track, sequenceNumber));
+
+ TrackRunBox firstTrun = moof.getTrackRunBoxes().get(0);
+ firstTrun.setDataOffset(1); // dummy to make size correct
+ firstTrun.setDataOffset((int) (8 + moof.getSize())); // mdat header + moof size
+
+ return moof;
+ }
+
+ /**
+ * Creates a single 'mvhd' movie header box for a given movie.
+ *
+ * @param movie the concerned movie
+ * @return an 'mvhd' box
+ */
+ protected Box createMvhd(Movie movie) {
+ MovieHeaderBox mvhd = new MovieHeaderBox();
+ mvhd.setVersion(1);
+ mvhd.setCreationTime(DateHelper.convert(new Date()));
+ mvhd.setModificationTime(DateHelper.convert(new Date()));
+ long movieTimeScale = movie.getTimescale();
+ long duration = 0;
+
+ for (Track track : movie.getTracks()) {
+ long tracksDuration = getDuration(track) * movieTimeScale / track.getTrackMetaData().getTimescale();
+ if (tracksDuration > duration) {
+ duration = tracksDuration;
+ }
+
+
+ }
+
+ mvhd.setDuration(duration);
+ mvhd.setTimescale(movieTimeScale);
+ // find the next available trackId
+ long nextTrackId = 0;
+ for (Track track : movie.getTracks()) {
+ nextTrackId = nextTrackId < track.getTrackMetaData().getTrackId() ? track.getTrackMetaData().getTrackId() : nextTrackId;
+ }
+ mvhd.setNextTrackId(++nextTrackId);
+ return mvhd;
+ }
+
+ /**
+ * Creates a fully populated 'moov' box with all child boxes. Child boxes are:
+ * <ul>
+ * <li>{@link #createMvhd(com.googlecode.mp4parser.authoring.Movie) mvhd}</li>
+ * <li>{@link #createMvex(com.googlecode.mp4parser.authoring.Movie) mvex}</li>
+ * <li>a {@link #createTrak(com.googlecode.mp4parser.authoring.Track, com.googlecode.mp4parser.authoring.Movie) trak} for every track</li>
+ * </ul>
+ *
+ * @param movie the concerned movie
+ * @return fully populated 'moov'
+ */
+ protected Box createMoov(Movie movie) {
+ MovieBox movieBox = new MovieBox();
+
+ movieBox.addBox(createMvhd(movie));
+ movieBox.addBox(createMvex(movie));
+
+ for (Track track : movie.getTracks()) {
+ movieBox.addBox(createTrak(track, movie));
+ }
+ // metadata here
+ return movieBox;
+
+ }
+
+ /**
+ * Creates a 'tfra' - track fragment random access box for the given track with the isoFile.
+ * The tfra contains a map of random access points with time as key and offset within the isofile
+ * as value.
+ *
+ * @param track the concerned track
+ * @param isoFile the track is contained in
+ * @return a track fragment random access box.
+ */
+ protected Box createTfra(Track track, IsoFile isoFile) {
+ TrackFragmentRandomAccessBox tfra = new TrackFragmentRandomAccessBox();
+ tfra.setVersion(1); // use long offsets and times
+ List<TrackFragmentRandomAccessBox.Entry> offset2timeEntries = new LinkedList<TrackFragmentRandomAccessBox.Entry>();
+ List<Box> boxes = isoFile.getBoxes();
+ long offset = 0;
+ long duration = 0;
+ for (Box box : boxes) {
+ if (box instanceof MovieFragmentBox) {
+ List<TrackFragmentBox> trafs = ((MovieFragmentBox) box).getBoxes(TrackFragmentBox.class);
+ for (int i = 0; i < trafs.size(); i++) {
+ TrackFragmentBox traf = trafs.get(i);
+ if (traf.getTrackFragmentHeaderBox().getTrackId() == track.getTrackMetaData().getTrackId()) {
+ // here we are at the offset required for the current entry.
+ List<TrackRunBox> truns = traf.getBoxes(TrackRunBox.class);
+ for (int j = 0; j < truns.size(); j++) {
+ List<TrackFragmentRandomAccessBox.Entry> offset2timeEntriesThisTrun = new LinkedList<TrackFragmentRandomAccessBox.Entry>();
+ TrackRunBox trun = truns.get(j);
+ for (int k = 0; k < trun.getEntries().size(); k++) {
+ TrackRunBox.Entry trunEntry = trun.getEntries().get(k);
+ SampleFlags sf = null;
+ if (k == 0 && trun.isFirstSampleFlagsPresent()) {
+ sf = trun.getFirstSampleFlags();
+ } else if (trun.isSampleFlagsPresent()) {
+ sf = trunEntry.getSampleFlags();
+ } else {
+ List<MovieExtendsBox> mvexs = isoFile.getMovieBox().getBoxes(MovieExtendsBox.class);
+ for (MovieExtendsBox mvex : mvexs) {
+ List<TrackExtendsBox> trexs = mvex.getBoxes(TrackExtendsBox.class);
+ for (TrackExtendsBox trex : trexs) {
+ if (trex.getTrackId() == track.getTrackMetaData().getTrackId()) {
+ sf = trex.getDefaultSampleFlags();
+ }
+ }
+ }
+
+ }
+ if (sf == null) {
+ throw new RuntimeException("Could not find any SampleFlags to indicate random access or not");
+ }
+ if (sf.getSampleDependsOn() == 2) {
+ offset2timeEntriesThisTrun.add(new TrackFragmentRandomAccessBox.Entry(
+ duration,
+ offset,
+ i + 1, j + 1, k + 1));
+ }
+ duration += trunEntry.getSampleDuration();
+ }
+ if (offset2timeEntriesThisTrun.size() == trun.getEntries().size() && trun.getEntries().size() > 0) {
+ // Oooops every sample seems to be random access sample
+ // is this an audio track? I don't care.
+ // I just use the first for trun sample for tfra random access
+ offset2timeEntries.add(offset2timeEntriesThisTrun.get(0));
+ } else {
+ offset2timeEntries.addAll(offset2timeEntriesThisTrun);
+ }
+ }
+ }
+ }
+ }
+
+
+ offset += box.getSize();
+ }
+ tfra.setEntries(offset2timeEntries);
+ tfra.setTrackId(track.getTrackMetaData().getTrackId());
+ return tfra;
+ }
+
+ /**
+ * Creates a 'mfra' - movie fragment random access box for the given movie in the given
+ * isofile. Uses {@link #createTfra(com.googlecode.mp4parser.authoring.Track, com.coremedia.iso.IsoFile)}
+ * to generate the child boxes.
+ *
+ * @param movie concerned movie
+ * @param isoFile concerned isofile
+ * @return a complete 'mfra' box
+ */
+ protected Box createMfra(Movie movie, IsoFile isoFile) {
+ MovieFragmentRandomAccessBox mfra = new MovieFragmentRandomAccessBox();
+ for (Track track : movie.getTracks()) {
+ mfra.addBox(createTfra(track, isoFile));
+ }
+
+ MovieFragmentRandomAccessOffsetBox mfro = new MovieFragmentRandomAccessOffsetBox();
+ mfra.addBox(mfro);
+ mfro.setMfraSize(mfra.getSize());
+ return mfra;
+ }
+
+ protected Box createTrex(Movie movie, Track track) {
+ TrackExtendsBox trex = new TrackExtendsBox();
+ trex.setTrackId(track.getTrackMetaData().getTrackId());
+ trex.setDefaultSampleDescriptionIndex(1);
+ trex.setDefaultSampleDuration(0);
+ trex.setDefaultSampleSize(0);
+ SampleFlags sf = new SampleFlags();
+ if ("soun".equals(track.getHandler())) {
+ // as far as I know there is no audio encoding
+ // where the sample are not self contained.
+ sf.setSampleDependsOn(2);
+ sf.setSampleIsDependedOn(2);
+ }
+ trex.setDefaultSampleFlags(sf);
+ return trex;
+ }
+
+ /**
+ * Creates a 'mvex' - movie extends box and populates it with 'trex' boxes
+ * by calling {@link #createTrex(com.googlecode.mp4parser.authoring.Movie, com.googlecode.mp4parser.authoring.Track)}
+ * for each track to generate them
+ *
+ * @param movie the source movie
+ * @return a complete 'mvex'
+ */
+ protected Box createMvex(Movie movie) {
+ MovieExtendsBox mvex = new MovieExtendsBox();
+ final MovieExtendsHeaderBox mved = new MovieExtendsHeaderBox();
+ for (Track track : movie.getTracks()) {
+ final long trackDuration = getTrackDuration(movie, track);
+ if (mved.getFragmentDuration() < trackDuration) {
+ mved.setFragmentDuration(trackDuration);
+ }
+ }
+ mvex.addBox(mved);
+
+ for (Track track : movie.getTracks()) {
+ mvex.addBox(createTrex(movie, track));
+ }
+ return mvex;
+ }
+
+ protected Box createTkhd(Movie movie, Track track) {
+ TrackHeaderBox tkhd = new TrackHeaderBox();
+ tkhd.setVersion(1);
+ int flags = 0;
+ if (track.isEnabled()) {
+ flags += 1;
+ }
+
+ if (track.isInMovie()) {
+ flags += 2;
+ }
+
+ if (track.isInPreview()) {
+ flags += 4;
+ }
+
+ if (track.isInPoster()) {
+ flags += 8;
+ }
+ tkhd.setFlags(flags);
+
+ tkhd.setAlternateGroup(track.getTrackMetaData().getGroup());
+ tkhd.setCreationTime(DateHelper.convert(track.getTrackMetaData().getCreationTime()));
+ // We need to take edit list box into account in trackheader duration
+ // but as long as I don't support edit list boxes it is sufficient to
+ // just translate media duration to movie timescale
+ tkhd.setDuration(getTrackDuration(movie, track));
+ tkhd.setHeight(track.getTrackMetaData().getHeight());
+ tkhd.setWidth(track.getTrackMetaData().getWidth());
+ tkhd.setLayer(track.getTrackMetaData().getLayer());
+ tkhd.setModificationTime(DateHelper.convert(new Date()));
+ tkhd.setTrackId(track.getTrackMetaData().getTrackId());
+ tkhd.setVolume(track.getTrackMetaData().getVolume());
+ return tkhd;
+ }
+
+ private long getTrackDuration(Movie movie, Track track) {
+ return getDuration(track) * movie.getTimescale() / track.getTrackMetaData().getTimescale();
+ }
+
+ protected Box createMdhd(Movie movie, Track track) {
+ MediaHeaderBox mdhd = new MediaHeaderBox();
+ mdhd.setCreationTime(DateHelper.convert(track.getTrackMetaData().getCreationTime()));
+ mdhd.setDuration(getDuration(track));
+ mdhd.setTimescale(track.getTrackMetaData().getTimescale());
+ mdhd.setLanguage(track.getTrackMetaData().getLanguage());
+ return mdhd;
+ }
+
+ protected Box createStbl(Movie movie, Track track) {
+ SampleTableBox stbl = new SampleTableBox();
+
+ stbl.addBox(track.getSampleDescriptionBox());
+ stbl.addBox(new TimeToSampleBox());
+ //stbl.addBox(new SampleToChunkBox());
+ stbl.addBox(new StaticChunkOffsetBox());
+ return stbl;
+ }
+
+ protected Box createMinf(Track track, Movie movie) {
+ MediaInformationBox minf = new MediaInformationBox();
+ minf.addBox(track.getMediaHeaderBox());
+ minf.addBox(createDinf(movie, track));
+ minf.addBox(createStbl(movie, track));
+ return minf;
+ }
+
+ protected Box createMdiaHdlr(Track track, Movie movie) {
+ HandlerBox hdlr = new HandlerBox();
+ hdlr.setHandlerType(track.getHandler());
+ return hdlr;
+ }
+
+ protected Box createMdia(Track track, Movie movie) {
+ MediaBox mdia = new MediaBox();
+ mdia.addBox(createMdhd(movie, track));
+
+
+ mdia.addBox(createMdiaHdlr(track, movie));
+
+
+ mdia.addBox(createMinf(track, movie));
+ return mdia;
+ }
+
+ protected Box createTrak(Track track, Movie movie) {
+ LOG.fine("Creating Track " + track);
+ TrackBox trackBox = new TrackBox();
+ trackBox.addBox(createTkhd(movie, track));
+ trackBox.addBox(createMdia(track, movie));
+ return trackBox;
+ }
+
+ protected DataInformationBox createDinf(Movie movie, Track track) {
+ DataInformationBox dinf = new DataInformationBox();
+ DataReferenceBox dref = new DataReferenceBox();
+ dinf.addBox(dref);
+ DataEntryUrlBox url = new DataEntryUrlBox();
+ url.setFlags(1);
+ dref.addBox(url);
+ return dinf;
+ }
+
+ public FragmentIntersectionFinder getFragmentIntersectionFinder() {
+ return intersectionFinder;
+ }
+
+ public void setIntersectionFinder(FragmentIntersectionFinder intersectionFinder) {
+ this.intersectionFinder = intersectionFinder;
+ }
+
+ protected long getDuration(Track track) {
+ long duration = 0;
+ for (TimeToSampleBox.Entry entry : track.getDecodingTimeEntries()) {
+ duration += entry.getCount() * entry.getDelta();
+ }
+ return duration;
+ }
+
+
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/Mp4Builder.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/Mp4Builder.java.svn-base
new file mode 100644
index 0000000..725745e
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/Mp4Builder.java.svn-base
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.builder;
+
+import com.coremedia.iso.IsoFile;
+import com.googlecode.mp4parser.authoring.Movie;
+
+/**
+ * Transforms a <code>Movie</code> object to an IsoFile. Implementations can
+ * determine the specific format: Fragmented MP4, MP4, MP4 with Apple Metadata,
+ * MP4 with 3GPP Metadata, MOV.
+ */
+public interface Mp4Builder {
+ /**
+ * Builds the actual IsoFile from the Movie.
+ *
+ * @param movie data source
+ * @return the freshly built IsoFile
+ */
+ public IsoFile build(Movie movie);
+
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/SyncSampleIntersectFinderImpl.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/SyncSampleIntersectFinderImpl.java.svn-base
new file mode 100644
index 0000000..2766c5e
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/SyncSampleIntersectFinderImpl.java.svn-base
@@ -0,0 +1,334 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.builder;
+
+import com.coremedia.iso.boxes.TimeToSampleBox;
+import com.coremedia.iso.boxes.sampleentry.AudioSampleEntry;
+import com.googlecode.mp4parser.authoring.Movie;
+import com.googlecode.mp4parser.authoring.Track;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Logger;
+
+import static com.googlecode.mp4parser.util.Math.lcm;
+
+/**
+ * This <code>FragmentIntersectionFinder</code> cuts the input movie video tracks in
+ * fragments of the same length exactly before the sync samples. Audio tracks are cut
+ * into pieces of similar length.
+ */
+public class SyncSampleIntersectFinderImpl implements FragmentIntersectionFinder {
+
+ private static Logger LOG = Logger.getLogger(SyncSampleIntersectFinderImpl.class.getName());
+ private static Map<CacheTuple, long[]> getTimesCache = new ConcurrentHashMap<CacheTuple, long[]>();
+ private static Map<CacheTuple, long[]> getSampleNumbersCache = new ConcurrentHashMap<CacheTuple, long[]>();
+
+ private final int minFragmentDurationSeconds;
+
+ public SyncSampleIntersectFinderImpl() {
+ minFragmentDurationSeconds = 0;
+ }
+
+ /**
+ * Creates a <code>SyncSampleIntersectFinderImpl</code> that will not create any fragment
+ * smaller than the given <code>minFragmentDurationSeconds</code>
+ *
+ * @param minFragmentDurationSeconds the smallest allowable duration of a fragment.
+ */
+ public SyncSampleIntersectFinderImpl(int minFragmentDurationSeconds) {
+ this.minFragmentDurationSeconds = minFragmentDurationSeconds;
+ }
+
+ /**
+ * Gets an array of sample numbers that are meant to be the first sample of each
+ * chunk or fragment.
+ *
+ * @param track concerned track
+ * @param movie the context of the track
+ * @return an array containing the ordinal of each fragment's first sample
+ */
+ public long[] sampleNumbers(Track track, Movie movie) {
+ final CacheTuple key = new CacheTuple(track, movie);
+ final long[] result = getSampleNumbersCache.get(key);
+ if (result != null) {
+ return result;
+ }
+
+ if ("vide".equals(track.getHandler())) {
+ if (track.getSyncSamples() != null && track.getSyncSamples().length > 0) {
+ List<long[]> times = getSyncSamplesTimestamps(movie, track);
+ final long[] commonIndices = getCommonIndices(track.getSyncSamples(), getTimes(track, movie), track.getTrackMetaData().getTimescale(), times.toArray(new long[times.size()][]));
+ getSampleNumbersCache.put(key, commonIndices);
+ return commonIndices;
+ } else {
+ throw new RuntimeException("Video Tracks need sync samples. Only tracks other than video may have no sync samples.");
+ }
+ } else if ("soun".equals(track.getHandler())) {
+ Track referenceTrack = null;
+ for (Track candidate : movie.getTracks()) {
+ if (candidate.getSyncSamples() != null && "vide".equals(candidate.getHandler()) && candidate.getSyncSamples().length > 0) {
+ referenceTrack = candidate;
+ }
+ }
+ if (referenceTrack != null) {
+
+ // Gets the reference track's fra
+ long[] refSyncSamples = sampleNumbers(referenceTrack, movie);
+
+ int refSampleCount = referenceTrack.getSamples().size();
+
+ long[] syncSamples = new long[refSyncSamples.length];
+ long minSampleRate = 192000;
+ for (Track testTrack : movie.getTracks()) {
+ if ("soun".equals(testTrack.getHandler())) {
+ AudioSampleEntry ase = (AudioSampleEntry) testTrack.getSampleDescriptionBox().getSampleEntry();
+ if (ase.getSampleRate() < minSampleRate) {
+ minSampleRate = ase.getSampleRate();
+ long sc = testTrack.getSamples().size();
+ double stretch = (double) sc / refSampleCount;
+ TimeToSampleBox.Entry sttsEntry = testTrack.getDecodingTimeEntries().get(0);
+ long samplesPerFrame = sttsEntry.getDelta(); // Assuming all audio tracks have the same number of samples per frame, which they do for all known types
+
+ for (int i = 0; i < syncSamples.length; i++) {
+ long start = (long) Math.ceil(stretch * (refSyncSamples[i] - 1) * samplesPerFrame);
+ syncSamples[i] = start;
+ // The Stretch makes sure that there are as much audio and video chunks!
+ }
+ break;
+ }
+ }
+ }
+ AudioSampleEntry ase = (AudioSampleEntry) track.getSampleDescriptionBox().getSampleEntry();
+ TimeToSampleBox.Entry sttsEntry = track.getDecodingTimeEntries().get(0);
+ long samplesPerFrame = sttsEntry.getDelta(); // Assuming all audio tracks have the same number of samples per frame, which they do for all known types
+ double factor = (double) ase.getSampleRate() / (double) minSampleRate;
+ if (factor != Math.rint(factor)) { // Not an integer
+ throw new RuntimeException("Sample rates must be a multiple of the lowest sample rate to create a correct file!");
+ }
+ for (int i = 0; i < syncSamples.length; i++) {
+ syncSamples[i] = (long) (1 + syncSamples[i] * factor / (double) samplesPerFrame);
+ }
+ getSampleNumbersCache.put(key, syncSamples);
+ return syncSamples;
+ }
+ throw new RuntimeException("There was absolutely no Track with sync samples. I can't work with that!");
+ } else {
+ // Ok, my track has no sync samples - let's find one with sync samples.
+ for (Track candidate : movie.getTracks()) {
+ if (candidate.getSyncSamples() != null && candidate.getSyncSamples().length > 0) {
+ long[] refSyncSamples = sampleNumbers(candidate, movie);
+ int refSampleCount = candidate.getSamples().size();
+
+ long[] syncSamples = new long[refSyncSamples.length];
+ long sc = track.getSamples().size();
+ double stretch = (double) sc / refSampleCount;
+
+ for (int i = 0; i < syncSamples.length; i++) {
+ long start = (long) Math.ceil(stretch * (refSyncSamples[i] - 1)) + 1;
+ syncSamples[i] = start;
+ // The Stretch makes sure that there are as much audio and video chunks!
+ }
+ getSampleNumbersCache.put(key, syncSamples);
+ return syncSamples;
+ }
+ }
+ throw new RuntimeException("There was absolutely no Track with sync samples. I can't work with that!");
+ }
+
+
+ }
+
+ /**
+ * Calculates the timestamp of all tracks' sync samples.
+ *
+ * @param movie
+ * @param track
+ * @return
+ */
+ public static List<long[]> getSyncSamplesTimestamps(Movie movie, Track track) {
+ List<long[]> times = new LinkedList<long[]>();
+ for (Track currentTrack : movie.getTracks()) {
+ if (currentTrack.getHandler().equals(track.getHandler())) {
+ long[] currentTrackSyncSamples = currentTrack.getSyncSamples();
+ if (currentTrackSyncSamples != null && currentTrackSyncSamples.length > 0) {
+ final long[] currentTrackTimes = getTimes(currentTrack, movie);
+ times.add(currentTrackTimes);
+ }
+ }
+ }
+ return times;
+ }
+
+ public long[] getCommonIndices(long[] syncSamples, long[] syncSampleTimes, long timeScale, long[]... otherTracksTimes) {
+ List<Long> nuSyncSamples = new LinkedList<Long>();
+ List<Long> nuSyncSampleTimes = new LinkedList<Long>();
+
+
+ for (int i = 0; i < syncSampleTimes.length; i++) {
+ boolean foundInEveryRef = true;
+ for (long[] times : otherTracksTimes) {
+ foundInEveryRef &= (Arrays.binarySearch(times, syncSampleTimes[i]) >= 0);
+ }
+
+ if (foundInEveryRef) {
+ // use sample only if found in every other track.
+ nuSyncSamples.add(syncSamples[i]);
+ nuSyncSampleTimes.add(syncSampleTimes[i]);
+ }
+ }
+ // We have two arrays now:
+ // nuSyncSamples: Contains all common sync samples
+ // nuSyncSampleTimes: Contains the times of all sync samples
+
+ // Start: Warn user if samples are not matching!
+ if (nuSyncSamples.size() < (syncSamples.length * 0.25)) {
+ String log = "";
+ log += String.format("%5d - Common: [", nuSyncSamples.size());
+ for (long l : nuSyncSamples) {
+ log += (String.format("%10d,", l));
+ }
+ log += ("]");
+ LOG.warning(log);
+ log = "";
+
+ log += String.format("%5d - In : [", syncSamples.length);
+ for (long l : syncSamples) {
+ log += (String.format("%10d,", l));
+ }
+ log += ("]");
+ LOG.warning(log);
+ LOG.warning("There are less than 25% of common sync samples in the given track.");
+ throw new RuntimeException("There are less than 25% of common sync samples in the given track.");
+ } else if (nuSyncSamples.size() < (syncSamples.length * 0.5)) {
+ LOG.fine("There are less than 50% of common sync samples in the given track. This is implausible but I'm ok to continue.");
+ } else if (nuSyncSamples.size() < syncSamples.length) {
+ LOG.finest("Common SyncSample positions vs. this tracks SyncSample positions: " + nuSyncSamples.size() + " vs. " + syncSamples.length);
+ }
+ // End: Warn user if samples are not matching!
+
+
+
+
+ List<Long> finalSampleList = new LinkedList<Long>();
+
+ if (minFragmentDurationSeconds > 0) {
+ // if minFragmentDurationSeconds is greater 0
+ // we need to throw away certain samples.
+ long lastSyncSampleTime = -1;
+ Iterator<Long> nuSyncSamplesIterator = nuSyncSamples.iterator();
+ Iterator<Long> nuSyncSampleTimesIterator = nuSyncSampleTimes.iterator();
+ while (nuSyncSamplesIterator.hasNext() && nuSyncSampleTimesIterator.hasNext()) {
+ long curSyncSample = nuSyncSamplesIterator.next();
+ long curSyncSampleTime = nuSyncSampleTimesIterator.next();
+ if (lastSyncSampleTime == -1 || (curSyncSampleTime - lastSyncSampleTime) / timeScale >= minFragmentDurationSeconds) {
+ finalSampleList.add(curSyncSample);
+ lastSyncSampleTime = curSyncSampleTime;
+ }
+ }
+ } else {
+ // the list of all samples is the final list of samples
+ // since minFragmentDurationSeconds ist not used.
+ finalSampleList = nuSyncSamples;
+ }
+
+
+ // transform the list to an array
+ long[] finalSampleArray = new long[finalSampleList.size()];
+ for (int i = 0; i < finalSampleArray.length; i++) {
+ finalSampleArray[i] = finalSampleList.get(i);
+ }
+ return finalSampleArray;
+
+ }
+
+
+ private static long[] getTimes(Track track, Movie m) {
+ final CacheTuple key = new CacheTuple(track, m);
+ final long[] result = getTimesCache.get(key);
+ if (result != null) {
+ return result;
+ }
+
+ long[] syncSamples = track.getSyncSamples();
+ long[] syncSampleTimes = new long[syncSamples.length];
+ Queue<TimeToSampleBox.Entry> timeQueue = new LinkedList<TimeToSampleBox.Entry>(track.getDecodingTimeEntries());
+
+ int currentSample = 1; // first syncsample is 1
+ long currentDuration = 0;
+ long currentDelta = 0;
+ int currentSyncSampleIndex = 0;
+ long left = 0;
+
+ final long scalingFactor = calculateTracktimesScalingFactor(m, track);
+
+ while (currentSample <= syncSamples[syncSamples.length - 1]) {
+ if (currentSample++ == syncSamples[currentSyncSampleIndex]) {
+ syncSampleTimes[currentSyncSampleIndex++] = currentDuration * scalingFactor;
+ }
+ if (left-- == 0) {
+ TimeToSampleBox.Entry entry = timeQueue.poll();
+ left = entry.getCount() - 1;
+ currentDelta = entry.getDelta();
+ }
+ currentDuration += currentDelta;
+ }
+ getTimesCache.put(key, syncSampleTimes);
+ return syncSampleTimes;
+ }
+
+ private static long calculateTracktimesScalingFactor(Movie m, Track track) {
+ long timeScale = 1;
+ for (Track track1 : m.getTracks()) {
+ if (track1.getHandler().equals(track.getHandler())) {
+ if (track1.getTrackMetaData().getTimescale() != track.getTrackMetaData().getTimescale()) {
+ timeScale = lcm(timeScale, track1.getTrackMetaData().getTimescale());
+ }
+ }
+ }
+ return timeScale;
+ }
+
+ public static class CacheTuple {
+ Track track;
+ Movie movie;
+
+ public CacheTuple(Track track, Movie movie) {
+ this.track = track;
+ this.movie = movie;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ CacheTuple that = (CacheTuple) o;
+
+ if (movie != null ? !movie.equals(that.movie) : that.movie != null) return false;
+ if (track != null ? !track.equals(that.track) : that.track != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = track != null ? track.hashCode() : 0;
+ result = 31 * result + (movie != null ? movie.hashCode() : 0);
+ return result;
+ }
+ }
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/TwoSecondIntersectionFinder.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/TwoSecondIntersectionFinder.java.svn-base
new file mode 100644
index 0000000..88aa4ab
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/TwoSecondIntersectionFinder.java.svn-base
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.builder;
+
+import com.coremedia.iso.boxes.TimeToSampleBox;
+import com.googlecode.mp4parser.authoring.Movie;
+import com.googlecode.mp4parser.authoring.Track;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This <code>FragmentIntersectionFinder</code> cuts the input movie in 2 second
+ * snippets.
+ */
+public class TwoSecondIntersectionFinder implements FragmentIntersectionFinder {
+
+ protected long getDuration(Track track) {
+ long duration = 0;
+ for (TimeToSampleBox.Entry entry : track.getDecodingTimeEntries()) {
+ duration += entry.getCount() * entry.getDelta();
+ }
+ return duration;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public long[] sampleNumbers(Track track, Movie movie) {
+ List<TimeToSampleBox.Entry> entries = track.getDecodingTimeEntries();
+
+ double trackLength = 0;
+ for (Track thisTrack : movie.getTracks()) {
+ double thisTracksLength = getDuration(thisTrack) / thisTrack.getTrackMetaData().getTimescale();
+ if (trackLength < thisTracksLength) {
+ trackLength = thisTracksLength;
+ }
+ }
+
+ int fragmentCount = (int)Math.ceil(trackLength / 2) - 1;
+ if (fragmentCount < 1) {
+ fragmentCount = 1;
+ }
+
+ long fragments[] = new long[fragmentCount];
+ Arrays.fill(fragments, -1);
+ fragments[0] = 1;
+
+ long time = 0;
+ int samples = 0;
+ for (TimeToSampleBox.Entry entry : entries) {
+ for (int i = 0; i < entry.getCount(); i++) {
+ int currentFragment = (int) (time / track.getTrackMetaData().getTimescale() / 2) + 1;
+ if (currentFragment >= fragments.length) {
+ break;
+ }
+ fragments[currentFragment] = samples++ + 1;
+ time += entry.getDelta();
+ }
+ }
+ long last = samples + 1;
+ // fill all -1 ones.
+ for (int i = fragments.length - 1; i >= 0; i--) {
+ if (fragments[i] == -1) {
+ fragments[i] = last ;
+ }
+ last = fragments[i];
+ }
+ return fragments;
+
+ }
+
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/ByteBufferHelper.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/ByteBufferHelper.java
new file mode 100644
index 0000000..ad21b11
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/ByteBufferHelper.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.builder;
+
+import java.nio.ByteBuffer;
+import java.nio.MappedByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Used to merge adjacent byte buffers.
+ */
+public class ByteBufferHelper {
+ public static List<ByteBuffer> mergeAdjacentBuffers(List<ByteBuffer> samples) {
+ ArrayList<ByteBuffer> nuSamples = new ArrayList<ByteBuffer>(samples.size());
+ for (ByteBuffer buffer : samples) {
+ int lastIndex = nuSamples.size() - 1;
+ if (lastIndex >= 0 && buffer.hasArray() && nuSamples.get(lastIndex).hasArray() && buffer.array() == nuSamples.get(lastIndex).array() &&
+ nuSamples.get(lastIndex).arrayOffset() + nuSamples.get(lastIndex).limit() == buffer.arrayOffset()) {
+ ByteBuffer oldBuffer = nuSamples.remove(lastIndex);
+ ByteBuffer nu = ByteBuffer.wrap(buffer.array(), oldBuffer.arrayOffset(), oldBuffer.limit() + buffer.limit()).slice();
+ // We need to slice here since wrap([], offset, length) just sets position and not the arrayOffset.
+ nuSamples.add(nu);
+ } else if (lastIndex >= 0 &&
+ buffer instanceof MappedByteBuffer && nuSamples.get(lastIndex) instanceof MappedByteBuffer &&
+ nuSamples.get(lastIndex).limit() == nuSamples.get(lastIndex).capacity() - buffer.capacity()) {
+ // This can go wrong - but will it?
+ ByteBuffer oldBuffer = nuSamples.get(lastIndex);
+ oldBuffer.limit(buffer.limit() + oldBuffer.limit());
+ } else {
+ buffer.rewind();
+ nuSamples.add(buffer);
+ }
+ }
+ return nuSamples;
+ }
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/DefaultMp4Builder.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/DefaultMp4Builder.java
new file mode 100644
index 0000000..9bd1ca6
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/DefaultMp4Builder.java
@@ -0,0 +1,576 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.builder;
+
+import com.coremedia.iso.BoxParser;
+import com.coremedia.iso.IsoFile;
+import com.coremedia.iso.IsoTypeWriter;
+import com.coremedia.iso.boxes.Box;
+import com.coremedia.iso.boxes.CompositionTimeToSample;
+import com.coremedia.iso.boxes.ContainerBox;
+import com.coremedia.iso.boxes.DataEntryUrlBox;
+import com.coremedia.iso.boxes.DataInformationBox;
+import com.coremedia.iso.boxes.DataReferenceBox;
+import com.coremedia.iso.boxes.FileTypeBox;
+import com.coremedia.iso.boxes.HandlerBox;
+import com.coremedia.iso.boxes.MediaBox;
+import com.coremedia.iso.boxes.MediaHeaderBox;
+import com.coremedia.iso.boxes.MediaInformationBox;
+import com.coremedia.iso.boxes.MovieBox;
+import com.coremedia.iso.boxes.MovieHeaderBox;
+import com.coremedia.iso.boxes.SampleDependencyTypeBox;
+import com.coremedia.iso.boxes.SampleSizeBox;
+import com.coremedia.iso.boxes.SampleTableBox;
+import com.coremedia.iso.boxes.SampleToChunkBox;
+import com.coremedia.iso.boxes.StaticChunkOffsetBox;
+import com.coremedia.iso.boxes.SyncSampleBox;
+import com.coremedia.iso.boxes.TimeToSampleBox;
+import com.coremedia.iso.boxes.TrackBox;
+import com.coremedia.iso.boxes.TrackHeaderBox;
+import com.googlecode.mp4parser.authoring.DateHelper;
+import com.googlecode.mp4parser.authoring.Movie;
+import com.googlecode.mp4parser.authoring.Track;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.GatheringByteChannel;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import static com.googlecode.mp4parser.util.CastUtils.l2i;
+
+/**
+ * Creates a plain MP4 file from a video. Plain as plain can be.
+ */
+public class DefaultMp4Builder implements Mp4Builder {
+
+ public int STEPSIZE = 64;
+ Set<StaticChunkOffsetBox> chunkOffsetBoxes = new HashSet<StaticChunkOffsetBox>();
+ private static Logger LOG = Logger.getLogger(DefaultMp4Builder.class.getName());
+
+ HashMap<Track, List<ByteBuffer>> track2Sample = new HashMap<Track, List<ByteBuffer>>();
+ HashMap<Track, long[]> track2SampleSizes = new HashMap<Track, long[]>();
+ private FragmentIntersectionFinder intersectionFinder = new TwoSecondIntersectionFinder();
+
+ public void setIntersectionFinder(FragmentIntersectionFinder intersectionFinder) {
+ this.intersectionFinder = intersectionFinder;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public IsoFile build(Movie movie) {
+ LOG.fine("Creating movie " + movie);
+ for (Track track : movie.getTracks()) {
+ // getting the samples may be a time consuming activity
+ List<ByteBuffer> samples = track.getSamples();
+ putSamples(track, samples);
+ long[] sizes = new long[samples.size()];
+ for (int i = 0; i < sizes.length; i++) {
+ sizes[i] = samples.get(i).limit();
+ }
+ putSampleSizes(track, sizes);
+ }
+
+ IsoFile isoFile = new IsoFile();
+ // ouch that is ugly but I don't know how to do it else
+ List<String> minorBrands = new LinkedList<String>();
+ minorBrands.add("isom");
+ minorBrands.add("iso2");
+ minorBrands.add("avc1");
+
+ isoFile.addBox(new FileTypeBox("isom", 0, minorBrands));
+ isoFile.addBox(createMovieBox(movie));
+ InterleaveChunkMdat mdat = new InterleaveChunkMdat(movie);
+ isoFile.addBox(mdat);
+
+ /*
+ dataOffset is where the first sample starts. In this special mdat the samples always start
+ at offset 16 so that we can use the same offset for large boxes and small boxes
+ */
+ long dataOffset = mdat.getDataOffset();
+ for (StaticChunkOffsetBox chunkOffsetBox : chunkOffsetBoxes) {
+ long[] offsets = chunkOffsetBox.getChunkOffsets();
+ for (int i = 0; i < offsets.length; i++) {
+ offsets[i] += dataOffset;
+ }
+ }
+
+
+ return isoFile;
+ }
+
+ public FragmentIntersectionFinder getFragmentIntersectionFinder() {
+ throw new UnsupportedOperationException("No fragment intersection finder in default MP4 builder!");
+ }
+
+ protected long[] putSampleSizes(Track track, long[] sizes) {
+ return track2SampleSizes.put(track, sizes);
+ }
+
+ protected List<ByteBuffer> putSamples(Track track, List<ByteBuffer> samples) {
+ return track2Sample.put(track, samples);
+ }
+
+ private MovieBox createMovieBox(Movie movie) {
+ MovieBox movieBox = new MovieBox();
+ MovieHeaderBox mvhd = new MovieHeaderBox();
+
+ mvhd.setCreationTime(DateHelper.convert(new Date()));
+ mvhd.setModificationTime(DateHelper.convert(new Date()));
+
+ long movieTimeScale = getTimescale(movie);
+ long duration = 0;
+
+ for (Track track : movie.getTracks()) {
+ long tracksDuration = getDuration(track) * movieTimeScale / track.getTrackMetaData().getTimescale();
+ if (tracksDuration > duration) {
+ duration = tracksDuration;
+ }
+
+
+ }
+
+ mvhd.setDuration(duration);
+ mvhd.setTimescale(movieTimeScale);
+ // find the next available trackId
+ long nextTrackId = 0;
+ for (Track track : movie.getTracks()) {
+ nextTrackId = nextTrackId < track.getTrackMetaData().getTrackId() ? track.getTrackMetaData().getTrackId() : nextTrackId;
+ }
+ mvhd.setNextTrackId(++nextTrackId);
+ if (mvhd.getCreationTime() >= 1l << 32 ||
+ mvhd.getModificationTime() >= 1l << 32 ||
+ mvhd.getDuration() >= 1l << 32) {
+ mvhd.setVersion(1);
+ }
+
+ movieBox.addBox(mvhd);
+ for (Track track : movie.getTracks()) {
+ movieBox.addBox(createTrackBox(track, movie));
+ }
+ // metadata here
+ Box udta = createUdta(movie);
+ if (udta != null) {
+ movieBox.addBox(udta);
+ }
+ return movieBox;
+
+ }
+
+ /**
+ * Override to create a user data box that may contain metadata.
+ *
+ * @return a 'udta' box or <code>null</code> if none provided
+ */
+ protected Box createUdta(Movie movie) {
+ return null;
+ }
+
+ private TrackBox createTrackBox(Track track, Movie movie) {
+
+ LOG.info("Creating Mp4TrackImpl " + track);
+ TrackBox trackBox = new TrackBox();
+ TrackHeaderBox tkhd = new TrackHeaderBox();
+ int flags = 0;
+ if (track.isEnabled()) {
+ flags += 1;
+ }
+
+ if (track.isInMovie()) {
+ flags += 2;
+ }
+
+ if (track.isInPreview()) {
+ flags += 4;
+ }
+
+ if (track.isInPoster()) {
+ flags += 8;
+ }
+ tkhd.setFlags(flags);
+
+ tkhd.setAlternateGroup(track.getTrackMetaData().getGroup());
+ tkhd.setCreationTime(DateHelper.convert(track.getTrackMetaData().getCreationTime()));
+ // We need to take edit list box into account in trackheader duration
+ // but as long as I don't support edit list boxes it is sufficient to
+ // just translate media duration to movie timescale
+ tkhd.setDuration(getDuration(track) * getTimescale(movie) / track.getTrackMetaData().getTimescale());
+ tkhd.setHeight(track.getTrackMetaData().getHeight());
+ tkhd.setWidth(track.getTrackMetaData().getWidth());
+ tkhd.setLayer(track.getTrackMetaData().getLayer());
+ tkhd.setModificationTime(DateHelper.convert(new Date()));
+ tkhd.setTrackId(track.getTrackMetaData().getTrackId());
+ tkhd.setVolume(track.getTrackMetaData().getVolume());
+ if (tkhd.getCreationTime() >= 1l << 32 ||
+ tkhd.getModificationTime() >= 1l << 32 ||
+ tkhd.getDuration() >= 1l << 32) {
+ tkhd.setVersion(1);
+ }
+
+ trackBox.addBox(tkhd);
+
+/*
+ EditBox edit = new EditBox();
+ EditListBox editListBox = new EditListBox();
+ editListBox.setEntries(Collections.singletonList(
+ new EditListBox.Entry(editListBox, (long) (track.getTrackMetaData().getStartTime() * getTimescale(movie)), -1, 1)));
+ edit.addBox(editListBox);
+ trackBox.addBox(edit);
+*/
+
+ MediaBox mdia = new MediaBox();
+ trackBox.addBox(mdia);
+ MediaHeaderBox mdhd = new MediaHeaderBox();
+ mdhd.setCreationTime(DateHelper.convert(track.getTrackMetaData().getCreationTime()));
+ mdhd.setDuration(getDuration(track));
+ mdhd.setTimescale(track.getTrackMetaData().getTimescale());
+ mdhd.setLanguage(track.getTrackMetaData().getLanguage());
+ mdia.addBox(mdhd);
+ HandlerBox hdlr = new HandlerBox();
+ mdia.addBox(hdlr);
+
+ hdlr.setHandlerType(track.getHandler());
+
+ MediaInformationBox minf = new MediaInformationBox();
+ minf.addBox(track.getMediaHeaderBox());
+
+ // dinf: all these three boxes tell us is that the actual
+ // data is in the current file and not somewhere external
+ DataInformationBox dinf = new DataInformationBox();
+ DataReferenceBox dref = new DataReferenceBox();
+ dinf.addBox(dref);
+ DataEntryUrlBox url = new DataEntryUrlBox();
+ url.setFlags(1);
+ dref.addBox(url);
+ minf.addBox(dinf);
+ //
+
+ SampleTableBox stbl = new SampleTableBox();
+
+ stbl.addBox(track.getSampleDescriptionBox());
+
+ List<TimeToSampleBox.Entry> decodingTimeToSampleEntries = track.getDecodingTimeEntries();
+ if (decodingTimeToSampleEntries != null && !track.getDecodingTimeEntries().isEmpty()) {
+ TimeToSampleBox stts = new TimeToSampleBox();
+ stts.setEntries(track.getDecodingTimeEntries());
+ stbl.addBox(stts);
+ }
+
+ List<CompositionTimeToSample.Entry> compositionTimeToSampleEntries = track.getCompositionTimeEntries();
+ if (compositionTimeToSampleEntries != null && !compositionTimeToSampleEntries.isEmpty()) {
+ CompositionTimeToSample ctts = new CompositionTimeToSample();
+ ctts.setEntries(compositionTimeToSampleEntries);
+ stbl.addBox(ctts);
+ }
+
+ long[] syncSamples = track.getSyncSamples();
+ if (syncSamples != null && syncSamples.length > 0) {
+ SyncSampleBox stss = new SyncSampleBox();
+ stss.setSampleNumber(syncSamples);
+ stbl.addBox(stss);
+ }
+
+ if (track.getSampleDependencies() != null && !track.getSampleDependencies().isEmpty()) {
+ SampleDependencyTypeBox sdtp = new SampleDependencyTypeBox();
+ sdtp.setEntries(track.getSampleDependencies());
+ stbl.addBox(sdtp);
+ }
+ HashMap<Track, int[]> track2ChunkSizes = new HashMap<Track, int[]>();
+ for (Track current : movie.getTracks()) {
+ track2ChunkSizes.put(current, getChunkSizes(current, movie));
+ }
+ int[] tracksChunkSizes = track2ChunkSizes.get(track);
+
+ SampleToChunkBox stsc = new SampleToChunkBox();
+ stsc.setEntries(new LinkedList<SampleToChunkBox.Entry>());
+ long lastChunkSize = Integer.MIN_VALUE; // to be sure the first chunks hasn't got the same size
+ for (int i = 0; i < tracksChunkSizes.length; i++) {
+ // The sample description index references the sample description box
+ // that describes the samples of this chunk. My Tracks cannot have more
+ // than one sample description box. Therefore 1 is always right
+ // the first chunk has the number '1'
+ if (lastChunkSize != tracksChunkSizes[i]) {
+ stsc.getEntries().add(new SampleToChunkBox.Entry(i + 1, tracksChunkSizes[i], 1));
+ lastChunkSize = tracksChunkSizes[i];
+ }
+ }
+ stbl.addBox(stsc);
+
+ SampleSizeBox stsz = new SampleSizeBox();
+ stsz.setSampleSizes(track2SampleSizes.get(track));
+
+ stbl.addBox(stsz);
+ // The ChunkOffsetBox we create here is just a stub
+ // since we haven't created the whole structure we can't tell where the
+ // first chunk starts (mdat box). So I just let the chunk offset
+ // start at zero and I will add the mdat offset later.
+ StaticChunkOffsetBox stco = new StaticChunkOffsetBox();
+ this.chunkOffsetBoxes.add(stco);
+ long offset = 0;
+ long[] chunkOffset = new long[tracksChunkSizes.length];
+ // all tracks have the same number of chunks
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Calculating chunk offsets for track_" + track.getTrackMetaData().getTrackId());
+ }
+
+
+ for (int i = 0; i < tracksChunkSizes.length; i++) {
+ // The filelayout will be:
+ // chunk_1_track_1,... ,chunk_1_track_n, chunk_2_track_1,... ,chunk_2_track_n, ... , chunk_m_track_1,... ,chunk_m_track_n
+ // calculating the offsets
+ if (LOG.isLoggable(Level.FINER)) {
+ LOG.finer("Calculating chunk offsets for track_" + track.getTrackMetaData().getTrackId() + " chunk " + i);
+ }
+ for (Track current : movie.getTracks()) {
+ if (LOG.isLoggable(Level.FINEST)) {
+ LOG.finest("Adding offsets of track_" + current.getTrackMetaData().getTrackId());
+ }
+ int[] chunkSizes = track2ChunkSizes.get(current);
+ long firstSampleOfChunk = 0;
+ for (int j = 0; j < i; j++) {
+ firstSampleOfChunk += chunkSizes[j];
+ }
+ if (current == track) {
+ chunkOffset[i] = offset;
+ }
+ for (int j = l2i(firstSampleOfChunk); j < firstSampleOfChunk + chunkSizes[i]; j++) {
+ offset += track2SampleSizes.get(current)[j];
+ }
+ }
+ }
+ stco.setChunkOffsets(chunkOffset);
+ stbl.addBox(stco);
+ minf.addBox(stbl);
+ mdia.addBox(minf);
+
+ return trackBox;
+ }
+
+ private class InterleaveChunkMdat implements Box {
+ List<Track> tracks;
+ List<ByteBuffer> samples = new ArrayList<ByteBuffer>();
+ ContainerBox parent;
+
+ long contentSize = 0;
+
+ public ContainerBox getParent() {
+ return parent;
+ }
+
+ public void setParent(ContainerBox parent) {
+ this.parent = parent;
+ }
+
+ public void parse(ReadableByteChannel readableByteChannel, ByteBuffer header, long contentSize, BoxParser boxParser) throws IOException {
+ }
+
+ private InterleaveChunkMdat(Movie movie) {
+
+ tracks = movie.getTracks();
+ Map<Track, int[]> chunks = new HashMap<Track, int[]>();
+ for (Track track : movie.getTracks()) {
+ chunks.put(track, getChunkSizes(track, movie));
+ }
+
+ for (int i = 0; i < chunks.values().iterator().next().length; i++) {
+ for (Track track : tracks) {
+
+ int[] chunkSizes = chunks.get(track);
+ long firstSampleOfChunk = 0;
+ for (int j = 0; j < i; j++) {
+ firstSampleOfChunk += chunkSizes[j];
+ }
+
+ for (int j = l2i(firstSampleOfChunk); j < firstSampleOfChunk + chunkSizes[i]; j++) {
+
+ ByteBuffer s = DefaultMp4Builder.this.track2Sample.get(track).get(j);
+ contentSize += s.limit();
+ samples.add((ByteBuffer) s.rewind());
+ }
+
+ }
+
+ }
+
+ }
+
+ public long getDataOffset() {
+ Box b = this;
+ long offset = 16;
+ while (b.getParent() != null) {
+ for (Box box : b.getParent().getBoxes()) {
+ if (b == box) {
+ break;
+ }
+ offset += box.getSize();
+ }
+ b = b.getParent();
+ }
+ return offset;
+ }
+
+
+ public String getType() {
+ return "mdat";
+ }
+
+ public long getSize() {
+ return 16 + contentSize;
+ }
+
+ private boolean isSmallBox(long contentSize) {
+ return (contentSize + 8) < 4294967296L;
+ }
+
+
+ public void getBox(WritableByteChannel writableByteChannel) throws IOException {
+ ByteBuffer bb = ByteBuffer.allocate(16);
+ long size = getSize();
+ if (isSmallBox(size)) {
+ IsoTypeWriter.writeUInt32(bb, size);
+ } else {
+ IsoTypeWriter.writeUInt32(bb, 1);
+ }
+ bb.put(IsoFile.fourCCtoBytes("mdat"));
+ if (isSmallBox(size)) {
+ bb.put(new byte[8]);
+ } else {
+ IsoTypeWriter.writeUInt64(bb, size);
+ }
+ bb.rewind();
+ writableByteChannel.write(bb);
+ if (writableByteChannel instanceof GatheringByteChannel) {
+ List<ByteBuffer> nuSamples = unifyAdjacentBuffers(samples);
+
+
+ for (int i = 0; i < Math.ceil((double) nuSamples.size() / STEPSIZE); i++) {
+ List<ByteBuffer> sublist = nuSamples.subList(
+ i * STEPSIZE, // start
+ (i + 1) * STEPSIZE < nuSamples.size() ? (i + 1) * STEPSIZE : nuSamples.size()); // end
+ ByteBuffer sampleArray[] = sublist.toArray(new ByteBuffer[sublist.size()]);
+ do {
+ ((GatheringByteChannel) writableByteChannel).write(sampleArray);
+ } while (sampleArray[sampleArray.length - 1].remaining() > 0);
+ }
+ //System.err.println(bytesWritten);
+ } else {
+ for (ByteBuffer sample : samples) {
+ sample.rewind();
+ writableByteChannel.write(sample);
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Gets the chunk sizes for the given track.
+ *
+ * @param track
+ * @param movie
+ * @return
+ */
+ int[] getChunkSizes(Track track, Movie movie) {
+
+ long[] referenceChunkStarts = intersectionFinder.sampleNumbers(track, movie);
+ int[] chunkSizes = new int[referenceChunkStarts.length];
+
+
+ for (int i = 0; i < referenceChunkStarts.length; i++) {
+ long start = referenceChunkStarts[i] - 1;
+ long end;
+ if (referenceChunkStarts.length == i + 1) {
+ end = track.getSamples().size();
+ } else {
+ end = referenceChunkStarts[i + 1] - 1;
+ }
+
+ chunkSizes[i] = l2i(end - start);
+ // The Stretch makes sure that there are as much audio and video chunks!
+ }
+ assert DefaultMp4Builder.this.track2Sample.get(track).size() == sum(chunkSizes) : "The number of samples and the sum of all chunk lengths must be equal";
+ return chunkSizes;
+
+
+ }
+
+
+ private static long sum(int[] ls) {
+ long rc = 0;
+ for (long l : ls) {
+ rc += l;
+ }
+ return rc;
+ }
+
+ protected static long getDuration(Track track) {
+ long duration = 0;
+ for (TimeToSampleBox.Entry entry : track.getDecodingTimeEntries()) {
+ duration += entry.getCount() * entry.getDelta();
+ }
+ return duration;
+ }
+
+ public long getTimescale(Movie movie) {
+ long timescale = movie.getTracks().iterator().next().getTrackMetaData().getTimescale();
+ for (Track track : movie.getTracks()) {
+ timescale = gcd(track.getTrackMetaData().getTimescale(), timescale);
+ }
+ return timescale;
+ }
+
+ public static long gcd(long a, long b) {
+ if (b == 0) {
+ return a;
+ }
+ return gcd(b, a % b);
+ }
+
+ public List<ByteBuffer> unifyAdjacentBuffers(List<ByteBuffer> samples) {
+ ArrayList<ByteBuffer> nuSamples = new ArrayList<ByteBuffer>(samples.size());
+ for (ByteBuffer buffer : samples) {
+ int lastIndex = nuSamples.size() - 1;
+ if (lastIndex >= 0 && buffer.hasArray() && nuSamples.get(lastIndex).hasArray() && buffer.array() == nuSamples.get(lastIndex).array() &&
+ nuSamples.get(lastIndex).arrayOffset() + nuSamples.get(lastIndex).limit() == buffer.arrayOffset()) {
+ ByteBuffer oldBuffer = nuSamples.remove(lastIndex);
+ ByteBuffer nu = ByteBuffer.wrap(buffer.array(), oldBuffer.arrayOffset(), oldBuffer.limit() + buffer.limit()).slice();
+ // We need to slice here since wrap([], offset, length) just sets position and not the arrayOffset.
+ nuSamples.add(nu);
+ } else if (lastIndex >= 0 &&
+ buffer instanceof MappedByteBuffer && nuSamples.get(lastIndex) instanceof MappedByteBuffer &&
+ nuSamples.get(lastIndex).limit() == nuSamples.get(lastIndex).capacity() - buffer.capacity()) {
+ // This can go wrong - but will it?
+ ByteBuffer oldBuffer = nuSamples.get(lastIndex);
+ oldBuffer.limit(buffer.limit() + oldBuffer.limit());
+ } else {
+ nuSamples.add(buffer);
+ }
+ }
+ return nuSamples;
+ }
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/FragmentIntersectionFinder.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/FragmentIntersectionFinder.java
new file mode 100644
index 0000000..1224bbf
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/FragmentIntersectionFinder.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.builder;
+
+import com.googlecode.mp4parser.authoring.Movie;
+import com.googlecode.mp4parser.authoring.Track;
+
+/**
+ *
+ */
+public interface FragmentIntersectionFinder {
+ /**
+ * Gets the ordinal number of the samples which will be the first sample
+ * in each fragment.
+ *
+ * @param track concerned track
+ * @param movie the context of the track
+ * @return an array containing the ordinal of each fragment's first sample
+ */
+ public long[] sampleNumbers(Track track, Movie movie);
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/FragmentedMp4Builder.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/FragmentedMp4Builder.java
new file mode 100644
index 0000000..c65ff1c
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/FragmentedMp4Builder.java
@@ -0,0 +1,742 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.builder;
+
+import com.coremedia.iso.BoxParser;
+import com.coremedia.iso.IsoFile;
+import com.coremedia.iso.IsoTypeWriter;
+import com.coremedia.iso.boxes.*;
+import com.coremedia.iso.boxes.fragment.*;
+import com.googlecode.mp4parser.authoring.DateHelper;
+import com.googlecode.mp4parser.authoring.Movie;
+import com.googlecode.mp4parser.authoring.Track;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.GatheringByteChannel;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+import java.util.*;
+import java.util.logging.Logger;
+
+import static com.googlecode.mp4parser.util.CastUtils.l2i;
+
+/**
+ * Creates a fragmented MP4 file.
+ */
+public class FragmentedMp4Builder implements Mp4Builder {
+ private static final Logger LOG = Logger.getLogger(FragmentedMp4Builder.class.getName());
+
+ protected FragmentIntersectionFinder intersectionFinder;
+
+ public FragmentedMp4Builder() {
+ this.intersectionFinder = new SyncSampleIntersectFinderImpl();
+ }
+
+ public List<String> getAllowedHandlers() {
+ return Arrays.asList("soun", "vide");
+ }
+
+ public Box createFtyp(Movie movie) {
+ List<String> minorBrands = new LinkedList<String>();
+ minorBrands.add("isom");
+ minorBrands.add("iso2");
+ minorBrands.add("avc1");
+ return new FileTypeBox("isom", 0, minorBrands);
+ }
+
+ /**
+ * Some formats require sorting of the fragments. E.g. Ultraviolet CFF files are required
+ * to contain the fragments size sort:
+ * <ul>
+ * <li>video[1].getBytes().length < audio[1].getBytes().length < subs[1].getBytes().length</li>
+ * <li> audio[2].getBytes().length < video[2].getBytes().length < subs[2].getBytes().length</li>
+ * </ul>
+ *
+ * make this fragment:
+ *
+ * <ol>
+ * <li>video[1]</li>
+ * <li>audio[1]</li>
+ * <li>subs[1]</li>
+ * <li>audio[2]</li>
+ * <li>video[2]</li>
+ * <li>subs[2]</li>
+ * </ol>
+ *
+ * @param tracks the list of tracks to returned sorted
+ * @param cycle current fragment (sorting may vary between the fragments)
+ * @param intersectionMap a map from tracks to their fragments' first samples.
+ * @return the list of tracks in order of appearance in the fragment
+ */
+ protected List<Track> sortTracksInSequence(List<Track> tracks, final int cycle, final Map<Track, long[]> intersectionMap) {
+ tracks = new LinkedList<Track>(tracks);
+ Collections.sort(tracks, new Comparator<Track>() {
+ public int compare(Track o1, Track o2) {
+ long[] startSamples1 = intersectionMap.get(o1);
+ long startSample1 = startSamples1[cycle];
+ // one based sample numbers - the first sample is 1
+ long endSample1 = cycle + 1 < startSamples1.length ? startSamples1[cycle + 1] : o1.getSamples().size() + 1;
+ long[] startSamples2 = intersectionMap.get(o2);
+ long startSample2 = startSamples2[cycle];
+ // one based sample numbers - the first sample is 1
+ long endSample2 = cycle + 1 < startSamples2.length ? startSamples2[cycle + 1] : o2.getSamples().size() + 1;
+ List<ByteBuffer> samples1 = o1.getSamples().subList(l2i(startSample1) - 1, l2i(endSample1) - 1);
+ List<ByteBuffer> samples2 = o2.getSamples().subList(l2i(startSample2) - 1, l2i(endSample2) - 1);
+ int size1 = 0;
+ for (ByteBuffer byteBuffer : samples1) {
+ size1 += byteBuffer.limit();
+ }
+ int size2 = 0;
+ for (ByteBuffer byteBuffer : samples2) {
+ size2 += byteBuffer.limit();
+ }
+ return size1 - size2;
+ }
+ });
+ return tracks;
+ }
+
+ protected List<Box> createMoofMdat(final Movie movie) {
+ List<Box> boxes = new LinkedList<Box>();
+ HashMap<Track, long[]> intersectionMap = new HashMap<Track, long[]>();
+ int maxNumberOfFragments = 0;
+ for (Track track : movie.getTracks()) {
+ long[] intersects = intersectionFinder.sampleNumbers(track, movie);
+ intersectionMap.put(track, intersects);
+ maxNumberOfFragments = Math.max(maxNumberOfFragments, intersects.length);
+ }
+
+
+ int sequence = 1;
+ // this loop has two indices:
+
+ for (int cycle = 0; cycle < maxNumberOfFragments; cycle++) {
+
+ final List<Track> sortedTracks = sortTracksInSequence(movie.getTracks(), cycle, intersectionMap);
+
+ for (Track track : sortedTracks) {
+ if (getAllowedHandlers().isEmpty() || getAllowedHandlers().contains(track.getHandler())) {
+ long[] startSamples = intersectionMap.get(track);
+ //some tracks may have less fragments -> skip them
+ if (cycle < startSamples.length) {
+
+ long startSample = startSamples[cycle];
+ // one based sample numbers - the first sample is 1
+ long endSample = cycle + 1 < startSamples.length ? startSamples[cycle + 1] : track.getSamples().size() + 1;
+
+ // if startSample == endSample the cycle is empty!
+ if (startSample != endSample) {
+ boxes.add(createMoof(startSample, endSample, track, sequence));
+ boxes.add(createMdat(startSample, endSample, track, sequence++));
+ }
+ }
+ }
+ }
+ }
+ return boxes;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public IsoFile build(Movie movie) {
+ LOG.fine("Creating movie " + movie);
+ IsoFile isoFile = new IsoFile();
+
+
+ isoFile.addBox(createFtyp(movie));
+ isoFile.addBox(createMoov(movie));
+
+ for (Box box : createMoofMdat(movie)) {
+ isoFile.addBox(box);
+ }
+ isoFile.addBox(createMfra(movie, isoFile));
+
+ return isoFile;
+ }
+
+ protected Box createMdat(final long startSample, final long endSample, final Track track, final int i) {
+
+ class Mdat implements Box {
+ ContainerBox parent;
+
+ public ContainerBox getParent() {
+ return parent;
+ }
+
+ public void setParent(ContainerBox parent) {
+ this.parent = parent;
+ }
+
+ public long getSize() {
+ long size = 8; // I don't expect 2gig fragments
+ for (ByteBuffer sample : getSamples(startSample, endSample, track, i)) {
+ size += sample.limit();
+ }
+ return size;
+ }
+
+ public String getType() {
+ return "mdat";
+ }
+
+ public void getBox(WritableByteChannel writableByteChannel) throws IOException {
+ List<ByteBuffer> bbs = getSamples(startSample, endSample, track, i);
+ final List<ByteBuffer> samples = ByteBufferHelper.mergeAdjacentBuffers(bbs);
+ ByteBuffer header = ByteBuffer.allocate(8);
+ IsoTypeWriter.writeUInt32(header, l2i(getSize()));
+ header.put(IsoFile.fourCCtoBytes(getType()));
+ header.rewind();
+ writableByteChannel.write(header);
+ if (writableByteChannel instanceof GatheringByteChannel) {
+
+ int STEPSIZE = 1024;
+ // This is required to prevent android from crashing
+ // it seems that {@link GatheringByteChannel#write(java.nio.ByteBuffer[])}
+ // just handles up to 1024 buffers
+ for (int i = 0; i < Math.ceil((double) samples.size() / STEPSIZE); i++) {
+ List<ByteBuffer> sublist = samples.subList(
+ i * STEPSIZE, // start
+ (i + 1) * STEPSIZE < samples.size() ? (i + 1) * STEPSIZE : samples.size()); // end
+ ByteBuffer sampleArray[] = sublist.toArray(new ByteBuffer[sublist.size()]);
+ do {
+ ((GatheringByteChannel) writableByteChannel).write(sampleArray);
+ } while (sampleArray[sampleArray.length - 1].remaining() > 0);
+ }
+ //System.err.println(bytesWritten);
+ } else {
+ for (ByteBuffer sample : samples) {
+ sample.rewind();
+ writableByteChannel.write(sample);
+ }
+ }
+
+ }
+
+ public void parse(ReadableByteChannel readableByteChannel, ByteBuffer header, long contentSize, BoxParser boxParser) throws IOException {
+
+ }
+ }
+
+ return new Mdat();
+ }
+
+ protected Box createTfhd(long startSample, long endSample, Track track, int sequenceNumber) {
+ TrackFragmentHeaderBox tfhd = new TrackFragmentHeaderBox();
+ SampleFlags sf = new SampleFlags();
+
+ tfhd.setDefaultSampleFlags(sf);
+ tfhd.setBaseDataOffset(-1);
+ tfhd.setTrackId(track.getTrackMetaData().getTrackId());
+ return tfhd;
+ }
+
+ protected Box createMfhd(long startSample, long endSample, Track track, int sequenceNumber) {
+ MovieFragmentHeaderBox mfhd = new MovieFragmentHeaderBox();
+ mfhd.setSequenceNumber(sequenceNumber);
+ return mfhd;
+ }
+
+ protected Box createTraf(long startSample, long endSample, Track track, int sequenceNumber) {
+ TrackFragmentBox traf = new TrackFragmentBox();
+ traf.addBox(createTfhd(startSample, endSample, track, sequenceNumber));
+ for (Box trun : createTruns(startSample, endSample, track, sequenceNumber)) {
+ traf.addBox(trun);
+ }
+
+ return traf;
+ }
+
+
+ /**
+ * Gets the all samples starting with <code>startSample</code> (one based -> one is the first) and
+ * ending with <code>endSample</code> (exclusive).
+ *
+ * @param startSample low endpoint (inclusive) of the sample sequence
+ * @param endSample high endpoint (exclusive) of the sample sequence
+ * @param track source of the samples
+ * @param sequenceNumber the fragment index of the requested list of samples
+ * @return a <code>List&lt;ByteBuffer></code> of raw samples
+ */
+ protected List<ByteBuffer> getSamples(long startSample, long endSample, Track track, int sequenceNumber) {
+ // since startSample and endSample are one-based substract 1 before addressing list elements
+ return track.getSamples().subList(l2i(startSample) - 1, l2i(endSample) - 1);
+ }
+
+ /**
+ * Gets the sizes of a sequence of samples-
+ *
+ * @param startSample low endpoint (inclusive) of the sample sequence
+ * @param endSample high endpoint (exclusive) of the sample sequence
+ * @param track source of the samples
+ * @param sequenceNumber the fragment index of the requested list of samples
+ * @return
+ */
+ protected long[] getSampleSizes(long startSample, long endSample, Track track, int sequenceNumber) {
+ List<ByteBuffer> samples = getSamples(startSample, endSample, track, sequenceNumber);
+
+ long[] sampleSizes = new long[samples.size()];
+ for (int i = 0; i < sampleSizes.length; i++) {
+ sampleSizes[i] = samples.get(i).limit();
+ }
+ return sampleSizes;
+ }
+
+ /**
+ * Creates one or more track run boxes for a given sequence.
+ *
+ * @param startSample low endpoint (inclusive) of the sample sequence
+ * @param endSample high endpoint (exclusive) of the sample sequence
+ * @param track source of the samples
+ * @param sequenceNumber the fragment index of the requested list of samples
+ * @return the list of TrackRun boxes.
+ */
+ protected List<? extends Box> createTruns(long startSample, long endSample, Track track, int sequenceNumber) {
+ TrackRunBox trun = new TrackRunBox();
+ long[] sampleSizes = getSampleSizes(startSample, endSample, track, sequenceNumber);
+
+ trun.setSampleDurationPresent(true);
+ trun.setSampleSizePresent(true);
+ List<TrackRunBox.Entry> entries = new ArrayList<TrackRunBox.Entry>(l2i(endSample - startSample));
+
+
+ Queue<TimeToSampleBox.Entry> timeQueue = new LinkedList<TimeToSampleBox.Entry>(track.getDecodingTimeEntries());
+ long left = startSample - 1;
+ long curEntryLeft = timeQueue.peek().getCount();
+ while (left > curEntryLeft) {
+ left -= curEntryLeft;
+ timeQueue.remove();
+ curEntryLeft = timeQueue.peek().getCount();
+ }
+ curEntryLeft -= left;
+
+
+ Queue<CompositionTimeToSample.Entry> compositionTimeQueue =
+ track.getCompositionTimeEntries() != null && track.getCompositionTimeEntries().size() > 0 ?
+ new LinkedList<CompositionTimeToSample.Entry>(track.getCompositionTimeEntries()) : null;
+ long compositionTimeEntriesLeft = compositionTimeQueue != null ? compositionTimeQueue.peek().getCount() : -1;
+
+
+ trun.setSampleCompositionTimeOffsetPresent(compositionTimeEntriesLeft > 0);
+
+ // fast forward composition stuff
+ for (long i = 1; i < startSample; i++) {
+ if (compositionTimeQueue != null) {
+ //trun.setSampleCompositionTimeOffsetPresent(true);
+ if (--compositionTimeEntriesLeft == 0 && compositionTimeQueue.size() > 1) {
+ compositionTimeQueue.remove();
+ compositionTimeEntriesLeft = compositionTimeQueue.element().getCount();
+ }
+ }
+ }
+
+ boolean sampleFlagsRequired = (track.getSampleDependencies() != null && !track.getSampleDependencies().isEmpty() ||
+ track.getSyncSamples() != null && track.getSyncSamples().length != 0);
+
+ trun.setSampleFlagsPresent(sampleFlagsRequired);
+
+ for (int i = 0; i < sampleSizes.length; i++) {
+ TrackRunBox.Entry entry = new TrackRunBox.Entry();
+ entry.setSampleSize(sampleSizes[i]);
+ if (sampleFlagsRequired) {
+ //if (false) {
+ SampleFlags sflags = new SampleFlags();
+
+ if (track.getSampleDependencies() != null && !track.getSampleDependencies().isEmpty()) {
+ SampleDependencyTypeBox.Entry e = track.getSampleDependencies().get(i);
+ sflags.setSampleDependsOn(e.getSampleDependsOn());
+ sflags.setSampleIsDependedOn(e.getSampleIsDependentOn());
+ sflags.setSampleHasRedundancy(e.getSampleHasRedundancy());
+ }
+ if (track.getSyncSamples() != null && track.getSyncSamples().length > 0) {
+ // we have to mark non-sync samples!
+ if (Arrays.binarySearch(track.getSyncSamples(), startSample + i) >= 0) {
+ sflags.setSampleIsDifferenceSample(false);
+ sflags.setSampleDependsOn(2);
+ } else {
+ sflags.setSampleIsDifferenceSample(true);
+ sflags.setSampleDependsOn(1);
+ }
+ }
+ // i don't have sample degradation
+ entry.setSampleFlags(sflags);
+
+ }
+
+ entry.setSampleDuration(timeQueue.peek().getDelta());
+ if (--curEntryLeft == 0 && timeQueue.size() > 1) {
+ timeQueue.remove();
+ curEntryLeft = timeQueue.peek().getCount();
+ }
+
+ if (compositionTimeQueue != null) {
+ entry.setSampleCompositionTimeOffset(compositionTimeQueue.peek().getOffset());
+ if (--compositionTimeEntriesLeft == 0 && compositionTimeQueue.size() > 1) {
+ compositionTimeQueue.remove();
+ compositionTimeEntriesLeft = compositionTimeQueue.element().getCount();
+ }
+ }
+ entries.add(entry);
+ }
+
+ trun.setEntries(entries);
+
+ return Collections.singletonList(trun);
+ }
+
+ /**
+ * Creates a 'moof' box for a given sequence of samples.
+ *
+ * @param startSample low endpoint (inclusive) of the sample sequence
+ * @param endSample high endpoint (exclusive) of the sample sequence
+ * @param track source of the samples
+ * @param sequenceNumber the fragment index of the requested list of samples
+ * @return the list of TrackRun boxes.
+ */
+ protected Box createMoof(long startSample, long endSample, Track track, int sequenceNumber) {
+ MovieFragmentBox moof = new MovieFragmentBox();
+ moof.addBox(createMfhd(startSample, endSample, track, sequenceNumber));
+ moof.addBox(createTraf(startSample, endSample, track, sequenceNumber));
+
+ TrackRunBox firstTrun = moof.getTrackRunBoxes().get(0);
+ firstTrun.setDataOffset(1); // dummy to make size correct
+ firstTrun.setDataOffset((int) (8 + moof.getSize())); // mdat header + moof size
+
+ return moof;
+ }
+
+ /**
+ * Creates a single 'mvhd' movie header box for a given movie.
+ *
+ * @param movie the concerned movie
+ * @return an 'mvhd' box
+ */
+ protected Box createMvhd(Movie movie) {
+ MovieHeaderBox mvhd = new MovieHeaderBox();
+ mvhd.setVersion(1);
+ mvhd.setCreationTime(DateHelper.convert(new Date()));
+ mvhd.setModificationTime(DateHelper.convert(new Date()));
+ long movieTimeScale = movie.getTimescale();
+ long duration = 0;
+
+ for (Track track : movie.getTracks()) {
+ long tracksDuration = getDuration(track) * movieTimeScale / track.getTrackMetaData().getTimescale();
+ if (tracksDuration > duration) {
+ duration = tracksDuration;
+ }
+
+
+ }
+
+ mvhd.setDuration(duration);
+ mvhd.setTimescale(movieTimeScale);
+ // find the next available trackId
+ long nextTrackId = 0;
+ for (Track track : movie.getTracks()) {
+ nextTrackId = nextTrackId < track.getTrackMetaData().getTrackId() ? track.getTrackMetaData().getTrackId() : nextTrackId;
+ }
+ mvhd.setNextTrackId(++nextTrackId);
+ return mvhd;
+ }
+
+ /**
+ * Creates a fully populated 'moov' box with all child boxes. Child boxes are:
+ * <ul>
+ * <li>{@link #createMvhd(com.googlecode.mp4parser.authoring.Movie) mvhd}</li>
+ * <li>{@link #createMvex(com.googlecode.mp4parser.authoring.Movie) mvex}</li>
+ * <li>a {@link #createTrak(com.googlecode.mp4parser.authoring.Track, com.googlecode.mp4parser.authoring.Movie) trak} for every track</li>
+ * </ul>
+ *
+ * @param movie the concerned movie
+ * @return fully populated 'moov'
+ */
+ protected Box createMoov(Movie movie) {
+ MovieBox movieBox = new MovieBox();
+
+ movieBox.addBox(createMvhd(movie));
+ movieBox.addBox(createMvex(movie));
+
+ for (Track track : movie.getTracks()) {
+ movieBox.addBox(createTrak(track, movie));
+ }
+ // metadata here
+ return movieBox;
+
+ }
+
+ /**
+ * Creates a 'tfra' - track fragment random access box for the given track with the isoFile.
+ * The tfra contains a map of random access points with time as key and offset within the isofile
+ * as value.
+ *
+ * @param track the concerned track
+ * @param isoFile the track is contained in
+ * @return a track fragment random access box.
+ */
+ protected Box createTfra(Track track, IsoFile isoFile) {
+ TrackFragmentRandomAccessBox tfra = new TrackFragmentRandomAccessBox();
+ tfra.setVersion(1); // use long offsets and times
+ List<TrackFragmentRandomAccessBox.Entry> offset2timeEntries = new LinkedList<TrackFragmentRandomAccessBox.Entry>();
+ List<Box> boxes = isoFile.getBoxes();
+ long offset = 0;
+ long duration = 0;
+ for (Box box : boxes) {
+ if (box instanceof MovieFragmentBox) {
+ List<TrackFragmentBox> trafs = ((MovieFragmentBox) box).getBoxes(TrackFragmentBox.class);
+ for (int i = 0; i < trafs.size(); i++) {
+ TrackFragmentBox traf = trafs.get(i);
+ if (traf.getTrackFragmentHeaderBox().getTrackId() == track.getTrackMetaData().getTrackId()) {
+ // here we are at the offset required for the current entry.
+ List<TrackRunBox> truns = traf.getBoxes(TrackRunBox.class);
+ for (int j = 0; j < truns.size(); j++) {
+ List<TrackFragmentRandomAccessBox.Entry> offset2timeEntriesThisTrun = new LinkedList<TrackFragmentRandomAccessBox.Entry>();
+ TrackRunBox trun = truns.get(j);
+ for (int k = 0; k < trun.getEntries().size(); k++) {
+ TrackRunBox.Entry trunEntry = trun.getEntries().get(k);
+ SampleFlags sf = null;
+ if (k == 0 && trun.isFirstSampleFlagsPresent()) {
+ sf = trun.getFirstSampleFlags();
+ } else if (trun.isSampleFlagsPresent()) {
+ sf = trunEntry.getSampleFlags();
+ } else {
+ List<MovieExtendsBox> mvexs = isoFile.getMovieBox().getBoxes(MovieExtendsBox.class);
+ for (MovieExtendsBox mvex : mvexs) {
+ List<TrackExtendsBox> trexs = mvex.getBoxes(TrackExtendsBox.class);
+ for (TrackExtendsBox trex : trexs) {
+ if (trex.getTrackId() == track.getTrackMetaData().getTrackId()) {
+ sf = trex.getDefaultSampleFlags();
+ }
+ }
+ }
+
+ }
+ if (sf == null) {
+ throw new RuntimeException("Could not find any SampleFlags to indicate random access or not");
+ }
+ if (sf.getSampleDependsOn() == 2) {
+ offset2timeEntriesThisTrun.add(new TrackFragmentRandomAccessBox.Entry(
+ duration,
+ offset,
+ i + 1, j + 1, k + 1));
+ }
+ duration += trunEntry.getSampleDuration();
+ }
+ if (offset2timeEntriesThisTrun.size() == trun.getEntries().size() && trun.getEntries().size() > 0) {
+ // Oooops every sample seems to be random access sample
+ // is this an audio track? I don't care.
+ // I just use the first for trun sample for tfra random access
+ offset2timeEntries.add(offset2timeEntriesThisTrun.get(0));
+ } else {
+ offset2timeEntries.addAll(offset2timeEntriesThisTrun);
+ }
+ }
+ }
+ }
+ }
+
+
+ offset += box.getSize();
+ }
+ tfra.setEntries(offset2timeEntries);
+ tfra.setTrackId(track.getTrackMetaData().getTrackId());
+ return tfra;
+ }
+
+ /**
+ * Creates a 'mfra' - movie fragment random access box for the given movie in the given
+ * isofile. Uses {@link #createTfra(com.googlecode.mp4parser.authoring.Track, com.coremedia.iso.IsoFile)}
+ * to generate the child boxes.
+ *
+ * @param movie concerned movie
+ * @param isoFile concerned isofile
+ * @return a complete 'mfra' box
+ */
+ protected Box createMfra(Movie movie, IsoFile isoFile) {
+ MovieFragmentRandomAccessBox mfra = new MovieFragmentRandomAccessBox();
+ for (Track track : movie.getTracks()) {
+ mfra.addBox(createTfra(track, isoFile));
+ }
+
+ MovieFragmentRandomAccessOffsetBox mfro = new MovieFragmentRandomAccessOffsetBox();
+ mfra.addBox(mfro);
+ mfro.setMfraSize(mfra.getSize());
+ return mfra;
+ }
+
+ protected Box createTrex(Movie movie, Track track) {
+ TrackExtendsBox trex = new TrackExtendsBox();
+ trex.setTrackId(track.getTrackMetaData().getTrackId());
+ trex.setDefaultSampleDescriptionIndex(1);
+ trex.setDefaultSampleDuration(0);
+ trex.setDefaultSampleSize(0);
+ SampleFlags sf = new SampleFlags();
+ if ("soun".equals(track.getHandler())) {
+ // as far as I know there is no audio encoding
+ // where the sample are not self contained.
+ sf.setSampleDependsOn(2);
+ sf.setSampleIsDependedOn(2);
+ }
+ trex.setDefaultSampleFlags(sf);
+ return trex;
+ }
+
+ /**
+ * Creates a 'mvex' - movie extends box and populates it with 'trex' boxes
+ * by calling {@link #createTrex(com.googlecode.mp4parser.authoring.Movie, com.googlecode.mp4parser.authoring.Track)}
+ * for each track to generate them
+ *
+ * @param movie the source movie
+ * @return a complete 'mvex'
+ */
+ protected Box createMvex(Movie movie) {
+ MovieExtendsBox mvex = new MovieExtendsBox();
+ final MovieExtendsHeaderBox mved = new MovieExtendsHeaderBox();
+ for (Track track : movie.getTracks()) {
+ final long trackDuration = getTrackDuration(movie, track);
+ if (mved.getFragmentDuration() < trackDuration) {
+ mved.setFragmentDuration(trackDuration);
+ }
+ }
+ mvex.addBox(mved);
+
+ for (Track track : movie.getTracks()) {
+ mvex.addBox(createTrex(movie, track));
+ }
+ return mvex;
+ }
+
+ protected Box createTkhd(Movie movie, Track track) {
+ TrackHeaderBox tkhd = new TrackHeaderBox();
+ tkhd.setVersion(1);
+ int flags = 0;
+ if (track.isEnabled()) {
+ flags += 1;
+ }
+
+ if (track.isInMovie()) {
+ flags += 2;
+ }
+
+ if (track.isInPreview()) {
+ flags += 4;
+ }
+
+ if (track.isInPoster()) {
+ flags += 8;
+ }
+ tkhd.setFlags(flags);
+
+ tkhd.setAlternateGroup(track.getTrackMetaData().getGroup());
+ tkhd.setCreationTime(DateHelper.convert(track.getTrackMetaData().getCreationTime()));
+ // We need to take edit list box into account in trackheader duration
+ // but as long as I don't support edit list boxes it is sufficient to
+ // just translate media duration to movie timescale
+ tkhd.setDuration(getTrackDuration(movie, track));
+ tkhd.setHeight(track.getTrackMetaData().getHeight());
+ tkhd.setWidth(track.getTrackMetaData().getWidth());
+ tkhd.setLayer(track.getTrackMetaData().getLayer());
+ tkhd.setModificationTime(DateHelper.convert(new Date()));
+ tkhd.setTrackId(track.getTrackMetaData().getTrackId());
+ tkhd.setVolume(track.getTrackMetaData().getVolume());
+ return tkhd;
+ }
+
+ private long getTrackDuration(Movie movie, Track track) {
+ return getDuration(track) * movie.getTimescale() / track.getTrackMetaData().getTimescale();
+ }
+
+ protected Box createMdhd(Movie movie, Track track) {
+ MediaHeaderBox mdhd = new MediaHeaderBox();
+ mdhd.setCreationTime(DateHelper.convert(track.getTrackMetaData().getCreationTime()));
+ mdhd.setDuration(getDuration(track));
+ mdhd.setTimescale(track.getTrackMetaData().getTimescale());
+ mdhd.setLanguage(track.getTrackMetaData().getLanguage());
+ return mdhd;
+ }
+
+ protected Box createStbl(Movie movie, Track track) {
+ SampleTableBox stbl = new SampleTableBox();
+
+ stbl.addBox(track.getSampleDescriptionBox());
+ stbl.addBox(new TimeToSampleBox());
+ //stbl.addBox(new SampleToChunkBox());
+ stbl.addBox(new StaticChunkOffsetBox());
+ return stbl;
+ }
+
+ protected Box createMinf(Track track, Movie movie) {
+ MediaInformationBox minf = new MediaInformationBox();
+ minf.addBox(track.getMediaHeaderBox());
+ minf.addBox(createDinf(movie, track));
+ minf.addBox(createStbl(movie, track));
+ return minf;
+ }
+
+ protected Box createMdiaHdlr(Track track, Movie movie) {
+ HandlerBox hdlr = new HandlerBox();
+ hdlr.setHandlerType(track.getHandler());
+ return hdlr;
+ }
+
+ protected Box createMdia(Track track, Movie movie) {
+ MediaBox mdia = new MediaBox();
+ mdia.addBox(createMdhd(movie, track));
+
+
+ mdia.addBox(createMdiaHdlr(track, movie));
+
+
+ mdia.addBox(createMinf(track, movie));
+ return mdia;
+ }
+
+ protected Box createTrak(Track track, Movie movie) {
+ LOG.fine("Creating Track " + track);
+ TrackBox trackBox = new TrackBox();
+ trackBox.addBox(createTkhd(movie, track));
+ trackBox.addBox(createMdia(track, movie));
+ return trackBox;
+ }
+
+ protected DataInformationBox createDinf(Movie movie, Track track) {
+ DataInformationBox dinf = new DataInformationBox();
+ DataReferenceBox dref = new DataReferenceBox();
+ dinf.addBox(dref);
+ DataEntryUrlBox url = new DataEntryUrlBox();
+ url.setFlags(1);
+ dref.addBox(url);
+ return dinf;
+ }
+
+ public FragmentIntersectionFinder getFragmentIntersectionFinder() {
+ return intersectionFinder;
+ }
+
+ public void setIntersectionFinder(FragmentIntersectionFinder intersectionFinder) {
+ this.intersectionFinder = intersectionFinder;
+ }
+
+ protected long getDuration(Track track) {
+ long duration = 0;
+ for (TimeToSampleBox.Entry entry : track.getDecodingTimeEntries()) {
+ duration += entry.getCount() * entry.getDelta();
+ }
+ return duration;
+ }
+
+
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/Mp4Builder.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/Mp4Builder.java
new file mode 100644
index 0000000..725745e
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/Mp4Builder.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.builder;
+
+import com.coremedia.iso.IsoFile;
+import com.googlecode.mp4parser.authoring.Movie;
+
+/**
+ * Transforms a <code>Movie</code> object to an IsoFile. Implementations can
+ * determine the specific format: Fragmented MP4, MP4, MP4 with Apple Metadata,
+ * MP4 with 3GPP Metadata, MOV.
+ */
+public interface Mp4Builder {
+ /**
+ * Builds the actual IsoFile from the Movie.
+ *
+ * @param movie data source
+ * @return the freshly built IsoFile
+ */
+ public IsoFile build(Movie movie);
+
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/SyncSampleIntersectFinderImpl.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/SyncSampleIntersectFinderImpl.java
new file mode 100644
index 0000000..2766c5e
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/SyncSampleIntersectFinderImpl.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.builder;
+
+import com.coremedia.iso.boxes.TimeToSampleBox;
+import com.coremedia.iso.boxes.sampleentry.AudioSampleEntry;
+import com.googlecode.mp4parser.authoring.Movie;
+import com.googlecode.mp4parser.authoring.Track;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Logger;
+
+import static com.googlecode.mp4parser.util.Math.lcm;
+
+/**
+ * This <code>FragmentIntersectionFinder</code> cuts the input movie video tracks in
+ * fragments of the same length exactly before the sync samples. Audio tracks are cut
+ * into pieces of similar length.
+ */
+public class SyncSampleIntersectFinderImpl implements FragmentIntersectionFinder {
+
+ private static Logger LOG = Logger.getLogger(SyncSampleIntersectFinderImpl.class.getName());
+ private static Map<CacheTuple, long[]> getTimesCache = new ConcurrentHashMap<CacheTuple, long[]>();
+ private static Map<CacheTuple, long[]> getSampleNumbersCache = new ConcurrentHashMap<CacheTuple, long[]>();
+
+ private final int minFragmentDurationSeconds;
+
+ public SyncSampleIntersectFinderImpl() {
+ minFragmentDurationSeconds = 0;
+ }
+
+ /**
+ * Creates a <code>SyncSampleIntersectFinderImpl</code> that will not create any fragment
+ * smaller than the given <code>minFragmentDurationSeconds</code>
+ *
+ * @param minFragmentDurationSeconds the smallest allowable duration of a fragment.
+ */
+ public SyncSampleIntersectFinderImpl(int minFragmentDurationSeconds) {
+ this.minFragmentDurationSeconds = minFragmentDurationSeconds;
+ }
+
+ /**
+ * Gets an array of sample numbers that are meant to be the first sample of each
+ * chunk or fragment.
+ *
+ * @param track concerned track
+ * @param movie the context of the track
+ * @return an array containing the ordinal of each fragment's first sample
+ */
+ public long[] sampleNumbers(Track track, Movie movie) {
+ final CacheTuple key = new CacheTuple(track, movie);
+ final long[] result = getSampleNumbersCache.get(key);
+ if (result != null) {
+ return result;
+ }
+
+ if ("vide".equals(track.getHandler())) {
+ if (track.getSyncSamples() != null && track.getSyncSamples().length > 0) {
+ List<long[]> times = getSyncSamplesTimestamps(movie, track);
+ final long[] commonIndices = getCommonIndices(track.getSyncSamples(), getTimes(track, movie), track.getTrackMetaData().getTimescale(), times.toArray(new long[times.size()][]));
+ getSampleNumbersCache.put(key, commonIndices);
+ return commonIndices;
+ } else {
+ throw new RuntimeException("Video Tracks need sync samples. Only tracks other than video may have no sync samples.");
+ }
+ } else if ("soun".equals(track.getHandler())) {
+ Track referenceTrack = null;
+ for (Track candidate : movie.getTracks()) {
+ if (candidate.getSyncSamples() != null && "vide".equals(candidate.getHandler()) && candidate.getSyncSamples().length > 0) {
+ referenceTrack = candidate;
+ }
+ }
+ if (referenceTrack != null) {
+
+ // Gets the reference track's fra
+ long[] refSyncSamples = sampleNumbers(referenceTrack, movie);
+
+ int refSampleCount = referenceTrack.getSamples().size();
+
+ long[] syncSamples = new long[refSyncSamples.length];
+ long minSampleRate = 192000;
+ for (Track testTrack : movie.getTracks()) {
+ if ("soun".equals(testTrack.getHandler())) {
+ AudioSampleEntry ase = (AudioSampleEntry) testTrack.getSampleDescriptionBox().getSampleEntry();
+ if (ase.getSampleRate() < minSampleRate) {
+ minSampleRate = ase.getSampleRate();
+ long sc = testTrack.getSamples().size();
+ double stretch = (double) sc / refSampleCount;
+ TimeToSampleBox.Entry sttsEntry = testTrack.getDecodingTimeEntries().get(0);
+ long samplesPerFrame = sttsEntry.getDelta(); // Assuming all audio tracks have the same number of samples per frame, which they do for all known types
+
+ for (int i = 0; i < syncSamples.length; i++) {
+ long start = (long) Math.ceil(stretch * (refSyncSamples[i] - 1) * samplesPerFrame);
+ syncSamples[i] = start;
+ // The Stretch makes sure that there are as much audio and video chunks!
+ }
+ break;
+ }
+ }
+ }
+ AudioSampleEntry ase = (AudioSampleEntry) track.getSampleDescriptionBox().getSampleEntry();
+ TimeToSampleBox.Entry sttsEntry = track.getDecodingTimeEntries().get(0);
+ long samplesPerFrame = sttsEntry.getDelta(); // Assuming all audio tracks have the same number of samples per frame, which they do for all known types
+ double factor = (double) ase.getSampleRate() / (double) minSampleRate;
+ if (factor != Math.rint(factor)) { // Not an integer
+ throw new RuntimeException("Sample rates must be a multiple of the lowest sample rate to create a correct file!");
+ }
+ for (int i = 0; i < syncSamples.length; i++) {
+ syncSamples[i] = (long) (1 + syncSamples[i] * factor / (double) samplesPerFrame);
+ }
+ getSampleNumbersCache.put(key, syncSamples);
+ return syncSamples;
+ }
+ throw new RuntimeException("There was absolutely no Track with sync samples. I can't work with that!");
+ } else {
+ // Ok, my track has no sync samples - let's find one with sync samples.
+ for (Track candidate : movie.getTracks()) {
+ if (candidate.getSyncSamples() != null && candidate.getSyncSamples().length > 0) {
+ long[] refSyncSamples = sampleNumbers(candidate, movie);
+ int refSampleCount = candidate.getSamples().size();
+
+ long[] syncSamples = new long[refSyncSamples.length];
+ long sc = track.getSamples().size();
+ double stretch = (double) sc / refSampleCount;
+
+ for (int i = 0; i < syncSamples.length; i++) {
+ long start = (long) Math.ceil(stretch * (refSyncSamples[i] - 1)) + 1;
+ syncSamples[i] = start;
+ // The Stretch makes sure that there are as much audio and video chunks!
+ }
+ getSampleNumbersCache.put(key, syncSamples);
+ return syncSamples;
+ }
+ }
+ throw new RuntimeException("There was absolutely no Track with sync samples. I can't work with that!");
+ }
+
+
+ }
+
+ /**
+ * Calculates the timestamp of all tracks' sync samples.
+ *
+ * @param movie
+ * @param track
+ * @return
+ */
+ public static List<long[]> getSyncSamplesTimestamps(Movie movie, Track track) {
+ List<long[]> times = new LinkedList<long[]>();
+ for (Track currentTrack : movie.getTracks()) {
+ if (currentTrack.getHandler().equals(track.getHandler())) {
+ long[] currentTrackSyncSamples = currentTrack.getSyncSamples();
+ if (currentTrackSyncSamples != null && currentTrackSyncSamples.length > 0) {
+ final long[] currentTrackTimes = getTimes(currentTrack, movie);
+ times.add(currentTrackTimes);
+ }
+ }
+ }
+ return times;
+ }
+
+ public long[] getCommonIndices(long[] syncSamples, long[] syncSampleTimes, long timeScale, long[]... otherTracksTimes) {
+ List<Long> nuSyncSamples = new LinkedList<Long>();
+ List<Long> nuSyncSampleTimes = new LinkedList<Long>();
+
+
+ for (int i = 0; i < syncSampleTimes.length; i++) {
+ boolean foundInEveryRef = true;
+ for (long[] times : otherTracksTimes) {
+ foundInEveryRef &= (Arrays.binarySearch(times, syncSampleTimes[i]) >= 0);
+ }
+
+ if (foundInEveryRef) {
+ // use sample only if found in every other track.
+ nuSyncSamples.add(syncSamples[i]);
+ nuSyncSampleTimes.add(syncSampleTimes[i]);
+ }
+ }
+ // We have two arrays now:
+ // nuSyncSamples: Contains all common sync samples
+ // nuSyncSampleTimes: Contains the times of all sync samples
+
+ // Start: Warn user if samples are not matching!
+ if (nuSyncSamples.size() < (syncSamples.length * 0.25)) {
+ String log = "";
+ log += String.format("%5d - Common: [", nuSyncSamples.size());
+ for (long l : nuSyncSamples) {
+ log += (String.format("%10d,", l));
+ }
+ log += ("]");
+ LOG.warning(log);
+ log = "";
+
+ log += String.format("%5d - In : [", syncSamples.length);
+ for (long l : syncSamples) {
+ log += (String.format("%10d,", l));
+ }
+ log += ("]");
+ LOG.warning(log);
+ LOG.warning("There are less than 25% of common sync samples in the given track.");
+ throw new RuntimeException("There are less than 25% of common sync samples in the given track.");
+ } else if (nuSyncSamples.size() < (syncSamples.length * 0.5)) {
+ LOG.fine("There are less than 50% of common sync samples in the given track. This is implausible but I'm ok to continue.");
+ } else if (nuSyncSamples.size() < syncSamples.length) {
+ LOG.finest("Common SyncSample positions vs. this tracks SyncSample positions: " + nuSyncSamples.size() + " vs. " + syncSamples.length);
+ }
+ // End: Warn user if samples are not matching!
+
+
+
+
+ List<Long> finalSampleList = new LinkedList<Long>();
+
+ if (minFragmentDurationSeconds > 0) {
+ // if minFragmentDurationSeconds is greater 0
+ // we need to throw away certain samples.
+ long lastSyncSampleTime = -1;
+ Iterator<Long> nuSyncSamplesIterator = nuSyncSamples.iterator();
+ Iterator<Long> nuSyncSampleTimesIterator = nuSyncSampleTimes.iterator();
+ while (nuSyncSamplesIterator.hasNext() && nuSyncSampleTimesIterator.hasNext()) {
+ long curSyncSample = nuSyncSamplesIterator.next();
+ long curSyncSampleTime = nuSyncSampleTimesIterator.next();
+ if (lastSyncSampleTime == -1 || (curSyncSampleTime - lastSyncSampleTime) / timeScale >= minFragmentDurationSeconds) {
+ finalSampleList.add(curSyncSample);
+ lastSyncSampleTime = curSyncSampleTime;
+ }
+ }
+ } else {
+ // the list of all samples is the final list of samples
+ // since minFragmentDurationSeconds ist not used.
+ finalSampleList = nuSyncSamples;
+ }
+
+
+ // transform the list to an array
+ long[] finalSampleArray = new long[finalSampleList.size()];
+ for (int i = 0; i < finalSampleArray.length; i++) {
+ finalSampleArray[i] = finalSampleList.get(i);
+ }
+ return finalSampleArray;
+
+ }
+
+
+ private static long[] getTimes(Track track, Movie m) {
+ final CacheTuple key = new CacheTuple(track, m);
+ final long[] result = getTimesCache.get(key);
+ if (result != null) {
+ return result;
+ }
+
+ long[] syncSamples = track.getSyncSamples();
+ long[] syncSampleTimes = new long[syncSamples.length];
+ Queue<TimeToSampleBox.Entry> timeQueue = new LinkedList<TimeToSampleBox.Entry>(track.getDecodingTimeEntries());
+
+ int currentSample = 1; // first syncsample is 1
+ long currentDuration = 0;
+ long currentDelta = 0;
+ int currentSyncSampleIndex = 0;
+ long left = 0;
+
+ final long scalingFactor = calculateTracktimesScalingFactor(m, track);
+
+ while (currentSample <= syncSamples[syncSamples.length - 1]) {
+ if (currentSample++ == syncSamples[currentSyncSampleIndex]) {
+ syncSampleTimes[currentSyncSampleIndex++] = currentDuration * scalingFactor;
+ }
+ if (left-- == 0) {
+ TimeToSampleBox.Entry entry = timeQueue.poll();
+ left = entry.getCount() - 1;
+ currentDelta = entry.getDelta();
+ }
+ currentDuration += currentDelta;
+ }
+ getTimesCache.put(key, syncSampleTimes);
+ return syncSampleTimes;
+ }
+
+ private static long calculateTracktimesScalingFactor(Movie m, Track track) {
+ long timeScale = 1;
+ for (Track track1 : m.getTracks()) {
+ if (track1.getHandler().equals(track.getHandler())) {
+ if (track1.getTrackMetaData().getTimescale() != track.getTrackMetaData().getTimescale()) {
+ timeScale = lcm(timeScale, track1.getTrackMetaData().getTimescale());
+ }
+ }
+ }
+ return timeScale;
+ }
+
+ public static class CacheTuple {
+ Track track;
+ Movie movie;
+
+ public CacheTuple(Track track, Movie movie) {
+ this.track = track;
+ this.movie = movie;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ CacheTuple that = (CacheTuple) o;
+
+ if (movie != null ? !movie.equals(that.movie) : that.movie != null) return false;
+ if (track != null ? !track.equals(that.track) : that.track != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = track != null ? track.hashCode() : 0;
+ result = 31 * result + (movie != null ? movie.hashCode() : 0);
+ return result;
+ }
+ }
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/TwoSecondIntersectionFinder.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/TwoSecondIntersectionFinder.java
new file mode 100644
index 0000000..88aa4ab
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/TwoSecondIntersectionFinder.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.builder;
+
+import com.coremedia.iso.boxes.TimeToSampleBox;
+import com.googlecode.mp4parser.authoring.Movie;
+import com.googlecode.mp4parser.authoring.Track;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This <code>FragmentIntersectionFinder</code> cuts the input movie in 2 second
+ * snippets.
+ */
+public class TwoSecondIntersectionFinder implements FragmentIntersectionFinder {
+
+ protected long getDuration(Track track) {
+ long duration = 0;
+ for (TimeToSampleBox.Entry entry : track.getDecodingTimeEntries()) {
+ duration += entry.getCount() * entry.getDelta();
+ }
+ return duration;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public long[] sampleNumbers(Track track, Movie movie) {
+ List<TimeToSampleBox.Entry> entries = track.getDecodingTimeEntries();
+
+ double trackLength = 0;
+ for (Track thisTrack : movie.getTracks()) {
+ double thisTracksLength = getDuration(thisTrack) / thisTrack.getTrackMetaData().getTimescale();
+ if (trackLength < thisTracksLength) {
+ trackLength = thisTracksLength;
+ }
+ }
+
+ int fragmentCount = (int)Math.ceil(trackLength / 2) - 1;
+ if (fragmentCount < 1) {
+ fragmentCount = 1;
+ }
+
+ long fragments[] = new long[fragmentCount];
+ Arrays.fill(fragments, -1);
+ fragments[0] = 1;
+
+ long time = 0;
+ int samples = 0;
+ for (TimeToSampleBox.Entry entry : entries) {
+ for (int i = 0; i < entry.getCount(); i++) {
+ int currentFragment = (int) (time / track.getTrackMetaData().getTimescale() / 2) + 1;
+ if (currentFragment >= fragments.length) {
+ break;
+ }
+ fragments[currentFragment] = samples++ + 1;
+ time += entry.getDelta();
+ }
+ }
+ long last = samples + 1;
+ // fill all -1 ones.
+ for (int i = fragments.length - 1; i >= 0; i--) {
+ if (fragments[i] == -1) {
+ fragments[i] = last ;
+ }
+ last = fragments[i];
+ }
+ return fragments;
+
+ }
+
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/container/.svn/all-wcprops b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/container/.svn/all-wcprops
new file mode 100644
index 0000000..a7245be
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/container/.svn/all-wcprops
@@ -0,0 +1,5 @@
+K 25
+svn:wc:ra_dav:version-url
+V 92
+/svn/!svn/ver/418/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/container
+END
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/container/.svn/entries b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/container/.svn/entries
new file mode 100644
index 0000000..bde4d0e
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/container/.svn/entries
@@ -0,0 +1,31 @@
+10
+
+dir
+778
+http://mp4parser.googlecode.com/svn/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/container
+http://mp4parser.googlecode.com/svn
+
+
+
+2012-03-11T20:54:45.638478Z
+418
+Sebastian.Annies@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+7decde4b-c250-0410-a0da-51896bc88be6
+
+mp4
+dir
+
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/container/mp4/.svn/all-wcprops b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/container/mp4/.svn/all-wcprops
new file mode 100644
index 0000000..ec2a4f9
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/container/mp4/.svn/all-wcprops
@@ -0,0 +1,11 @@
+K 25
+svn:wc:ra_dav:version-url
+V 96
+/svn/!svn/ver/418/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/container/mp4
+END
+MovieCreator.java
+K 25
+svn:wc:ra_dav:version-url
+V 114
+/svn/!svn/ver/418/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/container/mp4/MovieCreator.java
+END
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/container/mp4/.svn/entries b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/container/mp4/.svn/entries
new file mode 100644
index 0000000..d7d7f30
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/container/mp4/.svn/entries
@@ -0,0 +1,62 @@
+10
+
+dir
+778
+http://mp4parser.googlecode.com/svn/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/container/mp4
+http://mp4parser.googlecode.com/svn
+
+
+
+2012-03-11T20:54:45.638478Z
+418
+Sebastian.Annies@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+7decde4b-c250-0410-a0da-51896bc88be6
+
+MovieCreator.java
+file
+
+
+
+
+2012-09-14T17:27:49.987212Z
+ecb22de8d79473683de67e40f494641d
+2012-03-11T20:54:45.638478Z
+418
+Sebastian.Annies@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1404
+
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/container/mp4/.svn/text-base/MovieCreator.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/container/mp4/.svn/text-base/MovieCreator.java.svn-base
new file mode 100644
index 0000000..ed9d15f
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/container/mp4/.svn/text-base/MovieCreator.java.svn-base
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.container.mp4;
+
+import com.coremedia.iso.IsoFile;
+import com.coremedia.iso.boxes.TrackBox;
+import com.googlecode.mp4parser.authoring.Movie;
+import com.googlecode.mp4parser.authoring.Mp4TrackImpl;
+
+import java.io.IOException;
+import java.nio.channels.ReadableByteChannel;
+import java.util.List;
+
+/**
+ * Shortcut to build a movie from an MP4 file.
+ */
+public class MovieCreator {
+ public static Movie build(ReadableByteChannel channel) throws IOException {
+ IsoFile isoFile = new IsoFile(channel);
+ Movie m = new Movie();
+ List<TrackBox> trackBoxes = isoFile.getMovieBox().getBoxes(TrackBox.class);
+ for (TrackBox trackBox : trackBoxes) {
+ m.addTrack(new Mp4TrackImpl(trackBox));
+ }
+ return m;
+ }
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/container/mp4/MovieCreator.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/container/mp4/MovieCreator.java
new file mode 100644
index 0000000..ed9d15f
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/container/mp4/MovieCreator.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.container.mp4;
+
+import com.coremedia.iso.IsoFile;
+import com.coremedia.iso.boxes.TrackBox;
+import com.googlecode.mp4parser.authoring.Movie;
+import com.googlecode.mp4parser.authoring.Mp4TrackImpl;
+
+import java.io.IOException;
+import java.nio.channels.ReadableByteChannel;
+import java.util.List;
+
+/**
+ * Shortcut to build a movie from an MP4 file.
+ */
+public class MovieCreator {
+ public static Movie build(ReadableByteChannel channel) throws IOException {
+ IsoFile isoFile = new IsoFile(channel);
+ Movie m = new Movie();
+ List<TrackBox> trackBoxes = isoFile.getMovieBox().getBoxes(TrackBox.class);
+ for (TrackBox trackBox : trackBoxes) {
+ m.addTrack(new Mp4TrackImpl(trackBox));
+ }
+ return m;
+ }
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/all-wcprops b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/all-wcprops
new file mode 100644
index 0000000..496d7bb
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/all-wcprops
@@ -0,0 +1,89 @@
+K 25
+svn:wc:ra_dav:version-url
+V 89
+/svn/!svn/ver/756/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks
+END
+DivideTimeScaleTrack.java
+K 25
+svn:wc:ra_dav:version-url
+V 115
+/svn/!svn/ver/686/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/DivideTimeScaleTrack.java
+END
+CroppedTrack.java
+K 25
+svn:wc:ra_dav:version-url
+V 107
+/svn/!svn/ver/686/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/CroppedTrack.java
+END
+ChangeTimeScaleTrack.java
+K 25
+svn:wc:ra_dav:version-url
+V 115
+/svn/!svn/ver/686/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/ChangeTimeScaleTrack.java
+END
+EC3TrackImpl.java
+K 25
+svn:wc:ra_dav:version-url
+V 107
+/svn/!svn/ver/756/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/EC3TrackImpl.java
+END
+ReplaceSampleTrack.java
+K 25
+svn:wc:ra_dav:version-url
+V 113
+/svn/!svn/ver/686/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/ReplaceSampleTrack.java
+END
+QuicktimeTextTrackImpl.java
+K 25
+svn:wc:ra_dav:version-url
+V 117
+/svn/!svn/ver/691/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/QuicktimeTextTrackImpl.java
+END
+Amf0Track.java
+K 25
+svn:wc:ra_dav:version-url
+V 104
+/svn/!svn/ver/686/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/Amf0Track.java
+END
+SilenceTrackImpl.java
+K 25
+svn:wc:ra_dav:version-url
+V 111
+/svn/!svn/ver/698/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/SilenceTrackImpl.java
+END
+H264TrackImpl.java
+K 25
+svn:wc:ra_dav:version-url
+V 108
+/svn/!svn/ver/756/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/H264TrackImpl.java
+END
+TextTrackImpl.java
+K 25
+svn:wc:ra_dav:version-url
+V 108
+/svn/!svn/ver/684/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/TextTrackImpl.java
+END
+AACTrackImpl.java
+K 25
+svn:wc:ra_dav:version-url
+V 107
+/svn/!svn/ver/756/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/AACTrackImpl.java
+END
+MultiplyTimeScaleTrack.java
+K 25
+svn:wc:ra_dav:version-url
+V 117
+/svn/!svn/ver/686/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/MultiplyTimeScaleTrack.java
+END
+AppendTrack.java
+K 25
+svn:wc:ra_dav:version-url
+V 106
+/svn/!svn/ver/714/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/AppendTrack.java
+END
+AC3TrackImpl.java
+K 25
+svn:wc:ra_dav:version-url
+V 107
+/svn/!svn/ver/756/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/AC3TrackImpl.java
+END
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/entries b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/entries
new file mode 100644
index 0000000..dbe8ae3
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/entries
@@ -0,0 +1,504 @@
+10
+
+dir
+778
+http://mp4parser.googlecode.com/svn/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks
+http://mp4parser.googlecode.com/svn
+
+
+
+2012-08-17T01:19:11.953078Z
+756
+michael.stattmann@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+7decde4b-c250-0410-a0da-51896bc88be6
+
+DivideTimeScaleTrack.java
+file
+
+
+
+
+2012-09-14T17:27:50.507219Z
+6aa41cdb7489e16e879ddebd68b7ac52
+2012-06-24T19:52:05.961412Z
+686
+Sebastian.Annies@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+3893
+
+CroppedTrack.java
+file
+
+
+
+
+2012-09-14T17:27:50.507219Z
+5d9c65d9aac52a26372aa3fdf6f1b7ea
+2012-06-24T19:52:05.961412Z
+686
+Sebastian.Annies@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+6025
+
+ChangeTimeScaleTrack.java
+file
+
+
+
+
+2012-09-14T17:27:50.507219Z
+ba01d30ac4b7c4fa9a2c6538ee537594
+2012-06-24T19:52:05.961412Z
+686
+Sebastian.Annies@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+7152
+
+EC3TrackImpl.java
+file
+
+
+
+
+2012-09-14T17:27:50.507219Z
+1d568bfaa0f41c3771986a7790347072
+2012-08-17T01:19:11.953078Z
+756
+michael.stattmann@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+13623
+
+ReplaceSampleTrack.java
+file
+
+
+
+
+2012-09-14T17:27:50.507219Z
+f6846b7a262ab0e530da46f4d7e34850
+2012-06-24T19:52:05.961412Z
+686
+Sebastian.Annies@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+3139
+
+QuicktimeTextTrackImpl.java
+file
+
+
+
+
+2012-09-14T17:27:50.507219Z
+7ccd01a58545fb02b507b57892eb53e5
+2012-06-24T21:35:59.546504Z
+691
+Sebastian.Annies@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+5128
+
+Amf0Track.java
+file
+
+
+
+
+2012-09-14T17:27:50.507219Z
+4718a34bc271adf4517de9829ac74a9d
+2012-06-24T19:52:05.961412Z
+686
+Sebastian.Annies@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+3858
+
+SilenceTrackImpl.java
+file
+
+
+
+
+2012-09-14T17:27:50.507219Z
+a897677e602dfa0d64af1b0d33a04ca8
+2012-06-26T08:37:32.910396Z
+698
+Sebastian.Annies@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+2623
+
+H264TrackImpl.java
+file
+
+
+
+
+2012-09-14T17:27:50.507219Z
+76abb4b21c13d11215a2ac4cb0c7e461
+2012-08-17T01:19:11.953078Z
+756
+michael.stattmann@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+28816
+
+TextTrackImpl.java
+file
+
+
+
+
+2012-09-14T17:27:50.507219Z
+5a643c876754eb5c7bcf75b4b71114a1
+2012-06-24T14:45:45.932648Z
+684
+Sebastian.Annies@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+4963
+
+AACTrackImpl.java
+file
+
+
+
+
+2012-09-14T17:27:50.507219Z
+ece8d364a9a5aeabf6a281f6d428e3cf
+2012-08-17T01:19:11.953078Z
+756
+michael.stattmann@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+10097
+
+MultiplyTimeScaleTrack.java
+file
+
+
+
+
+2012-09-14T17:27:50.507219Z
+b0ea53239c124607a26a181f594f82a1
+2012-06-24T19:52:05.961412Z
+686
+Sebastian.Annies@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+4192
+
+AppendTrack.java
+file
+
+
+
+
+2012-09-14T17:27:50.507219Z
+e7814aebc4500724771fd0582455a7ca
+2012-07-18T23:22:45.506793Z
+714
+Sebastian.Annies@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+15783
+
+AC3TrackImpl.java
+file
+
+
+
+
+2012-09-14T17:27:50.507219Z
+fbd724739cd9a5f2b28f16b412b27309
+2012-08-17T01:19:11.953078Z
+756
+michael.stattmann@gmail.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+19303
+
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/AACTrackImpl.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/AACTrackImpl.java.svn-base
new file mode 100644
index 0000000..df51a1a
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/AACTrackImpl.java.svn-base
@@ -0,0 +1,292 @@
+/*
+ * Copyright 2012 castLabs GmbH, Berlin
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.tracks;
+
+import com.coremedia.iso.boxes.*;
+import com.coremedia.iso.boxes.sampleentry.AudioSampleEntry;
+import com.googlecode.mp4parser.authoring.AbstractTrack;
+import com.googlecode.mp4parser.authoring.TrackMetaData;
+import com.googlecode.mp4parser.boxes.AC3SpecificBox;
+import com.googlecode.mp4parser.boxes.mp4.ESDescriptorBox;
+import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.*;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.*;
+
+/**
+ */
+public class AACTrackImpl extends AbstractTrack {
+ public static Map<Integer, Integer> samplingFrequencyIndexMap = new HashMap<Integer, Integer>();
+
+ static {
+ samplingFrequencyIndexMap.put(96000, 0);
+ samplingFrequencyIndexMap.put(88200, 1);
+ samplingFrequencyIndexMap.put(64000, 2);
+ samplingFrequencyIndexMap.put(48000, 3);
+ samplingFrequencyIndexMap.put(44100, 4);
+ samplingFrequencyIndexMap.put(32000, 5);
+ samplingFrequencyIndexMap.put(24000, 6);
+ samplingFrequencyIndexMap.put(22050, 7);
+ samplingFrequencyIndexMap.put(16000, 8);
+ samplingFrequencyIndexMap.put(12000, 9);
+ samplingFrequencyIndexMap.put(11025, 10);
+ samplingFrequencyIndexMap.put(8000, 11);
+ samplingFrequencyIndexMap.put(0x0, 96000);
+ samplingFrequencyIndexMap.put(0x1, 88200);
+ samplingFrequencyIndexMap.put(0x2, 64000);
+ samplingFrequencyIndexMap.put(0x3, 48000);
+ samplingFrequencyIndexMap.put(0x4, 44100);
+ samplingFrequencyIndexMap.put(0x5, 32000);
+ samplingFrequencyIndexMap.put(0x6, 24000);
+ samplingFrequencyIndexMap.put(0x7, 22050);
+ samplingFrequencyIndexMap.put(0x8, 16000);
+ samplingFrequencyIndexMap.put(0x9, 12000);
+ samplingFrequencyIndexMap.put(0xa, 11025);
+ samplingFrequencyIndexMap.put(0xb, 8000);
+ }
+
+ TrackMetaData trackMetaData = new TrackMetaData();
+ SampleDescriptionBox sampleDescriptionBox;
+
+ int samplerate;
+ int bitrate;
+ int channelCount;
+ int channelconfig;
+
+ int bufferSizeDB;
+ long maxBitRate;
+ long avgBitRate;
+
+ private BufferedInputStream inputStream;
+ private List<ByteBuffer> samples;
+ boolean readSamples = false;
+ List<TimeToSampleBox.Entry> stts;
+ private String lang = "und";
+
+
+ public AACTrackImpl(InputStream inputStream, String lang) throws IOException {
+ this.lang = lang;
+ parse(inputStream);
+ }
+
+ public AACTrackImpl(InputStream inputStream) throws IOException {
+ parse(inputStream);
+ }
+
+ private void parse(InputStream inputStream) throws IOException {
+ this.inputStream = new BufferedInputStream(inputStream);
+ stts = new LinkedList<TimeToSampleBox.Entry>();
+
+ if (!readVariables()) {
+ throw new IOException();
+ }
+
+ samples = new LinkedList<ByteBuffer>();
+ if (!readSamples()) {
+ throw new IOException();
+ }
+
+ double packetsPerSecond = (double)samplerate / 1024.0;
+ double duration = samples.size() / packetsPerSecond;
+
+ long dataSize = 0;
+ LinkedList<Integer> queue = new LinkedList<Integer>();
+ for (int i = 0; i < samples.size(); i++) {
+ int size = samples.get(i).capacity();
+ dataSize += size;
+ queue.add(size);
+ while (queue.size() > packetsPerSecond) {
+ queue.pop();
+ }
+ if (queue.size() == (int) packetsPerSecond) {
+ int currSize = 0;
+ for (int j = 0 ; j < queue.size(); j++) {
+ currSize += queue.get(j);
+ }
+ double currBitrate = 8.0 * currSize / queue.size() * packetsPerSecond;
+ if (currBitrate > maxBitRate) {
+ maxBitRate = (int)currBitrate;
+ }
+ }
+ }
+
+ avgBitRate = (int) (8 * dataSize / duration);
+
+ bufferSizeDB = 1536; /* TODO: Calcultate this somehow! */
+
+ sampleDescriptionBox = new SampleDescriptionBox();
+ AudioSampleEntry audioSampleEntry = new AudioSampleEntry("mp4a");
+ audioSampleEntry.setChannelCount(2);
+ audioSampleEntry.setSampleRate(samplerate);
+ audioSampleEntry.setDataReferenceIndex(1);
+ audioSampleEntry.setSampleSize(16);
+
+
+ ESDescriptorBox esds = new ESDescriptorBox();
+ ESDescriptor descriptor = new ESDescriptor();
+ descriptor.setEsId(0);
+
+ SLConfigDescriptor slConfigDescriptor = new SLConfigDescriptor();
+ slConfigDescriptor.setPredefined(2);
+ descriptor.setSlConfigDescriptor(slConfigDescriptor);
+
+ DecoderConfigDescriptor decoderConfigDescriptor = new DecoderConfigDescriptor();
+ decoderConfigDescriptor.setObjectTypeIndication(0x40);
+ decoderConfigDescriptor.setStreamType(5);
+ decoderConfigDescriptor.setBufferSizeDB(bufferSizeDB);
+ decoderConfigDescriptor.setMaxBitRate(maxBitRate);
+ decoderConfigDescriptor.setAvgBitRate(avgBitRate);
+
+ AudioSpecificConfig audioSpecificConfig = new AudioSpecificConfig();
+ audioSpecificConfig.setAudioObjectType(2); // AAC LC
+ audioSpecificConfig.setSamplingFrequencyIndex(samplingFrequencyIndexMap.get(samplerate));
+ audioSpecificConfig.setChannelConfiguration(channelconfig);
+ decoderConfigDescriptor.setAudioSpecificInfo(audioSpecificConfig);
+
+ descriptor.setDecoderConfigDescriptor(decoderConfigDescriptor);
+
+ ByteBuffer data = descriptor.serialize();
+ esds.setData(data);
+ audioSampleEntry.addBox(esds);
+ sampleDescriptionBox.addBox(audioSampleEntry);
+
+ trackMetaData.setCreationTime(new Date());
+ trackMetaData.setModificationTime(new Date());
+ trackMetaData.setLanguage(lang);
+ trackMetaData.setTimescale(samplerate); // Audio tracks always use samplerate as timescale
+ }
+
+ public SampleDescriptionBox getSampleDescriptionBox() {
+ return sampleDescriptionBox;
+ }
+
+ public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
+ return stts;
+ }
+
+ public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
+ return null;
+ }
+
+ public long[] getSyncSamples() {
+ return null;
+ }
+
+ public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
+ return null;
+ }
+
+ public TrackMetaData getTrackMetaData() {
+ return trackMetaData;
+ }
+
+ public String getHandler() {
+ return "soun";
+ }
+
+ public List<ByteBuffer> getSamples() {
+ return samples;
+ }
+
+ public Box getMediaHeaderBox() {
+ return new SoundMediaHeaderBox();
+ }
+
+ public SubSampleInformationBox getSubsampleInformationBox() {
+ return null;
+ }
+
+ private boolean readVariables() throws IOException {
+ byte[] data = new byte[100];
+ inputStream.mark(100);
+ if (100 != inputStream.read(data, 0, 100)) {
+ return false;
+ }
+ inputStream.reset(); // Rewind
+ ByteBuffer bb = ByteBuffer.wrap(data);
+ BitReaderBuffer brb = new BitReaderBuffer(bb);
+ int syncword = brb.readBits(12);
+ if (syncword != 0xfff) {
+ return false;
+ }
+ int id = brb.readBits(1);
+ int layer = brb.readBits(2);
+ int protectionAbsent = brb.readBits(1);
+ int profile = brb.readBits(2);
+ samplerate = samplingFrequencyIndexMap.get(brb.readBits(4));
+ brb.readBits(1);
+ channelconfig = brb.readBits(3);
+ int original = brb.readBits(1);
+ int home = brb.readBits(1);
+ int emphasis = brb.readBits(2);
+
+ return true;
+ }
+
+ private boolean readSamples() throws IOException {
+ if (readSamples) {
+ return true;
+ }
+
+ readSamples = true;
+ byte[] header = new byte[15];
+ boolean ret = false;
+ inputStream.mark(15);
+ while (-1 != inputStream.read(header)) {
+ ret = true;
+ ByteBuffer bb = ByteBuffer.wrap(header);
+ inputStream.reset();
+ BitReaderBuffer brb = new BitReaderBuffer(bb);
+ int syncword = brb.readBits(12);
+ if (syncword != 0xfff) {
+ return false;
+ }
+ brb.readBits(3);
+ int protectionAbsent = brb.readBits(1);
+ brb.readBits(14);
+ int frameSize = brb.readBits(13);
+ int bufferFullness = brb.readBits(11);
+ int noBlocks = brb.readBits(2);
+ int used = (int) Math.ceil(brb.getPosition() / 8.0);
+ if (protectionAbsent == 0) {
+ used += 2;
+ }
+ inputStream.skip(used);
+ frameSize -= used;
+// System.out.println("Size: " + frameSize + " fullness: " + bufferFullness + " no blocks: " + noBlocks);
+ byte[] data = new byte[frameSize];
+ inputStream.read(data);
+ samples.add(ByteBuffer.wrap(data));
+ stts.add(new TimeToSampleBox.Entry(1, 1024));
+ inputStream.mark(15);
+ }
+ return ret;
+ }
+
+ @Override
+ public String toString() {
+ return "AACTrackImpl{" +
+ "samplerate=" + samplerate +
+ ", bitrate=" + bitrate +
+ ", channelCount=" + channelCount +
+ ", channelconfig=" + channelconfig +
+ '}';
+ }
+}
+
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/AC3TrackImpl.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/AC3TrackImpl.java.svn-base
new file mode 100644
index 0000000..5e5b2cd
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/AC3TrackImpl.java.svn-base
@@ -0,0 +1,513 @@
+package com.googlecode.mp4parser.authoring.tracks;
+
+import com.coremedia.iso.boxes.*;
+import com.coremedia.iso.boxes.sampleentry.AudioSampleEntry;
+import com.googlecode.mp4parser.authoring.AbstractTrack;
+import com.googlecode.mp4parser.authoring.TrackMetaData;
+import com.googlecode.mp4parser.boxes.AC3SpecificBox;
+import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.BitReaderBuffer;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+public class AC3TrackImpl extends AbstractTrack {
+ TrackMetaData trackMetaData = new TrackMetaData();
+ SampleDescriptionBox sampleDescriptionBox;
+
+ int samplerate;
+ int bitrate;
+ int channelCount;
+
+ int fscod;
+ int bsid;
+ int bsmod;
+ int acmod;
+ int lfeon;
+ int frmsizecod;
+
+ int frameSize;
+ int[][][][] bitRateAndFrameSizeTable;
+
+ private InputStream inputStream;
+ private List<ByteBuffer> samples;
+ boolean readSamples = false;
+ List<TimeToSampleBox.Entry> stts;
+ private String lang = "und";
+
+ public AC3TrackImpl(InputStream fin, String lang) throws IOException {
+ this.lang = lang;
+ parse(fin);
+ }
+
+ public AC3TrackImpl(InputStream fin) throws IOException {
+ parse(fin);
+ }
+
+ private void parse(InputStream fin) throws IOException {
+ inputStream = fin;
+ bitRateAndFrameSizeTable = new int[19][2][3][2];
+ stts = new LinkedList<TimeToSampleBox.Entry>();
+ initBitRateAndFrameSizeTable();
+ if (!readVariables()) {
+ throw new IOException();
+ }
+
+ sampleDescriptionBox = new SampleDescriptionBox();
+ AudioSampleEntry audioSampleEntry = new AudioSampleEntry("ac-3");
+ audioSampleEntry.setChannelCount(2); // According to ETSI TS 102 366 Annex F
+ audioSampleEntry.setSampleRate(samplerate);
+ audioSampleEntry.setDataReferenceIndex(1);
+ audioSampleEntry.setSampleSize(16);
+
+ AC3SpecificBox ac3 = new AC3SpecificBox();
+ ac3.setAcmod(acmod);
+ ac3.setBitRateCode(frmsizecod >> 1);
+ ac3.setBsid(bsid);
+ ac3.setBsmod(bsmod);
+ ac3.setFscod(fscod);
+ ac3.setLfeon(lfeon);
+ ac3.setReserved(0);
+
+ audioSampleEntry.addBox(ac3);
+ sampleDescriptionBox.addBox(audioSampleEntry);
+
+ trackMetaData.setCreationTime(new Date());
+ trackMetaData.setModificationTime(new Date());
+ trackMetaData.setLanguage(lang);
+ trackMetaData.setTimescale(samplerate); // Audio tracks always use samplerate as timescale
+
+ samples = new LinkedList<ByteBuffer>();
+ if (!readSamples()) {
+ throw new IOException();
+ }
+ }
+
+
+ public List<ByteBuffer> getSamples() {
+
+ return samples;
+ }
+
+ public SampleDescriptionBox getSampleDescriptionBox() {
+ return sampleDescriptionBox;
+ }
+
+ public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
+ return stts;
+ }
+
+ public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
+ return null;
+ }
+
+ public long[] getSyncSamples() {
+ return null;
+ }
+
+ public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
+ return null;
+ }
+
+ public TrackMetaData getTrackMetaData() {
+ return trackMetaData;
+ }
+
+ public String getHandler() {
+ return "soun";
+ }
+
+ public Box getMediaHeaderBox() {
+ return new SoundMediaHeaderBox();
+ }
+
+ public SubSampleInformationBox getSubsampleInformationBox() {
+ return null;
+ }
+
+ private boolean readVariables() throws IOException {
+ byte[] data = new byte[100];
+ inputStream.mark(100);
+ if (100 != inputStream.read(data, 0, 100)) {
+ return false;
+ }
+ inputStream.reset(); // Rewind
+ ByteBuffer bb = ByteBuffer.wrap(data);
+ BitReaderBuffer brb = new BitReaderBuffer(bb);
+ int syncword = brb.readBits(16);
+ if (syncword != 0xb77) {
+ return false;
+ }
+ brb.readBits(16); // CRC-1
+ fscod = brb.readBits(2);
+
+ switch (fscod) {
+ case 0:
+ samplerate = 48000;
+ break;
+
+ case 1:
+ samplerate = 44100;
+ break;
+
+ case 2:
+ samplerate = 32000;
+ break;
+
+ case 3:
+ samplerate = 0;
+ break;
+
+ }
+ if (samplerate == 0) {
+ return false;
+ }
+
+ frmsizecod = brb.readBits(6);
+
+ if (!calcBitrateAndFrameSize(frmsizecod)) {
+ return false;
+ }
+
+ if (frameSize == 0) {
+ return false;
+ }
+ bsid = brb.readBits(5);
+ bsmod = brb.readBits(3);
+ acmod = brb.readBits(3);
+
+ if (bsid == 9) {
+ samplerate /= 2;
+ } else if (bsid != 8 && bsid != 6) {
+ return false;
+ }
+
+ if ((acmod != 1) && ((acmod & 1) == 1)) {
+ brb.readBits(2);
+ }
+
+ if (0 != (acmod & 4)) {
+ brb.readBits(2);
+ }
+
+ if (acmod == 2) {
+ brb.readBits(2);
+ }
+
+ switch (acmod) {
+ case 0:
+ channelCount = 2;
+ break;
+
+ case 1:
+ channelCount = 1;
+ break;
+
+ case 2:
+ channelCount = 2;
+ break;
+
+ case 3:
+ channelCount = 3;
+ break;
+
+ case 4:
+ channelCount = 3;
+ break;
+
+ case 5:
+ channelCount = 4;
+ break;
+
+ case 6:
+ channelCount = 4;
+ break;
+
+ case 7:
+ channelCount = 5;
+ break;
+
+ }
+
+ lfeon = brb.readBits(1);
+
+ if (lfeon == 1) {
+ channelCount++;
+ }
+ return true;
+ }
+
+ private boolean calcBitrateAndFrameSize(int code) {
+ int frmsizecode = code >>> 1;
+ int flag = code & 1;
+ if (frmsizecode > 18 || flag > 1 || fscod > 2) {
+ return false;
+ }
+ bitrate = bitRateAndFrameSizeTable[frmsizecode][flag][fscod][0];
+ frameSize = 2 * bitRateAndFrameSizeTable[frmsizecode][flag][fscod][1];
+ return true;
+ }
+
+ private boolean readSamples() throws IOException {
+ if (readSamples) {
+ return true;
+ }
+ readSamples = true;
+ byte[] header = new byte[5];
+ boolean ret = false;
+ inputStream.mark(5);
+ while (-1 != inputStream.read(header)) {
+ ret = true;
+ int frmsizecode = header[4] & 63;
+ calcBitrateAndFrameSize(frmsizecode);
+ inputStream.reset();
+ byte[] data = new byte[frameSize];
+ inputStream.read(data);
+ samples.add(ByteBuffer.wrap(data));
+ stts.add(new TimeToSampleBox.Entry(1, 1536));
+ inputStream.mark(5);
+ }
+ return ret;
+ }
+
+ private void initBitRateAndFrameSizeTable() {
+ // ETSI 102 366 Table 4.13, in frmsizecod, flag, fscod, bitrate/size order. Note that all sizes are in words, and all bitrates in kbps
+
+ // 48kHz
+ bitRateAndFrameSizeTable[0][0][0][0] = 32;
+ bitRateAndFrameSizeTable[0][1][0][0] = 32;
+ bitRateAndFrameSizeTable[0][0][0][1] = 64;
+ bitRateAndFrameSizeTable[0][1][0][1] = 64;
+ bitRateAndFrameSizeTable[1][0][0][0] = 40;
+ bitRateAndFrameSizeTable[1][1][0][0] = 40;
+ bitRateAndFrameSizeTable[1][0][0][1] = 80;
+ bitRateAndFrameSizeTable[1][1][0][1] = 80;
+ bitRateAndFrameSizeTable[2][0][0][0] = 48;
+ bitRateAndFrameSizeTable[2][1][0][0] = 48;
+ bitRateAndFrameSizeTable[2][0][0][1] = 96;
+ bitRateAndFrameSizeTable[2][1][0][1] = 96;
+ bitRateAndFrameSizeTable[3][0][0][0] = 56;
+ bitRateAndFrameSizeTable[3][1][0][0] = 56;
+ bitRateAndFrameSizeTable[3][0][0][1] = 112;
+ bitRateAndFrameSizeTable[3][1][0][1] = 112;
+ bitRateAndFrameSizeTable[4][0][0][0] = 64;
+ bitRateAndFrameSizeTable[4][1][0][0] = 64;
+ bitRateAndFrameSizeTable[4][0][0][1] = 128;
+ bitRateAndFrameSizeTable[4][1][0][1] = 128;
+ bitRateAndFrameSizeTable[5][0][0][0] = 80;
+ bitRateAndFrameSizeTable[5][1][0][0] = 80;
+ bitRateAndFrameSizeTable[5][0][0][1] = 160;
+ bitRateAndFrameSizeTable[5][1][0][1] = 160;
+ bitRateAndFrameSizeTable[6][0][0][0] = 96;
+ bitRateAndFrameSizeTable[6][1][0][0] = 96;
+ bitRateAndFrameSizeTable[6][0][0][1] = 192;
+ bitRateAndFrameSizeTable[6][1][0][1] = 192;
+ bitRateAndFrameSizeTable[7][0][0][0] = 112;
+ bitRateAndFrameSizeTable[7][1][0][0] = 112;
+ bitRateAndFrameSizeTable[7][0][0][1] = 224;
+ bitRateAndFrameSizeTable[7][1][0][1] = 224;
+ bitRateAndFrameSizeTable[8][0][0][0] = 128;
+ bitRateAndFrameSizeTable[8][1][0][0] = 128;
+ bitRateAndFrameSizeTable[8][0][0][1] = 256;
+ bitRateAndFrameSizeTable[8][1][0][1] = 256;
+ bitRateAndFrameSizeTable[9][0][0][0] = 160;
+ bitRateAndFrameSizeTable[9][1][0][0] = 160;
+ bitRateAndFrameSizeTable[9][0][0][1] = 320;
+ bitRateAndFrameSizeTable[9][1][0][1] = 320;
+ bitRateAndFrameSizeTable[10][0][0][0] = 192;
+ bitRateAndFrameSizeTable[10][1][0][0] = 192;
+ bitRateAndFrameSizeTable[10][0][0][1] = 384;
+ bitRateAndFrameSizeTable[10][1][0][1] = 384;
+ bitRateAndFrameSizeTable[11][0][0][0] = 224;
+ bitRateAndFrameSizeTable[11][1][0][0] = 224;
+ bitRateAndFrameSizeTable[11][0][0][1] = 448;
+ bitRateAndFrameSizeTable[11][1][0][1] = 448;
+ bitRateAndFrameSizeTable[12][0][0][0] = 256;
+ bitRateAndFrameSizeTable[12][1][0][0] = 256;
+ bitRateAndFrameSizeTable[12][0][0][1] = 512;
+ bitRateAndFrameSizeTable[12][1][0][1] = 512;
+ bitRateAndFrameSizeTable[13][0][0][0] = 320;
+ bitRateAndFrameSizeTable[13][1][0][0] = 320;
+ bitRateAndFrameSizeTable[13][0][0][1] = 640;
+ bitRateAndFrameSizeTable[13][1][0][1] = 640;
+ bitRateAndFrameSizeTable[14][0][0][0] = 384;
+ bitRateAndFrameSizeTable[14][1][0][0] = 384;
+ bitRateAndFrameSizeTable[14][0][0][1] = 768;
+ bitRateAndFrameSizeTable[14][1][0][1] = 768;
+ bitRateAndFrameSizeTable[15][0][0][0] = 448;
+ bitRateAndFrameSizeTable[15][1][0][0] = 448;
+ bitRateAndFrameSizeTable[15][0][0][1] = 896;
+ bitRateAndFrameSizeTable[15][1][0][1] = 896;
+ bitRateAndFrameSizeTable[16][0][0][0] = 512;
+ bitRateAndFrameSizeTable[16][1][0][0] = 512;
+ bitRateAndFrameSizeTable[16][0][0][1] = 1024;
+ bitRateAndFrameSizeTable[16][1][0][1] = 1024;
+ bitRateAndFrameSizeTable[17][0][0][0] = 576;
+ bitRateAndFrameSizeTable[17][1][0][0] = 576;
+ bitRateAndFrameSizeTable[17][0][0][1] = 1152;
+ bitRateAndFrameSizeTable[17][1][0][1] = 1152;
+ bitRateAndFrameSizeTable[18][0][0][0] = 640;
+ bitRateAndFrameSizeTable[18][1][0][0] = 640;
+ bitRateAndFrameSizeTable[18][0][0][1] = 1280;
+ bitRateAndFrameSizeTable[18][1][0][1] = 1280;
+
+ // 44.1 kHz
+ bitRateAndFrameSizeTable[0][0][1][0] = 32;
+ bitRateAndFrameSizeTable[0][1][1][0] = 32;
+ bitRateAndFrameSizeTable[0][0][1][1] = 69;
+ bitRateAndFrameSizeTable[0][1][1][1] = 70;
+ bitRateAndFrameSizeTable[1][0][1][0] = 40;
+ bitRateAndFrameSizeTable[1][1][1][0] = 40;
+ bitRateAndFrameSizeTable[1][0][1][1] = 87;
+ bitRateAndFrameSizeTable[1][1][1][1] = 88;
+ bitRateAndFrameSizeTable[2][0][1][0] = 48;
+ bitRateAndFrameSizeTable[2][1][1][0] = 48;
+ bitRateAndFrameSizeTable[2][0][1][1] = 104;
+ bitRateAndFrameSizeTable[2][1][1][1] = 105;
+ bitRateAndFrameSizeTable[3][0][1][0] = 56;
+ bitRateAndFrameSizeTable[3][1][1][0] = 56;
+ bitRateAndFrameSizeTable[3][0][1][1] = 121;
+ bitRateAndFrameSizeTable[3][1][1][1] = 122;
+ bitRateAndFrameSizeTable[4][0][1][0] = 64;
+ bitRateAndFrameSizeTable[4][1][1][0] = 64;
+ bitRateAndFrameSizeTable[4][0][1][1] = 139;
+ bitRateAndFrameSizeTable[4][1][1][1] = 140;
+ bitRateAndFrameSizeTable[5][0][1][0] = 80;
+ bitRateAndFrameSizeTable[5][1][1][0] = 80;
+ bitRateAndFrameSizeTable[5][0][1][1] = 174;
+ bitRateAndFrameSizeTable[5][1][1][1] = 175;
+ bitRateAndFrameSizeTable[6][0][1][0] = 96;
+ bitRateAndFrameSizeTable[6][1][1][0] = 96;
+ bitRateAndFrameSizeTable[6][0][1][1] = 208;
+ bitRateAndFrameSizeTable[6][1][1][1] = 209;
+ bitRateAndFrameSizeTable[7][0][1][0] = 112;
+ bitRateAndFrameSizeTable[7][1][1][0] = 112;
+ bitRateAndFrameSizeTable[7][0][1][1] = 243;
+ bitRateAndFrameSizeTable[7][1][1][1] = 244;
+ bitRateAndFrameSizeTable[8][0][1][0] = 128;
+ bitRateAndFrameSizeTable[8][1][1][0] = 128;
+ bitRateAndFrameSizeTable[8][0][1][1] = 278;
+ bitRateAndFrameSizeTable[8][1][1][1] = 279;
+ bitRateAndFrameSizeTable[9][0][1][0] = 160;
+ bitRateAndFrameSizeTable[9][1][1][0] = 160;
+ bitRateAndFrameSizeTable[9][0][1][1] = 348;
+ bitRateAndFrameSizeTable[9][1][1][1] = 349;
+ bitRateAndFrameSizeTable[10][0][1][0] = 192;
+ bitRateAndFrameSizeTable[10][1][1][0] = 192;
+ bitRateAndFrameSizeTable[10][0][1][1] = 417;
+ bitRateAndFrameSizeTable[10][1][1][1] = 418;
+ bitRateAndFrameSizeTable[11][0][1][0] = 224;
+ bitRateAndFrameSizeTable[11][1][1][0] = 224;
+ bitRateAndFrameSizeTable[11][0][1][1] = 487;
+ bitRateAndFrameSizeTable[11][1][1][1] = 488;
+ bitRateAndFrameSizeTable[12][0][1][0] = 256;
+ bitRateAndFrameSizeTable[12][1][1][0] = 256;
+ bitRateAndFrameSizeTable[12][0][1][1] = 557;
+ bitRateAndFrameSizeTable[12][1][1][1] = 558;
+ bitRateAndFrameSizeTable[13][0][1][0] = 320;
+ bitRateAndFrameSizeTable[13][1][1][0] = 320;
+ bitRateAndFrameSizeTable[13][0][1][1] = 696;
+ bitRateAndFrameSizeTable[13][1][1][1] = 697;
+ bitRateAndFrameSizeTable[14][0][1][0] = 384;
+ bitRateAndFrameSizeTable[14][1][1][0] = 384;
+ bitRateAndFrameSizeTable[14][0][1][1] = 835;
+ bitRateAndFrameSizeTable[14][1][1][1] = 836;
+ bitRateAndFrameSizeTable[15][0][1][0] = 448;
+ bitRateAndFrameSizeTable[15][1][1][0] = 448;
+ bitRateAndFrameSizeTable[15][0][1][1] = 975;
+ bitRateAndFrameSizeTable[15][1][1][1] = 975;
+ bitRateAndFrameSizeTable[16][0][1][0] = 512;
+ bitRateAndFrameSizeTable[16][1][1][0] = 512;
+ bitRateAndFrameSizeTable[16][0][1][1] = 1114;
+ bitRateAndFrameSizeTable[16][1][1][1] = 1115;
+ bitRateAndFrameSizeTable[17][0][1][0] = 576;
+ bitRateAndFrameSizeTable[17][1][1][0] = 576;
+ bitRateAndFrameSizeTable[17][0][1][1] = 1253;
+ bitRateAndFrameSizeTable[17][1][1][1] = 1254;
+ bitRateAndFrameSizeTable[18][0][1][0] = 640;
+ bitRateAndFrameSizeTable[18][1][1][0] = 640;
+ bitRateAndFrameSizeTable[18][0][1][1] = 1393;
+ bitRateAndFrameSizeTable[18][1][1][1] = 1394;
+
+ // 32kHz
+ bitRateAndFrameSizeTable[0][0][2][0] = 32;
+ bitRateAndFrameSizeTable[0][1][2][0] = 32;
+ bitRateAndFrameSizeTable[0][0][2][1] = 96;
+ bitRateAndFrameSizeTable[0][1][2][1] = 96;
+ bitRateAndFrameSizeTable[1][0][2][0] = 40;
+ bitRateAndFrameSizeTable[1][1][2][0] = 40;
+ bitRateAndFrameSizeTable[1][0][2][1] = 120;
+ bitRateAndFrameSizeTable[1][1][2][1] = 120;
+ bitRateAndFrameSizeTable[2][0][2][0] = 48;
+ bitRateAndFrameSizeTable[2][1][2][0] = 48;
+ bitRateAndFrameSizeTable[2][0][2][1] = 144;
+ bitRateAndFrameSizeTable[2][1][2][1] = 144;
+ bitRateAndFrameSizeTable[3][0][2][0] = 56;
+ bitRateAndFrameSizeTable[3][1][2][0] = 56;
+ bitRateAndFrameSizeTable[3][0][2][1] = 168;
+ bitRateAndFrameSizeTable[3][1][2][1] = 168;
+ bitRateAndFrameSizeTable[4][0][2][0] = 64;
+ bitRateAndFrameSizeTable[4][1][2][0] = 64;
+ bitRateAndFrameSizeTable[4][0][2][1] = 192;
+ bitRateAndFrameSizeTable[4][1][2][1] = 192;
+ bitRateAndFrameSizeTable[5][0][2][0] = 80;
+ bitRateAndFrameSizeTable[5][1][2][0] = 80;
+ bitRateAndFrameSizeTable[5][0][2][1] = 240;
+ bitRateAndFrameSizeTable[5][1][2][1] = 240;
+ bitRateAndFrameSizeTable[6][0][2][0] = 96;
+ bitRateAndFrameSizeTable[6][1][2][0] = 96;
+ bitRateAndFrameSizeTable[6][0][2][1] = 288;
+ bitRateAndFrameSizeTable[6][1][2][1] = 288;
+ bitRateAndFrameSizeTable[7][0][2][0] = 112;
+ bitRateAndFrameSizeTable[7][1][2][0] = 112;
+ bitRateAndFrameSizeTable[7][0][2][1] = 336;
+ bitRateAndFrameSizeTable[7][1][2][1] = 336;
+ bitRateAndFrameSizeTable[8][0][2][0] = 128;
+ bitRateAndFrameSizeTable[8][1][2][0] = 128;
+ bitRateAndFrameSizeTable[8][0][2][1] = 384;
+ bitRateAndFrameSizeTable[8][1][2][1] = 384;
+ bitRateAndFrameSizeTable[9][0][2][0] = 160;
+ bitRateAndFrameSizeTable[9][1][2][0] = 160;
+ bitRateAndFrameSizeTable[9][0][2][1] = 480;
+ bitRateAndFrameSizeTable[9][1][2][1] = 480;
+ bitRateAndFrameSizeTable[10][0][2][0] = 192;
+ bitRateAndFrameSizeTable[10][1][2][0] = 192;
+ bitRateAndFrameSizeTable[10][0][2][1] = 576;
+ bitRateAndFrameSizeTable[10][1][2][1] = 576;
+ bitRateAndFrameSizeTable[11][0][2][0] = 224;
+ bitRateAndFrameSizeTable[11][1][2][0] = 224;
+ bitRateAndFrameSizeTable[11][0][2][1] = 672;
+ bitRateAndFrameSizeTable[11][1][2][1] = 672;
+ bitRateAndFrameSizeTable[12][0][2][0] = 256;
+ bitRateAndFrameSizeTable[12][1][2][0] = 256;
+ bitRateAndFrameSizeTable[12][0][2][1] = 768;
+ bitRateAndFrameSizeTable[12][1][2][1] = 768;
+ bitRateAndFrameSizeTable[13][0][2][0] = 320;
+ bitRateAndFrameSizeTable[13][1][2][0] = 320;
+ bitRateAndFrameSizeTable[13][0][2][1] = 960;
+ bitRateAndFrameSizeTable[13][1][2][1] = 960;
+ bitRateAndFrameSizeTable[14][0][2][0] = 384;
+ bitRateAndFrameSizeTable[14][1][2][0] = 384;
+ bitRateAndFrameSizeTable[14][0][2][1] = 1152;
+ bitRateAndFrameSizeTable[14][1][2][1] = 1152;
+ bitRateAndFrameSizeTable[15][0][2][0] = 448;
+ bitRateAndFrameSizeTable[15][1][2][0] = 448;
+ bitRateAndFrameSizeTable[15][0][2][1] = 1344;
+ bitRateAndFrameSizeTable[15][1][2][1] = 1344;
+ bitRateAndFrameSizeTable[16][0][2][0] = 512;
+ bitRateAndFrameSizeTable[16][1][2][0] = 512;
+ bitRateAndFrameSizeTable[16][0][2][1] = 1536;
+ bitRateAndFrameSizeTable[16][1][2][1] = 1536;
+ bitRateAndFrameSizeTable[17][0][2][0] = 576;
+ bitRateAndFrameSizeTable[17][1][2][0] = 576;
+ bitRateAndFrameSizeTable[17][0][2][1] = 1728;
+ bitRateAndFrameSizeTable[17][1][2][1] = 1728;
+ bitRateAndFrameSizeTable[18][0][2][0] = 640;
+ bitRateAndFrameSizeTable[18][1][2][0] = 640;
+ bitRateAndFrameSizeTable[18][0][2][1] = 1920;
+ bitRateAndFrameSizeTable[18][1][2][1] = 1920;
+ }
+} \ No newline at end of file
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/Amf0Track.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/Amf0Track.java.svn-base
new file mode 100644
index 0000000..0917767
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/Amf0Track.java.svn-base
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.googlecode.mp4parser.authoring.tracks;
+
+import com.coremedia.iso.boxes.*;
+import com.googlecode.mp4parser.authoring.AbstractTrack;
+import com.googlecode.mp4parser.authoring.TrackMetaData;
+import com.googlecode.mp4parser.boxes.adobe.ActionMessageFormat0SampleEntryBox;
+
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+public class Amf0Track extends AbstractTrack {
+ SortedMap<Long, byte[]> rawSamples = new TreeMap<Long, byte[]>() {
+ };
+ private TrackMetaData trackMetaData = new TrackMetaData();
+
+
+ /**
+ * Creates a new AMF0 track from
+ *
+ * @param rawSamples
+ */
+ public Amf0Track(Map<Long, byte[]> rawSamples) {
+ this.rawSamples = new TreeMap<Long, byte[]>(rawSamples);
+ trackMetaData.setCreationTime(new Date());
+ trackMetaData.setModificationTime(new Date());
+ trackMetaData.setTimescale(1000); // Text tracks use millieseconds
+ trackMetaData.setLanguage("eng");
+ }
+
+ public List<ByteBuffer> getSamples() {
+ LinkedList<ByteBuffer> samples = new LinkedList<ByteBuffer>();
+ for (byte[] bytes : rawSamples.values()) {
+ samples.add(ByteBuffer.wrap(bytes));
+ }
+ return samples;
+ }
+
+ public SampleDescriptionBox getSampleDescriptionBox() {
+ SampleDescriptionBox stsd = new SampleDescriptionBox();
+ ActionMessageFormat0SampleEntryBox amf0 = new ActionMessageFormat0SampleEntryBox();
+ amf0.setDataReferenceIndex(1);
+ stsd.addBox(amf0);
+ return stsd;
+ }
+
+ public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
+ LinkedList<TimeToSampleBox.Entry> timesToSample = new LinkedList<TimeToSampleBox.Entry>();
+ LinkedList<Long> keys = new LinkedList<Long>(rawSamples.keySet());
+ Collections.sort(keys);
+ long lastTimeStamp = 0;
+ for (Long key : keys) {
+ long delta = key - lastTimeStamp;
+ if (timesToSample.size() > 0 && timesToSample.peek().getDelta() == delta) {
+ timesToSample.peek().setCount(timesToSample.peek().getCount() + 1);
+ } else {
+ timesToSample.add(new TimeToSampleBox.Entry(1, delta));
+ }
+ lastTimeStamp = key;
+ }
+ return timesToSample;
+ }
+
+ public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
+ // AMF0 tracks do not have Composition Time
+ return null;
+ }
+
+ public long[] getSyncSamples() {
+ // AMF0 tracks do not have Sync Samples
+ return null;
+ }
+
+ public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
+ // AMF0 tracks do not have Sample Dependencies
+ return null;
+ }
+
+ public TrackMetaData getTrackMetaData() {
+ return trackMetaData; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public String getHandler() {
+ return "data";
+ }
+
+ public Box getMediaHeaderBox() {
+ return new NullMediaHeaderBox();
+ }
+
+ public SubSampleInformationBox getSubsampleInformationBox() {
+ return null;
+ }
+
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/AppendTrack.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/AppendTrack.java.svn-base
new file mode 100644
index 0000000..93ee0cd
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/AppendTrack.java.svn-base
@@ -0,0 +1,348 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.tracks;
+
+import com.coremedia.iso.boxes.*;
+import com.coremedia.iso.boxes.sampleentry.AudioSampleEntry;
+import com.googlecode.mp4parser.authoring.AbstractTrack;
+import com.googlecode.mp4parser.authoring.Track;
+import com.googlecode.mp4parser.authoring.TrackMetaData;
+import com.googlecode.mp4parser.boxes.mp4.ESDescriptorBox;
+import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.DecoderConfigDescriptor;
+import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.ESDescriptor;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
+import java.util.*;
+
+/**
+ * Appends two or more <code>Tracks</code> of the same type. No only that the type must be equal
+ * also the decoder settings must be the same.
+ */
+public class AppendTrack extends AbstractTrack {
+ Track[] tracks;
+ SampleDescriptionBox stsd;
+
+ public AppendTrack(Track... tracks) throws IOException {
+ this.tracks = tracks;
+
+ for (Track track : tracks) {
+
+ if (stsd == null) {
+ stsd = track.getSampleDescriptionBox();
+ } else {
+ ByteArrayOutputStream curBaos = new ByteArrayOutputStream();
+ ByteArrayOutputStream refBaos = new ByteArrayOutputStream();
+ track.getSampleDescriptionBox().getBox(Channels.newChannel(curBaos));
+ stsd.getBox(Channels.newChannel(refBaos));
+ byte[] cur = curBaos.toByteArray();
+ byte[] ref = refBaos.toByteArray();
+
+ if (!Arrays.equals(ref, cur)) {
+ SampleDescriptionBox curStsd = track.getSampleDescriptionBox();
+ if (stsd.getBoxes().size() == 1 && curStsd.getBoxes().size() == 1) {
+ if (stsd.getBoxes().get(0) instanceof AudioSampleEntry && curStsd.getBoxes().get(0) instanceof AudioSampleEntry) {
+ AudioSampleEntry aseResult = mergeAudioSampleEntries((AudioSampleEntry) stsd.getBoxes().get(0), (AudioSampleEntry) curStsd.getBoxes().get(0));
+ if (aseResult != null) {
+ stsd.setBoxes(Collections.<Box>singletonList(aseResult));
+ return;
+ }
+ }
+ }
+ throw new IOException("Cannot append " + track + " to " + tracks[0] + " since their Sample Description Boxes differ: \n" + track.getSampleDescriptionBox() + "\n vs. \n" + tracks[0].getSampleDescriptionBox());
+ }
+ }
+ }
+ }
+
+ private AudioSampleEntry mergeAudioSampleEntries(AudioSampleEntry ase1, AudioSampleEntry ase2) throws IOException {
+ if (ase1.getType().equals(ase2.getType())) {
+ AudioSampleEntry ase = new AudioSampleEntry(ase2.getType());
+ if (ase1.getBytesPerFrame() == ase2.getBytesPerFrame()) {
+ ase.setBytesPerFrame(ase1.getBytesPerFrame());
+ } else {
+ return null;
+ }
+ if (ase1.getBytesPerPacket() == ase2.getBytesPerPacket()) {
+ ase.setBytesPerPacket(ase1.getBytesPerPacket());
+ } else {
+ return null;
+ }
+ if (ase1.getBytesPerSample() == ase2.getBytesPerSample()) {
+ ase.setBytesPerSample(ase1.getBytesPerSample());
+ } else {
+ return null;
+ }
+ if (ase1.getChannelCount() == ase2.getChannelCount()) {
+ ase.setChannelCount(ase1.getChannelCount());
+ } else {
+ return null;
+ }
+ if (ase1.getPacketSize() == ase2.getPacketSize()) {
+ ase.setPacketSize(ase1.getPacketSize());
+ } else {
+ return null;
+ }
+ if (ase1.getCompressionId() == ase2.getCompressionId()) {
+ ase.setCompressionId(ase1.getCompressionId());
+ } else {
+ return null;
+ }
+ if (ase1.getSampleRate() == ase2.getSampleRate()) {
+ ase.setSampleRate(ase1.getSampleRate());
+ } else {
+ return null;
+ }
+ if (ase1.getSampleSize() == ase2.getSampleSize()) {
+ ase.setSampleSize(ase1.getSampleSize());
+ } else {
+ return null;
+ }
+ if (ase1.getSamplesPerPacket() == ase2.getSamplesPerPacket()) {
+ ase.setSamplesPerPacket(ase1.getSamplesPerPacket());
+ } else {
+ return null;
+ }
+ if (ase1.getSoundVersion() == ase2.getSoundVersion()) {
+ ase.setSoundVersion(ase1.getSoundVersion());
+ } else {
+ return null;
+ }
+ if (Arrays.equals(ase1.getSoundVersion2Data(), ase2.getSoundVersion2Data())) {
+ ase.setSoundVersion2Data(ase1.getSoundVersion2Data());
+ } else {
+ return null;
+ }
+ if (ase1.getBoxes().size() == ase2.getBoxes().size()) {
+ Iterator<Box> bxs1 = ase1.getBoxes().iterator();
+ Iterator<Box> bxs2 = ase2.getBoxes().iterator();
+ while (bxs1.hasNext()) {
+ Box cur1 = bxs1.next();
+ Box cur2 = bxs2.next();
+ ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
+ ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
+ cur1.getBox(Channels.newChannel(baos1));
+ cur2.getBox(Channels.newChannel(baos2));
+ if (Arrays.equals(baos1.toByteArray(), baos2.toByteArray())) {
+ ase.addBox(cur1);
+ } else {
+ if (ESDescriptorBox.TYPE.equals(cur1.getType()) && ESDescriptorBox.TYPE.equals(cur2.getType())) {
+ ESDescriptorBox esdsBox1 = (ESDescriptorBox) cur1;
+ ESDescriptorBox esdsBox2 = (ESDescriptorBox) cur2;
+ ESDescriptor esds1 = esdsBox1.getEsDescriptor();
+ ESDescriptor esds2 = esdsBox2.getEsDescriptor();
+ if (esds1.getURLFlag() != esds2.getURLFlag()) {
+ return null;
+ }
+ if (esds1.getURLLength() != esds2.getURLLength()) {
+ return null;
+ }
+ if (esds1.getDependsOnEsId() != esds2.getDependsOnEsId()) {
+ return null;
+ }
+ if (esds1.getEsId() != esds2.getEsId()) {
+ return null;
+ }
+ if (esds1.getoCREsId() != esds2.getoCREsId()) {
+ return null;
+ }
+ if (esds1.getoCRstreamFlag() != esds2.getoCRstreamFlag()) {
+ return null;
+ }
+ if (esds1.getRemoteODFlag() != esds2.getRemoteODFlag()) {
+ return null;
+ }
+ if (esds1.getStreamDependenceFlag() != esds2.getStreamDependenceFlag()) {
+ return null;
+ }
+ if (esds1.getStreamPriority() != esds2.getStreamPriority()) {
+ return null;
+ }
+ if (esds1.getURLString() != null ? !esds1.getURLString().equals(esds2.getURLString()) : esds2.getURLString() != null) {
+ return null;
+ }
+ if (esds1.getDecoderConfigDescriptor() != null ? !esds1.getDecoderConfigDescriptor().equals(esds2.getDecoderConfigDescriptor()) : esds2.getDecoderConfigDescriptor() != null) {
+ DecoderConfigDescriptor dcd1 = esds1.getDecoderConfigDescriptor();
+ DecoderConfigDescriptor dcd2 = esds2.getDecoderConfigDescriptor();
+ if (!dcd1.getAudioSpecificInfo().equals(dcd2.getAudioSpecificInfo())) {
+ return null;
+ }
+ if (dcd1.getAvgBitRate() != dcd2.getAvgBitRate()) {
+ // I don't care
+ }
+ if (dcd1.getBufferSizeDB() != dcd2.getBufferSizeDB()) {
+ // I don't care
+ }
+
+ if (dcd1.getDecoderSpecificInfo() != null ? !dcd1.getDecoderSpecificInfo().equals(dcd2.getDecoderSpecificInfo()) : dcd2.getDecoderSpecificInfo() != null) {
+ return null;
+ }
+
+ if (dcd1.getMaxBitRate() != dcd2.getMaxBitRate()) {
+ // I don't care
+ }
+ if (!dcd1.getProfileLevelIndicationDescriptors().equals(dcd2.getProfileLevelIndicationDescriptors())) {
+ return null;
+ }
+
+ if (dcd1.getObjectTypeIndication() != dcd2.getObjectTypeIndication()) {
+ return null;
+ }
+ if (dcd1.getStreamType() != dcd2.getStreamType()) {
+ return null;
+ }
+ if (dcd1.getUpStream() != dcd2.getUpStream()) {
+ return null;
+ }
+
+
+ }
+ if (esds1.getOtherDescriptors() != null ? !esds1.getOtherDescriptors().equals(esds2.getOtherDescriptors()) : esds2.getOtherDescriptors() != null) {
+ return null;
+ }
+ if (esds1.getSlConfigDescriptor() != null ? !esds1.getSlConfigDescriptor().equals(esds2.getSlConfigDescriptor()) : esds2.getSlConfigDescriptor() != null) {
+ return null;
+ }
+ ase.addBox(cur1);
+ }
+ }
+ }
+ }
+ return ase;
+ } else {
+ return null;
+ }
+
+
+ }
+
+
+ public List<ByteBuffer> getSamples() {
+ ArrayList<ByteBuffer> lists = new ArrayList<ByteBuffer>();
+
+ for (Track track : tracks) {
+ lists.addAll(track.getSamples());
+ }
+
+ return lists;
+ }
+
+ public SampleDescriptionBox getSampleDescriptionBox() {
+ return stsd;
+ }
+
+ public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
+ if (tracks[0].getDecodingTimeEntries() != null && !tracks[0].getDecodingTimeEntries().isEmpty()) {
+ List<long[]> lists = new LinkedList<long[]>();
+ for (Track track : tracks) {
+ lists.add(TimeToSampleBox.blowupTimeToSamples(track.getDecodingTimeEntries()));
+ }
+
+ LinkedList<TimeToSampleBox.Entry> returnDecodingEntries = new LinkedList<TimeToSampleBox.Entry>();
+ for (long[] list : lists) {
+ for (long nuDecodingTime : list) {
+ if (returnDecodingEntries.isEmpty() || returnDecodingEntries.getLast().getDelta() != nuDecodingTime) {
+ TimeToSampleBox.Entry e = new TimeToSampleBox.Entry(1, nuDecodingTime);
+ returnDecodingEntries.add(e);
+ } else {
+ TimeToSampleBox.Entry e = returnDecodingEntries.getLast();
+ e.setCount(e.getCount() + 1);
+ }
+ }
+ }
+ return returnDecodingEntries;
+ } else {
+ return null;
+ }
+ }
+
+ public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
+ if (tracks[0].getCompositionTimeEntries() != null && !tracks[0].getCompositionTimeEntries().isEmpty()) {
+ List<int[]> lists = new LinkedList<int[]>();
+ for (Track track : tracks) {
+ lists.add(CompositionTimeToSample.blowupCompositionTimes(track.getCompositionTimeEntries()));
+ }
+ LinkedList<CompositionTimeToSample.Entry> compositionTimeEntries = new LinkedList<CompositionTimeToSample.Entry>();
+ for (int[] list : lists) {
+ for (int compositionTime : list) {
+ if (compositionTimeEntries.isEmpty() || compositionTimeEntries.getLast().getOffset() != compositionTime) {
+ CompositionTimeToSample.Entry e = new CompositionTimeToSample.Entry(1, compositionTime);
+ compositionTimeEntries.add(e);
+ } else {
+ CompositionTimeToSample.Entry e = compositionTimeEntries.getLast();
+ e.setCount(e.getCount() + 1);
+ }
+ }
+ }
+ return compositionTimeEntries;
+ } else {
+ return null;
+ }
+ }
+
+ public long[] getSyncSamples() {
+ if (tracks[0].getSyncSamples() != null && tracks[0].getSyncSamples().length > 0) {
+ int numSyncSamples = 0;
+ for (Track track : tracks) {
+ numSyncSamples += track.getSyncSamples().length;
+ }
+ long[] returnSyncSamples = new long[numSyncSamples];
+
+ int pos = 0;
+ long samplesBefore = 0;
+ for (Track track : tracks) {
+ for (long l : track.getSyncSamples()) {
+ returnSyncSamples[pos++] = samplesBefore + l;
+ }
+ samplesBefore += track.getSamples().size();
+ }
+ return returnSyncSamples;
+ } else {
+ return null;
+ }
+ }
+
+ public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
+ if (tracks[0].getSampleDependencies() != null && !tracks[0].getSampleDependencies().isEmpty()) {
+ List<SampleDependencyTypeBox.Entry> list = new LinkedList<SampleDependencyTypeBox.Entry>();
+ for (Track track : tracks) {
+ list.addAll(track.getSampleDependencies());
+ }
+ return list;
+ } else {
+ return null;
+ }
+ }
+
+ public TrackMetaData getTrackMetaData() {
+ return tracks[0].getTrackMetaData();
+ }
+
+ public String getHandler() {
+ return tracks[0].getHandler();
+ }
+
+ public Box getMediaHeaderBox() {
+ return tracks[0].getMediaHeaderBox();
+ }
+
+ public SubSampleInformationBox getSubsampleInformationBox() {
+ return tracks[0].getSubsampleInformationBox();
+ }
+
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/ChangeTimeScaleTrack.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/ChangeTimeScaleTrack.java.svn-base
new file mode 100644
index 0000000..50f76c2
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/ChangeTimeScaleTrack.java.svn-base
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.tracks;
+
+import com.coremedia.iso.boxes.*;
+import com.googlecode.mp4parser.authoring.Track;
+import com.googlecode.mp4parser.authoring.TrackMetaData;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.logging.Logger;
+
+/**
+ * Changes the timescale of a track by wrapping the track.
+ */
+public class ChangeTimeScaleTrack implements Track {
+ private static final Logger LOG = Logger.getLogger(ChangeTimeScaleTrack.class.getName());
+
+ Track source;
+ List<CompositionTimeToSample.Entry> ctts;
+ List<TimeToSampleBox.Entry> tts;
+ long timeScale;
+
+ /**
+ * Changes the time scale of the source track to the target time scale and makes sure
+ * that any rounding errors that may have summed are corrected exactly before the syncSamples.
+ *
+ * @param source the source track
+ * @param targetTimeScale the resulting time scale of this track.
+ * @param syncSamples at these sync points where rounding error are corrected.
+ */
+ public ChangeTimeScaleTrack(Track source, long targetTimeScale, long[] syncSamples) {
+ this.source = source;
+ this.timeScale = targetTimeScale;
+ double timeScaleFactor = (double) targetTimeScale / source.getTrackMetaData().getTimescale();
+ ctts = adjustCtts(source.getCompositionTimeEntries(), timeScaleFactor);
+ tts = adjustTts(source.getDecodingTimeEntries(), timeScaleFactor, syncSamples, getTimes(source, syncSamples, targetTimeScale));
+ }
+
+ private static long[] getTimes(Track track, long[] syncSamples, long targetTimeScale) {
+ long[] syncSampleTimes = new long[syncSamples.length];
+ Queue<TimeToSampleBox.Entry> timeQueue = new LinkedList<TimeToSampleBox.Entry>(track.getDecodingTimeEntries());
+
+ int currentSample = 1; // first syncsample is 1
+ long currentDuration = 0;
+ long currentDelta = 0;
+ int currentSyncSampleIndex = 0;
+ long left = 0;
+
+
+ while (currentSample <= syncSamples[syncSamples.length - 1]) {
+ if (currentSample++ == syncSamples[currentSyncSampleIndex]) {
+ syncSampleTimes[currentSyncSampleIndex++] = (currentDuration * targetTimeScale) / track.getTrackMetaData().getTimescale();
+ }
+ if (left-- == 0) {
+ TimeToSampleBox.Entry entry = timeQueue.poll();
+ left = entry.getCount() - 1;
+ currentDelta = entry.getDelta();
+ }
+ currentDuration += currentDelta;
+ }
+ return syncSampleTimes;
+
+ }
+
+ public SampleDescriptionBox getSampleDescriptionBox() {
+ return source.getSampleDescriptionBox();
+ }
+
+ public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
+ return tts;
+ }
+
+ public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
+ return ctts;
+ }
+
+ public long[] getSyncSamples() {
+ return source.getSyncSamples();
+ }
+
+ public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
+ return source.getSampleDependencies();
+ }
+
+ public TrackMetaData getTrackMetaData() {
+ TrackMetaData trackMetaData = (TrackMetaData) source.getTrackMetaData().clone();
+ trackMetaData.setTimescale(timeScale);
+ return trackMetaData;
+ }
+
+ public String getHandler() {
+ return source.getHandler();
+ }
+
+ public boolean isEnabled() {
+ return source.isEnabled();
+ }
+
+ public boolean isInMovie() {
+ return source.isInMovie();
+ }
+
+ public boolean isInPreview() {
+ return source.isInPreview();
+ }
+
+ public boolean isInPoster() {
+ return source.isInPoster();
+ }
+
+ public List<ByteBuffer> getSamples() {
+ return source.getSamples();
+ }
+
+
+ /**
+ * Adjusting the composition times is easy. Just scale it by the factor - that's it. There is no rounding
+ * error summing up.
+ *
+ * @param source
+ * @param timeScaleFactor
+ * @return
+ */
+ static List<CompositionTimeToSample.Entry> adjustCtts(List<CompositionTimeToSample.Entry> source, double timeScaleFactor) {
+ if (source != null) {
+ List<CompositionTimeToSample.Entry> entries2 = new ArrayList<CompositionTimeToSample.Entry>(source.size());
+ for (CompositionTimeToSample.Entry entry : source) {
+ entries2.add(new CompositionTimeToSample.Entry(entry.getCount(), (int) Math.round(timeScaleFactor * entry.getOffset())));
+ }
+ return entries2;
+ } else {
+ return null;
+ }
+ }
+
+ static List<TimeToSampleBox.Entry> adjustTts(List<TimeToSampleBox.Entry> source, double timeScaleFactor, long[] syncSample, long[] syncSampleTimes) {
+
+ long[] sourceArray = TimeToSampleBox.blowupTimeToSamples(source);
+ long summedDurations = 0;
+
+ LinkedList<TimeToSampleBox.Entry> entries2 = new LinkedList<TimeToSampleBox.Entry>();
+ for (int i = 1; i <= sourceArray.length; i++) {
+ long duration = sourceArray[i - 1];
+
+ long x = Math.round(timeScaleFactor * duration);
+
+
+ TimeToSampleBox.Entry last = entries2.peekLast();
+ int ssIndex;
+ if ((ssIndex = Arrays.binarySearch(syncSample, i + 1)) >= 0) {
+ // we are at the sample before sync point
+ if (syncSampleTimes[ssIndex] != summedDurations) {
+ long correction = syncSampleTimes[ssIndex] - (summedDurations + x);
+ LOG.finest(String.format("Sample %d %d / %d - correct by %d", i, summedDurations, syncSampleTimes[ssIndex], correction));
+ x += correction;
+ }
+ }
+ summedDurations += x;
+ if (last == null) {
+ entries2.add(new TimeToSampleBox.Entry(1, x));
+ } else if (last.getDelta() != x) {
+ entries2.add(new TimeToSampleBox.Entry(1, x));
+ } else {
+ last.setCount(last.getCount() + 1);
+ }
+
+ }
+ return entries2;
+ }
+
+ public Box getMediaHeaderBox() {
+ return source.getMediaHeaderBox();
+ }
+
+ public SubSampleInformationBox getSubsampleInformationBox() {
+ return source.getSubsampleInformationBox();
+ }
+
+ @Override
+ public String toString() {
+ return "ChangeTimeScaleTrack{" +
+ "source=" + source +
+ '}';
+ }
+} \ No newline at end of file
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/CroppedTrack.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/CroppedTrack.java.svn-base
new file mode 100644
index 0000000..2389961
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/CroppedTrack.java.svn-base
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.tracks;
+
+import com.coremedia.iso.boxes.*;
+import com.googlecode.mp4parser.authoring.AbstractTrack;
+import com.googlecode.mp4parser.authoring.Track;
+import com.googlecode.mp4parser.authoring.TrackMetaData;
+
+import java.nio.ByteBuffer;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Generates a Track that starts at fromSample and ends at toSample (exclusive). The user of this class
+ * has to make sure that the fromSample is a random access sample.
+ * <ul>
+ * <li>In AAC this is every single sample</li>
+ * <li>In H264 this is every sample that is marked in the SyncSampleBox</li>
+ * </ul>
+ */
+public class CroppedTrack extends AbstractTrack {
+ Track origTrack;
+ private int fromSample;
+ private int toSample;
+ private long[] syncSampleArray;
+
+ public CroppedTrack(Track origTrack, long fromSample, long toSample) {
+ this.origTrack = origTrack;
+ assert fromSample <= Integer.MAX_VALUE;
+ assert toSample <= Integer.MAX_VALUE;
+ this.fromSample = (int) fromSample;
+ this.toSample = (int) toSample;
+ }
+
+ public List<ByteBuffer> getSamples() {
+ return origTrack.getSamples().subList(fromSample, toSample);
+ }
+
+ public SampleDescriptionBox getSampleDescriptionBox() {
+ return origTrack.getSampleDescriptionBox();
+ }
+
+ public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
+ if (origTrack.getDecodingTimeEntries() != null && !origTrack.getDecodingTimeEntries().isEmpty()) {
+ // todo optimize! too much long is allocated but then not used
+ long[] decodingTimes = TimeToSampleBox.blowupTimeToSamples(origTrack.getDecodingTimeEntries());
+ long[] nuDecodingTimes = new long[toSample - fromSample];
+ System.arraycopy(decodingTimes, fromSample, nuDecodingTimes, 0, toSample - fromSample);
+
+ LinkedList<TimeToSampleBox.Entry> returnDecodingEntries = new LinkedList<TimeToSampleBox.Entry>();
+
+ for (long nuDecodingTime : nuDecodingTimes) {
+ if (returnDecodingEntries.isEmpty() || returnDecodingEntries.getLast().getDelta() != nuDecodingTime) {
+ TimeToSampleBox.Entry e = new TimeToSampleBox.Entry(1, nuDecodingTime);
+ returnDecodingEntries.add(e);
+ } else {
+ TimeToSampleBox.Entry e = returnDecodingEntries.getLast();
+ e.setCount(e.getCount() + 1);
+ }
+ }
+ return returnDecodingEntries;
+ } else {
+ return null;
+ }
+ }
+
+ public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
+ if (origTrack.getCompositionTimeEntries() != null && !origTrack.getCompositionTimeEntries().isEmpty()) {
+ int[] compositionTime = CompositionTimeToSample.blowupCompositionTimes(origTrack.getCompositionTimeEntries());
+ int[] nuCompositionTimes = new int[toSample - fromSample];
+ System.arraycopy(compositionTime, fromSample, nuCompositionTimes, 0, toSample - fromSample);
+
+ LinkedList<CompositionTimeToSample.Entry> returnDecodingEntries = new LinkedList<CompositionTimeToSample.Entry>();
+
+ for (int nuDecodingTime : nuCompositionTimes) {
+ if (returnDecodingEntries.isEmpty() || returnDecodingEntries.getLast().getOffset() != nuDecodingTime) {
+ CompositionTimeToSample.Entry e = new CompositionTimeToSample.Entry(1, nuDecodingTime);
+ returnDecodingEntries.add(e);
+ } else {
+ CompositionTimeToSample.Entry e = returnDecodingEntries.getLast();
+ e.setCount(e.getCount() + 1);
+ }
+ }
+ return returnDecodingEntries;
+ } else {
+ return null;
+ }
+ }
+
+ synchronized public long[] getSyncSamples() {
+ if (this.syncSampleArray == null) {
+ if (origTrack.getSyncSamples() != null && origTrack.getSyncSamples().length > 0) {
+ List<Long> syncSamples = new LinkedList<Long>();
+ for (long l : origTrack.getSyncSamples()) {
+ if (l >= fromSample && l < toSample) {
+ syncSamples.add(l - fromSample);
+ }
+ }
+ syncSampleArray = new long[syncSamples.size()];
+ for (int i = 0; i < syncSampleArray.length; i++) {
+ syncSampleArray[i] = syncSamples.get(i);
+
+ }
+ return syncSampleArray;
+ } else {
+ return null;
+ }
+ } else {
+ return this.syncSampleArray;
+ }
+ }
+
+ public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
+ if (origTrack.getSampleDependencies() != null && !origTrack.getSampleDependencies().isEmpty()) {
+ return origTrack.getSampleDependencies().subList(fromSample, toSample);
+ } else {
+ return null;
+ }
+ }
+
+ public TrackMetaData getTrackMetaData() {
+ return origTrack.getTrackMetaData();
+ }
+
+ public String getHandler() {
+ return origTrack.getHandler();
+ }
+
+ public Box getMediaHeaderBox() {
+ return origTrack.getMediaHeaderBox();
+ }
+
+ public SubSampleInformationBox getSubsampleInformationBox() {
+ return origTrack.getSubsampleInformationBox();
+ }
+
+} \ No newline at end of file
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/DivideTimeScaleTrack.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/DivideTimeScaleTrack.java.svn-base
new file mode 100644
index 0000000..c51e8e0
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/DivideTimeScaleTrack.java.svn-base
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.tracks;
+
+import com.coremedia.iso.boxes.*;
+import com.googlecode.mp4parser.authoring.Track;
+import com.googlecode.mp4parser.authoring.TrackMetaData;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Changes the timescale of a track by wrapping the track.
+ */
+public class DivideTimeScaleTrack implements Track {
+ Track source;
+ private int timeScaleDivisor;
+
+ public DivideTimeScaleTrack(Track source, int timeScaleDivisor) {
+ this.source = source;
+ this.timeScaleDivisor = timeScaleDivisor;
+ }
+
+ public SampleDescriptionBox getSampleDescriptionBox() {
+ return source.getSampleDescriptionBox();
+ }
+
+ public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
+ return adjustTts();
+ }
+
+ public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
+ return adjustCtts();
+ }
+
+ public long[] getSyncSamples() {
+ return source.getSyncSamples();
+ }
+
+ public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
+ return source.getSampleDependencies();
+ }
+
+ public TrackMetaData getTrackMetaData() {
+ TrackMetaData trackMetaData = (TrackMetaData) source.getTrackMetaData().clone();
+ trackMetaData.setTimescale(source.getTrackMetaData().getTimescale() / this.timeScaleDivisor);
+ return trackMetaData;
+ }
+
+ public String getHandler() {
+ return source.getHandler();
+ }
+
+ public boolean isEnabled() {
+ return source.isEnabled();
+ }
+
+ public boolean isInMovie() {
+ return source.isInMovie();
+ }
+
+ public boolean isInPreview() {
+ return source.isInPreview();
+ }
+
+ public boolean isInPoster() {
+ return source.isInPoster();
+ }
+
+ public List<ByteBuffer> getSamples() {
+ return source.getSamples();
+ }
+
+
+ List<CompositionTimeToSample.Entry> adjustCtts() {
+ List<CompositionTimeToSample.Entry> origCtts = this.source.getCompositionTimeEntries();
+ if (origCtts != null) {
+ List<CompositionTimeToSample.Entry> entries2 = new ArrayList<CompositionTimeToSample.Entry>(origCtts.size());
+ for (CompositionTimeToSample.Entry entry : origCtts) {
+ entries2.add(new CompositionTimeToSample.Entry(entry.getCount(), entry.getOffset() / timeScaleDivisor));
+ }
+ return entries2;
+ } else {
+ return null;
+ }
+ }
+
+ List<TimeToSampleBox.Entry> adjustTts() {
+ List<TimeToSampleBox.Entry> origTts = source.getDecodingTimeEntries();
+ LinkedList<TimeToSampleBox.Entry> entries2 = new LinkedList<TimeToSampleBox.Entry>();
+ for (TimeToSampleBox.Entry e : origTts) {
+ entries2.add(new TimeToSampleBox.Entry(e.getCount(), e.getDelta() / timeScaleDivisor));
+ }
+ return entries2;
+ }
+
+ public Box getMediaHeaderBox() {
+ return source.getMediaHeaderBox();
+ }
+
+ public SubSampleInformationBox getSubsampleInformationBox() {
+ return source.getSubsampleInformationBox();
+ }
+
+ @Override
+ public String toString() {
+ return "MultiplyTimeScaleTrack{" +
+ "source=" + source +
+ '}';
+ }
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/EC3TrackImpl.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/EC3TrackImpl.java.svn-base
new file mode 100644
index 0000000..d0b2d76
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/EC3TrackImpl.java.svn-base
@@ -0,0 +1,436 @@
+package com.googlecode.mp4parser.authoring.tracks;
+
+import com.coremedia.iso.boxes.*;
+import com.coremedia.iso.boxes.sampleentry.AudioSampleEntry;
+import com.googlecode.mp4parser.authoring.AbstractTrack;
+import com.googlecode.mp4parser.authoring.TrackMetaData;
+import com.googlecode.mp4parser.boxes.EC3SpecificBox;
+import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.BitReaderBuffer;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: magnus
+ * Date: 2012-03-14
+ * Time: 10:39
+ * To change this template use File | Settings | File Templates.
+ */
+public class EC3TrackImpl extends AbstractTrack {
+ TrackMetaData trackMetaData = new TrackMetaData();
+ SampleDescriptionBox sampleDescriptionBox;
+
+ int samplerate;
+ int bitrate;
+ int frameSize;
+
+ List<BitStreamInfo> entries = new LinkedList<BitStreamInfo>();
+
+ private BufferedInputStream inputStream;
+ private List<ByteBuffer> samples;
+ List<TimeToSampleBox.Entry> stts = new LinkedList<TimeToSampleBox.Entry>();
+ private String lang = "und";
+
+ public EC3TrackImpl(InputStream fin, String lang) throws IOException {
+ this.lang = lang;
+ parse(fin);
+ }
+
+ public EC3TrackImpl(InputStream fin) throws IOException {
+ parse(fin);
+ }
+
+ private void parse(InputStream fin) throws IOException {
+ inputStream = new BufferedInputStream(fin);
+
+ boolean done = false;
+ inputStream.mark(10000);
+ while (!done) {
+ BitStreamInfo bsi = readVariables();
+ if (bsi == null) {
+ throw new IOException();
+ }
+ for (BitStreamInfo entry : entries) {
+ if (bsi.strmtyp != 1 && entry.substreamid == bsi.substreamid) {
+ done = true;
+ }
+ }
+ if (!done) {
+ entries.add(bsi);
+ long skipped = inputStream.skip(bsi.frameSize);
+ assert skipped == bsi.frameSize;
+ }
+ }
+
+ inputStream.reset();
+
+ if (entries.size() == 0) {
+ throw new IOException();
+ }
+ samplerate = entries.get(0).samplerate;
+
+ sampleDescriptionBox = new SampleDescriptionBox();
+ AudioSampleEntry audioSampleEntry = new AudioSampleEntry("ec-3");
+ audioSampleEntry.setChannelCount(2); // According to ETSI TS 102 366 Annex F
+ audioSampleEntry.setSampleRate(samplerate);
+ audioSampleEntry.setDataReferenceIndex(1);
+ audioSampleEntry.setSampleSize(16);
+
+ EC3SpecificBox ec3 = new EC3SpecificBox();
+ int[] deps = new int[entries.size()];
+ int[] chan_locs = new int[entries.size()];
+ for (BitStreamInfo bsi : entries) {
+ if (bsi.strmtyp == 1) {
+ deps[bsi.substreamid]++;
+ chan_locs[bsi.substreamid] = ((bsi.chanmap >> 6) & 0x100) | ((bsi.chanmap >> 5) & 0xff);
+ }
+ }
+ for (BitStreamInfo bsi : entries) {
+ if (bsi.strmtyp != 1) {
+ EC3SpecificBox.Entry e = new EC3SpecificBox.Entry();
+ e.fscod = bsi.fscod;
+ e.bsid = bsi.bsid;
+ e.bsmod = bsi.bsmod;
+ e.acmod = bsi.acmod;
+ e.lfeon = bsi.lfeon;
+ e.reserved = 0;
+ e.num_dep_sub = deps[bsi.substreamid];
+ e.chan_loc = chan_locs[bsi.substreamid];
+ e.reserved2 = 0;
+ ec3.addEntry(e);
+ }
+ bitrate += bsi.bitrate;
+ frameSize += bsi.frameSize;
+ }
+
+ ec3.setDataRate(bitrate / 1000);
+ audioSampleEntry.addBox(ec3);
+ sampleDescriptionBox.addBox(audioSampleEntry);
+
+ trackMetaData.setCreationTime(new Date());
+ trackMetaData.setModificationTime(new Date());
+ trackMetaData.setLanguage(lang);
+ trackMetaData.setTimescale(samplerate); // Audio tracks always use samplerate as timescale
+
+ samples = new LinkedList<ByteBuffer>();
+ if (!readSamples()) {
+ throw new IOException();
+ }
+ }
+
+
+ public List<ByteBuffer> getSamples() {
+
+ return samples;
+ }
+
+ public SampleDescriptionBox getSampleDescriptionBox() {
+ return sampleDescriptionBox;
+ }
+
+ public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
+ return stts;
+ }
+
+ public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
+ return null;
+ }
+
+ public long[] getSyncSamples() {
+ return null;
+ }
+
+ public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
+ return null;
+ }
+
+ public TrackMetaData getTrackMetaData() {
+ return trackMetaData;
+ }
+
+ public String getHandler() {
+ return "soun";
+ }
+
+ public AbstractMediaHeaderBox getMediaHeaderBox() {
+ return new SoundMediaHeaderBox();
+ }
+
+ public SubSampleInformationBox getSubsampleInformationBox() {
+ return null;
+ }
+
+ private BitStreamInfo readVariables() throws IOException {
+ byte[] data = new byte[200];
+ inputStream.mark(200);
+ if (200 != inputStream.read(data, 0, 200)) {
+ return null;
+ }
+ inputStream.reset(); // Rewind
+ ByteBuffer bb = ByteBuffer.wrap(data);
+ BitReaderBuffer brb = new BitReaderBuffer(bb);
+ int syncword = brb.readBits(16);
+ if (syncword != 0xb77) {
+ return null;
+ }
+
+ BitStreamInfo entry = new BitStreamInfo();
+
+ entry.strmtyp = brb.readBits(2);
+ entry.substreamid = brb.readBits(3);
+ int frmsiz = brb.readBits(11);
+ entry.frameSize = 2 * (frmsiz + 1);
+
+ entry.fscod = brb.readBits(2);
+ int fscod2 = -1;
+ int numblkscod;
+ if (entry.fscod == 3) {
+ fscod2 = brb.readBits(2);
+ numblkscod = 3;
+ } else {
+ numblkscod = brb.readBits(2);
+ }
+ int numberOfBlocksPerSyncFrame = 0;
+ switch (numblkscod) {
+ case 0:
+ numberOfBlocksPerSyncFrame = 1;
+ break;
+
+ case 1:
+ numberOfBlocksPerSyncFrame = 2;
+ break;
+
+ case 2:
+ numberOfBlocksPerSyncFrame = 3;
+ break;
+
+ case 3:
+ numberOfBlocksPerSyncFrame = 6;
+ break;
+
+ }
+ entry.frameSize *= (6 / numberOfBlocksPerSyncFrame);
+
+ entry.acmod = brb.readBits(3);
+ entry.lfeon = brb.readBits(1);
+ entry.bsid = brb.readBits(5);
+ brb.readBits(5);
+ if (1 == brb.readBits(1)) {
+ brb.readBits(8); // compr
+ }
+ if (0 == entry.acmod) {
+ brb.readBits(5);
+ if (1 == brb.readBits(1)) {
+ brb.readBits(8);
+ }
+ }
+ if (1 == entry.strmtyp) {
+ if (1 == brb.readBits(1)) {
+ entry.chanmap = brb.readBits(16);
+ }
+ }
+ if (1 == brb.readBits(1)) { // mixing metadata
+ if (entry.acmod > 2) {
+ brb.readBits(2);
+ }
+ if (1 == (entry.acmod & 1) && entry.acmod > 2) {
+ brb.readBits(3);
+ brb.readBits(3);
+ }
+ if (0 < (entry.acmod & 4)) {
+ brb.readBits(3);
+ brb.readBits(3);
+ }
+ if (1 == entry.lfeon) {
+ if (1 == brb.readBits(1)) {
+ brb.readBits(5);
+ }
+ }
+ if (0 == entry.strmtyp) {
+ if (1 == brb.readBits(1)) {
+ brb.readBits(6);
+ }
+ if (0 == entry.acmod) {
+ if (1 == brb.readBits(1)) {
+ brb.readBits(6);
+ }
+ }
+ if (1 == brb.readBits(1)) {
+ brb.readBits(6);
+ }
+ int mixdef = brb.readBits(2);
+ if (1 == mixdef) {
+ brb.readBits(5);
+ } else if (2 == mixdef) {
+ brb.readBits(12);
+ } else if (3 == mixdef) {
+ int mixdeflen = brb.readBits(5);
+ if (1 == brb.readBits(1)) {
+ brb.readBits(5);
+ if (1 == brb.readBits(1)) {
+ brb.readBits(4);
+ }
+ if (1 == brb.readBits(1)) {
+ brb.readBits(4);
+ }
+ if (1 == brb.readBits(1)) {
+ brb.readBits(4);
+ }
+ if (1 == brb.readBits(1)) {
+ brb.readBits(4);
+ }
+ if (1 == brb.readBits(1)) {
+ brb.readBits(4);
+ }
+ if (1 == brb.readBits(1)) {
+ brb.readBits(4);
+ }
+ if (1 == brb.readBits(1)) {
+ brb.readBits(4);
+ }
+ if (1 == brb.readBits(1)) {
+ if (1 == brb.readBits(1)) {
+ brb.readBits(4);
+ }
+ if (1 == brb.readBits(1)) {
+ brb.readBits(4);
+ }
+ }
+ }
+ if (1 == brb.readBits(1)) {
+ brb.readBits(5);
+ if (1 == brb.readBits(1)) {
+ brb.readBits(7);
+ if (1 == brb.readBits(1)) {
+ brb.readBits(8);
+ }
+ }
+ }
+ for (int i = 0; i < (mixdeflen + 2); i++) {
+ brb.readBits(8);
+ }
+ brb.byteSync();
+ }
+ if (entry.acmod < 2) {
+ if (1 == brb.readBits(1)) {
+ brb.readBits(14);
+ }
+ if (0 == entry.acmod) {
+ if (1 == brb.readBits(1)) {
+ brb.readBits(14);
+ }
+ }
+ if (1 == brb.readBits(1)) {
+ if (numblkscod == 0) {
+ brb.readBits(5);
+ } else {
+ for (int i = 0; i < numberOfBlocksPerSyncFrame; i++) {
+ if (1 == brb.readBits(1)) {
+ brb.readBits(5);
+ }
+ }
+ }
+
+ }
+ }
+ }
+ }
+ if (1 == brb.readBits(1)) { // infomdate
+ entry.bsmod = brb.readBits(3);
+ }
+
+ switch (entry.fscod) {
+ case 0:
+ entry.samplerate = 48000;
+ break;
+
+ case 1:
+ entry.samplerate = 44100;
+ break;
+
+ case 2:
+ entry.samplerate = 32000;
+ break;
+
+ case 3: {
+ switch (fscod2) {
+ case 0:
+ entry.samplerate = 24000;
+ break;
+
+ case 1:
+ entry.samplerate = 22050;
+ break;
+
+ case 2:
+ entry.samplerate = 16000;
+ break;
+
+ case 3:
+ entry.samplerate = 0;
+ break;
+ }
+ break;
+ }
+
+ }
+ if (entry.samplerate == 0) {
+ return null;
+ }
+
+ entry.bitrate = (int) (((double) entry.samplerate) / 1536.0 * entry.frameSize * 8);
+
+ return entry;
+ }
+
+ private boolean readSamples() throws IOException {
+ int read = frameSize;
+ boolean ret = false;
+ while (frameSize == read) {
+ ret = true;
+ byte[] data = new byte[frameSize];
+ read = inputStream.read(data);
+ if (read == frameSize) {
+ samples.add(ByteBuffer.wrap(data));
+ stts.add(new TimeToSampleBox.Entry(1, 1536));
+ }
+ }
+ return ret;
+ }
+
+ public static class BitStreamInfo extends EC3SpecificBox.Entry {
+ public int frameSize;
+ public int substreamid;
+ public int bitrate;
+ public int samplerate;
+ public int strmtyp;
+ public int chanmap;
+
+ @Override
+ public String toString() {
+ return "BitStreamInfo{" +
+ "frameSize=" + frameSize +
+ ", substreamid=" + substreamid +
+ ", bitrate=" + bitrate +
+ ", samplerate=" + samplerate +
+ ", strmtyp=" + strmtyp +
+ ", chanmap=" + chanmap +
+ '}';
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "EC3TrackImpl{" +
+ "bitrate=" + bitrate +
+ ", samplerate=" + samplerate +
+ ", entries=" + entries +
+ '}';
+ }
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/H264TrackImpl.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/H264TrackImpl.java.svn-base
new file mode 100644
index 0000000..b3c1866
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/H264TrackImpl.java.svn-base
@@ -0,0 +1,740 @@
+package com.googlecode.mp4parser.authoring.tracks;
+
+import com.coremedia.iso.boxes.*;
+import com.coremedia.iso.boxes.h264.AvcConfigurationBox;
+import com.coremedia.iso.boxes.sampleentry.VisualSampleEntry;
+import com.googlecode.mp4parser.authoring.AbstractTrack;
+import com.googlecode.mp4parser.authoring.TrackMetaData;
+import com.googlecode.mp4parser.h264.model.PictureParameterSet;
+import com.googlecode.mp4parser.h264.model.SeqParameterSet;
+import com.googlecode.mp4parser.h264.read.CAVLCReader;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.logging.Logger;
+
+/**
+ * The <code>H264TrackImpl</code> creates a <code>Track</code> from an H.264
+ * Annex B file.
+ */
+public class H264TrackImpl extends AbstractTrack {
+ private static final Logger LOG = Logger.getLogger(H264TrackImpl.class.getName());
+
+ TrackMetaData trackMetaData = new TrackMetaData();
+ SampleDescriptionBox sampleDescriptionBox;
+
+ private ReaderWrapper reader;
+ private List<ByteBuffer> samples;
+ boolean readSamples = false;
+
+ List<TimeToSampleBox.Entry> stts;
+ List<CompositionTimeToSample.Entry> ctts;
+ List<SampleDependencyTypeBox.Entry> sdtp;
+ List<Integer> stss;
+
+ SeqParameterSet seqParameterSet = null;
+ PictureParameterSet pictureParameterSet = null;
+ LinkedList<byte[]> seqParameterSetList = new LinkedList<byte[]>();
+ LinkedList<byte[]> pictureParameterSetList = new LinkedList<byte[]>();
+
+ private int width;
+ private int height;
+ private int timescale;
+ private int frametick;
+ private int currentScSize;
+ private int prevScSize;
+
+ private SEIMessage seiMessage;
+ int frameNrInGop = 0;
+ private boolean determineFrameRate = true;
+ private String lang = "und";
+
+ public H264TrackImpl(InputStream inputStream, String lang, long timescale) throws IOException {
+ this.lang = lang;
+ if (timescale > 1000) {
+ timescale = timescale; //e.g. 23976
+ frametick = 1000;
+ determineFrameRate = false;
+ } else {
+ throw new IllegalArgumentException("Timescale must be specified in milliseconds!");
+ }
+ parse(inputStream);
+ }
+
+ public H264TrackImpl(InputStream inputStream, String lang) throws IOException {
+ this.lang = lang;
+ parse(inputStream);
+ }
+
+ public H264TrackImpl(InputStream inputStream) throws IOException {
+ parse(inputStream);
+ }
+
+ private void parse(InputStream inputStream) throws IOException {
+ this.reader = new ReaderWrapper(inputStream);
+ stts = new LinkedList<TimeToSampleBox.Entry>();
+ ctts = new LinkedList<CompositionTimeToSample.Entry>();
+ sdtp = new LinkedList<SampleDependencyTypeBox.Entry>();
+ stss = new LinkedList<Integer>();
+
+ samples = new LinkedList<ByteBuffer>();
+ if (!readSamples()) {
+ throw new IOException();
+ }
+
+ if (!readVariables()) {
+ throw new IOException();
+ }
+
+ sampleDescriptionBox = new SampleDescriptionBox();
+ VisualSampleEntry visualSampleEntry = new VisualSampleEntry("avc1");
+ visualSampleEntry.setDataReferenceIndex(1);
+ visualSampleEntry.setDepth(24);
+ visualSampleEntry.setFrameCount(1);
+ visualSampleEntry.setHorizresolution(72);
+ visualSampleEntry.setVertresolution(72);
+ visualSampleEntry.setWidth(width);
+ visualSampleEntry.setHeight(height);
+ visualSampleEntry.setCompressorname("AVC Coding");
+
+ AvcConfigurationBox avcConfigurationBox = new AvcConfigurationBox();
+
+ avcConfigurationBox.setSequenceParameterSets(seqParameterSetList);
+ avcConfigurationBox.setPictureParameterSets(pictureParameterSetList);
+ avcConfigurationBox.setAvcLevelIndication(seqParameterSet.level_idc);
+ avcConfigurationBox.setAvcProfileIndication(seqParameterSet.profile_idc);
+ avcConfigurationBox.setBitDepthLumaMinus8(seqParameterSet.bit_depth_luma_minus8);
+ avcConfigurationBox.setBitDepthChromaMinus8(seqParameterSet.bit_depth_chroma_minus8);
+ avcConfigurationBox.setChromaFormat(seqParameterSet.chroma_format_idc.getId());
+ avcConfigurationBox.setConfigurationVersion(1);
+ avcConfigurationBox.setLengthSizeMinusOne(3);
+ avcConfigurationBox.setProfileCompatibility(seqParameterSetList.get(0)[1]);
+
+ visualSampleEntry.addBox(avcConfigurationBox);
+ sampleDescriptionBox.addBox(visualSampleEntry);
+
+ trackMetaData.setCreationTime(new Date());
+ trackMetaData.setModificationTime(new Date());
+ trackMetaData.setLanguage(lang);
+ trackMetaData.setTimescale(timescale);
+ trackMetaData.setWidth(width);
+ trackMetaData.setHeight(height);
+ }
+
+ public SampleDescriptionBox getSampleDescriptionBox() {
+ return sampleDescriptionBox;
+ }
+
+ public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
+ return stts;
+ }
+
+ public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
+ return ctts;
+ }
+
+ public long[] getSyncSamples() {
+ long[] returns = new long[stss.size()];
+ for (int i = 0; i < stss.size(); i++) {
+ returns[i] = stss.get(i);
+ }
+ return returns;
+ }
+
+ public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
+ return sdtp;
+ }
+
+ public TrackMetaData getTrackMetaData() {
+ return trackMetaData;
+ }
+
+ public String getHandler() {
+ return "vide";
+ }
+
+ public List<ByteBuffer> getSamples() {
+ return samples;
+ }
+
+ public AbstractMediaHeaderBox getMediaHeaderBox() {
+ return new VideoMediaHeaderBox();
+ }
+
+ public SubSampleInformationBox getSubsampleInformationBox() {
+ return null;
+ }
+
+ private boolean readVariables() {
+ width = (seqParameterSet.pic_width_in_mbs_minus1 + 1) * 16;
+ int mult = 2;
+ if (seqParameterSet.frame_mbs_only_flag) {
+ mult = 1;
+ }
+ height = 16 * (seqParameterSet.pic_height_in_map_units_minus1 + 1) * mult;
+ if (seqParameterSet.frame_cropping_flag) {
+ int chromaArrayType = 0;
+ if (seqParameterSet.residual_color_transform_flag == false) {
+ chromaArrayType = seqParameterSet.chroma_format_idc.getId();
+ }
+ int cropUnitX = 1;
+ int cropUnitY = mult;
+ if (chromaArrayType != 0) {
+ cropUnitX = seqParameterSet.chroma_format_idc.getSubWidth();
+ cropUnitY = seqParameterSet.chroma_format_idc.getSubHeight() * mult;
+ }
+
+ width -= cropUnitX * (seqParameterSet.frame_crop_left_offset + seqParameterSet.frame_crop_right_offset);
+ height -= cropUnitY * (seqParameterSet.frame_crop_top_offset + seqParameterSet.frame_crop_bottom_offset);
+ }
+ return true;
+ }
+
+ private boolean findNextStartcode() throws IOException {
+ byte[] test = new byte[]{-1, -1, -1, -1};
+
+ int c;
+ while ((c = reader.read()) != -1) {
+ test[0] = test[1];
+ test[1] = test[2];
+ test[2] = test[3];
+ test[3] = (byte) c;
+ if (test[0] == 0 && test[1] == 0 && test[2] == 0 && test[3] == 1) {
+ prevScSize = currentScSize;
+ currentScSize = 4;
+ return true;
+ }
+ if (test[0] == 0 && test[1] == 0 && test[2] == 1) {
+ prevScSize = currentScSize;
+ currentScSize = 3;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private enum NALActions {
+ IGNORE, BUFFER, STORE, END
+ }
+
+ private boolean readSamples() throws IOException {
+ if (readSamples) {
+ return true;
+ }
+
+ readSamples = true;
+
+
+ findNextStartcode();
+ reader.mark();
+ long pos = reader.getPos();
+
+ ArrayList<byte[]> buffered = new ArrayList<byte[]>();
+
+ int frameNr = 0;
+
+ while (findNextStartcode()) {
+ long newpos = reader.getPos();
+ int size = (int) (newpos - pos - prevScSize);
+ reader.reset();
+ byte[] data = new byte[size ];
+ reader.read(data);
+ int type = data[0];
+ int nal_ref_idc = (type >> 5) & 3;
+ int nal_unit_type = type & 0x1f;
+ LOG.fine("Found startcode at " + (pos -4) + " Type: " + nal_unit_type + " ref idc: " + nal_ref_idc + " (size " + size + ")");
+ NALActions action = handleNALUnit(nal_ref_idc, nal_unit_type, data);
+ switch (action) {
+ case IGNORE:
+ break;
+
+ case BUFFER:
+ buffered.add(data);
+ break;
+
+ case STORE:
+ int stdpValue = 22;
+ frameNr++;
+ buffered.add(data);
+ ByteBuffer bb = createSample(buffered);
+ boolean IdrPicFlag = false;
+ if (nal_unit_type == 5) {
+ stdpValue += 16;
+ IdrPicFlag = true;
+ }
+ ByteArrayInputStream bs = cleanBuffer(buffered.get(buffered.size() - 1));
+ SliceHeader sh = new SliceHeader(bs, seqParameterSet, pictureParameterSet, IdrPicFlag);
+ if (sh.slice_type == SliceHeader.SliceType.B) {
+ stdpValue += 4;
+ }
+ LOG.fine("Adding sample with size " + bb.capacity() + " and header " + sh);
+ buffered.clear();
+ samples.add(bb);
+ stts.add(new TimeToSampleBox.Entry(1, frametick));
+ if (nal_unit_type == 5) { // IDR Picture
+ stss.add(frameNr);
+ }
+ if (seiMessage.n_frames == 0) {
+ frameNrInGop = 0;
+ }
+ int offset = 0;
+ if (seiMessage.clock_timestamp_flag) {
+ offset = seiMessage.n_frames - frameNrInGop;
+ } else if (seiMessage.removal_delay_flag) {
+ offset = seiMessage.dpb_removal_delay / 2;
+ }
+ ctts.add(new CompositionTimeToSample.Entry(1, offset * frametick));
+ sdtp.add(new SampleDependencyTypeBox.Entry(stdpValue));
+ frameNrInGop++;
+ break;
+
+ case END:
+ return true;
+
+
+ }
+ pos = newpos;
+ reader.seek(currentScSize);
+ reader.mark();
+ }
+ return true;
+ }
+
+ private ByteBuffer createSample(List<byte[]> buffers) {
+ int outsize = 0;
+ for (int i = 0; i < buffers.size(); i++) {
+ outsize += buffers.get(i).length + 4;
+ }
+ byte[] output = new byte[outsize];
+
+ ByteBuffer bb = ByteBuffer.wrap(output);
+ for (int i = 0; i < buffers.size(); i++) {
+ bb.putInt(buffers.get(i).length);
+ bb.put(buffers.get(i));
+ }
+ bb.rewind();
+ return bb;
+ }
+
+ private ByteArrayInputStream cleanBuffer(byte[] data) {
+ byte[] output = new byte[data.length];
+ int inPos = 0;
+ int outPos = 0;
+ while (inPos < data.length) {
+ if (data[inPos] == 0 && data[inPos + 1] == 0 && data[inPos + 2] == 3) {
+ output[outPos] = 0;
+ output[outPos + 1] = 0;
+ inPos += 3;
+ outPos += 2;
+ } else {
+ output[outPos] = data[inPos];
+ inPos++;
+ outPos++;
+ }
+ }
+ return new ByteArrayInputStream(output, 0, outPos);
+ }
+
+ private NALActions handleNALUnit(int nal_ref_idc, int nal_unit_type, byte[] data) throws IOException {
+ NALActions action;
+ switch (nal_unit_type) {
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ action = NALActions.STORE; // Will only work in single slice per frame mode!
+ break;
+
+ case 6:
+ seiMessage = new SEIMessage(cleanBuffer(data), seqParameterSet);
+ action = NALActions.BUFFER;
+ break;
+
+ case 9:
+// printAccessUnitDelimiter(data);
+ int type = data[1] >> 5;
+ LOG.fine("Access unit delimiter type: " + type);
+ action = NALActions.BUFFER;
+ break;
+
+
+ case 7:
+ if (seqParameterSet == null) {
+ ByteArrayInputStream is = cleanBuffer(data);
+ is.read();
+ seqParameterSet = SeqParameterSet.read(is);
+ seqParameterSetList.add(data);
+ configureFramerate();
+ }
+ action = NALActions.IGNORE;
+ break;
+
+ case 8:
+ if (pictureParameterSet == null) {
+ ByteArrayInputStream is = new ByteArrayInputStream(data);
+ is.read();
+ pictureParameterSet = PictureParameterSet.read(is);
+ pictureParameterSetList.add(data);
+ }
+ action = NALActions.IGNORE;
+ break;
+
+ case 10:
+ case 11:
+ action = NALActions.END;
+ break;
+
+ default:
+ System.err.println("Unknown NAL unit type: " + nal_unit_type);
+ action = NALActions.IGNORE;
+
+ }
+
+ return action;
+ }
+
+ private void configureFramerate() {
+ if (determineFrameRate) {
+ if (seqParameterSet.vuiParams != null) {
+ timescale = seqParameterSet.vuiParams.time_scale >> 1; // Not sure why, but I found this in several places, and it works...
+ frametick = seqParameterSet.vuiParams.num_units_in_tick;
+ if (timescale == 0 || frametick == 0) {
+ System.err.println("Warning: vuiParams contain invalid values: time_scale: " + timescale + " and frame_tick: " + frametick + ". Setting frame rate to 25fps");
+ timescale = 90000;
+ frametick = 3600;
+ }
+ } else {
+ System.err.println("Warning: Can't determine frame rate. Guessing 25 fps");
+ timescale = 90000;
+ frametick = 3600;
+ }
+ }
+ }
+
+ public void printAccessUnitDelimiter(byte[] data) {
+ LOG.fine("Access unit delimiter: " + (data[1] >> 5));
+ }
+
+ public static class SliceHeader {
+
+ public enum SliceType {
+ P, B, I, SP, SI
+ }
+
+ public int first_mb_in_slice;
+ public SliceType slice_type;
+ public int pic_parameter_set_id;
+ public int colour_plane_id;
+ public int frame_num;
+ public boolean field_pic_flag = false;
+ public boolean bottom_field_flag = false;
+ public int idr_pic_id;
+ public int pic_order_cnt_lsb;
+ public int delta_pic_order_cnt_bottom;
+
+ public SliceHeader(InputStream is, SeqParameterSet sps, PictureParameterSet pps, boolean IdrPicFlag) throws IOException {
+ is.read();
+ CAVLCReader reader = new CAVLCReader(is);
+ first_mb_in_slice = reader.readUE("SliceHeader: first_mb_in_slice");
+ switch (reader.readUE("SliceHeader: slice_type")) {
+ case 0:
+ case 5:
+ slice_type = SliceType.P;
+ break;
+
+ case 1:
+ case 6:
+ slice_type = SliceType.B;
+ break;
+
+ case 2:
+ case 7:
+ slice_type = SliceType.I;
+ break;
+
+ case 3:
+ case 8:
+ slice_type = SliceType.SP;
+ break;
+
+ case 4:
+ case 9:
+ slice_type = SliceType.SI;
+ break;
+
+ }
+ pic_parameter_set_id = reader.readUE("SliceHeader: pic_parameter_set_id");
+ if (sps.residual_color_transform_flag) {
+ colour_plane_id = reader.readU(2, "SliceHeader: colour_plane_id");
+ }
+ frame_num = reader.readU(sps.log2_max_frame_num_minus4 + 4, "SliceHeader: frame_num");
+
+ if (!sps.frame_mbs_only_flag) {
+ field_pic_flag = reader.readBool("SliceHeader: field_pic_flag");
+ if (field_pic_flag) {
+ bottom_field_flag = reader.readBool("SliceHeader: bottom_field_flag");
+ }
+ }
+ if (IdrPicFlag) {
+ idr_pic_id = reader.readUE("SliceHeader: idr_pic_id");
+ if (sps.pic_order_cnt_type == 0) {
+ pic_order_cnt_lsb = reader.readU(sps.log2_max_pic_order_cnt_lsb_minus4 + 4, "SliceHeader: pic_order_cnt_lsb");
+ if (pps.pic_order_present_flag && !field_pic_flag) {
+ delta_pic_order_cnt_bottom = reader.readSE("SliceHeader: delta_pic_order_cnt_bottom");
+ }
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "SliceHeader{" +
+ "first_mb_in_slice=" + first_mb_in_slice +
+ ", slice_type=" + slice_type +
+ ", pic_parameter_set_id=" + pic_parameter_set_id +
+ ", colour_plane_id=" + colour_plane_id +
+ ", frame_num=" + frame_num +
+ ", field_pic_flag=" + field_pic_flag +
+ ", bottom_field_flag=" + bottom_field_flag +
+ ", idr_pic_id=" + idr_pic_id +
+ ", pic_order_cnt_lsb=" + pic_order_cnt_lsb +
+ ", delta_pic_order_cnt_bottom=" + delta_pic_order_cnt_bottom +
+ '}';
+ }
+ }
+
+ private class ReaderWrapper {
+ private InputStream inputStream;
+ private long pos = 0;
+
+ private long markPos = 0;
+
+
+ private ReaderWrapper(InputStream inputStream) {
+ this.inputStream = inputStream;
+ }
+
+ int read() throws IOException {
+ pos++;
+ return inputStream.read();
+ }
+
+ long read(byte[] data) throws IOException {
+ long read = inputStream.read(data);
+ pos += read;
+ return read;
+ }
+
+ long seek(int dist) throws IOException {
+ long seeked = inputStream.skip(dist);
+ pos += seeked;
+ return seeked;
+ }
+
+ public long getPos() {
+ return pos;
+ }
+
+ public void mark() {
+ int i = 1048576;
+ LOG.fine("Marking with " + i + " at " + pos);
+ inputStream.mark(i);
+ markPos = pos;
+ }
+
+
+ public void reset() throws IOException {
+ long diff = pos - markPos;
+ LOG.fine("Resetting to " + markPos + " (pos is " + pos + ") which makes the buffersize " + diff);
+ inputStream.reset();
+ pos = markPos;
+ }
+ }
+
+ public class SEIMessage {
+
+ int payloadType = 0;
+ int payloadSize = 0;
+
+ boolean removal_delay_flag;
+ int cpb_removal_delay;
+ int dpb_removal_delay;
+
+ boolean clock_timestamp_flag;
+ int pic_struct;
+ int ct_type;
+ int nuit_field_based_flag;
+ int counting_type;
+ int full_timestamp_flag;
+ int discontinuity_flag;
+ int cnt_dropped_flag;
+ int n_frames;
+ int seconds_value;
+ int minutes_value;
+ int hours_value;
+ int time_offset_length;
+ int time_offset;
+
+ SeqParameterSet sps;
+
+ public SEIMessage(InputStream is, SeqParameterSet sps) throws IOException {
+ this.sps = sps;
+ is.read();
+ int datasize = is.available();
+ int read = 0;
+ while (read < datasize) {
+ payloadType = 0;
+ payloadSize = 0;
+ int last_payload_type_bytes = is.read();
+ read++;
+ while (last_payload_type_bytes == 0xff) {
+ payloadType += last_payload_type_bytes;
+ last_payload_type_bytes = is.read();
+ read++;
+ }
+ payloadType += last_payload_type_bytes;
+ int last_payload_size_bytes = is.read();
+ read++;
+
+ while (last_payload_size_bytes == 0xff) {
+ payloadSize += last_payload_size_bytes;
+ last_payload_size_bytes = is.read();
+ read++;
+ }
+ payloadSize += last_payload_size_bytes;
+ if (datasize - read >= payloadSize) {
+ if (payloadType == 1) { // pic_timing is what we are interested in!
+ if (sps.vuiParams != null && (sps.vuiParams.nalHRDParams != null || sps.vuiParams.vclHRDParams != null || sps.vuiParams.pic_struct_present_flag)) {
+ byte[] data = new byte[payloadSize];
+ is.read(data);
+ read += payloadSize;
+ CAVLCReader reader = new CAVLCReader(new ByteArrayInputStream(data));
+ if (sps.vuiParams.nalHRDParams != null || sps.vuiParams.vclHRDParams != null) {
+ removal_delay_flag = true;
+ cpb_removal_delay = reader.readU(sps.vuiParams.nalHRDParams.cpb_removal_delay_length_minus1 + 1, "SEI: cpb_removal_delay");
+ dpb_removal_delay = reader.readU(sps.vuiParams.nalHRDParams.dpb_output_delay_length_minus1 + 1, "SEI: dpb_removal_delay");
+ } else {
+ removal_delay_flag = false;
+ }
+ if (sps.vuiParams.pic_struct_present_flag) {
+ pic_struct = reader.readU(4, "SEI: pic_struct");
+ int numClockTS;
+ switch (pic_struct) {
+ case 0:
+ case 1:
+ case 2:
+ default:
+ numClockTS = 1;
+ break;
+
+ case 3:
+ case 4:
+ case 7:
+ numClockTS = 2;
+ break;
+
+ case 5:
+ case 6:
+ case 8:
+ numClockTS = 3;
+ break;
+ }
+ for (int i = 0; i < numClockTS; i++) {
+ clock_timestamp_flag = reader.readBool("pic_timing SEI: clock_timestamp_flag[" + i + "]");
+ if (clock_timestamp_flag) {
+ ct_type = reader.readU(2, "pic_timing SEI: ct_type");
+ nuit_field_based_flag = reader.readU(1, "pic_timing SEI: nuit_field_based_flag");
+ counting_type = reader.readU(5, "pic_timing SEI: counting_type");
+ full_timestamp_flag = reader.readU(1, "pic_timing SEI: full_timestamp_flag");
+ discontinuity_flag = reader.readU(1, "pic_timing SEI: discontinuity_flag");
+ cnt_dropped_flag = reader.readU(1, "pic_timing SEI: cnt_dropped_flag");
+ n_frames = reader.readU(8, "pic_timing SEI: n_frames");
+ if (full_timestamp_flag == 1) {
+ seconds_value = reader.readU(6, "pic_timing SEI: seconds_value");
+ minutes_value = reader.readU(6, "pic_timing SEI: minutes_value");
+ hours_value = reader.readU(5, "pic_timing SEI: hours_value");
+ } else {
+ if (reader.readBool("pic_timing SEI: seconds_flag")) {
+ seconds_value = reader.readU(6, "pic_timing SEI: seconds_value");
+ if (reader.readBool("pic_timing SEI: minutes_flag")) {
+ minutes_value = reader.readU(6, "pic_timing SEI: minutes_value");
+ if (reader.readBool("pic_timing SEI: hours_flag")) {
+ hours_value = reader.readU(5, "pic_timing SEI: hours_value");
+ }
+ }
+ }
+ }
+ if (true) {
+ if (sps.vuiParams.nalHRDParams != null) {
+ time_offset_length = sps.vuiParams.nalHRDParams.time_offset_length;
+ } else if (sps.vuiParams.vclHRDParams != null) {
+ time_offset_length = sps.vuiParams.vclHRDParams.time_offset_length;
+ } else {
+ time_offset_length = 24;
+ }
+ time_offset = reader.readU(24, "pic_timing SEI: time_offset");
+ }
+ }
+ }
+ }
+
+ } else {
+ for (int i = 0; i < payloadSize; i++) {
+ is.read();
+ read++;
+ }
+ }
+ } else {
+ for (int i = 0; i < payloadSize; i++) {
+ is.read();
+ read++;
+ }
+ }
+ } else {
+ read = datasize;
+ }
+ LOG.fine(this.toString());
+ }
+ }
+
+ @Override
+ public String toString() {
+ String out = "SEIMessage{" +
+ "payloadType=" + payloadType +
+ ", payloadSize=" + payloadSize;
+ if (payloadType == 1) {
+ if (sps.vuiParams.nalHRDParams != null || sps.vuiParams.vclHRDParams != null) {
+
+ out += ", cpb_removal_delay=" + cpb_removal_delay +
+ ", dpb_removal_delay=" + dpb_removal_delay;
+ }
+ if (sps.vuiParams.pic_struct_present_flag) {
+ out += ", pic_struct=" + pic_struct;
+ if (clock_timestamp_flag) {
+ out += ", ct_type=" + ct_type +
+ ", nuit_field_based_flag=" + nuit_field_based_flag +
+ ", counting_type=" + counting_type +
+ ", full_timestamp_flag=" + full_timestamp_flag +
+ ", discontinuity_flag=" + discontinuity_flag +
+ ", cnt_dropped_flag=" + cnt_dropped_flag +
+ ", n_frames=" + n_frames +
+ ", seconds_value=" + seconds_value +
+ ", minutes_value=" + minutes_value +
+ ", hours_value=" + hours_value +
+ ", time_offset_length=" + time_offset_length +
+ ", time_offset=" + time_offset;
+ }
+ }
+ }
+ out += '}';
+ return out;
+ }
+ }
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/MultiplyTimeScaleTrack.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/MultiplyTimeScaleTrack.java.svn-base
new file mode 100644
index 0000000..e9a90e4
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/MultiplyTimeScaleTrack.java.svn-base
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.tracks;
+
+import com.coremedia.iso.boxes.*;
+import com.googlecode.mp4parser.authoring.Movie;
+import com.googlecode.mp4parser.authoring.Track;
+import com.googlecode.mp4parser.authoring.TrackMetaData;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import static com.googlecode.mp4parser.util.CastUtils.l2i;
+import static com.googlecode.mp4parser.util.Math.gcd;
+import static com.googlecode.mp4parser.util.Math.lcm;
+import static java.lang.Math.round;
+
+/**
+ * Changes the timescale of a track by wrapping the track.
+ */
+public class MultiplyTimeScaleTrack implements Track {
+ Track source;
+ private int timeScaleFactor;
+
+ public MultiplyTimeScaleTrack(Track source, int timeScaleFactor) {
+ this.source = source;
+ this.timeScaleFactor = timeScaleFactor;
+ }
+
+ public SampleDescriptionBox getSampleDescriptionBox() {
+ return source.getSampleDescriptionBox();
+ }
+
+ public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
+ return adjustTts(source.getDecodingTimeEntries(), timeScaleFactor);
+ }
+
+ public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
+ return adjustCtts(source.getCompositionTimeEntries(), timeScaleFactor);
+ }
+
+ public long[] getSyncSamples() {
+ return source.getSyncSamples();
+ }
+
+ public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
+ return source.getSampleDependencies();
+ }
+
+ public TrackMetaData getTrackMetaData() {
+ TrackMetaData trackMetaData = (TrackMetaData) source.getTrackMetaData().clone();
+ trackMetaData.setTimescale(source.getTrackMetaData().getTimescale() * this.timeScaleFactor);
+ return trackMetaData;
+ }
+
+ public String getHandler() {
+ return source.getHandler();
+ }
+
+ public boolean isEnabled() {
+ return source.isEnabled();
+ }
+
+ public boolean isInMovie() {
+ return source.isInMovie();
+ }
+
+ public boolean isInPreview() {
+ return source.isInPreview();
+ }
+
+ public boolean isInPoster() {
+ return source.isInPoster();
+ }
+
+ public List<ByteBuffer> getSamples() {
+ return source.getSamples();
+ }
+
+
+ static List<CompositionTimeToSample.Entry> adjustCtts(List<CompositionTimeToSample.Entry> source, int timeScaleFactor) {
+ if (source != null) {
+ List<CompositionTimeToSample.Entry> entries2 = new ArrayList<CompositionTimeToSample.Entry>(source.size());
+ for (CompositionTimeToSample.Entry entry : source) {
+ entries2.add(new CompositionTimeToSample.Entry(entry.getCount(), timeScaleFactor * entry.getOffset()));
+ }
+ return entries2;
+ } else {
+ return null;
+ }
+ }
+
+ static List<TimeToSampleBox.Entry> adjustTts(List<TimeToSampleBox.Entry> source, int timeScaleFactor) {
+ LinkedList<TimeToSampleBox.Entry> entries2 = new LinkedList<TimeToSampleBox.Entry>();
+ for (TimeToSampleBox.Entry e : source) {
+ entries2.add(new TimeToSampleBox.Entry(e.getCount(), timeScaleFactor * e.getDelta()));
+ }
+ return entries2;
+ }
+
+ public Box getMediaHeaderBox() {
+ return source.getMediaHeaderBox();
+ }
+
+ public SubSampleInformationBox getSubsampleInformationBox() {
+ return source.getSubsampleInformationBox();
+ }
+
+ @Override
+ public String toString() {
+ return "MultiplyTimeScaleTrack{" +
+ "source=" + source +
+ '}';
+ }
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/QuicktimeTextTrackImpl.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/QuicktimeTextTrackImpl.java.svn-base
new file mode 100644
index 0000000..8efa399
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/QuicktimeTextTrackImpl.java.svn-base
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.tracks;
+
+import com.coremedia.iso.boxes.*;
+import com.coremedia.iso.boxes.sampleentry.TextSampleEntry;
+import com.googlecode.mp4parser.authoring.AbstractTrack;
+import com.googlecode.mp4parser.authoring.TrackMetaData;
+import com.googlecode.mp4parser.boxes.apple.BaseMediaInfoAtom;
+import com.googlecode.mp4parser.boxes.apple.GenericMediaHeaderAtom;
+import com.googlecode.mp4parser.boxes.apple.GenericMediaHeaderTextAtom;
+import com.googlecode.mp4parser.boxes.apple.QuicktimeTextSampleEntry;
+import com.googlecode.mp4parser.boxes.threegpp26245.FontTableBox;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * A Text track as Quicktime Pro would create.
+ */
+public class QuicktimeTextTrackImpl extends AbstractTrack {
+ TrackMetaData trackMetaData = new TrackMetaData();
+ SampleDescriptionBox sampleDescriptionBox;
+ List<Line> subs = new LinkedList<Line>();
+
+ public List<Line> getSubs() {
+ return subs;
+ }
+
+ public QuicktimeTextTrackImpl() {
+ sampleDescriptionBox = new SampleDescriptionBox();
+ QuicktimeTextSampleEntry textTrack = new QuicktimeTextSampleEntry();
+ textTrack.setDataReferenceIndex(1);
+ sampleDescriptionBox.addBox(textTrack);
+
+
+ trackMetaData.setCreationTime(new Date());
+ trackMetaData.setModificationTime(new Date());
+ trackMetaData.setTimescale(1000);
+
+
+ }
+
+
+ public List<ByteBuffer> getSamples() {
+ List<ByteBuffer> samples = new LinkedList<ByteBuffer>();
+ long lastEnd = 0;
+ for (Line sub : subs) {
+ long silentTime = sub.from - lastEnd;
+ if (silentTime > 0) {
+ samples.add(ByteBuffer.wrap(new byte[]{0, 0}));
+ } else if (silentTime < 0) {
+ throw new Error("Subtitle display times may not intersect");
+ }
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(baos);
+ try {
+ dos.writeShort(sub.text.getBytes("UTF-8").length);
+ dos.write(sub.text.getBytes("UTF-8"));
+ dos.close();
+ } catch (IOException e) {
+ throw new Error("VM is broken. Does not support UTF-8");
+ }
+ samples.add(ByteBuffer.wrap(baos.toByteArray()));
+ lastEnd = sub.to;
+ }
+ return samples;
+ }
+
+ public SampleDescriptionBox getSampleDescriptionBox() {
+ return sampleDescriptionBox;
+ }
+
+ public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
+ List<TimeToSampleBox.Entry> stts = new LinkedList<TimeToSampleBox.Entry>();
+ long lastEnd = 0;
+ for (Line sub : subs) {
+ long silentTime = sub.from - lastEnd;
+ if (silentTime > 0) {
+ stts.add(new TimeToSampleBox.Entry(1, silentTime));
+ } else if (silentTime < 0) {
+ throw new Error("Subtitle display times may not intersect");
+ }
+ stts.add(new TimeToSampleBox.Entry(1, sub.to - sub.from));
+ lastEnd = sub.to;
+ }
+ return stts;
+ }
+
+ public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
+ return null;
+ }
+
+ public long[] getSyncSamples() {
+ return null;
+ }
+
+ public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
+ return null;
+ }
+
+ public TrackMetaData getTrackMetaData() {
+ return trackMetaData;
+ }
+
+ public String getHandler() {
+ return "text";
+ }
+
+
+ public static class Line {
+ long from;
+ long to;
+ String text;
+
+
+ public Line(long from, long to, String text) {
+ this.from = from;
+ this.to = to;
+ this.text = text;
+ }
+
+ public long getFrom() {
+ return from;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public long getTo() {
+ return to;
+ }
+ }
+
+ public Box getMediaHeaderBox() {
+ GenericMediaHeaderAtom ghmd = new GenericMediaHeaderAtom();
+ ghmd.addBox(new BaseMediaInfoAtom());
+ ghmd.addBox(new GenericMediaHeaderTextAtom());
+ return ghmd;
+ }
+
+ public SubSampleInformationBox getSubsampleInformationBox() {
+ return null;
+ }
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/ReplaceSampleTrack.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/ReplaceSampleTrack.java.svn-base
new file mode 100644
index 0000000..81a129d
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/ReplaceSampleTrack.java.svn-base
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.tracks;
+
+import com.coremedia.iso.boxes.*;
+import com.googlecode.mp4parser.authoring.AbstractTrack;
+import com.googlecode.mp4parser.authoring.Track;
+import com.googlecode.mp4parser.authoring.TrackMetaData;
+
+import java.nio.ByteBuffer;
+import java.util.AbstractList;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Generates a Track where a single sample has been replaced by a given <code>ByteBuffer</code>.
+ */
+
+public class ReplaceSampleTrack extends AbstractTrack {
+ Track origTrack;
+ private long sampleNumber;
+ private ByteBuffer sampleContent;
+ private List<ByteBuffer> samples;
+
+ public ReplaceSampleTrack(Track origTrack, long sampleNumber, ByteBuffer content) {
+ this.origTrack = origTrack;
+ this.sampleNumber = sampleNumber;
+ this.sampleContent = content;
+ this.samples = new ReplaceASingleEntryList();
+
+ }
+
+ public List<ByteBuffer> getSamples() {
+ return samples;
+ }
+
+ public SampleDescriptionBox getSampleDescriptionBox() {
+ return origTrack.getSampleDescriptionBox();
+ }
+
+ public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
+ return origTrack.getDecodingTimeEntries();
+
+ }
+
+ public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
+ return origTrack.getCompositionTimeEntries();
+
+ }
+
+ synchronized public long[] getSyncSamples() {
+ return origTrack.getSyncSamples();
+ }
+
+ public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
+ return origTrack.getSampleDependencies();
+ }
+
+ public TrackMetaData getTrackMetaData() {
+ return origTrack.getTrackMetaData();
+ }
+
+ public String getHandler() {
+ return origTrack.getHandler();
+ }
+
+ public Box getMediaHeaderBox() {
+ return origTrack.getMediaHeaderBox();
+ }
+
+ public SubSampleInformationBox getSubsampleInformationBox() {
+ return origTrack.getSubsampleInformationBox();
+ }
+
+ private class ReplaceASingleEntryList extends AbstractList<ByteBuffer> {
+ @Override
+ public ByteBuffer get(int index) {
+ if (ReplaceSampleTrack.this.sampleNumber == index) {
+ return ReplaceSampleTrack.this.sampleContent;
+ } else {
+ return ReplaceSampleTrack.this.origTrack.getSamples().get(index);
+ }
+ }
+
+ @Override
+ public int size() {
+ return ReplaceSampleTrack.this.origTrack.getSamples().size();
+ }
+ }
+
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/SilenceTrackImpl.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/SilenceTrackImpl.java.svn-base
new file mode 100644
index 0000000..f74ab3c
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/SilenceTrackImpl.java.svn-base
@@ -0,0 +1,98 @@
+package com.googlecode.mp4parser.authoring.tracks;
+
+import com.coremedia.iso.boxes.*;
+import com.googlecode.mp4parser.authoring.Mp4TrackImpl;
+import com.googlecode.mp4parser.authoring.Track;
+import com.googlecode.mp4parser.authoring.TrackMetaData;
+
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * This is just a basic idea how things could work but they don't.
+ */
+public class SilenceTrackImpl implements Track {
+ Track source;
+
+ List<ByteBuffer> samples = new LinkedList<ByteBuffer>();
+ TimeToSampleBox.Entry entry;
+
+ public SilenceTrackImpl(Track ofType, long ms) {
+ source = ofType;
+ if ("mp4a".equals(ofType.getSampleDescriptionBox().getSampleEntry().getType())) {
+ long numFrames = getTrackMetaData().getTimescale() * ms / 1000 / 1024;
+ long standZeit = getTrackMetaData().getTimescale() * ms / numFrames / 1000;
+ entry = new TimeToSampleBox.Entry(numFrames, standZeit);
+
+
+ while (numFrames-- > 0) {
+ samples.add((ByteBuffer) ByteBuffer.wrap(new byte[]{
+ 0x21, 0x10, 0x04, 0x60, (byte) 0x8c, 0x1c,
+ }).rewind());
+ }
+
+ } else {
+ throw new RuntimeException("Tracks of type " + ofType.getClass().getSimpleName() + " are not supported");
+ }
+ }
+
+ public SampleDescriptionBox getSampleDescriptionBox() {
+ return source.getSampleDescriptionBox();
+ }
+
+ public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
+ return Collections.singletonList(entry);
+
+ }
+
+ public TrackMetaData getTrackMetaData() {
+ return source.getTrackMetaData();
+ }
+
+ public String getHandler() {
+ return source.getHandler();
+ }
+
+ public boolean isEnabled() {
+ return source.isEnabled();
+ }
+
+ public boolean isInMovie() {
+ return source.isInMovie();
+ }
+
+ public boolean isInPreview() {
+ return source.isInPreview();
+ }
+
+ public boolean isInPoster() {
+ return source.isInPoster();
+ }
+
+ public List<ByteBuffer> getSamples() {
+ return samples;
+ }
+
+ public Box getMediaHeaderBox() {
+ return source.getMediaHeaderBox();
+ }
+
+ public SubSampleInformationBox getSubsampleInformationBox() {
+ return null;
+ }
+
+ public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
+ return null;
+ }
+
+ public long[] getSyncSamples() {
+ return null;
+ }
+
+ public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
+ return null;
+ }
+
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/TextTrackImpl.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/TextTrackImpl.java.svn-base
new file mode 100644
index 0000000..3bae143
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/TextTrackImpl.java.svn-base
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.tracks;
+
+import com.coremedia.iso.boxes.*;
+import com.coremedia.iso.boxes.sampleentry.TextSampleEntry;
+import com.googlecode.mp4parser.authoring.AbstractTrack;
+import com.googlecode.mp4parser.authoring.TrackMetaData;
+import com.googlecode.mp4parser.boxes.threegpp26245.FontTableBox;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ *
+ */
+public class TextTrackImpl extends AbstractTrack {
+ TrackMetaData trackMetaData = new TrackMetaData();
+ SampleDescriptionBox sampleDescriptionBox;
+ List<Line> subs = new LinkedList<Line>();
+
+ public List<Line> getSubs() {
+ return subs;
+ }
+
+ public TextTrackImpl() {
+ sampleDescriptionBox = new SampleDescriptionBox();
+ TextSampleEntry tx3g = new TextSampleEntry("tx3g");
+ tx3g.setDataReferenceIndex(1);
+ tx3g.setStyleRecord(new TextSampleEntry.StyleRecord());
+ tx3g.setBoxRecord(new TextSampleEntry.BoxRecord());
+ sampleDescriptionBox.addBox(tx3g);
+
+ FontTableBox ftab = new FontTableBox();
+ ftab.setEntries(Collections.singletonList(new FontTableBox.FontRecord(1, "Serif")));
+
+ tx3g.addBox(ftab);
+
+
+ trackMetaData.setCreationTime(new Date());
+ trackMetaData.setModificationTime(new Date());
+ trackMetaData.setTimescale(1000); // Text tracks use millieseconds
+
+
+ }
+
+
+ public List<ByteBuffer> getSamples() {
+ List<ByteBuffer> samples = new LinkedList<ByteBuffer>();
+ long lastEnd = 0;
+ for (Line sub : subs) {
+ long silentTime = sub.from - lastEnd;
+ if (silentTime > 0) {
+ samples.add(ByteBuffer.wrap(new byte[]{0, 0}));
+ } else if (silentTime < 0) {
+ throw new Error("Subtitle display times may not intersect");
+ }
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(baos);
+ try {
+ dos.writeShort(sub.text.getBytes("UTF-8").length);
+ dos.write(sub.text.getBytes("UTF-8"));
+ dos.close();
+ } catch (IOException e) {
+ throw new Error("VM is broken. Does not support UTF-8");
+ }
+ samples.add(ByteBuffer.wrap(baos.toByteArray()));
+ lastEnd = sub.to;
+ }
+ return samples;
+ }
+
+ public SampleDescriptionBox getSampleDescriptionBox() {
+ return sampleDescriptionBox;
+ }
+
+ public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
+ List<TimeToSampleBox.Entry> stts = new LinkedList<TimeToSampleBox.Entry>();
+ long lastEnd = 0;
+ for (Line sub : subs) {
+ long silentTime = sub.from - lastEnd;
+ if (silentTime > 0) {
+ stts.add(new TimeToSampleBox.Entry(1, silentTime));
+ } else if (silentTime < 0) {
+ throw new Error("Subtitle display times may not intersect");
+ }
+ stts.add(new TimeToSampleBox.Entry(1, sub.to - sub.from));
+ lastEnd = sub.to;
+ }
+ return stts;
+ }
+
+ public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
+ return null;
+ }
+
+ public long[] getSyncSamples() {
+ return null;
+ }
+
+ public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
+ return null;
+ }
+
+ public TrackMetaData getTrackMetaData() {
+ return trackMetaData;
+ }
+
+ public String getHandler() {
+ return "sbtl";
+ }
+
+
+ public static class Line {
+ long from;
+ long to;
+ String text;
+
+
+ public Line(long from, long to, String text) {
+ this.from = from;
+ this.to = to;
+ this.text = text;
+ }
+
+ public long getFrom() {
+ return from;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public long getTo() {
+ return to;
+ }
+ }
+
+ public AbstractMediaHeaderBox getMediaHeaderBox() {
+ return new NullMediaHeaderBox();
+ }
+
+ public SubSampleInformationBox getSubsampleInformationBox() {
+ return null;
+ }
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/AACTrackImpl.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/AACTrackImpl.java
new file mode 100644
index 0000000..df51a1a
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/AACTrackImpl.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright 2012 castLabs GmbH, Berlin
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.tracks;
+
+import com.coremedia.iso.boxes.*;
+import com.coremedia.iso.boxes.sampleentry.AudioSampleEntry;
+import com.googlecode.mp4parser.authoring.AbstractTrack;
+import com.googlecode.mp4parser.authoring.TrackMetaData;
+import com.googlecode.mp4parser.boxes.AC3SpecificBox;
+import com.googlecode.mp4parser.boxes.mp4.ESDescriptorBox;
+import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.*;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.*;
+
+/**
+ */
+public class AACTrackImpl extends AbstractTrack {
+ public static Map<Integer, Integer> samplingFrequencyIndexMap = new HashMap<Integer, Integer>();
+
+ static {
+ samplingFrequencyIndexMap.put(96000, 0);
+ samplingFrequencyIndexMap.put(88200, 1);
+ samplingFrequencyIndexMap.put(64000, 2);
+ samplingFrequencyIndexMap.put(48000, 3);
+ samplingFrequencyIndexMap.put(44100, 4);
+ samplingFrequencyIndexMap.put(32000, 5);
+ samplingFrequencyIndexMap.put(24000, 6);
+ samplingFrequencyIndexMap.put(22050, 7);
+ samplingFrequencyIndexMap.put(16000, 8);
+ samplingFrequencyIndexMap.put(12000, 9);
+ samplingFrequencyIndexMap.put(11025, 10);
+ samplingFrequencyIndexMap.put(8000, 11);
+ samplingFrequencyIndexMap.put(0x0, 96000);
+ samplingFrequencyIndexMap.put(0x1, 88200);
+ samplingFrequencyIndexMap.put(0x2, 64000);
+ samplingFrequencyIndexMap.put(0x3, 48000);
+ samplingFrequencyIndexMap.put(0x4, 44100);
+ samplingFrequencyIndexMap.put(0x5, 32000);
+ samplingFrequencyIndexMap.put(0x6, 24000);
+ samplingFrequencyIndexMap.put(0x7, 22050);
+ samplingFrequencyIndexMap.put(0x8, 16000);
+ samplingFrequencyIndexMap.put(0x9, 12000);
+ samplingFrequencyIndexMap.put(0xa, 11025);
+ samplingFrequencyIndexMap.put(0xb, 8000);
+ }
+
+ TrackMetaData trackMetaData = new TrackMetaData();
+ SampleDescriptionBox sampleDescriptionBox;
+
+ int samplerate;
+ int bitrate;
+ int channelCount;
+ int channelconfig;
+
+ int bufferSizeDB;
+ long maxBitRate;
+ long avgBitRate;
+
+ private BufferedInputStream inputStream;
+ private List<ByteBuffer> samples;
+ boolean readSamples = false;
+ List<TimeToSampleBox.Entry> stts;
+ private String lang = "und";
+
+
+ public AACTrackImpl(InputStream inputStream, String lang) throws IOException {
+ this.lang = lang;
+ parse(inputStream);
+ }
+
+ public AACTrackImpl(InputStream inputStream) throws IOException {
+ parse(inputStream);
+ }
+
+ private void parse(InputStream inputStream) throws IOException {
+ this.inputStream = new BufferedInputStream(inputStream);
+ stts = new LinkedList<TimeToSampleBox.Entry>();
+
+ if (!readVariables()) {
+ throw new IOException();
+ }
+
+ samples = new LinkedList<ByteBuffer>();
+ if (!readSamples()) {
+ throw new IOException();
+ }
+
+ double packetsPerSecond = (double)samplerate / 1024.0;
+ double duration = samples.size() / packetsPerSecond;
+
+ long dataSize = 0;
+ LinkedList<Integer> queue = new LinkedList<Integer>();
+ for (int i = 0; i < samples.size(); i++) {
+ int size = samples.get(i).capacity();
+ dataSize += size;
+ queue.add(size);
+ while (queue.size() > packetsPerSecond) {
+ queue.pop();
+ }
+ if (queue.size() == (int) packetsPerSecond) {
+ int currSize = 0;
+ for (int j = 0 ; j < queue.size(); j++) {
+ currSize += queue.get(j);
+ }
+ double currBitrate = 8.0 * currSize / queue.size() * packetsPerSecond;
+ if (currBitrate > maxBitRate) {
+ maxBitRate = (int)currBitrate;
+ }
+ }
+ }
+
+ avgBitRate = (int) (8 * dataSize / duration);
+
+ bufferSizeDB = 1536; /* TODO: Calcultate this somehow! */
+
+ sampleDescriptionBox = new SampleDescriptionBox();
+ AudioSampleEntry audioSampleEntry = new AudioSampleEntry("mp4a");
+ audioSampleEntry.setChannelCount(2);
+ audioSampleEntry.setSampleRate(samplerate);
+ audioSampleEntry.setDataReferenceIndex(1);
+ audioSampleEntry.setSampleSize(16);
+
+
+ ESDescriptorBox esds = new ESDescriptorBox();
+ ESDescriptor descriptor = new ESDescriptor();
+ descriptor.setEsId(0);
+
+ SLConfigDescriptor slConfigDescriptor = new SLConfigDescriptor();
+ slConfigDescriptor.setPredefined(2);
+ descriptor.setSlConfigDescriptor(slConfigDescriptor);
+
+ DecoderConfigDescriptor decoderConfigDescriptor = new DecoderConfigDescriptor();
+ decoderConfigDescriptor.setObjectTypeIndication(0x40);
+ decoderConfigDescriptor.setStreamType(5);
+ decoderConfigDescriptor.setBufferSizeDB(bufferSizeDB);
+ decoderConfigDescriptor.setMaxBitRate(maxBitRate);
+ decoderConfigDescriptor.setAvgBitRate(avgBitRate);
+
+ AudioSpecificConfig audioSpecificConfig = new AudioSpecificConfig();
+ audioSpecificConfig.setAudioObjectType(2); // AAC LC
+ audioSpecificConfig.setSamplingFrequencyIndex(samplingFrequencyIndexMap.get(samplerate));
+ audioSpecificConfig.setChannelConfiguration(channelconfig);
+ decoderConfigDescriptor.setAudioSpecificInfo(audioSpecificConfig);
+
+ descriptor.setDecoderConfigDescriptor(decoderConfigDescriptor);
+
+ ByteBuffer data = descriptor.serialize();
+ esds.setData(data);
+ audioSampleEntry.addBox(esds);
+ sampleDescriptionBox.addBox(audioSampleEntry);
+
+ trackMetaData.setCreationTime(new Date());
+ trackMetaData.setModificationTime(new Date());
+ trackMetaData.setLanguage(lang);
+ trackMetaData.setTimescale(samplerate); // Audio tracks always use samplerate as timescale
+ }
+
+ public SampleDescriptionBox getSampleDescriptionBox() {
+ return sampleDescriptionBox;
+ }
+
+ public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
+ return stts;
+ }
+
+ public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
+ return null;
+ }
+
+ public long[] getSyncSamples() {
+ return null;
+ }
+
+ public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
+ return null;
+ }
+
+ public TrackMetaData getTrackMetaData() {
+ return trackMetaData;
+ }
+
+ public String getHandler() {
+ return "soun";
+ }
+
+ public List<ByteBuffer> getSamples() {
+ return samples;
+ }
+
+ public Box getMediaHeaderBox() {
+ return new SoundMediaHeaderBox();
+ }
+
+ public SubSampleInformationBox getSubsampleInformationBox() {
+ return null;
+ }
+
+ private boolean readVariables() throws IOException {
+ byte[] data = new byte[100];
+ inputStream.mark(100);
+ if (100 != inputStream.read(data, 0, 100)) {
+ return false;
+ }
+ inputStream.reset(); // Rewind
+ ByteBuffer bb = ByteBuffer.wrap(data);
+ BitReaderBuffer brb = new BitReaderBuffer(bb);
+ int syncword = brb.readBits(12);
+ if (syncword != 0xfff) {
+ return false;
+ }
+ int id = brb.readBits(1);
+ int layer = brb.readBits(2);
+ int protectionAbsent = brb.readBits(1);
+ int profile = brb.readBits(2);
+ samplerate = samplingFrequencyIndexMap.get(brb.readBits(4));
+ brb.readBits(1);
+ channelconfig = brb.readBits(3);
+ int original = brb.readBits(1);
+ int home = brb.readBits(1);
+ int emphasis = brb.readBits(2);
+
+ return true;
+ }
+
+ private boolean readSamples() throws IOException {
+ if (readSamples) {
+ return true;
+ }
+
+ readSamples = true;
+ byte[] header = new byte[15];
+ boolean ret = false;
+ inputStream.mark(15);
+ while (-1 != inputStream.read(header)) {
+ ret = true;
+ ByteBuffer bb = ByteBuffer.wrap(header);
+ inputStream.reset();
+ BitReaderBuffer brb = new BitReaderBuffer(bb);
+ int syncword = brb.readBits(12);
+ if (syncword != 0xfff) {
+ return false;
+ }
+ brb.readBits(3);
+ int protectionAbsent = brb.readBits(1);
+ brb.readBits(14);
+ int frameSize = brb.readBits(13);
+ int bufferFullness = brb.readBits(11);
+ int noBlocks = brb.readBits(2);
+ int used = (int) Math.ceil(brb.getPosition() / 8.0);
+ if (protectionAbsent == 0) {
+ used += 2;
+ }
+ inputStream.skip(used);
+ frameSize -= used;
+// System.out.println("Size: " + frameSize + " fullness: " + bufferFullness + " no blocks: " + noBlocks);
+ byte[] data = new byte[frameSize];
+ inputStream.read(data);
+ samples.add(ByteBuffer.wrap(data));
+ stts.add(new TimeToSampleBox.Entry(1, 1024));
+ inputStream.mark(15);
+ }
+ return ret;
+ }
+
+ @Override
+ public String toString() {
+ return "AACTrackImpl{" +
+ "samplerate=" + samplerate +
+ ", bitrate=" + bitrate +
+ ", channelCount=" + channelCount +
+ ", channelconfig=" + channelconfig +
+ '}';
+ }
+}
+
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/AC3TrackImpl.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/AC3TrackImpl.java
new file mode 100644
index 0000000..5e5b2cd
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/AC3TrackImpl.java
@@ -0,0 +1,513 @@
+package com.googlecode.mp4parser.authoring.tracks;
+
+import com.coremedia.iso.boxes.*;
+import com.coremedia.iso.boxes.sampleentry.AudioSampleEntry;
+import com.googlecode.mp4parser.authoring.AbstractTrack;
+import com.googlecode.mp4parser.authoring.TrackMetaData;
+import com.googlecode.mp4parser.boxes.AC3SpecificBox;
+import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.BitReaderBuffer;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+public class AC3TrackImpl extends AbstractTrack {
+ TrackMetaData trackMetaData = new TrackMetaData();
+ SampleDescriptionBox sampleDescriptionBox;
+
+ int samplerate;
+ int bitrate;
+ int channelCount;
+
+ int fscod;
+ int bsid;
+ int bsmod;
+ int acmod;
+ int lfeon;
+ int frmsizecod;
+
+ int frameSize;
+ int[][][][] bitRateAndFrameSizeTable;
+
+ private InputStream inputStream;
+ private List<ByteBuffer> samples;
+ boolean readSamples = false;
+ List<TimeToSampleBox.Entry> stts;
+ private String lang = "und";
+
+ public AC3TrackImpl(InputStream fin, String lang) throws IOException {
+ this.lang = lang;
+ parse(fin);
+ }
+
+ public AC3TrackImpl(InputStream fin) throws IOException {
+ parse(fin);
+ }
+
+ private void parse(InputStream fin) throws IOException {
+ inputStream = fin;
+ bitRateAndFrameSizeTable = new int[19][2][3][2];
+ stts = new LinkedList<TimeToSampleBox.Entry>();
+ initBitRateAndFrameSizeTable();
+ if (!readVariables()) {
+ throw new IOException();
+ }
+
+ sampleDescriptionBox = new SampleDescriptionBox();
+ AudioSampleEntry audioSampleEntry = new AudioSampleEntry("ac-3");
+ audioSampleEntry.setChannelCount(2); // According to ETSI TS 102 366 Annex F
+ audioSampleEntry.setSampleRate(samplerate);
+ audioSampleEntry.setDataReferenceIndex(1);
+ audioSampleEntry.setSampleSize(16);
+
+ AC3SpecificBox ac3 = new AC3SpecificBox();
+ ac3.setAcmod(acmod);
+ ac3.setBitRateCode(frmsizecod >> 1);
+ ac3.setBsid(bsid);
+ ac3.setBsmod(bsmod);
+ ac3.setFscod(fscod);
+ ac3.setLfeon(lfeon);
+ ac3.setReserved(0);
+
+ audioSampleEntry.addBox(ac3);
+ sampleDescriptionBox.addBox(audioSampleEntry);
+
+ trackMetaData.setCreationTime(new Date());
+ trackMetaData.setModificationTime(new Date());
+ trackMetaData.setLanguage(lang);
+ trackMetaData.setTimescale(samplerate); // Audio tracks always use samplerate as timescale
+
+ samples = new LinkedList<ByteBuffer>();
+ if (!readSamples()) {
+ throw new IOException();
+ }
+ }
+
+
+ public List<ByteBuffer> getSamples() {
+
+ return samples;
+ }
+
+ public SampleDescriptionBox getSampleDescriptionBox() {
+ return sampleDescriptionBox;
+ }
+
+ public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
+ return stts;
+ }
+
+ public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
+ return null;
+ }
+
+ public long[] getSyncSamples() {
+ return null;
+ }
+
+ public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
+ return null;
+ }
+
+ public TrackMetaData getTrackMetaData() {
+ return trackMetaData;
+ }
+
+ public String getHandler() {
+ return "soun";
+ }
+
+ public Box getMediaHeaderBox() {
+ return new SoundMediaHeaderBox();
+ }
+
+ public SubSampleInformationBox getSubsampleInformationBox() {
+ return null;
+ }
+
+ private boolean readVariables() throws IOException {
+ byte[] data = new byte[100];
+ inputStream.mark(100);
+ if (100 != inputStream.read(data, 0, 100)) {
+ return false;
+ }
+ inputStream.reset(); // Rewind
+ ByteBuffer bb = ByteBuffer.wrap(data);
+ BitReaderBuffer brb = new BitReaderBuffer(bb);
+ int syncword = brb.readBits(16);
+ if (syncword != 0xb77) {
+ return false;
+ }
+ brb.readBits(16); // CRC-1
+ fscod = brb.readBits(2);
+
+ switch (fscod) {
+ case 0:
+ samplerate = 48000;
+ break;
+
+ case 1:
+ samplerate = 44100;
+ break;
+
+ case 2:
+ samplerate = 32000;
+ break;
+
+ case 3:
+ samplerate = 0;
+ break;
+
+ }
+ if (samplerate == 0) {
+ return false;
+ }
+
+ frmsizecod = brb.readBits(6);
+
+ if (!calcBitrateAndFrameSize(frmsizecod)) {
+ return false;
+ }
+
+ if (frameSize == 0) {
+ return false;
+ }
+ bsid = brb.readBits(5);
+ bsmod = brb.readBits(3);
+ acmod = brb.readBits(3);
+
+ if (bsid == 9) {
+ samplerate /= 2;
+ } else if (bsid != 8 && bsid != 6) {
+ return false;
+ }
+
+ if ((acmod != 1) && ((acmod & 1) == 1)) {
+ brb.readBits(2);
+ }
+
+ if (0 != (acmod & 4)) {
+ brb.readBits(2);
+ }
+
+ if (acmod == 2) {
+ brb.readBits(2);
+ }
+
+ switch (acmod) {
+ case 0:
+ channelCount = 2;
+ break;
+
+ case 1:
+ channelCount = 1;
+ break;
+
+ case 2:
+ channelCount = 2;
+ break;
+
+ case 3:
+ channelCount = 3;
+ break;
+
+ case 4:
+ channelCount = 3;
+ break;
+
+ case 5:
+ channelCount = 4;
+ break;
+
+ case 6:
+ channelCount = 4;
+ break;
+
+ case 7:
+ channelCount = 5;
+ break;
+
+ }
+
+ lfeon = brb.readBits(1);
+
+ if (lfeon == 1) {
+ channelCount++;
+ }
+ return true;
+ }
+
+ private boolean calcBitrateAndFrameSize(int code) {
+ int frmsizecode = code >>> 1;
+ int flag = code & 1;
+ if (frmsizecode > 18 || flag > 1 || fscod > 2) {
+ return false;
+ }
+ bitrate = bitRateAndFrameSizeTable[frmsizecode][flag][fscod][0];
+ frameSize = 2 * bitRateAndFrameSizeTable[frmsizecode][flag][fscod][1];
+ return true;
+ }
+
+ private boolean readSamples() throws IOException {
+ if (readSamples) {
+ return true;
+ }
+ readSamples = true;
+ byte[] header = new byte[5];
+ boolean ret = false;
+ inputStream.mark(5);
+ while (-1 != inputStream.read(header)) {
+ ret = true;
+ int frmsizecode = header[4] & 63;
+ calcBitrateAndFrameSize(frmsizecode);
+ inputStream.reset();
+ byte[] data = new byte[frameSize];
+ inputStream.read(data);
+ samples.add(ByteBuffer.wrap(data));
+ stts.add(new TimeToSampleBox.Entry(1, 1536));
+ inputStream.mark(5);
+ }
+ return ret;
+ }
+
+ private void initBitRateAndFrameSizeTable() {
+ // ETSI 102 366 Table 4.13, in frmsizecod, flag, fscod, bitrate/size order. Note that all sizes are in words, and all bitrates in kbps
+
+ // 48kHz
+ bitRateAndFrameSizeTable[0][0][0][0] = 32;
+ bitRateAndFrameSizeTable[0][1][0][0] = 32;
+ bitRateAndFrameSizeTable[0][0][0][1] = 64;
+ bitRateAndFrameSizeTable[0][1][0][1] = 64;
+ bitRateAndFrameSizeTable[1][0][0][0] = 40;
+ bitRateAndFrameSizeTable[1][1][0][0] = 40;
+ bitRateAndFrameSizeTable[1][0][0][1] = 80;
+ bitRateAndFrameSizeTable[1][1][0][1] = 80;
+ bitRateAndFrameSizeTable[2][0][0][0] = 48;
+ bitRateAndFrameSizeTable[2][1][0][0] = 48;
+ bitRateAndFrameSizeTable[2][0][0][1] = 96;
+ bitRateAndFrameSizeTable[2][1][0][1] = 96;
+ bitRateAndFrameSizeTable[3][0][0][0] = 56;
+ bitRateAndFrameSizeTable[3][1][0][0] = 56;
+ bitRateAndFrameSizeTable[3][0][0][1] = 112;
+ bitRateAndFrameSizeTable[3][1][0][1] = 112;
+ bitRateAndFrameSizeTable[4][0][0][0] = 64;
+ bitRateAndFrameSizeTable[4][1][0][0] = 64;
+ bitRateAndFrameSizeTable[4][0][0][1] = 128;
+ bitRateAndFrameSizeTable[4][1][0][1] = 128;
+ bitRateAndFrameSizeTable[5][0][0][0] = 80;
+ bitRateAndFrameSizeTable[5][1][0][0] = 80;
+ bitRateAndFrameSizeTable[5][0][0][1] = 160;
+ bitRateAndFrameSizeTable[5][1][0][1] = 160;
+ bitRateAndFrameSizeTable[6][0][0][0] = 96;
+ bitRateAndFrameSizeTable[6][1][0][0] = 96;
+ bitRateAndFrameSizeTable[6][0][0][1] = 192;
+ bitRateAndFrameSizeTable[6][1][0][1] = 192;
+ bitRateAndFrameSizeTable[7][0][0][0] = 112;
+ bitRateAndFrameSizeTable[7][1][0][0] = 112;
+ bitRateAndFrameSizeTable[7][0][0][1] = 224;
+ bitRateAndFrameSizeTable[7][1][0][1] = 224;
+ bitRateAndFrameSizeTable[8][0][0][0] = 128;
+ bitRateAndFrameSizeTable[8][1][0][0] = 128;
+ bitRateAndFrameSizeTable[8][0][0][1] = 256;
+ bitRateAndFrameSizeTable[8][1][0][1] = 256;
+ bitRateAndFrameSizeTable[9][0][0][0] = 160;
+ bitRateAndFrameSizeTable[9][1][0][0] = 160;
+ bitRateAndFrameSizeTable[9][0][0][1] = 320;
+ bitRateAndFrameSizeTable[9][1][0][1] = 320;
+ bitRateAndFrameSizeTable[10][0][0][0] = 192;
+ bitRateAndFrameSizeTable[10][1][0][0] = 192;
+ bitRateAndFrameSizeTable[10][0][0][1] = 384;
+ bitRateAndFrameSizeTable[10][1][0][1] = 384;
+ bitRateAndFrameSizeTable[11][0][0][0] = 224;
+ bitRateAndFrameSizeTable[11][1][0][0] = 224;
+ bitRateAndFrameSizeTable[11][0][0][1] = 448;
+ bitRateAndFrameSizeTable[11][1][0][1] = 448;
+ bitRateAndFrameSizeTable[12][0][0][0] = 256;
+ bitRateAndFrameSizeTable[12][1][0][0] = 256;
+ bitRateAndFrameSizeTable[12][0][0][1] = 512;
+ bitRateAndFrameSizeTable[12][1][0][1] = 512;
+ bitRateAndFrameSizeTable[13][0][0][0] = 320;
+ bitRateAndFrameSizeTable[13][1][0][0] = 320;
+ bitRateAndFrameSizeTable[13][0][0][1] = 640;
+ bitRateAndFrameSizeTable[13][1][0][1] = 640;
+ bitRateAndFrameSizeTable[14][0][0][0] = 384;
+ bitRateAndFrameSizeTable[14][1][0][0] = 384;
+ bitRateAndFrameSizeTable[14][0][0][1] = 768;
+ bitRateAndFrameSizeTable[14][1][0][1] = 768;
+ bitRateAndFrameSizeTable[15][0][0][0] = 448;
+ bitRateAndFrameSizeTable[15][1][0][0] = 448;
+ bitRateAndFrameSizeTable[15][0][0][1] = 896;
+ bitRateAndFrameSizeTable[15][1][0][1] = 896;
+ bitRateAndFrameSizeTable[16][0][0][0] = 512;
+ bitRateAndFrameSizeTable[16][1][0][0] = 512;
+ bitRateAndFrameSizeTable[16][0][0][1] = 1024;
+ bitRateAndFrameSizeTable[16][1][0][1] = 1024;
+ bitRateAndFrameSizeTable[17][0][0][0] = 576;
+ bitRateAndFrameSizeTable[17][1][0][0] = 576;
+ bitRateAndFrameSizeTable[17][0][0][1] = 1152;
+ bitRateAndFrameSizeTable[17][1][0][1] = 1152;
+ bitRateAndFrameSizeTable[18][0][0][0] = 640;
+ bitRateAndFrameSizeTable[18][1][0][0] = 640;
+ bitRateAndFrameSizeTable[18][0][0][1] = 1280;
+ bitRateAndFrameSizeTable[18][1][0][1] = 1280;
+
+ // 44.1 kHz
+ bitRateAndFrameSizeTable[0][0][1][0] = 32;
+ bitRateAndFrameSizeTable[0][1][1][0] = 32;
+ bitRateAndFrameSizeTable[0][0][1][1] = 69;
+ bitRateAndFrameSizeTable[0][1][1][1] = 70;
+ bitRateAndFrameSizeTable[1][0][1][0] = 40;
+ bitRateAndFrameSizeTable[1][1][1][0] = 40;
+ bitRateAndFrameSizeTable[1][0][1][1] = 87;
+ bitRateAndFrameSizeTable[1][1][1][1] = 88;
+ bitRateAndFrameSizeTable[2][0][1][0] = 48;
+ bitRateAndFrameSizeTable[2][1][1][0] = 48;
+ bitRateAndFrameSizeTable[2][0][1][1] = 104;
+ bitRateAndFrameSizeTable[2][1][1][1] = 105;
+ bitRateAndFrameSizeTable[3][0][1][0] = 56;
+ bitRateAndFrameSizeTable[3][1][1][0] = 56;
+ bitRateAndFrameSizeTable[3][0][1][1] = 121;
+ bitRateAndFrameSizeTable[3][1][1][1] = 122;
+ bitRateAndFrameSizeTable[4][0][1][0] = 64;
+ bitRateAndFrameSizeTable[4][1][1][0] = 64;
+ bitRateAndFrameSizeTable[4][0][1][1] = 139;
+ bitRateAndFrameSizeTable[4][1][1][1] = 140;
+ bitRateAndFrameSizeTable[5][0][1][0] = 80;
+ bitRateAndFrameSizeTable[5][1][1][0] = 80;
+ bitRateAndFrameSizeTable[5][0][1][1] = 174;
+ bitRateAndFrameSizeTable[5][1][1][1] = 175;
+ bitRateAndFrameSizeTable[6][0][1][0] = 96;
+ bitRateAndFrameSizeTable[6][1][1][0] = 96;
+ bitRateAndFrameSizeTable[6][0][1][1] = 208;
+ bitRateAndFrameSizeTable[6][1][1][1] = 209;
+ bitRateAndFrameSizeTable[7][0][1][0] = 112;
+ bitRateAndFrameSizeTable[7][1][1][0] = 112;
+ bitRateAndFrameSizeTable[7][0][1][1] = 243;
+ bitRateAndFrameSizeTable[7][1][1][1] = 244;
+ bitRateAndFrameSizeTable[8][0][1][0] = 128;
+ bitRateAndFrameSizeTable[8][1][1][0] = 128;
+ bitRateAndFrameSizeTable[8][0][1][1] = 278;
+ bitRateAndFrameSizeTable[8][1][1][1] = 279;
+ bitRateAndFrameSizeTable[9][0][1][0] = 160;
+ bitRateAndFrameSizeTable[9][1][1][0] = 160;
+ bitRateAndFrameSizeTable[9][0][1][1] = 348;
+ bitRateAndFrameSizeTable[9][1][1][1] = 349;
+ bitRateAndFrameSizeTable[10][0][1][0] = 192;
+ bitRateAndFrameSizeTable[10][1][1][0] = 192;
+ bitRateAndFrameSizeTable[10][0][1][1] = 417;
+ bitRateAndFrameSizeTable[10][1][1][1] = 418;
+ bitRateAndFrameSizeTable[11][0][1][0] = 224;
+ bitRateAndFrameSizeTable[11][1][1][0] = 224;
+ bitRateAndFrameSizeTable[11][0][1][1] = 487;
+ bitRateAndFrameSizeTable[11][1][1][1] = 488;
+ bitRateAndFrameSizeTable[12][0][1][0] = 256;
+ bitRateAndFrameSizeTable[12][1][1][0] = 256;
+ bitRateAndFrameSizeTable[12][0][1][1] = 557;
+ bitRateAndFrameSizeTable[12][1][1][1] = 558;
+ bitRateAndFrameSizeTable[13][0][1][0] = 320;
+ bitRateAndFrameSizeTable[13][1][1][0] = 320;
+ bitRateAndFrameSizeTable[13][0][1][1] = 696;
+ bitRateAndFrameSizeTable[13][1][1][1] = 697;
+ bitRateAndFrameSizeTable[14][0][1][0] = 384;
+ bitRateAndFrameSizeTable[14][1][1][0] = 384;
+ bitRateAndFrameSizeTable[14][0][1][1] = 835;
+ bitRateAndFrameSizeTable[14][1][1][1] = 836;
+ bitRateAndFrameSizeTable[15][0][1][0] = 448;
+ bitRateAndFrameSizeTable[15][1][1][0] = 448;
+ bitRateAndFrameSizeTable[15][0][1][1] = 975;
+ bitRateAndFrameSizeTable[15][1][1][1] = 975;
+ bitRateAndFrameSizeTable[16][0][1][0] = 512;
+ bitRateAndFrameSizeTable[16][1][1][0] = 512;
+ bitRateAndFrameSizeTable[16][0][1][1] = 1114;
+ bitRateAndFrameSizeTable[16][1][1][1] = 1115;
+ bitRateAndFrameSizeTable[17][0][1][0] = 576;
+ bitRateAndFrameSizeTable[17][1][1][0] = 576;
+ bitRateAndFrameSizeTable[17][0][1][1] = 1253;
+ bitRateAndFrameSizeTable[17][1][1][1] = 1254;
+ bitRateAndFrameSizeTable[18][0][1][0] = 640;
+ bitRateAndFrameSizeTable[18][1][1][0] = 640;
+ bitRateAndFrameSizeTable[18][0][1][1] = 1393;
+ bitRateAndFrameSizeTable[18][1][1][1] = 1394;
+
+ // 32kHz
+ bitRateAndFrameSizeTable[0][0][2][0] = 32;
+ bitRateAndFrameSizeTable[0][1][2][0] = 32;
+ bitRateAndFrameSizeTable[0][0][2][1] = 96;
+ bitRateAndFrameSizeTable[0][1][2][1] = 96;
+ bitRateAndFrameSizeTable[1][0][2][0] = 40;
+ bitRateAndFrameSizeTable[1][1][2][0] = 40;
+ bitRateAndFrameSizeTable[1][0][2][1] = 120;
+ bitRateAndFrameSizeTable[1][1][2][1] = 120;
+ bitRateAndFrameSizeTable[2][0][2][0] = 48;
+ bitRateAndFrameSizeTable[2][1][2][0] = 48;
+ bitRateAndFrameSizeTable[2][0][2][1] = 144;
+ bitRateAndFrameSizeTable[2][1][2][1] = 144;
+ bitRateAndFrameSizeTable[3][0][2][0] = 56;
+ bitRateAndFrameSizeTable[3][1][2][0] = 56;
+ bitRateAndFrameSizeTable[3][0][2][1] = 168;
+ bitRateAndFrameSizeTable[3][1][2][1] = 168;
+ bitRateAndFrameSizeTable[4][0][2][0] = 64;
+ bitRateAndFrameSizeTable[4][1][2][0] = 64;
+ bitRateAndFrameSizeTable[4][0][2][1] = 192;
+ bitRateAndFrameSizeTable[4][1][2][1] = 192;
+ bitRateAndFrameSizeTable[5][0][2][0] = 80;
+ bitRateAndFrameSizeTable[5][1][2][0] = 80;
+ bitRateAndFrameSizeTable[5][0][2][1] = 240;
+ bitRateAndFrameSizeTable[5][1][2][1] = 240;
+ bitRateAndFrameSizeTable[6][0][2][0] = 96;
+ bitRateAndFrameSizeTable[6][1][2][0] = 96;
+ bitRateAndFrameSizeTable[6][0][2][1] = 288;
+ bitRateAndFrameSizeTable[6][1][2][1] = 288;
+ bitRateAndFrameSizeTable[7][0][2][0] = 112;
+ bitRateAndFrameSizeTable[7][1][2][0] = 112;
+ bitRateAndFrameSizeTable[7][0][2][1] = 336;
+ bitRateAndFrameSizeTable[7][1][2][1] = 336;
+ bitRateAndFrameSizeTable[8][0][2][0] = 128;
+ bitRateAndFrameSizeTable[8][1][2][0] = 128;
+ bitRateAndFrameSizeTable[8][0][2][1] = 384;
+ bitRateAndFrameSizeTable[8][1][2][1] = 384;
+ bitRateAndFrameSizeTable[9][0][2][0] = 160;
+ bitRateAndFrameSizeTable[9][1][2][0] = 160;
+ bitRateAndFrameSizeTable[9][0][2][1] = 480;
+ bitRateAndFrameSizeTable[9][1][2][1] = 480;
+ bitRateAndFrameSizeTable[10][0][2][0] = 192;
+ bitRateAndFrameSizeTable[10][1][2][0] = 192;
+ bitRateAndFrameSizeTable[10][0][2][1] = 576;
+ bitRateAndFrameSizeTable[10][1][2][1] = 576;
+ bitRateAndFrameSizeTable[11][0][2][0] = 224;
+ bitRateAndFrameSizeTable[11][1][2][0] = 224;
+ bitRateAndFrameSizeTable[11][0][2][1] = 672;
+ bitRateAndFrameSizeTable[11][1][2][1] = 672;
+ bitRateAndFrameSizeTable[12][0][2][0] = 256;
+ bitRateAndFrameSizeTable[12][1][2][0] = 256;
+ bitRateAndFrameSizeTable[12][0][2][1] = 768;
+ bitRateAndFrameSizeTable[12][1][2][1] = 768;
+ bitRateAndFrameSizeTable[13][0][2][0] = 320;
+ bitRateAndFrameSizeTable[13][1][2][0] = 320;
+ bitRateAndFrameSizeTable[13][0][2][1] = 960;
+ bitRateAndFrameSizeTable[13][1][2][1] = 960;
+ bitRateAndFrameSizeTable[14][0][2][0] = 384;
+ bitRateAndFrameSizeTable[14][1][2][0] = 384;
+ bitRateAndFrameSizeTable[14][0][2][1] = 1152;
+ bitRateAndFrameSizeTable[14][1][2][1] = 1152;
+ bitRateAndFrameSizeTable[15][0][2][0] = 448;
+ bitRateAndFrameSizeTable[15][1][2][0] = 448;
+ bitRateAndFrameSizeTable[15][0][2][1] = 1344;
+ bitRateAndFrameSizeTable[15][1][2][1] = 1344;
+ bitRateAndFrameSizeTable[16][0][2][0] = 512;
+ bitRateAndFrameSizeTable[16][1][2][0] = 512;
+ bitRateAndFrameSizeTable[16][0][2][1] = 1536;
+ bitRateAndFrameSizeTable[16][1][2][1] = 1536;
+ bitRateAndFrameSizeTable[17][0][2][0] = 576;
+ bitRateAndFrameSizeTable[17][1][2][0] = 576;
+ bitRateAndFrameSizeTable[17][0][2][1] = 1728;
+ bitRateAndFrameSizeTable[17][1][2][1] = 1728;
+ bitRateAndFrameSizeTable[18][0][2][0] = 640;
+ bitRateAndFrameSizeTable[18][1][2][0] = 640;
+ bitRateAndFrameSizeTable[18][0][2][1] = 1920;
+ bitRateAndFrameSizeTable[18][1][2][1] = 1920;
+ }
+} \ No newline at end of file
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/Amf0Track.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/Amf0Track.java
new file mode 100644
index 0000000..0917767
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/Amf0Track.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.googlecode.mp4parser.authoring.tracks;
+
+import com.coremedia.iso.boxes.*;
+import com.googlecode.mp4parser.authoring.AbstractTrack;
+import com.googlecode.mp4parser.authoring.TrackMetaData;
+import com.googlecode.mp4parser.boxes.adobe.ActionMessageFormat0SampleEntryBox;
+
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+public class Amf0Track extends AbstractTrack {
+ SortedMap<Long, byte[]> rawSamples = new TreeMap<Long, byte[]>() {
+ };
+ private TrackMetaData trackMetaData = new TrackMetaData();
+
+
+ /**
+ * Creates a new AMF0 track from
+ *
+ * @param rawSamples
+ */
+ public Amf0Track(Map<Long, byte[]> rawSamples) {
+ this.rawSamples = new TreeMap<Long, byte[]>(rawSamples);
+ trackMetaData.setCreationTime(new Date());
+ trackMetaData.setModificationTime(new Date());
+ trackMetaData.setTimescale(1000); // Text tracks use millieseconds
+ trackMetaData.setLanguage("eng");
+ }
+
+ public List<ByteBuffer> getSamples() {
+ LinkedList<ByteBuffer> samples = new LinkedList<ByteBuffer>();
+ for (byte[] bytes : rawSamples.values()) {
+ samples.add(ByteBuffer.wrap(bytes));
+ }
+ return samples;
+ }
+
+ public SampleDescriptionBox getSampleDescriptionBox() {
+ SampleDescriptionBox stsd = new SampleDescriptionBox();
+ ActionMessageFormat0SampleEntryBox amf0 = new ActionMessageFormat0SampleEntryBox();
+ amf0.setDataReferenceIndex(1);
+ stsd.addBox(amf0);
+ return stsd;
+ }
+
+ public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
+ LinkedList<TimeToSampleBox.Entry> timesToSample = new LinkedList<TimeToSampleBox.Entry>();
+ LinkedList<Long> keys = new LinkedList<Long>(rawSamples.keySet());
+ Collections.sort(keys);
+ long lastTimeStamp = 0;
+ for (Long key : keys) {
+ long delta = key - lastTimeStamp;
+ if (timesToSample.size() > 0 && timesToSample.peek().getDelta() == delta) {
+ timesToSample.peek().setCount(timesToSample.peek().getCount() + 1);
+ } else {
+ timesToSample.add(new TimeToSampleBox.Entry(1, delta));
+ }
+ lastTimeStamp = key;
+ }
+ return timesToSample;
+ }
+
+ public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
+ // AMF0 tracks do not have Composition Time
+ return null;
+ }
+
+ public long[] getSyncSamples() {
+ // AMF0 tracks do not have Sync Samples
+ return null;
+ }
+
+ public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
+ // AMF0 tracks do not have Sample Dependencies
+ return null;
+ }
+
+ public TrackMetaData getTrackMetaData() {
+ return trackMetaData; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public String getHandler() {
+ return "data";
+ }
+
+ public Box getMediaHeaderBox() {
+ return new NullMediaHeaderBox();
+ }
+
+ public SubSampleInformationBox getSubsampleInformationBox() {
+ return null;
+ }
+
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/AppendTrack.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/AppendTrack.java
new file mode 100644
index 0000000..93ee0cd
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/AppendTrack.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.tracks;
+
+import com.coremedia.iso.boxes.*;
+import com.coremedia.iso.boxes.sampleentry.AudioSampleEntry;
+import com.googlecode.mp4parser.authoring.AbstractTrack;
+import com.googlecode.mp4parser.authoring.Track;
+import com.googlecode.mp4parser.authoring.TrackMetaData;
+import com.googlecode.mp4parser.boxes.mp4.ESDescriptorBox;
+import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.DecoderConfigDescriptor;
+import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.ESDescriptor;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
+import java.util.*;
+
+/**
+ * Appends two or more <code>Tracks</code> of the same type. No only that the type must be equal
+ * also the decoder settings must be the same.
+ */
+public class AppendTrack extends AbstractTrack {
+ Track[] tracks;
+ SampleDescriptionBox stsd;
+
+ public AppendTrack(Track... tracks) throws IOException {
+ this.tracks = tracks;
+
+ for (Track track : tracks) {
+
+ if (stsd == null) {
+ stsd = track.getSampleDescriptionBox();
+ } else {
+ ByteArrayOutputStream curBaos = new ByteArrayOutputStream();
+ ByteArrayOutputStream refBaos = new ByteArrayOutputStream();
+ track.getSampleDescriptionBox().getBox(Channels.newChannel(curBaos));
+ stsd.getBox(Channels.newChannel(refBaos));
+ byte[] cur = curBaos.toByteArray();
+ byte[] ref = refBaos.toByteArray();
+
+ if (!Arrays.equals(ref, cur)) {
+ SampleDescriptionBox curStsd = track.getSampleDescriptionBox();
+ if (stsd.getBoxes().size() == 1 && curStsd.getBoxes().size() == 1) {
+ if (stsd.getBoxes().get(0) instanceof AudioSampleEntry && curStsd.getBoxes().get(0) instanceof AudioSampleEntry) {
+ AudioSampleEntry aseResult = mergeAudioSampleEntries((AudioSampleEntry) stsd.getBoxes().get(0), (AudioSampleEntry) curStsd.getBoxes().get(0));
+ if (aseResult != null) {
+ stsd.setBoxes(Collections.<Box>singletonList(aseResult));
+ return;
+ }
+ }
+ }
+ throw new IOException("Cannot append " + track + " to " + tracks[0] + " since their Sample Description Boxes differ: \n" + track.getSampleDescriptionBox() + "\n vs. \n" + tracks[0].getSampleDescriptionBox());
+ }
+ }
+ }
+ }
+
+ private AudioSampleEntry mergeAudioSampleEntries(AudioSampleEntry ase1, AudioSampleEntry ase2) throws IOException {
+ if (ase1.getType().equals(ase2.getType())) {
+ AudioSampleEntry ase = new AudioSampleEntry(ase2.getType());
+ if (ase1.getBytesPerFrame() == ase2.getBytesPerFrame()) {
+ ase.setBytesPerFrame(ase1.getBytesPerFrame());
+ } else {
+ return null;
+ }
+ if (ase1.getBytesPerPacket() == ase2.getBytesPerPacket()) {
+ ase.setBytesPerPacket(ase1.getBytesPerPacket());
+ } else {
+ return null;
+ }
+ if (ase1.getBytesPerSample() == ase2.getBytesPerSample()) {
+ ase.setBytesPerSample(ase1.getBytesPerSample());
+ } else {
+ return null;
+ }
+ if (ase1.getChannelCount() == ase2.getChannelCount()) {
+ ase.setChannelCount(ase1.getChannelCount());
+ } else {
+ return null;
+ }
+ if (ase1.getPacketSize() == ase2.getPacketSize()) {
+ ase.setPacketSize(ase1.getPacketSize());
+ } else {
+ return null;
+ }
+ if (ase1.getCompressionId() == ase2.getCompressionId()) {
+ ase.setCompressionId(ase1.getCompressionId());
+ } else {
+ return null;
+ }
+ if (ase1.getSampleRate() == ase2.getSampleRate()) {
+ ase.setSampleRate(ase1.getSampleRate());
+ } else {
+ return null;
+ }
+ if (ase1.getSampleSize() == ase2.getSampleSize()) {
+ ase.setSampleSize(ase1.getSampleSize());
+ } else {
+ return null;
+ }
+ if (ase1.getSamplesPerPacket() == ase2.getSamplesPerPacket()) {
+ ase.setSamplesPerPacket(ase1.getSamplesPerPacket());
+ } else {
+ return null;
+ }
+ if (ase1.getSoundVersion() == ase2.getSoundVersion()) {
+ ase.setSoundVersion(ase1.getSoundVersion());
+ } else {
+ return null;
+ }
+ if (Arrays.equals(ase1.getSoundVersion2Data(), ase2.getSoundVersion2Data())) {
+ ase.setSoundVersion2Data(ase1.getSoundVersion2Data());
+ } else {
+ return null;
+ }
+ if (ase1.getBoxes().size() == ase2.getBoxes().size()) {
+ Iterator<Box> bxs1 = ase1.getBoxes().iterator();
+ Iterator<Box> bxs2 = ase2.getBoxes().iterator();
+ while (bxs1.hasNext()) {
+ Box cur1 = bxs1.next();
+ Box cur2 = bxs2.next();
+ ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
+ ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
+ cur1.getBox(Channels.newChannel(baos1));
+ cur2.getBox(Channels.newChannel(baos2));
+ if (Arrays.equals(baos1.toByteArray(), baos2.toByteArray())) {
+ ase.addBox(cur1);
+ } else {
+ if (ESDescriptorBox.TYPE.equals(cur1.getType()) && ESDescriptorBox.TYPE.equals(cur2.getType())) {
+ ESDescriptorBox esdsBox1 = (ESDescriptorBox) cur1;
+ ESDescriptorBox esdsBox2 = (ESDescriptorBox) cur2;
+ ESDescriptor esds1 = esdsBox1.getEsDescriptor();
+ ESDescriptor esds2 = esdsBox2.getEsDescriptor();
+ if (esds1.getURLFlag() != esds2.getURLFlag()) {
+ return null;
+ }
+ if (esds1.getURLLength() != esds2.getURLLength()) {
+ return null;
+ }
+ if (esds1.getDependsOnEsId() != esds2.getDependsOnEsId()) {
+ return null;
+ }
+ if (esds1.getEsId() != esds2.getEsId()) {
+ return null;
+ }
+ if (esds1.getoCREsId() != esds2.getoCREsId()) {
+ return null;
+ }
+ if (esds1.getoCRstreamFlag() != esds2.getoCRstreamFlag()) {
+ return null;
+ }
+ if (esds1.getRemoteODFlag() != esds2.getRemoteODFlag()) {
+ return null;
+ }
+ if (esds1.getStreamDependenceFlag() != esds2.getStreamDependenceFlag()) {
+ return null;
+ }
+ if (esds1.getStreamPriority() != esds2.getStreamPriority()) {
+ return null;
+ }
+ if (esds1.getURLString() != null ? !esds1.getURLString().equals(esds2.getURLString()) : esds2.getURLString() != null) {
+ return null;
+ }
+ if (esds1.getDecoderConfigDescriptor() != null ? !esds1.getDecoderConfigDescriptor().equals(esds2.getDecoderConfigDescriptor()) : esds2.getDecoderConfigDescriptor() != null) {
+ DecoderConfigDescriptor dcd1 = esds1.getDecoderConfigDescriptor();
+ DecoderConfigDescriptor dcd2 = esds2.getDecoderConfigDescriptor();
+ if (!dcd1.getAudioSpecificInfo().equals(dcd2.getAudioSpecificInfo())) {
+ return null;
+ }
+ if (dcd1.getAvgBitRate() != dcd2.getAvgBitRate()) {
+ // I don't care
+ }
+ if (dcd1.getBufferSizeDB() != dcd2.getBufferSizeDB()) {
+ // I don't care
+ }
+
+ if (dcd1.getDecoderSpecificInfo() != null ? !dcd1.getDecoderSpecificInfo().equals(dcd2.getDecoderSpecificInfo()) : dcd2.getDecoderSpecificInfo() != null) {
+ return null;
+ }
+
+ if (dcd1.getMaxBitRate() != dcd2.getMaxBitRate()) {
+ // I don't care
+ }
+ if (!dcd1.getProfileLevelIndicationDescriptors().equals(dcd2.getProfileLevelIndicationDescriptors())) {
+ return null;
+ }
+
+ if (dcd1.getObjectTypeIndication() != dcd2.getObjectTypeIndication()) {
+ return null;
+ }
+ if (dcd1.getStreamType() != dcd2.getStreamType()) {
+ return null;
+ }
+ if (dcd1.getUpStream() != dcd2.getUpStream()) {
+ return null;
+ }
+
+
+ }
+ if (esds1.getOtherDescriptors() != null ? !esds1.getOtherDescriptors().equals(esds2.getOtherDescriptors()) : esds2.getOtherDescriptors() != null) {
+ return null;
+ }
+ if (esds1.getSlConfigDescriptor() != null ? !esds1.getSlConfigDescriptor().equals(esds2.getSlConfigDescriptor()) : esds2.getSlConfigDescriptor() != null) {
+ return null;
+ }
+ ase.addBox(cur1);
+ }
+ }
+ }
+ }
+ return ase;
+ } else {
+ return null;
+ }
+
+
+ }
+
+
+ public List<ByteBuffer> getSamples() {
+ ArrayList<ByteBuffer> lists = new ArrayList<ByteBuffer>();
+
+ for (Track track : tracks) {
+ lists.addAll(track.getSamples());
+ }
+
+ return lists;
+ }
+
+ public SampleDescriptionBox getSampleDescriptionBox() {
+ return stsd;
+ }
+
+ public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
+ if (tracks[0].getDecodingTimeEntries() != null && !tracks[0].getDecodingTimeEntries().isEmpty()) {
+ List<long[]> lists = new LinkedList<long[]>();
+ for (Track track : tracks) {
+ lists.add(TimeToSampleBox.blowupTimeToSamples(track.getDecodingTimeEntries()));
+ }
+
+ LinkedList<TimeToSampleBox.Entry> returnDecodingEntries = new LinkedList<TimeToSampleBox.Entry>();
+ for (long[] list : lists) {
+ for (long nuDecodingTime : list) {
+ if (returnDecodingEntries.isEmpty() || returnDecodingEntries.getLast().getDelta() != nuDecodingTime) {
+ TimeToSampleBox.Entry e = new TimeToSampleBox.Entry(1, nuDecodingTime);
+ returnDecodingEntries.add(e);
+ } else {
+ TimeToSampleBox.Entry e = returnDecodingEntries.getLast();
+ e.setCount(e.getCount() + 1);
+ }
+ }
+ }
+ return returnDecodingEntries;
+ } else {
+ return null;
+ }
+ }
+
+ public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
+ if (tracks[0].getCompositionTimeEntries() != null && !tracks[0].getCompositionTimeEntries().isEmpty()) {
+ List<int[]> lists = new LinkedList<int[]>();
+ for (Track track : tracks) {
+ lists.add(CompositionTimeToSample.blowupCompositionTimes(track.getCompositionTimeEntries()));
+ }
+ LinkedList<CompositionTimeToSample.Entry> compositionTimeEntries = new LinkedList<CompositionTimeToSample.Entry>();
+ for (int[] list : lists) {
+ for (int compositionTime : list) {
+ if (compositionTimeEntries.isEmpty() || compositionTimeEntries.getLast().getOffset() != compositionTime) {
+ CompositionTimeToSample.Entry e = new CompositionTimeToSample.Entry(1, compositionTime);
+ compositionTimeEntries.add(e);
+ } else {
+ CompositionTimeToSample.Entry e = compositionTimeEntries.getLast();
+ e.setCount(e.getCount() + 1);
+ }
+ }
+ }
+ return compositionTimeEntries;
+ } else {
+ return null;
+ }
+ }
+
+ public long[] getSyncSamples() {
+ if (tracks[0].getSyncSamples() != null && tracks[0].getSyncSamples().length > 0) {
+ int numSyncSamples = 0;
+ for (Track track : tracks) {
+ numSyncSamples += track.getSyncSamples().length;
+ }
+ long[] returnSyncSamples = new long[numSyncSamples];
+
+ int pos = 0;
+ long samplesBefore = 0;
+ for (Track track : tracks) {
+ for (long l : track.getSyncSamples()) {
+ returnSyncSamples[pos++] = samplesBefore + l;
+ }
+ samplesBefore += track.getSamples().size();
+ }
+ return returnSyncSamples;
+ } else {
+ return null;
+ }
+ }
+
+ public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
+ if (tracks[0].getSampleDependencies() != null && !tracks[0].getSampleDependencies().isEmpty()) {
+ List<SampleDependencyTypeBox.Entry> list = new LinkedList<SampleDependencyTypeBox.Entry>();
+ for (Track track : tracks) {
+ list.addAll(track.getSampleDependencies());
+ }
+ return list;
+ } else {
+ return null;
+ }
+ }
+
+ public TrackMetaData getTrackMetaData() {
+ return tracks[0].getTrackMetaData();
+ }
+
+ public String getHandler() {
+ return tracks[0].getHandler();
+ }
+
+ public Box getMediaHeaderBox() {
+ return tracks[0].getMediaHeaderBox();
+ }
+
+ public SubSampleInformationBox getSubsampleInformationBox() {
+ return tracks[0].getSubsampleInformationBox();
+ }
+
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/ChangeTimeScaleTrack.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/ChangeTimeScaleTrack.java
new file mode 100644
index 0000000..50f76c2
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/ChangeTimeScaleTrack.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.tracks;
+
+import com.coremedia.iso.boxes.*;
+import com.googlecode.mp4parser.authoring.Track;
+import com.googlecode.mp4parser.authoring.TrackMetaData;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.logging.Logger;
+
+/**
+ * Changes the timescale of a track by wrapping the track.
+ */
+public class ChangeTimeScaleTrack implements Track {
+ private static final Logger LOG = Logger.getLogger(ChangeTimeScaleTrack.class.getName());
+
+ Track source;
+ List<CompositionTimeToSample.Entry> ctts;
+ List<TimeToSampleBox.Entry> tts;
+ long timeScale;
+
+ /**
+ * Changes the time scale of the source track to the target time scale and makes sure
+ * that any rounding errors that may have summed are corrected exactly before the syncSamples.
+ *
+ * @param source the source track
+ * @param targetTimeScale the resulting time scale of this track.
+ * @param syncSamples at these sync points where rounding error are corrected.
+ */
+ public ChangeTimeScaleTrack(Track source, long targetTimeScale, long[] syncSamples) {
+ this.source = source;
+ this.timeScale = targetTimeScale;
+ double timeScaleFactor = (double) targetTimeScale / source.getTrackMetaData().getTimescale();
+ ctts = adjustCtts(source.getCompositionTimeEntries(), timeScaleFactor);
+ tts = adjustTts(source.getDecodingTimeEntries(), timeScaleFactor, syncSamples, getTimes(source, syncSamples, targetTimeScale));
+ }
+
+ private static long[] getTimes(Track track, long[] syncSamples, long targetTimeScale) {
+ long[] syncSampleTimes = new long[syncSamples.length];
+ Queue<TimeToSampleBox.Entry> timeQueue = new LinkedList<TimeToSampleBox.Entry>(track.getDecodingTimeEntries());
+
+ int currentSample = 1; // first syncsample is 1
+ long currentDuration = 0;
+ long currentDelta = 0;
+ int currentSyncSampleIndex = 0;
+ long left = 0;
+
+
+ while (currentSample <= syncSamples[syncSamples.length - 1]) {
+ if (currentSample++ == syncSamples[currentSyncSampleIndex]) {
+ syncSampleTimes[currentSyncSampleIndex++] = (currentDuration * targetTimeScale) / track.getTrackMetaData().getTimescale();
+ }
+ if (left-- == 0) {
+ TimeToSampleBox.Entry entry = timeQueue.poll();
+ left = entry.getCount() - 1;
+ currentDelta = entry.getDelta();
+ }
+ currentDuration += currentDelta;
+ }
+ return syncSampleTimes;
+
+ }
+
+ public SampleDescriptionBox getSampleDescriptionBox() {
+ return source.getSampleDescriptionBox();
+ }
+
+ public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
+ return tts;
+ }
+
+ public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
+ return ctts;
+ }
+
+ public long[] getSyncSamples() {
+ return source.getSyncSamples();
+ }
+
+ public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
+ return source.getSampleDependencies();
+ }
+
+ public TrackMetaData getTrackMetaData() {
+ TrackMetaData trackMetaData = (TrackMetaData) source.getTrackMetaData().clone();
+ trackMetaData.setTimescale(timeScale);
+ return trackMetaData;
+ }
+
+ public String getHandler() {
+ return source.getHandler();
+ }
+
+ public boolean isEnabled() {
+ return source.isEnabled();
+ }
+
+ public boolean isInMovie() {
+ return source.isInMovie();
+ }
+
+ public boolean isInPreview() {
+ return source.isInPreview();
+ }
+
+ public boolean isInPoster() {
+ return source.isInPoster();
+ }
+
+ public List<ByteBuffer> getSamples() {
+ return source.getSamples();
+ }
+
+
+ /**
+ * Adjusting the composition times is easy. Just scale it by the factor - that's it. There is no rounding
+ * error summing up.
+ *
+ * @param source
+ * @param timeScaleFactor
+ * @return
+ */
+ static List<CompositionTimeToSample.Entry> adjustCtts(List<CompositionTimeToSample.Entry> source, double timeScaleFactor) {
+ if (source != null) {
+ List<CompositionTimeToSample.Entry> entries2 = new ArrayList<CompositionTimeToSample.Entry>(source.size());
+ for (CompositionTimeToSample.Entry entry : source) {
+ entries2.add(new CompositionTimeToSample.Entry(entry.getCount(), (int) Math.round(timeScaleFactor * entry.getOffset())));
+ }
+ return entries2;
+ } else {
+ return null;
+ }
+ }
+
+ static List<TimeToSampleBox.Entry> adjustTts(List<TimeToSampleBox.Entry> source, double timeScaleFactor, long[] syncSample, long[] syncSampleTimes) {
+
+ long[] sourceArray = TimeToSampleBox.blowupTimeToSamples(source);
+ long summedDurations = 0;
+
+ LinkedList<TimeToSampleBox.Entry> entries2 = new LinkedList<TimeToSampleBox.Entry>();
+ for (int i = 1; i <= sourceArray.length; i++) {
+ long duration = sourceArray[i - 1];
+
+ long x = Math.round(timeScaleFactor * duration);
+
+
+ TimeToSampleBox.Entry last = entries2.peekLast();
+ int ssIndex;
+ if ((ssIndex = Arrays.binarySearch(syncSample, i + 1)) >= 0) {
+ // we are at the sample before sync point
+ if (syncSampleTimes[ssIndex] != summedDurations) {
+ long correction = syncSampleTimes[ssIndex] - (summedDurations + x);
+ LOG.finest(String.format("Sample %d %d / %d - correct by %d", i, summedDurations, syncSampleTimes[ssIndex], correction));
+ x += correction;
+ }
+ }
+ summedDurations += x;
+ if (last == null) {
+ entries2.add(new TimeToSampleBox.Entry(1, x));
+ } else if (last.getDelta() != x) {
+ entries2.add(new TimeToSampleBox.Entry(1, x));
+ } else {
+ last.setCount(last.getCount() + 1);
+ }
+
+ }
+ return entries2;
+ }
+
+ public Box getMediaHeaderBox() {
+ return source.getMediaHeaderBox();
+ }
+
+ public SubSampleInformationBox getSubsampleInformationBox() {
+ return source.getSubsampleInformationBox();
+ }
+
+ @Override
+ public String toString() {
+ return "ChangeTimeScaleTrack{" +
+ "source=" + source +
+ '}';
+ }
+} \ No newline at end of file
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/CroppedTrack.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/CroppedTrack.java
new file mode 100644
index 0000000..2389961
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/CroppedTrack.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.tracks;
+
+import com.coremedia.iso.boxes.*;
+import com.googlecode.mp4parser.authoring.AbstractTrack;
+import com.googlecode.mp4parser.authoring.Track;
+import com.googlecode.mp4parser.authoring.TrackMetaData;
+
+import java.nio.ByteBuffer;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Generates a Track that starts at fromSample and ends at toSample (exclusive). The user of this class
+ * has to make sure that the fromSample is a random access sample.
+ * <ul>
+ * <li>In AAC this is every single sample</li>
+ * <li>In H264 this is every sample that is marked in the SyncSampleBox</li>
+ * </ul>
+ */
+public class CroppedTrack extends AbstractTrack {
+ Track origTrack;
+ private int fromSample;
+ private int toSample;
+ private long[] syncSampleArray;
+
+ public CroppedTrack(Track origTrack, long fromSample, long toSample) {
+ this.origTrack = origTrack;
+ assert fromSample <= Integer.MAX_VALUE;
+ assert toSample <= Integer.MAX_VALUE;
+ this.fromSample = (int) fromSample;
+ this.toSample = (int) toSample;
+ }
+
+ public List<ByteBuffer> getSamples() {
+ return origTrack.getSamples().subList(fromSample, toSample);
+ }
+
+ public SampleDescriptionBox getSampleDescriptionBox() {
+ return origTrack.getSampleDescriptionBox();
+ }
+
+ public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
+ if (origTrack.getDecodingTimeEntries() != null && !origTrack.getDecodingTimeEntries().isEmpty()) {
+ // todo optimize! too much long is allocated but then not used
+ long[] decodingTimes = TimeToSampleBox.blowupTimeToSamples(origTrack.getDecodingTimeEntries());
+ long[] nuDecodingTimes = new long[toSample - fromSample];
+ System.arraycopy(decodingTimes, fromSample, nuDecodingTimes, 0, toSample - fromSample);
+
+ LinkedList<TimeToSampleBox.Entry> returnDecodingEntries = new LinkedList<TimeToSampleBox.Entry>();
+
+ for (long nuDecodingTime : nuDecodingTimes) {
+ if (returnDecodingEntries.isEmpty() || returnDecodingEntries.getLast().getDelta() != nuDecodingTime) {
+ TimeToSampleBox.Entry e = new TimeToSampleBox.Entry(1, nuDecodingTime);
+ returnDecodingEntries.add(e);
+ } else {
+ TimeToSampleBox.Entry e = returnDecodingEntries.getLast();
+ e.setCount(e.getCount() + 1);
+ }
+ }
+ return returnDecodingEntries;
+ } else {
+ return null;
+ }
+ }
+
+ public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
+ if (origTrack.getCompositionTimeEntries() != null && !origTrack.getCompositionTimeEntries().isEmpty()) {
+ int[] compositionTime = CompositionTimeToSample.blowupCompositionTimes(origTrack.getCompositionTimeEntries());
+ int[] nuCompositionTimes = new int[toSample - fromSample];
+ System.arraycopy(compositionTime, fromSample, nuCompositionTimes, 0, toSample - fromSample);
+
+ LinkedList<CompositionTimeToSample.Entry> returnDecodingEntries = new LinkedList<CompositionTimeToSample.Entry>();
+
+ for (int nuDecodingTime : nuCompositionTimes) {
+ if (returnDecodingEntries.isEmpty() || returnDecodingEntries.getLast().getOffset() != nuDecodingTime) {
+ CompositionTimeToSample.Entry e = new CompositionTimeToSample.Entry(1, nuDecodingTime);
+ returnDecodingEntries.add(e);
+ } else {
+ CompositionTimeToSample.Entry e = returnDecodingEntries.getLast();
+ e.setCount(e.getCount() + 1);
+ }
+ }
+ return returnDecodingEntries;
+ } else {
+ return null;
+ }
+ }
+
+ synchronized public long[] getSyncSamples() {
+ if (this.syncSampleArray == null) {
+ if (origTrack.getSyncSamples() != null && origTrack.getSyncSamples().length > 0) {
+ List<Long> syncSamples = new LinkedList<Long>();
+ for (long l : origTrack.getSyncSamples()) {
+ if (l >= fromSample && l < toSample) {
+ syncSamples.add(l - fromSample);
+ }
+ }
+ syncSampleArray = new long[syncSamples.size()];
+ for (int i = 0; i < syncSampleArray.length; i++) {
+ syncSampleArray[i] = syncSamples.get(i);
+
+ }
+ return syncSampleArray;
+ } else {
+ return null;
+ }
+ } else {
+ return this.syncSampleArray;
+ }
+ }
+
+ public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
+ if (origTrack.getSampleDependencies() != null && !origTrack.getSampleDependencies().isEmpty()) {
+ return origTrack.getSampleDependencies().subList(fromSample, toSample);
+ } else {
+ return null;
+ }
+ }
+
+ public TrackMetaData getTrackMetaData() {
+ return origTrack.getTrackMetaData();
+ }
+
+ public String getHandler() {
+ return origTrack.getHandler();
+ }
+
+ public Box getMediaHeaderBox() {
+ return origTrack.getMediaHeaderBox();
+ }
+
+ public SubSampleInformationBox getSubsampleInformationBox() {
+ return origTrack.getSubsampleInformationBox();
+ }
+
+} \ No newline at end of file
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/DivideTimeScaleTrack.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/DivideTimeScaleTrack.java
new file mode 100644
index 0000000..c51e8e0
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/DivideTimeScaleTrack.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.tracks;
+
+import com.coremedia.iso.boxes.*;
+import com.googlecode.mp4parser.authoring.Track;
+import com.googlecode.mp4parser.authoring.TrackMetaData;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Changes the timescale of a track by wrapping the track.
+ */
+public class DivideTimeScaleTrack implements Track {
+ Track source;
+ private int timeScaleDivisor;
+
+ public DivideTimeScaleTrack(Track source, int timeScaleDivisor) {
+ this.source = source;
+ this.timeScaleDivisor = timeScaleDivisor;
+ }
+
+ public SampleDescriptionBox getSampleDescriptionBox() {
+ return source.getSampleDescriptionBox();
+ }
+
+ public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
+ return adjustTts();
+ }
+
+ public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
+ return adjustCtts();
+ }
+
+ public long[] getSyncSamples() {
+ return source.getSyncSamples();
+ }
+
+ public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
+ return source.getSampleDependencies();
+ }
+
+ public TrackMetaData getTrackMetaData() {
+ TrackMetaData trackMetaData = (TrackMetaData) source.getTrackMetaData().clone();
+ trackMetaData.setTimescale(source.getTrackMetaData().getTimescale() / this.timeScaleDivisor);
+ return trackMetaData;
+ }
+
+ public String getHandler() {
+ return source.getHandler();
+ }
+
+ public boolean isEnabled() {
+ return source.isEnabled();
+ }
+
+ public boolean isInMovie() {
+ return source.isInMovie();
+ }
+
+ public boolean isInPreview() {
+ return source.isInPreview();
+ }
+
+ public boolean isInPoster() {
+ return source.isInPoster();
+ }
+
+ public List<ByteBuffer> getSamples() {
+ return source.getSamples();
+ }
+
+
+ List<CompositionTimeToSample.Entry> adjustCtts() {
+ List<CompositionTimeToSample.Entry> origCtts = this.source.getCompositionTimeEntries();
+ if (origCtts != null) {
+ List<CompositionTimeToSample.Entry> entries2 = new ArrayList<CompositionTimeToSample.Entry>(origCtts.size());
+ for (CompositionTimeToSample.Entry entry : origCtts) {
+ entries2.add(new CompositionTimeToSample.Entry(entry.getCount(), entry.getOffset() / timeScaleDivisor));
+ }
+ return entries2;
+ } else {
+ return null;
+ }
+ }
+
+ List<TimeToSampleBox.Entry> adjustTts() {
+ List<TimeToSampleBox.Entry> origTts = source.getDecodingTimeEntries();
+ LinkedList<TimeToSampleBox.Entry> entries2 = new LinkedList<TimeToSampleBox.Entry>();
+ for (TimeToSampleBox.Entry e : origTts) {
+ entries2.add(new TimeToSampleBox.Entry(e.getCount(), e.getDelta() / timeScaleDivisor));
+ }
+ return entries2;
+ }
+
+ public Box getMediaHeaderBox() {
+ return source.getMediaHeaderBox();
+ }
+
+ public SubSampleInformationBox getSubsampleInformationBox() {
+ return source.getSubsampleInformationBox();
+ }
+
+ @Override
+ public String toString() {
+ return "MultiplyTimeScaleTrack{" +
+ "source=" + source +
+ '}';
+ }
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/EC3TrackImpl.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/EC3TrackImpl.java
new file mode 100644
index 0000000..d0b2d76
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/EC3TrackImpl.java
@@ -0,0 +1,436 @@
+package com.googlecode.mp4parser.authoring.tracks;
+
+import com.coremedia.iso.boxes.*;
+import com.coremedia.iso.boxes.sampleentry.AudioSampleEntry;
+import com.googlecode.mp4parser.authoring.AbstractTrack;
+import com.googlecode.mp4parser.authoring.TrackMetaData;
+import com.googlecode.mp4parser.boxes.EC3SpecificBox;
+import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.BitReaderBuffer;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: magnus
+ * Date: 2012-03-14
+ * Time: 10:39
+ * To change this template use File | Settings | File Templates.
+ */
+public class EC3TrackImpl extends AbstractTrack {
+ TrackMetaData trackMetaData = new TrackMetaData();
+ SampleDescriptionBox sampleDescriptionBox;
+
+ int samplerate;
+ int bitrate;
+ int frameSize;
+
+ List<BitStreamInfo> entries = new LinkedList<BitStreamInfo>();
+
+ private BufferedInputStream inputStream;
+ private List<ByteBuffer> samples;
+ List<TimeToSampleBox.Entry> stts = new LinkedList<TimeToSampleBox.Entry>();
+ private String lang = "und";
+
+ public EC3TrackImpl(InputStream fin, String lang) throws IOException {
+ this.lang = lang;
+ parse(fin);
+ }
+
+ public EC3TrackImpl(InputStream fin) throws IOException {
+ parse(fin);
+ }
+
+ private void parse(InputStream fin) throws IOException {
+ inputStream = new BufferedInputStream(fin);
+
+ boolean done = false;
+ inputStream.mark(10000);
+ while (!done) {
+ BitStreamInfo bsi = readVariables();
+ if (bsi == null) {
+ throw new IOException();
+ }
+ for (BitStreamInfo entry : entries) {
+ if (bsi.strmtyp != 1 && entry.substreamid == bsi.substreamid) {
+ done = true;
+ }
+ }
+ if (!done) {
+ entries.add(bsi);
+ long skipped = inputStream.skip(bsi.frameSize);
+ assert skipped == bsi.frameSize;
+ }
+ }
+
+ inputStream.reset();
+
+ if (entries.size() == 0) {
+ throw new IOException();
+ }
+ samplerate = entries.get(0).samplerate;
+
+ sampleDescriptionBox = new SampleDescriptionBox();
+ AudioSampleEntry audioSampleEntry = new AudioSampleEntry("ec-3");
+ audioSampleEntry.setChannelCount(2); // According to ETSI TS 102 366 Annex F
+ audioSampleEntry.setSampleRate(samplerate);
+ audioSampleEntry.setDataReferenceIndex(1);
+ audioSampleEntry.setSampleSize(16);
+
+ EC3SpecificBox ec3 = new EC3SpecificBox();
+ int[] deps = new int[entries.size()];
+ int[] chan_locs = new int[entries.size()];
+ for (BitStreamInfo bsi : entries) {
+ if (bsi.strmtyp == 1) {
+ deps[bsi.substreamid]++;
+ chan_locs[bsi.substreamid] = ((bsi.chanmap >> 6) & 0x100) | ((bsi.chanmap >> 5) & 0xff);
+ }
+ }
+ for (BitStreamInfo bsi : entries) {
+ if (bsi.strmtyp != 1) {
+ EC3SpecificBox.Entry e = new EC3SpecificBox.Entry();
+ e.fscod = bsi.fscod;
+ e.bsid = bsi.bsid;
+ e.bsmod = bsi.bsmod;
+ e.acmod = bsi.acmod;
+ e.lfeon = bsi.lfeon;
+ e.reserved = 0;
+ e.num_dep_sub = deps[bsi.substreamid];
+ e.chan_loc = chan_locs[bsi.substreamid];
+ e.reserved2 = 0;
+ ec3.addEntry(e);
+ }
+ bitrate += bsi.bitrate;
+ frameSize += bsi.frameSize;
+ }
+
+ ec3.setDataRate(bitrate / 1000);
+ audioSampleEntry.addBox(ec3);
+ sampleDescriptionBox.addBox(audioSampleEntry);
+
+ trackMetaData.setCreationTime(new Date());
+ trackMetaData.setModificationTime(new Date());
+ trackMetaData.setLanguage(lang);
+ trackMetaData.setTimescale(samplerate); // Audio tracks always use samplerate as timescale
+
+ samples = new LinkedList<ByteBuffer>();
+ if (!readSamples()) {
+ throw new IOException();
+ }
+ }
+
+
+ public List<ByteBuffer> getSamples() {
+
+ return samples;
+ }
+
+ public SampleDescriptionBox getSampleDescriptionBox() {
+ return sampleDescriptionBox;
+ }
+
+ public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
+ return stts;
+ }
+
+ public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
+ return null;
+ }
+
+ public long[] getSyncSamples() {
+ return null;
+ }
+
+ public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
+ return null;
+ }
+
+ public TrackMetaData getTrackMetaData() {
+ return trackMetaData;
+ }
+
+ public String getHandler() {
+ return "soun";
+ }
+
+ public AbstractMediaHeaderBox getMediaHeaderBox() {
+ return new SoundMediaHeaderBox();
+ }
+
+ public SubSampleInformationBox getSubsampleInformationBox() {
+ return null;
+ }
+
+ private BitStreamInfo readVariables() throws IOException {
+ byte[] data = new byte[200];
+ inputStream.mark(200);
+ if (200 != inputStream.read(data, 0, 200)) {
+ return null;
+ }
+ inputStream.reset(); // Rewind
+ ByteBuffer bb = ByteBuffer.wrap(data);
+ BitReaderBuffer brb = new BitReaderBuffer(bb);
+ int syncword = brb.readBits(16);
+ if (syncword != 0xb77) {
+ return null;
+ }
+
+ BitStreamInfo entry = new BitStreamInfo();
+
+ entry.strmtyp = brb.readBits(2);
+ entry.substreamid = brb.readBits(3);
+ int frmsiz = brb.readBits(11);
+ entry.frameSize = 2 * (frmsiz + 1);
+
+ entry.fscod = brb.readBits(2);
+ int fscod2 = -1;
+ int numblkscod;
+ if (entry.fscod == 3) {
+ fscod2 = brb.readBits(2);
+ numblkscod = 3;
+ } else {
+ numblkscod = brb.readBits(2);
+ }
+ int numberOfBlocksPerSyncFrame = 0;
+ switch (numblkscod) {
+ case 0:
+ numberOfBlocksPerSyncFrame = 1;
+ break;
+
+ case 1:
+ numberOfBlocksPerSyncFrame = 2;
+ break;
+
+ case 2:
+ numberOfBlocksPerSyncFrame = 3;
+ break;
+
+ case 3:
+ numberOfBlocksPerSyncFrame = 6;
+ break;
+
+ }
+ entry.frameSize *= (6 / numberOfBlocksPerSyncFrame);
+
+ entry.acmod = brb.readBits(3);
+ entry.lfeon = brb.readBits(1);
+ entry.bsid = brb.readBits(5);
+ brb.readBits(5);
+ if (1 == brb.readBits(1)) {
+ brb.readBits(8); // compr
+ }
+ if (0 == entry.acmod) {
+ brb.readBits(5);
+ if (1 == brb.readBits(1)) {
+ brb.readBits(8);
+ }
+ }
+ if (1 == entry.strmtyp) {
+ if (1 == brb.readBits(1)) {
+ entry.chanmap = brb.readBits(16);
+ }
+ }
+ if (1 == brb.readBits(1)) { // mixing metadata
+ if (entry.acmod > 2) {
+ brb.readBits(2);
+ }
+ if (1 == (entry.acmod & 1) && entry.acmod > 2) {
+ brb.readBits(3);
+ brb.readBits(3);
+ }
+ if (0 < (entry.acmod & 4)) {
+ brb.readBits(3);
+ brb.readBits(3);
+ }
+ if (1 == entry.lfeon) {
+ if (1 == brb.readBits(1)) {
+ brb.readBits(5);
+ }
+ }
+ if (0 == entry.strmtyp) {
+ if (1 == brb.readBits(1)) {
+ brb.readBits(6);
+ }
+ if (0 == entry.acmod) {
+ if (1 == brb.readBits(1)) {
+ brb.readBits(6);
+ }
+ }
+ if (1 == brb.readBits(1)) {
+ brb.readBits(6);
+ }
+ int mixdef = brb.readBits(2);
+ if (1 == mixdef) {
+ brb.readBits(5);
+ } else if (2 == mixdef) {
+ brb.readBits(12);
+ } else if (3 == mixdef) {
+ int mixdeflen = brb.readBits(5);
+ if (1 == brb.readBits(1)) {
+ brb.readBits(5);
+ if (1 == brb.readBits(1)) {
+ brb.readBits(4);
+ }
+ if (1 == brb.readBits(1)) {
+ brb.readBits(4);
+ }
+ if (1 == brb.readBits(1)) {
+ brb.readBits(4);
+ }
+ if (1 == brb.readBits(1)) {
+ brb.readBits(4);
+ }
+ if (1 == brb.readBits(1)) {
+ brb.readBits(4);
+ }
+ if (1 == brb.readBits(1)) {
+ brb.readBits(4);
+ }
+ if (1 == brb.readBits(1)) {
+ brb.readBits(4);
+ }
+ if (1 == brb.readBits(1)) {
+ if (1 == brb.readBits(1)) {
+ brb.readBits(4);
+ }
+ if (1 == brb.readBits(1)) {
+ brb.readBits(4);
+ }
+ }
+ }
+ if (1 == brb.readBits(1)) {
+ brb.readBits(5);
+ if (1 == brb.readBits(1)) {
+ brb.readBits(7);
+ if (1 == brb.readBits(1)) {
+ brb.readBits(8);
+ }
+ }
+ }
+ for (int i = 0; i < (mixdeflen + 2); i++) {
+ brb.readBits(8);
+ }
+ brb.byteSync();
+ }
+ if (entry.acmod < 2) {
+ if (1 == brb.readBits(1)) {
+ brb.readBits(14);
+ }
+ if (0 == entry.acmod) {
+ if (1 == brb.readBits(1)) {
+ brb.readBits(14);
+ }
+ }
+ if (1 == brb.readBits(1)) {
+ if (numblkscod == 0) {
+ brb.readBits(5);
+ } else {
+ for (int i = 0; i < numberOfBlocksPerSyncFrame; i++) {
+ if (1 == brb.readBits(1)) {
+ brb.readBits(5);
+ }
+ }
+ }
+
+ }
+ }
+ }
+ }
+ if (1 == brb.readBits(1)) { // infomdate
+ entry.bsmod = brb.readBits(3);
+ }
+
+ switch (entry.fscod) {
+ case 0:
+ entry.samplerate = 48000;
+ break;
+
+ case 1:
+ entry.samplerate = 44100;
+ break;
+
+ case 2:
+ entry.samplerate = 32000;
+ break;
+
+ case 3: {
+ switch (fscod2) {
+ case 0:
+ entry.samplerate = 24000;
+ break;
+
+ case 1:
+ entry.samplerate = 22050;
+ break;
+
+ case 2:
+ entry.samplerate = 16000;
+ break;
+
+ case 3:
+ entry.samplerate = 0;
+ break;
+ }
+ break;
+ }
+
+ }
+ if (entry.samplerate == 0) {
+ return null;
+ }
+
+ entry.bitrate = (int) (((double) entry.samplerate) / 1536.0 * entry.frameSize * 8);
+
+ return entry;
+ }
+
+ private boolean readSamples() throws IOException {
+ int read = frameSize;
+ boolean ret = false;
+ while (frameSize == read) {
+ ret = true;
+ byte[] data = new byte[frameSize];
+ read = inputStream.read(data);
+ if (read == frameSize) {
+ samples.add(ByteBuffer.wrap(data));
+ stts.add(new TimeToSampleBox.Entry(1, 1536));
+ }
+ }
+ return ret;
+ }
+
+ public static class BitStreamInfo extends EC3SpecificBox.Entry {
+ public int frameSize;
+ public int substreamid;
+ public int bitrate;
+ public int samplerate;
+ public int strmtyp;
+ public int chanmap;
+
+ @Override
+ public String toString() {
+ return "BitStreamInfo{" +
+ "frameSize=" + frameSize +
+ ", substreamid=" + substreamid +
+ ", bitrate=" + bitrate +
+ ", samplerate=" + samplerate +
+ ", strmtyp=" + strmtyp +
+ ", chanmap=" + chanmap +
+ '}';
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "EC3TrackImpl{" +
+ "bitrate=" + bitrate +
+ ", samplerate=" + samplerate +
+ ", entries=" + entries +
+ '}';
+ }
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/H264TrackImpl.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/H264TrackImpl.java
new file mode 100644
index 0000000..b3c1866
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/H264TrackImpl.java
@@ -0,0 +1,740 @@
+package com.googlecode.mp4parser.authoring.tracks;
+
+import com.coremedia.iso.boxes.*;
+import com.coremedia.iso.boxes.h264.AvcConfigurationBox;
+import com.coremedia.iso.boxes.sampleentry.VisualSampleEntry;
+import com.googlecode.mp4parser.authoring.AbstractTrack;
+import com.googlecode.mp4parser.authoring.TrackMetaData;
+import com.googlecode.mp4parser.h264.model.PictureParameterSet;
+import com.googlecode.mp4parser.h264.model.SeqParameterSet;
+import com.googlecode.mp4parser.h264.read.CAVLCReader;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.logging.Logger;
+
+/**
+ * The <code>H264TrackImpl</code> creates a <code>Track</code> from an H.264
+ * Annex B file.
+ */
+public class H264TrackImpl extends AbstractTrack {
+ private static final Logger LOG = Logger.getLogger(H264TrackImpl.class.getName());
+
+ TrackMetaData trackMetaData = new TrackMetaData();
+ SampleDescriptionBox sampleDescriptionBox;
+
+ private ReaderWrapper reader;
+ private List<ByteBuffer> samples;
+ boolean readSamples = false;
+
+ List<TimeToSampleBox.Entry> stts;
+ List<CompositionTimeToSample.Entry> ctts;
+ List<SampleDependencyTypeBox.Entry> sdtp;
+ List<Integer> stss;
+
+ SeqParameterSet seqParameterSet = null;
+ PictureParameterSet pictureParameterSet = null;
+ LinkedList<byte[]> seqParameterSetList = new LinkedList<byte[]>();
+ LinkedList<byte[]> pictureParameterSetList = new LinkedList<byte[]>();
+
+ private int width;
+ private int height;
+ private int timescale;
+ private int frametick;
+ private int currentScSize;
+ private int prevScSize;
+
+ private SEIMessage seiMessage;
+ int frameNrInGop = 0;
+ private boolean determineFrameRate = true;
+ private String lang = "und";
+
+ public H264TrackImpl(InputStream inputStream, String lang, long timescale) throws IOException {
+ this.lang = lang;
+ if (timescale > 1000) {
+ timescale = timescale; //e.g. 23976
+ frametick = 1000;
+ determineFrameRate = false;
+ } else {
+ throw new IllegalArgumentException("Timescale must be specified in milliseconds!");
+ }
+ parse(inputStream);
+ }
+
+ public H264TrackImpl(InputStream inputStream, String lang) throws IOException {
+ this.lang = lang;
+ parse(inputStream);
+ }
+
+ public H264TrackImpl(InputStream inputStream) throws IOException {
+ parse(inputStream);
+ }
+
+ private void parse(InputStream inputStream) throws IOException {
+ this.reader = new ReaderWrapper(inputStream);
+ stts = new LinkedList<TimeToSampleBox.Entry>();
+ ctts = new LinkedList<CompositionTimeToSample.Entry>();
+ sdtp = new LinkedList<SampleDependencyTypeBox.Entry>();
+ stss = new LinkedList<Integer>();
+
+ samples = new LinkedList<ByteBuffer>();
+ if (!readSamples()) {
+ throw new IOException();
+ }
+
+ if (!readVariables()) {
+ throw new IOException();
+ }
+
+ sampleDescriptionBox = new SampleDescriptionBox();
+ VisualSampleEntry visualSampleEntry = new VisualSampleEntry("avc1");
+ visualSampleEntry.setDataReferenceIndex(1);
+ visualSampleEntry.setDepth(24);
+ visualSampleEntry.setFrameCount(1);
+ visualSampleEntry.setHorizresolution(72);
+ visualSampleEntry.setVertresolution(72);
+ visualSampleEntry.setWidth(width);
+ visualSampleEntry.setHeight(height);
+ visualSampleEntry.setCompressorname("AVC Coding");
+
+ AvcConfigurationBox avcConfigurationBox = new AvcConfigurationBox();
+
+ avcConfigurationBox.setSequenceParameterSets(seqParameterSetList);
+ avcConfigurationBox.setPictureParameterSets(pictureParameterSetList);
+ avcConfigurationBox.setAvcLevelIndication(seqParameterSet.level_idc);
+ avcConfigurationBox.setAvcProfileIndication(seqParameterSet.profile_idc);
+ avcConfigurationBox.setBitDepthLumaMinus8(seqParameterSet.bit_depth_luma_minus8);
+ avcConfigurationBox.setBitDepthChromaMinus8(seqParameterSet.bit_depth_chroma_minus8);
+ avcConfigurationBox.setChromaFormat(seqParameterSet.chroma_format_idc.getId());
+ avcConfigurationBox.setConfigurationVersion(1);
+ avcConfigurationBox.setLengthSizeMinusOne(3);
+ avcConfigurationBox.setProfileCompatibility(seqParameterSetList.get(0)[1]);
+
+ visualSampleEntry.addBox(avcConfigurationBox);
+ sampleDescriptionBox.addBox(visualSampleEntry);
+
+ trackMetaData.setCreationTime(new Date());
+ trackMetaData.setModificationTime(new Date());
+ trackMetaData.setLanguage(lang);
+ trackMetaData.setTimescale(timescale);
+ trackMetaData.setWidth(width);
+ trackMetaData.setHeight(height);
+ }
+
+ public SampleDescriptionBox getSampleDescriptionBox() {
+ return sampleDescriptionBox;
+ }
+
+ public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
+ return stts;
+ }
+
+ public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
+ return ctts;
+ }
+
+ public long[] getSyncSamples() {
+ long[] returns = new long[stss.size()];
+ for (int i = 0; i < stss.size(); i++) {
+ returns[i] = stss.get(i);
+ }
+ return returns;
+ }
+
+ public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
+ return sdtp;
+ }
+
+ public TrackMetaData getTrackMetaData() {
+ return trackMetaData;
+ }
+
+ public String getHandler() {
+ return "vide";
+ }
+
+ public List<ByteBuffer> getSamples() {
+ return samples;
+ }
+
+ public AbstractMediaHeaderBox getMediaHeaderBox() {
+ return new VideoMediaHeaderBox();
+ }
+
+ public SubSampleInformationBox getSubsampleInformationBox() {
+ return null;
+ }
+
+ private boolean readVariables() {
+ width = (seqParameterSet.pic_width_in_mbs_minus1 + 1) * 16;
+ int mult = 2;
+ if (seqParameterSet.frame_mbs_only_flag) {
+ mult = 1;
+ }
+ height = 16 * (seqParameterSet.pic_height_in_map_units_minus1 + 1) * mult;
+ if (seqParameterSet.frame_cropping_flag) {
+ int chromaArrayType = 0;
+ if (seqParameterSet.residual_color_transform_flag == false) {
+ chromaArrayType = seqParameterSet.chroma_format_idc.getId();
+ }
+ int cropUnitX = 1;
+ int cropUnitY = mult;
+ if (chromaArrayType != 0) {
+ cropUnitX = seqParameterSet.chroma_format_idc.getSubWidth();
+ cropUnitY = seqParameterSet.chroma_format_idc.getSubHeight() * mult;
+ }
+
+ width -= cropUnitX * (seqParameterSet.frame_crop_left_offset + seqParameterSet.frame_crop_right_offset);
+ height -= cropUnitY * (seqParameterSet.frame_crop_top_offset + seqParameterSet.frame_crop_bottom_offset);
+ }
+ return true;
+ }
+
+ private boolean findNextStartcode() throws IOException {
+ byte[] test = new byte[]{-1, -1, -1, -1};
+
+ int c;
+ while ((c = reader.read()) != -1) {
+ test[0] = test[1];
+ test[1] = test[2];
+ test[2] = test[3];
+ test[3] = (byte) c;
+ if (test[0] == 0 && test[1] == 0 && test[2] == 0 && test[3] == 1) {
+ prevScSize = currentScSize;
+ currentScSize = 4;
+ return true;
+ }
+ if (test[0] == 0 && test[1] == 0 && test[2] == 1) {
+ prevScSize = currentScSize;
+ currentScSize = 3;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private enum NALActions {
+ IGNORE, BUFFER, STORE, END
+ }
+
+ private boolean readSamples() throws IOException {
+ if (readSamples) {
+ return true;
+ }
+
+ readSamples = true;
+
+
+ findNextStartcode();
+ reader.mark();
+ long pos = reader.getPos();
+
+ ArrayList<byte[]> buffered = new ArrayList<byte[]>();
+
+ int frameNr = 0;
+
+ while (findNextStartcode()) {
+ long newpos = reader.getPos();
+ int size = (int) (newpos - pos - prevScSize);
+ reader.reset();
+ byte[] data = new byte[size ];
+ reader.read(data);
+ int type = data[0];
+ int nal_ref_idc = (type >> 5) & 3;
+ int nal_unit_type = type & 0x1f;
+ LOG.fine("Found startcode at " + (pos -4) + " Type: " + nal_unit_type + " ref idc: " + nal_ref_idc + " (size " + size + ")");
+ NALActions action = handleNALUnit(nal_ref_idc, nal_unit_type, data);
+ switch (action) {
+ case IGNORE:
+ break;
+
+ case BUFFER:
+ buffered.add(data);
+ break;
+
+ case STORE:
+ int stdpValue = 22;
+ frameNr++;
+ buffered.add(data);
+ ByteBuffer bb = createSample(buffered);
+ boolean IdrPicFlag = false;
+ if (nal_unit_type == 5) {
+ stdpValue += 16;
+ IdrPicFlag = true;
+ }
+ ByteArrayInputStream bs = cleanBuffer(buffered.get(buffered.size() - 1));
+ SliceHeader sh = new SliceHeader(bs, seqParameterSet, pictureParameterSet, IdrPicFlag);
+ if (sh.slice_type == SliceHeader.SliceType.B) {
+ stdpValue += 4;
+ }
+ LOG.fine("Adding sample with size " + bb.capacity() + " and header " + sh);
+ buffered.clear();
+ samples.add(bb);
+ stts.add(new TimeToSampleBox.Entry(1, frametick));
+ if (nal_unit_type == 5) { // IDR Picture
+ stss.add(frameNr);
+ }
+ if (seiMessage.n_frames == 0) {
+ frameNrInGop = 0;
+ }
+ int offset = 0;
+ if (seiMessage.clock_timestamp_flag) {
+ offset = seiMessage.n_frames - frameNrInGop;
+ } else if (seiMessage.removal_delay_flag) {
+ offset = seiMessage.dpb_removal_delay / 2;
+ }
+ ctts.add(new CompositionTimeToSample.Entry(1, offset * frametick));
+ sdtp.add(new SampleDependencyTypeBox.Entry(stdpValue));
+ frameNrInGop++;
+ break;
+
+ case END:
+ return true;
+
+
+ }
+ pos = newpos;
+ reader.seek(currentScSize);
+ reader.mark();
+ }
+ return true;
+ }
+
+ private ByteBuffer createSample(List<byte[]> buffers) {
+ int outsize = 0;
+ for (int i = 0; i < buffers.size(); i++) {
+ outsize += buffers.get(i).length + 4;
+ }
+ byte[] output = new byte[outsize];
+
+ ByteBuffer bb = ByteBuffer.wrap(output);
+ for (int i = 0; i < buffers.size(); i++) {
+ bb.putInt(buffers.get(i).length);
+ bb.put(buffers.get(i));
+ }
+ bb.rewind();
+ return bb;
+ }
+
+ private ByteArrayInputStream cleanBuffer(byte[] data) {
+ byte[] output = new byte[data.length];
+ int inPos = 0;
+ int outPos = 0;
+ while (inPos < data.length) {
+ if (data[inPos] == 0 && data[inPos + 1] == 0 && data[inPos + 2] == 3) {
+ output[outPos] = 0;
+ output[outPos + 1] = 0;
+ inPos += 3;
+ outPos += 2;
+ } else {
+ output[outPos] = data[inPos];
+ inPos++;
+ outPos++;
+ }
+ }
+ return new ByteArrayInputStream(output, 0, outPos);
+ }
+
+ private NALActions handleNALUnit(int nal_ref_idc, int nal_unit_type, byte[] data) throws IOException {
+ NALActions action;
+ switch (nal_unit_type) {
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ action = NALActions.STORE; // Will only work in single slice per frame mode!
+ break;
+
+ case 6:
+ seiMessage = new SEIMessage(cleanBuffer(data), seqParameterSet);
+ action = NALActions.BUFFER;
+ break;
+
+ case 9:
+// printAccessUnitDelimiter(data);
+ int type = data[1] >> 5;
+ LOG.fine("Access unit delimiter type: " + type);
+ action = NALActions.BUFFER;
+ break;
+
+
+ case 7:
+ if (seqParameterSet == null) {
+ ByteArrayInputStream is = cleanBuffer(data);
+ is.read();
+ seqParameterSet = SeqParameterSet.read(is);
+ seqParameterSetList.add(data);
+ configureFramerate();
+ }
+ action = NALActions.IGNORE;
+ break;
+
+ case 8:
+ if (pictureParameterSet == null) {
+ ByteArrayInputStream is = new ByteArrayInputStream(data);
+ is.read();
+ pictureParameterSet = PictureParameterSet.read(is);
+ pictureParameterSetList.add(data);
+ }
+ action = NALActions.IGNORE;
+ break;
+
+ case 10:
+ case 11:
+ action = NALActions.END;
+ break;
+
+ default:
+ System.err.println("Unknown NAL unit type: " + nal_unit_type);
+ action = NALActions.IGNORE;
+
+ }
+
+ return action;
+ }
+
+ private void configureFramerate() {
+ if (determineFrameRate) {
+ if (seqParameterSet.vuiParams != null) {
+ timescale = seqParameterSet.vuiParams.time_scale >> 1; // Not sure why, but I found this in several places, and it works...
+ frametick = seqParameterSet.vuiParams.num_units_in_tick;
+ if (timescale == 0 || frametick == 0) {
+ System.err.println("Warning: vuiParams contain invalid values: time_scale: " + timescale + " and frame_tick: " + frametick + ". Setting frame rate to 25fps");
+ timescale = 90000;
+ frametick = 3600;
+ }
+ } else {
+ System.err.println("Warning: Can't determine frame rate. Guessing 25 fps");
+ timescale = 90000;
+ frametick = 3600;
+ }
+ }
+ }
+
+ public void printAccessUnitDelimiter(byte[] data) {
+ LOG.fine("Access unit delimiter: " + (data[1] >> 5));
+ }
+
+ public static class SliceHeader {
+
+ public enum SliceType {
+ P, B, I, SP, SI
+ }
+
+ public int first_mb_in_slice;
+ public SliceType slice_type;
+ public int pic_parameter_set_id;
+ public int colour_plane_id;
+ public int frame_num;
+ public boolean field_pic_flag = false;
+ public boolean bottom_field_flag = false;
+ public int idr_pic_id;
+ public int pic_order_cnt_lsb;
+ public int delta_pic_order_cnt_bottom;
+
+ public SliceHeader(InputStream is, SeqParameterSet sps, PictureParameterSet pps, boolean IdrPicFlag) throws IOException {
+ is.read();
+ CAVLCReader reader = new CAVLCReader(is);
+ first_mb_in_slice = reader.readUE("SliceHeader: first_mb_in_slice");
+ switch (reader.readUE("SliceHeader: slice_type")) {
+ case 0:
+ case 5:
+ slice_type = SliceType.P;
+ break;
+
+ case 1:
+ case 6:
+ slice_type = SliceType.B;
+ break;
+
+ case 2:
+ case 7:
+ slice_type = SliceType.I;
+ break;
+
+ case 3:
+ case 8:
+ slice_type = SliceType.SP;
+ break;
+
+ case 4:
+ case 9:
+ slice_type = SliceType.SI;
+ break;
+
+ }
+ pic_parameter_set_id = reader.readUE("SliceHeader: pic_parameter_set_id");
+ if (sps.residual_color_transform_flag) {
+ colour_plane_id = reader.readU(2, "SliceHeader: colour_plane_id");
+ }
+ frame_num = reader.readU(sps.log2_max_frame_num_minus4 + 4, "SliceHeader: frame_num");
+
+ if (!sps.frame_mbs_only_flag) {
+ field_pic_flag = reader.readBool("SliceHeader: field_pic_flag");
+ if (field_pic_flag) {
+ bottom_field_flag = reader.readBool("SliceHeader: bottom_field_flag");
+ }
+ }
+ if (IdrPicFlag) {
+ idr_pic_id = reader.readUE("SliceHeader: idr_pic_id");
+ if (sps.pic_order_cnt_type == 0) {
+ pic_order_cnt_lsb = reader.readU(sps.log2_max_pic_order_cnt_lsb_minus4 + 4, "SliceHeader: pic_order_cnt_lsb");
+ if (pps.pic_order_present_flag && !field_pic_flag) {
+ delta_pic_order_cnt_bottom = reader.readSE("SliceHeader: delta_pic_order_cnt_bottom");
+ }
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "SliceHeader{" +
+ "first_mb_in_slice=" + first_mb_in_slice +
+ ", slice_type=" + slice_type +
+ ", pic_parameter_set_id=" + pic_parameter_set_id +
+ ", colour_plane_id=" + colour_plane_id +
+ ", frame_num=" + frame_num +
+ ", field_pic_flag=" + field_pic_flag +
+ ", bottom_field_flag=" + bottom_field_flag +
+ ", idr_pic_id=" + idr_pic_id +
+ ", pic_order_cnt_lsb=" + pic_order_cnt_lsb +
+ ", delta_pic_order_cnt_bottom=" + delta_pic_order_cnt_bottom +
+ '}';
+ }
+ }
+
+ private class ReaderWrapper {
+ private InputStream inputStream;
+ private long pos = 0;
+
+ private long markPos = 0;
+
+
+ private ReaderWrapper(InputStream inputStream) {
+ this.inputStream = inputStream;
+ }
+
+ int read() throws IOException {
+ pos++;
+ return inputStream.read();
+ }
+
+ long read(byte[] data) throws IOException {
+ long read = inputStream.read(data);
+ pos += read;
+ return read;
+ }
+
+ long seek(int dist) throws IOException {
+ long seeked = inputStream.skip(dist);
+ pos += seeked;
+ return seeked;
+ }
+
+ public long getPos() {
+ return pos;
+ }
+
+ public void mark() {
+ int i = 1048576;
+ LOG.fine("Marking with " + i + " at " + pos);
+ inputStream.mark(i);
+ markPos = pos;
+ }
+
+
+ public void reset() throws IOException {
+ long diff = pos - markPos;
+ LOG.fine("Resetting to " + markPos + " (pos is " + pos + ") which makes the buffersize " + diff);
+ inputStream.reset();
+ pos = markPos;
+ }
+ }
+
+ public class SEIMessage {
+
+ int payloadType = 0;
+ int payloadSize = 0;
+
+ boolean removal_delay_flag;
+ int cpb_removal_delay;
+ int dpb_removal_delay;
+
+ boolean clock_timestamp_flag;
+ int pic_struct;
+ int ct_type;
+ int nuit_field_based_flag;
+ int counting_type;
+ int full_timestamp_flag;
+ int discontinuity_flag;
+ int cnt_dropped_flag;
+ int n_frames;
+ int seconds_value;
+ int minutes_value;
+ int hours_value;
+ int time_offset_length;
+ int time_offset;
+
+ SeqParameterSet sps;
+
+ public SEIMessage(InputStream is, SeqParameterSet sps) throws IOException {
+ this.sps = sps;
+ is.read();
+ int datasize = is.available();
+ int read = 0;
+ while (read < datasize) {
+ payloadType = 0;
+ payloadSize = 0;
+ int last_payload_type_bytes = is.read();
+ read++;
+ while (last_payload_type_bytes == 0xff) {
+ payloadType += last_payload_type_bytes;
+ last_payload_type_bytes = is.read();
+ read++;
+ }
+ payloadType += last_payload_type_bytes;
+ int last_payload_size_bytes = is.read();
+ read++;
+
+ while (last_payload_size_bytes == 0xff) {
+ payloadSize += last_payload_size_bytes;
+ last_payload_size_bytes = is.read();
+ read++;
+ }
+ payloadSize += last_payload_size_bytes;
+ if (datasize - read >= payloadSize) {
+ if (payloadType == 1) { // pic_timing is what we are interested in!
+ if (sps.vuiParams != null && (sps.vuiParams.nalHRDParams != null || sps.vuiParams.vclHRDParams != null || sps.vuiParams.pic_struct_present_flag)) {
+ byte[] data = new byte[payloadSize];
+ is.read(data);
+ read += payloadSize;
+ CAVLCReader reader = new CAVLCReader(new ByteArrayInputStream(data));
+ if (sps.vuiParams.nalHRDParams != null || sps.vuiParams.vclHRDParams != null) {
+ removal_delay_flag = true;
+ cpb_removal_delay = reader.readU(sps.vuiParams.nalHRDParams.cpb_removal_delay_length_minus1 + 1, "SEI: cpb_removal_delay");
+ dpb_removal_delay = reader.readU(sps.vuiParams.nalHRDParams.dpb_output_delay_length_minus1 + 1, "SEI: dpb_removal_delay");
+ } else {
+ removal_delay_flag = false;
+ }
+ if (sps.vuiParams.pic_struct_present_flag) {
+ pic_struct = reader.readU(4, "SEI: pic_struct");
+ int numClockTS;
+ switch (pic_struct) {
+ case 0:
+ case 1:
+ case 2:
+ default:
+ numClockTS = 1;
+ break;
+
+ case 3:
+ case 4:
+ case 7:
+ numClockTS = 2;
+ break;
+
+ case 5:
+ case 6:
+ case 8:
+ numClockTS = 3;
+ break;
+ }
+ for (int i = 0; i < numClockTS; i++) {
+ clock_timestamp_flag = reader.readBool("pic_timing SEI: clock_timestamp_flag[" + i + "]");
+ if (clock_timestamp_flag) {
+ ct_type = reader.readU(2, "pic_timing SEI: ct_type");
+ nuit_field_based_flag = reader.readU(1, "pic_timing SEI: nuit_field_based_flag");
+ counting_type = reader.readU(5, "pic_timing SEI: counting_type");
+ full_timestamp_flag = reader.readU(1, "pic_timing SEI: full_timestamp_flag");
+ discontinuity_flag = reader.readU(1, "pic_timing SEI: discontinuity_flag");
+ cnt_dropped_flag = reader.readU(1, "pic_timing SEI: cnt_dropped_flag");
+ n_frames = reader.readU(8, "pic_timing SEI: n_frames");
+ if (full_timestamp_flag == 1) {
+ seconds_value = reader.readU(6, "pic_timing SEI: seconds_value");
+ minutes_value = reader.readU(6, "pic_timing SEI: minutes_value");
+ hours_value = reader.readU(5, "pic_timing SEI: hours_value");
+ } else {
+ if (reader.readBool("pic_timing SEI: seconds_flag")) {
+ seconds_value = reader.readU(6, "pic_timing SEI: seconds_value");
+ if (reader.readBool("pic_timing SEI: minutes_flag")) {
+ minutes_value = reader.readU(6, "pic_timing SEI: minutes_value");
+ if (reader.readBool("pic_timing SEI: hours_flag")) {
+ hours_value = reader.readU(5, "pic_timing SEI: hours_value");
+ }
+ }
+ }
+ }
+ if (true) {
+ if (sps.vuiParams.nalHRDParams != null) {
+ time_offset_length = sps.vuiParams.nalHRDParams.time_offset_length;
+ } else if (sps.vuiParams.vclHRDParams != null) {
+ time_offset_length = sps.vuiParams.vclHRDParams.time_offset_length;
+ } else {
+ time_offset_length = 24;
+ }
+ time_offset = reader.readU(24, "pic_timing SEI: time_offset");
+ }
+ }
+ }
+ }
+
+ } else {
+ for (int i = 0; i < payloadSize; i++) {
+ is.read();
+ read++;
+ }
+ }
+ } else {
+ for (int i = 0; i < payloadSize; i++) {
+ is.read();
+ read++;
+ }
+ }
+ } else {
+ read = datasize;
+ }
+ LOG.fine(this.toString());
+ }
+ }
+
+ @Override
+ public String toString() {
+ String out = "SEIMessage{" +
+ "payloadType=" + payloadType +
+ ", payloadSize=" + payloadSize;
+ if (payloadType == 1) {
+ if (sps.vuiParams.nalHRDParams != null || sps.vuiParams.vclHRDParams != null) {
+
+ out += ", cpb_removal_delay=" + cpb_removal_delay +
+ ", dpb_removal_delay=" + dpb_removal_delay;
+ }
+ if (sps.vuiParams.pic_struct_present_flag) {
+ out += ", pic_struct=" + pic_struct;
+ if (clock_timestamp_flag) {
+ out += ", ct_type=" + ct_type +
+ ", nuit_field_based_flag=" + nuit_field_based_flag +
+ ", counting_type=" + counting_type +
+ ", full_timestamp_flag=" + full_timestamp_flag +
+ ", discontinuity_flag=" + discontinuity_flag +
+ ", cnt_dropped_flag=" + cnt_dropped_flag +
+ ", n_frames=" + n_frames +
+ ", seconds_value=" + seconds_value +
+ ", minutes_value=" + minutes_value +
+ ", hours_value=" + hours_value +
+ ", time_offset_length=" + time_offset_length +
+ ", time_offset=" + time_offset;
+ }
+ }
+ }
+ out += '}';
+ return out;
+ }
+ }
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/MultiplyTimeScaleTrack.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/MultiplyTimeScaleTrack.java
new file mode 100644
index 0000000..e9a90e4
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/MultiplyTimeScaleTrack.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.tracks;
+
+import com.coremedia.iso.boxes.*;
+import com.googlecode.mp4parser.authoring.Movie;
+import com.googlecode.mp4parser.authoring.Track;
+import com.googlecode.mp4parser.authoring.TrackMetaData;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import static com.googlecode.mp4parser.util.CastUtils.l2i;
+import static com.googlecode.mp4parser.util.Math.gcd;
+import static com.googlecode.mp4parser.util.Math.lcm;
+import static java.lang.Math.round;
+
+/**
+ * Changes the timescale of a track by wrapping the track.
+ */
+public class MultiplyTimeScaleTrack implements Track {
+ Track source;
+ private int timeScaleFactor;
+
+ public MultiplyTimeScaleTrack(Track source, int timeScaleFactor) {
+ this.source = source;
+ this.timeScaleFactor = timeScaleFactor;
+ }
+
+ public SampleDescriptionBox getSampleDescriptionBox() {
+ return source.getSampleDescriptionBox();
+ }
+
+ public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
+ return adjustTts(source.getDecodingTimeEntries(), timeScaleFactor);
+ }
+
+ public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
+ return adjustCtts(source.getCompositionTimeEntries(), timeScaleFactor);
+ }
+
+ public long[] getSyncSamples() {
+ return source.getSyncSamples();
+ }
+
+ public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
+ return source.getSampleDependencies();
+ }
+
+ public TrackMetaData getTrackMetaData() {
+ TrackMetaData trackMetaData = (TrackMetaData) source.getTrackMetaData().clone();
+ trackMetaData.setTimescale(source.getTrackMetaData().getTimescale() * this.timeScaleFactor);
+ return trackMetaData;
+ }
+
+ public String getHandler() {
+ return source.getHandler();
+ }
+
+ public boolean isEnabled() {
+ return source.isEnabled();
+ }
+
+ public boolean isInMovie() {
+ return source.isInMovie();
+ }
+
+ public boolean isInPreview() {
+ return source.isInPreview();
+ }
+
+ public boolean isInPoster() {
+ return source.isInPoster();
+ }
+
+ public List<ByteBuffer> getSamples() {
+ return source.getSamples();
+ }
+
+
+ static List<CompositionTimeToSample.Entry> adjustCtts(List<CompositionTimeToSample.Entry> source, int timeScaleFactor) {
+ if (source != null) {
+ List<CompositionTimeToSample.Entry> entries2 = new ArrayList<CompositionTimeToSample.Entry>(source.size());
+ for (CompositionTimeToSample.Entry entry : source) {
+ entries2.add(new CompositionTimeToSample.Entry(entry.getCount(), timeScaleFactor * entry.getOffset()));
+ }
+ return entries2;
+ } else {
+ return null;
+ }
+ }
+
+ static List<TimeToSampleBox.Entry> adjustTts(List<TimeToSampleBox.Entry> source, int timeScaleFactor) {
+ LinkedList<TimeToSampleBox.Entry> entries2 = new LinkedList<TimeToSampleBox.Entry>();
+ for (TimeToSampleBox.Entry e : source) {
+ entries2.add(new TimeToSampleBox.Entry(e.getCount(), timeScaleFactor * e.getDelta()));
+ }
+ return entries2;
+ }
+
+ public Box getMediaHeaderBox() {
+ return source.getMediaHeaderBox();
+ }
+
+ public SubSampleInformationBox getSubsampleInformationBox() {
+ return source.getSubsampleInformationBox();
+ }
+
+ @Override
+ public String toString() {
+ return "MultiplyTimeScaleTrack{" +
+ "source=" + source +
+ '}';
+ }
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/QuicktimeTextTrackImpl.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/QuicktimeTextTrackImpl.java
new file mode 100644
index 0000000..8efa399
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/QuicktimeTextTrackImpl.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.tracks;
+
+import com.coremedia.iso.boxes.*;
+import com.coremedia.iso.boxes.sampleentry.TextSampleEntry;
+import com.googlecode.mp4parser.authoring.AbstractTrack;
+import com.googlecode.mp4parser.authoring.TrackMetaData;
+import com.googlecode.mp4parser.boxes.apple.BaseMediaInfoAtom;
+import com.googlecode.mp4parser.boxes.apple.GenericMediaHeaderAtom;
+import com.googlecode.mp4parser.boxes.apple.GenericMediaHeaderTextAtom;
+import com.googlecode.mp4parser.boxes.apple.QuicktimeTextSampleEntry;
+import com.googlecode.mp4parser.boxes.threegpp26245.FontTableBox;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * A Text track as Quicktime Pro would create.
+ */
+public class QuicktimeTextTrackImpl extends AbstractTrack {
+ TrackMetaData trackMetaData = new TrackMetaData();
+ SampleDescriptionBox sampleDescriptionBox;
+ List<Line> subs = new LinkedList<Line>();
+
+ public List<Line> getSubs() {
+ return subs;
+ }
+
+ public QuicktimeTextTrackImpl() {
+ sampleDescriptionBox = new SampleDescriptionBox();
+ QuicktimeTextSampleEntry textTrack = new QuicktimeTextSampleEntry();
+ textTrack.setDataReferenceIndex(1);
+ sampleDescriptionBox.addBox(textTrack);
+
+
+ trackMetaData.setCreationTime(new Date());
+ trackMetaData.setModificationTime(new Date());
+ trackMetaData.setTimescale(1000);
+
+
+ }
+
+
+ public List<ByteBuffer> getSamples() {
+ List<ByteBuffer> samples = new LinkedList<ByteBuffer>();
+ long lastEnd = 0;
+ for (Line sub : subs) {
+ long silentTime = sub.from - lastEnd;
+ if (silentTime > 0) {
+ samples.add(ByteBuffer.wrap(new byte[]{0, 0}));
+ } else if (silentTime < 0) {
+ throw new Error("Subtitle display times may not intersect");
+ }
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(baos);
+ try {
+ dos.writeShort(sub.text.getBytes("UTF-8").length);
+ dos.write(sub.text.getBytes("UTF-8"));
+ dos.close();
+ } catch (IOException e) {
+ throw new Error("VM is broken. Does not support UTF-8");
+ }
+ samples.add(ByteBuffer.wrap(baos.toByteArray()));
+ lastEnd = sub.to;
+ }
+ return samples;
+ }
+
+ public SampleDescriptionBox getSampleDescriptionBox() {
+ return sampleDescriptionBox;
+ }
+
+ public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
+ List<TimeToSampleBox.Entry> stts = new LinkedList<TimeToSampleBox.Entry>();
+ long lastEnd = 0;
+ for (Line sub : subs) {
+ long silentTime = sub.from - lastEnd;
+ if (silentTime > 0) {
+ stts.add(new TimeToSampleBox.Entry(1, silentTime));
+ } else if (silentTime < 0) {
+ throw new Error("Subtitle display times may not intersect");
+ }
+ stts.add(new TimeToSampleBox.Entry(1, sub.to - sub.from));
+ lastEnd = sub.to;
+ }
+ return stts;
+ }
+
+ public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
+ return null;
+ }
+
+ public long[] getSyncSamples() {
+ return null;
+ }
+
+ public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
+ return null;
+ }
+
+ public TrackMetaData getTrackMetaData() {
+ return trackMetaData;
+ }
+
+ public String getHandler() {
+ return "text";
+ }
+
+
+ public static class Line {
+ long from;
+ long to;
+ String text;
+
+
+ public Line(long from, long to, String text) {
+ this.from = from;
+ this.to = to;
+ this.text = text;
+ }
+
+ public long getFrom() {
+ return from;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public long getTo() {
+ return to;
+ }
+ }
+
+ public Box getMediaHeaderBox() {
+ GenericMediaHeaderAtom ghmd = new GenericMediaHeaderAtom();
+ ghmd.addBox(new BaseMediaInfoAtom());
+ ghmd.addBox(new GenericMediaHeaderTextAtom());
+ return ghmd;
+ }
+
+ public SubSampleInformationBox getSubsampleInformationBox() {
+ return null;
+ }
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/ReplaceSampleTrack.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/ReplaceSampleTrack.java
new file mode 100644
index 0000000..81a129d
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/ReplaceSampleTrack.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.tracks;
+
+import com.coremedia.iso.boxes.*;
+import com.googlecode.mp4parser.authoring.AbstractTrack;
+import com.googlecode.mp4parser.authoring.Track;
+import com.googlecode.mp4parser.authoring.TrackMetaData;
+
+import java.nio.ByteBuffer;
+import java.util.AbstractList;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Generates a Track where a single sample has been replaced by a given <code>ByteBuffer</code>.
+ */
+
+public class ReplaceSampleTrack extends AbstractTrack {
+ Track origTrack;
+ private long sampleNumber;
+ private ByteBuffer sampleContent;
+ private List<ByteBuffer> samples;
+
+ public ReplaceSampleTrack(Track origTrack, long sampleNumber, ByteBuffer content) {
+ this.origTrack = origTrack;
+ this.sampleNumber = sampleNumber;
+ this.sampleContent = content;
+ this.samples = new ReplaceASingleEntryList();
+
+ }
+
+ public List<ByteBuffer> getSamples() {
+ return samples;
+ }
+
+ public SampleDescriptionBox getSampleDescriptionBox() {
+ return origTrack.getSampleDescriptionBox();
+ }
+
+ public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
+ return origTrack.getDecodingTimeEntries();
+
+ }
+
+ public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
+ return origTrack.getCompositionTimeEntries();
+
+ }
+
+ synchronized public long[] getSyncSamples() {
+ return origTrack.getSyncSamples();
+ }
+
+ public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
+ return origTrack.getSampleDependencies();
+ }
+
+ public TrackMetaData getTrackMetaData() {
+ return origTrack.getTrackMetaData();
+ }
+
+ public String getHandler() {
+ return origTrack.getHandler();
+ }
+
+ public Box getMediaHeaderBox() {
+ return origTrack.getMediaHeaderBox();
+ }
+
+ public SubSampleInformationBox getSubsampleInformationBox() {
+ return origTrack.getSubsampleInformationBox();
+ }
+
+ private class ReplaceASingleEntryList extends AbstractList<ByteBuffer> {
+ @Override
+ public ByteBuffer get(int index) {
+ if (ReplaceSampleTrack.this.sampleNumber == index) {
+ return ReplaceSampleTrack.this.sampleContent;
+ } else {
+ return ReplaceSampleTrack.this.origTrack.getSamples().get(index);
+ }
+ }
+
+ @Override
+ public int size() {
+ return ReplaceSampleTrack.this.origTrack.getSamples().size();
+ }
+ }
+
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/SilenceTrackImpl.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/SilenceTrackImpl.java
new file mode 100644
index 0000000..f74ab3c
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/SilenceTrackImpl.java
@@ -0,0 +1,98 @@
+package com.googlecode.mp4parser.authoring.tracks;
+
+import com.coremedia.iso.boxes.*;
+import com.googlecode.mp4parser.authoring.Mp4TrackImpl;
+import com.googlecode.mp4parser.authoring.Track;
+import com.googlecode.mp4parser.authoring.TrackMetaData;
+
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * This is just a basic idea how things could work but they don't.
+ */
+public class SilenceTrackImpl implements Track {
+ Track source;
+
+ List<ByteBuffer> samples = new LinkedList<ByteBuffer>();
+ TimeToSampleBox.Entry entry;
+
+ public SilenceTrackImpl(Track ofType, long ms) {
+ source = ofType;
+ if ("mp4a".equals(ofType.getSampleDescriptionBox().getSampleEntry().getType())) {
+ long numFrames = getTrackMetaData().getTimescale() * ms / 1000 / 1024;
+ long standZeit = getTrackMetaData().getTimescale() * ms / numFrames / 1000;
+ entry = new TimeToSampleBox.Entry(numFrames, standZeit);
+
+
+ while (numFrames-- > 0) {
+ samples.add((ByteBuffer) ByteBuffer.wrap(new byte[]{
+ 0x21, 0x10, 0x04, 0x60, (byte) 0x8c, 0x1c,
+ }).rewind());
+ }
+
+ } else {
+ throw new RuntimeException("Tracks of type " + ofType.getClass().getSimpleName() + " are not supported");
+ }
+ }
+
+ public SampleDescriptionBox getSampleDescriptionBox() {
+ return source.getSampleDescriptionBox();
+ }
+
+ public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
+ return Collections.singletonList(entry);
+
+ }
+
+ public TrackMetaData getTrackMetaData() {
+ return source.getTrackMetaData();
+ }
+
+ public String getHandler() {
+ return source.getHandler();
+ }
+
+ public boolean isEnabled() {
+ return source.isEnabled();
+ }
+
+ public boolean isInMovie() {
+ return source.isInMovie();
+ }
+
+ public boolean isInPreview() {
+ return source.isInPreview();
+ }
+
+ public boolean isInPoster() {
+ return source.isInPoster();
+ }
+
+ public List<ByteBuffer> getSamples() {
+ return samples;
+ }
+
+ public Box getMediaHeaderBox() {
+ return source.getMediaHeaderBox();
+ }
+
+ public SubSampleInformationBox getSubsampleInformationBox() {
+ return null;
+ }
+
+ public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
+ return null;
+ }
+
+ public long[] getSyncSamples() {
+ return null;
+ }
+
+ public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
+ return null;
+ }
+
+}
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/TextTrackImpl.java b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/TextTrackImpl.java
new file mode 100644
index 0000000..3bae143
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/TextTrackImpl.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.tracks;
+
+import com.coremedia.iso.boxes.*;
+import com.coremedia.iso.boxes.sampleentry.TextSampleEntry;
+import com.googlecode.mp4parser.authoring.AbstractTrack;
+import com.googlecode.mp4parser.authoring.TrackMetaData;
+import com.googlecode.mp4parser.boxes.threegpp26245.FontTableBox;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ *
+ */
+public class TextTrackImpl extends AbstractTrack {
+ TrackMetaData trackMetaData = new TrackMetaData();
+ SampleDescriptionBox sampleDescriptionBox;
+ List<Line> subs = new LinkedList<Line>();
+
+ public List<Line> getSubs() {
+ return subs;
+ }
+
+ public TextTrackImpl() {
+ sampleDescriptionBox = new SampleDescriptionBox();
+ TextSampleEntry tx3g = new TextSampleEntry("tx3g");
+ tx3g.setDataReferenceIndex(1);
+ tx3g.setStyleRecord(new TextSampleEntry.StyleRecord());
+ tx3g.setBoxRecord(new TextSampleEntry.BoxRecord());
+ sampleDescriptionBox.addBox(tx3g);
+
+ FontTableBox ftab = new FontTableBox();
+ ftab.setEntries(Collections.singletonList(new FontTableBox.FontRecord(1, "Serif")));
+
+ tx3g.addBox(ftab);
+
+
+ trackMetaData.setCreationTime(new Date());
+ trackMetaData.setModificationTime(new Date());
+ trackMetaData.setTimescale(1000); // Text tracks use millieseconds
+
+
+ }
+
+
+ public List<ByteBuffer> getSamples() {
+ List<ByteBuffer> samples = new LinkedList<ByteBuffer>();
+ long lastEnd = 0;
+ for (Line sub : subs) {
+ long silentTime = sub.from - lastEnd;
+ if (silentTime > 0) {
+ samples.add(ByteBuffer.wrap(new byte[]{0, 0}));
+ } else if (silentTime < 0) {
+ throw new Error("Subtitle display times may not intersect");
+ }
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(baos);
+ try {
+ dos.writeShort(sub.text.getBytes("UTF-8").length);
+ dos.write(sub.text.getBytes("UTF-8"));
+ dos.close();
+ } catch (IOException e) {
+ throw new Error("VM is broken. Does not support UTF-8");
+ }
+ samples.add(ByteBuffer.wrap(baos.toByteArray()));
+ lastEnd = sub.to;
+ }
+ return samples;
+ }
+
+ public SampleDescriptionBox getSampleDescriptionBox() {
+ return sampleDescriptionBox;
+ }
+
+ public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
+ List<TimeToSampleBox.Entry> stts = new LinkedList<TimeToSampleBox.Entry>();
+ long lastEnd = 0;
+ for (Line sub : subs) {
+ long silentTime = sub.from - lastEnd;
+ if (silentTime > 0) {
+ stts.add(new TimeToSampleBox.Entry(1, silentTime));
+ } else if (silentTime < 0) {
+ throw new Error("Subtitle display times may not intersect");
+ }
+ stts.add(new TimeToSampleBox.Entry(1, sub.to - sub.from));
+ lastEnd = sub.to;
+ }
+ return stts;
+ }
+
+ public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
+ return null;
+ }
+
+ public long[] getSyncSamples() {
+ return null;
+ }
+
+ public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
+ return null;
+ }
+
+ public TrackMetaData getTrackMetaData() {
+ return trackMetaData;
+ }
+
+ public String getHandler() {
+ return "sbtl";
+ }
+
+
+ public static class Line {
+ long from;
+ long to;
+ String text;
+
+
+ public Line(long from, long to, String text) {
+ this.from = from;
+ this.to = to;
+ this.text = text;
+ }
+
+ public long getFrom() {
+ return from;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public long getTo() {
+ return to;
+ }
+ }
+
+ public AbstractMediaHeaderBox getMediaHeaderBox() {
+ return new NullMediaHeaderBox();
+ }
+
+ public SubSampleInformationBox getSubsampleInformationBox() {
+ return null;
+ }
+}