/*
* ServeStream: A HTTP stream browser/player for Android
* Copyright 2014 William Seemann
*
* 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 net.sourceforge.servestream.media;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Map;
/**
* FFmpegMediaMetadataRetriever class provides a unified interface for retrieving
* frame and meta data from an input media file.
*/
public class FFmpegMediaMetadataRetriever
{
private final static String TAG = "FFmpegMediaMetadataRetriever";
/**
* User defined bitmap configuration. A bitmap configuration describes how pixels are
* stored. This affects the quality (color depth) as well as the ability to display
* transparent/translucent colors.
*/
public static Bitmap.Config IN_PREFERRED_CONFIG;
static {
System.loadLibrary("ffmpeg_mediametadataretriever_jni");
native_init();
}
// The field below is accessed by native methods
private int mNativeContext;
public FFmpegMediaMetadataRetriever() {
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 of the input media file.
* @throws IllegalArgumentException If the path is invalid.
*/
public native void setDataSource(String path) throws IllegalArgumentException;
/**
* 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(uri, keys, values);
}
private native void _setDataSource(
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();
}
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();
}
if (fd == null) {
throw new IllegalArgumentException();
}
FileDescriptor descriptor = fd.getFileDescriptor();
if (!descriptor.valid()) {
throw new IllegalArgumentException();
}
// 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());
}
/**
* 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 native String extractMetadata(String key);
/**
* 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. This is
* useful for generating a thumbnail for an input data source or just
* obtain and display a frame at the given time position.
*
* @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.
*/
public Bitmap getFrameAtTime(long timeUs, int option) {
if (option < OPTION_PREVIOUS_SYNC ||
option > OPTION_CLOSEST) {
throw new IllegalArgumentException("Unsupported option: " + option);
}
Bitmap b = null;
BitmapFactory.Options bitmapOptionsCache = new BitmapFactory.Options();
bitmapOptionsCache.inPreferredConfig = getInPreferredConfig();
bitmapOptionsCache.inDither = false;
byte [] picture = _getFrameAtTime(timeUs, option);
if (picture != null) {
b = BitmapFactory.decodeByteArray(picture, 0, picture.length, bitmapOptionsCache);
}
return b;
}
/**
* 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. This is useful for generating a thumbnail
* for an input data source. 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)}.
*
* @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 containing a representative video frame, which
* can be null, if such a frame cannot be retrieved.
*
* @see #getFrameAtTime(long, int)
*/
public Bitmap getFrameAtTime(long timeUs) {
Bitmap b = null;
BitmapFactory.Options bitmapOptionsCache = new BitmapFactory.Options();
bitmapOptionsCache.inPreferredConfig = getInPreferredConfig();
bitmapOptionsCache.inDither = false;
byte [] picture = _getFrameAtTime(timeUs, OPTION_CLOSEST_SYNC);
if (picture != null) {
b = BitmapFactory.decodeByteArray(picture, 0, picture.length, bitmapOptionsCache);
}
return b;
}
/**
* Call this method after setDataSource(). This method finds a
* representative frame at any time position if possible,
* and returns it as a bitmap. This is useful for generating a thumbnail
* for an input data source. 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)}
*
* @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 Bitmap getFrameAtTime() {
return getFrameAtTime(-1, OPTION_CLOSEST_SYNC);
}
private native byte [] _getFrameAtTime(long timeUs, int option);
/**
* 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 native byte[] getEmbeddedPicture();
/**
* Call it when one is done with the object. This method releases the memory
* allocated internally.
*/
public native void release();
private native void native_setup();
private static native void native_init();
private native final void native_finalize();
@Override
protected void finalize() throws Throwable {
try {
native_finalize();
} finally {
super.finalize();
}
}
private Bitmap.Config getInPreferredConfig() {
if (IN_PREFERRED_CONFIG != null) {
return IN_PREFERRED_CONFIG;
}
return Bitmap.Config.RGB_565;
}
/**
* 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 jni/metadata/ffmpeg_mediametadataretriever.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;
/**
* The metadata key to retrieve the name of the set this work belongs to.
*/
public static final String METADATA_KEY_ALBUM = "album";
/**
* The metadata key to retrieve the main creator of the set/album, if different
* from artist. e.g. "Various Artists" for compilation albums.
*/
public static final String METADATA_KEY_ALBUM_ARTIST = "album_artist";
/**
* The metadata key to retrieve the main creator of the work.
*/
public static final String METADATA_KEY_ARTIST = "artist";
/**
* The metadata key to retrieve the any additional description of the file.
*/
public static final String METADATA_KEY_COMMENT = "comment";
/**
* The metadata key to retrieve the who composed the work, if different from artist.
*/
public static final String METADATA_KEY_COMPOSER = "composer";
/**
* The metadata key to retrieve the name of copyright holder.
*/
public static final String METADATA_KEY_COPYRIGHT = "copyright";
/**
* The metadata key to retrieve the date when the file was created, preferably in ISO 8601.
*/
public static final String METADATA_KEY_CREATION_TIME = "creation_time";
/**
* The metadata key to retrieve the date when the work was created, preferably in ISO 8601.
*/
public static final String METADATA_KEY_DATE = "date";
/**
* The metadata key to retrieve the number of a subset, e.g. disc in a multi-disc collection.
*/
public static final String METADATA_KEY_DISC = "disc";
/**
* The metadata key to retrieve the name/settings of the software/hardware that produced the file.
*/
public static final String METADATA_KEY_ENCODER = "encoder";
/**
* The metadata key to retrieve the person/group who created the file.
*/
public static final String METADATA_KEY_ENCODED_BY = "encoded_by";
/**
* The metadata key to retrieve the original name of the file.
*/
public static final String METADATA_KEY_FILENAME = "filename";
/**
* The metadata key to retrieve the genre of the work.
*/
public static final String METADATA_KEY_GENRE = "genre";
/**
* The metadata key to retrieve the main language in which the work is performed, preferably
* in ISO 639-2 format. Multiple languages can be specified by separating them with commas.
*/
public static final String METADATA_KEY_LANGUAGE = "language";
/**
* The metadata key to retrieve the artist who performed the work, if different from artist.
* E.g for "Also sprach Zarathustra", artist would be "Richard Strauss" and performer "London
* Philharmonic Orchestra".
*/
public static final String METADATA_KEY_PERFORMER = "performer";
/**
* The metadata key to retrieve the name of the label/publisher.
*/
public static final String METADATA_KEY_PUBLISHER = "publisher";
/**
* The metadata key to retrieve the name of the service in broadcasting (channel name).
*/
public static final String METADATA_KEY_SERVICE_NAME = "service_name";
/**
* The metadata key to retrieve the name of the service provider in broadcasting.
*/
public static final String METADATA_KEY_SERVICE_PROVIDER = "service_provider";
/**
* The metadata key to retrieve the name of the work.
*/
public static final String METADATA_KEY_TITLE = "title";
/**
* The metadata key to retrieve the number of this work in the set, can be in form current/total.
*/
public static final String METADATA_KEY_TRACK = "track";
/**
* The metadata key to retrieve the total bitrate of the bitrate variant that the current stream
* is part of.
*/
public static final String METADATA_KEY_VARIANT_BITRATE = "bitrate";
/**
* The metadata key to retrieve the duration of the work in milliseconds.
*/
public static final String METADATA_KEY_DURATION = "duration";
/**
* The metadata key to retrieve the audio codec of the work.
*/
public static final String METADATA_KEY_AUDIO_CODEC = "audio_codec";
/**
* The metadata key to retrieve the video codec of the work.
*/
public static final String METADATA_KEY_VIDEO_CODEC = "video_codec";
/**
* 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 String METADATA_KEY_VIDEO_ROTATION = "rotate";
/**
* The metadata key to retrieve the main creator of the work.
*/
public static final String METADATA_KEY_ICY_METADATA = "icy_metadata";
/**
* The metadata key to retrieve the main creator of the work.
*/
//private static final String METADATA_KEY_ICY_ARTIST = "icy_artist";
/**
* The metadata key to retrieve the name of the work.
*/
//private static final String METADATA_KEY_ICY_TITLE = "icy_title";
/**
* This metadata key retrieves the average framerate (in frames/sec), if available.
*/
public static final String METADATA_KEY_FRAMERATE = "framerate";
}