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