From f2634d11f5abb80bb06596419e9d7cfa502726ed Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 6 Jan 2017 08:07:15 -0800 Subject: [PATCH] Support emsg metadata decoding Note: End to end emsg support is still non-functional. There's some additional plumbing that still needs to be done. Issue: #2176 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=143775147 --- .../emsg/EventMessageDecoderTest.java | 47 ++++++ .../metadata/emsg/EventMessageTest.java | 39 +++++ .../metadata/id3/Id3DecoderTest.java | 4 +- .../metadata/emsg/EventMessage.java | 142 ++++++++++++++++++ .../metadata/emsg/EventMessageDecoder.java | 50 ++++++ .../android/exoplayer2/util/MimeTypes.java | 1 + 6 files changed, 281 insertions(+), 2 deletions(-) create mode 100644 library/src/androidTest/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoderTest.java create mode 100644 library/src/androidTest/java/com/google/android/exoplayer2/metadata/emsg/EventMessageTest.java create mode 100644 library/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessage.java create mode 100644 library/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoderTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoderTest.java new file mode 100644 index 00000000000..51219b60d25 --- /dev/null +++ b/library/src/androidTest/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoderTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * 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.google.android.exoplayer2.metadata.emsg; + +import android.test.MoreAsserts; +import com.google.android.exoplayer2.metadata.Metadata; +import junit.framework.TestCase; + +/** + * Test for {@link EventMessageDecoder}. + */ +public final class EventMessageDecoderTest extends TestCase { + + public void testDecodeEventMessage() { + byte[] rawEmsgBody = 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, 0, 0, // presentation_time_delta (ignored) = 0 + 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} + EventMessageDecoder decoder = new EventMessageDecoder(); + Metadata metadata = decoder.decode(rawEmsgBody, rawEmsgBody.length); + assertEquals(1, metadata.length()); + EventMessage eventMessage = (EventMessage) metadata.get(0); + assertEquals("urn:test", eventMessage.schemeIdUri); + assertEquals("123", eventMessage.value); + assertEquals(3000, eventMessage.durationMs); + assertEquals(1000403, eventMessage.id); + MoreAsserts.assertEquals(new byte[] {0, 1, 2, 3, 4}, eventMessage.messageData); + } + +} diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/metadata/emsg/EventMessageTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/metadata/emsg/EventMessageTest.java new file mode 100644 index 00000000000..baafb6b18b9 --- /dev/null +++ b/library/src/androidTest/java/com/google/android/exoplayer2/metadata/emsg/EventMessageTest.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * 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.google.android.exoplayer2.metadata.emsg; + +import android.os.Parcel; +import junit.framework.TestCase; + +/** + * Test for {@link EventMessage}. + */ +public final class EventMessageTest extends TestCase { + + public void testEventMessageParcelable() { + 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); + // Create from parcel. + parcel.setDataPosition(0); + EventMessage fromParcelEventMessage = EventMessage.CREATOR.createFromParcel(parcel); + // Assert equals. + assertEquals(eventMessage, fromParcelEventMessage); + } + +} diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java index 83bd18f8779..20b026d670f 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java @@ -21,9 +21,9 @@ import junit.framework.TestCase; /** - * Test for {@link Id3Decoder} + * Test for {@link Id3Decoder}. */ -public class Id3DecoderTest extends TestCase { +public final class Id3DecoderTest extends TestCase { public void testDecodeTxxxFrame() throws MetadataDecoderException { byte[] rawId3 = new byte[] {73, 68, 51, 4, 0, 0, 0, 0, 0, 41, 84, 88, 88, 88, 0, 0, 0, 31, 0, 0, diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessage.java b/library/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessage.java new file mode 100644 index 00000000000..9d6d0af60ce --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessage.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * 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.google.android.exoplayer2.metadata.emsg; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.google.android.exoplayer2.metadata.Metadata; +import com.google.android.exoplayer2.util.Util; +import java.util.Arrays; + +/** + * An Event Message (emsg) as defined in ISO 23009-1. + */ +public final class EventMessage implements Metadata.Entry { + + /** + * The message scheme. + */ + public final String schemeIdUri; + + /** + * The value for the event. + */ + public final String value; + + /** + * The duration of the event in milliseconds. + */ + public final long durationMs; + + /** + * The instance identifier. + */ + public final long id; + + /** + * The body of the message. + */ + public final byte[] messageData; + + // Lazily initialized hashcode. + private int hashCode; + + /** + * + * @param schemeIdUri The message scheme. + * @param value The value for the event. + * @param durationMs The duration of the event in milliseconds. + * @param id The instance identifier. + * @param messageData The body of the message. + */ + 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; + } + + /* package */ EventMessage(Parcel in) { + schemeIdUri = in.readString(); + value = in.readString(); + durationMs = in.readLong(); + id = in.readLong(); + messageData = in.createByteArray(); + } + + @Override + public int hashCode() { + if (hashCode == 0) { + int result = 17; + result = 31 * result + (schemeIdUri != null ? schemeIdUri.hashCode() : 0); + result = 31 * result + (value != null ? value.hashCode() : 0); + result = 31 * result + (int) (durationMs ^ (durationMs >>> 32)); + result = 31 * result + (int) (id ^ (id >>> 32)); + result = 31 * result + Arrays.hashCode(messageData); + hashCode = result; + } + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + EventMessage other = (EventMessage) obj; + return durationMs == other.durationMs && id == other.id + && Util.areEqual(schemeIdUri, other.schemeIdUri) && Util.areEqual(value, other.value) + && Arrays.equals(messageData, other.messageData); + } + + // Parcelable implementation. + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(schemeIdUri); + dest.writeString(value); + dest.writeLong(durationMs); + dest.writeLong(id); + dest.writeByteArray(messageData); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + @Override + public EventMessage createFromParcel(Parcel in) { + return new EventMessage(in); + } + + @Override + public EventMessage[] newArray(int size) { + return new EventMessage[size]; + } + + }; + +} diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java b/library/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java new file mode 100644 index 00000000000..eaf81a37754 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * 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.google.android.exoplayer2.metadata.emsg; + +import com.google.android.exoplayer2.metadata.Metadata; +import com.google.android.exoplayer2.metadata.MetadataDecoder; +import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.ParsableByteArray; +import java.util.Arrays; + +/** + * Decodes Event Message (emsg) atoms, as defined in ISO 23009-1. + *

+ * 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). + */ +public final class EventMessageDecoder implements MetadataDecoder { + + @Override + public boolean canDecode(String mimeType) { + return MimeTypes.APPLICATION_EMSG.equals(mimeType); + } + + @Override + public Metadata decode(byte[] data, int size) { + ParsableByteArray emsgData = new ParsableByteArray(data, size); + String schemeIdUri = emsgData.readNullTerminatedString(); + String value = emsgData.readNullTerminatedString(); + long timescale = emsgData.readUnsignedInt(); + emsgData.skipBytes(4); // presentation_time_delta + long durationMs = (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)); + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java b/library/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java index 84e1f427076..9870b6547a0 100644 --- a/library/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java +++ b/library/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java @@ -79,6 +79,7 @@ public final class MimeTypes { public static final String APPLICATION_PGS = BASE_TYPE_APPLICATION + "/pgs"; public static final String APPLICATION_SCTE35 = BASE_TYPE_APPLICATION + "/x-scte35"; public static final String APPLICATION_CAMERA_MOTION = BASE_TYPE_APPLICATION + "/x-camera-motion"; + public static final String APPLICATION_EMSG = BASE_TYPE_APPLICATION + "/x-emsg"; private MimeTypes() {}