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