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