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