问题
Given the fact that public file paths will generally not be available in Android Q with scoped storage, I am attempting to figure out how to make my FFmpeg audio decoder work with file descriptors, without copying the file to my app's private directories.
We can easily get a file descriptor using the methods described in Android Q privacy changes, and it is possible to open the file descriptor using the pipe protocol as described in Passing a native fd int to FFMPEG from openable URI. However, the result is not seekable using av_seek_frame
and also the duration is not available using the duration member of AVFormatContext
.
Is there way to seek with a file descriptor with FFmpeg and retrieve the duration?
回答1:
it is possible to open the file descriptor using the pipe protocol as described
I'm curious why it is necessary opening file descriptor via pipe protocol? sView player opens file descriptor by custom AVIOContext, which is seekable, at least on older tested versions of Android. Here is a pseudo-code opening AVFormatContext with custom AVIOContext.
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);
Below is an attempt to extract a simplified StAVIOFileContext class definition.
//! 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;
};
回答2:
A custom protocol can handle Uri like content://com.android.providers.downloads.documents/document/msf%3A62
or content://com.android.externalstorage.documents/document/primary%3ADownload%2Ftranscode.aac
Here is the C code that opens such Uri (error checks hidden for brevity):
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;
}
来源:https://stackoverflow.com/questions/57445600/ffmpeg-seeking-not-possible-with-file-descriptor-on-android-q