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