FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavformat/file.c
Date: 2026-07-21 08:37:06
Exec Total Coverage
Lines: 83 176 47.2%
Functions: 9 15 60.0%
Branches: 46 128 35.9%

Line Branch Exec Source
1 /*
2 * buffered file I/O
3 * Copyright (c) 2001 Fabrice Bellard
4 *
5 * This file is part of FFmpeg.
6 *
7 * FFmpeg is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * FFmpeg is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with FFmpeg; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22 #include <string.h>
23
24 #include "config_components.h"
25
26 #include "libavutil/avstring.h"
27 #include "libavutil/error.h"
28 #include "libavutil/file_open.h"
29 #include "libavutil/internal.h"
30 #include "libavutil/mem.h"
31 #include "libavutil/opt.h"
32 #include "avio.h"
33 #if HAVE_DIRENT_H
34 #include <dirent.h>
35 #endif
36 #include <fcntl.h>
37 #if HAVE_IO_H
38 #include <io.h>
39 #endif
40 #if HAVE_UNISTD_H
41 #include <unistd.h>
42 #endif
43 #include <sys/stat.h>
44 #include <stdlib.h>
45 #include "os_support.h"
46 #include "url.h"
47
48 /* Some systems may not have S_ISFIFO */
49 #ifndef S_ISFIFO
50 # ifdef S_IFIFO
51 # define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)
52 # else
53 # define S_ISFIFO(m) 0
54 # endif
55 #endif
56
57 /* Not available in POSIX.1-1996 */
58 #ifndef S_ISLNK
59 # ifdef S_IFLNK
60 # define S_ISLNK(m) (((m) & S_IFLNK) == S_IFLNK)
61 # else
62 # define S_ISLNK(m) 0
63 # endif
64 #endif
65
66 /* Not available in POSIX.1-1996 */
67 #ifndef S_ISSOCK
68 # ifdef S_IFSOCK
69 # define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)
70 # else
71 # define S_ISSOCK(m) 0
72 # endif
73 #endif
74
75 /* S_ISREG not available on Windows */
76 #ifndef S_ISREG
77 # ifdef S_IFREG
78 # define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
79 # else
80 # define S_ISREG(m) 0
81 # endif
82 #endif
83
84 /* S_ISBLK not available on Windows */
85 #ifndef S_ISBLK
86 # ifdef S_IFBLK
87 # define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)
88 # else
89 # define S_ISBLK(m) 0
90 # endif
91 #endif
92
93 /* standard file protocol */
94
95 typedef struct FileContext {
96 const AVClass *class;
97 int fd;
98 int trunc;
99 int blocksize;
100 int pkt_size;
101 int follow;
102 int seekable;
103 #if HAVE_DIRENT_H
104 DIR *dir;
105 #endif
106 } FileContext;
107
108 static const AVOption file_options[] = {
109 { "truncate", "truncate existing files on write", offsetof(FileContext, trunc), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, AV_OPT_FLAG_ENCODING_PARAM },
110 { "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 },
111 { "follow", "Follow a file as it is being written", offsetof(FileContext, follow), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, AV_OPT_FLAG_DECODING_PARAM },
112 { "seekable", "Sets if the file is seekable", offsetof(FileContext, seekable), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 0, AV_OPT_FLAG_DECODING_PARAM | AV_OPT_FLAG_ENCODING_PARAM },
113 { "pkt_size", "Maximum packet size", offsetof(FileContext, pkt_size), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, AV_OPT_FLAG_DECODING_PARAM | AV_OPT_FLAG_ENCODING_PARAM },
114 { NULL }
115 };
116
117 static const AVOption pipe_options[] = {
118 { "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 },
119 { "fd", "set file descriptor", offsetof(FileContext, fd), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
120 { NULL }
121 };
122
123 static const AVClass file_class = {
124 .class_name = "file",
125 .item_name = av_default_item_name,
126 .option = file_options,
127 .version = LIBAVUTIL_VERSION_INT,
128 };
129
130 static const AVClass pipe_class = {
131 .class_name = "pipe",
132 .item_name = av_default_item_name,
133 .option = pipe_options,
134 .version = LIBAVUTIL_VERSION_INT,
135 };
136
137 static const AVClass fd_class = {
138 .class_name = "fd",
139 .item_name = av_default_item_name,
140 .option = pipe_options,
141 .version = LIBAVUTIL_VERSION_INT,
142 };
143
144 241309 static int file_read(URLContext *h, unsigned char *buf, int size)
145 {
146 241309 FileContext *c = h->priv_data;
147 int ret;
148 241309 size = FFMIN(size, c->blocksize);
149 241309 ret = read(c->fd, buf, size);
150
3/4
✓ Branch 0 taken 13230 times.
✓ Branch 1 taken 228079 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 13230 times.
241309 if (ret == 0 && c->follow)
151 return AVERROR(EAGAIN);
152
2/2
✓ Branch 0 taken 13230 times.
✓ Branch 1 taken 228079 times.
241309 if (ret == 0)
153 13230 return AVERROR_EOF;
154
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 228079 times.
228079 return (ret == -1) ? AVERROR(errno) : ret;
155 }
156
157 303170 static int file_write(URLContext *h, const unsigned char *buf, int size)
158 {
159 303170 FileContext *c = h->priv_data;
160 int ret;
161 303170 size = FFMIN(size, c->blocksize);
162 303170 ret = write(c->fd, buf, size);
163
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 303170 times.
303170 return (ret == -1) ? AVERROR(errno) : ret;
164 }
165
166 static int file_get_handle(URLContext *h)
167 {
168 FileContext *c = h->priv_data;
169 return c->fd;
170 }
171
172 52600 static int file_check(URLContext *h, int mask)
173 {
174 52600 int ret = 0;
175 52600 const char *filename = h->filename;
176 52600 av_strstart(filename, "file:", &filename);
177
178 {
179 #if HAVE_ACCESS && defined(R_OK)
180
2/2
✓ Branch 1 taken 12455 times.
✓ Branch 2 taken 40145 times.
52600 if (access(filename, F_OK) < 0)
181 12455 return AVERROR(errno);
182
1/2
✓ Branch 0 taken 40145 times.
✗ Branch 1 not taken.
40145 if (mask&AVIO_FLAG_READ)
183
1/2
✓ Branch 1 taken 40145 times.
✗ Branch 2 not taken.
40145 if (access(filename, R_OK) >= 0)
184 40145 ret |= AVIO_FLAG_READ;
185
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 40145 times.
40145 if (mask&AVIO_FLAG_WRITE)
186 if (access(filename, W_OK) >= 0)
187 ret |= AVIO_FLAG_WRITE;
188 #else
189 struct stat st;
190 ret = stat(filename, &st);
191 if (ret < 0)
192 return AVERROR(errno);
193
194 ret |= st.st_mode&S_IRUSR ? mask&AVIO_FLAG_READ : 0;
195 ret |= st.st_mode&S_IWUSR ? mask&AVIO_FLAG_WRITE : 0;
196 #endif
197 }
198 40145 return ret;
199 }
200
201 #if CONFIG_FD_PROTOCOL || CONFIG_PIPE_PROTOCOL
202 2806 static int fd_dup(URLContext *h, int oldfd)
203 {
204 int newfd;
205
206 #ifdef F_DUPFD_CLOEXEC
207 newfd = fcntl(oldfd, F_DUPFD_CLOEXEC, 0);
208 #else
209 2806 newfd = dup(oldfd);
210 #endif
211
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2806 times.
2806 if (newfd == -1)
212 return newfd;
213
214 #if HAVE_FCNTL
215
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2806 times.
2806 if (fcntl(newfd, F_SETFD, FD_CLOEXEC) == -1)
216 av_log(h, AV_LOG_DEBUG, "Failed to set close on exec\n");
217 #endif
218
219 #if HAVE_SETMODE
220 setmode(newfd, O_BINARY);
221 #endif
222 2806 return newfd;
223 }
224 #endif
225
226 129352 static int file_close(URLContext *h)
227 {
228 129352 FileContext *c = h->priv_data;
229 129352 int ret = close(c->fd);
230
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 129352 times.
129352 return (ret == -1) ? AVERROR(errno) : 0;
231 }
232
233 /* XXX: use llseek */
234 410043 static int64_t file_seek(URLContext *h, int64_t pos, int whence)
235 {
236 410043 FileContext *c = h->priv_data;
237 int64_t ret;
238
239
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 410043 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
410043 if (c->follow && (whence == SEEK_END || whence == AVSEEK_SIZE))
240 return AVERROR(ENOSYS); /* true size is not known */
241
242
2/2
✓ Branch 0 taken 271600 times.
✓ Branch 1 taken 138443 times.
410043 if (whence == AVSEEK_SIZE) {
243 struct stat st;
244 271600 ret = fstat(c->fd, &st);
245
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 271600 times.
✓ Branch 2 taken 271600 times.
✗ Branch 3 not taken.
271600 return ret < 0 ? AVERROR(errno) : (S_ISFIFO(st.st_mode) ? 0 : st.st_size);
246 }
247
248 138443 ret = lseek(c->fd, pos, whence);
249
250
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 138443 times.
138443 return ret < 0 ? AVERROR(errno) : ret;
251 }
252
253 #if CONFIG_FILE_PROTOCOL
254
255 static int file_delete(URLContext *h)
256 {
257 #if HAVE_UNISTD_H
258 int ret;
259 const char *filename = h->filename;
260 av_strstart(filename, "file:", &filename);
261
262 ret = rmdir(filename);
263 if (ret < 0 && (errno == ENOTDIR
264 # ifdef _WIN32
265 || errno == EINVAL
266 # endif
267 ))
268 ret = unlink(filename);
269 if (ret < 0)
270 return AVERROR(errno);
271
272 return ret;
273 #else
274 return AVERROR(ENOSYS);
275 #endif /* HAVE_UNISTD_H */
276 }
277
278 116 static int file_move(URLContext *h_src, URLContext *h_dst)
279 {
280 116 const char *filename_src = h_src->filename;
281 116 const char *filename_dst = h_dst->filename;
282 116 av_strstart(filename_src, "file:", &filename_src);
283 116 av_strstart(filename_dst, "file:", &filename_dst);
284
285
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 116 times.
116 if (rename(filename_src, filename_dst) < 0)
286 return AVERROR(errno);
287
288 116 return 0;
289 }
290
291 126547 static int file_open(URLContext *h, const char *filename, int flags)
292 {
293 126547 FileContext *c = h->priv_data;
294 int access;
295 int fd;
296 struct stat st;
297
298 126547 av_strstart(filename, "file:", &filename);
299
300
3/4
✓ Branch 0 taken 4287 times.
✓ Branch 1 taken 122260 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 4287 times.
126547 if (flags & AVIO_FLAG_WRITE && flags & AVIO_FLAG_READ) {
301 access = O_CREAT | O_RDWR;
302 if (c->trunc)
303 access |= O_TRUNC;
304
2/2
✓ Branch 0 taken 4287 times.
✓ Branch 1 taken 122260 times.
126547 } else if (flags & AVIO_FLAG_WRITE) {
305 4287 access = O_CREAT | O_WRONLY;
306
1/2
✓ Branch 0 taken 4287 times.
✗ Branch 1 not taken.
4287 if (c->trunc)
307 4287 access |= O_TRUNC;
308 } else {
309 122260 access = O_RDONLY;
310 }
311 #ifdef O_BINARY
312 access |= O_BINARY;
313 #endif
314 126547 fd = avpriv_open(filename, access, 0666);
315
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 126546 times.
126547 if (fd == -1)
316 1 return AVERROR(errno);
317 126546 c->fd = fd;
318
319
2/4
✓ Branch 1 taken 126546 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 126546 times.
126546 h->is_streamed = !fstat(fd, &st) && S_ISFIFO(st.st_mode);
320
321
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 126546 times.
126546 if (c->pkt_size) {
322 h->max_packet_size = c->pkt_size;
323 } else {
324 /* Buffer writes more than the default 32k to improve throughput especially
325 * with networked file systems */
326
3/4
✓ Branch 0 taken 126546 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4287 times.
✓ Branch 3 taken 122259 times.
126546 if (!h->is_streamed && flags & AVIO_FLAG_WRITE)
327 4287 h->max_packet_size = 262144;
328 }
329 /* Disable per-packet flushing by default to improve throughput especially
330 * with networked file systems */
331
3/4
✓ Branch 0 taken 126546 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4287 times.
✓ Branch 3 taken 122259 times.
126546 if (!h->is_streamed && flags & AVIO_FLAG_WRITE)
332 4287 h->min_packet_size = h->max_packet_size;
333
334
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 126546 times.
126546 if (c->seekable >= 0)
335 h->is_streamed = !c->seekable;
336
337 126546 return 0;
338 }
339
340 static int file_open_dir(URLContext *h)
341 {
342 #if HAVE_LSTAT
343 FileContext *c = h->priv_data;
344
345 c->dir = opendir(h->filename);
346 if (!c->dir)
347 return AVERROR(errno);
348
349 return 0;
350 #else
351 return AVERROR(ENOSYS);
352 #endif /* HAVE_LSTAT */
353 }
354
355 static int file_read_dir(URLContext *h, AVIODirEntry **next)
356 {
357 #if HAVE_LSTAT
358 FileContext *c = h->priv_data;
359 struct dirent *dir;
360 char *fullpath = NULL;
361
362 *next = ff_alloc_dir_entry();
363 if (!*next)
364 return AVERROR(ENOMEM);
365 do {
366 errno = 0;
367 dir = readdir(c->dir);
368 if (!dir) {
369 av_freep(next);
370 return AVERROR(errno);
371 }
372 } while (!strcmp(dir->d_name, ".") || !strcmp(dir->d_name, ".."));
373
374 fullpath = av_append_path_component(h->filename, dir->d_name);
375 if (fullpath) {
376 struct stat st;
377 if (!lstat(fullpath, &st)) {
378 if (S_ISDIR(st.st_mode))
379 (*next)->type = AVIO_ENTRY_DIRECTORY;
380 else if (S_ISFIFO(st.st_mode))
381 (*next)->type = AVIO_ENTRY_NAMED_PIPE;
382 else if (S_ISCHR(st.st_mode))
383 (*next)->type = AVIO_ENTRY_CHARACTER_DEVICE;
384 else if (S_ISBLK(st.st_mode))
385 (*next)->type = AVIO_ENTRY_BLOCK_DEVICE;
386 else if (S_ISLNK(st.st_mode))
387 (*next)->type = AVIO_ENTRY_SYMBOLIC_LINK;
388 else if (S_ISSOCK(st.st_mode))
389 (*next)->type = AVIO_ENTRY_SOCKET;
390 else if (S_ISREG(st.st_mode))
391 (*next)->type = AVIO_ENTRY_FILE;
392 else
393 (*next)->type = AVIO_ENTRY_UNKNOWN;
394
395 (*next)->group_id = st.st_gid;
396 (*next)->user_id = st.st_uid;
397 (*next)->size = st.st_size;
398 (*next)->filemode = st.st_mode & 0777;
399 (*next)->modification_timestamp = INT64_C(1000000) * st.st_mtime;
400 (*next)->access_timestamp = INT64_C(1000000) * st.st_atime;
401 (*next)->status_change_timestamp = INT64_C(1000000) * st.st_ctime;
402 }
403 av_free(fullpath);
404 }
405
406 (*next)->name = av_strdup(dir->d_name);
407 return 0;
408 #else
409 return AVERROR(ENOSYS);
410 #endif /* HAVE_LSTAT */
411 }
412
413 static int file_close_dir(URLContext *h)
414 {
415 #if HAVE_LSTAT
416 FileContext *c = h->priv_data;
417 closedir(c->dir);
418 return 0;
419 #else
420 return AVERROR(ENOSYS);
421 #endif /* HAVE_LSTAT */
422 }
423
424 const URLProtocol ff_file_protocol = {
425 .name = "file",
426 .url_open = file_open,
427 .url_read = file_read,
428 .url_write = file_write,
429 .url_seek = file_seek,
430 .url_close = file_close,
431 .url_get_file_handle = file_get_handle,
432 .url_check = file_check,
433 .url_delete = file_delete,
434 .url_move = file_move,
435 .priv_data_size = sizeof(FileContext),
436 .priv_data_class = &file_class,
437 .url_open_dir = file_open_dir,
438 .url_read_dir = file_read_dir,
439 .url_close_dir = file_close_dir,
440 .default_whitelist = "file,crypto,data"
441 };
442
443 #endif /* CONFIG_FILE_PROTOCOL */
444
445 #if CONFIG_PIPE_PROTOCOL
446
447 2806 static int pipe_open(URLContext *h, const char *filename, int flags)
448 {
449 2806 FileContext *c = h->priv_data;
450 int fd;
451 char *final;
452
453
1/2
✓ Branch 0 taken 2806 times.
✗ Branch 1 not taken.
2806 if (c->fd < 0) {
454 2806 av_strstart(filename, "pipe:", &filename);
455
456
2/2
✓ Branch 0 taken 2779 times.
✓ Branch 1 taken 27 times.
2806 if (!*filename) {
457
1/2
✓ Branch 0 taken 2779 times.
✗ Branch 1 not taken.
2779 if (flags & AVIO_FLAG_WRITE) {
458 2779 fd = 1;
459 } else {
460 fd = 0;
461 }
462 } else {
463 27 fd = strtol(filename, &final, 10);
464
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
27 if (*final) /* No digits found, or something like 10ab */
465 return AVERROR(EINVAL);
466 }
467 2806 c->fd = fd;
468 }
469
470 2806 c->fd = fd_dup(h, c->fd);
471
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2806 times.
2806 if (c->fd == -1)
472 return AVERROR(errno);
473 2806 h->is_streamed = 1;
474 2806 return 0;
475 }
476
477 const URLProtocol ff_pipe_protocol = {
478 .name = "pipe",
479 .url_open = pipe_open,
480 .url_read = file_read,
481 .url_write = file_write,
482 .url_close = file_close,
483 .url_get_file_handle = file_get_handle,
484 .url_check = file_check,
485 .priv_data_size = sizeof(FileContext),
486 .priv_data_class = &pipe_class,
487 .default_whitelist = "crypto,data"
488 };
489
490 #endif /* CONFIG_PIPE_PROTOCOL */
491
492 #if CONFIG_FD_PROTOCOL
493
494 static int fd_open(URLContext *h, const char *filename, int flags)
495 {
496 FileContext *c = h->priv_data;
497 struct stat st;
498
499 if (strcmp(filename, "fd:") != 0) {
500 av_log(h, AV_LOG_ERROR, "Doesn't support pass file descriptor via URL,"
501 " please set it via -fd {num}\n");
502 return AVERROR(EINVAL);
503 }
504
505 if (c->fd < 0) {
506 if (flags & AVIO_FLAG_WRITE) {
507 c->fd = 1;
508 } else {
509 c->fd = 0;
510 }
511 }
512 if (fstat(c->fd, &st) < 0)
513 return AVERROR(errno);
514 h->is_streamed = !(S_ISREG(st.st_mode) || S_ISBLK(st.st_mode));
515 c->fd = fd_dup(h, c->fd);
516 if (c->fd == -1)
517 return AVERROR(errno);
518
519 return 0;
520 }
521
522 const URLProtocol ff_fd_protocol = {
523 .name = "fd",
524 .url_open = fd_open,
525 .url_read = file_read,
526 .url_write = file_write,
527 .url_seek = file_seek,
528 .url_close = file_close,
529 .url_get_file_handle = file_get_handle,
530 .url_check = file_check,
531 .priv_data_size = sizeof(FileContext),
532 .priv_data_class = &fd_class,
533 .default_whitelist = "crypto,data"
534 };
535
536 #endif /* CONFIG_FD_PROTOCOL */
537
538 #if CONFIG_ANDROID_CONTENT_PROTOCOL
539 #include <jni.h>
540 #include "libavcodec/ffjni.h"
541 #include "libavcodec/jni.h"
542
543 typedef struct JFields {
544 jclass uri_class;
545 jmethodID parse_id;
546
547 jclass context_class;
548 jmethodID get_content_resolver_id;
549
550 jclass content_resolver_class;
551 jmethodID open_file_descriptor_id;
552
553 jclass parcel_file_descriptor_class;
554 jmethodID detach_fd_id;
555 } JFields;
556
557 #define OFFSET(x) offsetof(JFields, x)
558 static const struct FFJniField jfields_mapping[] = {
559 { "android/net/Uri", NULL, NULL, FF_JNI_CLASS, OFFSET(uri_class), 1 },
560 { "android/net/Uri", "parse", "(Ljava/lang/String;)Landroid/net/Uri;", FF_JNI_STATIC_METHOD, OFFSET(parse_id), 1 },
561
562 { "android/content/Context", NULL, NULL, FF_JNI_CLASS, OFFSET(context_class), 1 },
563 { "android/content/Context", "getContentResolver", "()Landroid/content/ContentResolver;", FF_JNI_METHOD, OFFSET(get_content_resolver_id), 1 },
564
565 { "android/content/ContentResolver", NULL, NULL, FF_JNI_CLASS, OFFSET(content_resolver_class), 1 },
566 { "android/content/ContentResolver", "openFileDescriptor", "(Landroid/net/Uri;Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;", FF_JNI_METHOD, OFFSET(open_file_descriptor_id), 1 },
567
568 { "android/os/ParcelFileDescriptor", NULL, NULL, FF_JNI_CLASS, OFFSET(parcel_file_descriptor_class), 1 },
569 { "android/os/ParcelFileDescriptor", "detachFd", "()I", FF_JNI_METHOD, OFFSET(detach_fd_id), 1 },
570
571 { NULL }
572 };
573 #undef OFFSET
574
575 static int android_content_open(URLContext *h, const char *filename, int flags)
576 {
577 FileContext *c = h->priv_data;
578 int fd, ret;
579 struct stat st;
580 const char *mode_str = "r";
581
582 JNIEnv *env;
583 JFields jfields = { 0 };
584 jobject application_context = NULL;
585 jobject url = NULL;
586 jobject mode = NULL;
587 jobject uri = NULL;
588 jobject content_resolver = NULL;
589 jobject parcel_file_descriptor = NULL;
590
591 env = ff_jni_get_env(c);
592 if (!env) {
593 return AVERROR(EINVAL);
594 }
595
596 ret = ff_jni_init_jfields(env, &jfields, jfields_mapping, 0, c);
597 if (ret < 0) {
598 av_log(c, AV_LOG_ERROR, "failed to initialize jni fields\n");
599 return ret;
600 }
601
602 application_context = av_jni_get_android_app_ctx();
603 if (!application_context) {
604 av_log(c, AV_LOG_ERROR, "application context is not set\n");
605 ret = AVERROR_EXTERNAL;
606 goto done;
607 }
608
609 url = ff_jni_utf_chars_to_jstring(env, filename, c);
610 if (!url) {
611 ret = AVERROR_EXTERNAL;
612 goto done;
613 }
614
615 if (flags & AVIO_FLAG_WRITE && flags & AVIO_FLAG_READ)
616 mode_str = "rw";
617 else if (flags & AVIO_FLAG_WRITE)
618 mode_str = "w";
619
620 mode = ff_jni_utf_chars_to_jstring(env, mode_str, c);
621 if (!mode) {
622 ret = AVERROR_EXTERNAL;
623 goto done;
624 }
625
626 uri = (*env)->CallStaticObjectMethod(env, jfields.uri_class, jfields.parse_id, url);
627 ret = ff_jni_exception_check(env, 1, c);
628 if (ret < 0)
629 goto done;
630
631 content_resolver = (*env)->CallObjectMethod(env, application_context, jfields.get_content_resolver_id);
632 ret = ff_jni_exception_check(env, 1, c);
633 if (ret < 0)
634 goto done;
635
636 parcel_file_descriptor = (*env)->CallObjectMethod(env, content_resolver, jfields.open_file_descriptor_id, uri, mode);
637 ret = ff_jni_exception_check(env, 1, c);
638 if (ret < 0)
639 goto done;
640 if (!parcel_file_descriptor) {
641 av_log(c, AV_LOG_ERROR, "file descriptor is null\n");
642 ret = AVERROR_EXTERNAL;
643 goto done;
644 }
645
646 fd = (*env)->CallIntMethod(env, parcel_file_descriptor, jfields.detach_fd_id);
647 ret = ff_jni_exception_check(env, 1, c);
648 if (ret < 0)
649 goto done;
650
651 if (fstat(fd, &st) < 0) {
652 close(fd);
653 return AVERROR(errno);
654 }
655
656 c->fd = fd;
657 h->is_streamed = !(S_ISREG(st.st_mode) || S_ISBLK(st.st_mode));
658
659 done:
660 (*env)->DeleteLocalRef(env, url);
661 (*env)->DeleteLocalRef(env, mode);
662 (*env)->DeleteLocalRef(env, uri);
663 (*env)->DeleteLocalRef(env, content_resolver);
664 (*env)->DeleteLocalRef(env, parcel_file_descriptor);
665 ff_jni_reset_jfields(env, &jfields, jfields_mapping, 0, c);
666
667 return ret;
668 }
669
670 static const AVOption android_content_options[] = {
671 { "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 },
672 { NULL }
673 };
674
675 static const AVClass android_content_class = {
676 .class_name = "android_content",
677 .item_name = av_default_item_name,
678 .option = android_content_options,
679 .version = LIBAVUTIL_VERSION_INT,
680 };
681
682 const URLProtocol ff_android_content_protocol = {
683 .name = "content",
684 .url_open = android_content_open,
685 .url_read = file_read,
686 .url_write = file_write,
687 .url_seek = file_seek,
688 .url_close = file_close,
689 .url_get_file_handle = file_get_handle,
690 .url_check = NULL,
691 .priv_data_size = sizeof(FileContext),
692 .priv_data_class = &android_content_class,
693 };
694
695 #endif /* CONFIG_ANDROID_CONTENT_PROTOCOL */
696