Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleanups / Improvements to PSSH, KID, init data #1668

Merged
merged 6 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 86 additions & 62 deletions src/Session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -392,15 +392,15 @@ bool CSession::InitializeDRM(bool addDefaultKID /* = false */)
if (session.m_cencSingleSampleDecrypter)
continue;

std::vector<uint8_t> initData;
std::string drmOptionalKeyParam;

CPeriod::PSSHSet& sessionPsshset = m_adaptiveTree->m_currentPeriod->GetPSSHSets()[ses];
const CPeriod::PSSHSet& sessionPsshset = m_adaptiveTree->m_currentPeriod->GetPSSHSets()[ses];

if (sessionPsshset.adaptation_set_->GetStreamType() == StreamType::NOTYPE)
continue;

const std::vector<uint8_t> defaultKid = DRM::ConvertKidStrToBytes(sessionPsshset.defaultKID_);
std::vector<uint8_t> initData = sessionPsshset.pssh_;
std::string defaultKidStr = sessionPsshset.defaultKID_;
std::string drmOptionalKeyParam;

std::string_view licenseDataStr = CSrvBroker::GetKodiProps().GetLicenseData();

if (m_adaptiveTree->GetTreeType() == adaptive::TreeType::SMOOTH_STREAMING)
Expand All @@ -422,9 +422,8 @@ bool CSession::InitializeDRM(bool addDefaultKID /* = false */)
LOG::Log(LOGDEBUG, "License data: Create Widevine PSSH for SmoothStreaming %s",
licenseData.empty() ? "" : "(with custom data)");

std::vector<uint8_t> wvPsshData;
if (DRM::MakeWidevinePsshData(defaultKid, licenseData, wvPsshData))
DRM::MakePssh(DRM::ID_WIDEVINE, wvPsshData, initData);
initData =
DRM::PSSH::MakeWidevine({DRM::ConvertKidStrToBytes(defaultKidStr)}, licenseData);
}
}
else if (licenseType == "com.microsoft.playready")
Expand All @@ -451,22 +450,28 @@ bool CSession::InitializeDRM(bool addDefaultKID /* = false */)
initData = BASE64::Decode(licenseDataStr);
}

if (initData.empty() && sessionPsshset.m_licenseUrl.empty())
// If no KID, but init data, extract the KID from init data
if (!initData.empty() && defaultKidStr.empty())
{
if (!sessionPsshset.pssh_.empty())
DRM::PSSH parser;
if (parser.Parse(initData) && !parser.GetKeyIds().empty())
{
// Use the init data provided by manifest (e.g. PSSH)
initData = sessionPsshset.pssh_;
}
else if (licenseType != DRM::KS_CLEARKEY)
{
// Try extract the PSSH/KID from the stream
// only if clearkeys are not used (use case e.g. Widevine manifest tested with ClearKey DRM)
if (!ExtractStreamProtectionData(sessionPsshset, initData, m_adaptiveTree->m_supportedKeySystems))
LOG::Log(LOGERROR, "License data: Cannot extract PSSH/KID data from the stream");
LOG::Log(LOGDEBUG, "Default KID parsed from init data");
defaultKidStr = STRING::ToHexadecimal(parser.GetKeyIds()[0]);
}
}

//! @todo: as is implemented InitializeDRM will initialize all PSSHSet's also when are not used,
//! therefore ExtractStreamProtectionData can perform many (not needed) downloads of mp4 init files
if ((initData.empty() && licenseType != DRM::KS_CLEARKEY) || defaultKidStr.empty())
{
// Try extract the PSSH/KID from the stream
ExtractStreamProtectionData(sessionPsshset, defaultKidStr, initData,
m_adaptiveTree->m_supportedKeySystems);
}

const std::vector<uint8_t> defaultKid = DRM::ConvertKidStrToBytes(defaultKidStr);

if (addDefaultKID && ses == 1 && session.m_cencSingleSampleDecrypter)
{
// If the CDM has been pre-initialized, on non-android systems
Expand All @@ -478,7 +483,7 @@ bool CSession::InitializeDRM(bool addDefaultKID /* = false */)

if (m_decrypter && !defaultKid.empty())
{
LOG::Log(LOGDEBUG, "Initializing stream with KID: %s", sessionPsshset.defaultKID_.c_str());
LOG::Log(LOGDEBUG, "Initializing stream with KID: %s", defaultKidStr.c_str());

for (size_t i{1}; i < ses; ++i)
{
Expand Down Expand Up @@ -1457,13 +1462,18 @@ bool CSession::SeekChapter(int ch)
return false;
}

bool CSession::ExtractStreamProtectionData(PLAYLIST::CPeriod::PSSHSet& sessionPsshset,
void CSession::ExtractStreamProtectionData(const PLAYLIST::CPeriod::PSSHSet& psshSet,
std::string& defaultKid,
std::vector<uint8_t>& initData,
std::vector<std::string_view> keySystems)
const std::vector<std::string_view>& keySystems)
{
auto initialRepr = m_reprChooser->GetRepresentation(sessionPsshset.adaptation_set_);
auto initialRepr = m_reprChooser->GetRepresentation(psshSet.adaptation_set_);

if (initialRepr->GetContainerType() != ContainerType::MP4)
return;

CStream stream{m_adaptiveTree, sessionPsshset.adaptation_set_, initialRepr};
LOG::LogF(LOGDEBUG, "Parse protection data from stream");
CStream stream{m_adaptiveTree, psshSet.adaptation_set_, initialRepr};

stream.m_isEnabled = true;
stream.m_adStream.start_stream();
Expand All @@ -1475,61 +1485,75 @@ bool CSession::ExtractStreamProtectionData(PLAYLIST::CPeriod::PSSHSet& sessionPs
{
LOG::LogF(LOGERROR, "No MOOV atom in stream");
stream.Disable();
return false;
return;
}
AP4_Array<AP4_PsshAtom>& pssh{movie->GetPsshAtoms()};

for (std::string_view keySystem : keySystems)
AP4_Track* track =
movie->GetTrack(static_cast<AP4_Track::Type>(stream.m_adStream.GetTrackType()));

if (track) // Try extract the default KID from tenc / piff mp4 box
{
std::vector<uint8_t> systemIdBytes;
STRING::ToHexBytes(DRM::UrnToSystemId(keySystem), systemIdBytes);
AP4_ProtectedSampleDescription* protSampleDesc =
static_cast<AP4_ProtectedSampleDescription*>(track->GetSampleDescription(0));

for (unsigned int i = 0; initData.size() == 0 && i < pssh.ItemCount(); i++)
if (protSampleDesc)
{
if (std::memcmp(pssh[i].GetSystemId(), systemIdBytes.data(), 16) == 0)
AP4_ProtectionSchemeInfo* psi = protSampleDesc->GetSchemeInfo();
if (psi)
{
const AP4_DataBuffer& dataBuf = pssh[i].GetData();

initData.insert(initData.end(), dataBuf.GetData(), dataBuf.GetData() + dataBuf.GetDataSize());

if (sessionPsshset.defaultKID_.empty())
AP4_ContainerAtom* schi = protSampleDesc->GetSchemeInfo()->GetSchiAtom();
if (schi)
{
if (pssh[i].GetKid(0))
AP4_TencAtom* tenc =
AP4_DYNAMIC_CAST(AP4_TencAtom, schi->GetChild(AP4_ATOM_TYPE_TENC, 0));
if (tenc)
{
sessionPsshset.defaultKID_ = STRING::ToHexadecimal(pssh[i].GetKid(0), 16);
defaultKid = STRING::ToHexadecimal(tenc->GetDefaultKid(), 16);
}
else if (AP4_Track* track = movie->GetTrack(
static_cast<AP4_Track::Type>(stream.m_adStream.GetTrackType())))
else
{
AP4_ProtectedSampleDescription* m_protectedDesc =
static_cast<AP4_ProtectedSampleDescription*>(track->GetSampleDescription(0));
AP4_ContainerAtom* schi;
if (m_protectedDesc->GetSchemeInfo() &&
(schi = m_protectedDesc->GetSchemeInfo()->GetSchiAtom()))
AP4_PiffTrackEncryptionAtom* piff =
AP4_DYNAMIC_CAST(AP4_PiffTrackEncryptionAtom,
schi->GetChild(AP4_UUID_PIFF_TRACK_ENCRYPTION_ATOM, 0));
if (piff)
{
AP4_TencAtom* tenc{
AP4_DYNAMIC_CAST(AP4_TencAtom, schi->GetChild(AP4_ATOM_TYPE_TENC, 0)) };
if (tenc)
{
sessionPsshset.defaultKID_ = STRING::ToHexadecimal(tenc->GetDefaultKid(), 16);
}
else
{
AP4_PiffTrackEncryptionAtom* piff{
AP4_DYNAMIC_CAST(AP4_PiffTrackEncryptionAtom,
schi->GetChild(AP4_UUID_PIFF_TRACK_ENCRYPTION_ATOM, 0)) };
if (piff)
{
sessionPsshset.defaultKID_ = STRING::ToHexadecimal(piff->GetDefaultKid(), 16);
}
}
defaultKid = STRING::ToHexadecimal(piff->GetDefaultKid(), 16);
}
}
}
}
}
}

if (initData.empty() || defaultKid.empty())
{
const std::vector<std::string> systemIds = DRM::UrnsToSystemIds(keySystems);
AP4_Array<AP4_PsshAtom>& pssh{movie->GetPsshAtoms()};

for (unsigned int i = 0; i < pssh.ItemCount(); ++i)
{
AP4_PsshAtom& psshAtom = pssh[i];

std::string systemId = STRING::ToHexadecimal(psshAtom.GetSystemId(), 16);

// Check if the system id is supported
if (std::find(systemIds.cbegin(), systemIds.cend(), systemId) != systemIds.cend())
{
const AP4_DataBuffer& dataBuf = psshAtom.GetData();
const std::vector<uint8_t> psshData{dataBuf.GetData(),
dataBuf.GetData() + dataBuf.GetDataSize()};

initData = DRM::PSSH::Make(psshAtom.GetSystemId(), {}, psshData);

if (psshAtom.GetKid(0))
{
defaultKid = STRING::ToHexadecimal(pssh[i].GetKid(0), 16);
}

break;
}
}
}

stream.Disable();
return !initData.empty();
}
5 changes: 3 additions & 2 deletions src/Session.h
Original file line number Diff line number Diff line change
Expand Up @@ -334,9 +334,10 @@ class ATTR_DLL_LOCAL CSession : public adaptive::AdaptiveStreamObserver
*/
void DisposeDecrypter();

bool ExtractStreamProtectionData(PLAYLIST::CPeriod::PSSHSet& sessionPsshset,
void ExtractStreamProtectionData(const PLAYLIST::CPeriod::PSSHSet& psshSet,
std::string& defaultKid,
std::vector<uint8_t>& initData,
std::vector<std::string_view> keySystems);
const std::vector<std::string_view>& keySystems);

private:
std::string m_manifestUrl;
Expand Down
4 changes: 4 additions & 0 deletions src/decrypters/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
set(SOURCES
DrmFactory.cpp
Helpers.cpp
HelperPr.cpp
HelperWv.cpp
)

set(HEADERS
DrmFactory.h
Helpers.h
HelperPr.h
HelperWv.h
IDecrypter.h
)

Expand Down
Loading