鉴于公共文件路径在具有作用域存储的 Android Q 中通常不可用,我正在尝试弄清楚如何让我的 FFmpeg 音频解码器与文件描述符一起使用,而无需将文件复制到我的应用程序的私有目录。
我们可以使用 Android Q 隐私更改中描述的方法轻松获取文件描述符,并且可以使用管道协议打开文件描述符,如从可打开的 URI 将本机 fd int 传递给 FFMPEG 中所述。但是,使用av_seek_frame
无法查找结果,并且无法使用持续时间成员AVFormatContext
提供持续时间。
有没有办法使用 FFmpeg 使用文件描述符进行查找并检索持续时间?
管道协议打开文件描述符,如下所述
我很好奇为什么有必要通过管道协议打开文件描述符? sView 播放器通过自定义AVIOContext打开文件描述符,这是可搜索的,至少在较旧的测试版本的 Android 上是这样。这是一个伪代码,使用自定义AVIOContext打开AVFormatContext。
int aFileDescriptor = myResMgr->openFileDescriptor(theFileToLoad);
AVFormatContext* aFormatCtx = avformat_alloc_context();
StAVIOContext myAvioContext;
if(!myAvioContext.openFromDescriptor(aFileDescriptor, "rb")) {
// error
}
aFormatCtx->pb = myAvioContext.getAvioContext();
int avErrCode = avformat_open_input(&aFormatCtx, theFileToLoad, NULL, NULL);
下面是提取简化的StAVIOFileContext类定义的尝试。
//! Wrapper over AVIOContext for passing the custom I/O.
class StAVIOContext {
public:
//! Main constructor.
StAVIOContext() {
const int aBufferSize = 32768;
unsigned char* aBufferIO = (unsigned char* )av_malloc(aBufferSize + AV_INPUT_BUFFER_PADDING_SIZE);
AVIOContext* myAvioCtx = avio_alloc_context (aBufferIO, aBufferSize, 0, this, readCallback, writeCallback, seekCallback);
}
//! Destructor.
virtual ~StAVIOContext() {
close();
if (myAvioCtx != NULL) { av_free (myAvioCtx); }
}
//! Close the file.
void close() {
if(myFile != NULL) {
fclose(myFile);
myFile = NULL;
}
}
//! Associate a stream with a file that was previously opened for low-level I/O.
//! The associated file will be automatically closed on destruction.
bool openFromDescriptor(int theFD, const char* theMode) {
close();
#ifdef _WIN32
myFile = ::_fdopen(theFD, theMode);
#else
myFile = ::fdopen(theFD, theMode);
#endif
return myFile != NULL;
}
//! Access AVIO context.
AVIOContext* getAvioContext() const { return myAvioCtx; }
public:
//! Virtual method for reading the data.
virtual int read (uint8_t* theBuf,
int theBufSize) {
if(myFile == NULL) { return -1; }
int aNbRead = (int )::fread(theBuf, 1, theBufSize, myFile);
if(aNbRead == 0 && feof(myFile) != 0) { return AVERROR_EOF; }
return aNbRead;
}
//! Virtual method for writing the data.
virtual int write (uint8_t* theBuf,
int theBufSize) {
if(myFile == NULL) { return -1; }
return (int )::fwrite(theBuf, 1, theBufSize, myFile);
}
//! Virtual method for seeking to new position.
virtual int64_t seek (int64_t theOffset,
int theWhence) {
if(theWhence == AVSEEK_SIZE || myFile == NULL) { return -1; }
#ifdef _WIN32
bool isOk = ::_fseeki64(myFile, theOffset, theWhence) == 0;
#else
bool isOk = ::fseeko(myFile, theOffset, theWhence) == 0;
#endif
if(!isOk) { return -1; }
#ifdef _WIN32
return ::_ftelli64(myFile);
#else
return ::ftello(myFile);
#endif
}
private:
//! Callback for reading the data.
static int readCallback(void* theOpaque,
uint8_t* theBuf,
int theBufSize) {
return theOpaque != NULL
? ((StAVIOContext* )theOpaque)->read(theBuf, theBufSize)
: 0;
}
//! Callback for writing the data.
static int writeCallback(void* theOpaque,
uint8_t* theBuf,
int theBufSize) {
return theOpaque != NULL
? ((StAVIOContext* )theOpaque)->write(theBuf, theBufSize)
: 0;
}
//! Callback for seeking to new position.
static int64_t seekCallback(void* theOpaque,
int64_t theOffset,
int theWhence) {
return theOpaque != NULL
? ((StAVIOContext* )theOpaque)->seek(theOffset, theWhence)
: -1;
}
protected:
AVIOContext* myAvioCtx;
FILE* myFile;
};
自定义协议可以像content://com.android.providers.downloads.documents/document/msf%3A62
或content://com.android.externalstorage.documents/document/primary%3ADownload%2Ftranscode.aac
一样处理 Uri
以下是打开此类 Uri 的 C 代码(为简洁起见,隐藏了错误检查):
int get_fd_from_content(const char *content, int access) {
static jclass android_net_Uri;
static jmethodID android_net_Uri_parse = 0;
static jmethodID android_content_Context_getContentResolver = 0;
static jmethodID android_content_ContentResolver_openFileDescriptor = 0;
static jmethodID android_os_ParcelFileDescriptor_getFd = 0;
int fd = -1;
JNIEnv *env;
int ret = (*globalVm)->GetEnv(globalVm, (void **)&env, JNI_VERSION_1_6);
android_net_Uri_parse = get_static_method_id(env, "android/net/Uri", "parse", "(Ljava/lang/String;)Landroid/net/Uri;", &android_net_Uri);
android_content_Context_getContentResolver = get_method_id(env, "android/content/Context", "getContentResolver", "()Landroid/content/ContentResolver;");
android_content_ContentResolver_openFileDescriptor = get_method_id(env, "android/content/ContentResolver", "openFileDescriptor", "(Landroid/net/Uri;Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;");
android_os_ParcelFileDescriptor_getFd = get_method_id(env, "android/os/ParcelFileDescriptor", "getFd", "()I"));
const char *fmode = "r";
if (access & (O_WRONLY | O_RDWR)) {
fmode = "w";
}
LOGI("get_fd_from_content" " "%s" fd from %s", fmode, content);
jstring uriString = (*env)->NewStringUTF(env, content);
jstring fmodeString = (*env)->NewStringUTF(env, fmode);
jobject uri = (*env)->CallStaticObjectMethod(env, android_net_Uri, android_net_Uri_parse, uriString);
jobject contentResolver = (*env)->CallObjectMethod(env, appContext, android_content_Context_getContentResolver);
jobject parcelFileDescriptor = (*env)->CallObjectMethod(env, contentResolver, android_content_ContentResolver_openFileDescriptor, uri, fmodeString);
fd = (*env)->CallIntMethod(env, parcelFileDescriptor, android_os_ParcelFileDescriptor_getFd);
(*env)->DeleteLocalRef(env, uriString);
(*env)->DeleteLocalRef(env, fmodeString);
(*env)->DeleteLocalRef(env, uri);
(*env)->DeleteLocalRef(env, contentResolver);
(*env)->DeleteLocalRef(env, parcelFileDescriptor);
return fd;
}