如何在 Android 中使用 JNI 将资产 FileDescriptor 正确传递给 FFmpeg [英] How to properly pass an asset FileDescriptor to FFmpeg using JNI in Android
问题描述
我正在尝试使用 FFmpeg、JNI 和 Java 在 Android 中检索元数据 FileDescriptor 并且它不工作.我知道 FFmpeg 支持 管道协议,所以我正在尝试模拟:cat test.mp3 | ffmpeg i pipe:0
" 以编程方式.我使用以下代码从与 Android 应用程序捆绑的资产中获取 FileDescriptor:
I'm trying to retrieve metadata in Android using FFmpeg, JNI and a Java FileDescriptor and it isn't' working. I know FFmpeg supports the pipe protocol so I'm trying to emmulate: "cat test.mp3 | ffmpeg i pipe:0
" programmatically. I use the following code to get a FileDescriptor from an asset bundled with the Android application:
FileDescriptor fd = getContext().getAssets().openFd("test.mp3").getFileDescriptor();
setDataSource(fd, 0, 0x7ffffffffffffffL); // native function, shown below
然后,在我的本机(在 C++ 中)代码中,我通过调用获取 FileDescriptor:
Then, in my native (In C++) code I get the FileDescriptor by calling:
static void wseemann_media_FFmpegMediaMetadataRetriever_setDataSource(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
{
//...
int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); // function contents show below
//...
}
// function contents
static int jniGetFDFromFileDescriptor(JNIEnv * env, jobject fileDescriptor) {
jint fd = -1;
jclass fdClass = env->FindClass("java/io/FileDescriptor");
if (fdClass != NULL) {
jfieldID fdClassDescriptorFieldID = env->GetFieldID(fdClass, "descriptor", "I");
if (fdClassDescriptorFieldID != NULL && fileDescriptor != NULL) {
fd = env->GetIntField(fileDescriptor, fdClassDescriptorFieldID);
}
}
return fd;
}
然后我将文件描述符管道 #(在 C 中)传递给 FFmpeg:
I then pass the file descriptor pipe # (In C) to FFmpeg:
char path[256] = "";
FILE *file = fdopen(fd, "rb");
if (file && (fseek(file, offset, SEEK_SET) == 0)) {
char str[20];
sprintf(str, "pipe:%d", fd);
strcat(path, str);
}
State *state = av_mallocz(sizeof(State));
state->pFormatCtx = NULL;
if (avformat_open_input(&state->pFormatCtx, path, NULL, &options) != 0) { // Note: path is in the format "pipe:<the FD #>"
printf("Metadata could not be retrieved
");
*ps = NULL;
return FAILURE;
}
if (avformat_find_stream_info(state->pFormatCtx, NULL) < 0) {
printf("Metadata could not be retrieved
");
avformat_close_input(&state->pFormatCtx);
*ps = NULL;
return FAILURE;
}
// Find the first audio and video stream
for (i = 0; i < state->pFormatCtx->nb_streams; i++) {
if (state->pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO && video_index < 0) {
video_index = i;
}
if (state->pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO && audio_index < 0) {
audio_index = i;
}
set_codec(state->pFormatCtx, i);
}
if (audio_index >= 0) {
stream_component_open(state, audio_index);
}
if (video_index >= 0) {
stream_component_open(state, video_index);
}
printf("Found metadata
");
AVDictionaryEntry *tag = NULL;
while ((tag = av_dict_get(state->pFormatCtx->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) {
printf("Key %s:
", tag->key);
printf("Value %s:
", tag->value);
}
*ps = state;
return SUCCESS;
我的问题是 avformat_open_input
不会失败,但它也不允许我检索任何元数据或帧,如果我使用常规文件 URI(例如 file://sdcard/test.mp3) 作为路径.我究竟做错了什么?提前致谢.
My issue is avformat_open_input
doesn't fail but it also doesn't let me retrieve any metadata or frames, The same code works if I use a regular file URI (e.g file://sdcard/test.mp3) as the path. What am I doing wrong? Thanks in advance.
注意:如果您想查看我正在尝试解决问题的所有代码,以便为我的库提供此功能:FFmpegMediaMetadataRetriever.
Note: if you would like to look at all of the code I'm trying to solve the issue in order to provide this functionality for my library: FFmpegMediaMetadataRetriever.
推荐答案
Java
AssetFileDescriptor afd = getContext().getAssets().openFd("test.mp3");
setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), fd.getLength());
C
void ***_setDataSource(JNIEnv *env, jobject thiz,
jobject fileDescriptor, jlong offset, jlong length)
{
int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
char path[20];
sprintf(path, "pipe:%d", fd);
State *state = av_mallocz(sizeof(State));
state->pFormatCtx = avformat_alloc_context();
state->pFormatCtx->skip_initial_bytes = offset;
state->pFormatCtx->iformat = av_find_input_format("mp3");
现在我们可以照常继续了:
and now we can continue as usual:
if (avformat_open_input(&state->pFormatCtx, path, NULL, &options) != 0) {
printf("Metadata could not be retrieved
");
*ps = NULL;
return FAILURE;
}
...
<小时>
更好的是,使用
,像这样:
Java
setDataSource(getContext().getAssets(), "test.mp3");
C
#include <android/asset_manager_jni.h>
void ***_setDataSource(JNIEnv *env, jobject thiz,
jobject assetManager, jstring assetName)
{
AAssetManager* assetManager = AAssetManager_fromJava(env, assetManager);
const char *szAssetName = (*env)->GetStringUTFChars(env, assetName, NULL);
AAsset* asset = AAssetManager_open(assetManager, szAssetName, AASSET_MODE_RANDOM);
(*env)->ReleaseStringUTFChars(env, assetName, szAssetName);
off_t offset, length;
int fd = AAsset_openFileDescriptor(asset, &offset, &length);
AAsset_close(asset);
免责声明:为简洁起见省略了错误检查,但资源被正确释放,fd除外.完成后必须close(fd)
.
Disclaimer: error checking was omitted for brevity, but resources are released correctly, except for fd. You must close(fd)
when finished.
Post Scriptum:请注意某些媒体格式,例如mp4 需要可搜索的协议,pipe:
帮不上忙.在这种情况下,您可以尝试 sprintf(path, "/proc/self/fd/%d", fd);
,或使用 自定义saf:
协议.
Post Scriptum: note that some media formats, e.g. mp4 need seekable protocol, and pipe:
cannot help. In such case, you may try sprintf(path, "/proc/self/fd/%d", fd);
, or use the custom saf:
protocol.
这篇关于如何在 Android 中使用 JNI 将资产 FileDescriptor 正确传递给 FFmpeg的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!