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. 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 43 @hide 44 */ 45 public class Metadata 46 { 47 // The metadata are keyed using integers rather than more heavy 48 // weight strings. We considered using Bundle to ship the metadata 49 // between the native layer and the java layer but dropped that 50 // option since keeping in sync a native implementation of Bundle 51 // and the java one would be too burdensome. Besides Bundle uses 52 // String for its keys. 53 // The key range [0 8192) is reserved for the system. 54 // 55 // We manually serialize the data in Parcels. For large memory 56 // blob (bitmaps, raw pictures) we use MemoryFile which allow the 57 // client to make the data purge-able once it is done with it. 58 // 59 60 /** 61 * {@hide} 62 */ 63 public static final int ANY = 0; // Never used for metadata returned, only for filtering. 64 // Keep in sync with kAny in MediaPlayerService.cpp 65 66 // Playback capabilities. 67 /** 68 * Indicate whether the media can be paused 69 */ 70 public static final int PAUSE_AVAILABLE = 1; // Boolean 71 /** 72 * Indicate whether the media can be backward seeked 73 */ 74 public static final int SEEK_BACKWARD_AVAILABLE = 2; // Boolean 75 /** 76 * Indicate whether the media can be forward seeked 77 */ 78 public static final int SEEK_FORWARD_AVAILABLE = 3; // Boolean 79 /** 80 * Indicate whether the media can be seeked 81 */ 82 public static final int SEEK_AVAILABLE = 4; // Boolean 83 84 // TODO: Should we use numbers compatible with the metadata retriever? 85 /** 86 * {@hide} 87 */ 88 public static final int TITLE = 5; // String 89 /** 90 * {@hide} 91 */ 92 public static final int COMMENT = 6; // String 93 /** 94 * {@hide} 95 */ 96 public static final int COPYRIGHT = 7; // String 97 /** 98 * {@hide} 99 */ 100 public static final int ALBUM = 8; // String 101 /** 102 * {@hide} 103 */ 104 public static final int ARTIST = 9; // String 105 /** 106 * {@hide} 107 */ 108 public static final int AUTHOR = 10; // String 109 /** 110 * {@hide} 111 */ 112 public static final int COMPOSER = 11; // String 113 /** 114 * {@hide} 115 */ 116 public static final int GENRE = 12; // String 117 /** 118 * {@hide} 119 */ 120 public static final int DATE = 13; // Date 121 /** 122 * {@hide} 123 */ 124 public static final int DURATION = 14; // Integer(millisec) 125 /** 126 * {@hide} 127 */ 128 public static final int CD_TRACK_NUM = 15; // Integer 1-based 129 /** 130 * {@hide} 131 */ 132 public static final int CD_TRACK_MAX = 16; // Integer 133 /** 134 * {@hide} 135 */ 136 public static final int RATING = 17; // String 137 /** 138 * {@hide} 139 */ 140 public static final int ALBUM_ART = 18; // byte[] 141 /** 142 * {@hide} 143 */ 144 public static final int VIDEO_FRAME = 19; // Bitmap 145 146 /** 147 * {@hide} 148 */ 149 public static final int BIT_RATE = 20; // Integer, Aggregate rate of 150 // all the streams in bps. 151 152 /** 153 * {@hide} 154 */ 155 public static final int AUDIO_BIT_RATE = 21; // Integer, bps 156 /** 157 * {@hide} 158 */ 159 public static final int VIDEO_BIT_RATE = 22; // Integer, bps 160 /** 161 * {@hide} 162 */ 163 public static final int AUDIO_SAMPLE_RATE = 23; // Integer, Hz 164 /** 165 * {@hide} 166 */ 167 public static final int VIDEO_FRAME_RATE = 24; // Integer, Hz 168 169 // See RFC2046 and RFC4281. 170 /** 171 * {@hide} 172 */ 173 public static final int MIME_TYPE = 25; // String 174 /** 175 * {@hide} 176 */ 177 public static final int AUDIO_CODEC = 26; // String 178 /** 179 * {@hide} 180 */ 181 public static final int VIDEO_CODEC = 27; // String 182 183 /** 184 * {@hide} 185 */ 186 public static final int VIDEO_HEIGHT = 28; // Integer 187 /** 188 * {@hide} 189 */ 190 public static final int VIDEO_WIDTH = 29; // Integer 191 /** 192 * {@hide} 193 */ 194 public static final int NUM_TRACKS = 30; // Integer 195 /** 196 * {@hide} 197 */ 198 public static final int DRM_CRIPPLED = 31; // Boolean 199 200 private static final int LAST_SYSTEM = 31; 201 private static final int FIRST_CUSTOM = 8192; 202 203 // Shorthands to set the MediaPlayer's metadata filter. 204 /** 205 * {@hide} 206 */ 207 public static final Set<Integer> MATCH_NONE = Collections.EMPTY_SET; 208 /** 209 * {@hide} 210 */ 211 public static final Set<Integer> MATCH_ALL = Collections.singleton(ANY); 212 213 /** 214 * {@hide} 215 */ 216 public static final int STRING_VAL = 1; 217 /** 218 * {@hide} 219 */ 220 public static final int INTEGER_VAL = 2; 221 /** 222 * {@hide} 223 */ 224 public static final int BOOLEAN_VAL = 3; 225 /** 226 * {@hide} 227 */ 228 public static final int LONG_VAL = 4; 229 /** 230 * {@hide} 231 */ 232 public static final int DOUBLE_VAL = 5; 233 /** 234 * {@hide} 235 */ 236 public static final int DATE_VAL = 6; 237 /** 238 * {@hide} 239 */ 240 public static final int BYTE_ARRAY_VAL = 7; 241 // FIXME: misses a type for shared heap is missing (MemoryFile). 242 // FIXME: misses a type for bitmaps. 243 private static final int LAST_TYPE = 7; 244 245 private static final String TAG = "media.Metadata"; 246 private static final int kInt32Size = 4; 247 private static final int kMetaHeaderSize = 2 * kInt32Size; // size + marker 248 private static final int kRecordHeaderSize = 3 * kInt32Size; // size + id + type 249 250 private static final int kMetaMarker = 0x4d455441; // 'M' 'E' 'T' 'A' 251 252 // After a successful parsing, set the parcel with the serialized metadata. 253 private Parcel mParcel; 254 255 // Map to associate a Metadata key (e.g TITLE) with the offset of 256 // the record's payload in the parcel. 257 // Used to look up if a key was present too. 258 // Key: Metadata ID 259 // Value: Offset of the metadata type field in the record. 260 private final HashMap<Integer, Integer> mKeyToPosMap = 261 new HashMap<Integer, Integer>(); 262 263 /** 264 * {@hide} 265 */ 266 public Metadata() { } 267 268 /** 269 * Go over all the records, collecting metadata keys and records' 270 * type field offset in the Parcel. These are stored in 271 * mKeyToPosMap for latter retrieval. 272 * Format of a metadata record: 273 <pre> 274 1 2 3 275 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 276 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 277 | record size | 278 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 279 | metadata key | // TITLE 280 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 281 | metadata type | // STRING_VAL 282 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 283 | | 284 | .... metadata payload .... | 285 | | 286 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 287 </pre> 288 * @param parcel With the serialized records. 289 * @param bytesLeft How many bytes in the parcel should be processed. 290 * @return false if an error occurred during parsing. 291 */ 292 private boolean scanAllRecords(Parcel parcel, int bytesLeft) { 293 int recCount = 0; 294 boolean error = false; 295 296 mKeyToPosMap.clear(); 297 while (bytesLeft > kRecordHeaderSize) { 298 final int start = parcel.dataPosition(); 299 // Check the size. 300 final int size = parcel.readInt(); 301 302 if (size <= kRecordHeaderSize) { // at least 1 byte should be present. 303 Log.e(TAG, "Record is too short"); 304 error = true; 305 break; 306 } 307 308 // Check the metadata key. 309 final int metadataId = parcel.readInt(); 310 if (!checkMetadataId(metadataId)) { 311 error = true; 312 break; 313 } 314 315 // Store the record offset which points to the type 316 // field so we can later on read/unmarshall the record 317 // payload. 318 if (mKeyToPosMap.containsKey(metadataId)) { 319 Log.e(TAG, "Duplicate metadata ID found"); 320 error = true; 321 break; 322 } 323 324 mKeyToPosMap.put(metadataId, parcel.dataPosition()); 325 326 // Check the metadata type. 327 final int metadataType = parcel.readInt(); 328 if (metadataType <= 0 || metadataType > LAST_TYPE) { 329 Log.e(TAG, "Invalid metadata type " + metadataType); 330 error = true; 331 break; 332 } 333 334 // Skip to the next one. 335 parcel.setDataPosition(start + size); 336 bytesLeft -= size; 337 ++recCount; 338 } 339 340 if (0 != bytesLeft || error) { 341 Log.e(TAG, "Ran out of data or error on record " + recCount); 342 mKeyToPosMap.clear(); 343 return false; 344 } else { 345 return true; 346 } 347 } 348 349 /** 350 * Check a parcel containing metadata is well formed. The header 351 * is checked as well as the individual records format. However, the 352 * data inside the record is not checked because we do lazy access 353 * (we check/unmarshall only data the user asks for.) 354 * 355 * Format of a metadata parcel: 356 <pre> 357 1 2 3 358 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 359 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 360 | metadata total size | 361 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 362 | 'M' | 'E' | 'T' | 'A' | 363 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 364 | | 365 | .... metadata records .... | 366 | | 367 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 368 </pre> 369 * 370 * @param parcel With the serialized data. Metadata keeps a 371 * reference on it to access it later on. The caller 372 * should not modify the parcel after this call (and 373 * not call recycle on it.) 374 * @return false if an error occurred. 375 * {@hide} 376 */ 377 public boolean parse(Parcel parcel) { 378 if (parcel.dataAvail() < kMetaHeaderSize) { 379 Log.e(TAG, "Not enough data " + parcel.dataAvail()); 380 return false; 381 } 382 383 final int pin = parcel.dataPosition(); // to roll back in case of errors. 384 final int size = parcel.readInt(); 385 386 // The extra kInt32Size below is to account for the int32 'size' just read. 387 if (parcel.dataAvail() + kInt32Size < size || size < kMetaHeaderSize) { 388 Log.e(TAG, "Bad size " + size + " avail " + parcel.dataAvail() + " position " + pin); 389 parcel.setDataPosition(pin); 390 return false; 391 } 392 393 // Checks if the 'M' 'E' 'T' 'A' marker is present. 394 final int kShouldBeMetaMarker = parcel.readInt(); 395 if (kShouldBeMetaMarker != kMetaMarker ) { 396 Log.e(TAG, "Marker missing " + Integer.toHexString(kShouldBeMetaMarker)); 397 parcel.setDataPosition(pin); 398 return false; 399 } 400 401 // Scan the records to collect metadata ids and offsets. 402 if (!scanAllRecords(parcel, size - kMetaHeaderSize)) { 403 parcel.setDataPosition(pin); 404 return false; 405 } 406 mParcel = parcel; 407 return true; 408 } 409 410 /** 411 * @return The set of metadata ID found. 412 */ 413 public Set<Integer> keySet() { 414 return mKeyToPosMap.keySet(); 415 } 416 417 /** 418 * @return true if a value is present for the given key. 419 */ 420 public boolean has(final int metadataId) { 421 if (!checkMetadataId(metadataId)) { 422 throw new IllegalArgumentException("Invalid key: " + metadataId); 423 } 424 return mKeyToPosMap.containsKey(metadataId); 425 } 426 427 // Accessors. 428 // Caller must make sure the key is present using the {@code has} 429 // method otherwise a RuntimeException will occur. 430 431 /** 432 * {@hide} 433 */ 434 public String getString(final int key) { 435 checkType(key, STRING_VAL); 436 return mParcel.readString(); 437 } 438 439 /** 440 * {@hide} 441 */ 442 public int getInt(final int key) { 443 checkType(key, INTEGER_VAL); 444 return mParcel.readInt(); 445 } 446 447 /** 448 * Get the boolean value indicated by key 449 */ 450 public boolean getBoolean(final int key) { 451 checkType(key, BOOLEAN_VAL); 452 return mParcel.readInt() == 1; 453 } 454 455 /** 456 * {@hide} 457 */ 458 public long getLong(final int key) { 459 checkType(key, LONG_VAL); /** 460 * {@hide} 461 */ 462 return mParcel.readLong(); 463 } 464 465 /** 466 * {@hide} 467 */ 468 public double getDouble(final int key) { 469 checkType(key, DOUBLE_VAL); 470 return mParcel.readDouble(); 471 } 472 473 /** 474 * {@hide} 475 */ 476 public byte[] getByteArray(final int key) { 477 checkType(key, BYTE_ARRAY_VAL); 478 return mParcel.createByteArray(); 479 } 480 481 /** 482 * {@hide} 483 */ 484 public Date getDate(final int key) { 485 checkType(key, DATE_VAL); 486 final long timeSinceEpoch = mParcel.readLong(); 487 final String timeZone = mParcel.readString(); 488 489 if (timeZone.length() == 0) { 490 return new Date(timeSinceEpoch); 491 } else { 492 TimeZone tz = TimeZone.getTimeZone(timeZone); 493 Calendar cal = Calendar.getInstance(tz); 494 495 cal.setTimeInMillis(timeSinceEpoch); 496 return cal.getTime(); 497 } 498 } 499 500 /** 501 * @return the last available system metadata id. Ids are 502 * 1-indexed. 503 * {@hide} 504 */ 505 public static int lastSytemId() { return LAST_SYSTEM; } 506 507 /** 508 * @return the first available cutom metadata id. 509 * {@hide} 510 */ 511 public static int firstCustomId() { return FIRST_CUSTOM; } 512 513 /** 514 * @return the last value of known type. Types are 1-indexed. 515 * {@hide} 516 */ 517 public static int lastType() { return LAST_TYPE; } 518 519 /** 520 * Check val is either a system id or a custom one. 521 * @param val Metadata key to test. 522 * @return true if it is in a valid range. 523 **/ 524 private boolean checkMetadataId(final int val) { 525 if (val <= ANY || (LAST_SYSTEM < val && val < FIRST_CUSTOM)) { 526 Log.e(TAG, "Invalid metadata ID " + val); 527 return false; 528 } 529 return true; 530 } 531 532 /** 533 * Check the type of the data match what is expected. 534 */ 535 private void checkType(final int key, final int expectedType) { 536 final int pos = mKeyToPosMap.get(key); 537 538 mParcel.setDataPosition(pos); 539 540 final int type = mParcel.readInt(); 541 if (type != expectedType) { 542 throw new IllegalStateException("Wrong type " + expectedType + " but got " + type); 543 } 544 } 545 } 546