Home | History | Annotate | Download | only in media
      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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    442     public String getString(final int key) {
    443         checkType(key, STRING_VAL);
    444         return mParcel.readString();
    445     }
    446 
    447     /**
    448      * {@hide}
    449      */
    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      */
    458     public boolean getBoolean(final int key) {
    459         checkType(key, BOOLEAN_VAL);
    460         return mParcel.readInt() == 1;
    461     }
    462 
    463     /**
    464      * {@hide}
    465      */
    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      */
    476     public double getDouble(final int key) {
    477         checkType(key, DOUBLE_VAL);
    478         return mParcel.readDouble();
    479     }
    480 
    481     /**
    482      * {@hide}
    483      */
    484     public byte[] getByteArray(final int key) {
    485         checkType(key, BYTE_ARRAY_VAL);
    486         return mParcel.createByteArray();
    487     }
    488 
    489     /**
    490      * {@hide}
    491      */
    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      */
    513     public static int lastSytemId() { return LAST_SYSTEM; }
    514 
    515     /**
    516      * @return the first available cutom metadata id.
    517      * {@hide}
    518      */
    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      */
    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      **/
    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      */
    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