Как правильно передать актив FileDescriptor в FFmpeg с помощью JNI в Android
Я пытаюсь получить метаданные в Android, используя FFmpeg, JNI и Java FileDescriptor, но это не работает. Я знаю, что FFmpeg поддерживает протокол канала, поэтому я пытаюсь подражать: "cat test.mp3 | ffmpeg i pipe:0
"программно. Я использую следующий код, чтобы получить FileDescriptor из ресурса, связанного с приложением Android:
FileDescriptor fd = getContext().getAssets().openFd("test.mp3").getFileDescriptor();
setDataSource(fd, 0, 0x7ffffffffffffffL); // native function, shown below
Затем в своем родном (на C++) коде я получаю FileDescriptor, вызывая:
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;
}
Затем я передаю дескриптор файла # (In C) в 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\n");
*ps = NULL;
return FAILURE;
}
if (avformat_find_stream_info(state->pFormatCtx, NULL) < 0) {
printf("Metadata could not be retrieved\n");
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\n");
AVDictionaryEntry *tag = NULL;
while ((tag = av_dict_get(state->pFormatCtx->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) {
printf("Key %s: \n", tag->key);
printf("Value %s: \n", tag->value);
}
*ps = state;
return SUCCESS;
Моя проблема avformat_open_input
не дает сбоя, но также не позволяет мне получать метаданные или фреймы. Тот же код работает, если я использую обычный URI файла (например, file://sdcard/test.mp3) в качестве пути. Что я делаю неправильно? Заранее спасибо.
Примечание: если вы хотите просмотреть весь код, который я пытаюсь решить, чтобы предоставить эту функцию для моей библиотеки: https://github.com/wseemann/FFmpegMediaMetadataRetriever.
4 ответа
Джава
AssetFileDescriptor afd = getContext().getAssets().openFd("test.mp3");
setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), fd.getLength());
С
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");
и теперь мы можем продолжить как обычно:
if (avformat_open_input(&state->pFormatCtx, path, NULL, &options) != 0) {
printf("Metadata could not be retrieved\n");
*ps = NULL;
return FAILURE;
}
...
Еще лучше, используйте <android/asset_manager.h>
, как это:
Джава
setDataSource(getContext().getAssets(), "test.mp3");
С
#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)
когда закончите.
Большое спасибо за этот пост. Это очень помогло мне интегрировать Android 10 и хранилище с ограниченным объемом памяти с FFmpeg с помощью FileDescriptor.
Вот решение, которое я использую на Android 10:
Джава
URI uri = ContentUris.withAppendedId(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
trackId // Coming from `MediaStore.Audio.Media._ID`
);
ParcelFileDescriptor parcelFileDescriptor = getContentResolver().openFileDescriptor(
uri,
"r"
);
int pid = android.os.Process.myPid();
String path = "/proc/" + pid + "/fd/" + parcelFileDescriptor.dup().getFd();
loadFFmpeg(path); // Call native code
CPP
// Native code, `path` coming from Java `loadFFmpeg(String)`
avformat_open_input(&format, path, nullptr, nullptr);
Хорошо, я потратил много времени, пытаясь передать медиаданные в ffmpeg через
Assetfiledescriptor
. Наконец, я обнаружил, что может быть ошибка в . Когда
mov.c
разобрал
trak
атом, соответствующий
skip_initial_bytes
не был установлен. Я пытался решить эту проблему.
Подробности см. в FFmpegForAndroidAssetFileDescriptor, демонстрация — в WhatTheCodec .
FileDescriptor fd = getContext().getAssets().openFd("test.mp3").getFileDescriptor();
Думаю, вам следует начать с AssetFileDescripter. http://developer.android.com/reference/android/content/res/AssetFileDescriptor.html