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