| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.media; |
| |
| import android.annotation.IntDef; |
| import android.annotation.IntRange; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.compat.annotation.UnsupportedAppUsage; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.res.AssetFileDescriptor; |
| import android.graphics.Bitmap; |
| import android.net.Uri; |
| import android.os.Build; |
| import android.os.IBinder; |
| import android.text.TextUtils; |
| |
| import java.io.FileDescriptor; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * MediaMetadataRetriever class provides a unified interface for retrieving |
| * frame and meta data from an input media file. |
| */ |
| public class MediaMetadataRetriever implements AutoCloseable { |
| |
| // borrowed from ExoPlayer |
| private static final String[] STANDARD_GENRES = new String[] { |
| // These are the official ID3v1 genres. |
| "Blues", |
| "Classic Rock", |
| "Country", |
| "Dance", |
| "Disco", |
| "Funk", |
| "Grunge", |
| "Hip-Hop", |
| "Jazz", |
| "Metal", |
| "New Age", |
| "Oldies", |
| "Other", |
| "Pop", |
| "R&B", |
| "Rap", |
| "Reggae", |
| "Rock", |
| "Techno", |
| "Industrial", |
| "Alternative", |
| "Ska", |
| "Death Metal", |
| "Pranks", |
| "Soundtrack", |
| "Euro-Techno", |
| "Ambient", |
| "Trip-Hop", |
| "Vocal", |
| "Jazz+Funk", |
| "Fusion", |
| "Trance", |
| "Classical", |
| "Instrumental", |
| "Acid", |
| "House", |
| "Game", |
| "Sound Clip", |
| "Gospel", |
| "Noise", |
| "AlternRock", |
| "Bass", |
| "Soul", |
| "Punk", |
| "Space", |
| "Meditative", |
| "Instrumental Pop", |
| "Instrumental Rock", |
| "Ethnic", |
| "Gothic", |
| "Darkwave", |
| "Techno-Industrial", |
| "Electronic", |
| "Pop-Folk", |
| "Eurodance", |
| "Dream", |
| "Southern Rock", |
| "Comedy", |
| "Cult", |
| "Gangsta", |
| "Top 40", |
| "Christian Rap", |
| "Pop/Funk", |
| "Jungle", |
| "Native American", |
| "Cabaret", |
| "New Wave", |
| "Psychadelic", |
| "Rave", |
| "Showtunes", |
| "Trailer", |
| "Lo-Fi", |
| "Tribal", |
| "Acid Punk", |
| "Acid Jazz", |
| "Polka", |
| "Retro", |
| "Musical", |
| "Rock & Roll", |
| "Hard Rock", |
| // These were made up by the authors of Winamp and later added to the ID3 spec. |
| "Folk", |
| "Folk-Rock", |
| "National Folk", |
| "Swing", |
| "Fast Fusion", |
| "Bebob", |
| "Latin", |
| "Revival", |
| "Celtic", |
| "Bluegrass", |
| "Avantgarde", |
| "Gothic Rock", |
| "Progressive Rock", |
| "Psychedelic Rock", |
| "Symphonic Rock", |
| "Slow Rock", |
| "Big Band", |
| "Chorus", |
| "Easy Listening", |
| "Acoustic", |
| "Humour", |
| "Speech", |
| "Chanson", |
| "Opera", |
| "Chamber Music", |
| "Sonata", |
| "Symphony", |
| "Booty Bass", |
| "Primus", |
| "Porn Groove", |
| "Satire", |
| "Slow Jam", |
| "Club", |
| "Tango", |
| "Samba", |
| "Folklore", |
| "Ballad", |
| "Power Ballad", |
| "Rhythmic Soul", |
| "Freestyle", |
| "Duet", |
| "Punk Rock", |
| "Drum Solo", |
| "A capella", |
| "Euro-House", |
| "Dance Hall", |
| // These were made up by the authors of Winamp but have not been added to the ID3 spec. |
| "Goa", |
| "Drum & Bass", |
| "Club-House", |
| "Hardcore", |
| "Terror", |
| "Indie", |
| "BritPop", |
| "Afro-Punk", |
| "Polsk Punk", |
| "Beat", |
| "Christian Gangsta Rap", |
| "Heavy Metal", |
| "Black Metal", |
| "Crossover", |
| "Contemporary Christian", |
| "Christian Rock", |
| "Merengue", |
| "Salsa", |
| "Thrash Metal", |
| "Anime", |
| "Jpop", |
| "Synthpop" |
| }; |
| |
| static { |
| System.loadLibrary("media_jni"); |
| native_init(); |
| } |
| |
| // The field below is accessed by native methods |
| @SuppressWarnings("unused") |
| private long mNativeContext; |
| |
| private static final int EMBEDDED_PICTURE_TYPE_ANY = 0xFFFF; |
| |
| public MediaMetadataRetriever() { |
| native_setup(); |
| } |
| |
| /** |
| * Sets the data source (file pathname) to use. Call this |
| * method before the rest of the methods in this class. This method may be |
| * time-consuming. |
| * |
| * @param path The path, or the URI (doesn't support streaming source currently) |
| * of the input media file. |
| * @throws IllegalArgumentException If the path is invalid. |
| */ |
| public void setDataSource(String path) throws IllegalArgumentException { |
| if (path == null) { |
| throw new IllegalArgumentException("null path"); |
| } |
| |
| final Uri uri = Uri.parse(path); |
| final String scheme = uri.getScheme(); |
| if ("file".equals(scheme)) { |
| path = uri.getPath(); |
| } else if (scheme != null) { |
| setDataSource(path, new HashMap<String, String>()); |
| return; |
| } |
| |
| try (FileInputStream is = new FileInputStream(path)) { |
| FileDescriptor fd = is.getFD(); |
| setDataSource(fd, 0, 0x7ffffffffffffffL); |
| } catch (FileNotFoundException fileEx) { |
| throw new IllegalArgumentException(path + " does not exist"); |
| } catch (IOException ioEx) { |
| throw new IllegalArgumentException("couldn't open " + path); |
| } |
| } |
| |
| /** |
| * Sets the data source (URI) to use. Call this |
| * method before the rest of the methods in this class. This method may be |
| * time-consuming. |
| * |
| * @param uri The URI of the input media. |
| * @param headers the headers to be sent together with the request for the data |
| * @throws IllegalArgumentException If the URI is invalid. |
| */ |
| public void setDataSource(String uri, Map<String, String> headers) |
| throws IllegalArgumentException { |
| int i = 0; |
| String[] keys = new String[headers.size()]; |
| String[] values = new String[headers.size()]; |
| for (Map.Entry<String, String> entry: headers.entrySet()) { |
| keys[i] = entry.getKey(); |
| values[i] = entry.getValue(); |
| ++i; |
| } |
| |
| _setDataSource( |
| MediaHTTPService.createHttpServiceBinderIfNecessary(uri), |
| uri, |
| keys, |
| values); |
| } |
| |
| private native void _setDataSource( |
| IBinder httpServiceBinder, String uri, String[] keys, String[] values) |
| throws IllegalArgumentException; |
| |
| /** |
| * Sets the data source (FileDescriptor) to use. It is the caller's |
| * responsibility to close the file descriptor. It is safe to do so as soon |
| * as this call returns. Call this method before the rest of the methods in |
| * this class. This method may be time-consuming. |
| * |
| * @param fd the FileDescriptor for the file you want to play |
| * @param offset the offset into the file where the data to be played starts, |
| * in bytes. It must be non-negative |
| * @param length the length in bytes of the data to be played. It must be |
| * non-negative. |
| * @throws IllegalArgumentException if the arguments are invalid |
| */ |
| public native void setDataSource(FileDescriptor fd, long offset, long length) |
| throws IllegalArgumentException; |
| |
| /** |
| * Sets the data source (FileDescriptor) to use. It is the caller's |
| * responsibility to close the file descriptor. It is safe to do so as soon |
| * as this call returns. Call this method before the rest of the methods in |
| * this class. This method may be time-consuming. |
| * |
| * @param fd the FileDescriptor for the file you want to play |
| * @throws IllegalArgumentException if the FileDescriptor is invalid |
| */ |
| public void setDataSource(FileDescriptor fd) |
| throws IllegalArgumentException { |
| // intentionally less than LONG_MAX |
| setDataSource(fd, 0, 0x7ffffffffffffffL); |
| } |
| |
| /** |
| * Sets the data source as a content Uri. Call this method before |
| * the rest of the methods in this class. This method may be time-consuming. |
| * |
| * @param context the Context to use when resolving the Uri |
| * @param uri the Content URI of the data you want to play |
| * @throws IllegalArgumentException if the Uri is invalid |
| * @throws SecurityException if the Uri cannot be used due to lack of |
| * permission. |
| */ |
| public void setDataSource(Context context, Uri uri) |
| throws IllegalArgumentException, SecurityException { |
| if (uri == null) { |
| throw new IllegalArgumentException("null uri"); |
| } |
| |
| String scheme = uri.getScheme(); |
| if(scheme == null || scheme.equals("file")) { |
| setDataSource(uri.getPath()); |
| return; |
| } |
| |
| AssetFileDescriptor fd = null; |
| try { |
| ContentResolver resolver = context.getContentResolver(); |
| try { |
| fd = resolver.openAssetFileDescriptor(uri, "r"); |
| } catch(FileNotFoundException e) { |
| throw new IllegalArgumentException("could not access " + uri); |
| } |
| if (fd == null) { |
| throw new IllegalArgumentException("got null FileDescriptor for " + uri); |
| } |
| FileDescriptor descriptor = fd.getFileDescriptor(); |
| if (!descriptor.valid()) { |
| throw new IllegalArgumentException("got invalid FileDescriptor for " + uri); |
| } |
| // Note: using getDeclaredLength so that our behavior is the same |
| // as previous versions when the content provider is returning |
| // a full file. |
| if (fd.getDeclaredLength() < 0) { |
| setDataSource(descriptor); |
| } else { |
| setDataSource(descriptor, fd.getStartOffset(), fd.getDeclaredLength()); |
| } |
| return; |
| } catch (SecurityException ex) { |
| } finally { |
| try { |
| if (fd != null) { |
| fd.close(); |
| } |
| } catch(IOException ioEx) { |
| } |
| } |
| setDataSource(uri.toString()); |
| } |
| |
| /** |
| * Sets the data source (MediaDataSource) to use. |
| * |
| * @param dataSource the MediaDataSource for the media you want to play |
| */ |
| public void setDataSource(MediaDataSource dataSource) |
| throws IllegalArgumentException { |
| _setDataSource(dataSource); |
| } |
| |
| private native void _setDataSource(MediaDataSource dataSource) |
| throws IllegalArgumentException; |
| |
| private native @Nullable String nativeExtractMetadata(int keyCode); |
| |
| /** |
| * Call this method after setDataSource(). This method retrieves the |
| * meta data value associated with the keyCode. |
| * |
| * The keyCode currently supported is listed below as METADATA_XXX |
| * constants. With any other value, it returns a null pointer. |
| * |
| * @param keyCode One of the constants listed below at the end of the class. |
| * @return The meta data value associate with the given keyCode on success; |
| * null on failure. |
| */ |
| public @Nullable String extractMetadata(int keyCode) { |
| String meta = nativeExtractMetadata(keyCode); |
| if (keyCode == METADATA_KEY_GENRE) { |
| // translate numeric genre code(s) to human readable |
| meta = convertGenreTag(meta); |
| } |
| return meta; |
| } |
| |
| /* |
| * The id3v2 spec doesn't specify the syntax of the genre tag very precisely, so |
| * some assumptions are made. Using one possible interpretation of the id3v2 |
| * spec, this method converts an id3 genre tag string to a human readable string, |
| * as follows: |
| * - if the first character of the tag is a digit, the entire tag is assumed to |
| * be an id3v1 numeric genre code. If the tag does not parse to a number, or |
| * the number is outside the range of defined standard genres, it is ignored. |
| * - if the tag does not start with a digit, it is assumed to be an id3v2 style |
| * tag consisting of one or more genres, with each genre being either a parenthesized |
| * integer referring to an id3v1 numeric genre code, the special indicators "(CR)" or |
| * "(RX)" (for "Cover" or "Remix", respectively), or a custom genre string. When |
| * a custom genre string is encountered, it is assumed to continue until the end |
| * of the tag, unless it starts with "((" in which case it is assumed to continue |
| * until the next close-parenthesis or the end of the tag. Any parse error in the tag |
| * causes it to be ignored. |
| * The human-readable genre string is not localized, and uses the English genre names |
| * from the spec. |
| */ |
| private String convertGenreTag(String meta) { |
| if (TextUtils.isEmpty(meta)) { |
| return null; |
| } |
| |
| if (Character.isDigit(meta.charAt(0))) { |
| // assume a single id3v1-style bare number without any extra characters |
| try { |
| int genreIndex = Integer.parseInt(meta); |
| if (genreIndex >= 0 && genreIndex < STANDARD_GENRES.length) { |
| return STANDARD_GENRES[genreIndex]; |
| } |
| } catch (NumberFormatException e) { |
| // ignore and fall through |
| } |
| return null; |
| } else { |
| // assume id3v2-style genre tag, with parenthesized numeric genres |
| // and/or literal genre strings, possibly more than one per tag. |
| StringBuilder genres = null; |
| String nextGenre = null; |
| while (true) { |
| if (!TextUtils.isEmpty(nextGenre)) { |
| if (genres == null) { |
| genres = new StringBuilder(); |
| } |
| if (genres.length() != 0) { |
| genres.append(", "); |
| } |
| genres.append(nextGenre); |
| nextGenre = null; |
| } |
| if (TextUtils.isEmpty(meta)) { |
| // entire tag has been processed. |
| break; |
| } |
| if (meta.startsWith("(RX)")) { |
| nextGenre = "Remix"; |
| meta = meta.substring(4); |
| } else if (meta.startsWith("(CR)")) { |
| nextGenre = "Cover"; |
| meta = meta.substring(4); |
| } else if (meta.startsWith("((")) { |
| // the id3v2 spec says that custom genres that start with a parenthesis |
| // should be "escaped" with another parenthesis, however the spec doesn't |
| // specify escaping parentheses inside the custom string. We'll parse any |
| // such strings until a closing parenthesis is found, or the end of |
| // the tag is reached. |
| int closeParenOffset = meta.indexOf(')'); |
| if (closeParenOffset == -1) { |
| // string continues to end of tag |
| nextGenre = meta.substring(1); |
| meta = ""; |
| } else { |
| nextGenre = meta.substring(1, closeParenOffset + 1); |
| meta = meta.substring(closeParenOffset + 1); |
| } |
| } else if (meta.startsWith("(")) { |
| // should be a parenthesized numeric genre |
| int closeParenOffset = meta.indexOf(')'); |
| if (closeParenOffset == -1) { |
| return null; |
| } |
| String genreNumString = meta.substring(1, closeParenOffset); |
| try { |
| int genreIndex = Integer.parseInt(genreNumString.toString()); |
| if (genreIndex >= 0 && genreIndex < STANDARD_GENRES.length) { |
| nextGenre = STANDARD_GENRES[genreIndex]; |
| } else { |
| return null; |
| } |
| } catch (NumberFormatException e) { |
| return null; |
| } |
| meta = meta.substring(closeParenOffset + 1); |
| } else { |
| // custom genre |
| nextGenre = meta; |
| meta = ""; |
| } |
| } |
| return genres == null || genres.length() == 0 ? null : genres.toString(); |
| } |
| } |
| |
| /** |
| * This method is similar to {@link #getFrameAtTime(long, int, BitmapParams)} |
| * except that the device will choose the actual {@link Bitmap.Config} to use. |
| * |
| * @param timeUs The time position where the frame will be retrieved. |
| * When retrieving the frame at the given time position, there is no |
| * guarantee that the data source has a frame located at the position. |
| * When this happens, a frame nearby will be returned. If timeUs is |
| * negative, time position and option will ignored, and any frame |
| * that the implementation considers as representative may be returned. |
| * |
| * @param option a hint on how the frame is found. Use |
| * {@link #OPTION_PREVIOUS_SYNC} if one wants to retrieve a sync frame |
| * that has a timestamp earlier than or the same as timeUs. Use |
| * {@link #OPTION_NEXT_SYNC} if one wants to retrieve a sync frame |
| * that has a timestamp later than or the same as timeUs. Use |
| * {@link #OPTION_CLOSEST_SYNC} if one wants to retrieve a sync frame |
| * that has a timestamp closest to or the same as timeUs. Use |
| * {@link #OPTION_CLOSEST} if one wants to retrieve a frame that may |
| * or may not be a sync frame but is closest to or the same as timeUs. |
| * {@link #OPTION_CLOSEST} often has larger performance overhead compared |
| * to the other options if there is no sync frame located at timeUs. |
| * |
| * @return A Bitmap containing a representative video frame, which can be null, |
| * if such a frame cannot be retrieved. {@link Bitmap#getConfig()} can |
| * be used to query the actual {@link Bitmap.Config}. |
| * |
| * @see #getFrameAtTime(long, int, BitmapParams) |
| */ |
| public @Nullable Bitmap getFrameAtTime(long timeUs, @Option int option) { |
| if (option < OPTION_PREVIOUS_SYNC || |
| option > OPTION_CLOSEST) { |
| throw new IllegalArgumentException("Unsupported option: " + option); |
| } |
| |
| return _getFrameAtTime(timeUs, option, -1 /*dst_width*/, -1 /*dst_height*/, null); |
| } |
| |
| /** |
| * Call this method after setDataSource(). This method finds a |
| * representative frame close to the given time position by considering |
| * the given option if possible, and returns it as a bitmap. |
| * |
| * <p>If you don't need a full-resolution |
| * frame (for example, because you need a thumbnail image), use |
| * {@link #getScaledFrameAtTime getScaledFrameAtTime()} instead of this |
| * method.</p> |
| * |
| * @param timeUs The time position where the frame will be retrieved. |
| * When retrieving the frame at the given time position, there is no |
| * guarantee that the data source has a frame located at the position. |
| * When this happens, a frame nearby will be returned. If timeUs is |
| * negative, time position and option will ignored, and any frame |
| * that the implementation considers as representative may be returned. |
| * |
| * @param option a hint on how the frame is found. Use |
| * {@link #OPTION_PREVIOUS_SYNC} if one wants to retrieve a sync frame |
| * that has a timestamp earlier than or the same as timeUs. Use |
| * {@link #OPTION_NEXT_SYNC} if one wants to retrieve a sync frame |
| * that has a timestamp later than or the same as timeUs. Use |
| * {@link #OPTION_CLOSEST_SYNC} if one wants to retrieve a sync frame |
| * that has a timestamp closest to or the same as timeUs. Use |
| * {@link #OPTION_CLOSEST} if one wants to retrieve a frame that may |
| * or may not be a sync frame but is closest to or the same as timeUs. |
| * {@link #OPTION_CLOSEST} often has larger performance overhead compared |
| * to the other options if there is no sync frame located at timeUs. |
| * |
| * @param params BitmapParams that controls the returned bitmap config |
| * (such as pixel formats). |
| * |
| * @return A Bitmap containing a representative video frame, which |
| * can be null, if such a frame cannot be retrieved. |
| * |
| * @see #getFrameAtTime(long, int) |
| */ |
| public @Nullable Bitmap getFrameAtTime( |
| long timeUs, @Option int option, @NonNull BitmapParams params) { |
| if (option < OPTION_PREVIOUS_SYNC || |
| option > OPTION_CLOSEST) { |
| throw new IllegalArgumentException("Unsupported option: " + option); |
| } |
| |
| return _getFrameAtTime(timeUs, option, -1 /*dst_width*/, -1 /*dst_height*/, params); |
| } |
| |
| /** |
| * This method is similar to {@link #getScaledFrameAtTime(long, int, int, int, BitmapParams)} |
| * except that the device will choose the actual {@link Bitmap.Config} to use. |
| * |
| * @param timeUs The time position in microseconds where the frame will be retrieved. |
| * When retrieving the frame at the given time position, there is no |
| * guarantee that the data source has a frame located at the position. |
| * When this happens, a frame nearby will be returned. If timeUs is |
| * negative, time position and option will ignored, and any frame |
| * that the implementation considers as representative may be returned. |
| * |
| * @param option a hint on how the frame is found. Use |
| * {@link #OPTION_PREVIOUS_SYNC} if one wants to retrieve a sync frame |
| * that has a timestamp earlier than or the same as timeUs. Use |
| * {@link #OPTION_NEXT_SYNC} if one wants to retrieve a sync frame |
| * that has a timestamp later than or the same as timeUs. Use |
| * {@link #OPTION_CLOSEST_SYNC} if one wants to retrieve a sync frame |
| * that has a timestamp closest to or the same as timeUs. Use |
| * {@link #OPTION_CLOSEST} if one wants to retrieve a frame that may |
| * or may not be a sync frame but is closest to or the same as timeUs. |
| * {@link #OPTION_CLOSEST} often has larger performance overhead compared |
| * to the other options if there is no sync frame located at timeUs. |
| * |
| * @param dstWidth expected output bitmap width |
| * @param dstHeight expected output bitmap height |
| * @return A Bitmap containing a representative video frame, which can be null, |
| * if such a frame cannot be retrieved. {@link Bitmap#getConfig()} can |
| * be used to query the actual {@link Bitmap.Config}. |
| * @throws IllegalArgumentException if passed in invalid option or width by height |
| * is less than or equal to 0. |
| * @see #getScaledFrameAtTime(long, int, int, int, BitmapParams) |
| */ |
| public @Nullable Bitmap getScaledFrameAtTime(long timeUs, @Option int option, |
| @IntRange(from=1) int dstWidth, @IntRange(from=1) int dstHeight) { |
| validate(option, dstWidth, dstHeight); |
| return _getFrameAtTime(timeUs, option, dstWidth, dstHeight, null); |
| } |
| |
| /** |
| * Retrieve a video frame near a given timestamp scaled to a desired size. |
| * Call this method after setDataSource(). This method finds a representative |
| * frame close to the given time position by considering the given option |
| * if possible, and returns it as a bitmap with same aspect ratio as the source |
| * while scaling it so that it fits into the desired size of dst_width by dst_height. |
| * This is useful for generating a thumbnail for an input data source or just to |
| * obtain a scaled frame at the given time position. |
| * |
| * @param timeUs The time position in microseconds where the frame will be retrieved. |
| * When retrieving the frame at the given time position, there is no |
| * guarantee that the data source has a frame located at the position. |
| * When this happens, a frame nearby will be returned. If timeUs is |
| * negative, time position and option will ignored, and any frame |
| * that the implementation considers as representative may be returned. |
| * |
| * @param option a hint on how the frame is found. Use |
| * {@link #OPTION_PREVIOUS_SYNC} if one wants to retrieve a sync frame |
| * that has a timestamp earlier than or the same as timeUs. Use |
| * {@link #OPTION_NEXT_SYNC} if one wants to retrieve a sync frame |
| * that has a timestamp later than or the same as timeUs. Use |
| * {@link #OPTION_CLOSEST_SYNC} if one wants to retrieve a sync frame |
| * that has a timestamp closest to or the same as timeUs. Use |
| * {@link #OPTION_CLOSEST} if one wants to retrieve a frame that may |
| * or may not be a sync frame but is closest to or the same as timeUs. |
| * {@link #OPTION_CLOSEST} often has larger performance overhead compared |
| * to the other options if there is no sync frame located at timeUs. |
| * |
| * @param dstWidth expected output bitmap width |
| * @param dstHeight expected output bitmap height |
| * @param params BitmapParams that controls the returned bitmap config |
| * (such as pixel formats). |
| * |
| * @return A Bitmap of size not larger than dstWidth by dstHeight containing a |
| * scaled video frame, which can be null, if such a frame cannot be retrieved. |
| * @throws IllegalArgumentException if passed in invalid option or width by height |
| * is less than or equal to 0. |
| * @see #getScaledFrameAtTime(long, int, int, int) |
| */ |
| public @Nullable Bitmap getScaledFrameAtTime(long timeUs, @Option int option, |
| @IntRange(from=1) int dstWidth, @IntRange(from=1) int dstHeight, |
| @NonNull BitmapParams params) { |
| validate(option, dstWidth, dstHeight); |
| return _getFrameAtTime(timeUs, option, dstWidth, dstHeight, params); |
| } |
| |
| private void validate(@Option int option, int dstWidth, int dstHeight) { |
| if (option < OPTION_PREVIOUS_SYNC || option > OPTION_CLOSEST) { |
| throw new IllegalArgumentException("Unsupported option: " + option); |
| } |
| if (dstWidth <= 0) { |
| throw new IllegalArgumentException("Invalid width: " + dstWidth); |
| } |
| if (dstHeight <= 0) { |
| throw new IllegalArgumentException("Invalid height: " + dstHeight); |
| } |
| } |
| |
| /** |
| * Call this method after setDataSource(). This method finds a |
| * representative frame close to the given time position if possible, |
| * and returns it as a bitmap. Call this method if one does not care |
| * how the frame is found as long as it is close to the given time; |
| * otherwise, please call {@link #getFrameAtTime(long, int)}. |
| * |
| * <p>If you don't need a full-resolution |
| * frame (for example, because you need a thumbnail image), use |
| * {@link #getScaledFrameAtTime getScaledFrameAtTime()} instead of this |
| * method.</p> |
| * |
| * @param timeUs The time position where the frame will be retrieved. |
| * When retrieving the frame at the given time position, there is no |
| * guarentee that the data source has a frame located at the position. |
| * When this happens, a frame nearby will be returned. If timeUs is |
| * negative, time position and option will ignored, and any frame |
| * that the implementation considers as representative may be returned. |
| * |
| * @return A Bitmap of size dst_widthxdst_height containing a representative |
| * video frame, which can be null, if such a frame cannot be retrieved. |
| * |
| * @see #getFrameAtTime(long, int) |
| */ |
| public @Nullable Bitmap getFrameAtTime(long timeUs) { |
| return getFrameAtTime(timeUs, OPTION_CLOSEST_SYNC); |
| } |
| |
| /** |
| * Call this method after setDataSource(). This method finds a |
| * representative frame at any time position if possible, |
| * and returns it as a bitmap. Call this method if one does not |
| * care about where the frame is located; otherwise, please call |
| * {@link #getFrameAtTime(long)} or {@link #getFrameAtTime(long, int)} |
| * |
| * <p>If you don't need a full-resolution |
| * frame (for example, because you need a thumbnail image), use |
| * {@link #getScaledFrameAtTime getScaledFrameAtTime()} instead of this |
| * method.</p> |
| * |
| * @return A Bitmap containing a representative video frame, which |
| * can be null, if such a frame cannot be retrieved. |
| * |
| * @see #getFrameAtTime(long) |
| * @see #getFrameAtTime(long, int) |
| */ |
| public @Nullable Bitmap getFrameAtTime() { |
| return _getFrameAtTime( |
| -1, OPTION_CLOSEST_SYNC, -1 /*dst_width*/, -1 /*dst_height*/, null); |
| } |
| |
| private native Bitmap _getFrameAtTime( |
| long timeUs, int option, int width, int height, @Nullable BitmapParams params); |
| |
| public static final class BitmapParams { |
| private Bitmap.Config inPreferredConfig = Bitmap.Config.ARGB_8888; |
| private Bitmap.Config outActualConfig = Bitmap.Config.ARGB_8888; |
| |
| /** |
| * Create a default BitmapParams object. By default, it uses {@link Bitmap.Config#ARGB_8888} |
| * as the preferred bitmap config. |
| */ |
| public BitmapParams() {} |
| |
| /** |
| * Set the preferred bitmap config for the decoder to decode into. |
| * |
| * If not set, or the request cannot be met, the decoder will output |
| * in {@link Bitmap.Config#ARGB_8888} config by default. |
| * |
| * After decode, the actual config used can be retrieved by {@link #getActualConfig()}. |
| * |
| * @param config the preferred bitmap config to use. |
| */ |
| public void setPreferredConfig(@NonNull Bitmap.Config config) { |
| if (config == null) { |
| throw new IllegalArgumentException("preferred config can't be null"); |
| } |
| inPreferredConfig = config; |
| } |
| |
| /** |
| * Retrieve the preferred bitmap config in the params. |
| * |
| * @return the preferred bitmap config. |
| */ |
| public @NonNull Bitmap.Config getPreferredConfig() { |
| return inPreferredConfig; |
| } |
| |
| /** |
| * Get the actual bitmap config used to decode the bitmap after the decoding. |
| * |
| * @return the actual bitmap config used. |
| */ |
| public @NonNull Bitmap.Config getActualConfig() { |
| return outActualConfig; |
| } |
| } |
| |
| /** |
| * This method retrieves a video frame by its index. It should only be called |
| * after {@link #setDataSource}. |
| * |
| * After the bitmap is returned, you can query the actual parameters that were |
| * used to create the bitmap from the {@code BitmapParams} argument, for instance |
| * to query the bitmap config used for the bitmap with {@link BitmapParams#getActualConfig}. |
| * |
| * @param frameIndex 0-based index of the video frame. The frame index must be that of |
| * a valid frame. The total number of frames available for retrieval can be queried |
| * via the {@link #METADATA_KEY_VIDEO_FRAME_COUNT} key. |
| * @param params BitmapParams that controls the returned bitmap config (such as pixel formats). |
| * |
| * @throws IllegalStateException if the container doesn't contain video or image sequences. |
| * @throws IllegalArgumentException if the requested frame index does not exist. |
| * |
| * @return A Bitmap containing the requested video frame, or null if the retrieval fails. |
| * |
| * @see #getFrameAtIndex(int) |
| * @see #getFramesAtIndex(int, int, BitmapParams) |
| * @see #getFramesAtIndex(int, int) |
| */ |
| public @Nullable Bitmap getFrameAtIndex(int frameIndex, @NonNull BitmapParams params) { |
| List<Bitmap> bitmaps = getFramesAtIndex(frameIndex, 1, params); |
| return bitmaps.get(0); |
| } |
| |
| /** |
| * This method is similar to {@link #getFrameAtIndex(int, BitmapParams)} except that |
| * the default for {@link BitmapParams} will be used. |
| * |
| * @param frameIndex 0-based index of the video frame. The frame index must be that of |
| * a valid frame. The total number of frames available for retrieval can be queried |
| * via the {@link #METADATA_KEY_VIDEO_FRAME_COUNT} key. |
| * |
| * @throws IllegalStateException if the container doesn't contain video or image sequences. |
| * @throws IllegalArgumentException if the requested frame index does not exist. |
| * |
| * @return A Bitmap containing the requested video frame, or null if the retrieval fails. |
| * |
| * @see #getFrameAtIndex(int, BitmapParams) |
| * @see #getFramesAtIndex(int, int, BitmapParams) |
| * @see #getFramesAtIndex(int, int) |
| */ |
| public @Nullable Bitmap getFrameAtIndex(int frameIndex) { |
| List<Bitmap> bitmaps = getFramesAtIndex(frameIndex, 1); |
| return bitmaps.get(0); |
| } |
| |
| /** |
| * This method retrieves a consecutive set of video frames starting at the |
| * specified index. It should only be called after {@link #setDataSource}. |
| * |
| * If the caller intends to retrieve more than one consecutive video frames, |
| * this method is preferred over {@link #getFrameAtIndex(int, BitmapParams)} for efficiency. |
| * |
| * After the bitmaps are returned, you can query the actual parameters that were |
| * used to create the bitmaps from the {@code BitmapParams} argument, for instance |
| * to query the bitmap config used for the bitmaps with {@link BitmapParams#getActualConfig}. |
| * |
| * @param frameIndex 0-based index of the first video frame to retrieve. The frame index |
| * must be that of a valid frame. The total number of frames available for retrieval |
| * can be queried via the {@link #METADATA_KEY_VIDEO_FRAME_COUNT} key. |
| * @param numFrames number of consecutive video frames to retrieve. Must be a positive |
| * value. The stream must contain at least numFrames frames starting at frameIndex. |
| * @param params BitmapParams that controls the returned bitmap config (such as pixel formats). |
| * |
| * @throws IllegalStateException if the container doesn't contain video or image sequences. |
| * @throws IllegalArgumentException if the frameIndex or numFrames is invalid, or the |
| * stream doesn't contain at least numFrames starting at frameIndex. |
| |
| * @return An list of Bitmaps containing the requested video frames. The returned |
| * array could contain less frames than requested if the retrieval fails. |
| * |
| * @see #getFrameAtIndex(int, BitmapParams) |
| * @see #getFrameAtIndex(int) |
| * @see #getFramesAtIndex(int, int) |
| */ |
| public @NonNull List<Bitmap> getFramesAtIndex( |
| int frameIndex, int numFrames, @NonNull BitmapParams params) { |
| return getFramesAtIndexInternal(frameIndex, numFrames, params); |
| } |
| |
| /** |
| * This method is similar to {@link #getFramesAtIndex(int, int, BitmapParams)} except that |
| * the default for {@link BitmapParams} will be used. |
| * |
| * @param frameIndex 0-based index of the first video frame to retrieve. The frame index |
| * must be that of a valid frame. The total number of frames available for retrieval |
| * can be queried via the {@link #METADATA_KEY_VIDEO_FRAME_COUNT} key. |
| * @param numFrames number of consecutive video frames to retrieve. Must be a positive |
| * value. The stream must contain at least numFrames frames starting at frameIndex. |
| * |
| * @throws IllegalStateException if the container doesn't contain video or image sequences. |
| * @throws IllegalArgumentException if the frameIndex or numFrames is invalid, or the |
| * stream doesn't contain at least numFrames starting at frameIndex. |
| |
| * @return An list of Bitmaps containing the requested video frames. The returned |
| * array could contain less frames than requested if the retrieval fails. |
| * |
| * @see #getFrameAtIndex(int, BitmapParams) |
| * @see #getFrameAtIndex(int) |
| * @see #getFramesAtIndex(int, int, BitmapParams) |
| */ |
| public @NonNull List<Bitmap> getFramesAtIndex(int frameIndex, int numFrames) { |
| return getFramesAtIndexInternal(frameIndex, numFrames, null); |
| } |
| |
| private @NonNull List<Bitmap> getFramesAtIndexInternal( |
| int frameIndex, int numFrames, @Nullable BitmapParams params) { |
| if (!"yes".equals(extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO))) { |
| throw new IllegalStateException("Does not contail video or image sequences"); |
| } |
| int frameCount = Integer.parseInt( |
| extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_FRAME_COUNT)); |
| if (frameIndex < 0 || numFrames < 1 |
| || frameIndex >= frameCount |
| || frameIndex > frameCount - numFrames) { |
| throw new IllegalArgumentException("Invalid frameIndex or numFrames: " |
| + frameIndex + ", " + numFrames); |
| } |
| return _getFrameAtIndex(frameIndex, numFrames, params); |
| } |
| |
| private native @NonNull List<Bitmap> _getFrameAtIndex( |
| int frameIndex, int numFrames, @Nullable BitmapParams params); |
| |
| /** |
| * This method retrieves a still image by its index. It should only be called |
| * after {@link #setDataSource}. |
| * |
| * After the bitmap is returned, you can query the actual parameters that were |
| * used to create the bitmap from the {@code BitmapParams} argument, for instance |
| * to query the bitmap config used for the bitmap with {@link BitmapParams#getActualConfig}. |
| * |
| * @param imageIndex 0-based index of the image. |
| * @param params BitmapParams that controls the returned bitmap config (such as pixel formats). |
| * |
| * @throws IllegalStateException if the container doesn't contain still images. |
| * @throws IllegalArgumentException if the requested image does not exist. |
| * |
| * @return the requested still image, or null if the image cannot be retrieved. |
| * |
| * @see #getImageAtIndex(int) |
| * @see #getPrimaryImage(BitmapParams) |
| * @see #getPrimaryImage() |
| */ |
| public @Nullable Bitmap getImageAtIndex(int imageIndex, @NonNull BitmapParams params) { |
| return getImageAtIndexInternal(imageIndex, params); |
| } |
| |
| /** |
| * @hide |
| * |
| * This method retrieves the thumbnail image for a still image if it's available. |
| * It should only be called after {@link #setDataSource}. |
| * |
| * @param imageIndex 0-based index of the image, negative value indicates primary image. |
| * @param params BitmapParams that controls the returned bitmap config (such as pixel formats). |
| * @param targetSize intended size of one edge (wdith or height) of the thumbnail, |
| * this is a heuristic for the framework to decide whether the embedded |
| * thumbnail should be used. |
| * @param maxPixels maximum pixels of thumbnail, this is a heuristic for the frameowrk to |
| * decide whehther the embedded thumnbail (or a downscaled version of it) |
| * should be used. |
| * @return the retrieved thumbnail, or null if no suitable thumbnail is available. |
| */ |
| public native @Nullable Bitmap getThumbnailImageAtIndex( |
| int imageIndex, @NonNull BitmapParams params, int targetSize, int maxPixels); |
| |
| /** |
| * This method is similar to {@link #getImageAtIndex(int, BitmapParams)} except that |
| * the default for {@link BitmapParams} will be used. |
| * |
| * @param imageIndex 0-based index of the image. |
| * |
| * @throws IllegalStateException if the container doesn't contain still images. |
| * @throws IllegalArgumentException if the requested image does not exist. |
| * |
| * @return the requested still image, or null if the image cannot be retrieved. |
| * |
| * @see #getImageAtIndex(int, BitmapParams) |
| * @see #getPrimaryImage(BitmapParams) |
| * @see #getPrimaryImage() |
| */ |
| public @Nullable Bitmap getImageAtIndex(int imageIndex) { |
| return getImageAtIndexInternal(imageIndex, null); |
| } |
| |
| /** |
| * This method retrieves the primary image of the media content. It should only |
| * be called after {@link #setDataSource}. |
| * |
| * After the bitmap is returned, you can query the actual parameters that were |
| * used to create the bitmap from the {@code BitmapParams} argument, for instance |
| * to query the bitmap config used for the bitmap with {@link BitmapParams#getActualConfig}. |
| * |
| * @param params BitmapParams that controls the returned bitmap config (such as pixel formats). |
| * |
| * @return the primary image, or null if it cannot be retrieved. |
| * |
| * @throws IllegalStateException if the container doesn't contain still images. |
| * |
| * @see #getImageAtIndex(int, BitmapParams) |
| * @see #getImageAtIndex(int) |
| * @see #getPrimaryImage() |
| */ |
| public @Nullable Bitmap getPrimaryImage(@NonNull BitmapParams params) { |
| return getImageAtIndexInternal(-1, params); |
| } |
| |
| /** |
| * This method is similar to {@link #getPrimaryImage(BitmapParams)} except that |
| * the default for {@link BitmapParams} will be used. |
| * |
| * @return the primary image, or null if it cannot be retrieved. |
| * |
| * @throws IllegalStateException if the container doesn't contain still images. |
| * |
| * @see #getImageAtIndex(int, BitmapParams) |
| * @see #getImageAtIndex(int) |
| * @see #getPrimaryImage(BitmapParams) |
| */ |
| public @Nullable Bitmap getPrimaryImage() { |
| return getImageAtIndexInternal(-1, null); |
| } |
| |
| private Bitmap getImageAtIndexInternal(int imageIndex, @Nullable BitmapParams params) { |
| if (!"yes".equals(extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE))) { |
| throw new IllegalStateException("Does not contail still images"); |
| } |
| |
| String imageCount = extractMetadata(MediaMetadataRetriever.METADATA_KEY_IMAGE_COUNT); |
| if (imageIndex >= Integer.parseInt(imageCount)) { |
| throw new IllegalArgumentException("Invalid image index: " + imageCount); |
| } |
| |
| return _getImageAtIndex(imageIndex, params); |
| } |
| |
| private native Bitmap _getImageAtIndex(int imageIndex, @Nullable BitmapParams params); |
| |
| /** |
| * Call this method after setDataSource(). This method finds the optional |
| * graphic or album/cover art associated associated with the data source. If |
| * there are more than one pictures, (any) one of them is returned. |
| * |
| * @return null if no such graphic is found. |
| */ |
| public @Nullable byte[] getEmbeddedPicture() { |
| return getEmbeddedPicture(EMBEDDED_PICTURE_TYPE_ANY); |
| } |
| |
| @UnsupportedAppUsage |
| private native byte[] getEmbeddedPicture(int pictureType); |
| |
| @Override |
| public void close() { |
| release(); |
| } |
| |
| /** |
| * Call it when one is done with the object. This method releases the memory |
| * allocated internally. |
| */ |
| public native void release(); |
| @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) |
| private native void native_setup(); |
| @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) |
| private static native void native_init(); |
| |
| @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) |
| private native final void native_finalize(); |
| |
| @Override |
| protected void finalize() throws Throwable { |
| try { |
| native_finalize(); |
| } finally { |
| super.finalize(); |
| } |
| } |
| |
| /** |
| * Option used in method {@link #getFrameAtTime(long, int)} to get a |
| * frame at a specified location. |
| * |
| * @see #getFrameAtTime(long, int) |
| */ |
| /* Do not change these option values without updating their counterparts |
| * in include/media/MediaSource.h! |
| */ |
| /** |
| * This option is used with {@link #getFrameAtTime(long, int)} to retrieve |
| * a sync (or key) frame associated with a data source that is located |
| * right before or at the given time. |
| * |
| * @see #getFrameAtTime(long, int) |
| */ |
| public static final int OPTION_PREVIOUS_SYNC = 0x00; |
| /** |
| * This option is used with {@link #getFrameAtTime(long, int)} to retrieve |
| * a sync (or key) frame associated with a data source that is located |
| * right after or at the given time. |
| * |
| * @see #getFrameAtTime(long, int) |
| */ |
| public static final int OPTION_NEXT_SYNC = 0x01; |
| /** |
| * This option is used with {@link #getFrameAtTime(long, int)} to retrieve |
| * a sync (or key) frame associated with a data source that is located |
| * closest to (in time) or at the given time. |
| * |
| * @see #getFrameAtTime(long, int) |
| */ |
| public static final int OPTION_CLOSEST_SYNC = 0x02; |
| /** |
| * This option is used with {@link #getFrameAtTime(long, int)} to retrieve |
| * a frame (not necessarily a key frame) associated with a data source that |
| * is located closest to or at the given time. |
| * |
| * @see #getFrameAtTime(long, int) |
| */ |
| public static final int OPTION_CLOSEST = 0x03; |
| |
| /** @hide */ |
| @IntDef(flag = true, prefix = { "OPTION_" }, value = { |
| OPTION_PREVIOUS_SYNC, |
| OPTION_NEXT_SYNC, |
| OPTION_CLOSEST_SYNC, |
| OPTION_CLOSEST, |
| }) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface Option {} |
| |
| /* |
| * Do not change these metadata key values without updating their |
| * counterparts in include/media/mediametadataretriever.h! |
| */ |
| /** |
| * The metadata key to retrieve the numeric string describing the |
| * order of the audio data source on its original recording. |
| */ |
| public static final int METADATA_KEY_CD_TRACK_NUMBER = 0; |
| /** |
| * The metadata key to retrieve the information about the album title |
| * of the data source. |
| */ |
| public static final int METADATA_KEY_ALBUM = 1; |
| /** |
| * The metadata key to retrieve the information about the artist of |
| * the data source. |
| */ |
| public static final int METADATA_KEY_ARTIST = 2; |
| /** |
| * The metadata key to retrieve the information about the author of |
| * the data source. |
| */ |
| public static final int METADATA_KEY_AUTHOR = 3; |
| /** |
| * The metadata key to retrieve the information about the composer of |
| * the data source. |
| */ |
| public static final int METADATA_KEY_COMPOSER = 4; |
| /** |
| * The metadata key to retrieve the date when the data source was created |
| * or modified. |
| */ |
| public static final int METADATA_KEY_DATE = 5; |
| /** |
| * The metadata key to retrieve the content type or genre of the data |
| * source. |
| */ |
| public static final int METADATA_KEY_GENRE = 6; |
| /** |
| * The metadata key to retrieve the data source title. |
| */ |
| public static final int METADATA_KEY_TITLE = 7; |
| /** |
| * The metadata key to retrieve the year when the data source was created |
| * or modified. |
| */ |
| public static final int METADATA_KEY_YEAR = 8; |
| /** |
| * The metadata key to retrieve the playback duration (in ms) of the data source. |
| */ |
| public static final int METADATA_KEY_DURATION = 9; |
| /** |
| * The metadata key to retrieve the number of tracks, such as audio, video, |
| * text, in the data source, such as a mp4 or 3gpp file. |
| */ |
| public static final int METADATA_KEY_NUM_TRACKS = 10; |
| /** |
| * The metadata key to retrieve the information of the writer (such as |
| * lyricist) of the data source. |
| */ |
| public static final int METADATA_KEY_WRITER = 11; |
| /** |
| * The metadata key to retrieve the mime type of the data source. Some |
| * example mime types include: "video/mp4", "audio/mp4", "audio/amr-wb", |
| * etc. |
| */ |
| public static final int METADATA_KEY_MIMETYPE = 12; |
| /** |
| * The metadata key to retrieve the information about the performers or |
| * artist associated with the data source. |
| */ |
| public static final int METADATA_KEY_ALBUMARTIST = 13; |
| /** |
| * The metadata key to retrieve the numberic string that describes which |
| * part of a set the audio data source comes from. |
| */ |
| public static final int METADATA_KEY_DISC_NUMBER = 14; |
| /** |
| * The metadata key to retrieve the music album compilation status. |
| */ |
| public static final int METADATA_KEY_COMPILATION = 15; |
| /** |
| * If this key exists the media contains audio content. |
| */ |
| public static final int METADATA_KEY_HAS_AUDIO = 16; |
| /** |
| * If this key exists the media contains video content. |
| */ |
| public static final int METADATA_KEY_HAS_VIDEO = 17; |
| /** |
| * If the media contains video, this key retrieves its width. |
| */ |
| public static final int METADATA_KEY_VIDEO_WIDTH = 18; |
| /** |
| * If the media contains video, this key retrieves its height. |
| */ |
| public static final int METADATA_KEY_VIDEO_HEIGHT = 19; |
| /** |
| * This key retrieves the average bitrate (in bits/sec), if available. |
| */ |
| public static final int METADATA_KEY_BITRATE = 20; |
| /** |
| * This key retrieves the language code of text tracks, if available. |
| * If multiple text tracks present, the return value will look like: |
| * "eng:chi" |
| * @hide |
| */ |
| public static final int METADATA_KEY_TIMED_TEXT_LANGUAGES = 21; |
| /** |
| * If this key exists the media is drm-protected. |
| * @hide |
| */ |
| public static final int METADATA_KEY_IS_DRM = 22; |
| /** |
| * This key retrieves the location information, if available. |
| * The location should be specified according to ISO-6709 standard, under |
| * a mp4/3gp box "@xyz". Location with longitude of -90 degrees and latitude |
| * of 180 degrees will be retrieved as "+180.0000-90.0000/", for instance. |
| */ |
| public static final int METADATA_KEY_LOCATION = 23; |
| /** |
| * This key retrieves the video rotation angle in degrees, if available. |
| * The video rotation angle may be 0, 90, 180, or 270 degrees. |
| */ |
| public static final int METADATA_KEY_VIDEO_ROTATION = 24; |
| /** |
| * This key retrieves the original capture framerate, if it's |
| * available. The capture framerate will be a floating point |
| * number. |
| */ |
| public static final int METADATA_KEY_CAPTURE_FRAMERATE = 25; |
| /** |
| * If this key exists the media contains still image content. |
| */ |
| public static final int METADATA_KEY_HAS_IMAGE = 26; |
| /** |
| * If the media contains still images, this key retrieves the number |
| * of still images. |
| */ |
| public static final int METADATA_KEY_IMAGE_COUNT = 27; |
| /** |
| * If the media contains still images, this key retrieves the image |
| * index of the primary image. |
| */ |
| public static final int METADATA_KEY_IMAGE_PRIMARY = 28; |
| /** |
| * If the media contains still images, this key retrieves the width |
| * of the primary image. |
| */ |
| public static final int METADATA_KEY_IMAGE_WIDTH = 29; |
| /** |
| * If the media contains still images, this key retrieves the height |
| * of the primary image. |
| */ |
| public static final int METADATA_KEY_IMAGE_HEIGHT = 30; |
| /** |
| * If the media contains still images, this key retrieves the rotation |
| * angle (in degrees clockwise) of the primary image. The image rotation |
| * angle must be one of 0, 90, 180, or 270 degrees. |
| */ |
| public static final int METADATA_KEY_IMAGE_ROTATION = 31; |
| /** |
| * If the media contains video and this key exists, it retrieves the |
| * total number of frames in the video sequence. |
| */ |
| public static final int METADATA_KEY_VIDEO_FRAME_COUNT = 32; |
| |
| /** |
| * If the media contains EXIF data, this key retrieves the offset value |
| * of the data. |
| */ |
| public static final int METADATA_KEY_EXIF_OFFSET = 33; |
| |
| /** |
| * If the media contains EXIF data, this key retrieves the length of the |
| * data. |
| */ |
| public static final int METADATA_KEY_EXIF_LENGTH = 34; |
| |
| /** |
| * This key retrieves the color standard, if available. |
| * |
| * @see MediaFormat#COLOR_STANDARD_BT709 |
| * @see MediaFormat#COLOR_STANDARD_BT601_PAL |
| * @see MediaFormat#COLOR_STANDARD_BT601_NTSC |
| * @see MediaFormat#COLOR_STANDARD_BT2020 |
| */ |
| public static final int METADATA_KEY_COLOR_STANDARD = 35; |
| |
| /** |
| * This key retrieves the color transfer, if available. |
| * |
| * @see MediaFormat#COLOR_TRANSFER_LINEAR |
| * @see MediaFormat#COLOR_TRANSFER_SDR_VIDEO |
| * @see MediaFormat#COLOR_TRANSFER_ST2084 |
| * @see MediaFormat#COLOR_TRANSFER_HLG |
| */ |
| public static final int METADATA_KEY_COLOR_TRANSFER = 36; |
| |
| /** |
| * This key retrieves the color range, if available. |
| * |
| * @see MediaFormat#COLOR_RANGE_LIMITED |
| * @see MediaFormat#COLOR_RANGE_FULL |
| */ |
| public static final int METADATA_KEY_COLOR_RANGE = 37; |
| // Add more here... |
| |
| /** |
| * This key retrieves the sample rate in Hz, if available. |
| * This is a signed 32-bit integer formatted as a string in base 10. |
| */ |
| public static final int METADATA_KEY_SAMPLERATE = 38; |
| |
| /** |
| * This key retrieves the bits per sample in numbers of bits, if available. |
| * This is a signed 32-bit integer formatted as a string in base 10. |
| */ |
| public static final int METADATA_KEY_BITS_PER_SAMPLE = 39; |
| } |