Metadata.java revision c39173be3237be9f39d2b57bb38249126e183c53
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        // Magic 4 below is for the int32 'size' just read.
284        if (parcel.dataAvail() + 4 < size || size < kMetaHeaderSize) {
285            Log.e(TAG, "Bad size " + size + " avail " + parcel.dataAvail() + " position " + pin);
286            parcel.setDataPosition(pin);
287            return false;
288        }
289
290        // Checks if the 'M' 'E' 'T' 'A' marker is present.
291        final int kShouldBeMetaMarker = parcel.readInt();
292        if (kShouldBeMetaMarker != kMetaMarker ) {
293            Log.e(TAG, "Marker missing " + Integer.toHexString(kShouldBeMetaMarker));
294            parcel.setDataPosition(pin);
295            return false;
296        }
297
298        // Scan the records to collect metadata ids and offsets.
299        if (!scanAllRecords(parcel, size - kMetaHeaderSize)) {
300            parcel.setDataPosition(pin);
301            return false;
302        }
303        mParcel = parcel;
304        return true;
305    }
306
307    /**
308     * @return The set of metadata ID found.
309     */
310    public Set<Integer> keySet() {
311        return mKeyToPosMap.keySet();
312    }
313
314    /**
315     * @return true if a value is present for the given key.
316     */
317    public boolean has(final int metadataId) {
318        if (!checkMetadataId(metadataId)) {
319            throw new IllegalArgumentException("Invalid key: " + metadataId);
320        }
321        return mKeyToPosMap.containsKey(metadataId);
322    }
323
324    // Accessors.
325    // Caller must make sure the key is present using the {@code has}
326    // method otherwise a RuntimeException will occur.
327
328    public String getString(final int key) {
329        checkType(key, STRING_VAL);
330        return mParcel.readString();
331    }
332
333    public int getInt(final int key) {
334        checkType(key, INTEGER_VAL);
335        return mParcel.readInt();
336    }
337
338    public boolean getBoolean(final int key) {
339        checkType(key, BOOLEAN_VAL);
340        return mParcel.readInt() == 1;
341    }
342
343    public long getLong(final int key) {
344        checkType(key, LONG_VAL);
345        return mParcel.readLong();
346    }
347
348    public double getDouble(final int key) {
349        checkType(key, DOUBLE_VAL);
350        return mParcel.readDouble();
351    }
352
353    public byte[] getByteArray(final int key) {
354        checkType(key, BYTE_ARRAY_VAL);
355        return mParcel.createByteArray();
356    }
357
358    public Date getDate(final int key) {
359        checkType(key, DATE_VAL);
360        final long timeSinceEpoch = mParcel.readLong();
361        final String timeZone = mParcel.readString();
362
363        if (timeZone.length() == 0) {
364            return new Date(timeSinceEpoch);
365        } else {
366            TimeZone tz = TimeZone.getTimeZone(timeZone);
367            Calendar cal = Calendar.getInstance(tz);
368
369            cal.setTimeInMillis(timeSinceEpoch);
370            return cal.getTime();
371        }
372    }
373
374    public TimedText getTimedText(final int key) {
375        checkType(key, TIMED_TEXT_VAL);
376        final Date startTime = new Date(mParcel.readLong());  // epoch
377        final int duration = mParcel.readInt();  // millisec
378
379        return new TimedText(startTime,
380                             duration,
381                             mParcel.readString());
382    }
383
384    // @return the last available system metadata id. Ids are
385    // 1-indexed.
386    public static int lastSytemId() { return LAST_SYSTEM; }
387
388    // @return the first available cutom metadata id.
389    public static int firstCustomId() { return FIRST_CUSTOM; }
390
391    // @return the last value of known type. Types are 1-indexed.
392    public static int lastType() { return LAST_TYPE; }
393
394    // Check val is either a system id or a custom one.
395    // @param val Metadata key to test.
396    // @return true if it is in a valid range.
397    private boolean checkMetadataId(final int val) {
398        if (val <= ANY || (LAST_SYSTEM < val && val < FIRST_CUSTOM)) {
399            Log.e(TAG, "Invalid metadata ID " + val);
400            return false;
401        }
402        return true;
403    }
404
405    // Check the type of the data match what is expected.
406    private void checkType(final int key, final int expectedType) {
407        final int pos = mKeyToPosMap.get(key);
408
409        mParcel.setDataPosition(pos);
410
411        final int type = mParcel.readInt();
412        if (type != expectedType) {
413            throw new IllegalStateException("Wrong type " + expectedType + " but got " + type);
414        }
415    }
416}
417