• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.media;
18 
19 import android.graphics.Bitmap;
20 import android.os.Parcel;
21 import android.util.Log;
22 
23 import java.util.Calendar;
24 import java.util.Collections;
25 import java.util.Date;
26 import java.util.HashMap;
27 import java.util.Set;
28 import java.util.TimeZone;
29 
30 
31 /**
32    Class to hold the media's metadata.  Metadata are used
33    for human consumption and can be embedded in the media (e.g
34    shoutcast) or available from an external source. The source can be
35    local (e.g thumbnail stored in the DB) or remote (e.g caption
36    server).
37 
38    Metadata is like a Bundle. It is sparse and each key can occur at
39    most once. The key is an integer and the value is the actual metadata.
40 
41    The caller is expected to know the type of the metadata and call
42    the right get* method to fetch its value.
43 
44    // FIXME: unhide.
45    {@hide}
46  */
47 public class Metadata
48 {
49     // The metadata are keyed using integers rather than more heavy
50     // weight strings. We considered using Bundle to ship the metadata
51     // between the native layer and the java layer but dropped that
52     // option since keeping in sync a native implementation of Bundle
53     // and the java one would be too burdensome. Besides Bundle uses
54     // String for its keys.
55     // The key range [0 8192) is reserved for the system.
56     //
57     // We manually serialize the data in Parcels. For large memory
58     // blob (bitmaps, raw pictures) we use MemoryFile which allow the
59     // client to make the data purge-able once it is done with it.
60     //
61 
62     public static final int ANY = 0;  // Never used for metadata returned, only for filtering.
63                                       // Keep in sync with kAny in MediaPlayerService.cpp
64 
65     // TODO: Should we use numbers compatible with the metadata retriever?
66     public static final int TITLE = 1;           // String
67     public static final int COMMENT = 2;         // String
68     public static final int COPYRIGHT = 3;       // String
69     public static final int ALBUM = 4;           // String
70     public static final int ARTIST = 5;          // String
71     public static final int AUTHOR = 6;          // String
72     public static final int COMPOSER = 7;        // String
73     public static final int GENRE = 8;           // String
74     public static final int DATE = 9;            // Date
75     public static final int DURATION = 10;       // Integer(millisec)
76     public static final int CD_TRACK_NUM = 11;   // Integer 1-based
77     public static final int CD_TRACK_MAX = 12;   // Integer
78     public static final int RATING = 13;         // String
79     public static final int ALBUM_ART = 14;      // byte[]
80     public static final int VIDEO_FRAME = 15;    // Bitmap
81     public static final int CAPTION = 16;        // TimedText
82 
83     public static final int BIT_RATE = 17;       // Integer, Aggregate rate of
84                                                  // all the streams in bps.
85 
86     public static final int AUDIO_BIT_RATE = 18; // Integer, bps
87     public static final int VIDEO_BIT_RATE = 19; // Integer, bps
88     public static final int AUDIO_SAMPLE_RATE = 20; // Integer, Hz
89     public static final int VIDEO_FRAME_RATE = 21;  // Integer, Hz
90 
91     // See RFC2046 and RFC4281.
92     public static final int MIME_TYPE = 22;      // String
93     public static final int AUDIO_CODEC = 23;    // String
94     public static final int VIDEO_CODEC = 24;    // String
95 
96     public static final int VIDEO_HEIGHT = 25;   // Integer
97     public static final int VIDEO_WIDTH = 26;    // Integer
98     public static final int NUM_TRACKS = 27;     // Integer
99     public static final int DRM_CRIPPLED = 28;   // Boolean
100 
101     // Playback capabilities.
102     public static final int PAUSE_AVAILABLE = 29;         // Boolean
103     public static final int SEEK_BACKWARD_AVAILABLE = 30; // Boolean
104     public static final int SEEK_FORWARD_AVAILABLE = 31;  // Boolean
105     public static final int SEEK_AVAILABLE = 32;          // Boolean
106 
107     private static final int LAST_SYSTEM = 32;
108     private static final int FIRST_CUSTOM = 8192;
109 
110     // Shorthands to set the MediaPlayer's metadata filter.
111     public static final Set<Integer> MATCH_NONE = Collections.EMPTY_SET;
112     public static final Set<Integer> MATCH_ALL = Collections.singleton(ANY);
113 
114     public static final int STRING_VAL     = 1;
115     public static final int INTEGER_VAL    = 2;
116     public static final int BOOLEAN_VAL    = 3;
117     public static final int LONG_VAL       = 4;
118     public static final int DOUBLE_VAL     = 5;
119     public static final int TIMED_TEXT_VAL = 6;
120     public static final int DATE_VAL       = 7;
121     public static final int BYTE_ARRAY_VAL = 8;
122     // FIXME: misses a type for shared heap is missing (MemoryFile).
123     // FIXME: misses a type for bitmaps.
124     private static final int LAST_TYPE = 8;
125 
126     private static final String TAG = "media.Metadata";
127     private static final int kInt32Size = 4;
128     private static final int kMetaHeaderSize = 2 * kInt32Size; //  size + marker
129     private static final int kRecordHeaderSize = 3 * kInt32Size; // size + id + type
130 
131     private static final int kMetaMarker = 0x4d455441;  // 'M' 'E' 'T' 'A'
132 
133     // After a successful parsing, set the parcel with the serialized metadata.
134     private Parcel mParcel;
135 
136     // Map to associate a Metadata key (e.g TITLE) with the offset of
137     // the record's payload in the parcel.
138     // Used to look up if a key was present too.
139     // Key: Metadata ID
140     // Value: Offset of the metadata type field in the record.
141     private final HashMap<Integer, Integer> mKeyToPosMap =
142             new HashMap<Integer, Integer>();
143 
144     /**
145      * Helper class to hold a triple (time, duration, text). Can be used to
146      * implement caption.
147      */
148     public class TimedText {
149         private Date mTime;
150         private int mDuration;  // millisec
151         private String mText;
152 
TimedText(Date time, int duration, String text)153         public TimedText(Date time, int duration, String text) {
154             mTime = time;
155             mDuration = duration;
156             mText = text;
157         }
158 
toString()159         public String toString() {
160             StringBuilder res = new StringBuilder(80);
161             res.append(mTime).append("-").append(mDuration)
162                     .append(":").append(mText);
163             return res.toString();
164         }
165     }
166 
Metadata()167     public Metadata() { }
168 
169     /**
170      * Go over all the records, collecting metadata keys and records'
171      * type field offset in the Parcel. These are stored in
172      * mKeyToPosMap for latter retrieval.
173      * Format of a metadata record:
174      <pre>
175                          1                   2                   3
176       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
177       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
178       |                     record size                               |
179       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
180       |                     metadata key                              |  // TITLE
181       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
182       |                     metadata type                             |  // STRING_VAL
183       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
184       |                                                               |
185       |                .... metadata payload ....                     |
186       |                                                               |
187       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
188      </pre>
189      * @param parcel With the serialized records.
190      * @param bytesLeft How many bytes in the parcel should be processed.
191      * @return false if an error occurred during parsing.
192      */
scanAllRecords(Parcel parcel, int bytesLeft)193     private boolean scanAllRecords(Parcel parcel, int bytesLeft) {
194         int recCount = 0;
195         boolean error = false;
196 
197         mKeyToPosMap.clear();
198         while (bytesLeft > kRecordHeaderSize) {
199             final int start = parcel.dataPosition();
200             // Check the size.
201             final int size = parcel.readInt();
202 
203             if (size <= kRecordHeaderSize) {  // at least 1 byte should be present.
204                 Log.e(TAG, "Record is too short");
205                 error = true;
206                 break;
207             }
208 
209             // Check the metadata key.
210             final int metadataId = parcel.readInt();
211             if (!checkMetadataId(metadataId)) {
212                 error = true;
213                 break;
214             }
215 
216             // Store the record offset which points to the type
217             // field so we can later on read/unmarshall the record
218             // payload.
219             if (mKeyToPosMap.containsKey(metadataId)) {
220                 Log.e(TAG, "Duplicate metadata ID found");
221                 error = true;
222                 break;
223             }
224 
225             mKeyToPosMap.put(metadataId, parcel.dataPosition());
226 
227             // Check the metadata type.
228             final int metadataType = parcel.readInt();
229             if (metadataType <= 0 || metadataType > LAST_TYPE) {
230                 Log.e(TAG, "Invalid metadata type " + metadataType);
231                 error = true;
232                 break;
233             }
234 
235             // Skip to the next one.
236             parcel.setDataPosition(start + size);
237             bytesLeft -= size;
238             ++recCount;
239         }
240 
241         if (0 != bytesLeft || error) {
242             Log.e(TAG, "Ran out of data or error on record " + recCount);
243             mKeyToPosMap.clear();
244             return false;
245         } else {
246             return true;
247         }
248     }
249 
250     /**
251      * Check a parcel containing metadata is well formed. The header
252      * is checked as well as the individual records format. However, the
253      * data inside the record is not checked because we do lazy access
254      * (we check/unmarshall only data the user asks for.)
255      *
256      * Format of a metadata parcel:
257      <pre>
258                          1                   2                   3
259       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
260       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
261       |                     metadata total size                       |
262       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
263       |     'M'       |     'E'       |     'T'       |     'A'       |
264       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
265       |                                                               |
266       |                .... metadata records ....                     |
267       |                                                               |
268       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
269      </pre>
270      *
271      * @param parcel With the serialized data. Metadata keeps a
272      *               reference on it to access it later on. The caller
273      *               should not modify the parcel after this call (and
274      *               not call recycle on it.)
275      * @return false if an error occurred.
276      */
parse(Parcel parcel)277     public boolean parse(Parcel parcel) {
278         if (parcel.dataAvail() < kMetaHeaderSize) {
279             Log.e(TAG, "Not enough data " + parcel.dataAvail());
280             return false;
281         }
282 
283         final int pin = parcel.dataPosition();  // to roll back in case of errors.
284         final int size = parcel.readInt();
285 
286         // The extra kInt32Size below is to account for the int32 'size' just read.
287         if (parcel.dataAvail() + kInt32Size < size || size < kMetaHeaderSize) {
288             Log.e(TAG, "Bad size " + size + " avail " + parcel.dataAvail() + " position " + pin);
289             parcel.setDataPosition(pin);
290             return false;
291         }
292 
293         // Checks if the 'M' 'E' 'T' 'A' marker is present.
294         final int kShouldBeMetaMarker = parcel.readInt();
295         if (kShouldBeMetaMarker != kMetaMarker ) {
296             Log.e(TAG, "Marker missing " + Integer.toHexString(kShouldBeMetaMarker));
297             parcel.setDataPosition(pin);
298             return false;
299         }
300 
301         // Scan the records to collect metadata ids and offsets.
302         if (!scanAllRecords(parcel, size - kMetaHeaderSize)) {
303             parcel.setDataPosition(pin);
304             return false;
305         }
306         mParcel = parcel;
307         return true;
308     }
309 
310     /**
311      * @return The set of metadata ID found.
312      */
keySet()313     public Set<Integer> keySet() {
314         return mKeyToPosMap.keySet();
315     }
316 
317     /**
318      * @return true if a value is present for the given key.
319      */
has(final int metadataId)320     public boolean has(final int metadataId) {
321         if (!checkMetadataId(metadataId)) {
322             throw new IllegalArgumentException("Invalid key: " + metadataId);
323         }
324         return mKeyToPosMap.containsKey(metadataId);
325     }
326 
327     // Accessors.
328     // Caller must make sure the key is present using the {@code has}
329     // method otherwise a RuntimeException will occur.
330 
getString(final int key)331     public String getString(final int key) {
332         checkType(key, STRING_VAL);
333         return mParcel.readString();
334     }
335 
getInt(final int key)336     public int getInt(final int key) {
337         checkType(key, INTEGER_VAL);
338         return mParcel.readInt();
339     }
340 
getBoolean(final int key)341     public boolean getBoolean(final int key) {
342         checkType(key, BOOLEAN_VAL);
343         return mParcel.readInt() == 1;
344     }
345 
getLong(final int key)346     public long getLong(final int key) {
347         checkType(key, LONG_VAL);
348         return mParcel.readLong();
349     }
350 
getDouble(final int key)351     public double getDouble(final int key) {
352         checkType(key, DOUBLE_VAL);
353         return mParcel.readDouble();
354     }
355 
getByteArray(final int key)356     public byte[] getByteArray(final int key) {
357         checkType(key, BYTE_ARRAY_VAL);
358         return mParcel.createByteArray();
359     }
360 
getDate(final int key)361     public Date getDate(final int key) {
362         checkType(key, DATE_VAL);
363         final long timeSinceEpoch = mParcel.readLong();
364         final String timeZone = mParcel.readString();
365 
366         if (timeZone.length() == 0) {
367             return new Date(timeSinceEpoch);
368         } else {
369             TimeZone tz = TimeZone.getTimeZone(timeZone);
370             Calendar cal = Calendar.getInstance(tz);
371 
372             cal.setTimeInMillis(timeSinceEpoch);
373             return cal.getTime();
374         }
375     }
376 
getTimedText(final int key)377     public TimedText getTimedText(final int key) {
378         checkType(key, TIMED_TEXT_VAL);
379         final Date startTime = new Date(mParcel.readLong());  // epoch
380         final int duration = mParcel.readInt();  // millisec
381 
382         return new TimedText(startTime,
383                              duration,
384                              mParcel.readString());
385     }
386 
387     // @return the last available system metadata id. Ids are
388     // 1-indexed.
lastSytemId()389     public static int lastSytemId() { return LAST_SYSTEM; }
390 
391     // @return the first available cutom metadata id.
firstCustomId()392     public static int firstCustomId() { return FIRST_CUSTOM; }
393 
394     // @return the last value of known type. Types are 1-indexed.
lastType()395     public static int lastType() { return LAST_TYPE; }
396 
397     // Check val is either a system id or a custom one.
398     // @param val Metadata key to test.
399     // @return true if it is in a valid range.
checkMetadataId(final int val)400     private boolean checkMetadataId(final int val) {
401         if (val <= ANY || (LAST_SYSTEM < val && val < FIRST_CUSTOM)) {
402             Log.e(TAG, "Invalid metadata ID " + val);
403             return false;
404         }
405         return true;
406     }
407 
408     // Check the type of the data match what is expected.
checkType(final int key, final int expectedType)409     private void checkType(final int key, final int expectedType) {
410         final int pos = mKeyToPosMap.get(key);
411 
412         mParcel.setDataPosition(pos);
413 
414         final int type = mParcel.readInt();
415         if (type != expectedType) {
416             throw new IllegalStateException("Wrong type " + expectedType + " but got " + type);
417         }
418     }
419 }
420