Metadata.java revision 1e1b13e62e38d6efc7cef4b496b3119bd45ee2c2
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.
36
37   Metadata is like a Bundle. It is sparse and each key can occur at
38   most once. The key is an integer and the value is the actual metadata.
39
40   The caller is expected to know the type of the metadata and call
41   the right get* method to fetch its value.
42 */
43public class Metadata
44{
45    // The metadata are keyed using integers rather than more heavy
46    // weight strings. We considered using Bundle to ship the metadata
47    // between the native layer and the java layer but dropped that
48    // option since keeping in sync a native implementation of Bundle
49    // and the java one would be too burdensome. Besides Bundle uses
50    // String for its keys.
51    // The key range [0 8192) is reserved for the system.
52    //
53    // We manually serialize the data in Parcels. For large memory
54    // blob (bitmaps, raw pictures) we use MemoryFile which allow the
55    // client to make the data purge-able once it is done with it.
56    //
57
58    /**
59     * {@hide}
60     */
61    public static final int ANY = 0;  // Never used for metadata returned, only for filtering.
62                                      // Keep in sync with kAny in MediaPlayerService.cpp
63
64    // Playback capabilities.
65    /**
66     * Indicate whether the media can be paused
67     */
68    public static final int PAUSE_AVAILABLE         = 1; // Boolean
69    /**
70     * Indicate whether the media can be backward seeked
71     */
72    public static final int SEEK_BACKWARD_AVAILABLE = 2; // Boolean
73    /**
74     * Indicate whether the media can be forward seeked
75     */
76    public static final int SEEK_FORWARD_AVAILABLE  = 3; // Boolean
77    /**
78     * Indicate whether the media can be seeked
79     */
80    public static final int SEEK_AVAILABLE          = 4; // Boolean
81
82    // TODO: Should we use numbers compatible with the metadata retriever?
83    /**
84     * {@hide}
85     */
86    public static final int TITLE                   = 5; // String
87    /**
88     * {@hide}
89     */
90    public static final int COMMENT                 = 6; // String
91    /**
92     * {@hide}
93     */
94    public static final int COPYRIGHT               = 7; // String
95    /**
96     * {@hide}
97     */
98    public static final int ALBUM                   = 8; // String
99    /**
100     * {@hide}
101     */
102    public static final int ARTIST                  = 9; // String
103    /**
104     * {@hide}
105     */
106    public static final int AUTHOR                  = 10; // String
107    /**
108     * {@hide}
109     */
110    public static final int COMPOSER                = 11; // String
111    /**
112     * {@hide}
113     */
114    public static final int GENRE                   = 12; // String
115    /**
116     * {@hide}
117     */
118    public static final int DATE                    = 13; // Date
119    /**
120     * {@hide}
121     */
122    public static final int DURATION                = 14; // Integer(millisec)
123    /**
124     * {@hide}
125     */
126    public static final int CD_TRACK_NUM            = 15; // Integer 1-based
127    /**
128     * {@hide}
129     */
130    public static final int CD_TRACK_MAX            = 16; // Integer
131    /**
132     * {@hide}
133     */
134    public static final int RATING                  = 17; // String
135    /**
136     * {@hide}
137     */
138    public static final int ALBUM_ART               = 18; // byte[]
139    /**
140     * {@hide}
141     */
142    public static final int VIDEO_FRAME             = 19; // Bitmap
143
144    /**
145     * {@hide}
146     */
147    public static final int BIT_RATE                = 20; // Integer, Aggregate rate of
148                                                          // all the streams in bps.
149
150    /**
151     * {@hide}
152     */
153    public static final int AUDIO_BIT_RATE          = 21; // Integer, bps
154    /**
155     * {@hide}
156     */
157    public static final int VIDEO_BIT_RATE          = 22; // Integer, bps
158    /**
159     * {@hide}
160     */
161    public static final int AUDIO_SAMPLE_RATE       = 23; // Integer, Hz
162    /**
163     * {@hide}
164     */
165    public static final int VIDEO_FRAME_RATE        = 24; // Integer, Hz
166
167    // See RFC2046 and RFC4281.
168    /**
169     * {@hide}
170     */
171    public static final int MIME_TYPE               = 25; // String
172    /**
173     * {@hide}
174     */
175    public static final int AUDIO_CODEC             = 26; // String
176    /**
177     * {@hide}
178     */
179    public static final int VIDEO_CODEC             = 27; // String
180
181    /**
182     * {@hide}
183     */
184    public static final int VIDEO_HEIGHT            = 28; // Integer
185    /**
186     * {@hide}
187     */
188    public static final int VIDEO_WIDTH             = 29; // Integer
189    /**
190     * {@hide}
191     */
192    public static final int NUM_TRACKS              = 30; // Integer
193    /**
194     * {@hide}
195     */
196    public static final int DRM_CRIPPLED            = 31; // Boolean
197
198    private static final int LAST_SYSTEM = 31;
199    private static final int FIRST_CUSTOM = 8192;
200
201    // Shorthands to set the MediaPlayer's metadata filter.
202    /**
203     * {@hide}
204     */
205    public static final Set<Integer> MATCH_NONE = Collections.EMPTY_SET;
206    /**
207     * {@hide}
208     */
209    public static final Set<Integer> MATCH_ALL = Collections.singleton(ANY);
210
211    /**
212     * {@hide}
213     */
214    public static final int STRING_VAL     = 1;
215    /**
216     * {@hide}
217     */
218    public static final int INTEGER_VAL    = 2;
219    /**
220     * {@hide}
221     */
222    public static final int BOOLEAN_VAL    = 3;
223    /**
224     * {@hide}
225     */
226    public static final int LONG_VAL       = 4;
227    /**
228     * {@hide}
229     */
230    public static final int DOUBLE_VAL     = 5;
231    /**
232     * {@hide}
233     */
234    public static final int DATE_VAL       = 6;
235    /**
236     * {@hide}
237     */
238    public static final int BYTE_ARRAY_VAL = 7;
239    // FIXME: misses a type for shared heap is missing (MemoryFile).
240    // FIXME: misses a type for bitmaps.
241    private static final int LAST_TYPE = 7;
242
243    private static final String TAG = "media.Metadata";
244    private static final int kInt32Size = 4;
245    private static final int kMetaHeaderSize = 2 * kInt32Size; //  size + marker
246    private static final int kRecordHeaderSize = 3 * kInt32Size; // size + id + type
247
248    private static final int kMetaMarker = 0x4d455441;  // 'M' 'E' 'T' 'A'
249
250    // After a successful parsing, set the parcel with the serialized metadata.
251    private Parcel mParcel;
252
253    // Map to associate a Metadata key (e.g TITLE) with the offset of
254    // the record's payload in the parcel.
255    // Used to look up if a key was present too.
256    // Key: Metadata ID
257    // Value: Offset of the metadata type field in the record.
258    private final HashMap<Integer, Integer> mKeyToPosMap =
259            new HashMap<Integer, Integer>();
260
261    /**
262     * {@hide}
263     */
264    public Metadata() { }
265
266    /**
267     * Go over all the records, collecting metadata keys and records'
268     * type field offset in the Parcel. These are stored in
269     * mKeyToPosMap for latter retrieval.
270     * Format of a metadata record:
271     <pre>
272                         1                   2                   3
273      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
274      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
275      |                     record size                               |
276      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
277      |                     metadata key                              |  // TITLE
278      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
279      |                     metadata type                             |  // STRING_VAL
280      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
281      |                                                               |
282      |                .... metadata payload ....                     |
283      |                                                               |
284      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
285     </pre>
286     * @param parcel With the serialized records.
287     * @param bytesLeft How many bytes in the parcel should be processed.
288     * @return false if an error occurred during parsing.
289     */
290    private boolean scanAllRecords(Parcel parcel, int bytesLeft) {
291        int recCount = 0;
292        boolean error = false;
293
294        mKeyToPosMap.clear();
295        while (bytesLeft > kRecordHeaderSize) {
296            final int start = parcel.dataPosition();
297            // Check the size.
298            final int size = parcel.readInt();
299
300            if (size <= kRecordHeaderSize) {  // at least 1 byte should be present.
301                Log.e(TAG, "Record is too short");
302                error = true;
303                break;
304            }
305
306            // Check the metadata key.
307            final int metadataId = parcel.readInt();
308            if (!checkMetadataId(metadataId)) {
309                error = true;
310                break;
311            }
312
313            // Store the record offset which points to the type
314            // field so we can later on read/unmarshall the record
315            // payload.
316            if (mKeyToPosMap.containsKey(metadataId)) {
317                Log.e(TAG, "Duplicate metadata ID found");
318                error = true;
319                break;
320            }
321
322            mKeyToPosMap.put(metadataId, parcel.dataPosition());
323
324            // Check the metadata type.
325            final int metadataType = parcel.readInt();
326            if (metadataType <= 0 || metadataType > LAST_TYPE) {
327                Log.e(TAG, "Invalid metadata type " + metadataType);
328                error = true;
329                break;
330            }
331
332            // Skip to the next one.
333            parcel.setDataPosition(start + size);
334            bytesLeft -= size;
335            ++recCount;
336        }
337
338        if (0 != bytesLeft || error) {
339            Log.e(TAG, "Ran out of data or error on record " + recCount);
340            mKeyToPosMap.clear();
341            return false;
342        } else {
343            return true;
344        }
345    }
346
347    /**
348     * Check a parcel containing metadata is well formed. The header
349     * is checked as well as the individual records format. However, the
350     * data inside the record is not checked because we do lazy access
351     * (we check/unmarshall only data the user asks for.)
352     *
353     * Format of a metadata parcel:
354     <pre>
355                         1                   2                   3
356      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
357      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
358      |                     metadata total size                       |
359      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
360      |     'M'       |     'E'       |     'T'       |     'A'       |
361      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
362      |                                                               |
363      |                .... metadata records ....                     |
364      |                                                               |
365      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
366     </pre>
367     *
368     * @param parcel With the serialized data. Metadata keeps a
369     *               reference on it to access it later on. The caller
370     *               should not modify the parcel after this call (and
371     *               not call recycle on it.)
372     * @return false if an error occurred.
373     * {@hide}
374     */
375    public boolean parse(Parcel parcel) {
376        if (parcel.dataAvail() < kMetaHeaderSize) {
377            Log.e(TAG, "Not enough data " + parcel.dataAvail());
378            return false;
379        }
380
381        final int pin = parcel.dataPosition();  // to roll back in case of errors.
382        final int size = parcel.readInt();
383
384        // The extra kInt32Size below is to account for the int32 'size' just read.
385        if (parcel.dataAvail() + kInt32Size < size || size < kMetaHeaderSize) {
386            Log.e(TAG, "Bad size " + size + " avail " + parcel.dataAvail() + " position " + pin);
387            parcel.setDataPosition(pin);
388            return false;
389        }
390
391        // Checks if the 'M' 'E' 'T' 'A' marker is present.
392        final int kShouldBeMetaMarker = parcel.readInt();
393        if (kShouldBeMetaMarker != kMetaMarker ) {
394            Log.e(TAG, "Marker missing " + Integer.toHexString(kShouldBeMetaMarker));
395            parcel.setDataPosition(pin);
396            return false;
397        }
398
399        // Scan the records to collect metadata ids and offsets.
400        if (!scanAllRecords(parcel, size - kMetaHeaderSize)) {
401            parcel.setDataPosition(pin);
402            return false;
403        }
404        mParcel = parcel;
405        return true;
406    }
407
408    /**
409     * @return The set of metadata ID found.
410     */
411    public Set<Integer> keySet() {
412        return mKeyToPosMap.keySet();
413    }
414
415    /**
416     * @return true if a value is present for the given key.
417     */
418    public boolean has(final int metadataId) {
419        if (!checkMetadataId(metadataId)) {
420            throw new IllegalArgumentException("Invalid key: " + metadataId);
421        }
422        return mKeyToPosMap.containsKey(metadataId);
423    }
424
425    // Accessors.
426    // Caller must make sure the key is present using the {@code has}
427    // method otherwise a RuntimeException will occur.
428
429    /**
430     * {@hide}
431     */
432    public String getString(final int key) {
433        checkType(key, STRING_VAL);
434        return mParcel.readString();
435    }
436
437    /**
438     * {@hide}
439     */
440    public int getInt(final int key) {
441        checkType(key, INTEGER_VAL);
442        return mParcel.readInt();
443    }
444
445    /**
446     * Get the boolean value indicated by key
447     */
448    public boolean getBoolean(final int key) {
449        checkType(key, BOOLEAN_VAL);
450        return mParcel.readInt() == 1;
451    }
452
453    /**
454     * {@hide}
455     */
456    public long getLong(final int key) {
457        checkType(key, LONG_VAL);    /**
458     * {@hide}
459     */
460        return mParcel.readLong();
461    }
462
463    /**
464     * {@hide}
465     */
466    public double getDouble(final int key) {
467        checkType(key, DOUBLE_VAL);
468        return mParcel.readDouble();
469    }
470
471    /**
472     * {@hide}
473     */
474    public byte[] getByteArray(final int key) {
475        checkType(key, BYTE_ARRAY_VAL);
476        return mParcel.createByteArray();
477    }
478
479    /**
480     * {@hide}
481     */
482    public Date getDate(final int key) {
483        checkType(key, DATE_VAL);
484        final long timeSinceEpoch = mParcel.readLong();
485        final String timeZone = mParcel.readString();
486
487        if (timeZone.length() == 0) {
488            return new Date(timeSinceEpoch);
489        } else {
490            TimeZone tz = TimeZone.getTimeZone(timeZone);
491            Calendar cal = Calendar.getInstance(tz);
492
493            cal.setTimeInMillis(timeSinceEpoch);
494            return cal.getTime();
495        }
496    }
497
498    /**
499     * @return the last available system metadata id. Ids are
500     *         1-indexed.
501     * {@hide}
502     */
503    public static int lastSytemId() { return LAST_SYSTEM; }
504
505    /**
506     * @return the first available cutom metadata id.
507     * {@hide}
508     */
509    public static int firstCustomId() { return FIRST_CUSTOM; }
510
511    /**
512     * @return the last value of known type. Types are 1-indexed.
513     * {@hide}
514     */
515    public static int lastType() { return LAST_TYPE; }
516
517    /**
518     * Check val is either a system id or a custom one.
519     * @param val Metadata key to test.
520     * @return true if it is in a valid range.
521     **/
522    private boolean checkMetadataId(final int val) {
523        if (val <= ANY || (LAST_SYSTEM < val && val < FIRST_CUSTOM)) {
524            Log.e(TAG, "Invalid metadata ID " + val);
525            return false;
526        }
527        return true;
528    }
529
530    /**
531     * Check the type of the data match what is expected.
532     */
533    private void checkType(final int key, final int expectedType) {
534        final int pos = mKeyToPosMap.get(key);
535
536        mParcel.setDataPosition(pos);
537
538        final int type = mParcel.readInt();
539        if (type != expectedType) {
540            throw new IllegalStateException("Wrong type " + expectedType + " but got " + type);
541        }
542    }
543}
544