You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
553 lines
17 KiB
553 lines
17 KiB
/*****************************************************************************
|
|
* matroska_segment.hpp : matroska demuxer
|
|
*****************************************************************************
|
|
* Copyright (C) 2016 VLC authors and VideoLAN
|
|
*
|
|
* Authors: Filip Roséen <filip@videolabs.io>
|
|
*
|
|
* This program 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.
|
|
*
|
|
* This program 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 this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
|
|
*****************************************************************************/
|
|
|
|
#include "matroska_segment_seeker.hpp"
|
|
#include "matroska_segment.hpp"
|
|
#include "demux.hpp"
|
|
#include "Ebml_parser.hpp"
|
|
#include "Ebml_dispatcher.hpp"
|
|
#include "util.hpp"
|
|
#include "stream_io_callback.hpp"
|
|
|
|
#include <sstream>
|
|
#include <limits>
|
|
|
|
namespace {
|
|
template<class It, class T>
|
|
It greatest_lower_bound( It beg, It end, T const& value )
|
|
{
|
|
It it = std::upper_bound( beg, end, value );
|
|
if( it != beg ) --it;
|
|
return it;
|
|
}
|
|
|
|
// std::prev and std::next exists in C++11, in order to avoid ambiguity due
|
|
// to ADL and iterators being defined within namespace std, these two
|
|
// function-names have been postfixed with an underscore.
|
|
|
|
template<class It> It prev_( It it ) { return --it; }
|
|
template<class It> It next_( It it ) { return ++it; }
|
|
}
|
|
|
|
namespace mkv {
|
|
|
|
SegmentSeeker::cluster_positions_t::iterator
|
|
SegmentSeeker::add_cluster_position( fptr_t fpos )
|
|
{
|
|
cluster_positions_t::iterator insertion_point = std::upper_bound(
|
|
_cluster_positions.begin(),
|
|
_cluster_positions.end(),
|
|
fpos
|
|
);
|
|
|
|
return _cluster_positions.insert( insertion_point, fpos );
|
|
}
|
|
|
|
SegmentSeeker::cluster_map_t::iterator
|
|
SegmentSeeker::add_cluster( KaxCluster * const p_cluster )
|
|
{
|
|
Cluster cinfo = {
|
|
/* fpos */ p_cluster->GetElementPosition(),
|
|
/* pts */ vlc_tick_t( VLC_TICK_FROM_NS( p_cluster->GlobalTimestamp() ) ),
|
|
/* duration */ vlc_tick_t( -1 ),
|
|
/* size */ p_cluster->IsFiniteSize()
|
|
? p_cluster->GetEndPosition() - p_cluster->GetElementPosition()
|
|
: UINT64_MAX
|
|
};
|
|
|
|
add_cluster_position( cinfo.fpos );
|
|
|
|
cluster_map_t::iterator it = _clusters.lower_bound( cinfo.pts );
|
|
|
|
if( it != _clusters.end() && it->second.pts == cinfo.pts )
|
|
{
|
|
// cluster already known
|
|
}
|
|
else
|
|
{
|
|
it = _clusters.insert( cluster_map_t::value_type( cinfo.pts, cinfo ) ).first;
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
// IF we have two adjecent clusters, update duration where applicable
|
|
// ------------------------------------------------------------------
|
|
|
|
struct Duration {
|
|
static void fix( Cluster& prev, Cluster& next )
|
|
{
|
|
if( ( prev.fpos + prev.size) == next.fpos )
|
|
prev.duration = next.pts - prev.pts;
|
|
}
|
|
};
|
|
|
|
if( it != _clusters.begin() )
|
|
{
|
|
Duration::fix( prev_( it )->second, it->second );
|
|
}
|
|
|
|
if( it != _clusters.end() && next_( it ) != _clusters.end() )
|
|
{
|
|
Duration::fix( it->second, next_( it )->second );
|
|
}
|
|
|
|
return it;
|
|
}
|
|
|
|
void
|
|
SegmentSeeker::add_seekpoint( track_id_t track_id, Seekpoint sp )
|
|
{
|
|
seekpoints_t& seekpoints = _tracks_seekpoints[ track_id ];
|
|
seekpoints_t::iterator it = std::lower_bound( seekpoints.begin(), seekpoints.end(), sp );
|
|
|
|
if( it != seekpoints.end() && it->fpos == sp.fpos )
|
|
{
|
|
if (sp.trust_level <= it->trust_level)
|
|
return;
|
|
|
|
*it = sp;
|
|
}
|
|
else if( it != seekpoints.end() && it->pts == sp.pts )
|
|
{
|
|
if (sp.trust_level <= it->trust_level)
|
|
return;
|
|
|
|
*it = sp;
|
|
}
|
|
else
|
|
{
|
|
seekpoints.insert( it, sp );
|
|
}
|
|
}
|
|
|
|
SegmentSeeker::tracks_seekpoint_t
|
|
SegmentSeeker::find_greatest_seekpoints_in_range( fptr_t start_fpos, vlc_tick_t end_pts, track_ids_t const& filter_tracks )
|
|
{
|
|
tracks_seekpoint_t tpoints;
|
|
|
|
for( tracks_seekpoints_t::const_iterator it = _tracks_seekpoints.begin(); it != _tracks_seekpoints.end(); ++it )
|
|
{
|
|
if ( std::find( filter_tracks.begin(), filter_tracks.end(), it->first ) == filter_tracks.end() )
|
|
continue;
|
|
|
|
Seekpoint sp = get_first_seekpoint_around( end_pts, it->second );
|
|
|
|
if( sp.fpos < start_fpos )
|
|
continue;
|
|
|
|
if( sp.pts > end_pts )
|
|
continue;
|
|
|
|
tpoints.insert( tracks_seekpoint_t::value_type( it->first, sp ) );
|
|
}
|
|
|
|
if (tpoints.empty())
|
|
{
|
|
// try a further pts
|
|
for( tracks_seekpoints_t::const_iterator it = _tracks_seekpoints.begin(); it != _tracks_seekpoints.end(); ++it )
|
|
{
|
|
if ( std::find( filter_tracks.begin(), filter_tracks.end(), it->first ) == filter_tracks.end() )
|
|
continue;
|
|
|
|
Seekpoint sp = get_first_seekpoint_around( end_pts, it->second );
|
|
|
|
if( sp.fpos < start_fpos )
|
|
continue;
|
|
|
|
tpoints.insert( tracks_seekpoint_t::value_type( it->first, sp ) );
|
|
}
|
|
}
|
|
|
|
return tpoints;
|
|
}
|
|
|
|
SegmentSeeker::Seekpoint
|
|
SegmentSeeker::get_first_seekpoint_around( vlc_tick_t pts, seekpoints_t const& seekpoints,
|
|
Seekpoint::TrustLevel trust_level )
|
|
{
|
|
if( seekpoints.empty() )
|
|
{
|
|
return Seekpoint();
|
|
}
|
|
|
|
typedef seekpoints_t::const_iterator iterator;
|
|
|
|
Seekpoint const needle ( std::numeric_limits<fptr_t>::max(), pts );
|
|
|
|
iterator const it_begin = seekpoints.begin();
|
|
iterator const it_end = seekpoints.end();
|
|
iterator const it_middle = greatest_lower_bound( it_begin, it_end, needle );
|
|
|
|
iterator it_before;
|
|
|
|
// rewrind to _previous_ seekpoint with appropriate trust
|
|
for( it_before = it_middle; it_before != it_begin; --it_before )
|
|
{
|
|
if( it_before->trust_level >= trust_level )
|
|
return *it_before;
|
|
}
|
|
return *it_begin;
|
|
}
|
|
|
|
SegmentSeeker::seekpoint_pair_t
|
|
SegmentSeeker::get_seekpoints_around( vlc_tick_t pts, seekpoints_t const& seekpoints )
|
|
{
|
|
if( seekpoints.empty() )
|
|
{
|
|
return seekpoint_pair_t();
|
|
}
|
|
|
|
typedef seekpoints_t::const_iterator iterator;
|
|
|
|
Seekpoint const needle ( std::numeric_limits<fptr_t>::max(), pts );
|
|
|
|
iterator const it_begin = seekpoints.begin();
|
|
iterator const it_end = seekpoints.end();
|
|
iterator const it_middle = greatest_lower_bound( it_begin, it_end, needle );
|
|
|
|
if ( it_middle != it_end && (*it_middle).pts > pts)
|
|
// found nothing low enough, use the first one
|
|
return seekpoint_pair_t( *it_begin, Seekpoint() );
|
|
|
|
iterator it_before = it_middle;
|
|
iterator it_after = it_middle == it_end ? it_middle : next_( it_middle ) ;
|
|
|
|
return seekpoint_pair_t( *it_before,
|
|
it_after == it_end ? Seekpoint() : *it_after
|
|
);
|
|
}
|
|
|
|
SegmentSeeker::seekpoint_pair_t
|
|
SegmentSeeker::get_seekpoints_around( vlc_tick_t target_pts, track_ids_t const& priority_tracks )
|
|
{
|
|
seekpoint_pair_t points;
|
|
|
|
if( _tracks_seekpoints.empty() )
|
|
return points;
|
|
|
|
{ // locate the max/min seekpoints for priority_tracks //
|
|
|
|
typedef track_ids_t::const_iterator track_iterator;
|
|
|
|
track_iterator const begin = priority_tracks.begin();
|
|
track_iterator const end = priority_tracks.end();
|
|
|
|
for( track_iterator it = begin; it != end; ++it )
|
|
{
|
|
seekpoint_pair_t track_points = get_seekpoints_around( target_pts, _tracks_seekpoints[ *it ] );
|
|
|
|
if( it == begin ) {
|
|
points = track_points;
|
|
continue;
|
|
}
|
|
|
|
if( track_points.first.trust_level > Seekpoint::DISABLED &&
|
|
points.first.fpos > track_points.first.fpos )
|
|
points.first = track_points.first;
|
|
|
|
if( track_points.second.trust_level > Seekpoint::DISABLED &&
|
|
points.second.fpos < track_points.second.fpos )
|
|
points.second = track_points.second;
|
|
}
|
|
}
|
|
|
|
{ // check if we got a cluster which is closer to target_pts than the found cues //
|
|
|
|
cluster_map_t::iterator it = _clusters.lower_bound( target_pts );
|
|
|
|
if( it != _clusters.begin() && --it != _clusters.end() )
|
|
{
|
|
Cluster const& cluster = it->second;
|
|
|
|
if( cluster.fpos > points.first.fpos )
|
|
{
|
|
points.first = Seekpoint( cluster.fpos, cluster.pts );
|
|
|
|
// do we need to update the max point? //
|
|
|
|
if( points.second.fpos < points.first.fpos )
|
|
points.second = Seekpoint( cluster.fpos + cluster.size, cluster.pts + cluster.duration );
|
|
}
|
|
}
|
|
}
|
|
|
|
return points;
|
|
}
|
|
|
|
SegmentSeeker::tracks_seekpoint_t
|
|
SegmentSeeker::get_seekpoints( matroska_segment_c& ms, vlc_tick_t target_pts,
|
|
track_ids_t const& priority_tracks, track_ids_t const& filter_tracks )
|
|
{
|
|
struct contains_all_of_t {
|
|
bool operator()( tracks_seekpoint_t const& haystack, track_ids_t const& track_ids )
|
|
{
|
|
for( track_ids_t::const_iterator it = track_ids.begin(); it != track_ids.end(); ++it ) {
|
|
if( haystack.find( *it ) == haystack.end() )
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
for( vlc_tick_t needle_pts = target_pts; ; )
|
|
{
|
|
seekpoint_pair_t seekpoints = get_seekpoints_around( needle_pts, priority_tracks );
|
|
|
|
Seekpoint const& start = seekpoints.first;
|
|
Seekpoint const& end = seekpoints.second;
|
|
|
|
if ( start.fpos == std::numeric_limits<fptr_t>::max() )
|
|
return tracks_seekpoint_t();
|
|
|
|
if ( (end.fpos != std::numeric_limits<fptr_t>::max() || !ms.b_cues) &&
|
|
(needle_pts != start.pts || start.trust_level < Seekpoint::TRUSTED))
|
|
// do not read the whole (infinite?) file to get seek indexes
|
|
// do not generate an index if we already have the correct seekpoint
|
|
index_range( ms, Range( start.fpos, end.fpos ), needle_pts );
|
|
|
|
tracks_seekpoint_t tpoints = find_greatest_seekpoints_in_range( start.fpos, target_pts, filter_tracks );
|
|
|
|
if( contains_all_of_t() ( tpoints, priority_tracks ) )
|
|
return tpoints;
|
|
|
|
// Avoid busyloop, don't iterate on the same seekpoint
|
|
if( needle_pts == start.pts - 1 )
|
|
// we found the same needle twice, stop looking
|
|
return tpoints;
|
|
|
|
needle_pts = start.pts - 1;
|
|
}
|
|
|
|
vlc_assert_unreachable();
|
|
}
|
|
|
|
void
|
|
SegmentSeeker::index_range( matroska_segment_c& ms, Range search_area, vlc_tick_t max_pts )
|
|
{
|
|
ranges_t areas_to_search = get_search_areas( search_area.start, search_area.end );
|
|
|
|
for( ranges_t::const_iterator range_it = areas_to_search.begin(); range_it != areas_to_search.end(); ++range_it )
|
|
index_unsearched_range( ms, *range_it, max_pts );
|
|
}
|
|
|
|
void
|
|
SegmentSeeker::index_unsearched_range( matroska_segment_c& ms, Range search_area, vlc_tick_t max_pts )
|
|
{
|
|
mkv_jump_to( ms, search_area.start );
|
|
|
|
search_area.start = ms.es.I_O().getFilePointer();
|
|
|
|
fptr_t block_pos = search_area.start;
|
|
vlc_tick_t block_pts;
|
|
|
|
while( block_pos < search_area.end )
|
|
{
|
|
KaxBlock * block;
|
|
KaxSimpleBlock * simpleblock;
|
|
KaxBlockAdditions *additions;
|
|
|
|
bool b_key_picture;
|
|
bool b_discardable_picture;
|
|
int64_t i_block_duration;
|
|
track_id_t track_id;
|
|
|
|
if( ms.BlockGet( block, simpleblock, additions,
|
|
&b_key_picture, &b_discardable_picture, &i_block_duration ) )
|
|
{
|
|
delete additions;
|
|
break;
|
|
}
|
|
delete additions;
|
|
|
|
KaxInternalBlock& internal_block = simpleblock
|
|
? static_cast<KaxInternalBlock&>( *simpleblock )
|
|
: static_cast<KaxInternalBlock&>( *block );
|
|
|
|
block_pos = internal_block.GetElementPosition();
|
|
block_pts = VLC_TICK_FROM_NS(internal_block.GlobalTimestamp());
|
|
track_id = internal_block.TrackNum();
|
|
|
|
bool const b_valid_track = ms.FindTrackByBlock( block, simpleblock ) != NULL;
|
|
|
|
delete block;
|
|
|
|
if( b_valid_track )
|
|
{
|
|
if( b_key_picture )
|
|
add_seekpoint( track_id, Seekpoint( block_pos, block_pts ) );
|
|
|
|
if( max_pts < block_pts )
|
|
break;
|
|
}
|
|
}
|
|
|
|
search_area.end = ms.es.I_O().getFilePointer();
|
|
|
|
mark_range_as_searched( search_area );
|
|
}
|
|
|
|
void
|
|
SegmentSeeker::mark_range_as_searched( Range data )
|
|
{
|
|
/* TODO: this is utterly ugly, we should do the insertion in-place */
|
|
|
|
_ranges_searched.insert( std::upper_bound( _ranges_searched.begin(), _ranges_searched.end(), data ), data );
|
|
|
|
{
|
|
ranges_t merged;
|
|
|
|
for( ranges_t::iterator it = _ranges_searched.begin(); it != _ranges_searched.end(); ++it )
|
|
{
|
|
if( merged.size() )
|
|
{
|
|
Range& last_entry = *merged.rbegin();
|
|
|
|
if( last_entry.end+1 >= it->start && last_entry.end < it->end )
|
|
{
|
|
last_entry.end = it->end;
|
|
continue;
|
|
}
|
|
|
|
if( it->start >= last_entry.start && it->end <= last_entry.end )
|
|
{
|
|
last_entry.end = std::max( last_entry.end, it->end );
|
|
continue;
|
|
}
|
|
}
|
|
|
|
merged.push_back( *it );
|
|
}
|
|
|
|
_ranges_searched = std::move(merged);
|
|
}
|
|
}
|
|
|
|
|
|
SegmentSeeker::ranges_t
|
|
SegmentSeeker::get_search_areas( fptr_t start, fptr_t end ) const
|
|
{
|
|
ranges_t areas_to_search;
|
|
Range needle ( start, end );
|
|
|
|
ranges_t::const_iterator it = greatest_lower_bound( _ranges_searched.begin(), _ranges_searched.end(), needle );
|
|
|
|
for( ; it != _ranges_searched.end() && needle.start < needle.end; ++it )
|
|
{
|
|
if( needle.start < it->start )
|
|
{
|
|
areas_to_search.push_back( Range( needle.start, it->start ) );
|
|
}
|
|
|
|
if( needle.start <= it->end )
|
|
needle.start = it->end + 1;
|
|
}
|
|
|
|
needle.start = std::max( needle.start, start );
|
|
if( it == _ranges_searched.end() && needle.start < needle.end )
|
|
{
|
|
areas_to_search.push_back( needle );
|
|
}
|
|
|
|
return areas_to_search;
|
|
}
|
|
|
|
void
|
|
SegmentSeeker::mkv_jump_to( matroska_segment_c& ms, fptr_t fpos )
|
|
{
|
|
fptr_t i_cluster_pos = -1;
|
|
|
|
if ( fpos != std::numeric_limits<SegmentSeeker::fptr_t>::max() )
|
|
{
|
|
ms.cluster = NULL;
|
|
if ( !_cluster_positions.empty() )
|
|
{
|
|
cluster_positions_t::iterator cluster_it = greatest_lower_bound(
|
|
_cluster_positions.begin(), _cluster_positions.end(), fpos
|
|
);
|
|
|
|
ms.es.I_O().setFilePointer( *cluster_it );
|
|
ms.ep.reconstruct( &ms.es, ms.segment, &ms.sys.demuxer );
|
|
}
|
|
|
|
while( ms.cluster == NULL || (
|
|
ms.cluster->IsFiniteSize() && ms.cluster->GetEndPosition() < fpos ) )
|
|
{
|
|
EbmlElement *el = ms.ep.Get();
|
|
if( el == nullptr )
|
|
{
|
|
msg_Err( &ms.sys.demuxer, "unable to read KaxCluster during seek, giving up" );
|
|
return;
|
|
}
|
|
if (!MKV_IS_ID( el, KaxCluster ))
|
|
continue; // look for the next element
|
|
|
|
ms.cluster = static_cast<KaxCluster*>( el );
|
|
|
|
i_cluster_pos = ms.cluster->GetElementPosition();
|
|
|
|
add_cluster_position( i_cluster_pos );
|
|
|
|
mark_range_as_searched( Range( i_cluster_pos, ms.es.I_O().getFilePointer() ) );
|
|
}
|
|
}
|
|
else if (ms.cluster != NULL)
|
|
{
|
|
// make sure we start reading after the Cluster start
|
|
ms.es.I_O().setFilePointer(ms.cluster->GetDataStart());
|
|
}
|
|
|
|
ms.ep.Down();
|
|
|
|
/* read until cluster/timecode to initialize cluster */
|
|
|
|
while( EbmlElement * el = ms.ep.Get() )
|
|
{
|
|
try {
|
|
if( MKV_CHECKED_PTR_DECL( p_tc, KaxClusterTimestamp, el ) )
|
|
{
|
|
p_tc->ReadData( ms.es.I_O(), SCOPE_ALL_DATA );
|
|
ms.cluster->InitTimestamp( static_cast<uint64_t>( *p_tc ), ms.i_timescale );
|
|
add_cluster(ms.cluster);
|
|
break;
|
|
}
|
|
else if( MKV_CHECKED_PTR_DECL( crc, EbmlCrc32, el ) )
|
|
{
|
|
crc->ReadData( ms.es.I_O(), SCOPE_ALL_DATA ); /* avoid a skip that may fail */
|
|
}
|
|
}
|
|
catch(...)
|
|
{
|
|
msg_Err( &ms.sys.demuxer,"Error while reading %s", EBML_NAME(el) );
|
|
}
|
|
}
|
|
|
|
/* TODO: add error handling; what if we never get a KaxCluster and/or KaxClusterTimestamp? */
|
|
|
|
mark_range_as_searched( Range( i_cluster_pos, ms.es.I_O().getFilePointer() ) );
|
|
|
|
/* jump to desired position */
|
|
|
|
if ( fpos != std::numeric_limits<SegmentSeeker::fptr_t>::max() )
|
|
ms.es.I_O().setFilePointer( fpos );
|
|
}
|
|
|
|
} // namespace
|
|
|