Added support for reading MP4 chapters
This commit is contained in:
parent
4b1b271ca9
commit
1ebb209bc6
16
assets/LICENSE_FFMPEGMEDIAMETADATARETRIEVER.txt
Normal file
16
assets/LICENSE_FFMPEGMEDIAMETADATARETRIEVER.txt
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
FFmpegMediaMetadataRetriever: A unified interface for retrieving frame
|
||||||
|
and meta data from an input media file.
|
||||||
|
|
||||||
|
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.
|
@ -79,4 +79,12 @@ licensed under the Apache 2.0 license <a href="LICENSE_OKHTTP.txt">(View)</a>
|
|||||||
|
|
||||||
<h2>Okio <a href="https://github.com/square/okio">(Link)</a></h2>
|
<h2>Okio <a href="https://github.com/square/okio">(Link)</a></h2>
|
||||||
licensed under the Apache 2.0 license <a href="LICENSE_OKIO.txt">(View)</a>
|
licensed under the Apache 2.0 license <a href="LICENSE_OKIO.txt">(View)</a>
|
||||||
|
|
||||||
|
<h2>FFmpegMediaMetadataRetriever <a href="https://github.com/wseemann/FFmpegMediaMetadataRetriever">(Link)</a></h2>
|
||||||
|
licensed under the Apache 2.0 license <a href="LICENSE_FFMPEGMEDIAMETADATARETRIEVER.txt">(View)</a>
|
||||||
|
|
||||||
|
<p>This software uses <a href="https://ffmpeg.org">FFmpeg</a> licensed under the <a href="http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html">
|
||||||
|
LGPLv2.1</a> and its source can be downloaded <a href="https://github.com/wseemann/FFmpegMediaMetadataRetriever/blob/master/fmmr-library/ffmpeg-2.1-android-2013-11-13.tar.gz">here</a>.</p>
|
||||||
|
</a>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
@ -85,6 +85,7 @@ android {
|
|||||||
renderscript.srcDirs = ['src']
|
renderscript.srcDirs = ['src']
|
||||||
res.srcDirs = ['res']
|
res.srcDirs = ['res']
|
||||||
assets.srcDirs = ['assets']
|
assets.srcDirs = ['assets']
|
||||||
|
jniLibs.srcDirs = ['jniLibs']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BIN
jniLibs/armeabi/libavcodec.so
Executable file
BIN
jniLibs/armeabi/libavcodec.so
Executable file
Binary file not shown.
BIN
jniLibs/armeabi/libavformat.so
Executable file
BIN
jniLibs/armeabi/libavformat.so
Executable file
Binary file not shown.
BIN
jniLibs/armeabi/libavutil.so
Executable file
BIN
jniLibs/armeabi/libavutil.so
Executable file
Binary file not shown.
BIN
jniLibs/armeabi/libcrypto.so
Normal file
BIN
jniLibs/armeabi/libcrypto.so
Normal file
Binary file not shown.
BIN
jniLibs/armeabi/libffmpeg_mediametadataretriever_jni.so
Executable file
BIN
jniLibs/armeabi/libffmpeg_mediametadataretriever_jni.so
Executable file
Binary file not shown.
BIN
jniLibs/armeabi/libssl.so
Normal file
BIN
jniLibs/armeabi/libssl.so
Normal file
Binary file not shown.
BIN
jniLibs/armeabi/libswscale.so
Executable file
BIN
jniLibs/armeabi/libswscale.so
Executable file
Binary file not shown.
27
src/de/danoeh/antennapod/feed/MP4Chapter.java
Normal file
27
src/de/danoeh/antennapod/feed/MP4Chapter.java
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package de.danoeh.antennapod.feed;
|
||||||
|
|
||||||
|
import wseemann.media.FFmpegChapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a chapter contained in a MP4 file.
|
||||||
|
*/
|
||||||
|
public class MP4Chapter extends Chapter {
|
||||||
|
public static final int CHAPTERTYPE_MP4CHAPTER = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a MP4Chapter from an FFmpegChapter.
|
||||||
|
*/
|
||||||
|
public MP4Chapter(FFmpegChapter ch) {
|
||||||
|
this.start = ch.getStart();
|
||||||
|
this.title = ch.getTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MP4Chapter(long start, String title, FeedItem item, String link) {
|
||||||
|
super(start, title, item, link);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getChapterType() {
|
||||||
|
return CHAPTERTYPE_MP4CHAPTER;
|
||||||
|
}
|
||||||
|
}
|
@ -262,6 +262,9 @@ public final class DBReader {
|
|||||||
chapter = new VorbisCommentChapter(start,
|
chapter = new VorbisCommentChapter(start,
|
||||||
title, item, link);
|
title, item, link);
|
||||||
break;
|
break;
|
||||||
|
case MP4Chapter.CHAPTERTYPE_MP4CHAPTER:
|
||||||
|
chapter = new MP4Chapter(start, title, item, link);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (chapter != null) {
|
if (chapter != null) {
|
||||||
chapter.setId(chapterCursor
|
chapter.setId(chapterCursor
|
||||||
|
@ -3,17 +3,22 @@ package de.danoeh.antennapod.util;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import de.danoeh.antennapod.BuildConfig;
|
import de.danoeh.antennapod.BuildConfig;
|
||||||
import de.danoeh.antennapod.feed.Chapter;
|
import de.danoeh.antennapod.feed.Chapter;
|
||||||
|
import de.danoeh.antennapod.feed.MP4Chapter;
|
||||||
import de.danoeh.antennapod.util.comparator.ChapterStartTimeComparator;
|
import de.danoeh.antennapod.util.comparator.ChapterStartTimeComparator;
|
||||||
import de.danoeh.antennapod.util.id3reader.ChapterReader;
|
import de.danoeh.antennapod.util.id3reader.ChapterReader;
|
||||||
import de.danoeh.antennapod.util.id3reader.ID3ReaderException;
|
import de.danoeh.antennapod.util.id3reader.ID3ReaderException;
|
||||||
import de.danoeh.antennapod.util.playback.Playable;
|
import de.danoeh.antennapod.util.playback.Playable;
|
||||||
import de.danoeh.antennapod.util.vorbiscommentreader.VorbisCommentChapterReader;
|
import de.danoeh.antennapod.util.vorbiscommentreader.VorbisCommentChapterReader;
|
||||||
import de.danoeh.antennapod.util.vorbiscommentreader.VorbisCommentReaderException;
|
import de.danoeh.antennapod.util.vorbiscommentreader.VorbisCommentReaderException;
|
||||||
|
import wseemann.media.FFmpegChapter;
|
||||||
|
import wseemann.media.FFmpegMediaMetadataRetriever;
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -190,6 +195,30 @@ public class ChapterUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void readMP4ChaptersFromFileUrl(Playable p) {
|
||||||
|
if (!FFmpegMediaMetadataRetriever.LIB_AVAILABLE) {
|
||||||
|
if (BuildConfig.DEBUG) Log.d(TAG, "FFmpegMediaMetadataRetriever not available on this architecture");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (BuildConfig.DEBUG) Log.d(TAG, "Trying to read mp4 chapters from file " + p.getEpisodeTitle());
|
||||||
|
|
||||||
|
FFmpegMediaMetadataRetriever retriever = new FFmpegMediaMetadataRetriever();
|
||||||
|
retriever.setDataSource(p.getLocalMediaUrl());
|
||||||
|
FFmpegChapter[] res = retriever.getChapters();
|
||||||
|
retriever.release();
|
||||||
|
if (res != null) {
|
||||||
|
List<Chapter> chapters = new ArrayList<Chapter>();
|
||||||
|
for (FFmpegChapter fFmpegChapter : res) {
|
||||||
|
chapters.add(new MP4Chapter(fFmpegChapter));
|
||||||
|
}
|
||||||
|
Collections.sort(chapters, new ChapterStartTimeComparator());
|
||||||
|
processChapters(chapters, p);
|
||||||
|
p.setChapters(chapters);
|
||||||
|
} else {
|
||||||
|
if (BuildConfig.DEBUG) Log.d(TAG, "No mp4 chapters found in " + p.getEpisodeTitle());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Makes sure that chapter does a title and an item attribute. */
|
/** Makes sure that chapter does a title and an item attribute. */
|
||||||
private static void processChapters(List<Chapter> chapters, Playable p) {
|
private static void processChapters(List<Chapter> chapters, Playable p) {
|
||||||
for (int i = 0; i < chapters.size(); i++) {
|
for (int i = 0; i < chapters.size(); i++) {
|
||||||
@ -254,6 +283,9 @@ public class ChapterUtils {
|
|||||||
if (media.getChapters() == null) {
|
if (media.getChapters() == null) {
|
||||||
ChapterUtils.readOggChaptersFromPlayableFileUrl(media);
|
ChapterUtils.readOggChaptersFromPlayableFileUrl(media);
|
||||||
}
|
}
|
||||||
|
if (media.getChapters() == null) {
|
||||||
|
ChapterUtils.readMP4ChaptersFromFileUrl(media);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "Could not load chapters from file url: local file not available");
|
Log.e(TAG, "Could not load chapters from file url: local file not available");
|
||||||
}
|
}
|
||||||
|
29
src/wseemann/media/FFmpegChapter.java
Normal file
29
src/wseemann/media/FFmpegChapter.java
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package wseemann.media;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a chapter mark returned by FFmpegMediaMetadataRetriever.
|
||||||
|
* */
|
||||||
|
public class FFmpegChapter
|
||||||
|
{
|
||||||
|
private int id;
|
||||||
|
private String title;
|
||||||
|
private long start;
|
||||||
|
|
||||||
|
public FFmpegChapter(int id, String title, long start) {
|
||||||
|
this.id = id;
|
||||||
|
this.title = title;
|
||||||
|
this.start = start;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getStart() {
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
}
|
595
src/wseemann/media/FFmpegMediaMetadataRetriever.java
Normal file
595
src/wseemann/media/FFmpegMediaMetadataRetriever.java
Normal file
@ -0,0 +1,595 @@
|
|||||||
|
/*
|
||||||
|
* FFmpegMediaMetadataRetriever: A unified interface for retrieving frame
|
||||||
|
* and meta data from an input media file.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Changes by Daniel Oeh:
|
||||||
|
* - Rewrite of the 'static' section
|
||||||
|
* - Addition of 'getChapters' method
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package wseemann.media;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
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 android.util.Log;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
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";
|
||||||
|
|
||||||
|
public static boolean LIB_AVAILABLE = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
@SuppressLint("SdCardPath")
|
||||||
|
private static final String LIBRARY_PATH = "/data/data/";
|
||||||
|
|
||||||
|
private static final String [] JNI_LIBRARIES = {
|
||||||
|
"avutil",
|
||||||
|
"swscale",
|
||||||
|
"avcodec",
|
||||||
|
"avformat",
|
||||||
|
"ffmpeg_mediametadataretriever_jni"
|
||||||
|
};
|
||||||
|
|
||||||
|
static {
|
||||||
|
/*
|
||||||
|
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
|
||||||
|
|
||||||
|
StringBuffer path = null;
|
||||||
|
File file = null;
|
||||||
|
boolean foundLibs = false;
|
||||||
|
|
||||||
|
for (int j = 0; j < stackTraceElements.length; j++) {
|
||||||
|
String libraryPath = stackTraceElements[j].getClassName();
|
||||||
|
|
||||||
|
String [] packageFragments = libraryPath.trim().split("\\.");
|
||||||
|
|
||||||
|
path = new StringBuffer(LIBRARY_PATH);
|
||||||
|
|
||||||
|
for (int i = 0; i < packageFragments.length; i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
path.append(".");
|
||||||
|
}
|
||||||
|
|
||||||
|
path.append(packageFragments[i]);
|
||||||
|
try {
|
||||||
|
//System.load(path.toString() + "/lib/" + JNI_LIBRARIES[0]);
|
||||||
|
file = new File(path.toString() + "/lib/" + JNI_LIBRARIES[0]);
|
||||||
|
if (file.exists()) {
|
||||||
|
path.append("/lib/");
|
||||||
|
foundLibs = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (UnsatisfiedLinkError ex) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundLibs) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since libraries for some architectures have been excluded from the source in order to save
|
||||||
|
// space, this class might not work on all devices.
|
||||||
|
if (!foundLibs) {
|
||||||
|
Log.e(TAG, TAG + " libraries not found. Did you forget to add them to your libs folder?");
|
||||||
|
//throw new UnsatisfiedLinkError();
|
||||||
|
LIB_AVAILABLE = false;
|
||||||
|
} else {
|
||||||
|
LIB_AVAILABLE = true;
|
||||||
|
for (int i = 0; i < JNI_LIBRARIES.length; i++) {
|
||||||
|
System.load(path.toString() + JNI_LIBRARIES[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
native_init();
|
||||||
|
}*/
|
||||||
|
try {
|
||||||
|
for (int i = 0; i < JNI_LIBRARIES.length; i++) {
|
||||||
|
System.loadLibrary(JNI_LIBRARIES[i]);
|
||||||
|
}
|
||||||
|
LIB_AVAILABLE = true;
|
||||||
|
native_init();
|
||||||
|
} catch (UnsatisfiedLinkError e) {
|
||||||
|
Log.e(TAG, "Library not found");
|
||||||
|
LIB_AVAILABLE = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this method after setDataSource(). This method finds any
|
||||||
|
* chapter marks that are contained in the media file.
|
||||||
|
*
|
||||||
|
* @return An array of FFmpegChapter objects or null if no chapters
|
||||||
|
* could be found.
|
||||||
|
* */
|
||||||
|
public native FFmpegChapter[] getChapters();
|
||||||
|
|
||||||
|
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";
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user