Fairplay key system support

Support generation of fairplay key system tag: "com.apple.streamingkeydelivery" when --pssh 
includes fairplay key system id:

// Unofficial fairplay system id extracted from
// https://forums.developer.apple.com/thread/6185.
const uint8_t kFairplaySystemId[] = {0x29, 0x70, 0x1F, 0xE4, 0x3C, 0xC7,
                                     0x4A, 0x34, 0x8C, 0x5B, 0xAE, 0x90,
                                     0xC7, 0x43, 0x9A, 0x47};

Closes #258
This commit is contained in:
David Cavar 2017-08-08 19:31:40 +02:00 committed by Kongqun Yang
parent 085e038b89
commit 34c5e011a5
5 changed files with 92 additions and 12 deletions

View File

@ -17,6 +17,7 @@ Anders Hasselqvist <anders.hasselqvist@gmail.com>
Chun-da Chen <capitalm.c@gmail.com> Chun-da Chen <capitalm.c@gmail.com>
Google Inc. <*@google.com> Google Inc. <*@google.com>
Leandro Moreira <leandro.ribeiro.moreira@gmail.com> Leandro Moreira <leandro.ribeiro.moreira@gmail.com>
More Screens Ltd. <*@morescreens.net>
Philo Inc. <*@philo.com> Philo Inc. <*@philo.com>
Richard Eklycke <richard@eklycke.se> Richard Eklycke <richard@eklycke.se>
Sergio Ammirata <sergio@ammirata.net> Sergio Ammirata <sergio@ammirata.net>

View File

@ -25,6 +25,7 @@
Anders Hasselqvist <anders.hasselqvist@gmail.com> Anders Hasselqvist <anders.hasselqvist@gmail.com>
Bei Li <beil@google.com> Bei Li <beil@google.com>
Chun-da Chen <capitalm.c@gmail.com> Chun-da Chen <capitalm.c@gmail.com>
David Cavar <pal3thorn@gmail.com>
Gabe Kopley <gabe@philo.com> Gabe Kopley <gabe@philo.com>
Haoming Chen <hmchen@google.com> Haoming Chen <hmchen@google.com>
Jacob Trimble <modmaker@google.com> Jacob Trimble <modmaker@google.com>

View File

@ -27,6 +27,7 @@ namespace hls {
namespace { namespace {
const char kUriBase64Prefix[] = "data:text/plain;base64,"; const char kUriBase64Prefix[] = "data:text/plain;base64,";
const char kUriFairplayPrefix[] = "skd://";
const char kWidevineDashIfIopUUID[] = const char kWidevineDashIfIopUUID[] =
"urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"; "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed";
@ -41,6 +42,22 @@ bool IsCommonSystemId(const std::vector<uint8_t>& system_id) {
std::equal(system_id.begin(), system_id.end(), media::kCommonSystemId); std::equal(system_id.begin(), system_id.end(), media::kCommonSystemId);
} }
bool IsFairplaySystemId(const std::vector<uint8_t>& system_id) {
return system_id.size() == arraysize(media::kFairplaySystemId) &&
std::equal(system_id.begin(), system_id.end(), media::kFairplaySystemId);
}
std::string Base64EncodeData(const std::string& prefix,
const std::string& data) {
std::string data_base64;
base::Base64Encode(data, &data_base64);
return prefix + data_base64;
}
std::string VectorToString(const std::vector<uint8_t>& v) {
return std::string(v.begin(), v.end());
}
// TODO(rkuroiwa): Dedup these with the functions in MpdBuilder. // TODO(rkuroiwa): Dedup these with the functions in MpdBuilder.
std::string MakePathRelative(const std::string& original_path, std::string MakePathRelative(const std::string& original_path,
const std::string& output_dir) { const std::string& output_dir) {
@ -157,11 +174,10 @@ void NotifyEncryptionToMediaPlaylist(
if (!key_id.empty()) { if (!key_id.empty()) {
key_id_string = "0x" + base::HexEncode(key_id.data(), key_id.size()); key_id_string = "0x" + base::HexEncode(key_id.data(), key_id.size());
} }
std::string key_uri_data_base64;
base::Base64Encode(uri, &key_uri_data_base64);
media_playlist->AddEncryptionInfo( media_playlist->AddEncryptionInfo(
encryption_method, encryption_method,
kUriBase64Prefix + key_uri_data_base64, key_id_string, iv_string, uri, key_id_string, iv_string,
key_format, key_format_version); key_format, key_format_version);
} }
@ -179,8 +195,9 @@ bool HandleWidevineKeyFormats(
&key_uri_data)) { &key_uri_data)) {
return false; return false;
} }
// This format does not have a key id field. std::string key_uri_data_base64 =
NotifyEncryptionToMediaPlaylist(encryption_method, key_uri_data, Base64EncodeData(kUriBase64Prefix, key_uri_data);
NotifyEncryptionToMediaPlaylist(encryption_method, key_uri_data_base64,
std::vector<uint8_t>(), iv, "com.widevine", std::vector<uint8_t>(), iv, "com.widevine",
"1", media_playlist); "1", media_playlist);
} }
@ -188,8 +205,11 @@ bool HandleWidevineKeyFormats(
std::string pssh_as_string( std::string pssh_as_string(
reinterpret_cast<const char*>(protection_system_specific_data.data()), reinterpret_cast<const char*>(protection_system_specific_data.data()),
protection_system_specific_data.size()); protection_system_specific_data.size());
NotifyEncryptionToMediaPlaylist(encryption_method, pssh_as_string, key_id, iv, std::string key_uri_data_base64 =
kWidevineDashIfIopUUID, "1", media_playlist); Base64EncodeData(kUriBase64Prefix, pssh_as_string);
NotifyEncryptionToMediaPlaylist(encryption_method, key_uri_data_base64,
key_id, iv, kWidevineDashIfIopUUID, "1",
media_playlist);
return true; return true;
} }
@ -356,13 +376,31 @@ bool SimpleHlsNotifier::NotifyEncryptionUpdate(
if (IsCommonSystemId(system_id)) { if (IsCommonSystemId(system_id)) {
// Use key_id as the key_uri. The player needs to have custom logic to // Use key_id as the key_uri. The player needs to have custom logic to
// convert it to the actual key url. // convert it to the actual key url.
std::string key_uri_data; std::string key_uri_data = VectorToString(key_id);
key_uri_data.assign(key_id.begin(), key_id.end()); std::string key_uri_data_base64 =
Base64EncodeData(kUriBase64Prefix, key_uri_data);
NotifyEncryptionToMediaPlaylist(encryption_method, NotifyEncryptionToMediaPlaylist(encryption_method,
key_uri_data, std::vector<uint8_t>(), iv, key_uri_data_base64, std::vector<uint8_t>(),
"identity", "", media_playlist.get()); iv, "identity", "", media_playlist.get());
return true; return true;
} }
if (IsFairplaySystemId(system_id)) {
// Use key_id as the key_uri. The player needs to have custom logic to
// convert it to the actual key url.
std::string key_uri_data = VectorToString(key_id);
std::string key_uri_data_base64 =
Base64EncodeData(kUriFairplayPrefix, key_uri_data);
// Fairplay defines IV to be carried with the key, not the playlist.
NotifyEncryptionToMediaPlaylist(encryption_method,
key_uri_data_base64, std::vector<uint8_t>(),
std::vector<uint8_t>(),
"com.apple.streamingkeydelivery", "1",
media_playlist.get());
return true;
}
LOG(ERROR) << "Unknown system ID: " LOG(ERROR) << "Unknown system ID: "
<< base::HexEncode(system_id.data(), system_id.size()); << base::HexEncode(system_id.data(), system_id.size());
return false; return false;

View File

@ -31,6 +31,7 @@ using ::testing::_;
namespace { namespace {
const char kMasterPlaylistName[] = "master.m3u8"; const char kMasterPlaylistName[] = "master.m3u8";
const HlsPlaylistType kVodPlaylist = HlsPlaylistType::kVod; const HlsPlaylistType kVodPlaylist = HlsPlaylistType::kVod;
const HlsPlaylistType kLivePlaylist = HlsPlaylistType::kLive;
class MockMasterPlaylist : public MasterPlaylist { class MockMasterPlaylist : public MasterPlaylist {
public: public:
@ -94,7 +95,10 @@ class SimpleHlsNotifierTest : public ::testing::Test {
media::kWidevineSystemId + arraysize(media::kWidevineSystemId)), media::kWidevineSystemId + arraysize(media::kWidevineSystemId)),
common_system_id_( common_system_id_(
media::kCommonSystemId, media::kCommonSystemId,
media::kCommonSystemId + arraysize(media::kCommonSystemId)) {} media::kCommonSystemId + arraysize(media::kCommonSystemId)),
fairplay_system_id_(
media::kFairplaySystemId,
media::kFairplaySystemId + arraysize(media::kFairplaySystemId)) {}
void InjectMediaPlaylistFactory(std::unique_ptr<MediaPlaylistFactory> factory, void InjectMediaPlaylistFactory(std::unique_ptr<MediaPlaylistFactory> factory,
SimpleHlsNotifier* notifier) { SimpleHlsNotifier* notifier) {
@ -139,6 +143,7 @@ class SimpleHlsNotifierTest : public ::testing::Test {
const std::vector<uint8_t> widevine_system_id_; const std::vector<uint8_t> widevine_system_id_;
const std::vector<uint8_t> common_system_id_; const std::vector<uint8_t> common_system_id_;
const std::vector<uint8_t> fairplay_system_id_;
}; };
TEST_F(SimpleHlsNotifierTest, Init) { TEST_F(SimpleHlsNotifierTest, Init) {
@ -645,6 +650,35 @@ TEST_F(SimpleHlsNotifierTest, EncryptionScheme) {
stream_id, key_id, common_system_id_, iv, dummy_pssh_data)); stream_id, key_id, common_system_id_, iv, dummy_pssh_data));
} }
// Verify that the Fairplay systemID is correctly handled when constructing
// encryption info.
TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateFairplay) {
// Pointer released by SimpleHlsNotifier.
MockMediaPlaylist* mock_media_playlist =
new MockMediaPlaylist(kLivePlaylist, "playlist.m3u8", "", "");
SimpleHlsNotifier notifier(kLivePlaylist, kTestTimeShiftBufferDepth,
kTestPrefix, kAnyOutputDir, kMasterPlaylistName);
const uint32_t stream_id =
SetupStream(kSampleAesProtectionScheme, mock_media_playlist, &notifier);
const std::vector<uint8_t> key_id(16, 0x12);
const std::vector<uint8_t> dummy_pssh_data(10, 'p');
std::string expected_key_uri_base64;
base::Base64Encode(std::string(key_id.begin(), key_id.end()),
&expected_key_uri_base64);
EXPECT_CALL(
*mock_media_playlist,
AddEncryptionInfo(
MediaPlaylist::EncryptionMethod::kSampleAes,
StrEq("skd://" + expected_key_uri_base64),
StrEq(""),
StrEq(""),
StrEq("com.apple.streamingkeydelivery"), StrEq("1")));
EXPECT_TRUE(notifier.NotifyEncryptionUpdate(
stream_id, key_id, fairplay_system_id_, std::vector<uint8_t>(),
dummy_pssh_data));
}
// If using 'cenc' with Widevine, don't output the json form. // If using 'cenc' with Widevine, don't output the json form.
TEST_F(SimpleHlsNotifierTest, WidevineCencEncryptionScheme) { TEST_F(SimpleHlsNotifierTest, WidevineCencEncryptionScheme) {
// Pointer released by SimpleHlsNotifier. // Pointer released by SimpleHlsNotifier.

View File

@ -23,6 +23,12 @@ const uint8_t kCommonSystemId[] = {0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2,
0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e,
0x52, 0xe2, 0xfb, 0x4b}; 0x52, 0xe2, 0xfb, 0x4b};
// Unofficial fairplay system id extracted from
// https://forums.developer.apple.com/thread/6185.
const uint8_t kFairplaySystemId[] = {0x29, 0x70, 0x1F, 0xE4, 0x3C, 0xC7,
0x4A, 0x34, 0x8C, 0x5B, 0xAE, 0x90,
0xC7, 0x43, 0x9A, 0x47};
/// A key source that uses fixed keys for encryption. /// A key source that uses fixed keys for encryption.
class FixedKeySource : public KeySource { class FixedKeySource : public KeySource {
public: public: