diff options
| author | 2017-10-05 14:27:23 -0700 | |
|---|---|---|
| committer | 2017-10-30 19:59:43 +0000 | |
| commit | 4342f08faba4ce730d080796b13e9f2b9b06c350 (patch) | |
| tree | e3c35eeb94b942295296d2dfac2383cab735b7d7 | |
| parent | 0e0621a46bb23de1d2dea0902a133d0a1c0ba977 (diff) | |
heif: support heif image sequence and dual-function files
- Add a few methods to MediaMetadataRetriever to get still
images and frames by index.
- Declare keys in MediaFormat related to heif still images.
bug: 63633199
test:
cts-tradefed run cts-dev --module CtsMediaTestCases --compatibility:module-arg CtsMediaTestCases:include-annotation:android.platform.test.annotations.RequiresDevice
Change-Id: I9556b27f7395a163aadd01e979930061878994d0
| -rw-r--r-- | api/current.txt | 17 | ||||
| -rw-r--r-- | api/system-current.txt | 17 | ||||
| -rw-r--r-- | api/test-current.txt | 17 | ||||
| -rw-r--r-- | media/java/android/media/MediaFormat.java | 90 | ||||
| -rw-r--r-- | media/java/android/media/MediaMetadataRetriever.java | 162 | ||||
| -rw-r--r-- | media/jni/android_media_MediaMetadataRetriever.cpp | 117 |
6 files changed, 381 insertions, 39 deletions
diff --git a/api/current.txt b/api/current.txt index 55f8751912e2..960a2512fc15 100644 --- a/api/current.txt +++ b/api/current.txt @@ -22779,6 +22779,10 @@ package android.media { field public static final java.lang.String KEY_DURATION = "durationUs"; field public static final java.lang.String KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level"; field public static final java.lang.String KEY_FRAME_RATE = "frame-rate"; + field public static final java.lang.String KEY_GRID_COLS = "grid-cols"; + field public static final java.lang.String KEY_GRID_HEIGHT = "grid-height"; + field public static final java.lang.String KEY_GRID_ROWS = "grid-rows"; + field public static final java.lang.String KEY_GRID_WIDTH = "grid-width"; field public static final java.lang.String KEY_HDR_STATIC_INFO = "hdr-static-info"; field public static final java.lang.String KEY_HEIGHT = "height"; field public static final java.lang.String KEY_INTRA_REFRESH_PERIOD = "intra-refresh-period"; @@ -22822,6 +22826,7 @@ package android.media { field public static final java.lang.String MIMETYPE_AUDIO_RAW = "audio/raw"; field public static final java.lang.String MIMETYPE_AUDIO_SCRAMBLED = "audio/scrambled"; field public static final java.lang.String MIMETYPE_AUDIO_VORBIS = "audio/vorbis"; + field public static final java.lang.String MIMETYPE_IMAGE_ANDROID_HEIC = "image/vnd.android.heic"; field public static final java.lang.String MIMETYPE_TEXT_CEA_608 = "text/cea-608"; field public static final java.lang.String MIMETYPE_TEXT_VTT = "text/vtt"; field public static final java.lang.String MIMETYPE_VIDEO_AVC = "video/avc"; @@ -22914,9 +22919,13 @@ package android.media { ctor public MediaMetadataRetriever(); method public java.lang.String extractMetadata(int); method public byte[] getEmbeddedPicture(); + method public android.graphics.Bitmap getFrameAtIndex(int); method public android.graphics.Bitmap getFrameAtTime(long, int); method public android.graphics.Bitmap getFrameAtTime(long); method public android.graphics.Bitmap getFrameAtTime(); + method public android.graphics.Bitmap[] getFramesAtIndex(int, int); + method public android.graphics.Bitmap getImageAtIndex(int); + method public android.graphics.Bitmap getPrimaryImage(); method public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int); method public void release(); method public void setDataSource(java.lang.String) throws java.lang.IllegalArgumentException; @@ -22939,11 +22948,18 @@ package android.media { field public static final int METADATA_KEY_DURATION = 9; // 0x9 field public static final int METADATA_KEY_GENRE = 6; // 0x6 field public static final int METADATA_KEY_HAS_AUDIO = 16; // 0x10 + field public static final int METADATA_KEY_HAS_IMAGE = 26; // 0x1a field public static final int METADATA_KEY_HAS_VIDEO = 17; // 0x11 + field public static final int METADATA_KEY_IMAGE_COUNT = 27; // 0x1b + field public static final int METADATA_KEY_IMAGE_HEIGHT = 30; // 0x1e + field public static final int METADATA_KEY_IMAGE_PRIMARY = 28; // 0x1c + field public static final int METADATA_KEY_IMAGE_ROTATION = 31; // 0x1f + field public static final int METADATA_KEY_IMAGE_WIDTH = 29; // 0x1d field public static final int METADATA_KEY_LOCATION = 23; // 0x17 field public static final int METADATA_KEY_MIMETYPE = 12; // 0xc field public static final int METADATA_KEY_NUM_TRACKS = 10; // 0xa field public static final int METADATA_KEY_TITLE = 7; // 0x7 + field public static final int METADATA_KEY_VIDEO_FRAME_COUNT = 32; // 0x20 field public static final int METADATA_KEY_VIDEO_HEIGHT = 19; // 0x13 field public static final int METADATA_KEY_VIDEO_ROTATION = 24; // 0x18 field public static final int METADATA_KEY_VIDEO_WIDTH = 18; // 0x12 @@ -52074,7 +52090,6 @@ package android.widget { method public android.graphics.Typeface getTypeface(); method public android.text.style.URLSpan[] getUrls(); method public boolean hasSelection(); - method public void invalidate(int, int, int, int); method public boolean isAllCaps(); method public boolean isCursorVisible(); method public boolean isElegantTextHeight(); diff --git a/api/system-current.txt b/api/system-current.txt index c6a6fddf7d1b..0090cb03b0c9 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -24669,6 +24669,10 @@ package android.media { field public static final java.lang.String KEY_DURATION = "durationUs"; field public static final java.lang.String KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level"; field public static final java.lang.String KEY_FRAME_RATE = "frame-rate"; + field public static final java.lang.String KEY_GRID_COLS = "grid-cols"; + field public static final java.lang.String KEY_GRID_HEIGHT = "grid-height"; + field public static final java.lang.String KEY_GRID_ROWS = "grid-rows"; + field public static final java.lang.String KEY_GRID_WIDTH = "grid-width"; field public static final java.lang.String KEY_HDR_STATIC_INFO = "hdr-static-info"; field public static final java.lang.String KEY_HEIGHT = "height"; field public static final java.lang.String KEY_INTRA_REFRESH_PERIOD = "intra-refresh-period"; @@ -24712,6 +24716,7 @@ package android.media { field public static final java.lang.String MIMETYPE_AUDIO_RAW = "audio/raw"; field public static final java.lang.String MIMETYPE_AUDIO_SCRAMBLED = "audio/scrambled"; field public static final java.lang.String MIMETYPE_AUDIO_VORBIS = "audio/vorbis"; + field public static final java.lang.String MIMETYPE_IMAGE_ANDROID_HEIC = "image/vnd.android.heic"; field public static final java.lang.String MIMETYPE_TEXT_CEA_608 = "text/cea-608"; field public static final java.lang.String MIMETYPE_TEXT_VTT = "text/vtt"; field public static final java.lang.String MIMETYPE_VIDEO_AVC = "video/avc"; @@ -24804,9 +24809,13 @@ package android.media { ctor public MediaMetadataRetriever(); method public java.lang.String extractMetadata(int); method public byte[] getEmbeddedPicture(); + method public android.graphics.Bitmap getFrameAtIndex(int); method public android.graphics.Bitmap getFrameAtTime(long, int); method public android.graphics.Bitmap getFrameAtTime(long); method public android.graphics.Bitmap getFrameAtTime(); + method public android.graphics.Bitmap[] getFramesAtIndex(int, int); + method public android.graphics.Bitmap getImageAtIndex(int); + method public android.graphics.Bitmap getPrimaryImage(); method public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int); method public void release(); method public void setDataSource(java.lang.String) throws java.lang.IllegalArgumentException; @@ -24829,11 +24838,18 @@ package android.media { field public static final int METADATA_KEY_DURATION = 9; // 0x9 field public static final int METADATA_KEY_GENRE = 6; // 0x6 field public static final int METADATA_KEY_HAS_AUDIO = 16; // 0x10 + field public static final int METADATA_KEY_HAS_IMAGE = 26; // 0x1a field public static final int METADATA_KEY_HAS_VIDEO = 17; // 0x11 + field public static final int METADATA_KEY_IMAGE_COUNT = 27; // 0x1b + field public static final int METADATA_KEY_IMAGE_HEIGHT = 30; // 0x1e + field public static final int METADATA_KEY_IMAGE_PRIMARY = 28; // 0x1c + field public static final int METADATA_KEY_IMAGE_ROTATION = 31; // 0x1f + field public static final int METADATA_KEY_IMAGE_WIDTH = 29; // 0x1d field public static final int METADATA_KEY_LOCATION = 23; // 0x17 field public static final int METADATA_KEY_MIMETYPE = 12; // 0xc field public static final int METADATA_KEY_NUM_TRACKS = 10; // 0xa field public static final int METADATA_KEY_TITLE = 7; // 0x7 + field public static final int METADATA_KEY_VIDEO_FRAME_COUNT = 32; // 0x20 field public static final int METADATA_KEY_VIDEO_HEIGHT = 19; // 0x13 field public static final int METADATA_KEY_VIDEO_ROTATION = 24; // 0x18 field public static final int METADATA_KEY_VIDEO_WIDTH = 18; // 0x12 @@ -56178,7 +56194,6 @@ package android.widget { method public android.graphics.Typeface getTypeface(); method public android.text.style.URLSpan[] getUrls(); method public boolean hasSelection(); - method public void invalidate(int, int, int, int); method public boolean isAllCaps(); method public boolean isCursorVisible(); method public boolean isElegantTextHeight(); diff --git a/api/test-current.txt b/api/test-current.txt index 5e55bc3baa55..686c4228a180 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -22979,6 +22979,10 @@ package android.media { field public static final java.lang.String KEY_DURATION = "durationUs"; field public static final java.lang.String KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level"; field public static final java.lang.String KEY_FRAME_RATE = "frame-rate"; + field public static final java.lang.String KEY_GRID_COLS = "grid-cols"; + field public static final java.lang.String KEY_GRID_HEIGHT = "grid-height"; + field public static final java.lang.String KEY_GRID_ROWS = "grid-rows"; + field public static final java.lang.String KEY_GRID_WIDTH = "grid-width"; field public static final java.lang.String KEY_HDR_STATIC_INFO = "hdr-static-info"; field public static final java.lang.String KEY_HEIGHT = "height"; field public static final java.lang.String KEY_INTRA_REFRESH_PERIOD = "intra-refresh-period"; @@ -23022,6 +23026,7 @@ package android.media { field public static final java.lang.String MIMETYPE_AUDIO_RAW = "audio/raw"; field public static final java.lang.String MIMETYPE_AUDIO_SCRAMBLED = "audio/scrambled"; field public static final java.lang.String MIMETYPE_AUDIO_VORBIS = "audio/vorbis"; + field public static final java.lang.String MIMETYPE_IMAGE_ANDROID_HEIC = "image/vnd.android.heic"; field public static final java.lang.String MIMETYPE_TEXT_CEA_608 = "text/cea-608"; field public static final java.lang.String MIMETYPE_TEXT_VTT = "text/vtt"; field public static final java.lang.String MIMETYPE_VIDEO_AVC = "video/avc"; @@ -23114,9 +23119,13 @@ package android.media { ctor public MediaMetadataRetriever(); method public java.lang.String extractMetadata(int); method public byte[] getEmbeddedPicture(); + method public android.graphics.Bitmap getFrameAtIndex(int); method public android.graphics.Bitmap getFrameAtTime(long, int); method public android.graphics.Bitmap getFrameAtTime(long); method public android.graphics.Bitmap getFrameAtTime(); + method public android.graphics.Bitmap[] getFramesAtIndex(int, int); + method public android.graphics.Bitmap getImageAtIndex(int); + method public android.graphics.Bitmap getPrimaryImage(); method public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int); method public void release(); method public void setDataSource(java.lang.String) throws java.lang.IllegalArgumentException; @@ -23139,11 +23148,18 @@ package android.media { field public static final int METADATA_KEY_DURATION = 9; // 0x9 field public static final int METADATA_KEY_GENRE = 6; // 0x6 field public static final int METADATA_KEY_HAS_AUDIO = 16; // 0x10 + field public static final int METADATA_KEY_HAS_IMAGE = 26; // 0x1a field public static final int METADATA_KEY_HAS_VIDEO = 17; // 0x11 + field public static final int METADATA_KEY_IMAGE_COUNT = 27; // 0x1b + field public static final int METADATA_KEY_IMAGE_HEIGHT = 30; // 0x1e + field public static final int METADATA_KEY_IMAGE_PRIMARY = 28; // 0x1c + field public static final int METADATA_KEY_IMAGE_ROTATION = 31; // 0x1f + field public static final int METADATA_KEY_IMAGE_WIDTH = 29; // 0x1d field public static final int METADATA_KEY_LOCATION = 23; // 0x17 field public static final int METADATA_KEY_MIMETYPE = 12; // 0xc field public static final int METADATA_KEY_NUM_TRACKS = 10; // 0xa field public static final int METADATA_KEY_TITLE = 7; // 0x7 + field public static final int METADATA_KEY_VIDEO_FRAME_COUNT = 32; // 0x20 field public static final int METADATA_KEY_VIDEO_HEIGHT = 19; // 0x13 field public static final int METADATA_KEY_VIDEO_ROTATION = 24; // 0x18 field public static final int METADATA_KEY_VIDEO_WIDTH = 18; // 0x12 @@ -52683,7 +52699,6 @@ package android.widget { method public android.graphics.Typeface getTypeface(); method public android.text.style.URLSpan[] getUrls(); method public boolean hasSelection(); - method public void invalidate(int, int, int, int); method public boolean isAllCaps(); method public boolean isCursorVisible(); method public boolean isElegantTextHeight(); diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java index ed5f7d848663..c475e122833a 100644 --- a/media/java/android/media/MediaFormat.java +++ b/media/java/android/media/MediaFormat.java @@ -96,6 +96,19 @@ import java.util.Map; * <tr><td>{@link #KEY_MIME}</td><td>String</td><td>The type of the format.</td></tr> * <tr><td>{@link #KEY_LANGUAGE}</td><td>String</td><td>The language of the content.</td></tr> * </table> + * + * Image formats have the following keys: + * <table> + * <tr><td>{@link #KEY_MIME}</td><td>String</td><td>The type of the format.</td></tr> + * <tr><td>{@link #KEY_WIDTH}</td><td>Integer</td><td></td></tr> + * <tr><td>{@link #KEY_HEIGHT}</td><td>Integer</td><td></td></tr> + * <tr><td>{@link #KEY_COLOR_FORMAT}</td><td>Integer</td><td>set by the user + * for encoders, readable in the output format of decoders</b></td></tr> + * <tr><td>{@link #KEY_GRID_WIDTH}</td><td>Integer</td><td>required if the image has grid</td></tr> + * <tr><td>{@link #KEY_GRID_HEIGHT}</td><td>Integer</td><td>required if the image has grid</td></tr> + * <tr><td>{@link #KEY_GRID_ROWS}</td><td>Integer</td><td>required if the image has grid</td></tr> + * <tr><td>{@link #KEY_GRID_COLS}</td><td>Integer</td><td>required if the image has grid</td></tr> + * </table> */ public final class MediaFormat { public static final String MIMETYPE_VIDEO_VP8 = "video/x-vnd.on2.vp8"; @@ -126,6 +139,35 @@ public final class MediaFormat { public static final String MIMETYPE_AUDIO_SCRAMBLED = "audio/scrambled"; /** + * MIME type for HEIF still image data encoded in HEVC. + * + * To decode such an image, {@link MediaCodec} decoder for + * {@ #MIMETYPE_VIDEO_HEVC} shall be used. The client needs to form + * the correct {@link #MediaFormat} based on additional information in + * the track format, and send it to {@link MediaCodec#configure}. + * + * The track's MediaFormat will come with {@link #KEY_WIDTH} and + * {@link #KEY_HEIGHT} keys, which describes the width and height + * of the image. If the image doesn't contain grid (i.e. none of + * {@link #KEY_GRID_WIDTH}, {@link #KEY_GRID_HEIGHT}, + * {@link #KEY_GRID_ROWS}, {@link #KEY_GRID_COLS} are present}), the + * track will contain a single sample of coded data for the entire image, + * and the image width and height should be used to set up the decoder. + * + * If the image does come with grid, each sample from the track will + * contain one tile in the grid, of which the size is described by + * {@link #KEY_GRID_WIDTH} and {@link #KEY_GRID_HEIGHT}. This size + * (instead of {@link #KEY_WIDTH} and {@link #KEY_HEIGHT}) should be + * used to set up the decoder. The track contains {@link #KEY_GRID_ROWS} + * by {@link #KEY_GRID_COLS} samples in row-major, top-row first, + * left-to-right order. The output image should be reconstructed by + * first tiling the decoding results of the tiles in the correct order, + * then trimming (before rotation is applied) on the bottom and right + * side, if the tiled area is larger than the image width and height. + */ + public static final String MIMETYPE_IMAGE_ANDROID_HEIC = "image/vnd.android.heic"; + + /** * MIME type for WebVTT subtitle data. */ public static final String MIMETYPE_TEXT_VTT = "text/vtt"; @@ -232,6 +274,54 @@ public final class MediaFormat { public static final String KEY_FRAME_RATE = "frame-rate"; /** + * A key describing the grid width of the content in a {@link #MIMETYPE_IMAGE_ANDROID_HEIC} + * track. The associated value is an integer. + * + * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks. + * + * @see #KEY_GRID_HEIGHT + * @see #KEY_GRID_ROWS + * @see #KEY_GRID_COLS + */ + public static final String KEY_GRID_WIDTH = "grid-width"; + + /** + * A key describing the grid height of the content in a {@link #MIMETYPE_IMAGE_ANDROID_HEIC} + * track. The associated value is an integer. + * + * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks. + * + * @see #KEY_GRID_WIDTH + * @see #KEY_GRID_ROWS + * @see #KEY_GRID_COLS + */ + public static final String KEY_GRID_HEIGHT = "grid-height"; + + /** + * A key describing the number of grid rows in the content in a + * {@link #MIMETYPE_IMAGE_ANDROID_HEIC} track. The associated value is an integer. + * + * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks. + * + * @see #KEY_GRID_WIDTH + * @see #KEY_GRID_HEIGHT + * @see #KEY_GRID_COLS + */ + public static final String KEY_GRID_ROWS = "grid-rows"; + + /** + * A key describing the number of grid columns in the content in a + * {@link #MIMETYPE_IMAGE_ANDROID_HEIC} track. The associated value is an integer. + * + * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks. + * + * @see #KEY_GRID_WIDTH + * @see #KEY_GRID_HEIGHT + * @see #KEY_GRID_ROWS + */ + public static final String KEY_GRID_COLS = "grid-cols"; + + /** * A key describing the raw audio sample encoding/format. * * <p>The associated value is an integer, using one of the diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java index 760cc49bc1e2..0b8640184ff5 100644 --- a/media/java/android/media/MediaMetadataRetriever.java +++ b/media/java/android/media/MediaMetadataRetriever.java @@ -47,7 +47,7 @@ public class MediaMetadataRetriever // The field below is accessed by native methods @SuppressWarnings("unused") private long mNativeContext; - + private static final int EMBEDDED_PICTURE_TYPE_ANY = 0xFFFF; public MediaMetadataRetriever() { @@ -58,7 +58,7 @@ public class MediaMetadataRetriever * 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 of the input media file. * @throws IllegalArgumentException If the path is invalid. */ @@ -113,7 +113,7 @@ public class MediaMetadataRetriever * 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 @@ -123,13 +123,13 @@ public class MediaMetadataRetriever */ 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 */ @@ -138,11 +138,11 @@ public class MediaMetadataRetriever // intentionally less than LONG_MAX setDataSource(fd, 0, 0x7ffffffffffffffL); } - + /** - * Sets the data source as a content Uri. Call this method before + * 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 @@ -154,7 +154,7 @@ public class MediaMetadataRetriever if (uri == null) { throw new IllegalArgumentException(); } - + String scheme = uri.getScheme(); if(scheme == null || scheme.equals("file")) { setDataSource(uri.getPath()); @@ -213,12 +213,12 @@ public class MediaMetadataRetriever /** * 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; + * @return The meta data value associate with the given keyCode on success; * null on failure. */ public native String extractMetadata(int keyCode); @@ -357,6 +357,109 @@ public class MediaMetadataRetriever private native Bitmap _getFrameAtTime(long timeUs, int option, int width, int height); /** + * This method retrieves a video frame by its index. It should only be called + * after {@link #setDataSource}. + * + * @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 #getFramesAtIndex(int, int) + */ + public Bitmap getFrameAtIndex(int frameIndex) { + Bitmap[] bitmaps = getFramesAtIndex(frameIndex, 1); + if (bitmaps == null || bitmaps.length < 1) { + return null; + } + return bitmaps[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)} for efficiency. + * + * @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 array of Bitmaps containing the requested video frames. The returned + * array could contain less frames than requested if the retrieval fails. + * + * @see #getFrameAtIndex(int) + */ + public Bitmap[] getFramesAtIndex(int frameIndex, int numFrames) { + 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); + } + private native Bitmap[] _getFrameAtIndex(int frameIndex, int numFrames); + + /** + * This method retrieves a still image by its index. It should only be called + * after {@link #setDataSource}. + * + * @param imageIndex 0-based index of the image, with negative value indicating + * the primary 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 #getPrimaryImage + */ + public Bitmap getImageAtIndex(int imageIndex) { + 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); + } + + /** + * This method retrieves the primary image of the media content. It should only + * be called after {@link #setDataSource}. + * + * @return the primary image, or null if it cannot be retrieved. + * + * @throws IllegalStateException if the container doesn't contain still images. + * + * @see #getImageAtIndex(int) + */ + public Bitmap getPrimaryImage() { + return getImageAtIndex(-1); + } + + private native Bitmap _getImageAtIndex(int imageIndex); + + /** * 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. @@ -572,5 +675,40 @@ public class MediaMetadataRetriever * 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 + * of the primary image. + */ + 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; + // Add more here... } diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp index 4659ae131f53..42ebf6ab8cfc 100644 --- a/media/jni/android_media_MediaMetadataRetriever.cpp +++ b/media/jni/android_media_MediaMetadataRetriever.cpp @@ -244,29 +244,9 @@ static void rotate(T *dst, const T *src, size_t width, size_t height, int angle) } } -static jobject android_media_MediaMetadataRetriever_getFrameAtTime( - JNIEnv *env, jobject thiz, jlong timeUs, jint option, jint dst_width, jint dst_height) -{ - ALOGV("getFrameAtTime: %lld us option: %d dst width: %d heigh: %d", - (long long)timeUs, option, dst_width, dst_height); - MediaMetadataRetriever* retriever = getRetriever(env, thiz); - if (retriever == 0) { - jniThrowException(env, "java/lang/IllegalStateException", "No retriever available"); - return NULL; - } - - // Call native method to retrieve a video frame - VideoFrame *videoFrame = NULL; - sp<IMemory> frameMemory = retriever->getFrameAtTime(timeUs, option); - if (frameMemory != 0) { // cast the shared structure to a VideoFrame object - videoFrame = static_cast<VideoFrame *>(frameMemory->pointer()); - } - if (videoFrame == NULL) { - ALOGE("getFrameAtTime: videoFrame is a NULL pointer"); - return NULL; - } - - ALOGV("Dimension = %dx%d and bytes = %d", +static jobject getBitmapFromVideoFrame( + JNIEnv *env, VideoFrame *videoFrame, jint dst_width, jint dst_height) { + ALOGV("getBitmapFromVideoFrame: dimension = %dx%d and bytes = %d", videoFrame->mDisplayWidth, videoFrame->mDisplayHeight, videoFrame->mSize); @@ -301,7 +281,7 @@ static jobject android_media_MediaMetadataRetriever_getFrameAtTime( if (env->ExceptionCheck()) { env->ExceptionClear(); } - ALOGE("getFrameAtTime: create Bitmap failed!"); + ALOGE("getBitmapFromVideoFrame: create Bitmap failed!"); return NULL; } @@ -340,6 +320,93 @@ static jobject android_media_MediaMetadataRetriever_getFrameAtTime( return jBitmap; } +static jobject android_media_MediaMetadataRetriever_getFrameAtTime( + JNIEnv *env, jobject thiz, jlong timeUs, jint option, jint dst_width, jint dst_height) +{ + ALOGV("getFrameAtTime: %lld us option: %d dst width: %d heigh: %d", + (long long)timeUs, option, dst_width, dst_height); + MediaMetadataRetriever* retriever = getRetriever(env, thiz); + if (retriever == 0) { + jniThrowException(env, "java/lang/IllegalStateException", "No retriever available"); + return NULL; + } + + // Call native method to retrieve a video frame + VideoFrame *videoFrame = NULL; + sp<IMemory> frameMemory = retriever->getFrameAtTime(timeUs, option); + if (frameMemory != 0) { // cast the shared structure to a VideoFrame object + videoFrame = static_cast<VideoFrame *>(frameMemory->pointer()); + } + if (videoFrame == NULL) { + ALOGE("getFrameAtTime: videoFrame is a NULL pointer"); + return NULL; + } + + return getBitmapFromVideoFrame(env, videoFrame, dst_width, dst_height); +} + +static jobject android_media_MediaMetadataRetriever_getImageAtIndex( + JNIEnv *env, jobject thiz, jint index) +{ + ALOGV("getImageAtIndex: index %d", index); + MediaMetadataRetriever* retriever = getRetriever(env, thiz); + if (retriever == 0) { + jniThrowException(env, "java/lang/IllegalStateException", "No retriever available"); + return NULL; + } + + // Call native method to retrieve an image + VideoFrame *videoFrame = NULL; + sp<IMemory> frameMemory = retriever->getImageAtIndex(index); + if (frameMemory != 0) { // cast the shared structure to a VideoFrame object + videoFrame = static_cast<VideoFrame *>(frameMemory->pointer()); + } + if (videoFrame == NULL) { + ALOGE("getImageAtIndex: videoFrame is a NULL pointer"); + return NULL; + } + + return getBitmapFromVideoFrame(env, videoFrame, -1, -1); +} + +static jobjectArray android_media_MediaMetadataRetriever_getFrameAtIndex( + JNIEnv *env, jobject thiz, jint frameIndex, jint numFrames) +{ + ALOGV("getFrameAtIndex: frameIndex %d, numFrames %d", frameIndex, numFrames); + MediaMetadataRetriever* retriever = getRetriever(env, thiz); + if (retriever == 0) { + jniThrowException(env, + "java/lang/IllegalStateException", "No retriever available"); + return NULL; + } + + std::vector<sp<IMemory> > frames; + status_t err = retriever->getFrameAtIndex(&frames, frameIndex, numFrames); + if (err != OK || frames.size() == 0) { + ALOGE("failed to get frames from retriever, err=%d, size=%zu", + err, frames.size()); + return NULL; + } + + jobjectArray bitmapArrayObj = env->NewObjectArray( + frames.size(), fields.bitmapClazz, NULL); + if (bitmapArrayObj == NULL) { + ALOGE("can't create bitmap array object"); + return NULL; + } + + for (size_t i = 0; i < frames.size(); i++) { + if (frames[i] == NULL || frames[i]->pointer() == NULL) { + ALOGE("video frame at index %zu is a NULL pointer", frameIndex + i); + continue; + } + VideoFrame *videoFrame = static_cast<VideoFrame *>(frames[i]->pointer()); + jobject bitmapObj = getBitmapFromVideoFrame(env, videoFrame, -1, -1); + env->SetObjectArrayElement(bitmapArrayObj, i, bitmapObj); + } + return bitmapArrayObj; +} + static jbyteArray android_media_MediaMetadataRetriever_getEmbeddedPicture( JNIEnv *env, jobject thiz, jint pictureType) { @@ -485,6 +552,8 @@ static const JNINativeMethod nativeMethods[] = { {"setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaMetadataRetriever_setDataSourceFD}, {"_setDataSource", "(Landroid/media/MediaDataSource;)V", (void *)android_media_MediaMetadataRetriever_setDataSourceCallback}, {"_getFrameAtTime", "(JIII)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtTime}, + {"_getImageAtIndex", "(I)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getImageAtIndex}, + {"_getFrameAtIndex", "(II)[Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtIndex}, {"extractMetadata", "(I)Ljava/lang/String;", (void *)android_media_MediaMetadataRetriever_extractMetadata}, {"getEmbeddedPicture", "(I)[B", (void *)android_media_MediaMetadataRetriever_getEmbeddedPicture}, {"release", "()V", (void *)android_media_MediaMetadataRetriever_release}, |