Skip to content

Commit

Permalink
Fix EventMessage timestamp issue
Browse files Browse the repository at this point in the history
Stop encoding/decoding presentation time as part of the message.
What's actually in emsg boxes is a presentation time delta,
which is why it's only 32 bits, and hence why it doesn't handle
large absolute timestamps. We were using this field to hold
absolute timestamps only for the purpose of passing presentation
times from DashManifestParser.parseEvent back to the calling
method. After this change, we return Pair<Long, EventMessage>
instead.

Issue: #5490
PiperOrigin-RevId: 233561731
  • Loading branch information
ojw28 authored and andrewlewis committed Feb 19, 2019
1 parent b1fcfa9 commit 90c4cd2
Show file tree
Hide file tree
Showing 11 changed files with 161 additions and 158 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,14 @@ private void onEmsgLeafAtomRead(ParsableByteArray atom) {
long presentationTimeDeltaUs =
Util.scaleLargeTimestamp(atom.readUnsignedInt(), C.MICROS_PER_SECOND, timescale);

// The presentation_time_delta is accounted for by adjusting the sample timestamp, so we zero it
// in the sample data before writing it to the track outputs.
int position = atom.getPosition();
atom.data[position - 4] = 0;
atom.data[position - 3] = 0;
atom.data[position - 2] = 0;
atom.data[position - 1] = 0;

// Output the sample data.
for (TrackOutput emsgTrackOutput : emsgTrackOutputs) {
atom.setPosition(Atom.FULL_HEADER_SIZE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,6 @@ public final class EventMessage implements Metadata.Entry {
*/
public final long durationMs;

/**
* The presentation time value of this event message in microseconds.
* <p>
* Except in special cases, application code should <em>not</em> use this field.
*/
public final long presentationTimeUs;

/**
* The instance identifier.
*/
Expand All @@ -70,22 +63,19 @@ public final class EventMessage implements Metadata.Entry {
* @param durationMs The duration of the event in milliseconds.
* @param id The instance identifier.
* @param messageData The body of the message.
* @param presentationTimeUs The presentation time value of this event message in microseconds.
*/
public EventMessage(String schemeIdUri, String value, long durationMs, long id,
byte[] messageData, long presentationTimeUs) {
public EventMessage(
String schemeIdUri, String value, long durationMs, long id, byte[] messageData) {
this.schemeIdUri = schemeIdUri;
this.value = value;
this.durationMs = durationMs;
this.id = id;
this.messageData = messageData;
this.presentationTimeUs = presentationTimeUs;
}

/* package */ EventMessage(Parcel in) {
schemeIdUri = castNonNull(in.readString());
value = castNonNull(in.readString());
presentationTimeUs = in.readLong();
durationMs = in.readLong();
id = in.readLong();
messageData = castNonNull(in.createByteArray());
Expand All @@ -97,7 +87,6 @@ public int hashCode() {
int result = 17;
result = 31 * result + (schemeIdUri != null ? schemeIdUri.hashCode() : 0);
result = 31 * result + (value != null ? value.hashCode() : 0);
result = 31 * result + (int) (presentationTimeUs ^ (presentationTimeUs >>> 32));
result = 31 * result + (int) (durationMs ^ (durationMs >>> 32));
result = 31 * result + (int) (id ^ (id >>> 32));
result = 31 * result + Arrays.hashCode(messageData);
Expand All @@ -115,9 +104,11 @@ public boolean equals(@Nullable Object obj) {
return false;
}
EventMessage other = (EventMessage) obj;
return presentationTimeUs == other.presentationTimeUs && durationMs == other.durationMs
&& id == other.id && Util.areEqual(schemeIdUri, other.schemeIdUri)
&& Util.areEqual(value, other.value) && Arrays.equals(messageData, other.messageData);
return durationMs == other.durationMs
&& id == other.id
&& Util.areEqual(schemeIdUri, other.schemeIdUri)
&& Util.areEqual(value, other.value)
&& Arrays.equals(messageData, other.messageData);
}

@Override
Expand All @@ -136,7 +127,6 @@ public int describeContents() {
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(schemeIdUri);
dest.writeString(value);
dest.writeLong(presentationTimeUs);
dest.writeLong(durationMs);
dest.writeLong(id);
dest.writeByteArray(messageData);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,27 @@
*/
package com.google.android.exoplayer2.metadata.emsg;

import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.MetadataDecoder;
import com.google.android.exoplayer2.metadata.MetadataInputBuffer;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import java.nio.ByteBuffer;
import java.util.Arrays;

/**
* Decodes Event Message (emsg) atoms, as defined in ISO/IEC 23009-1:2014, Section 5.10.3.3.
* <p>
* Atom data should be provided to the decoder without the full atom header (i.e. starting from the
* first byte of the scheme_id_uri field).
*
* <p>Atom data should be provided to the decoder without the full atom header (i.e. starting from
* the first byte of the scheme_id_uri field). It is expected that the presentation_time_delta field
* should be 0, having already been accounted for by adjusting the sample timestamp.
*/
public final class EventMessageDecoder implements MetadataDecoder {

private static final String TAG = "EventMessageDecoder";

@SuppressWarnings("ByteBufferBackingArray")
@Override
public Metadata decode(MetadataInputBuffer inputBuffer) {
Expand All @@ -43,13 +46,16 @@ public Metadata decode(MetadataInputBuffer inputBuffer) {
String schemeIdUri = Assertions.checkNotNull(emsgData.readNullTerminatedString());
String value = Assertions.checkNotNull(emsgData.readNullTerminatedString());
long timescale = emsgData.readUnsignedInt();
long presentationTimeUs = Util.scaleLargeTimestamp(emsgData.readUnsignedInt(),
C.MICROS_PER_SECOND, timescale);
long presentationTimeDelta = emsgData.readUnsignedInt();
if (presentationTimeDelta != 0) {
// We expect the source to have accounted for presentation_time_delta by adjusting the sample
// timestamp and zeroing the field in the sample data. Log a warning if the field is non-zero.
Log.w(TAG, "Ignoring non-zero presentation_time_delta: " + presentationTimeDelta);
}
long durationMs = Util.scaleLargeTimestamp(emsgData.readUnsignedInt(), 1000, timescale);
long id = emsgData.readUnsignedInt();
byte[] messageData = Arrays.copyOfRange(data, emsgData.getPosition(), size);
return new Metadata(new EventMessage(schemeIdUri, value, durationMs, id, messageData,
presentationTimeUs));
return new Metadata(new EventMessage(schemeIdUri, value, durationMs, id, messageData));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@
package com.google.android.exoplayer2.metadata.emsg;

import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
Expand All @@ -37,27 +34,22 @@ public EventMessageEncoder() {
}

/**
* Encodes an {@link EventMessage} to a byte array that can be decoded by
* {@link EventMessageDecoder}.
* Encodes an {@link EventMessage} to a byte array that can be decoded by {@link
* EventMessageDecoder}.
*
* @param eventMessage The event message to be encoded.
* @param timescale Timescale of the event message, in units per second.
* @return The serialized byte array.
*/
@Nullable
public byte[] encode(EventMessage eventMessage, long timescale) {
Assertions.checkArgument(timescale >= 0);
public byte[] encode(EventMessage eventMessage) {
byteArrayOutputStream.reset();
try {
writeNullTerminatedString(dataOutputStream, eventMessage.schemeIdUri);
String nonNullValue = eventMessage.value != null ? eventMessage.value : "";
writeNullTerminatedString(dataOutputStream, nonNullValue);
writeUnsignedInt(dataOutputStream, timescale);
long presentationTime = Util.scaleLargeTimestamp(eventMessage.presentationTimeUs,
timescale, C.MICROS_PER_SECOND);
writeUnsignedInt(dataOutputStream, presentationTime);
long duration = Util.scaleLargeTimestamp(eventMessage.durationMs, timescale, 1000);
writeUnsignedInt(dataOutputStream, duration);
writeUnsignedInt(dataOutputStream, 1000); // timescale
writeUnsignedInt(dataOutputStream, 0); // presentation_time_delta
writeUnsignedInt(dataOutputStream, eventMessage.durationMs);
writeUnsignedInt(dataOutputStream, eventMessage.id);
dataOutputStream.write(eventMessage.messageData);
dataOutputStream.flush();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ public void testDecodeEventMessage() {
assertThat(eventMessage.durationMs).isEqualTo(3000);
assertThat(eventMessage.id).isEqualTo(1000403);
assertThat(eventMessage.messageData).isEqualTo(new byte[]{0, 1, 2, 3, 4});
assertThat(eventMessage.presentationTimeUs).isEqualTo(1000000);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -33,25 +33,27 @@ public final class EventMessageEncoderTest {

@Test
public void testEncodeEventStream() throws IOException {
EventMessage eventMessage = new EventMessage("urn:test", "123", 3000, 1000403,
new byte[] {0, 1, 2, 3, 4}, 1000000);
byte[] expectedEmsgBody = new byte[] {
117, 114, 110, 58, 116, 101, 115, 116, 0, // scheme_id_uri = "urn:test"
49, 50, 51, 0, // value = "123"
0, 0, -69, -128, // timescale = 48000
0, 0, -69, -128, // presentation_time_delta = 48000
0, 2, 50, -128, // event_duration = 144000
0, 15, 67, -45, // id = 1000403
0, 1, 2, 3, 4}; // message_data = {0, 1, 2, 3, 4}
byte[] encodedByteArray = new EventMessageEncoder().encode(eventMessage, 48000);
EventMessage eventMessage =
new EventMessage("urn:test", "123", 3000, 1000403, new byte[] {0, 1, 2, 3, 4});
byte[] expectedEmsgBody =
new byte[] {
117, 114, 110, 58, 116, 101, 115, 116, 0, // scheme_id_uri = "urn:test"
49, 50, 51, 0, // value = "123"
0, 0, 3, -24, // timescale = 1000
0, 0, 0, 0, // presentation_time_delta = 0
0, 0, 11, -72, // event_duration = 3000
0, 15, 67, -45, // id = 1000403
0, 1, 2, 3, 4
}; // message_data = {0, 1, 2, 3, 4}
byte[] encodedByteArray = new EventMessageEncoder().encode(eventMessage);
assertThat(encodedByteArray).isEqualTo(expectedEmsgBody);
}

@Test
public void testEncodeDecodeEventStream() throws IOException {
EventMessage expectedEmsg = new EventMessage("urn:test", "123", 3000, 1000403,
new byte[] {0, 1, 2, 3, 4}, 1000000);
byte[] encodedByteArray = new EventMessageEncoder().encode(expectedEmsg, 48000);
EventMessage expectedEmsg =
new EventMessage("urn:test", "123", 3000, 1000403, new byte[] {0, 1, 2, 3, 4});
byte[] encodedByteArray = new EventMessageEncoder().encode(expectedEmsg);
MetadataInputBuffer buffer = new MetadataInputBuffer();
buffer.data = ByteBuffer.allocate(encodedByteArray.length).put(encodedByteArray);

Expand All @@ -63,30 +65,34 @@ public void testEncodeDecodeEventStream() throws IOException {

@Test
public void testEncodeEventStreamMultipleTimesWorkingCorrectly() throws IOException {
EventMessage eventMessage = new EventMessage("urn:test", "123", 3000, 1000403,
new byte[] {0, 1, 2, 3, 4}, 1000000);
byte[] expectedEmsgBody = new byte[] {
117, 114, 110, 58, 116, 101, 115, 116, 0, // scheme_id_uri = "urn:test"
49, 50, 51, 0, // value = "123"
0, 0, -69, -128, // timescale = 48000
0, 0, -69, -128, // presentation_time_delta = 48000
0, 2, 50, -128, // event_duration = 144000
0, 15, 67, -45, // id = 1000403
0, 1, 2, 3, 4}; // message_data = {0, 1, 2, 3, 4}
EventMessage eventMessage1 = new EventMessage("urn:test", "123", 3000, 1000402,
new byte[] {4, 3, 2, 1, 0}, 1000000);
byte[] expectedEmsgBody1 = new byte[] {
117, 114, 110, 58, 116, 101, 115, 116, 0, // scheme_id_uri = "urn:test"
49, 50, 51, 0, // value = "123"
0, 0, -69, -128, // timescale = 48000
0, 0, -69, -128, // presentation_time_delta = 48000
0, 2, 50, -128, // event_duration = 144000
0, 15, 67, -46, // id = 1000402
4, 3, 2, 1, 0}; // message_data = {4, 3, 2, 1, 0}
EventMessage eventMessage =
new EventMessage("urn:test", "123", 3000, 1000403, new byte[] {0, 1, 2, 3, 4});
byte[] expectedEmsgBody =
new byte[] {
117, 114, 110, 58, 116, 101, 115, 116, 0, // scheme_id_uri = "urn:test"
49, 50, 51, 0, // value = "123"
0, 0, 3, -24, // timescale = 1000
0, 0, 0, 0, // presentation_time_delta = 0
0, 0, 11, -72, // event_duration = 3000
0, 15, 67, -45, // id = 1000403
0, 1, 2, 3, 4
}; // message_data = {0, 1, 2, 3, 4}
EventMessage eventMessage1 =
new EventMessage("urn:test", "123", 3000, 1000402, new byte[] {4, 3, 2, 1, 0});
byte[] expectedEmsgBody1 =
new byte[] {
117, 114, 110, 58, 116, 101, 115, 116, 0, // scheme_id_uri = "urn:test"
49, 50, 51, 0, // value = "123"
0, 0, 3, -24, // timescale = 1000
0, 0, 0, 0, // presentation_time_delta = 0
0, 0, 11, -72, // event_duration = 3000
0, 15, 67, -46, // id = 1000402
4, 3, 2, 1, 0
}; // message_data = {4, 3, 2, 1, 0}
EventMessageEncoder eventMessageEncoder = new EventMessageEncoder();
byte[] encodedByteArray = eventMessageEncoder.encode(eventMessage, 48000);
byte[] encodedByteArray = eventMessageEncoder.encode(eventMessage);
assertThat(encodedByteArray).isEqualTo(expectedEmsgBody);
byte[] encodedByteArray1 = eventMessageEncoder.encode(eventMessage1, 48000);
byte[] encodedByteArray1 = eventMessageEncoder.encode(eventMessage1);
assertThat(encodedByteArray1).isEqualTo(expectedEmsgBody1);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ public final class EventMessageTest {

@Test
public void testEventMessageParcelable() {
EventMessage eventMessage = new EventMessage("urn:test", "123", 3000, 1000403,
new byte[] {0, 1, 2, 3, 4}, 1000);
EventMessage eventMessage =
new EventMessage("urn:test", "123", 3000, 1000403, new byte[] {0, 1, 2, 3, 4});
// Write to parcel.
Parcel parcel = Parcel.obtain();
eventMessage.writeToParcel(parcel, 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,7 @@ public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer,
}
}
int sampleIndex = currentIndex++;
byte[] serializedEvent = eventMessageEncoder.encode(eventStream.events[sampleIndex],
eventStream.timescale);
byte[] serializedEvent = eventMessageEncoder.encode(eventStream.events[sampleIndex]);
if (serializedEvent != null) {
buffer.ensureSpaceForWrite(serializedEvent.length);
buffer.setFlags(C.BUFFER_FLAG_KEY_FRAME);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -834,13 +834,13 @@ protected EventStream parseEventStream(XmlPullParser xpp)
String schemeIdUri = parseString(xpp, "schemeIdUri", "");
String value = parseString(xpp, "value", "");
long timescale = parseLong(xpp, "timescale", 1);
List<EventMessage> eventMessages = new ArrayList<>();
List<Pair<Long, EventMessage>> eventMessages = new ArrayList<>();
ByteArrayOutputStream scratchOutputStream = new ByteArrayOutputStream(512);
do {
xpp.next();
if (XmlPullParserUtil.isStartTag(xpp, "Event")) {
EventMessage event = parseEvent(xpp, schemeIdUri, value, timescale,
scratchOutputStream);
Pair<Long, EventMessage> event =
parseEvent(xpp, schemeIdUri, value, timescale, scratchOutputStream);
eventMessages.add(event);
} else {
maybeSkipTag(xpp);
Expand All @@ -850,9 +850,9 @@ protected EventStream parseEventStream(XmlPullParser xpp)
long[] presentationTimesUs = new long[eventMessages.size()];
EventMessage[] events = new EventMessage[eventMessages.size()];
for (int i = 0; i < eventMessages.size(); i++) {
EventMessage event = eventMessages.get(i);
presentationTimesUs[i] = event.presentationTimeUs;
events[i] = event;
Pair<Long, EventMessage> event = eventMessages.get(i);
presentationTimesUs[i] = event.first;
events[i] = event.second;
}
return buildEventStream(schemeIdUri, value, timescale, presentationTimesUs, events);
}
Expand All @@ -871,11 +871,12 @@ protected EventStream buildEventStream(String schemeIdUri, String value, long ti
* @param timescale The timescale of the parent EventStream.
* @param scratchOutputStream A {@link ByteArrayOutputStream} that is used when parsing event
* objects.
* @return The {@link EventMessage} parsed from this EventStream node.
* @return A pair containing the node's presentation timestamp in microseconds and the parsed
* {@link EventMessage}.
* @throws XmlPullParserException If there is any error parsing this node.
* @throws IOException If there is any error reading from the underlying input stream.
*/
protected EventMessage parseEvent(
protected Pair<Long, EventMessage> parseEvent(
XmlPullParser xpp,
String schemeIdUri,
String value,
Expand All @@ -890,13 +891,14 @@ protected EventMessage parseEvent(
timescale);
String messageData = parseString(xpp, "messageData", null);
byte[] eventObject = parseEventObject(xpp, scratchOutputStream);
return buildEvent(
schemeIdUri,
value,
id,
durationMs,
messageData == null ? eventObject : Util.getUtf8Bytes(messageData),
presentationTimesUs);
return Pair.create(
presentationTimesUs,
buildEvent(
schemeIdUri,
value,
id,
durationMs,
messageData == null ? eventObject : Util.getUtf8Bytes(messageData)));
}

/**
Expand Down Expand Up @@ -963,9 +965,9 @@ protected byte[] parseEventObject(XmlPullParser xpp, ByteArrayOutputStream scrat
return scratchOutputStream.toByteArray();
}

protected EventMessage buildEvent(String schemeIdUri, String value, long id,
long durationMs, byte[] messageData, long presentationTimeUs) {
return new EventMessage(schemeIdUri, value, durationMs, id, messageData, presentationTimeUs);
protected EventMessage buildEvent(
String schemeIdUri, String value, long id, long durationMs, byte[] messageData) {
return new EventMessage(schemeIdUri, value, durationMs, id, messageData);
}

protected List<SegmentTimelineElement> parseSegmentTimeline(XmlPullParser xpp)
Expand Down
Loading

0 comments on commit 90c4cd2

Please sign in to comment.