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