Commit fa43c146 authored by Laurent Aimar's avatar Laurent Aimar

Fixed seeking and skipping behavior in stream_t layer.

Now seeking and skipping behave exactly the same.
It also allows forward seeking in non seekable stream by reading the data.
It closes #2994.
As it is a critical section of code, it must be carefully tested.
parent 670d5486
...@@ -1040,27 +1040,13 @@ static int AStreamRefillBlock( stream_t *s ) ...@@ -1040,27 +1040,13 @@ static int AStreamRefillBlock( stream_t *s )
* Method 2: * Method 2:
****************************************************************************/ ****************************************************************************/
static int AStreamRefillStream( stream_t *s ); static int AStreamRefillStream( stream_t *s );
static int AStreamReadNoSeekStream( stream_t *s, void *p_read, unsigned int i_read );
static int AStreamReadStream( stream_t *s, void *p_read, unsigned int i_read ) static int AStreamReadStream( stream_t *s, void *p_read, unsigned int i_read )
{ {
stream_sys_t *p_sys = s->p_sys; stream_sys_t *p_sys = s->p_sys;
stream_track_t *tk = &p_sys->stream.tk[p_sys->stream.i_tk];
uint8_t *p_data = (uint8_t *)p_read;
unsigned int i_data = 0;
if( tk->i_start >= tk->i_end )
return 0; /* EOF */
if( p_data == NULL ) if( !p_read )
{
/* seek within this stream if possible, else use plain old read and discard */
stream_sys_t *p_sys = s->p_sys;
access_t *p_access = p_sys->p_access;
bool b_aseek;
access_Control( p_access, ACCESS_CAN_SEEK, &b_aseek );
if( b_aseek )
{ {
const int64_t i_pos_wanted = p_sys->i_pos + i_read; const int64_t i_pos_wanted = p_sys->i_pos + i_read;
...@@ -1071,60 +1057,7 @@ static int AStreamReadStream( stream_t *s, void *p_read, unsigned int i_read ) ...@@ -1071,60 +1057,7 @@ static int AStreamReadStream( stream_t *s, void *p_read, unsigned int i_read )
} }
return i_read; return i_read;
} }
} return AStreamReadNoSeekStream( s, p_read, i_read );
#ifdef STREAM_DEBUG
msg_Dbg( s, "AStreamReadStream: %d pos=%"PRId64" tk=%d start=%"PRId64
" offset=%d end=%"PRId64,
i_read, p_sys->i_pos, p_sys->stream.i_tk,
tk->i_start, p_sys->stream.i_offset, tk->i_end );
#endif
while( i_data < i_read )
{
int i_off = (tk->i_start + p_sys->stream.i_offset) %
STREAM_CACHE_TRACK_SIZE;
unsigned int i_current =
__MAX(0,__MIN( tk->i_end - tk->i_start - p_sys->stream.i_offset,
STREAM_CACHE_TRACK_SIZE - i_off ));
int i_copy = __MIN( i_current, i_read - i_data );
if( i_copy <= 0 ) break; /* EOF */
/* Copy data */
/* msg_Dbg( s, "AStreamReadStream: copy %d", i_copy ); */
if( p_data )
{
memcpy( p_data, &tk->p_buffer[i_off], i_copy );
p_data += i_copy;
}
i_data += i_copy;
p_sys->stream.i_offset += i_copy;
/* Update pos now */
p_sys->i_pos += i_copy;
/* */
p_sys->stream.i_used += i_copy;
if( tk->i_end - tk->i_start - p_sys->stream.i_offset <= i_read -i_data )
{
const int i_read_requested = __MAX( __MIN( i_read - i_data,
STREAM_READ_ATONCE * 10 ),
STREAM_READ_ATONCE / 2 );
if( p_sys->stream.i_used < i_read_requested )
p_sys->stream.i_used = i_read_requested;
if( AStreamRefillStream( s ) )
{
/* EOF */
if( tk->i_start >= tk->i_end ) break;
}
}
}
return i_data;
} }
static int AStreamPeekStream( stream_t *s, const uint8_t **pp_peek, unsigned int i_read ) static int AStreamPeekStream( stream_t *s, const uint8_t **pp_peek, unsigned int i_read )
...@@ -1191,143 +1124,216 @@ static int AStreamPeekStream( stream_t *s, const uint8_t **pp_peek, unsigned int ...@@ -1191,143 +1124,216 @@ static int AStreamPeekStream( stream_t *s, const uint8_t **pp_peek, unsigned int
static int AStreamSeekStream( stream_t *s, int64_t i_pos ) static int AStreamSeekStream( stream_t *s, int64_t i_pos )
{ {
stream_sys_t *p_sys = s->p_sys; stream_sys_t *p_sys = s->p_sys;
stream_track_t *p_current = &p_sys->stream.tk[p_sys->stream.i_tk];
access_t *p_access = p_sys->p_access; access_t *p_access = p_sys->p_access;
bool b_aseek;
bool b_afastseek; if( p_current->i_start >= p_current->i_end && i_pos >= p_current->i_end )
int i_maxth; return 0; /* EOF */
int i_new;
int i;
#ifdef STREAM_DEBUG #ifdef STREAM_DEBUG
msg_Dbg( s, "AStreamSeekStream: to %"PRId64" pos=%"PRId64 msg_Dbg( s, "AStreamSeekStream: to %"PRId64" pos=%"PRId64
" tk=%d start=%"PRId64" offset=%d end=%"PRId64, " tk=%d start=%"PRId64" offset=%d end=%"PRId64,
i_pos, p_sys->i_pos, p_sys->stream.i_tk, i_pos, p_sys->i_pos, p_sys->stream.i_tk,
p_sys->stream.tk[p_sys->stream.i_tk].i_start, p_current->i_start,
p_sys->stream.i_offset, p_sys->stream.i_offset,
p_sys->stream.tk[p_sys->stream.i_tk].i_end ); p_current->i_end );
#endif #endif
bool b_aseek;
/* Seek in our current track ? */ access_Control( p_access, ACCESS_CAN_SEEK, &b_aseek );
if( i_pos >= p_sys->stream.tk[p_sys->stream.i_tk].i_start && if( !b_aseek && i_pos < p_current->i_start )
i_pos < p_sys->stream.tk[p_sys->stream.i_tk].i_end )
{ {
stream_track_t *tk = &p_sys->stream.tk[p_sys->stream.i_tk]; msg_Warn( s, "AStreamSeekStream: can't seek" );
#ifdef STREAM_DEBUG return VLC_EGENERIC;
msg_Dbg( s, "AStreamSeekStream: current track" ); }
#endif
p_sys->i_pos = i_pos;
p_sys->stream.i_offset = i_pos - tk->i_start;
/* If there is not enough data left in the track, refill */ bool b_afastseek;
/* \todo How to get a correct value for access_Control( p_access, ACCESS_CAN_SEEK, &b_afastseek );
* - refilling threshold
* - how much to refill /* FIXME compute seek cost (instead of static 'stupid' value) */
*/ int64_t i_skip_threshold;
if( (tk->i_end - tk->i_start ) - p_sys->stream.i_offset < if( b_aseek )
p_sys->stream.i_read_size ) i_skip_threshold = b_afastseek ? 128 : 3*p_sys->stream.i_read_size;
else
i_skip_threshold = INT64_MAX;
/* Date the current track */
p_current->i_date = mdate();
/* Search a new track slot */
stream_track_t *tk = NULL;
int i_tk_idx = -1;
/* Prefer the current track */
if( p_current->i_start <= i_pos && i_pos - p_current->i_end <= i_skip_threshold )
{ {
if( p_sys->stream.i_used < STREAM_READ_ATONCE / 2 ) tk = p_current;
i_tk_idx = p_sys->stream.i_tk;
}
if( !tk )
{
/* Try to maximize already read data */
for( int i = 0; i < STREAM_CACHE_TRACK; i++ )
{ {
p_sys->stream.i_used = STREAM_READ_ATONCE / 2 ; stream_track_t *t = &p_sys->stream.tk[i];
AStreamRefillStream( s );
if( t->i_start > i_pos || i_pos > t->i_end )
continue;
if( !tk || tk->i_end < t->i_end )
{
tk = t;
i_tk_idx = i;
} }
} }
return VLC_SUCCESS;
} }
if( !tk )
{
/* Use the oldest unused */
for( int i = 0; i < STREAM_CACHE_TRACK; i++ )
{
stream_track_t *t = &p_sys->stream.tk[i];
access_Control( p_access, ACCESS_CAN_SEEK, &b_aseek ); if( !tk || tk->i_date > t->i_date )
if( !b_aseek )
{ {
/* We can't do nothing */ tk = t;
msg_Dbg( s, "AStreamSeekStream: can't seek" ); i_tk_idx = i;
return VLC_EGENERIC;
} }
}
}
assert( i_tk_idx >= 0 && i_tk_idx < STREAM_CACHE_TRACK );
/* Date the current track */ if( tk != p_current )
p_sys->stream.tk[p_sys->stream.i_tk].i_date = mdate(); i_skip_threshold = 0;
if( tk->i_start <= i_pos && i_pos - tk->i_end <= i_skip_threshold )
/* Try to reuse already read data */
for( i = 0; i < STREAM_CACHE_TRACK; i++ )
{ {
stream_track_t *tk = &p_sys->stream.tk[i]; #ifdef STREAM_DEBUG
msg_Err( s, "AStreamSeekStream: reusing %d start=%"PRId64
" end=%"PRId64"(%s)",
i_tk_idx, tk->i_start, tk->i_end,
tk != p_current ? "seek" : i_pos > tk->i_end ? "skip" : "noseek" );
#endif
if( tk != p_current )
{
assert( b_aseek );
if( i_pos >= tk->i_start && i_pos <= tk->i_end ) /* Seek at the end of the buffer
* TODO it is stupid to seek now, it would be better to delay it
*/
if( ASeek( s, tk->i_end ) )
return VLC_EGENERIC;
}
else
{
int64_t i_skip = i_pos - tk->i_end;
while( i_skip > 0 )
{
const int i_read_max = __MIN( 10 * STREAM_READ_ATONCE, i_skip );
if( AStreamReadNoSeekStream( s, NULL, i_read_max ) != i_read_max )
return VLC_EGENERIC;
i_skip -= i_read_max;
}
}
}
else
{ {
#ifdef STREAM_DEBUG #ifdef STREAM_DEBUG
msg_Dbg( s, "AStreamSeekStream: reusing %d start=%"PRId64 msg_Err( s, "AStreamSeekStream: hard seek" );
" end=%"PRId64, i, tk->i_start, tk->i_end );
#endif #endif
/* Nothing good, seek and choose oldest segment */
if( ASeek( s, i_pos ) )
return VLC_EGENERIC;
/* Seek at the end of the buffer */ tk->i_start = i_pos;
if( ASeek( s, tk->i_end ) ) return VLC_EGENERIC; tk->i_end = i_pos;
}
/* That's it */
p_sys->i_pos = i_pos;
p_sys->stream.i_tk = i;
p_sys->stream.i_offset = i_pos - tk->i_start; p_sys->stream.i_offset = i_pos - tk->i_start;
p_sys->stream.i_tk = i_tk_idx;
p_sys->i_pos = i_pos;
if( p_sys->stream.i_used < STREAM_READ_ATONCE ) /* If there is not enough data left in the track, refill */
p_sys->stream.i_used = STREAM_READ_ATONCE; /* TODO How to get a correct value for
* - refilling threshold
* - how much to refill
*/
if( (tk->i_end - tk->i_start) - p_sys->stream.i_offset < p_sys->stream.i_read_size )
{
if( p_sys->stream.i_used < STREAM_READ_ATONCE / 2 )
p_sys->stream.i_used = STREAM_READ_ATONCE / 2;
if( AStreamRefillStream( s ) && i_pos == tk->i_end ) if( AStreamRefillStream( s ) && i_pos == tk->i_end )
return VLC_EGENERIC; return VLC_EGENERIC;
return VLC_SUCCESS;
}
} }
return VLC_SUCCESS;
}
access_Control( p_access, ACCESS_CAN_SEEK, &b_afastseek ); static int AStreamReadNoSeekStream( stream_t *s, void *p_read, unsigned int i_read )
/* FIXME compute seek cost (instead of static 'stupid' value) */ {
i_maxth = __MIN( p_sys->stream.i_read_size, STREAM_READ_ATONCE / 2 ); stream_sys_t *p_sys = s->p_sys;
if( !b_afastseek ) stream_track_t *tk = &p_sys->stream.tk[p_sys->stream.i_tk];
i_maxth *= 3;
/* FIXME TODO */ uint8_t *p_data = (uint8_t *)p_read;
#if 0 unsigned int i_data = 0;
/* Search closest segment TODO */
for( i = 0; i < STREAM_CACHE_TRACK; i++ )
{
stream_track_t *tk = &p_sys->stream.tk[i];
if( i_pos + i_maxth >= tk->i_start ) if( tk->i_start >= tk->i_end )
{ return 0; /* EOF */
msg_Dbg( s, "good segment before current pos, TODO" );
} #ifdef STREAM_DEBUG
if( i_pos - i_maxth <= tk->i_end ) msg_Dbg( s, "AStreamReadStream: %d pos=%"PRId64" tk=%d start=%"PRId64
{ " offset=%d end=%"PRId64,
msg_Dbg( s, "good segment after current pos, TODO" ); i_read, p_sys->i_pos, p_sys->stream.i_tk,
} tk->i_start, p_sys->stream.i_offset, tk->i_end );
}
#endif #endif
/* Nothing good, seek and choose oldest segment */ while( i_data < i_read )
if( ASeek( s, i_pos ) ) return VLC_EGENERIC; {
p_sys->i_pos = i_pos; int i_off = (tk->i_start + p_sys->stream.i_offset) %
STREAM_CACHE_TRACK_SIZE;
unsigned int i_current =
__MAX(0,__MIN( tk->i_end - tk->i_start - p_sys->stream.i_offset,
STREAM_CACHE_TRACK_SIZE - i_off ));
int i_copy = __MIN( i_current, i_read - i_data );
if( i_copy <= 0 ) break; /* EOF */
i_new = 0; /* Copy data */
for( i = 1; i < STREAM_CACHE_TRACK; i++ ) /* msg_Dbg( s, "AStreamReadStream: copy %d", i_copy ); */
if( p_data )
{ {
if( p_sys->stream.tk[i].i_date < p_sys->stream.tk[i_new].i_date ) memcpy( p_data, &tk->p_buffer[i_off], i_copy );
i_new = i; p_data += i_copy;
} }
i_data += i_copy;
p_sys->stream.i_offset += i_copy;
/* Reset the segment */ /* Update pos now */
p_sys->stream.i_tk = i_new; p_sys->i_pos += i_copy;
p_sys->stream.i_offset = 0;
p_sys->stream.tk[i_new].i_start = i_pos;
p_sys->stream.tk[i_new].i_end = i_pos;
/* Read data */ /* */
if( p_sys->stream.i_used < STREAM_READ_ATONCE / 2 ) p_sys->stream.i_used += i_copy;
p_sys->stream.i_used = STREAM_READ_ATONCE / 2;
if( tk->i_end - tk->i_start - p_sys->stream.i_offset <= i_read -i_data )
{
const int i_read_requested = __MAX( __MIN( i_read - i_data,
STREAM_READ_ATONCE * 10 ),
STREAM_READ_ATONCE / 2 );
if( p_sys->stream.i_used < i_read_requested )
p_sys->stream.i_used = i_read_requested;
if( AStreamRefillStream( s ) ) if( AStreamRefillStream( s ) )
return VLC_EGENERIC; {
/* EOF */
if( tk->i_start >= tk->i_end ) break;
}
}
}
return VLC_SUCCESS; return i_data;
} }
static int AStreamRefillStream( stream_t *s ) static int AStreamRefillStream( stream_t *s )
{ {
stream_sys_t *p_sys = s->p_sys; stream_sys_t *p_sys = s->p_sys;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment