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