Metadata.java revision cb2e00eedce99b30faf5f238136a00bc5448c5f2
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
17package android.media;
18
19import android.graphics.Bitmap;
20import android.os.Parcel;
21import android.util.Log;
22
23import java.util.Calendar;
24import java.util.Collections;
25import java.util.Date;
26import java.util.HashMap;
27import java.util.Set;
28import 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 */
47public 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    private static final int LAST_SYSTEM = 28;
101    private static final int FIRST_CUSTOM = 8092;
102
103    // Shorthands to set the MediaPlayer's metadata filter.
104    public static final Set<Integer> MATCH_NONE = Collections.EMPTY_SET;
105    public static final Set<Integer> MATCH_ALL = Collections.singleton(ANY);
106
107    public static final int STRING_VAL     = 1;
108    public static final int INTEGER_VAL    = 2;
109    public static final int BOOLEAN_VAL    = 3;
110    public static final int LONG_VAL       = 4;
111    public static final int DOUBLE_VAL     = 5;
112    public static final int TIMED_TEXT_VAL = 6;
113    public static final int DATE_VAL       = 7;
114    public static final int BYTE_ARRAY_VAL = 8;
115    // FIXME: misses a type for shared heap is missing (MemoryFile).
116    // FIXME: misses a type for bitmaps.
117    private static final int LAST_TYPE = 8;
118
119    private static final String TAG = "media.Metadata";
120    private static final int kMetaHeaderSize = 8;  //  size + marker
121    private static final int kMetaMarker = 0x4d455441;  // 'M' 'E' 'T' 'A'
122    private static final int kRecordHeaderSize = 12; // size + id + type
123
124    // After a successful parsing, set the parcel with the serialized metadata.
125    private Parcel mParcel;
126
127    // Map to associate a Metadata key (e.g TITLE) with the offset of
128    // the record's payload in the parcel.
129    // Used to look up if a key was present too.
130    // Key: Metadata ID
131    // Value: Offset of the metadata type field in the record.
132    private final HashMap<Integer, Integer> mKeyToPosMap =
133            new HashMap<Integer, Integer>();
134
135    /**
136     * Helper class to hold a triple (time, duration, text). Can be used to
137     * implement caption.
138     */
139    public class TimedText {
140        private Date mTime;
141        private int mDuration;  // millisec
142        private String mText;
143
144        public TimedText(Date time, int duration, String text) {
145            mTime = time;
146            mDuration = duration;
147            mText = text;
148        }
149
150        public String toString() {
151            StringBuilder res = new StringBuilder(80);
152            res.append(mTime).append("-").append(mDuration)
153                    .append(":").append(mText);
154            return res.toString();
155        }
156    }
157
158    public Metadata() { }
159
160    /**
161     * Go over all the records, collecting metadata keys and records'
162     * type field offset in the Parcel. These are stored in
163     * mKeyToPosMap for latter retrieval.
164     * Format of a metadata record:
165     <pre>
166                         1                   2                   3
167      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
168      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
169      |                     record size                               |
170      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
171      |                     metadata key                              |  // TITLE
172      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
173      |                     metadata type                             |  // STRING_VAL
174      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
175      |                                                               |
176      |                .... metadata payload ....                     |
177      |                                                               |
178      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
179     </pre>
180     * @param parcel With the serialized records.
181     * @param bytesLeft How many bytes in the parcel should be processed.
182     * @return false if an error occurred during parsing.
183     */
184    private boolean scanAllRecords(Parcel parcel, int bytesLeft) {
185        int recCount = 0;
186        boolean error = false;
187
188        mKeyToPosMap.clear();
189        while (bytesLeft > kRecordHeaderSize) {
190            final int start = parcel.dataPosition();
191            // Check the size.
192            final int size = parcel.readInt();
193
194            if (size <= kRecordHeaderSize) {  // at least 1 byte should be present.
195                Log.e(TAG, "Record is too short");
196                error = true;
197                break;
198            }
199
200            // Check the metadata key.
201            final int metadataId = parcel.readInt();
202            if (!checkMetadataId(metadataId)) {
203                error = true;
204                break;
205            }
206
207            // Store the record offset which points to the type
208            // field so we can later on read/unmarshall the record
209            // payload.
210            if (mKeyToPosMap.containsKey(metadataId)) {
211                Log.e(TAG, "Duplicate metadata ID found");
212                error = true;
213                break;
214            }
215
216            mKeyToPosMap.put(metadataId, parcel.dataPosition());
217
218            // Check the metadata type.
219            final int metadataType = parcel.readInt();
220            if (metadataType <= 0 || metadataType > LAST_TYPE) {
221                Log.e(TAG, "Invalid metadata type " + metadataType);
222                error = true;
223                break;
224            }
225
226            // Skip to the next one.
227            parcel.setDataPosition(start + size);
228            bytesLeft -= size;
229            ++recCount;
230        }
231
232        if (0 != bytesLeft || error) {
233            Log.e(TAG, "Ran out of data or error on record " + recCount);
234            mKeyToPosMap.clear();
235            return false;
236        } else {
237            return true;
238        }
239    }
240
241    /**
242     * Check a parcel containing metadata is well formed. The header
243     * is checked as well as the individual records format. However, the
244     * data inside the record is not checked because we do lazy access
245     * (we check/unmarshall only data the user asks for.)
246     *
247     * Format of a metadata parcel:
248     <pre>
249                         1                   2                   3
250      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
251      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
252      |                     metadata total size                       |
253      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
254      |     'M'       |     'E'       |     'T'       |     'A'       |
255      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
256      |                                                               |
257      |                .... metadata records ....                     |
258      |                                                               |
259      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
260     </pre>
261     *
262     * @param parcel With the serialized data. Metadata keeps a
263     *               reference on it to access it later on. The caller
264     *               should not modify the parcel after this call (and
265     *               not call recycle on it.)
266     * @return false if an error occurred.
267     */
268    public boolean parse(Parcel parcel) {
269        if (parcel.dataAvail() < kMetaHeaderSize) {
270            Log.e(TAG, "Not enough data " + parcel.dataAvail());
271            return false;
272        }
273
274        final int pin = parcel.dataPosition();  // to roll back in case of errors.
275        final int size = parcel.readInt();
276
277        if (parcel.dataAvail() < size || size < kMetaHeaderSize) {
278            Log.e(TAG, "Bad size " + size);
279            parcel.setDataPosition(pin);
280            return false;
281        }
282
283        // Checks if the 'M' 'E' 'T' 'A' marker is present.
284        final int kShouldBeMetaMarker = parcel.readInt();
285        if (kShouldBeMetaMarker != kMetaMarker ) {
286            Log.e(TAG, "Marker missing " + Integer.toHexString(kShouldBeMetaMarker));
287            parcel.setDataPosition(pin);
288            return false;
289        }
290
291        // Scan the records to collect metadata ids and offsets.
292        if (!scanAllRecords(parcel, size - kMetaHeaderSize)) {
293            parcel.setDataPosition(pin);
294            return false;
295        }
296        mParcel = parcel;
297        return true;
298    }
299
300    /**
301     * @return The set of metadata ID found.
302     */
303    public Set<Integer> keySet() {
304        return mKeyToPosMap.keySet();
305    }
306
307    /**
308     * @return true if a value is present for the given key.
309     */
310    public boolean has(final int metadataId) {
311        if (!checkMetadataId(metadataId)) {
312            throw new IllegalArgumentException("Invalid key: " + metadataId);
313        }
314        return mKeyToPosMap.containsKey(metadataId);
315    }
316
317    // Accessors.
318    // Caller must make sure the key is present using the {@code has}
319    // method otherwise a RuntimeException will occur.
320
321    public String getString(final int key) {
322        checkType(key, STRING_VAL);
323        return mParcel.readString();
324    }
325
326    public int getInt(final int key) {
327        checkType(key, INTEGER_VAL);
328        return mParcel.readInt();
329    }
330
331    public boolean getBoolean(final int key) {
332        checkType(key, BOOLEAN_VAL);
333        return mParcel.readInt() == 1;
334    }
335
336    public long getLong(final int key) {
337        checkType(key, LONG_VAL);
338        return mParcel.readLong();
339    }
340
341    public double getDouble(final int key) {
342        checkType(key, DOUBLE_VAL);
343        return mParcel.readDouble();
344    }
345
346    public byte[] getByteArray(final int key) {
347        checkType(key, BYTE_ARRAY_VAL);
348        return mParcel.createByteArray();
349    }
350
351    public Date getDate(final int key) {
352        checkType(key, DATE_VAL);
353        final long timeSinceEpoch = mParcel.readLong();
354        final String timeZone = mParcel.readString();
355
356        if (timeZone.length() == 0) {
357            return new Date(timeSinceEpoch);
358        } else {
359            TimeZone tz = TimeZone.getTimeZone(timeZone);
360            Calendar cal = Calendar.getInstance(tz);
361
362            cal.setTimeInMillis(timeSinceEpoch);
363            return cal.getTime();
364        }
365    }
366
367    public TimedText getTimedText(final int key) {
368        checkType(key, TIMED_TEXT_VAL);
369        final Date startTime = new Date(mParcel.readLong());  // epoch
370        final int duration = mParcel.readInt();  // millisec
371
372        return new TimedText(startTime,
373                             duration,
374                             mParcel.readString());
375    }
376
377    // @return the last available system metadata id. Ids are
378    // 1-indexed.
379    public static int lastSytemId() { return LAST_SYSTEM; }
380
381    // @return the first available cutom metadata id.
382    public static int firstCustomId() { return FIRST_CUSTOM; }
383
384    // @return the last value of known type. Types are 1-indexed.
385    public static int lastType() { return LAST_TYPE; }
386
387    // Check val is either a system id or a custom one.
388    // @param val Metadata key to test.
389    // @return true if it is in a valid range.
390    private boolean checkMetadataId(final int val) {
391        if (val <= ANY || (LAST_SYSTEM < val && val < FIRST_CUSTOM)) {
392            Log.e(TAG, "Invalid metadata ID " + val);
393            return false;
394        }
395        return true;
396    }
397
398    // Check the type of the data match what is expected.
399    private void checkType(final int key, final int expectedType) {
400        final int pos = mKeyToPosMap.get(key);
401
402        mParcel.setDataPosition(pos);
403
404        final int type = mParcel.readInt();
405        if (type != expectedType) {
406            throw new IllegalStateException("Wrong type " + expectedType + " but got " + type);
407        }
408    }
409}
410