Metadata.java revision 716383a686b086f68533a51785ba77186359ce6b
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 if (parcel.dataAvail() < size || size < kMetaHeaderSize) { 284 Log.e(TAG, "Bad size " + size); 285 parcel.setDataPosition(pin); 286 return false; 287 } 288 289 // Checks if the 'M' 'E' 'T' 'A' marker is present. 290 final int kShouldBeMetaMarker = parcel.readInt(); 291 if (kShouldBeMetaMarker != kMetaMarker ) { 292 Log.e(TAG, "Marker missing " + Integer.toHexString(kShouldBeMetaMarker)); 293 parcel.setDataPosition(pin); 294 return false; 295 } 296 297 // Scan the records to collect metadata ids and offsets. 298 if (!scanAllRecords(parcel, size - kMetaHeaderSize)) { 299 parcel.setDataPosition(pin); 300 return false; 301 } 302 mParcel = parcel; 303 return true; 304 } 305 306 /** 307 * @return The set of metadata ID found. 308 */ 309 public Set<Integer> keySet() { 310 return mKeyToPosMap.keySet(); 311 } 312 313 /** 314 * @return true if a value is present for the given key. 315 */ 316 public boolean has(final int metadataId) { 317 if (!checkMetadataId(metadataId)) { 318 throw new IllegalArgumentException("Invalid key: " + metadataId); 319 } 320 return mKeyToPosMap.containsKey(metadataId); 321 } 322 323 // Accessors. 324 // Caller must make sure the key is present using the {@code has} 325 // method otherwise a RuntimeException will occur. 326 327 public String getString(final int key) { 328 checkType(key, STRING_VAL); 329 return mParcel.readString(); 330 } 331 332 public int getInt(final int key) { 333 checkType(key, INTEGER_VAL); 334 return mParcel.readInt(); 335 } 336 337 public boolean getBoolean(final int key) { 338 checkType(key, BOOLEAN_VAL); 339 return mParcel.readInt() == 1; 340 } 341 342 public long getLong(final int key) { 343 checkType(key, LONG_VAL); 344 return mParcel.readLong(); 345 } 346 347 public double getDouble(final int key) { 348 checkType(key, DOUBLE_VAL); 349 return mParcel.readDouble(); 350 } 351 352 public byte[] getByteArray(final int key) { 353 checkType(key, BYTE_ARRAY_VAL); 354 return mParcel.createByteArray(); 355 } 356 357 public Date getDate(final int key) { 358 checkType(key, DATE_VAL); 359 final long timeSinceEpoch = mParcel.readLong(); 360 final String timeZone = mParcel.readString(); 361 362 if (timeZone.length() == 0) { 363 return new Date(timeSinceEpoch); 364 } else { 365 TimeZone tz = TimeZone.getTimeZone(timeZone); 366 Calendar cal = Calendar.getInstance(tz); 367 368 cal.setTimeInMillis(timeSinceEpoch); 369 return cal.getTime(); 370 } 371 } 372 373 public TimedText getTimedText(final int key) { 374 checkType(key, TIMED_TEXT_VAL); 375 final Date startTime = new Date(mParcel.readLong()); // epoch 376 final int duration = mParcel.readInt(); // millisec 377 378 return new TimedText(startTime, 379 duration, 380 mParcel.readString()); 381 } 382 383 // @return the last available system metadata id. Ids are 384 // 1-indexed. 385 public static int lastSytemId() { return LAST_SYSTEM; } 386 387 // @return the first available cutom metadata id. 388 public static int firstCustomId() { return FIRST_CUSTOM; } 389 390 // @return the last value of known type. Types are 1-indexed. 391 public static int lastType() { return LAST_TYPE; } 392 393 // Check val is either a system id or a custom one. 394 // @param val Metadata key to test. 395 // @return true if it is in a valid range. 396 private boolean checkMetadataId(final int val) { 397 if (val <= ANY || (LAST_SYSTEM < val && val < FIRST_CUSTOM)) { 398 Log.e(TAG, "Invalid metadata ID " + val); 399 return false; 400 } 401 return true; 402 } 403 404 // Check the type of the data match what is expected. 405 private void checkType(final int key, final int expectedType) { 406 final int pos = mKeyToPosMap.get(key); 407 408 mParcel.setDataPosition(pos); 409 410 final int type = mParcel.readInt(); 411 if (type != expectedType) { 412 throw new IllegalStateException("Wrong type " + expectedType + " but got " + type); 413 } 414 } 415} 416