Skip to content
This repository has been archived by the owner on Jan 10, 2023. It is now read-only.

Commit

Permalink
Browse files Browse the repository at this point in the history
lavf: add Android content resolver protocol support
Handles uri starting with content://.
  • Loading branch information
mbouron committed Jan 16, 2020
1 parent 174edaf commit 6d06b14
Show file tree
Hide file tree
Showing 6 changed files with 241 additions and 0 deletions.
2 changes: 2 additions & 0 deletions configure
Expand Up @@ -3352,6 +3352,8 @@ xv_outdev_deps="xlib"

# protocols
async_protocol_deps="threads"
android_content_protocol_deps="jni"
android_content_protocol_select="file_protocol"
bluray_protocol_deps="libbluray"
ffrtmpcrypt_protocol_conflict="librtmp_protocol"
ffrtmpcrypt_protocol_deps_any="gcrypt gmp openssl mbedtls"
Expand Down
3 changes: 3 additions & 0 deletions libavformat/Makefile
Expand Up @@ -5,6 +5,8 @@ HEADERS = avformat.h \
avio.h \
version.h \

HEADERS-$(CONFIG_ANDROID_CONTENT_PROTOCOL) += android_content.h

OBJS = allformats.o \
avio.o \
aviobuf.o \
Expand Down Expand Up @@ -578,6 +580,7 @@ OBJS-$(CONFIG_VAPOURSYNTH_DEMUXER) += vapoursynth.o

# protocols I/O
OBJS-$(CONFIG_ASYNC_PROTOCOL) += async.o
OBJS-$(CONFIG_ANDROID_CONTENT_PROTOCOL) += android_content.o file.o
OBJS-$(CONFIG_APPLEHTTP_PROTOCOL) += hlsproto.o
OBJS-$(CONFIG_BLURAY_PROTOCOL) += bluray.o
OBJS-$(CONFIG_CACHE_PROTOCOL) += cache.o
Expand Down
44 changes: 44 additions & 0 deletions libavformat/android_content.c
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2015-2016 Matthieu Bouron <matthieu.bouron stupeflix.com>
*
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* FFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include <pthread.h>

#include "android_content.h"

static void *android_app_ctx;
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

void av_android_content_set_app_ctx(void *ctx)
{
pthread_mutex_lock(&lock);
android_app_ctx = ctx;
pthread_mutex_unlock(&lock);
}

void *av_android_content_get_app_ctx(void)
{
void *ctx;

pthread_mutex_lock(&lock);
ctx = android_app_ctx;
pthread_mutex_unlock(&lock);

return ctx;
}
27 changes: 27 additions & 0 deletions libavformat/android_content.h
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2015-2016 Matthieu Bouron <matthieu.bouron stupeflix.com>
*
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* FFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#ifndef AVFORMAT_ANDROID_CONTENT_H
#define AVFORMAT_ANDROID_CONTENT_H

void av_android_content_set_app_ctx(void *ctx);
void *av_android_content_get_app_ctx(void);

#endif /* AVFORMAT_ANDROID_CONTENT_H */
164 changes: 164 additions & 0 deletions libavformat/file.c
Expand Up @@ -37,6 +37,12 @@
#include <stdlib.h>
#include "os_support.h"
#include "url.h"
#if CONFIG_ANDROID_CONTENT_PROTOCOL
#include <jni.h>
#include "libavcodec/ffjni.c"
#include "android_content.h"
#endif


/* Some systems may not have S_ISFIFO */
#ifndef S_ISFIFO
Expand Down Expand Up @@ -79,6 +85,22 @@ typedef struct FileContext {
#endif
} FileContext;


#if CONFIG_ANDROID_CONTENT_PROTOCOL
static const AVOption android_content_options[] = {
{ "truncate", "truncate existing files on write", offsetof(FileContext, trunc), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, AV_OPT_FLAG_ENCODING_PARAM },
{ "blocksize", "set I/O operation maximum block size", offsetof(FileContext, blocksize), AV_OPT_TYPE_INT, { .i64 = INT_MAX }, 1, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
{ NULL }
};

static const AVClass android_content_class = {
.class_name = "android_content",
.item_name = av_default_item_name,
.option = android_content_options,
.version = LIBAVUTIL_VERSION_INT,
};
#endif

static const AVOption file_options[] = {
{ "truncate", "truncate existing files on write", offsetof(FileContext, trunc), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, AV_OPT_FLAG_ENCODING_PARAM },
{ "blocksize", "set I/O operation maximum block size", offsetof(FileContext, blocksize), AV_OPT_TYPE_INT, { .i64 = INT_MAX }, 1, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
Expand Down Expand Up @@ -412,3 +434,145 @@ const URLProtocol ff_pipe_protocol = {
};

#endif /* CONFIG_PIPE_PROTOCOL */

#if CONFIG_ANDROID_CONTENT_PROTOCOL

struct JFields {

jclass uri_class;
jclass parse_id;

jclass context_class;
jmethodID get_content_resolver_id;

jclass content_resolver_class;
jmethodID open_file_descriptor_id;

jclass parcel_file_descriptor_class;
jmethodID detach_fd_id;

} JFields;

static const struct FFJniField jfields_mapping[] = {

{ "android/net/Uri", NULL, NULL, FF_JNI_CLASS, offsetof(struct JFields, uri_class), 1 },
{ "android/net/Uri", "parse", "(Ljava/lang/String;)Landroid/net/Uri;", FF_JNI_STATIC_METHOD, offsetof(struct JFields, parse_id), 1 },

{ "android/content/Context", NULL, NULL, FF_JNI_CLASS, offsetof(struct JFields, context_class), 1 },
{ "android/content/Context", "getContentResolver", "()Landroid/content/ContentResolver;", FF_JNI_METHOD, offsetof(struct JFields, get_content_resolver_id), 1 },

{ "android/content/ContentResolver", NULL, NULL, FF_JNI_CLASS, offsetof(struct JFields, content_resolver_class), 1 },
{ "android/content/ContentResolver", "openFileDescriptor", "(Landroid/net/Uri;Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;", FF_JNI_METHOD, offsetof(struct JFields, open_file_descriptor_id), 1 },

{ "android/os/ParcelFileDescriptor", NULL, NULL, FF_JNI_CLASS, offsetof(struct JFields, parcel_file_descriptor_class), 1 },
{ "android/os/ParcelFileDescriptor", "detachFd", "()I", FF_JNI_METHOD, offsetof(struct JFields, detach_fd_id), 1 },

{ NULL }
};

static int android_content_open(URLContext *h, const char *filename, int flags)
{
FileContext *c = h->priv_data;
int fd, ret = 0;
JNIEnv *env;
struct JFields jfields = { 0 };

jobject application_context = NULL;
jobject url = NULL;
jobject mode = NULL;
jobject uri = NULL;
jobject content_resolver = NULL;
jobject parcel_file_descriptor = NULL;

env = ff_jni_get_env(c);
if (!env) {
return AVERROR(EINVAL);
}

if (ff_jni_init_jfields(env, &jfields, jfields_mapping, 0, c) < 0) {
goto done;
}

application_context = av_android_content_get_app_ctx();
if (!application_context) {
av_log(c, AV_LOG_ERROR, "application context is not set\n");
ret = AVERROR_EXTERNAL;
goto done;
}

url = ff_jni_utf_chars_to_jstring(env, filename, c);
if (!url) {
ret = AVERROR_EXTERNAL;
goto done;
}

mode = ff_jni_utf_chars_to_jstring(env, "r", c);
if (!mode) {
ret = AVERROR_EXTERNAL;
goto done;
}

uri = (*env)->CallStaticObjectMethod(env, jfields.uri_class, jfields.parse_id, url);
if ((ret = ff_jni_exception_check(env, 1, c)) < 0) {
goto done;
}

content_resolver = (*env)->CallObjectMethod(env, application_context, jfields.get_content_resolver_id);
if ((ret = ff_jni_exception_check(env, 1, c)) < 0) {
goto done;
}

parcel_file_descriptor = (*env)->CallObjectMethod(env, content_resolver, jfields.open_file_descriptor_id, uri, mode);
if ((ret = ff_jni_exception_check(env, 1, c)) < 0) {
goto done;
}

fd = (*env)->CallIntMethod(env, parcel_file_descriptor, jfields.detach_fd_id);
if ((ret = ff_jni_exception_check(env, 1, c)) < 0) {
goto done;
}

#if HAVE_SETMODE
setmode(fd, O_BINARY);
#endif
c->fd = fd;
h->is_streamed = 0;

done:
if (url) {
(*env)->DeleteLocalRef(env, url);
}

if (mode) {
(*env)->DeleteLocalRef(env, mode);
}

if (uri) {
(*env)->DeleteLocalRef(env, uri);
}

if (content_resolver) {
(*env)->DeleteLocalRef(env, content_resolver);
}

if (parcel_file_descriptor) {
(*env)->DeleteLocalRef(env, parcel_file_descriptor);
}

return ret;
}

URLProtocol ff_android_content_protocol = {
.name = "content",
.url_open = android_content_open,
.url_read = file_read,
.url_write = file_write,
.url_seek = file_seek,
.url_close = file_close,
.url_get_file_handle = file_get_handle,
.url_check = NULL,
.priv_data_size = sizeof(FileContext),
.priv_data_class = &android_content_class,
};

#endif /* CONFIG_ANDROID_CONTENT_PROTOCOL */
1 change: 1 addition & 0 deletions libavformat/protocols.c
Expand Up @@ -23,6 +23,7 @@

#include "url.h"

extern const URLProtocol ff_android_content_protocol;
extern const URLProtocol ff_async_protocol;
extern const URLProtocol ff_bluray_protocol;
extern const URLProtocol ff_cache_protocol;
Expand Down

0 comments on commit 6d06b14

Please sign in to comment.