2011-10-16 16:02:35 +02:00
/*
* Input cache protocol.
2014-12-25 18:48:05 +01:00
* Copyright (c) 2011,2014 Michael Niedermayer
2011-10-16 16:02:35 +02:00
*
* 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
*
* Based on file.c by Fabrice Bellard
*/
2011-10-16 17:31:33 +02:00
/**
* @TODO
* support keeping files
* support filling with a background thread
*/
2011-10-16 16:02:35 +02:00
#include "libavutil/avassert.h"
#include "libavutil/avstring.h"
2022-08-27 23:33:06 +02:00
#include "libavutil/file_open.h"
2024-03-25 01:30:37 +01:00
#include "libavutil/mem.h"
2014-12-25 21:39:51 +01:00
#include "libavutil/opt.h"
2014-12-25 18:48:05 +01:00
#include "libavutil/tree.h"
2022-08-27 23:33:06 +02:00
#include "avio.h"
2011-10-16 16:02:35 +02:00
#include <fcntl.h>
2012-09-07 13:20:42 -04:00
#if HAVE_IO_H
2011-10-16 16:02:35 +02:00
#include <io.h>
#endif
2012-09-07 13:20:42 -04:00
#if HAVE_UNISTD_H
2011-10-16 16:02:35 +02:00
#include <unistd.h>
2012-09-07 13:20:42 -04:00
#endif
2011-10-16 16:02:35 +02:00
#include <sys/stat.h>
#include <stdlib.h>
#include "os_support.h"
#include "url.h"
2014-12-25 18:48:05 +01:00
typedef struct CacheEntry {
int64_t logical_pos ;
int64_t physical_pos ;
int size ;
} CacheEntry ;
2024-05-22 17:18:35 +02:00
typedef struct CacheContext {
2014-12-25 21:39:51 +01:00
AVClass * class ;
2011-10-16 16:02:35 +02:00
int fd ;
2019-05-22 18:05:04 +05:30
char * filename ;
2014-12-25 18:48:05 +01:00
struct AVTreeNode * root ;
int64_t logical_pos ;
int64_t cache_pos ;
int64_t inner_pos ;
2011-10-16 16:02:35 +02:00
int64_t end ;
2014-12-25 21:27:04 +01:00
int is_true_eof ;
2011-10-16 16:02:35 +02:00
URLContext * inner ;
2014-12-25 18:48:05 +01:00
int64_t cache_hit , cache_miss ;
2014-12-25 21:39:51 +01:00
int read_ahead_limit ;
2024-05-22 17:18:35 +02:00
} CacheContext ;
2011-10-16 16:02:35 +02:00
2015-10-24 17:53:21 -04:00
static int cmp ( const void * key , const void * node )
2014-12-25 18:48:05 +01:00
{
2015-11-08 16:35:01 -08:00
return FFDIFFSIGN ( * ( const int64_t * ) key , (( const CacheEntry * ) node ) -> logical_pos );
2014-12-25 18:48:05 +01:00
}
2015-01-31 13:35:04 +08:00
static int cache_open ( URLContext * h , const char * arg , int flags , AVDictionary ** options )
2011-10-16 16:02:35 +02:00
{
2024-05-22 17:18:35 +02:00
CacheContext * c = h -> priv_data ;
2019-05-22 18:05:04 +05:30
int ret ;
2012-02-06 01:02:45 +01:00
char * buffername ;
2011-10-16 16:02:35 +02:00
av_strstart ( arg , "cache:" , & arg );
2016-03-09 15:28:28 +01:00
c -> fd = avpriv_tempfile ( "ffcache" , & buffername , 0 , h );
2011-10-16 16:02:35 +02:00
if ( c -> fd < 0 ){
av_log ( h , AV_LOG_ERROR , "Failed to create tempfile \n " );
return c -> fd ;
}
2019-05-22 18:05:04 +05:30
ret = unlink ( buffername );
if ( ret >= 0 )
av_freep ( & buffername );
else
c -> filename = buffername ;
2011-10-16 16:02:35 +02:00
2016-01-30 02:17:51 +01:00
return ffurl_open_whitelist ( & c -> inner , arg , flags , & h -> interrupt_callback ,
2016-04-21 15:55:09 +01:00
options , h -> protocol_whitelist , h -> protocol_blacklist , h );
2011-10-16 16:02:35 +02:00
}
2014-12-25 18:48:05 +01:00
static int add_entry ( URLContext * h , const unsigned char * buf , int size )
{
2024-05-22 17:18:35 +02:00
CacheContext * c = h -> priv_data ;
2014-12-26 01:13:49 +01:00
int64_t pos = - 1 ;
2014-12-25 18:48:05 +01:00
int ret ;
2014-12-26 01:36:00 +01:00
CacheEntry * entry = NULL , * next [ 2 ] = { NULL , NULL };
2014-12-25 18:48:05 +01:00
CacheEntry * entry_ret ;
2014-12-26 01:36:00 +01:00
struct AVTreeNode * node = NULL ;
2014-12-25 18:48:05 +01:00
//FIXME avoid lseek
pos = lseek ( c -> fd , 0 , SEEK_END );
if ( pos < 0 ) {
ret = AVERROR ( errno );
av_log ( h , AV_LOG_ERROR , "seek in cache failed \n " );
goto fail ;
}
2014-12-26 01:22:52 +01:00
c -> cache_pos = pos ;
2014-12-25 18:48:05 +01:00
ret = write ( c -> fd , buf , size );
if ( ret < 0 ) {
ret = AVERROR ( errno );
av_log ( h , AV_LOG_ERROR , "write in cache failed \n " );
goto fail ;
}
2014-12-26 01:36:00 +01:00
c -> cache_pos += ret ;
2014-12-25 18:48:05 +01:00
2014-12-26 01:36:00 +01:00
entry = av_tree_find ( c -> root , & c -> logical_pos , cmp , ( void ** ) next );
2014-12-25 18:48:05 +01:00
2014-12-26 01:36:00 +01:00
if ( ! entry )
entry = next [ 0 ];
if ( ! entry ||
entry -> logical_pos + entry -> size != c -> logical_pos ||
entry -> physical_pos + entry -> size != pos
) {
entry = av_malloc ( sizeof ( * entry ));
node = av_tree_node_alloc ();
if ( ! entry || ! node ) {
ret = AVERROR ( ENOMEM );
goto fail ;
}
entry -> logical_pos = c -> logical_pos ;
entry -> physical_pos = pos ;
entry -> size = ret ;
entry_ret = av_tree_insert ( & c -> root , entry , cmp , & node );
if ( entry_ret && entry_ret != entry ) {
ret = - 1 ;
av_log ( h , AV_LOG_ERROR , "av_tree_insert failed \n " );
goto fail ;
}
} else
entry -> size += ret ;
2014-12-25 18:48:05 +01:00
return 0 ;
fail :
2015-03-06 20:26:32 +01:00
//we could truncate the file to pos here if pos >=0 but ftruncate isn't available in VS so
2025-08-01 22:43:23 +02:00
//for simplicity we just leave the file a bit larger
2014-12-25 18:48:05 +01:00
av_free ( entry );
av_free ( node );
return ret ;
}
2011-10-16 16:02:35 +02:00
static int cache_read ( URLContext * h , unsigned char * buf , int size )
{
2024-05-22 17:18:35 +02:00
CacheContext * c = h -> priv_data ;
2014-12-25 18:48:05 +01:00
CacheEntry * entry , * next [ 2 ] = { NULL , NULL };
2015-11-02 10:20:39 -08:00
int64_t r ;
2011-10-16 16:02:35 +02:00
2014-12-25 18:48:05 +01:00
entry = av_tree_find ( c -> root , & c -> logical_pos , cmp , ( void ** ) next );
if ( ! entry )
entry = next [ 0 ];
if ( entry ) {
int64_t in_block_pos = c -> logical_pos - entry -> logical_pos ;
av_assert0 ( entry -> logical_pos <= c -> logical_pos );
if ( in_block_pos < entry -> size ) {
int64_t physical_target = entry -> physical_pos + in_block_pos ;
2014-12-26 01:23:54 +01:00
if ( c -> cache_pos != physical_target ) {
r = lseek ( c -> fd , physical_target , SEEK_SET );
} else
r = c -> cache_pos ;
2014-12-26 01:22:52 +01:00
if ( r >= 0 ) {
c -> cache_pos = r ;
2014-12-25 18:48:05 +01:00
r = read ( c -> fd , buf , FFMIN ( size , entry -> size - in_block_pos ));
2014-12-26 01:22:52 +01:00
}
2014-12-25 18:48:05 +01:00
if ( r > 0 ) {
2014-12-26 01:22:52 +01:00
c -> cache_pos += r ;
2014-12-25 18:48:05 +01:00
c -> logical_pos += r ;
c -> cache_hit ++ ;
return r ;
}
2011-10-16 16:02:35 +02:00
}
}
2014-12-25 18:48:05 +01:00
// Cache miss or some kind of fault with the cache
if ( c -> logical_pos != c -> inner_pos ) {
r = ffurl_seek ( c -> inner , c -> logical_pos , SEEK_SET );
if ( r < 0 ) {
av_log ( h , AV_LOG_ERROR , "Failed to perform internal seek \n " );
return r ;
}
c -> inner_pos = r ;
}
r = ffurl_read ( c -> inner , buf , size );
2017-10-17 10:29:30 +02:00
if ( r == AVERROR_EOF && size > 0 ) {
2014-12-25 21:27:04 +01:00
c -> is_true_eof = 1 ;
av_assert0 ( c -> end >= c -> logical_pos );
}
2014-12-25 18:48:05 +01:00
if ( r <= 0 )
return r ;
c -> inner_pos += r ;
c -> cache_miss ++ ;
add_entry ( h , buf , r );
c -> logical_pos += r ;
c -> end = FFMAX ( c -> end , c -> logical_pos );
return r ;
2011-10-16 16:02:35 +02:00
}
static int64_t cache_seek ( URLContext * h , int64_t pos , int whence )
{
2024-05-22 17:18:35 +02:00
CacheContext * c = h -> priv_data ;
2014-12-25 21:30:10 +01:00
int64_t ret ;
2011-10-16 16:02:35 +02:00
if ( whence == AVSEEK_SIZE ) {
2011-10-16 16:54:27 +02:00
pos = ffurl_seek ( c -> inner , pos , whence );
if ( pos <= 0 ){
pos = ffurl_seek ( c -> inner , - 1 , SEEK_END );
2014-12-25 18:48:05 +01:00
if ( ffurl_seek ( c -> inner , c -> inner_pos , SEEK_SET ) < 0 )
2014-12-25 21:28:27 +01:00
av_log ( h , AV_LOG_ERROR , "Inner protocol failed to seekback end : %" PRId64 " \n " , pos );
2011-10-16 16:54:27 +02:00
}
2014-12-25 21:27:04 +01:00
if ( pos > 0 )
c -> is_true_eof = 1 ;
2014-12-25 18:48:05 +01:00
c -> end = FFMAX ( c -> end , pos );
2011-10-16 16:54:27 +02:00
return pos ;
2011-10-16 16:02:35 +02:00
}
2014-12-25 18:48:05 +01:00
if ( whence == SEEK_CUR ) {
whence = SEEK_SET ;
pos += c -> logical_pos ;
2014-12-25 21:27:04 +01:00
} else if ( whence == SEEK_END && c -> is_true_eof ) {
2014-12-25 21:39:51 +01:00
resolve_eof :
2014-12-25 21:27:04 +01:00
whence = SEEK_SET ;
pos += c -> end ;
2011-10-16 16:02:35 +02:00
}
2014-12-25 18:48:05 +01:00
if ( whence == SEEK_SET && pos >= 0 && pos < c -> end ) {
//Seems within filesize, assume it will not fail.
c -> logical_pos = pos ;
return pos ;
}
//cache miss
2014-12-25 21:30:10 +01:00
ret = ffurl_seek ( c -> inner , pos , whence );
2014-12-25 21:39:51 +01:00
if (( whence == SEEK_SET && pos >= c -> logical_pos ||
whence == SEEK_END && pos <= 0 ) && ret < 0 ) {
if ( ( whence == SEEK_SET && c -> read_ahead_limit >= pos - c -> logical_pos )
|| c -> read_ahead_limit < 0 ) {
uint8_t tmp [ 32768 ];
while ( c -> logical_pos < pos || whence == SEEK_END ) {
int size = sizeof ( tmp );
if ( whence == SEEK_SET )
size = FFMIN ( sizeof ( tmp ), pos - c -> logical_pos );
ret = cache_read ( h , tmp , size );
2017-10-17 10:29:30 +02:00
if ( ret == AVERROR_EOF && whence == SEEK_END ) {
2014-12-25 21:39:51 +01:00
av_assert0 ( c -> is_true_eof );
goto resolve_eof ;
}
if ( ret < 0 ) {
return ret ;
}
}
return c -> logical_pos ;
}
}
2014-12-25 21:30:10 +01:00
if ( ret >= 0 ) {
c -> logical_pos = ret ;
c -> end = FFMAX ( c -> end , ret );
2014-12-25 18:48:05 +01:00
}
2014-12-25 21:30:10 +01:00
return ret ;
2011-10-16 16:02:35 +02:00
}
2016-03-02 14:09:23 +01:00
static int enu_free ( void * opaque , void * elem )
{
av_free ( elem );
return 0 ;
}
2011-10-16 16:02:35 +02:00
static int cache_close ( URLContext * h )
{
2024-05-22 17:18:35 +02:00
CacheContext * c = h -> priv_data ;
2019-05-22 18:05:04 +05:30
int ret ;
2014-12-25 18:48:05 +01:00
av_log ( h , AV_LOG_INFO , "Statistics, cache hits:%" PRId64 " cache misses:%" PRId64 " \n " ,
c -> cache_hit , c -> cache_miss );
2011-10-16 16:02:35 +02:00
close ( c -> fd );
2019-05-22 18:05:04 +05:30
if ( c -> filename ) {
ret = unlink ( c -> filename );
if ( ret < 0 )
av_log ( h , AV_LOG_ERROR , "Could not delete %s. \n " , c -> filename );
av_freep ( & c -> filename );
}
2020-04-03 17:03:38 +02:00
ffurl_closep ( & c -> inner );
2016-03-02 14:09:23 +01:00
av_tree_enumerate ( c -> root , NULL , NULL , enu_free );
2014-12-25 18:48:05 +01:00
av_tree_destroy ( c -> root );
2011-10-16 16:02:35 +02:00
return 0 ;
}
2024-05-22 17:18:35 +02:00
#define OFFSET(x) offsetof(CacheContext, x)
2014-12-25 21:39:51 +01:00
#define D AV_OPT_FLAG_DECODING_PARAM
static const AVOption options [] = {
2015-03-06 20:26:32 +01:00
{ "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 },
2014-12-25 21:39:51 +01:00
{ NULL },
};
static const AVClass cache_context_class = {
2019-12-04 17:17:18 +08:00
. class_name = "cache" ,
2024-01-19 13:33:28 +01:00
. item_name = av_default_item_name ,
2014-12-25 21:39:51 +01:00
. option = options ,
. version = LIBAVUTIL_VERSION_INT ,
};
2016-02-29 16:50:39 +00:00
const URLProtocol ff_cache_protocol = {
2011-10-16 16:02:35 +02:00
. name = "cache" ,
2015-01-31 13:35:04 +08:00
. url_open2 = cache_open ,
2011-10-16 16:02:35 +02:00
. url_read = cache_read ,
. url_seek = cache_seek ,
. url_close = cache_close ,
2024-05-22 17:18:35 +02:00
. priv_data_size = sizeof ( CacheContext ),
2014-12-25 21:39:51 +01:00
. priv_data_class = & cache_context_class ,
2011-10-16 16:02:35 +02:00
};