mirror of
https://github.com/axiomatic-systems/Bento4.git
synced 2026-01-19 00:05:12 +08:00
1198 lines
49 KiB
C++
1198 lines
49 KiB
C++
/*****************************************************************
|
|
|
|
|
| AP4 - Marlin File Format Support
|
|
|
|
|
| Copyright 2002-2008 Axiomatic Systems, LLC
|
|
|
|
|
|
|
|
| This file is part of Bento4/AP4 (MP4 Atom Processing Library).
|
|
|
|
|
| Unless you have obtained Bento4 under a difference license,
|
|
| this version of Bento4 is Bento4|GPL.
|
|
| Bento4|GPL is free software; you can redistribute it and/or modify
|
|
| it under the terms of the GNU General Public License as published by
|
|
| the Free Software Foundation; either version 2, or (at your option)
|
|
| any later version.
|
|
|
|
|
| Bento4|GPL 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 General Public License for more details.
|
|
|
|
|
| You should have received a copy of the GNU General Public License
|
|
| along with Bento4|GPL; see the file COPYING. If not, write to the
|
|
| Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
|
|
| 02111-1307, USA.
|
|
|
|
|
****************************************************************/
|
|
|
|
/*----------------------------------------------------------------------
|
|
| includes
|
|
+---------------------------------------------------------------------*/
|
|
#include "Ap4SchmAtom.h"
|
|
#include "Ap4StsdAtom.h"
|
|
#include "Ap4Sample.h"
|
|
#include "Ap4StreamCipher.h"
|
|
#include "Ap4FrmaAtom.h"
|
|
#include "Ap4Utils.h"
|
|
#include "Ap4TrakAtom.h"
|
|
#include "Ap4FtypAtom.h"
|
|
#include "Ap4IodsAtom.h"
|
|
#include "Ap4MoovAtom.h"
|
|
#include "Ap4Track.h"
|
|
#include "Ap4DescriptorFactory.h"
|
|
#include "Ap4CommandFactory.h"
|
|
#include "Ap4Marlin.h"
|
|
#include "Ap4FileByteStream.h"
|
|
#include "Ap4Ipmp.h"
|
|
#include "Ap4AesBlockCipher.h"
|
|
#include "Ap4SyntheticSampleTable.h"
|
|
#include "Ap4HdlrAtom.h"
|
|
#include "Ap4Hmac.h"
|
|
#include "Ap4KeyWrap.h"
|
|
#include "Ap4ByteStream.h"
|
|
|
|
/*----------------------------------------------------------------------
|
|
| dynamic cast support
|
|
+---------------------------------------------------------------------*/
|
|
AP4_DEFINE_DYNAMIC_CAST_ANCHOR(AP4_MkidAtom)
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MarlinIpmpAtomTypeHandler
|
|
+---------------------------------------------------------------------*/
|
|
class AP4_MarlinIpmpAtomTypeHandler : public AP4_AtomFactory::TypeHandler
|
|
{
|
|
public:
|
|
// constructor
|
|
AP4_MarlinIpmpAtomTypeHandler(AP4_AtomFactory* atom_factory) :
|
|
m_AtomFactory(atom_factory) {}
|
|
virtual AP4_Result CreateAtom(AP4_Atom::Type type,
|
|
AP4_UI32 size,
|
|
AP4_ByteStream& stream,
|
|
AP4_Atom::Type context,
|
|
AP4_Atom*& atom);
|
|
|
|
private:
|
|
// members
|
|
AP4_AtomFactory* m_AtomFactory;
|
|
};
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MarlinIpmpAtomFactory
|
|
+---------------------------------------------------------------------*/
|
|
class AP4_MarlinIpmpAtomFactory : public AP4_DefaultAtomFactory
|
|
{
|
|
public:
|
|
// class members
|
|
static AP4_MarlinIpmpAtomFactory Instance;
|
|
|
|
// constructor
|
|
AP4_MarlinIpmpAtomFactory() {
|
|
AddTypeHandler(new AP4_MarlinIpmpAtomTypeHandler(this));
|
|
}
|
|
};
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MarlinIpmpAtomFactory::Instance
|
|
+---------------------------------------------------------------------*/
|
|
AP4_MarlinIpmpAtomFactory AP4_MarlinIpmpAtomFactory::Instance;
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MarlinIpmpAtomTypeHandler::CreateAtom
|
|
+---------------------------------------------------------------------*/
|
|
AP4_Result
|
|
AP4_MarlinIpmpAtomTypeHandler::CreateAtom(AP4_Atom::Type type,
|
|
AP4_UI32 size,
|
|
AP4_ByteStream& stream,
|
|
AP4_Atom::Type /*context*/,
|
|
AP4_Atom*& atom)
|
|
{
|
|
switch (type) {
|
|
case AP4_ATOM_TYPE_SATR:
|
|
atom = AP4_ContainerAtom::Create(type, size, false, false, stream, *m_AtomFactory);
|
|
break;
|
|
|
|
case AP4_ATOM_TYPE_STYP:
|
|
atom = new AP4_NullTerminatedStringAtom(type, size, stream);
|
|
break;
|
|
|
|
default:
|
|
atom = NULL;
|
|
}
|
|
|
|
return atom ? AP4_SUCCESS : AP4_FAILURE;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MarlinIpmpParser:Parse
|
|
+---------------------------------------------------------------------*/
|
|
AP4_Result
|
|
AP4_MarlinIpmpParser::Parse(AP4_AtomParent& top_level,
|
|
AP4_ByteStream& stream,
|
|
AP4_List<SinfEntry>& sinf_entries,
|
|
bool remove_od_data)
|
|
{
|
|
// check the file type
|
|
AP4_FtypAtom* ftyp = AP4_DYNAMIC_CAST(AP4_FtypAtom, top_level.GetChild(AP4_ATOM_TYPE_FTYP));
|
|
if (ftyp == NULL ||
|
|
(ftyp->GetMajorBrand() != AP4_MARLIN_BRAND_MGSV && !ftyp->HasCompatibleBrand(AP4_MARLIN_BRAND_MGSV))) {
|
|
return AP4_ERROR_INVALID_FORMAT;
|
|
}
|
|
|
|
// check the initial object descriptor and get the OD Track ID
|
|
AP4_IodsAtom* iods = AP4_DYNAMIC_CAST(AP4_IodsAtom, top_level.FindChild("moov/iods"));
|
|
AP4_UI32 od_track_id = 0;
|
|
if (iods == NULL) return AP4_ERROR_INVALID_FORMAT;
|
|
const AP4_ObjectDescriptor* od = iods->GetObjectDescriptor();
|
|
if (od == NULL) return AP4_ERROR_INVALID_FORMAT;
|
|
AP4_EsIdIncDescriptor* es_id_inc = AP4_DYNAMIC_CAST(AP4_EsIdIncDescriptor, od->FindSubDescriptor(AP4_DESCRIPTOR_TAG_ES_ID_INC));
|
|
if (es_id_inc == NULL) return AP4_ERROR_INVALID_FORMAT;
|
|
od_track_id = es_id_inc->GetTrackId();
|
|
|
|
// find the track pointed to by the descriptor
|
|
AP4_MoovAtom* moov = AP4_DYNAMIC_CAST(AP4_MoovAtom, top_level.GetChild(AP4_ATOM_TYPE_MOOV));
|
|
if (moov == NULL) return AP4_ERROR_INVALID_FORMAT;
|
|
AP4_TrakAtom* od_trak = NULL;
|
|
for (AP4_List<AP4_TrakAtom>::Item* trak_item = moov->GetTrakAtoms().FirstItem();
|
|
trak_item;
|
|
trak_item = trak_item->GetNext()) {
|
|
AP4_TrakAtom* trak = trak_item->GetData();
|
|
if (trak) {
|
|
if (trak->GetId() == od_track_id) {
|
|
od_trak = trak;
|
|
} else {
|
|
sinf_entries.Add(new SinfEntry(trak->GetId(), NULL));
|
|
}
|
|
}
|
|
}
|
|
|
|
// check that we have found the OD track
|
|
if (od_trak == NULL) return AP4_ERROR_INVALID_FORMAT;
|
|
|
|
// look for the 'mpod' trak references
|
|
AP4_TrefTypeAtom* track_references;
|
|
track_references = AP4_DYNAMIC_CAST(AP4_TrefTypeAtom, od_trak->FindChild("tref/mpod"));
|
|
if (track_references == NULL) return AP4_ERROR_INVALID_FORMAT;
|
|
|
|
// create an AP4_Track object from the trak atom and check that it has samples
|
|
AP4_Track* od_track = new AP4_Track(*od_trak, stream, 0);
|
|
if (od_track->GetSampleCount() < 1) {
|
|
delete od_track;
|
|
return AP4_ERROR_INVALID_FORMAT;
|
|
}
|
|
|
|
// get the first sample (in this version, we only look at a single OD command)
|
|
AP4_Sample od_sample;
|
|
AP4_Result result = od_track->GetSample(0, od_sample);
|
|
if (AP4_FAILED(result)) {
|
|
delete od_track;
|
|
return AP4_ERROR_INVALID_FORMAT;
|
|
}
|
|
|
|
// adapt the sample data into a byte stream for parsing
|
|
AP4_DataBuffer sample_data;
|
|
od_sample.ReadData(sample_data);
|
|
AP4_MemoryByteStream* sample_stream = new AP4_MemoryByteStream(sample_data);
|
|
|
|
// look for one ObjectDescriptorUpdate command and
|
|
// one IPMP_DescriptorUpdate command
|
|
AP4_DescriptorUpdateCommand* od_update = NULL;
|
|
AP4_DescriptorUpdateCommand* ipmp_update = NULL;
|
|
do {
|
|
AP4_Command* command = NULL;
|
|
result = AP4_CommandFactory::CreateCommandFromStream(*sample_stream, command);
|
|
if (AP4_SUCCEEDED(result)) {
|
|
// found a command in the sample, check the type
|
|
switch (command->GetTag()) {
|
|
case AP4_COMMAND_TAG_OBJECT_DESCRIPTOR_UPDATE:
|
|
if (od_update == NULL) {
|
|
od_update = AP4_DYNAMIC_CAST(AP4_DescriptorUpdateCommand, command);
|
|
}
|
|
break;
|
|
|
|
case AP4_COMMAND_TAG_IPMP_DESCRIPTOR_UPDATE:
|
|
if (ipmp_update == NULL) {
|
|
ipmp_update = AP4_DYNAMIC_CAST(AP4_DescriptorUpdateCommand, command);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
} while (AP4_SUCCEEDED(result));
|
|
sample_stream->Release();
|
|
sample_stream = NULL;
|
|
|
|
// check that we have what we need
|
|
if (od_update == NULL || ipmp_update == NULL) {
|
|
delete od_track;
|
|
return AP4_ERROR_INVALID_FORMAT;
|
|
}
|
|
|
|
// process all the object descriptors in the od update
|
|
for (AP4_List<AP4_Descriptor>::Item* od_item = od_update->GetDescriptors().FirstItem();
|
|
od_item;
|
|
od_item = od_item->GetNext()) {
|
|
od = AP4_DYNAMIC_CAST(AP4_ObjectDescriptor, od_item->GetData());
|
|
if (od == NULL) continue;
|
|
|
|
// find which track this od references
|
|
AP4_EsIdRefDescriptor* es_id_ref;
|
|
es_id_ref = AP4_DYNAMIC_CAST(AP4_EsIdRefDescriptor, od->FindSubDescriptor(AP4_DESCRIPTOR_TAG_ES_ID_REF));
|
|
if (es_id_ref == NULL ||
|
|
es_id_ref->GetRefIndex() > track_references->GetTrackIds().ItemCount() ||
|
|
es_id_ref->GetRefIndex() == 0) {
|
|
continue;
|
|
}
|
|
AP4_UI32 track_id = track_references->GetTrackIds()[es_id_ref->GetRefIndex()-1];
|
|
SinfEntry* sinf_entry = NULL;
|
|
for (AP4_List<SinfEntry>::Item* sinf_entry_item = sinf_entries.FirstItem();
|
|
sinf_entry_item;
|
|
sinf_entry_item = sinf_entry_item->GetNext()) {
|
|
sinf_entry = sinf_entry_item->GetData();
|
|
if (sinf_entry->m_TrackId == track_id) {
|
|
break; // match
|
|
} else {
|
|
sinf_entry = NULL; // no match
|
|
}
|
|
}
|
|
if (sinf_entry == NULL) continue; // no matching entry
|
|
if (sinf_entry->m_Sinf != NULL) continue; // entry already populated
|
|
|
|
// see what ipmp descriptor this od points to
|
|
AP4_IpmpDescriptorPointer* ipmpd_pointer;
|
|
ipmpd_pointer = AP4_DYNAMIC_CAST(AP4_IpmpDescriptorPointer, od->FindSubDescriptor(AP4_DESCRIPTOR_TAG_IPMP_DESCRIPTOR_POINTER));
|
|
if (ipmpd_pointer == NULL) continue; // no pointer
|
|
|
|
// find the ipmp descriptor referenced by the pointer
|
|
AP4_IpmpDescriptor* ipmpd = NULL;
|
|
for (AP4_List<AP4_Descriptor>::Item* ipmpd_item = ipmp_update->GetDescriptors().FirstItem();
|
|
ipmpd_item;
|
|
ipmpd_item = ipmpd_item->GetNext()) {
|
|
// check that this descriptor is of the right type
|
|
ipmpd = AP4_DYNAMIC_CAST(AP4_IpmpDescriptor, ipmpd_item->GetData());
|
|
if (ipmpd == NULL || ipmpd->GetIpmpsType() != AP4_MARLIN_IPMPS_TYPE_MGSV) continue;
|
|
|
|
// check the descriptor id
|
|
if (ipmpd->GetDescriptorId() == ipmpd_pointer->GetDescriptorId()) {
|
|
break; // match
|
|
} else {
|
|
ipmpd = NULL; // no match
|
|
}
|
|
}
|
|
if (ipmpd == NULL) continue; // no matching entry
|
|
|
|
// parse the ipmp data into one or more 'sinf' atoms, and keep the one with the
|
|
// right type
|
|
AP4_MemoryByteStream* data = new AP4_MemoryByteStream(ipmpd->GetData().GetData(),
|
|
ipmpd->GetData().GetDataSize());
|
|
AP4_LargeSize bytes_available = ipmpd->GetData().GetDataSize();
|
|
do {
|
|
AP4_Atom* atom = NULL;
|
|
|
|
// setup the factory with a context so we can instantiate a 'schm'
|
|
// atom with a slightly different format than the standard 'schm'
|
|
AP4_AtomFactory* factory = &AP4_MarlinIpmpAtomFactory::Instance;
|
|
factory->PushContext(AP4_ATOM_TYPE('m','r','l','n'));
|
|
|
|
// parse the next atom in the stream
|
|
result = factory->CreateAtomFromStream(*data, bytes_available, atom);
|
|
factory->PopContext();
|
|
if (AP4_FAILED(result) || atom == NULL) break;
|
|
|
|
// check that what we have parsed is indeed an 'sinf' of the right type
|
|
if (atom->GetType() == AP4_ATOM_TYPE_SINF) {
|
|
AP4_ContainerAtom* sinf = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom);
|
|
AP4_SchmAtom* schm = AP4_DYNAMIC_CAST(AP4_SchmAtom, sinf->FindChild("schm"));
|
|
if ((schm->GetSchemeType() == AP4_PROTECTION_SCHEME_TYPE_MARLIN_ACBC &&
|
|
schm->GetSchemeVersion() == 0x0100) ||
|
|
(schm->GetSchemeType() == AP4_PROTECTION_SCHEME_TYPE_MARLIN_ACGK &&
|
|
schm->GetSchemeVersion() == 0x0100)) {
|
|
// store the sinf in the entry for that track
|
|
sinf_entry->m_Sinf = sinf;
|
|
break;
|
|
}
|
|
}
|
|
delete atom;
|
|
} while (AP4_SUCCEEDED(result));
|
|
data->Release();
|
|
}
|
|
|
|
// get rid of entries that have no SINF
|
|
AP4_List<SinfEntry>::Item* sinf_entry_item = sinf_entries.FirstItem();
|
|
while (sinf_entry_item) {
|
|
SinfEntry* sinf_entry = sinf_entry_item->GetData();
|
|
sinf_entry_item = sinf_entry_item->GetNext();
|
|
if (sinf_entry->m_Sinf == NULL) {
|
|
sinf_entries.Remove(sinf_entry);
|
|
}
|
|
}
|
|
|
|
// remove the iods atom and the OD track if required
|
|
if (remove_od_data) {
|
|
od_trak->Detach();
|
|
delete od_trak;
|
|
iods->Detach();
|
|
delete iods;
|
|
}
|
|
|
|
// cleanup
|
|
delete od_track;
|
|
|
|
return AP4_SUCCESS;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MarlinIpmpSampleDecrypter::Create
|
|
+---------------------------------------------------------------------*/
|
|
AP4_Result
|
|
AP4_MarlinIpmpSampleDecrypter::Create(AP4_AtomParent& /*top_level*/,
|
|
const AP4_UI08* key,
|
|
AP4_Size key_size,
|
|
AP4_BlockCipherFactory* block_cipher_factory,
|
|
AP4_MarlinIpmpSampleDecrypter*& sample_decrypter)
|
|
{
|
|
// FIXME: need to parse group key info
|
|
return Create(key, key_size, block_cipher_factory, sample_decrypter);
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MarlinIpmpSampleDecrypter::Create
|
|
+---------------------------------------------------------------------*/
|
|
AP4_Result
|
|
AP4_MarlinIpmpSampleDecrypter::Create(const AP4_UI08* key,
|
|
AP4_Size key_size,
|
|
AP4_BlockCipherFactory* block_cipher_factory,
|
|
AP4_MarlinIpmpSampleDecrypter*& sample_decrypter)
|
|
{
|
|
// default value
|
|
sample_decrypter = NULL;
|
|
if (block_cipher_factory == NULL) block_cipher_factory = &AP4_DefaultBlockCipherFactory::Instance;
|
|
|
|
// create a block cipher for the decrypter
|
|
AP4_BlockCipher* block_cipher = NULL;
|
|
AP4_Result result = block_cipher_factory->CreateCipher(AP4_BlockCipher::AES_128,
|
|
AP4_BlockCipher::DECRYPT,
|
|
AP4_BlockCipher::CBC,
|
|
NULL,
|
|
key,
|
|
key_size,
|
|
block_cipher);
|
|
if (AP4_FAILED(result)) return result;
|
|
|
|
// create a CBC cipher
|
|
AP4_CbcStreamCipher* cbc_cipher = new AP4_CbcStreamCipher(block_cipher);
|
|
sample_decrypter = new AP4_MarlinIpmpSampleDecrypter(cbc_cipher);
|
|
|
|
return AP4_SUCCESS;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MarlinIpmpSampleDecrypter::~AP4_MarlinIpmpSampleDecrypter
|
|
+---------------------------------------------------------------------*/
|
|
AP4_MarlinIpmpSampleDecrypter::~AP4_MarlinIpmpSampleDecrypter()
|
|
{
|
|
delete m_Cipher;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MarlinIpmpSampleDecrypter::GetDecryptedSampleSize
|
|
+---------------------------------------------------------------------*/
|
|
AP4_Size
|
|
AP4_MarlinIpmpSampleDecrypter::GetDecryptedSampleSize(AP4_Sample& sample)
|
|
{
|
|
// with CBC, we need to decrypt the last block to know what the padding was
|
|
AP4_Size encrypted_size = sample.GetSize()-AP4_AES_BLOCK_SIZE;
|
|
AP4_DataBuffer encrypted;
|
|
AP4_DataBuffer decrypted;
|
|
AP4_Size decrypted_size = AP4_CIPHER_BLOCK_SIZE;
|
|
if (sample.GetSize() < 2*AP4_CIPHER_BLOCK_SIZE) {
|
|
return 0;
|
|
}
|
|
AP4_Size offset = sample.GetSize()-2*AP4_CIPHER_BLOCK_SIZE;
|
|
if (AP4_FAILED(sample.ReadData(encrypted, 2*AP4_CIPHER_BLOCK_SIZE, offset))) {
|
|
return 0;
|
|
}
|
|
decrypted.Reserve(decrypted_size);
|
|
m_Cipher->SetIV(encrypted.GetData());
|
|
if (AP4_FAILED(m_Cipher->ProcessBuffer(encrypted.GetData()+AP4_CIPHER_BLOCK_SIZE,
|
|
AP4_CIPHER_BLOCK_SIZE,
|
|
decrypted.UseData(),
|
|
&decrypted_size,
|
|
true))) {
|
|
return 0;
|
|
}
|
|
unsigned int padding_size = AP4_CIPHER_BLOCK_SIZE-decrypted_size;
|
|
return encrypted_size-padding_size;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MarlinIpmpSampleDecrypter::DecryptSampleData
|
|
+---------------------------------------------------------------------*/
|
|
AP4_Result
|
|
AP4_MarlinIpmpSampleDecrypter::DecryptSampleData(AP4_DataBuffer& data_in,
|
|
AP4_DataBuffer& data_out,
|
|
const AP4_UI08* /*iv*/)
|
|
{
|
|
AP4_Result result;
|
|
|
|
const AP4_UI08* in = data_in.GetData();
|
|
AP4_Size in_size = data_in.GetDataSize();
|
|
|
|
// default to 0 output
|
|
data_out.SetDataSize(0);
|
|
|
|
// check that we have at least the minimum size
|
|
if (in_size < 2*AP4_AES_BLOCK_SIZE) return AP4_ERROR_INVALID_FORMAT;
|
|
|
|
// process the sample data
|
|
AP4_Size out_size = in_size-AP4_AES_BLOCK_SIZE; // worst case
|
|
data_out.SetDataSize(out_size);
|
|
AP4_UI08* out = data_out.UseData();
|
|
|
|
// decrypt the data
|
|
m_Cipher->SetIV(in);
|
|
result = m_Cipher->ProcessBuffer(in+AP4_AES_BLOCK_SIZE,
|
|
in_size-AP4_AES_BLOCK_SIZE,
|
|
out,
|
|
&out_size,
|
|
true);
|
|
if (AP4_FAILED(result)) return result;
|
|
|
|
// update the payload size
|
|
data_out.SetDataSize(out_size);
|
|
|
|
return AP4_SUCCESS;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MarlinIpmpDecryptingProcessor:AP4_MarlinIpmpDecryptingProcessor
|
|
+---------------------------------------------------------------------*/
|
|
AP4_MarlinIpmpDecryptingProcessor::AP4_MarlinIpmpDecryptingProcessor(
|
|
const AP4_ProtectionKeyMap* key_map, /* = NULL */
|
|
AP4_BlockCipherFactory* block_cipher_factory /* = NULL */){
|
|
if (key_map) {
|
|
// copy the keys
|
|
m_KeyMap.SetKeys(*key_map);
|
|
}
|
|
|
|
if (block_cipher_factory == NULL) {
|
|
m_BlockCipherFactory = &AP4_DefaultBlockCipherFactory::Instance;
|
|
} else {
|
|
m_BlockCipherFactory = block_cipher_factory;
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MarlinIpmpDecryptingProcessor::~AP4_MarlinIpmpDecryptingProcessor
|
|
+---------------------------------------------------------------------*/
|
|
AP4_MarlinIpmpDecryptingProcessor::~AP4_MarlinIpmpDecryptingProcessor()
|
|
{
|
|
m_SinfEntries.DeleteReferences();
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MarlinIpmpDecryptingProcessor:Initialize
|
|
+---------------------------------------------------------------------*/
|
|
AP4_Result
|
|
AP4_MarlinIpmpDecryptingProcessor::Initialize(AP4_AtomParent& top_level,
|
|
AP4_ByteStream& stream,
|
|
ProgressListener* /*listener*/)
|
|
{
|
|
AP4_Result result = AP4_MarlinIpmpParser::Parse(top_level, stream, m_SinfEntries, true);
|
|
if (AP4_FAILED(result)) return result;
|
|
|
|
// update the file type
|
|
AP4_FtypAtom* ftyp = AP4_DYNAMIC_CAST(AP4_FtypAtom, top_level.GetChild(AP4_ATOM_TYPE_FTYP));
|
|
if (ftyp) {
|
|
ftyp->SetMajorBrandAndVersion(AP4_FTYP_BRAND_MP42, 1);
|
|
for (unsigned int i=0; i<ftyp->GetCompatibleBrands().ItemCount(); i++) {
|
|
if (ftyp->GetCompatibleBrands()[i] == AP4_MARLIN_BRAND_MGSV) {
|
|
ftyp->GetCompatibleBrands()[i] = AP4_FTYP_BRAND_MP42;
|
|
}
|
|
}
|
|
}
|
|
|
|
return AP4_SUCCESS;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MarlinIpmpDecryptingProcessor:CreateTrackHandler
|
|
+---------------------------------------------------------------------*/
|
|
AP4_Processor::TrackHandler*
|
|
AP4_MarlinIpmpDecryptingProcessor::CreateTrackHandler(AP4_TrakAtom* trak)
|
|
{
|
|
// look for this track in the list of entries
|
|
AP4_MarlinIpmpParser::SinfEntry* sinf_entry = NULL;
|
|
for (AP4_List<AP4_MarlinIpmpParser::SinfEntry>::Item* sinf_entry_item = m_SinfEntries.FirstItem();
|
|
sinf_entry_item;
|
|
sinf_entry_item = sinf_entry_item->GetNext()) {
|
|
sinf_entry = sinf_entry_item->GetData();
|
|
if (sinf_entry->m_TrackId == trak->GetId()) {
|
|
break; // match
|
|
} else {
|
|
sinf_entry = NULL; // no match
|
|
}
|
|
}
|
|
if (sinf_entry == NULL) return NULL; // no matching entry
|
|
AP4_ContainerAtom* sinf = sinf_entry->m_Sinf;
|
|
|
|
// check the scheme
|
|
bool use_group_key;
|
|
AP4_SchmAtom* schm = AP4_DYNAMIC_CAST(AP4_SchmAtom, sinf->GetChild(AP4_ATOM_TYPE_SCHM));
|
|
if (schm == NULL) return NULL; // no schm
|
|
if (schm->GetSchemeType() == AP4_PROTECTION_SCHEME_TYPE_MARLIN_ACBC &&
|
|
schm->GetSchemeVersion() == 0x0100) {
|
|
use_group_key = false;
|
|
} else if (schm->GetSchemeType() == AP4_PROTECTION_SCHEME_TYPE_MARLIN_ACGK &&
|
|
schm->GetSchemeVersion() == 0x0100) {
|
|
use_group_key = true;
|
|
} else {
|
|
// unsupported scheme
|
|
return NULL;
|
|
}
|
|
|
|
// find the key
|
|
const AP4_DataBuffer* key = NULL;
|
|
AP4_DataBuffer unwrapped_key;
|
|
if (use_group_key) {
|
|
const AP4_DataBuffer* group_key = m_KeyMap.GetKey(0);
|
|
if (group_key == NULL) return NULL; // no group key
|
|
AP4_ContainerAtom* schi = AP4_DYNAMIC_CAST(AP4_ContainerAtom, sinf->GetChild(AP4_ATOM_TYPE_SCHI));
|
|
if (schi == NULL) return NULL; // no schi
|
|
AP4_Atom* gkey = schi->GetChild(AP4_ATOM_TYPE_GKEY);
|
|
if (gkey == NULL) return NULL; // no gkey
|
|
AP4_MemoryByteStream* gkey_data = new AP4_MemoryByteStream();
|
|
gkey->WriteFields(*gkey_data);
|
|
AP4_AesKeyUnwrap(group_key->GetData(), gkey_data->GetData(), gkey_data->GetDataSize(), unwrapped_key);
|
|
key = &unwrapped_key;
|
|
gkey_data->Release();
|
|
} else {
|
|
key = m_KeyMap.GetKey(sinf_entry->m_TrackId);
|
|
}
|
|
if (key == NULL) return NULL;
|
|
|
|
// create the decrypter
|
|
AP4_MarlinIpmpTrackDecrypter* decrypter = NULL;
|
|
AP4_Result result = AP4_MarlinIpmpTrackDecrypter::Create(*m_BlockCipherFactory,
|
|
key->GetData(),
|
|
key->GetDataSize(),
|
|
decrypter);
|
|
if (AP4_FAILED(result)) return NULL;
|
|
|
|
return decrypter;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MarlinIpmpTrackDecrypter::Create
|
|
+---------------------------------------------------------------------*/
|
|
AP4_Result
|
|
AP4_MarlinIpmpTrackDecrypter::Create(AP4_BlockCipherFactory& cipher_factory,
|
|
const AP4_UI08* key,
|
|
AP4_Size key_size,
|
|
AP4_MarlinIpmpTrackDecrypter*& decrypter)
|
|
{
|
|
decrypter = NULL;
|
|
|
|
// create a sample decrypter
|
|
AP4_MarlinIpmpSampleDecrypter* sample_decrypter = NULL;
|
|
AP4_Result result = AP4_MarlinIpmpSampleDecrypter::Create(key, key_size, &cipher_factory, sample_decrypter);
|
|
if (AP4_FAILED(result)) return result;
|
|
|
|
// create the track decrypter
|
|
decrypter = new AP4_MarlinIpmpTrackDecrypter(sample_decrypter);
|
|
|
|
return AP4_SUCCESS;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MarlinIpmpTrackDecrypter::~AP4_MarlinIpmpTrackDecrypter
|
|
+---------------------------------------------------------------------*/
|
|
AP4_MarlinIpmpTrackDecrypter::~AP4_MarlinIpmpTrackDecrypter()
|
|
{
|
|
delete m_SampleDecrypter;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MarlinIpmpTrackDecrypter:GetProcessedSampleSize
|
|
+---------------------------------------------------------------------*/
|
|
AP4_Size
|
|
AP4_MarlinIpmpTrackDecrypter::GetProcessedSampleSize(AP4_Sample& sample)
|
|
{
|
|
return m_SampleDecrypter->GetDecryptedSampleSize(sample);
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MarlinIpmpTrackDecrypter::ProcessSample
|
|
+---------------------------------------------------------------------*/
|
|
AP4_Result
|
|
AP4_MarlinIpmpTrackDecrypter::ProcessSample(AP4_DataBuffer& data_in,
|
|
AP4_DataBuffer& data_out)
|
|
{
|
|
return m_SampleDecrypter->DecryptSampleData(data_in, data_out);
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MarlinIpmpEncryptingProcessor::AP4_MarlinIpmpEncryptingProcessor
|
|
+---------------------------------------------------------------------*/
|
|
AP4_MarlinIpmpEncryptingProcessor::AP4_MarlinIpmpEncryptingProcessor(
|
|
bool use_group_key, /* = false */
|
|
const AP4_ProtectionKeyMap* key_map, /* = NULL */
|
|
AP4_BlockCipherFactory* block_cipher_factory /* = NULL */) :
|
|
m_UseGroupKey(use_group_key)
|
|
{
|
|
if (key_map) {
|
|
// copy the keys
|
|
m_KeyMap.SetKeys(*key_map);
|
|
}
|
|
|
|
if (block_cipher_factory == NULL) {
|
|
m_BlockCipherFactory = &AP4_DefaultBlockCipherFactory::Instance;
|
|
} else {
|
|
m_BlockCipherFactory = block_cipher_factory;
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MarlinIpmpEncryptingProcessor::Initialize
|
|
+---------------------------------------------------------------------*/
|
|
AP4_Result
|
|
AP4_MarlinIpmpEncryptingProcessor::Initialize(
|
|
AP4_AtomParent& top_level,
|
|
AP4_ByteStream& /*stream*/,
|
|
AP4_Processor::ProgressListener* /*listener = NULL*/)
|
|
{
|
|
// get the moov atom
|
|
AP4_MoovAtom* moov = AP4_DYNAMIC_CAST(AP4_MoovAtom, top_level.GetChild(AP4_ATOM_TYPE_MOOV));
|
|
if (moov == NULL) return AP4_ERROR_INVALID_FORMAT;
|
|
|
|
// deal with the file type
|
|
AP4_FtypAtom* ftyp = AP4_DYNAMIC_CAST(AP4_FtypAtom, top_level.GetChild(AP4_ATOM_TYPE_FTYP));
|
|
if (ftyp) {
|
|
// remove the atom, it will be replaced with a new one
|
|
top_level.RemoveChild(ftyp);
|
|
|
|
// keep the existing brand and compatible brands
|
|
AP4_Array<AP4_UI32> compatible_brands;
|
|
compatible_brands.EnsureCapacity(ftyp->GetCompatibleBrands().ItemCount()+1);
|
|
for (unsigned int i=0; i<ftyp->GetCompatibleBrands().ItemCount(); i++) {
|
|
compatible_brands.Append(ftyp->GetCompatibleBrands()[i]);
|
|
}
|
|
|
|
// add the MGSV compatible brand if it is not already there
|
|
if (!ftyp->HasCompatibleBrand(AP4_MARLIN_BRAND_MGSV)) {
|
|
compatible_brands.Append(AP4_MARLIN_BRAND_MGSV);
|
|
}
|
|
|
|
// create a replacement for the major brand
|
|
AP4_FtypAtom* new_ftyp = new AP4_FtypAtom(AP4_MARLIN_BRAND_MGSV,
|
|
0x13c078c, //AP4_MARLIN_BRAND_MGSV_MAJOR_VERSION,
|
|
&compatible_brands[0],
|
|
compatible_brands.ItemCount());
|
|
delete ftyp;
|
|
ftyp = new_ftyp;
|
|
} else {
|
|
AP4_UI32 isom = AP4_FTYP_BRAND_ISOM;
|
|
ftyp = new AP4_FtypAtom(AP4_MARLIN_BRAND_MGSV, 0, &isom, 1);
|
|
}
|
|
|
|
// insert the ftyp atom as the first child
|
|
top_level.AddChild(ftyp, 0);
|
|
|
|
// create and 'mpod' track reference atom
|
|
AP4_TrefTypeAtom* mpod = new AP4_TrefTypeAtom(AP4_ATOM_TYPE_MPOD);
|
|
|
|
// look for an available track ID, starting at 1
|
|
unsigned int od_track_id = 0;
|
|
unsigned int od_track_position = 0;
|
|
for (AP4_List<AP4_TrakAtom>::Item* trak_item = moov->GetTrakAtoms().FirstItem();
|
|
trak_item;
|
|
trak_item = trak_item->GetNext()) {
|
|
AP4_TrakAtom* trak = trak_item->GetData();
|
|
if (trak) {
|
|
od_track_position++;
|
|
if (trak->GetId() >= od_track_id) {
|
|
od_track_id = trak->GetId()+1;
|
|
}
|
|
|
|
// if the track is encrypted, reference it in the mpod
|
|
if (m_KeyMap.GetKey(trak->GetId())) {
|
|
mpod->AddTrackId(trak->GetId());
|
|
}
|
|
|
|
//m_SinfEntries.Add(new SinfEntry(trak->GetId(), NULL));
|
|
}
|
|
}
|
|
|
|
// check that there was at least one track in the file
|
|
if (od_track_id == 0) return AP4_ERROR_INVALID_FORMAT;
|
|
|
|
// create an initial object descriptor
|
|
AP4_InitialObjectDescriptor* iod =
|
|
// FIXME: get real values from the property map
|
|
new AP4_InitialObjectDescriptor(AP4_DESCRIPTOR_TAG_MP4_IOD,
|
|
1022, // object descriptor id
|
|
false,
|
|
0xFE, // OD profile level (0xFE = No OD profile specified)
|
|
0xFF, // scene profile level
|
|
0xFE, // audio profile level
|
|
0xFE, // visual profile level
|
|
0xFF); // graphics profile
|
|
|
|
// create an ES_ID_Inc subdescriptor and add it to the initial object descriptor
|
|
AP4_EsIdIncDescriptor* es_id_inc = new AP4_EsIdIncDescriptor(od_track_id);
|
|
iod->AddSubDescriptor(es_id_inc);
|
|
|
|
// create an iods atom to hold the initial object descriptor
|
|
AP4_IodsAtom* iods = new AP4_IodsAtom(iod);
|
|
|
|
// add the iods atom to the moov atom (try to put it just after mvhd)
|
|
int iods_position = 0;
|
|
int item_position = 0;
|
|
for (AP4_List<AP4_Atom>::Item* moov_item = moov->GetChildren().FirstItem();
|
|
moov_item;
|
|
moov_item = moov_item->GetNext()) {
|
|
++item_position;
|
|
if (moov_item->GetData()->GetType() == AP4_ATOM_TYPE_MVHD) {
|
|
iods_position = item_position;
|
|
break;
|
|
}
|
|
}
|
|
AP4_Result result = moov->AddChild(iods, iods_position);
|
|
if (AP4_FAILED(result)) {
|
|
delete iods;
|
|
return result;
|
|
}
|
|
|
|
// create a sample table for the OD track
|
|
AP4_SyntheticSampleTable* od_sample_table = new AP4_SyntheticSampleTable();
|
|
|
|
// create the sample description for the OD track
|
|
AP4_MpegSystemSampleDescription* od_sample_description;
|
|
od_sample_description = new AP4_MpegSystemSampleDescription(AP4_STREAM_TYPE_OD,
|
|
AP4_OTI_MPEG4_SYSTEM,
|
|
NULL,
|
|
32768, // buffer size
|
|
1024, // max bitrate
|
|
512); // avg bitrate
|
|
od_sample_table->AddSampleDescription(od_sample_description, true);
|
|
|
|
// create the OD descriptor update
|
|
AP4_DescriptorUpdateCommand od_update(AP4_COMMAND_TAG_OBJECT_DESCRIPTOR_UPDATE);
|
|
for (unsigned int i=0; i<mpod->GetTrackIds().ItemCount(); i++) {
|
|
AP4_ObjectDescriptor* od = new AP4_ObjectDescriptor(AP4_DESCRIPTOR_TAG_MP4_OD, (AP4_UI16)(256+i)); // descriptor id = 256+i
|
|
od->AddSubDescriptor(new AP4_EsIdRefDescriptor((AP4_UI16)(i+1))); // index into mpod (1-based)
|
|
od->AddSubDescriptor(new AP4_IpmpDescriptorPointer((AP4_UI08)(i+1))); // descriptor id = i+1
|
|
od_update.AddDescriptor(od);
|
|
}
|
|
|
|
// create the IPMP descriptor update
|
|
AP4_DescriptorUpdateCommand ipmp_update(AP4_COMMAND_TAG_IPMP_DESCRIPTOR_UPDATE);
|
|
for (unsigned int i=0; i<mpod->GetTrackIds().ItemCount(); i++) {
|
|
// create the ipmp descriptor
|
|
AP4_IpmpDescriptor* ipmp_descriptor = new AP4_IpmpDescriptor((AP4_UI08)(i+1), AP4_MARLIN_IPMPS_TYPE_MGSV);
|
|
|
|
// create the sinf container
|
|
AP4_ContainerAtom* sinf = new AP4_ContainerAtom(AP4_ATOM_TYPE_SINF);
|
|
|
|
// add the scheme type atom
|
|
sinf->AddChild(new AP4_SchmAtom(m_UseGroupKey?
|
|
AP4_PROTECTION_SCHEME_TYPE_MARLIN_ACGK:
|
|
AP4_PROTECTION_SCHEME_TYPE_MARLIN_ACBC,
|
|
0x0100, NULL, true));
|
|
|
|
// create the 'schi' container
|
|
AP4_ContainerAtom* schi = new AP4_ContainerAtom(AP4_ATOM_TYPE_SCHI);
|
|
|
|
// add the content ID
|
|
const char* content_id = m_PropertyMap.GetProperty(mpod->GetTrackIds()[i], "ContentId");
|
|
if (content_id) {
|
|
// add the content ID (8id_)
|
|
schi->AddChild(new AP4_NullTerminatedStringAtom(AP4_ATOM_TYPE_8ID_, content_id));
|
|
}
|
|
|
|
// find what the track type is (necessary for the next step) and the key
|
|
const AP4_DataBuffer* key = NULL;
|
|
AP4_Track::Type track_type = AP4_Track::TYPE_UNKNOWN;
|
|
for (AP4_List<AP4_TrakAtom>::Item* trak_item = moov->GetTrakAtoms().FirstItem();
|
|
trak_item;
|
|
trak_item = trak_item->GetNext()) {
|
|
AP4_TrakAtom* trak = trak_item->GetData();
|
|
if (trak->GetId() == mpod->GetTrackIds()[i]) {
|
|
// find the handler type
|
|
AP4_Atom* sub = trak->FindChild("mdia/hdlr");
|
|
if (sub) {
|
|
AP4_HdlrAtom* hdlr = AP4_DYNAMIC_CAST(AP4_HdlrAtom, sub);
|
|
if (hdlr) {
|
|
AP4_UI32 type = hdlr->GetHandlerType();
|
|
if (type == AP4_HANDLER_TYPE_SOUN) {
|
|
track_type = AP4_Track::TYPE_AUDIO;
|
|
} else if (type == AP4_HANDLER_TYPE_VIDE) {
|
|
track_type = AP4_Track::TYPE_VIDEO;
|
|
}
|
|
}
|
|
}
|
|
|
|
// find the key
|
|
key = m_KeyMap.GetKey(trak->GetId());
|
|
break;
|
|
}
|
|
}
|
|
|
|
// group key
|
|
if (m_UseGroupKey && key) {
|
|
// find the group key
|
|
const AP4_DataBuffer* group_key = m_KeyMap.GetKey(0);
|
|
if (group_key) {
|
|
AP4_DataBuffer wrapped_key;
|
|
result = AP4_AesKeyWrap(group_key->GetData(), key->GetData(), key->GetDataSize(), wrapped_key);
|
|
if (AP4_SUCCEEDED(result)) {
|
|
AP4_UnknownAtom* gkey = new AP4_UnknownAtom(AP4_ATOM_TYPE_GKEY,
|
|
wrapped_key.GetData(),
|
|
wrapped_key.GetDataSize());
|
|
schi->AddChild(gkey);
|
|
}
|
|
}
|
|
}
|
|
|
|
// create and add the security attributes (satr)
|
|
if (track_type != AP4_Track::TYPE_UNKNOWN && key != NULL && key != NULL) {
|
|
AP4_ContainerAtom* satr = new AP4_ContainerAtom(AP4_ATOM_TYPE_SATR);
|
|
switch (track_type) {
|
|
case AP4_Track::TYPE_AUDIO:
|
|
satr->AddChild(new AP4_NullTerminatedStringAtom(AP4_ATOM_TYPE_STYP, AP4_MARLIN_IPMP_STYP_AUDIO));
|
|
break;
|
|
case AP4_Track::TYPE_VIDEO:
|
|
satr->AddChild(new AP4_NullTerminatedStringAtom(AP4_ATOM_TYPE_STYP, AP4_MARLIN_IPMP_STYP_VIDEO));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// add the signed attributes, if any
|
|
const char* signed_attributes = m_PropertyMap.GetProperty(mpod->GetTrackIds()[i], "SignedAttributes");
|
|
if (signed_attributes) {
|
|
// decode the hex-encoded data
|
|
unsigned int size = (unsigned int)AP4_StringLength(signed_attributes)/2;
|
|
AP4_DataBuffer attributes_atoms;
|
|
attributes_atoms.SetDataSize(size);
|
|
if (AP4_SUCCEEDED(AP4_ParseHex(signed_attributes, attributes_atoms.UseData(), size))) {
|
|
// parse all the atoms encoded in the data and add them to the 'schi' container
|
|
AP4_MemoryByteStream* mbs = new AP4_MemoryByteStream(attributes_atoms.GetData(),
|
|
attributes_atoms.GetDataSize());
|
|
AP4_DefaultAtomFactory atom_factory;
|
|
do {
|
|
AP4_Atom* atom = NULL;
|
|
result = atom_factory.CreateAtomFromStream(*mbs, atom);
|
|
if (AP4_SUCCEEDED(result) && atom) {
|
|
satr->AddChild(atom);
|
|
}
|
|
} while (AP4_SUCCEEDED(result));
|
|
mbs->Release();
|
|
}
|
|
}
|
|
|
|
// compute the hmac
|
|
AP4_MemoryByteStream* mbs = new AP4_MemoryByteStream();
|
|
satr->Write(*mbs);
|
|
AP4_Hmac* digester = NULL;
|
|
AP4_Hmac::Create(AP4_Hmac::SHA256, key->GetData(), key->GetDataSize(), digester);
|
|
digester->Update(mbs->GetData(), mbs->GetDataSize());
|
|
AP4_DataBuffer hmac_value;
|
|
digester->Final(hmac_value);
|
|
AP4_Atom* hmac = new AP4_UnknownAtom(AP4_ATOM_TYPE_HMAC, hmac_value.GetData(), hmac_value.GetDataSize());
|
|
|
|
schi->AddChild(satr);
|
|
schi->AddChild(hmac);
|
|
|
|
mbs->Release();
|
|
}
|
|
|
|
sinf->AddChild(schi);
|
|
|
|
// serialize the sinf atom to a buffer and set it as the ipmp data
|
|
AP4_MemoryByteStream* sinf_data = new AP4_MemoryByteStream((AP4_Size)sinf->GetSize());
|
|
sinf->Write(*sinf_data);
|
|
ipmp_descriptor->SetData(sinf_data->GetData(), sinf_data->GetDataSize());
|
|
sinf_data->Release();
|
|
|
|
ipmp_update.AddDescriptor(ipmp_descriptor);
|
|
}
|
|
|
|
// add the sample with the descriptors and updates
|
|
AP4_MemoryByteStream* sample_data = new AP4_MemoryByteStream();
|
|
od_update.Write(*sample_data);
|
|
ipmp_update.Write(*sample_data);
|
|
od_sample_table->AddSample(*sample_data, 0, sample_data->GetDataSize(), 0, 0, 0, 0, true);
|
|
|
|
// create the OD track
|
|
AP4_TrakAtom* od_track = new AP4_TrakAtom(od_sample_table,
|
|
AP4_HANDLER_TYPE_ODSM,
|
|
"Bento4 Marlin OD Handler",
|
|
od_track_id,
|
|
0, 0,
|
|
1, 1000, 1, 0, "und",
|
|
0, 0);
|
|
|
|
// add an entry in the processor's stream table to indicate that the
|
|
// media data for the OD track is not in the file stream, but in our
|
|
// memory stream.
|
|
m_ExternalTrackData.Add(new ExternalTrackData(od_track_id, sample_data));
|
|
sample_data->Release();
|
|
|
|
// add a tref track reference atom
|
|
AP4_ContainerAtom* tref = new AP4_ContainerAtom(AP4_ATOM_TYPE_TREF);
|
|
tref->AddChild(mpod);
|
|
od_track->AddChild(tref, 1); // add after 'tkhd'
|
|
|
|
// add the track to the moov atoms (just after the last track)
|
|
moov->AddChild(od_track, od_track_position);
|
|
|
|
return AP4_SUCCESS;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MarlinIpmpEncryptingProcessor::CreateTrackHandler
|
|
+---------------------------------------------------------------------*/
|
|
AP4_Processor::TrackHandler*
|
|
AP4_MarlinIpmpEncryptingProcessor::CreateTrackHandler(AP4_TrakAtom* trak)
|
|
{
|
|
// create a handler for this track if we have a key for it
|
|
const AP4_DataBuffer* key;
|
|
const AP4_DataBuffer* iv;
|
|
if (AP4_SUCCEEDED(m_KeyMap.GetKeyAndIv(trak->GetId(), key, iv))) {
|
|
// create the track handler
|
|
AP4_MarlinIpmpTrackEncrypter* handler = NULL;
|
|
AP4_Result result = AP4_MarlinIpmpTrackEncrypter::Create(*m_BlockCipherFactory,
|
|
key->GetData(),
|
|
key->GetDataSize(),
|
|
iv->GetData(),
|
|
iv->GetDataSize(),
|
|
handler);
|
|
if (AP4_FAILED(result)) return NULL;
|
|
return handler;
|
|
}
|
|
|
|
// not encrypted
|
|
return NULL;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MarlinIpmpTrackEncrypter::Create
|
|
+---------------------------------------------------------------------*/
|
|
AP4_Result
|
|
AP4_MarlinIpmpTrackEncrypter::Create(AP4_BlockCipherFactory& cipher_factory,
|
|
const AP4_UI08* key,
|
|
AP4_Size key_size,
|
|
const AP4_UI08* iv,
|
|
AP4_Size iv_size,
|
|
AP4_MarlinIpmpTrackEncrypter*& encrypter)
|
|
{
|
|
// default value
|
|
encrypter = NULL;
|
|
|
|
// check args
|
|
if (iv != NULL && iv_size != 16) {
|
|
return AP4_ERROR_INVALID_PARAMETERS;
|
|
}
|
|
|
|
// create a block cipher
|
|
AP4_BlockCipher* block_cipher = NULL;
|
|
AP4_Result result = cipher_factory.CreateCipher(AP4_BlockCipher::AES_128,
|
|
AP4_BlockCipher::ENCRYPT,
|
|
AP4_BlockCipher::CBC,
|
|
NULL,
|
|
key,
|
|
key_size,
|
|
block_cipher);
|
|
if (AP4_FAILED(result)) return result;
|
|
|
|
// create a CBC cipher
|
|
AP4_CbcStreamCipher* cbc_cipher = new AP4_CbcStreamCipher(block_cipher);
|
|
|
|
// create the track encrypter
|
|
encrypter = new AP4_MarlinIpmpTrackEncrypter(cbc_cipher, iv);
|
|
|
|
return AP4_SUCCESS;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MarlinIpmpTrackEncrypter::AP4_MarlinIpmpTrackEncrypter
|
|
+---------------------------------------------------------------------*/
|
|
AP4_MarlinIpmpTrackEncrypter::AP4_MarlinIpmpTrackEncrypter(AP4_StreamCipher* cipher,
|
|
const AP4_UI08* iv) :
|
|
m_Cipher(cipher)
|
|
{
|
|
// copy the IV
|
|
AP4_CopyMemory(m_IV, iv, AP4_AES_BLOCK_SIZE);
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MarlinIpmpTrackEncrypter::~AP4_MarlinIpmpTrackEncrypter
|
|
+---------------------------------------------------------------------*/
|
|
AP4_MarlinIpmpTrackEncrypter::~AP4_MarlinIpmpTrackEncrypter()
|
|
{
|
|
delete m_Cipher;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MarlinIpmpTrackEncrypter:GetProcessedSampleSize
|
|
+---------------------------------------------------------------------*/
|
|
AP4_Size
|
|
AP4_MarlinIpmpTrackEncrypter::GetProcessedSampleSize(AP4_Sample& sample)
|
|
{
|
|
return AP4_CIPHER_BLOCK_SIZE*(2+(sample.GetSize()/AP4_CIPHER_BLOCK_SIZE));
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MarlinIpmpTrackEncrypter:ProcessSample
|
|
+---------------------------------------------------------------------*/
|
|
AP4_Result
|
|
AP4_MarlinIpmpTrackEncrypter::ProcessSample(AP4_DataBuffer& data_in,
|
|
AP4_DataBuffer& data_out)
|
|
{
|
|
AP4_Result result;
|
|
|
|
const AP4_UI08* in = data_in.GetData();
|
|
AP4_Size in_size = data_in.GetDataSize();
|
|
|
|
// default to 0 output
|
|
data_out.SetDataSize(0);
|
|
|
|
// process the sample data
|
|
AP4_Size out_size = AP4_CIPHER_BLOCK_SIZE*(2+(in_size/AP4_CIPHER_BLOCK_SIZE));
|
|
data_out.SetDataSize(out_size);
|
|
AP4_UI08* out = data_out.UseData();
|
|
|
|
// write the IV
|
|
AP4_CopyMemory(out, m_IV, AP4_CIPHER_BLOCK_SIZE);
|
|
out_size -= AP4_CIPHER_BLOCK_SIZE;
|
|
|
|
// encrypt the data
|
|
m_Cipher->SetIV(m_IV);
|
|
result = m_Cipher->ProcessBuffer(in,
|
|
in_size,
|
|
out+AP4_AES_BLOCK_SIZE,
|
|
&out_size,
|
|
true);
|
|
if (AP4_FAILED(result)) return result;
|
|
|
|
// update the payload size
|
|
data_out.SetDataSize(out_size+AP4_AES_BLOCK_SIZE);
|
|
|
|
return AP4_SUCCESS;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MkidAtom::AP4_MkidAtom
|
|
+---------------------------------------------------------------------*/
|
|
AP4_MkidAtom::AP4_MkidAtom() :
|
|
AP4_Atom(AP4_ATOM_TYPE_MKID, AP4_FULL_ATOM_HEADER_SIZE+4, 0, 0)
|
|
{
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MkidAtom::Create
|
|
+---------------------------------------------------------------------*/
|
|
AP4_MkidAtom*
|
|
AP4_MkidAtom::Create(AP4_Size size, AP4_ByteStream& stream)
|
|
{
|
|
AP4_UI08 version;
|
|
AP4_UI32 flags;
|
|
if (size < AP4_FULL_ATOM_HEADER_SIZE) return NULL;
|
|
if (AP4_FAILED(AP4_Atom::ReadFullHeader(stream, version, flags))) return NULL;
|
|
if (version > 0) return NULL;
|
|
return new AP4_MkidAtom(size, version, flags, stream);
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MkidAtom::AP4_MkidAtom
|
|
+---------------------------------------------------------------------*/
|
|
AP4_MkidAtom::AP4_MkidAtom(AP4_Size size,
|
|
AP4_UI08 version,
|
|
AP4_UI32 flags,
|
|
AP4_ByteStream& stream) :
|
|
AP4_Atom(AP4_ATOM_TYPE_MKID, size, version, flags)
|
|
{
|
|
AP4_Size available = size-(AP4_FULL_ATOM_HEADER_SIZE+4);
|
|
AP4_UI32 entry_count = 0;
|
|
stream.ReadUI32(entry_count);
|
|
if (available < entry_count*(16+4)) return;
|
|
m_Entries.SetItemCount(entry_count);
|
|
for (unsigned int i=0; i<entry_count && available >= 16+4; i++) {
|
|
AP4_UI32 entry_size;
|
|
stream.ReadUI32(entry_size);
|
|
if (available < 4+entry_size) break;
|
|
if (entry_size < 16) continue;
|
|
available -= (4+entry_size);
|
|
stream.Read(m_Entries[i].m_KID, 16);
|
|
unsigned int content_id_size = entry_size-16;
|
|
char* content_id = new char[content_id_size];
|
|
stream.Read(content_id, content_id_size);
|
|
m_Entries[i].m_ContentId.Assign(content_id, content_id_size);
|
|
delete[] content_id;
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MkidAtom::AddEntry
|
|
+---------------------------------------------------------------------*/
|
|
AP4_Result
|
|
AP4_MkidAtom::AddEntry(const AP4_UI08* kid, const char* content_id)
|
|
{
|
|
unsigned int content_id_size = (unsigned int)AP4_StringLength(content_id);
|
|
unsigned int entry_count = m_Entries.ItemCount();
|
|
|
|
// add the entry
|
|
m_Entries.SetItemCount(entry_count+1);
|
|
AP4_CopyMemory(m_Entries[entry_count].m_KID, kid, 16);
|
|
m_Entries[entry_count].m_ContentId.Assign(content_id, content_id_size);
|
|
|
|
// update the size
|
|
m_Size32 += 4+16+content_id_size;
|
|
|
|
return AP4_SUCCESS;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MkidAtom::WriteFields
|
|
+---------------------------------------------------------------------*/
|
|
AP4_Result
|
|
AP4_MkidAtom::WriteFields(AP4_ByteStream& stream)
|
|
{
|
|
AP4_Result result;
|
|
|
|
// encoding
|
|
result = stream.WriteUI32(m_Entries.ItemCount());
|
|
if (AP4_FAILED(result)) return result;
|
|
|
|
// entries
|
|
for (unsigned int i=0; i<m_Entries.ItemCount(); i++) {
|
|
stream.WriteUI32(16+m_Entries[i].m_ContentId.GetLength());
|
|
stream.Write(m_Entries[i].m_KID, 16);
|
|
stream.Write(m_Entries[i].m_ContentId.GetChars(), m_Entries[i].m_ContentId.GetLength());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
| AP4_MkidAtom::InspectFields
|
|
+---------------------------------------------------------------------*/
|
|
AP4_Result
|
|
AP4_MkidAtom::InspectFields(AP4_AtomInspector& inspector)
|
|
{
|
|
inspector.AddField("entry_count", m_Entries.ItemCount());
|
|
for (unsigned int i=0; i<m_Entries.ItemCount(); i++) {
|
|
inspector.AddField("KID", m_Entries[i].m_KID, 16);
|
|
inspector.AddField("content_id", m_Entries[i].m_ContentId.GetChars());
|
|
}
|
|
return AP4_SUCCESS;
|
|
}
|
|
|
|
|
|
|
|
|