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