xref: /third_party/ffmpeg/libavformat/cache.c (revision cabdff1a)
1cabdff1aSopenharmony_ci/*
2cabdff1aSopenharmony_ci * Input cache protocol.
3cabdff1aSopenharmony_ci * Copyright (c) 2011,2014 Michael Niedermayer
4cabdff1aSopenharmony_ci *
5cabdff1aSopenharmony_ci * This file is part of FFmpeg.
6cabdff1aSopenharmony_ci *
7cabdff1aSopenharmony_ci * FFmpeg is free software; you can redistribute it and/or
8cabdff1aSopenharmony_ci * modify it under the terms of the GNU Lesser General Public
9cabdff1aSopenharmony_ci * License as published by the Free Software Foundation; either
10cabdff1aSopenharmony_ci * version 2.1 of the License, or (at your option) any later version.
11cabdff1aSopenharmony_ci *
12cabdff1aSopenharmony_ci * FFmpeg is distributed in the hope that it will be useful,
13cabdff1aSopenharmony_ci * but WITHOUT ANY WARRANTY; without even the implied warranty of
14cabdff1aSopenharmony_ci * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15cabdff1aSopenharmony_ci * Lesser General Public License for more details.
16cabdff1aSopenharmony_ci *
17cabdff1aSopenharmony_ci * You should have received a copy of the GNU Lesser General Public
18cabdff1aSopenharmony_ci * License along with FFmpeg; if not, write to the Free Software
19cabdff1aSopenharmony_ci * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20cabdff1aSopenharmony_ci *
21cabdff1aSopenharmony_ci * Based on file.c by Fabrice Bellard
22cabdff1aSopenharmony_ci */
23cabdff1aSopenharmony_ci
24cabdff1aSopenharmony_ci/**
25cabdff1aSopenharmony_ci * @TODO
26cabdff1aSopenharmony_ci *      support keeping files
27cabdff1aSopenharmony_ci *      support filling with a background thread
28cabdff1aSopenharmony_ci */
29cabdff1aSopenharmony_ci
30cabdff1aSopenharmony_ci#include "libavutil/avassert.h"
31cabdff1aSopenharmony_ci#include "libavutil/avstring.h"
32cabdff1aSopenharmony_ci#include "libavutil/internal.h"
33cabdff1aSopenharmony_ci#include "libavutil/opt.h"
34cabdff1aSopenharmony_ci#include "libavutil/tree.h"
35cabdff1aSopenharmony_ci#include "avformat.h"
36cabdff1aSopenharmony_ci#include <fcntl.h>
37cabdff1aSopenharmony_ci#if HAVE_IO_H
38cabdff1aSopenharmony_ci#include <io.h>
39cabdff1aSopenharmony_ci#endif
40cabdff1aSopenharmony_ci#if HAVE_UNISTD_H
41cabdff1aSopenharmony_ci#include <unistd.h>
42cabdff1aSopenharmony_ci#endif
43cabdff1aSopenharmony_ci#include <sys/stat.h>
44cabdff1aSopenharmony_ci#include <stdlib.h>
45cabdff1aSopenharmony_ci#include "os_support.h"
46cabdff1aSopenharmony_ci#include "url.h"
47cabdff1aSopenharmony_ci
48cabdff1aSopenharmony_citypedef struct CacheEntry {
49cabdff1aSopenharmony_ci    int64_t logical_pos;
50cabdff1aSopenharmony_ci    int64_t physical_pos;
51cabdff1aSopenharmony_ci    int size;
52cabdff1aSopenharmony_ci} CacheEntry;
53cabdff1aSopenharmony_ci
54cabdff1aSopenharmony_citypedef struct Context {
55cabdff1aSopenharmony_ci    AVClass *class;
56cabdff1aSopenharmony_ci    int fd;
57cabdff1aSopenharmony_ci    char *filename;
58cabdff1aSopenharmony_ci    struct AVTreeNode *root;
59cabdff1aSopenharmony_ci    int64_t logical_pos;
60cabdff1aSopenharmony_ci    int64_t cache_pos;
61cabdff1aSopenharmony_ci    int64_t inner_pos;
62cabdff1aSopenharmony_ci    int64_t end;
63cabdff1aSopenharmony_ci    int is_true_eof;
64cabdff1aSopenharmony_ci    URLContext *inner;
65cabdff1aSopenharmony_ci    int64_t cache_hit, cache_miss;
66cabdff1aSopenharmony_ci    int read_ahead_limit;
67cabdff1aSopenharmony_ci} Context;
68cabdff1aSopenharmony_ci
69cabdff1aSopenharmony_cistatic int cmp(const void *key, const void *node)
70cabdff1aSopenharmony_ci{
71cabdff1aSopenharmony_ci    return FFDIFFSIGN(*(const int64_t *)key, ((const CacheEntry *) node)->logical_pos);
72cabdff1aSopenharmony_ci}
73cabdff1aSopenharmony_ci
74cabdff1aSopenharmony_cistatic int cache_open(URLContext *h, const char *arg, int flags, AVDictionary **options)
75cabdff1aSopenharmony_ci{
76cabdff1aSopenharmony_ci    int ret;
77cabdff1aSopenharmony_ci    char *buffername;
78cabdff1aSopenharmony_ci    Context *c= h->priv_data;
79cabdff1aSopenharmony_ci
80cabdff1aSopenharmony_ci    av_strstart(arg, "cache:", &arg);
81cabdff1aSopenharmony_ci
82cabdff1aSopenharmony_ci    c->fd = avpriv_tempfile("ffcache", &buffername, 0, h);
83cabdff1aSopenharmony_ci    if (c->fd < 0){
84cabdff1aSopenharmony_ci        av_log(h, AV_LOG_ERROR, "Failed to create tempfile\n");
85cabdff1aSopenharmony_ci        return c->fd;
86cabdff1aSopenharmony_ci    }
87cabdff1aSopenharmony_ci
88cabdff1aSopenharmony_ci    ret = unlink(buffername);
89cabdff1aSopenharmony_ci
90cabdff1aSopenharmony_ci    if (ret >= 0)
91cabdff1aSopenharmony_ci        av_freep(&buffername);
92cabdff1aSopenharmony_ci    else
93cabdff1aSopenharmony_ci        c->filename = buffername;
94cabdff1aSopenharmony_ci
95cabdff1aSopenharmony_ci    return ffurl_open_whitelist(&c->inner, arg, flags, &h->interrupt_callback,
96cabdff1aSopenharmony_ci                                options, h->protocol_whitelist, h->protocol_blacklist, h);
97cabdff1aSopenharmony_ci}
98cabdff1aSopenharmony_ci
99cabdff1aSopenharmony_cistatic int add_entry(URLContext *h, const unsigned char *buf, int size)
100cabdff1aSopenharmony_ci{
101cabdff1aSopenharmony_ci    Context *c= h->priv_data;
102cabdff1aSopenharmony_ci    int64_t pos = -1;
103cabdff1aSopenharmony_ci    int ret;
104cabdff1aSopenharmony_ci    CacheEntry *entry = NULL, *next[2] = {NULL, NULL};
105cabdff1aSopenharmony_ci    CacheEntry *entry_ret;
106cabdff1aSopenharmony_ci    struct AVTreeNode *node = NULL;
107cabdff1aSopenharmony_ci
108cabdff1aSopenharmony_ci    //FIXME avoid lseek
109cabdff1aSopenharmony_ci    pos = lseek(c->fd, 0, SEEK_END);
110cabdff1aSopenharmony_ci    if (pos < 0) {
111cabdff1aSopenharmony_ci        ret = AVERROR(errno);
112cabdff1aSopenharmony_ci        av_log(h, AV_LOG_ERROR, "seek in cache failed\n");
113cabdff1aSopenharmony_ci        goto fail;
114cabdff1aSopenharmony_ci    }
115cabdff1aSopenharmony_ci    c->cache_pos = pos;
116cabdff1aSopenharmony_ci
117cabdff1aSopenharmony_ci    ret = write(c->fd, buf, size);
118cabdff1aSopenharmony_ci    if (ret < 0) {
119cabdff1aSopenharmony_ci        ret = AVERROR(errno);
120cabdff1aSopenharmony_ci        av_log(h, AV_LOG_ERROR, "write in cache failed\n");
121cabdff1aSopenharmony_ci        goto fail;
122cabdff1aSopenharmony_ci    }
123cabdff1aSopenharmony_ci    c->cache_pos += ret;
124cabdff1aSopenharmony_ci
125cabdff1aSopenharmony_ci    entry = av_tree_find(c->root, &c->logical_pos, cmp, (void**)next);
126cabdff1aSopenharmony_ci
127cabdff1aSopenharmony_ci    if (!entry)
128cabdff1aSopenharmony_ci        entry = next[0];
129cabdff1aSopenharmony_ci
130cabdff1aSopenharmony_ci    if (!entry ||
131cabdff1aSopenharmony_ci        entry->logical_pos  + entry->size != c->logical_pos ||
132cabdff1aSopenharmony_ci        entry->physical_pos + entry->size != pos
133cabdff1aSopenharmony_ci    ) {
134cabdff1aSopenharmony_ci        entry = av_malloc(sizeof(*entry));
135cabdff1aSopenharmony_ci        node = av_tree_node_alloc();
136cabdff1aSopenharmony_ci        if (!entry || !node) {
137cabdff1aSopenharmony_ci            ret = AVERROR(ENOMEM);
138cabdff1aSopenharmony_ci            goto fail;
139cabdff1aSopenharmony_ci        }
140cabdff1aSopenharmony_ci        entry->logical_pos = c->logical_pos;
141cabdff1aSopenharmony_ci        entry->physical_pos = pos;
142cabdff1aSopenharmony_ci        entry->size = ret;
143cabdff1aSopenharmony_ci
144cabdff1aSopenharmony_ci        entry_ret = av_tree_insert(&c->root, entry, cmp, &node);
145cabdff1aSopenharmony_ci        if (entry_ret && entry_ret != entry) {
146cabdff1aSopenharmony_ci            ret = -1;
147cabdff1aSopenharmony_ci            av_log(h, AV_LOG_ERROR, "av_tree_insert failed\n");
148cabdff1aSopenharmony_ci            goto fail;
149cabdff1aSopenharmony_ci        }
150cabdff1aSopenharmony_ci    } else
151cabdff1aSopenharmony_ci        entry->size += ret;
152cabdff1aSopenharmony_ci
153cabdff1aSopenharmony_ci    return 0;
154cabdff1aSopenharmony_cifail:
155cabdff1aSopenharmony_ci    //we could truncate the file to pos here if pos >=0 but ftruncate isn't available in VS so
156cabdff1aSopenharmony_ci    //for simplicty we just leave the file a bit larger
157cabdff1aSopenharmony_ci    av_free(entry);
158cabdff1aSopenharmony_ci    av_free(node);
159cabdff1aSopenharmony_ci    return ret;
160cabdff1aSopenharmony_ci}
161cabdff1aSopenharmony_ci
162cabdff1aSopenharmony_cistatic int cache_read(URLContext *h, unsigned char *buf, int size)
163cabdff1aSopenharmony_ci{
164cabdff1aSopenharmony_ci    Context *c= h->priv_data;
165cabdff1aSopenharmony_ci    CacheEntry *entry, *next[2] = {NULL, NULL};
166cabdff1aSopenharmony_ci    int64_t r;
167cabdff1aSopenharmony_ci
168cabdff1aSopenharmony_ci    entry = av_tree_find(c->root, &c->logical_pos, cmp, (void**)next);
169cabdff1aSopenharmony_ci
170cabdff1aSopenharmony_ci    if (!entry)
171cabdff1aSopenharmony_ci        entry = next[0];
172cabdff1aSopenharmony_ci
173cabdff1aSopenharmony_ci    if (entry) {
174cabdff1aSopenharmony_ci        int64_t in_block_pos = c->logical_pos - entry->logical_pos;
175cabdff1aSopenharmony_ci        av_assert0(entry->logical_pos <= c->logical_pos);
176cabdff1aSopenharmony_ci        if (in_block_pos < entry->size) {
177cabdff1aSopenharmony_ci            int64_t physical_target = entry->physical_pos + in_block_pos;
178cabdff1aSopenharmony_ci
179cabdff1aSopenharmony_ci            if (c->cache_pos != physical_target) {
180cabdff1aSopenharmony_ci                r = lseek(c->fd, physical_target, SEEK_SET);
181cabdff1aSopenharmony_ci            } else
182cabdff1aSopenharmony_ci                r = c->cache_pos;
183cabdff1aSopenharmony_ci
184cabdff1aSopenharmony_ci            if (r >= 0) {
185cabdff1aSopenharmony_ci                c->cache_pos = r;
186cabdff1aSopenharmony_ci                r = read(c->fd, buf, FFMIN(size, entry->size - in_block_pos));
187cabdff1aSopenharmony_ci            }
188cabdff1aSopenharmony_ci
189cabdff1aSopenharmony_ci            if (r > 0) {
190cabdff1aSopenharmony_ci                c->cache_pos += r;
191cabdff1aSopenharmony_ci                c->logical_pos += r;
192cabdff1aSopenharmony_ci                c->cache_hit ++;
193cabdff1aSopenharmony_ci                return r;
194cabdff1aSopenharmony_ci            }
195cabdff1aSopenharmony_ci        }
196cabdff1aSopenharmony_ci    }
197cabdff1aSopenharmony_ci
198cabdff1aSopenharmony_ci    // Cache miss or some kind of fault with the cache
199cabdff1aSopenharmony_ci
200cabdff1aSopenharmony_ci    if (c->logical_pos != c->inner_pos) {
201cabdff1aSopenharmony_ci        r = ffurl_seek(c->inner, c->logical_pos, SEEK_SET);
202cabdff1aSopenharmony_ci        if (r<0) {
203cabdff1aSopenharmony_ci            av_log(h, AV_LOG_ERROR, "Failed to perform internal seek\n");
204cabdff1aSopenharmony_ci            return r;
205cabdff1aSopenharmony_ci        }
206cabdff1aSopenharmony_ci        c->inner_pos = r;
207cabdff1aSopenharmony_ci    }
208cabdff1aSopenharmony_ci
209cabdff1aSopenharmony_ci    r = ffurl_read(c->inner, buf, size);
210cabdff1aSopenharmony_ci    if (r == AVERROR_EOF && size>0) {
211cabdff1aSopenharmony_ci        c->is_true_eof = 1;
212cabdff1aSopenharmony_ci        av_assert0(c->end >= c->logical_pos);
213cabdff1aSopenharmony_ci    }
214cabdff1aSopenharmony_ci    if (r<=0)
215cabdff1aSopenharmony_ci        return r;
216cabdff1aSopenharmony_ci    c->inner_pos += r;
217cabdff1aSopenharmony_ci
218cabdff1aSopenharmony_ci    c->cache_miss ++;
219cabdff1aSopenharmony_ci
220cabdff1aSopenharmony_ci    add_entry(h, buf, r);
221cabdff1aSopenharmony_ci    c->logical_pos += r;
222cabdff1aSopenharmony_ci    c->end = FFMAX(c->end, c->logical_pos);
223cabdff1aSopenharmony_ci
224cabdff1aSopenharmony_ci    return r;
225cabdff1aSopenharmony_ci}
226cabdff1aSopenharmony_ci
227cabdff1aSopenharmony_cistatic int64_t cache_seek(URLContext *h, int64_t pos, int whence)
228cabdff1aSopenharmony_ci{
229cabdff1aSopenharmony_ci    Context *c= h->priv_data;
230cabdff1aSopenharmony_ci    int64_t ret;
231cabdff1aSopenharmony_ci
232cabdff1aSopenharmony_ci    if (whence == AVSEEK_SIZE) {
233cabdff1aSopenharmony_ci        pos= ffurl_seek(c->inner, pos, whence);
234cabdff1aSopenharmony_ci        if(pos <= 0){
235cabdff1aSopenharmony_ci            pos= ffurl_seek(c->inner, -1, SEEK_END);
236cabdff1aSopenharmony_ci            if (ffurl_seek(c->inner, c->inner_pos, SEEK_SET) < 0)
237cabdff1aSopenharmony_ci                av_log(h, AV_LOG_ERROR, "Inner protocol failed to seekback end : %"PRId64"\n", pos);
238cabdff1aSopenharmony_ci        }
239cabdff1aSopenharmony_ci        if (pos > 0)
240cabdff1aSopenharmony_ci            c->is_true_eof = 1;
241cabdff1aSopenharmony_ci        c->end = FFMAX(c->end, pos);
242cabdff1aSopenharmony_ci        return pos;
243cabdff1aSopenharmony_ci    }
244cabdff1aSopenharmony_ci
245cabdff1aSopenharmony_ci    if (whence == SEEK_CUR) {
246cabdff1aSopenharmony_ci        whence = SEEK_SET;
247cabdff1aSopenharmony_ci        pos += c->logical_pos;
248cabdff1aSopenharmony_ci    } else if (whence == SEEK_END && c->is_true_eof) {
249cabdff1aSopenharmony_ciresolve_eof:
250cabdff1aSopenharmony_ci        whence = SEEK_SET;
251cabdff1aSopenharmony_ci        pos += c->end;
252cabdff1aSopenharmony_ci    }
253cabdff1aSopenharmony_ci
254cabdff1aSopenharmony_ci    if (whence == SEEK_SET && pos >= 0 && pos < c->end) {
255cabdff1aSopenharmony_ci        //Seems within filesize, assume it will not fail.
256cabdff1aSopenharmony_ci        c->logical_pos = pos;
257cabdff1aSopenharmony_ci        return pos;
258cabdff1aSopenharmony_ci    }
259cabdff1aSopenharmony_ci
260cabdff1aSopenharmony_ci    //cache miss
261cabdff1aSopenharmony_ci    ret= ffurl_seek(c->inner, pos, whence);
262cabdff1aSopenharmony_ci    if ((whence == SEEK_SET && pos >= c->logical_pos ||
263cabdff1aSopenharmony_ci         whence == SEEK_END && pos <= 0) && ret < 0) {
264cabdff1aSopenharmony_ci        if (   (whence == SEEK_SET && c->read_ahead_limit >= pos - c->logical_pos)
265cabdff1aSopenharmony_ci            || c->read_ahead_limit < 0) {
266cabdff1aSopenharmony_ci            uint8_t tmp[32768];
267cabdff1aSopenharmony_ci            while (c->logical_pos < pos || whence == SEEK_END) {
268cabdff1aSopenharmony_ci                int size = sizeof(tmp);
269cabdff1aSopenharmony_ci                if (whence == SEEK_SET)
270cabdff1aSopenharmony_ci                    size = FFMIN(sizeof(tmp), pos - c->logical_pos);
271cabdff1aSopenharmony_ci                ret = cache_read(h, tmp, size);
272cabdff1aSopenharmony_ci                if (ret == AVERROR_EOF && whence == SEEK_END) {
273cabdff1aSopenharmony_ci                    av_assert0(c->is_true_eof);
274cabdff1aSopenharmony_ci                    goto resolve_eof;
275cabdff1aSopenharmony_ci                }
276cabdff1aSopenharmony_ci                if (ret < 0) {
277cabdff1aSopenharmony_ci                    return ret;
278cabdff1aSopenharmony_ci                }
279cabdff1aSopenharmony_ci            }
280cabdff1aSopenharmony_ci            return c->logical_pos;
281cabdff1aSopenharmony_ci        }
282cabdff1aSopenharmony_ci    }
283cabdff1aSopenharmony_ci
284cabdff1aSopenharmony_ci    if (ret >= 0) {
285cabdff1aSopenharmony_ci        c->logical_pos = ret;
286cabdff1aSopenharmony_ci        c->end = FFMAX(c->end, ret);
287cabdff1aSopenharmony_ci    }
288cabdff1aSopenharmony_ci
289cabdff1aSopenharmony_ci    return ret;
290cabdff1aSopenharmony_ci}
291cabdff1aSopenharmony_ci
292cabdff1aSopenharmony_cistatic int enu_free(void *opaque, void *elem)
293cabdff1aSopenharmony_ci{
294cabdff1aSopenharmony_ci    av_free(elem);
295cabdff1aSopenharmony_ci    return 0;
296cabdff1aSopenharmony_ci}
297cabdff1aSopenharmony_ci
298cabdff1aSopenharmony_cistatic int cache_close(URLContext *h)
299cabdff1aSopenharmony_ci{
300cabdff1aSopenharmony_ci    Context *c= h->priv_data;
301cabdff1aSopenharmony_ci    int ret;
302cabdff1aSopenharmony_ci
303cabdff1aSopenharmony_ci    av_log(h, AV_LOG_INFO, "Statistics, cache hits:%"PRId64" cache misses:%"PRId64"\n",
304cabdff1aSopenharmony_ci           c->cache_hit, c->cache_miss);
305cabdff1aSopenharmony_ci
306cabdff1aSopenharmony_ci    close(c->fd);
307cabdff1aSopenharmony_ci    if (c->filename) {
308cabdff1aSopenharmony_ci        ret = unlink(c->filename);
309cabdff1aSopenharmony_ci        if (ret < 0)
310cabdff1aSopenharmony_ci            av_log(h, AV_LOG_ERROR, "Could not delete %s.\n", c->filename);
311cabdff1aSopenharmony_ci        av_freep(&c->filename);
312cabdff1aSopenharmony_ci    }
313cabdff1aSopenharmony_ci    ffurl_closep(&c->inner);
314cabdff1aSopenharmony_ci    av_tree_enumerate(c->root, NULL, NULL, enu_free);
315cabdff1aSopenharmony_ci    av_tree_destroy(c->root);
316cabdff1aSopenharmony_ci
317cabdff1aSopenharmony_ci    return 0;
318cabdff1aSopenharmony_ci}
319cabdff1aSopenharmony_ci
320cabdff1aSopenharmony_ci#define OFFSET(x) offsetof(Context, x)
321cabdff1aSopenharmony_ci#define D AV_OPT_FLAG_DECODING_PARAM
322cabdff1aSopenharmony_ci
323cabdff1aSopenharmony_cistatic const AVOption options[] = {
324cabdff1aSopenharmony_ci    { "read_ahead_limit", "Amount in bytes that may be read ahead when seeking isn't supported, -1 for unlimited", OFFSET(read_ahead_limit), AV_OPT_TYPE_INT, { .i64 = 65536 }, -1, INT_MAX, D },
325cabdff1aSopenharmony_ci    {NULL},
326cabdff1aSopenharmony_ci};
327cabdff1aSopenharmony_ci
328cabdff1aSopenharmony_cistatic const AVClass cache_context_class = {
329cabdff1aSopenharmony_ci    .class_name = "cache",
330cabdff1aSopenharmony_ci    .item_name  = av_default_item_name,
331cabdff1aSopenharmony_ci    .option     = options,
332cabdff1aSopenharmony_ci    .version    = LIBAVUTIL_VERSION_INT,
333cabdff1aSopenharmony_ci};
334cabdff1aSopenharmony_ci
335cabdff1aSopenharmony_ciconst URLProtocol ff_cache_protocol = {
336cabdff1aSopenharmony_ci    .name                = "cache",
337cabdff1aSopenharmony_ci    .url_open2           = cache_open,
338cabdff1aSopenharmony_ci    .url_read            = cache_read,
339cabdff1aSopenharmony_ci    .url_seek            = cache_seek,
340cabdff1aSopenharmony_ci    .url_close           = cache_close,
341cabdff1aSopenharmony_ci    .priv_data_size      = sizeof(Context),
342cabdff1aSopenharmony_ci    .priv_data_class     = &cache_context_class,
343cabdff1aSopenharmony_ci};
344