Fix HLS packaging failure with clear lead = 0

- Add MuxerListener::OnEncryptionStart() for notifying that further
  segments are encrypted.

b/29621230

Change-Id: I881b29d55baaf3d04e005a3b95d898071c3f272b
This commit is contained in:
Rintaro Kuroiwa 2016-07-05 16:32:19 -07:00
parent e0c5874d31
commit db70721e35
17 changed files with 244 additions and 30 deletions

View File

@ -146,7 +146,6 @@ bool SimpleHlsNotifier::NotifyNewStream(const MediaInfo& media_info,
const std::string& group_id,
uint32_t* stream_id) {
DCHECK(stream_id);
*stream_id = sequence_number_.GetNext();
MediaPlaylist::MediaPlaylistType type;
switch (profile()) {
@ -171,6 +170,7 @@ bool SimpleHlsNotifier::NotifyNewStream(const MediaInfo& media_info,
return false;
}
*stream_id = sequence_number_.GetNext();
base::AutoLock auto_lock(lock_);
master_playlist_->AddMediaPlaylist(media_playlist.get());
media_playlist_map_.insert(

View File

@ -36,6 +36,12 @@ void HlsNotifyMuxerListener::OnEncryptionInfoReady(
const std::vector<uint8_t>& key_id,
const std::vector<uint8_t>& iv,
const std::vector<ProtectionSystemSpecificInfo>& key_system_infos) {
if (!media_started_) {
next_key_id_ = key_id;
next_iv_ = iv;
next_key_system_infos_ = key_system_infos;
return;
}
for (const ProtectionSystemSpecificInfo& info : key_system_infos) {
const bool result = hls_notifier_->NotifyEncryptionUpdate(
stream_id_, key_id, info.system_id(), iv, info.pssh_data());
@ -43,6 +49,28 @@ void HlsNotifyMuxerListener::OnEncryptionInfoReady(
}
}
void HlsNotifyMuxerListener::OnEncryptionStart() {
if (!media_started_) {
DLOG(WARNING) << "Media not started, cannot notify encryption start.";
return;
}
if (next_key_id_.empty()) {
DCHECK(next_iv_.empty());
DCHECK(next_key_system_infos_.empty());
return;
}
for (const ProtectionSystemSpecificInfo& info : next_key_system_infos_) {
const bool result = hls_notifier_->NotifyEncryptionUpdate(
stream_id_, next_key_id_, info.system_id(), next_iv_,
info.pssh_data());
LOG_IF(WARNING, !result) << "Failed to add encryption info";
}
next_key_id_.clear();
next_iv_.clear();
next_key_system_infos_.clear();
}
void HlsNotifyMuxerListener::OnMediaStart(const MuxerOptions& muxer_options,
const StreamInfo& stream_info,
uint32_t time_scale,
@ -56,7 +84,12 @@ void HlsNotifyMuxerListener::OnMediaStart(const MuxerOptions& muxer_options,
const bool result = hls_notifier_->NotifyNewStream(
media_info, playlist_name_, ext_x_media_name_, ext_x_media_group_id_,
&stream_id_);
LOG_IF(WARNING, !result) << "Failed to notify new stream.";
if (!result) {
LOG(WARNING) << "Failed to notify new stream.";
return;
}
media_started_ = true;
}
void HlsNotifyMuxerListener::OnSampleDurationReady(uint32_t sample_duration) {}

View File

@ -45,6 +45,7 @@ class HlsNotifyMuxerListener : public MuxerListener {
const std::vector<uint8_t>& iv,
const std::vector<ProtectionSystemSpecificInfo>&
key_system_info) override;
void OnEncryptionStart() override;
void OnMediaStart(const MuxerOptions& muxer_options,
const StreamInfo& stream_info,
uint32_t time_scale,
@ -71,6 +72,12 @@ class HlsNotifyMuxerListener : public MuxerListener {
hls::HlsNotifier* const hls_notifier_;
uint32_t stream_id_ = 0;
bool media_started_ = false;
// Cached encryption info before OnMediaStart() is called.
std::vector<uint8_t> next_key_id_;
std::vector<uint8_t> next_iv_;
std::vector<ProtectionSystemSpecificInfo> next_key_system_infos_;
DISALLOW_COPY_AND_ASSIGN(HlsNotifyMuxerListener);
};

View File

@ -83,7 +83,126 @@ class HlsNotifyMuxerListenerTest : public ::testing::Test {
HlsNotifyMuxerListener listener_;
};
// Verify that NotifyEncryptionUpdate() is not called before OnMediaStart() is
// called.
TEST_F(HlsNotifyMuxerListenerTest, OnEncryptionInfoReadyBeforeMediaStart) {
ProtectionSystemSpecificInfo info;
std::vector<uint8_t> system_id(kAnySystemId,
kAnySystemId + arraysize(kAnySystemId));
info.set_system_id(system_id.data(), system_id.size());
std::vector<uint8_t> pssh_data(kAnyData, kAnyData + arraysize(kAnyData));
info.set_pssh_data(pssh_data);
std::vector<uint8_t> key_id(16, 0x05);
std::vector<ProtectionSystemSpecificInfo> key_system_infos;
key_system_infos.push_back(info);
std::vector<uint8_t> iv(16, 0x54);
EXPECT_CALL(mock_notifier_, NotifyEncryptionUpdate(_, _, _, _, _)).Times(0);
listener_.OnEncryptionInfoReady(kInitialEncryptionInfo, FOURCC_cbcs, key_id,
iv, key_system_infos);
}
TEST_F(HlsNotifyMuxerListenerTest, OnMediaStart) {
VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams();
scoped_refptr<StreamInfo> video_stream_info =
CreateVideoStreamInfo(video_params);
EXPECT_CALL(mock_notifier_,
NotifyNewStream(_, StrEq(kDefaultPlaylistName),
StrEq("DEFAULTNAME"), StrEq("DEFAULTGROUPID"), _))
.WillOnce(Return(true));
MuxerOptions muxer_options;
listener_.OnMediaStart(muxer_options, *video_stream_info, 90000,
MuxerListener::kContainerMpeg2ts);
}
// OnEncryptionStart() should NotifyEncryptionUpdate() after
// OnEncryptionInfoReady() and OnMediaStart().
TEST_F(HlsNotifyMuxerListenerTest, OnEncryptionStart) {
ProtectionSystemSpecificInfo info;
std::vector<uint8_t> system_id(kAnySystemId,
kAnySystemId + arraysize(kAnySystemId));
info.set_system_id(system_id.data(), system_id.size());
std::vector<uint8_t> pssh_data(kAnyData, kAnyData + arraysize(kAnyData));
info.set_pssh_data(pssh_data);
std::vector<uint8_t> key_id(16, 0x05);
std::vector<ProtectionSystemSpecificInfo> key_system_infos;
key_system_infos.push_back(info);
std::vector<uint8_t> iv(16, 0x54);
EXPECT_CALL(mock_notifier_, NotifyEncryptionUpdate(_, _, _, _, _)).Times(0);
listener_.OnEncryptionInfoReady(kInitialEncryptionInfo, FOURCC_cbcs, key_id,
iv, key_system_infos);
::testing::Mock::VerifyAndClearExpectations(&mock_notifier_);
ON_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _))
.WillByDefault(Return(true));
VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams();
scoped_refptr<StreamInfo> video_stream_info =
CreateVideoStreamInfo(video_params);
MuxerOptions muxer_options;
EXPECT_CALL(mock_notifier_, NotifyEncryptionUpdate(_, _, _, _, _)).Times(0);
listener_.OnMediaStart(muxer_options, *video_stream_info, 90000,
MuxerListener::kContainerMpeg2ts);
::testing::Mock::VerifyAndClearExpectations(&mock_notifier_);
EXPECT_CALL(mock_notifier_,
NotifyEncryptionUpdate(_, key_id, system_id, iv, pssh_data))
.WillOnce(Return(true));
listener_.OnEncryptionStart();
}
// NotifyEncryptionUpdate() should not be called if NotifyNewStream() fails in
// OnMediaStart().
TEST_F(HlsNotifyMuxerListenerTest, NoEncryptionUpdateIfNotifyNewStreamFails) {
ProtectionSystemSpecificInfo info;
std::vector<uint8_t> system_id(kAnySystemId,
kAnySystemId + arraysize(kAnySystemId));
info.set_system_id(system_id.data(), system_id.size());
std::vector<uint8_t> pssh_data(kAnyData, kAnyData + arraysize(kAnyData));
info.set_pssh_data(pssh_data);
std::vector<uint8_t> key_id(16, 0x05);
std::vector<ProtectionSystemSpecificInfo> key_system_infos;
key_system_infos.push_back(info);
std::vector<uint8_t> iv(16, 0x54);
EXPECT_CALL(mock_notifier_, NotifyEncryptionUpdate(_, _, _, _, _)).Times(0);
listener_.OnEncryptionInfoReady(kInitialEncryptionInfo, FOURCC_cbcs, key_id,
iv, key_system_infos);
::testing::Mock::VerifyAndClearExpectations(&mock_notifier_);
EXPECT_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _))
.WillOnce(Return(false));
VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams();
scoped_refptr<StreamInfo> video_stream_info =
CreateVideoStreamInfo(video_params);
MuxerOptions muxer_options;
EXPECT_CALL(mock_notifier_, NotifyEncryptionUpdate(_, _, _, _, _)).Times(0);
listener_.OnMediaStart(muxer_options, *video_stream_info, 90000,
MuxerListener::kContainerMpeg2ts);
}
// Verify that after OnMediaStart(), OnEncryptionInfoReady() calls
// NotifyEncryptionUpdate().
TEST_F(HlsNotifyMuxerListenerTest, OnEncryptionInfoReady) {
ON_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _))
.WillByDefault(Return(true));
VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams();
scoped_refptr<StreamInfo> video_stream_info =
CreateVideoStreamInfo(video_params);
MuxerOptions muxer_options;
listener_.OnMediaStart(muxer_options, *video_stream_info, 90000,
MuxerListener::kContainerMpeg2ts);
ProtectionSystemSpecificInfo info;
std::vector<uint8_t> system_id(kAnySystemId,
kAnySystemId + arraysize(kAnySystemId));
@ -104,21 +223,6 @@ TEST_F(HlsNotifyMuxerListenerTest, OnEncryptionInfoReady) {
iv, key_system_infos);
}
TEST_F(HlsNotifyMuxerListenerTest, OnMediaStart) {
VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams();
scoped_refptr<StreamInfo> video_stream_info =
CreateVideoStreamInfo(video_params);
EXPECT_CALL(mock_notifier_,
NotifyNewStream(_, StrEq(kDefaultPlaylistName),
StrEq("DEFAULTNAME"), StrEq("DEFAULTGROUPID"), _))
.WillOnce(Return(true));
MuxerOptions muxer_options;
listener_.OnMediaStart(muxer_options, *video_stream_info, 90000,
MuxerListener::kContainerMpeg2ts);
}
// Make sure it doesn't crash.
TEST_F(HlsNotifyMuxerListenerTest, OnSampleDurationReady) {
listener_.OnSampleDurationReady(2340);

View File

@ -30,6 +30,8 @@ class MockMuxerListener : public MuxerListener {
const std::vector<uint8_t>& iv,
const std::vector<ProtectionSystemSpecificInfo>& key_system_info));
MOCK_METHOD0(OnEncryptionStart, void());
MOCK_METHOD4(OnMediaStart,
void(const MuxerOptions& muxer_options,
const StreamInfo& stream_info,

View File

@ -54,6 +54,8 @@ void MpdNotifyMuxerListener::OnEncryptionInfoReady(
}
}
void MpdNotifyMuxerListener::OnEncryptionStart() {}
void MpdNotifyMuxerListener::OnMediaStart(
const MuxerOptions& muxer_options,
const StreamInfo& stream_info,

View File

@ -39,6 +39,7 @@ class MpdNotifyMuxerListener : public MuxerListener {
const std::vector<uint8_t>& iv,
const std::vector<ProtectionSystemSpecificInfo>&
key_system_info) override;
void OnEncryptionStart() override;
void OnMediaStart(const MuxerOptions& muxer_options,
const StreamInfo& stream_info,
uint32_t time_scale,

View File

@ -64,6 +64,12 @@ class MuxerListener {
const std::vector<uint8_t>& iv,
const std::vector<ProtectionSystemSpecificInfo>& key_system_info) = 0;
/// Called when the muxer starts encrypting the segments.
/// Further segments notified via OnNewSegment() are encrypted.
/// This may be called more than once e.g. per segment, but the semantics does
/// not change.
virtual void OnEncryptionStart() = 0;
/// Called when muxing starts.
/// For MPEG DASH Live profile, the initialization segment information is
/// available from StreamInfo.

View File

@ -62,6 +62,8 @@ void VodMediaInfoDumpMuxerListener::OnMediaStart(
}
}
void VodMediaInfoDumpMuxerListener::OnEncryptionStart() {}
void VodMediaInfoDumpMuxerListener::OnSampleDurationReady(
uint32_t sample_duration) {
// Assume one VideoInfo.

View File

@ -38,6 +38,7 @@ class VodMediaInfoDumpMuxerListener : public MuxerListener {
const std::vector<uint8_t>& iv,
const std::vector<ProtectionSystemSpecificInfo>&
key_system_info) override;
void OnEncryptionStart() override;
void OnMediaStart(const MuxerOptions& muxer_options,
const StreamInfo& stream_info,
uint32_t time_scale,

View File

@ -57,8 +57,18 @@ Status TsSegmenter::Initialize(const StreamInfo& stream_info,
}
if (!status.ok())
return status;
encryption_key_ = encryption_key.Pass();
clear_lead_in_seconds_ = clear_lead_in_seconds;
if (listener_) {
// For now this only happens once, so send true.
const bool kIsInitialEncryptionInfo = true;
listener_->OnEncryptionInfoReady(
kIsInitialEncryptionInfo, FOURCC_cbcs, encryption_key_->key_id,
encryption_key_->iv, encryption_key_->key_system_info);
}
status = NotifyEncrypted();
if (!status.ok())
return status;
@ -174,13 +184,8 @@ Status TsSegmenter::Flush() {
Status TsSegmenter::NotifyEncrypted() {
if (encryption_key_ && total_duration_in_seconds_ >= clear_lead_in_seconds_) {
if (listener_) {
// For now this only happens once, so send true.
const bool kIsInitialEncryptionInfo = true;
listener_->OnEncryptionInfoReady(
kIsInitialEncryptionInfo, FOURCC_cbcs, encryption_key_->key_id,
encryption_key_->iv, encryption_key_->key_system_info);
}
if (listener_)
listener_->OnEncryptionStart();
if (!pes_packet_generator_->SetEncryptionKey(encryption_key_.Pass()))
return Status(error::INTERNAL_ERROR, "Failed to set encryption key.");

View File

@ -481,6 +481,44 @@ TEST_F(TsSegmenterTest, WithEncryptionNoClearLead) {
kClearLeadSeconds));
}
// Verify that the muxer listener pointer is not used without checking that it's
// not null.
TEST_F(TsSegmenterTest, WithEncryptionNoClearLeadNoMuxerListener) {
scoped_refptr<VideoStreamInfo> stream_info(new VideoStreamInfo(
kTrackId, kTimeScale, kDuration, kH264VideoCodec, kCodecString, kLanguage,
kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate,
kNaluLengthSize, kExtraData, arraysize(kExtraData), kIsEncrypted));
MuxerOptions options;
options.segment_duration = 10.0;
options.segment_template = "file$Number$.ts";
TsSegmenter segmenter(options, nullptr);
EXPECT_CALL(*mock_ts_writer_, Initialize(_)).WillOnce(Return(true));
EXPECT_CALL(*mock_ts_writer_, SignalEncypted());
EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_))
.WillOnce(Return(true));
EXPECT_CALL(*mock_pes_packet_generator_, SetEncryptionKeyMock(_))
.WillOnce(Return(true));
segmenter.InjectTsWriterForTesting(mock_ts_writer_.Pass());
segmenter.InjectPesPacketGeneratorForTesting(
mock_pes_packet_generator_.Pass());
MockKeySource mock_key_source;
EXPECT_CALL(mock_key_source, GetKey(KeySource::TRACK_TYPE_HD, _))
.WillOnce(Return(Status::OK));
const uint32_t k480pPixels = 640 * 480;
// Set this to 0 so that Finalize will call
// PesPacketGenerator::SetEncryptionKey().
// Even tho no samples have been added.
const double kClearLeadSeconds = 0;
EXPECT_OK(segmenter.Initialize(*stream_info, &mock_key_source, k480pPixels,
kClearLeadSeconds));
}
// Verify that encryption notification is sent to objects after clear lead.
TEST_F(TsSegmenterTest, WithEncryptionWithClearLead) {
scoped_refptr<VideoStreamInfo> stream_info(new VideoStreamInfo(
@ -568,13 +606,14 @@ TEST_F(TsSegmenterTest, WithEncryptionWithClearLead) {
EXPECT_CALL(mock_key_source, GetKey(KeySource::TRACK_TYPE_HD, _))
.WillOnce(Return(Status::OK));
EXPECT_CALL(mock_listener, OnEncryptionInfoReady(_, _, _, _, _));
EXPECT_OK(segmenter.Initialize(*stream_info, &mock_key_source, 0,
kClearLeadSeconds));
EXPECT_OK(segmenter.AddSample(sample1));
// These should be called AFTER the first AddSample(), before the second
// segment.
EXPECT_CALL(mock_listener, OnEncryptionInfoReady(_, _, _, _, _));
EXPECT_CALL(mock_listener, OnEncryptionStart());
EXPECT_CALL(*mock_pes_packet_generator_raw, SetEncryptionKeyMock(_))
.WillOnce(Return(true));
EXPECT_CALL(*mock_ts_writer_raw, SignalEncypted());

View File

@ -66,7 +66,8 @@ EncryptingFragmenter::EncryptingFragmenter(
int64_t clear_time,
FourCC protection_scheme,
uint8_t crypt_byte_block,
uint8_t skip_byte_block)
uint8_t skip_byte_block,
MuxerListener* listener)
: Fragmenter(info, traf),
info_(info),
encryption_key_(encryption_key.Pass()),
@ -75,7 +76,8 @@ EncryptingFragmenter::EncryptingFragmenter(
clear_time_(clear_time),
protection_scheme_(protection_scheme),
crypt_byte_block_(crypt_byte_block),
skip_byte_block_(skip_byte_block) {
skip_byte_block_(skip_byte_block),
listener_(listener) {
DCHECK(encryption_key_);
switch (video_codec_) {
case kCodecVP8:
@ -143,6 +145,9 @@ Status EncryptingFragmenter::InitializeFragment(int64_t first_sample_dts) {
traf()->header.flags |=
TrackFragmentHeader::kSampleDescriptionIndexPresentMask;
traf()->header.sample_description_index = kClearSampleDescriptionIndex;
} else {
if (listener_)
listener_->OnEncryptionStart();
}
return PrepareFragmentForEncryption(enable_encryption);
}

View File

@ -12,6 +12,7 @@
#include "packager/media/base/fourccs.h"
#include "packager/media/codecs/video_slice_header_parser.h"
#include "packager/media/codecs/vpx_parser.h"
#include "packager/media/event/muxer_listener.h"
#include "packager/media/formats/mp4/fragmenter.h"
namespace shaka {
@ -43,7 +44,8 @@ class EncryptingFragmenter : public Fragmenter {
int64_t clear_time,
FourCC protection_scheme,
uint8_t crypt_byte_block,
uint8_t skip_byte_block);
uint8_t skip_byte_block,
MuxerListener* listener);
~EncryptingFragmenter() override;
@ -95,6 +97,7 @@ class EncryptingFragmenter : public Fragmenter {
const FourCC protection_scheme_;
const uint8_t crypt_byte_block_;
const uint8_t skip_byte_block_;
MuxerListener* listener_;
scoped_ptr<VPxParser> vpx_parser_;
scoped_ptr<VideoSliceHeaderParser> header_parser_;

View File

@ -34,7 +34,8 @@ KeyRotationFragmenter::KeyRotationFragmenter(MovieFragment* moof,
clear_time,
protection_scheme,
crypt_byte_block,
skip_byte_block),
skip_byte_block,
muxer_listener),
moof_(moof),
encryption_key_source_(encryption_key_source),
track_type_(track_type),

View File

@ -259,7 +259,8 @@ Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
fragmenters_[i] = new EncryptingFragmenter(
streams[i]->info(), &moof_->tracks[i], encryption_key.Pass(),
clear_lead_in_seconds * streams[i]->info()->time_scale(),
protection_scheme, pattern.crypt_byte_block, pattern.skip_byte_block);
protection_scheme, pattern.crypt_byte_block, pattern.skip_byte_block,
muxer_listener_);
}
// Choose the first stream if there is no VIDEO.

View File

@ -166,6 +166,8 @@ Status Segmenter::AddSample(scoped_refptr<MediaSample> sample) {
LOG(ERROR) << "Error encrypting frame.";
return status;
}
if (encrypt_frame && muxer_listener_)
muxer_listener_->OnEncryptionStart();
}