[DASH] Support TrickPlay using separate trick play specific streams (#736)
Closes #732.
This commit is contained in:
parent
962baf0286
commit
4bfa603b52
|
@ -143,18 +143,6 @@ Status TrickPlayHandler::OnMediaSample(const MediaSample& sample) {
|
||||||
return OnTrickFrame(sample);
|
return OnTrickFrame(sample);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update this now as it may be sent out soon via the delay message queue.
|
|
||||||
if (total_trick_frames_ < 2) {
|
|
||||||
// At this point, video_info will be at the head of the delay message queue
|
|
||||||
// and can still be updated safely.
|
|
||||||
|
|
||||||
// The play back rate is determined by the number of frames between the
|
|
||||||
// first two trick play frames. The first trick play frame will be the
|
|
||||||
// first frame in the video.
|
|
||||||
video_info_->set_playback_rate(total_frames_);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the frame is not a trick play frame, then take the duration of this
|
// If the frame is not a trick play frame, then take the duration of this
|
||||||
// frame and add it to the previous trick play frame so that it will span the
|
// frame and add it to the previous trick play frame so that it will span the
|
||||||
// gap created by not passing this frame through.
|
// gap created by not passing this frame through.
|
||||||
|
@ -182,6 +170,17 @@ Status TrickPlayHandler::OnTrickFrame(const MediaSample& sample) {
|
||||||
return Status::OK;
|
return Status::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update this now as it may be sent out soon via the delay message queue.
|
||||||
|
if (total_trick_frames_ == 2) {
|
||||||
|
// At this point, video_info will be at the head of the delay message queue
|
||||||
|
// and can still be updated safely.
|
||||||
|
|
||||||
|
// The play back rate is determined by the number of frames between the
|
||||||
|
// first two trick play frames. The first trick play frame will be the
|
||||||
|
// first frame in the video.
|
||||||
|
video_info_->set_playback_rate(total_frames_ - 1);
|
||||||
|
}
|
||||||
|
|
||||||
// Send out all delayed messages up until the new trick play frame we just
|
// Send out all delayed messages up until the new trick play frame we just
|
||||||
// added.
|
// added.
|
||||||
Status s;
|
Status s;
|
||||||
|
|
|
@ -216,16 +216,30 @@ bool Period::SetNewAdaptationSetAttributes(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (media_info.video_info().has_playback_rate()) {
|
if (media_info.video_info().has_playback_rate()) {
|
||||||
const AdaptationSet* trick_play_reference_adaptation_set =
|
std::string trick_play_reference_adaptation_set_key;
|
||||||
FindOriginalAdaptationSetForTrickPlay(
|
AdaptationSet* trick_play_reference_adaptation_set =
|
||||||
media_info, content_protection_in_adaptation_set);
|
FindMatchingAdaptationSetForTrickPlay(
|
||||||
if (!trick_play_reference_adaptation_set) {
|
media_info, content_protection_in_adaptation_set,
|
||||||
LOG(ERROR) << "Failed to find original AdaptationSet for trick play.";
|
&trick_play_reference_adaptation_set_key);
|
||||||
return false;
|
if (trick_play_reference_adaptation_set) {
|
||||||
}
|
|
||||||
new_adaptation_set->AddTrickPlayReference(
|
new_adaptation_set->AddTrickPlayReference(
|
||||||
trick_play_reference_adaptation_set);
|
trick_play_reference_adaptation_set);
|
||||||
|
} else {
|
||||||
|
trickplay_cache_[trick_play_reference_adaptation_set_key].push_back(
|
||||||
|
new_adaptation_set);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
std::string trick_play_adaptation_set_key;
|
||||||
|
AdaptationSet* trickplay_adaptation_set =
|
||||||
|
FindMatchingAdaptationSetForTrickPlay(
|
||||||
|
media_info, content_protection_in_adaptation_set,
|
||||||
|
&trick_play_adaptation_set_key);
|
||||||
|
if (trickplay_adaptation_set) {
|
||||||
|
trickplay_adaptation_set->AddTrickPlayReference(new_adaptation_set);
|
||||||
|
trickplay_cache_.erase(trick_play_adaptation_set_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else if (media_info.has_text_info()) {
|
} else if (media_info.has_text_info()) {
|
||||||
// IOP requires all AdaptationSets to have (sub)segmentAlignment set to
|
// IOP requires all AdaptationSets to have (sub)segmentAlignment set to
|
||||||
// true, so carelessly set it to true.
|
// true, so carelessly set it to true.
|
||||||
|
@ -235,26 +249,45 @@ bool Period::SetNewAdaptationSetAttributes(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AdaptationSet* Period::FindOriginalAdaptationSetForTrickPlay(
|
AdaptationSet* Period::FindMatchingAdaptationSetForTrickPlay(
|
||||||
const MediaInfo& media_info,
|
const MediaInfo& media_info,
|
||||||
bool content_protection_in_adaptation_set) {
|
bool content_protection_in_adaptation_set,
|
||||||
MediaInfo media_info_no_trickplay = media_info;
|
std::string* adaptation_set_key) {
|
||||||
media_info_no_trickplay.mutable_video_info()->clear_playback_rate();
|
std::list<AdaptationSet*>* adaptation_sets = nullptr;
|
||||||
|
const bool is_trickplay_adaptation_set =
|
||||||
std::string key = GetAdaptationSetKey(
|
media_info.video_info().has_playback_rate();
|
||||||
media_info_no_trickplay, mpd_options_.mpd_params.allow_codec_switching);
|
if (is_trickplay_adaptation_set) {
|
||||||
const std::list<AdaptationSet*>& adaptation_sets =
|
*adaptation_set_key = GetAdaptationSetKeyForTrickPlay(media_info);
|
||||||
adaptation_set_list_map_[key];
|
if (adaptation_set_list_map_.find(*adaptation_set_key) ==
|
||||||
for (AdaptationSet* adaptation_set : adaptation_sets) {
|
adaptation_set_list_map_.end())
|
||||||
|
return nullptr;
|
||||||
|
adaptation_sets = &adaptation_set_list_map_[*adaptation_set_key];
|
||||||
|
} else {
|
||||||
|
*adaptation_set_key = GetAdaptationSetKey(
|
||||||
|
media_info, mpd_options_.mpd_params.allow_codec_switching);
|
||||||
|
if (trickplay_cache_.find(*adaptation_set_key) == trickplay_cache_.end())
|
||||||
|
return nullptr;
|
||||||
|
adaptation_sets = &trickplay_cache_[*adaptation_set_key];
|
||||||
|
}
|
||||||
|
for (AdaptationSet* adaptation_set : *adaptation_sets) {
|
||||||
if (protected_adaptation_set_map_.Match(
|
if (protected_adaptation_set_map_.Match(
|
||||||
*adaptation_set, media_info,
|
*adaptation_set, media_info,
|
||||||
content_protection_in_adaptation_set)) {
|
content_protection_in_adaptation_set)) {
|
||||||
return adaptation_set;
|
return adaptation_set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string Period::GetAdaptationSetKeyForTrickPlay(
|
||||||
|
const MediaInfo& media_info) {
|
||||||
|
MediaInfo media_info_no_trickplay = media_info;
|
||||||
|
media_info_no_trickplay.mutable_video_info()->clear_playback_rate();
|
||||||
|
return GetAdaptationSetKey(media_info_no_trickplay,
|
||||||
|
mpd_options_.mpd_params.allow_codec_switching);
|
||||||
|
}
|
||||||
|
|
||||||
void Period::ProtectedAdaptationSetMap::Register(
|
void Period::ProtectedAdaptationSetMap::Register(
|
||||||
const AdaptationSet& adaptation_set,
|
const AdaptationSet& adaptation_set,
|
||||||
const MediaInfo& media_info) {
|
const MediaInfo& media_info) {
|
||||||
|
@ -303,4 +336,11 @@ bool Period::ProtectedAdaptationSetMap::Switchable(
|
||||||
GetUUIDs(protected_content_it_b->second);
|
GetUUIDs(protected_content_it_b->second);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Period::~Period() {
|
||||||
|
if (!trickplay_cache_.empty()) {
|
||||||
|
LOG(WARNING) << "Trickplay adaptation set did not get a valid adaptation "
|
||||||
|
"set match. Please check the command line options.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -29,7 +29,7 @@ class XmlNode;
|
||||||
/// AdaptationSets.
|
/// AdaptationSets.
|
||||||
class Period {
|
class Period {
|
||||||
public:
|
public:
|
||||||
virtual ~Period() = default;
|
virtual ~Period();
|
||||||
|
|
||||||
/// Check the existing AdaptationSets, if there is one matching the provided
|
/// Check the existing AdaptationSets, if there is one matching the provided
|
||||||
/// @a media_info, return it; otherwise a new AdaptationSet is created and
|
/// @a media_info, return it; otherwise a new AdaptationSet is created and
|
||||||
|
@ -66,6 +66,12 @@ class Period {
|
||||||
duration_seconds_ = duration_seconds;
|
duration_seconds_ = duration_seconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return trickplay_cache.
|
||||||
|
const std::map<std::string, std::list<AdaptationSet*>>& trickplay_cache()
|
||||||
|
const {
|
||||||
|
return trickplay_cache_;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// @param period_id is an ID number for this Period.
|
/// @param period_id is an ID number for this Period.
|
||||||
/// @param start_time_in_seconds is the start time for this Period.
|
/// @param start_time_in_seconds is the start time for this Period.
|
||||||
|
@ -98,13 +104,23 @@ class Period {
|
||||||
bool content_protection_in_adaptation_set,
|
bool content_protection_in_adaptation_set,
|
||||||
AdaptationSet* new_adaptation_set);
|
AdaptationSet* new_adaptation_set);
|
||||||
|
|
||||||
// Gets the original AdaptationSet which the trick play video belongs to.
|
// If processing a trick play AdaptationSet, gets the original AdaptationSet
|
||||||
// It is assumed that the corresponding AdaptationSet has been created before
|
// which the trick play video belongs to.It is assumed that the corresponding
|
||||||
// the trick play AdaptationSet.
|
// AdaptationSet has been created before the trick play AdaptationSet.
|
||||||
// Returns the original AdaptationSet if found, otherwise returns nullptr;
|
// Returns the matching AdaptationSet if found, otherwise returns nullptr;
|
||||||
const AdaptationSet* FindOriginalAdaptationSetForTrickPlay(
|
// If processing non-trick play AdaptationSet, gets the trick play
|
||||||
const MediaInfo& media_info, bool content_protection_in_adaptation_set);
|
// AdaptationSet that belongs to current AdaptationSet from trick play cache.
|
||||||
|
// Returns nullptr if matching trick play AdaptationSet is not found.
|
||||||
|
AdaptationSet* FindMatchingAdaptationSetForTrickPlay(
|
||||||
|
const MediaInfo& media_info,
|
||||||
|
bool content_protection_in_adaptation_set,
|
||||||
|
std::string* adaptation_set_key);
|
||||||
|
|
||||||
|
// Returns AdaptationSet key without ':trickplay' in it for trickplay
|
||||||
|
// AdaptationSet.
|
||||||
|
std::string GetAdaptationSetKeyForTrickPlay(const MediaInfo& media_info);
|
||||||
|
|
||||||
|
// FindMatchingAdaptationSetForTrickPlay
|
||||||
const uint32_t id_;
|
const uint32_t id_;
|
||||||
const double start_time_in_seconds_;
|
const double start_time_in_seconds_;
|
||||||
double duration_seconds_ = 0;
|
double duration_seconds_ = 0;
|
||||||
|
@ -117,6 +133,10 @@ class Period {
|
||||||
// if they contain identical ContentProtection elements. This map is only
|
// if they contain identical ContentProtection elements. This map is only
|
||||||
// useful when ContentProtection element is placed in AdaptationSet.
|
// useful when ContentProtection element is placed in AdaptationSet.
|
||||||
std::map<std::string, std::list<AdaptationSet*>> adaptation_set_list_map_;
|
std::map<std::string, std::list<AdaptationSet*>> adaptation_set_list_map_;
|
||||||
|
// Contains Trickplay AdaptationSets grouped by specific adaptation set
|
||||||
|
// grouping key. These AdaptationSets still have not found reference
|
||||||
|
// AdaptationSet.
|
||||||
|
std::map<std::string, std::list<AdaptationSet*>> trickplay_cache_;
|
||||||
|
|
||||||
// Tracks ProtectedContent in AdaptationSet.
|
// Tracks ProtectedContent in AdaptationSet.
|
||||||
class ProtectedAdaptationSetMap {
|
class ProtectedAdaptationSetMap {
|
||||||
|
|
|
@ -279,6 +279,52 @@ TEST_F(PeriodTest, TrickPlayWithMatchingAdaptationSet) {
|
||||||
content_protection_in_adaptation_set_));
|
content_protection_in_adaptation_set_));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(PeriodTest, TrickPlayCacheWithMatchingAdaptationSet) {
|
||||||
|
const char kVideoMediaInfo[] =
|
||||||
|
"video_info {\n"
|
||||||
|
" codec: 'avc1'\n"
|
||||||
|
" width: 1280\n"
|
||||||
|
" height: 720\n"
|
||||||
|
" time_scale: 10\n"
|
||||||
|
" frame_duration: 10\n"
|
||||||
|
" pixel_width: 1\n"
|
||||||
|
" pixel_height: 1\n"
|
||||||
|
"}\n"
|
||||||
|
"container_type: 1\n";
|
||||||
|
const char kTrickPlayMediaInfo[] =
|
||||||
|
"video_info {\n"
|
||||||
|
" codec: 'avc1'\n"
|
||||||
|
" width: 1280\n"
|
||||||
|
" height: 720\n"
|
||||||
|
" time_scale: 10\n"
|
||||||
|
" frame_duration: 100\n"
|
||||||
|
" pixel_width: 1\n"
|
||||||
|
" pixel_height: 1\n"
|
||||||
|
" playback_rate: 10\n"
|
||||||
|
"}\n"
|
||||||
|
"container_type: 1\n";
|
||||||
|
|
||||||
|
std::unique_ptr<StrictMock<MockAdaptationSet>> trick_play_adaptation_set(
|
||||||
|
new StrictMock<MockAdaptationSet>());
|
||||||
|
auto* trick_play_adaptation_set_ptr = trick_play_adaptation_set.get();
|
||||||
|
|
||||||
|
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _))
|
||||||
|
.WillOnce(Return(ByMove(std::move(trick_play_adaptation_set))))
|
||||||
|
.WillOnce(Return(ByMove(std::move(default_adaptation_set_))));
|
||||||
|
|
||||||
|
EXPECT_CALL(*trick_play_adaptation_set_ptr,
|
||||||
|
AddTrickPlayReference(Eq(default_adaptation_set_ptr_)));
|
||||||
|
|
||||||
|
ASSERT_EQ(trick_play_adaptation_set_ptr,
|
||||||
|
testable_period_.GetOrCreateAdaptationSet(
|
||||||
|
ConvertToMediaInfo(kTrickPlayMediaInfo),
|
||||||
|
content_protection_in_adaptation_set_));
|
||||||
|
ASSERT_EQ(default_adaptation_set_ptr_,
|
||||||
|
testable_period_.GetOrCreateAdaptationSet(
|
||||||
|
ConvertToMediaInfo(kVideoMediaInfo),
|
||||||
|
content_protection_in_adaptation_set_));
|
||||||
|
}
|
||||||
|
|
||||||
// Verify no AdaptationSet is returned on trickplay media info.
|
// Verify no AdaptationSet is returned on trickplay media info.
|
||||||
TEST_F(PeriodTest, TrickPlayWithNoMatchingAdaptationSet) {
|
TEST_F(PeriodTest, TrickPlayWithNoMatchingAdaptationSet) {
|
||||||
const char kVideoMediaInfo[] =
|
const char kVideoMediaInfo[] =
|
||||||
|
@ -316,10 +362,12 @@ TEST_F(PeriodTest, TrickPlayWithNoMatchingAdaptationSet) {
|
||||||
testable_period_.GetOrCreateAdaptationSet(
|
testable_period_.GetOrCreateAdaptationSet(
|
||||||
ConvertToMediaInfo(kVideoMediaInfo),
|
ConvertToMediaInfo(kVideoMediaInfo),
|
||||||
content_protection_in_adaptation_set_));
|
content_protection_in_adaptation_set_));
|
||||||
// A nullptr is returned if it is not able to find matching AdaptationSet.
|
|
||||||
ASSERT_FALSE(testable_period_.GetOrCreateAdaptationSet(
|
ASSERT_TRUE(testable_period_.GetOrCreateAdaptationSet(
|
||||||
ConvertToMediaInfo(kVp9TrickPlayMediaInfo),
|
ConvertToMediaInfo(kVp9TrickPlayMediaInfo),
|
||||||
content_protection_in_adaptation_set_));
|
content_protection_in_adaptation_set_));
|
||||||
|
|
||||||
|
ASSERT_TRUE(!testable_period_.trickplay_cache().empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't put different audio languages or codecs in the same AdaptationSet.
|
// Don't put different audio languages or codecs in the same AdaptationSet.
|
||||||
|
|
Loading…
Reference in New Issue