/***************************************************************** | | AP4 - Common Encryption Support | | Copyright 2002-2011 Axiomatic Systems, LLC | | | This file is part of Bento4/AP4 (MP4 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 "Ap4CommonEncryption.h" #include "Ap4Utils.h" #include "Ap4FrmaAtom.h" #include "Ap4SchmAtom.h" #include "Ap4TencAtom.h" #include "Ap4SencAtom.h" #include "Ap4FragmentSampleTable.h" #include "Ap4FtypAtom.h" #include "Ap4StsdAtom.h" #include "Ap4TrakAtom.h" #include "Ap4HdlrAtom.h" #include "Ap4TfhdAtom.h" #include "Ap4TrexAtom.h" #include "Ap4SaizAtom.h" #include "Ap4SaioAtom.h" #include "Ap4Piff.h" #include "Ap4StreamCipher.h" #include "Ap4SaioAtom.h" #include "Ap4SaizAtom.h" #include "Ap4TrunAtom.h" #include "Ap4Marlin.h" #include "Ap4PsshAtom.h" #include "Ap4AvcParser.h" #include "Ap4HevcParser.h" /*---------------------------------------------------------------------- | constants +---------------------------------------------------------------------*/ const AP4_UI08 AP4_EME_COMMON_SYSTEM_ID[16] = { 0x10, 0x77, 0xEF, 0xEC, 0xC0, 0xB2, 0x4D, 0x02, 0xAC, 0xE3, 0x3C, 0x1E, 0x52, 0xE2, 0xFB, 0x4B }; const unsigned int AP4_CENC_NAL_UNIT_ENCRYPTION_MIN_SIZE = 112; /*---------------------------------------------------------------------- | AP4_CencSubSampleMapAppend +---------------------------------------------------------------------*/ static void AP4_CencSubSampleMapAppendEntry(AP4_Array& bytes_of_cleartext_data, AP4_Array& bytes_of_encrypted_data, unsigned int cleartext_size, unsigned int encrypted_size) { // if there's already an entry, attempt to extend the last entry if (bytes_of_cleartext_data.ItemCount() > 0) { AP4_Cardinal last_index = bytes_of_cleartext_data.ItemCount() - 1; if (bytes_of_encrypted_data[last_index] == 0) { cleartext_size += bytes_of_cleartext_data[last_index]; bytes_of_cleartext_data.RemoveLast(); bytes_of_encrypted_data.RemoveLast(); } } // append chunks of cleartext size taking into account that the cleartext_size field is only 16 bits while (cleartext_size > 0xFFFF) { bytes_of_cleartext_data.Append(0xFFFF); bytes_of_encrypted_data.Append(0); cleartext_size -= 0xFFFF; } // append whatever is left bytes_of_cleartext_data.Append(cleartext_size); bytes_of_encrypted_data.Append(encrypted_size); } /*---------------------------------------------------------------------- | AP4_CencBasicSubSampleMapper::GetSubSampleMap +---------------------------------------------------------------------*/ AP4_Result AP4_CencBasicSubSampleMapper::GetSubSampleMap(AP4_DataBuffer& sample_data, AP4_Array& bytes_of_cleartext_data, AP4_Array& bytes_of_encrypted_data) { // setup direct pointers to the buffers const AP4_UI08* in = sample_data.GetData(); // process the sample data, one NALU at a time const AP4_UI08* in_end = sample_data.GetData()+sample_data.GetDataSize(); while ((AP4_Size)(in_end-in) > 1+m_NaluLengthSize) { unsigned int nalu_length; switch (m_NaluLengthSize) { case 1: nalu_length = *in; break; case 2: nalu_length = AP4_BytesToUInt16BE(in); break; case 4: nalu_length = AP4_BytesToUInt32BE(in); break; default: return AP4_ERROR_INVALID_FORMAT; } unsigned int chunk_size = m_NaluLengthSize+nalu_length; unsigned int cleartext_size = chunk_size%16; unsigned int block_count = chunk_size/16; if (cleartext_size < m_NaluLengthSize+1) { AP4_ASSERT(block_count); --block_count; cleartext_size += 16; } // move the pointers in += chunk_size; // store the info bytes_of_cleartext_data.Append((AP4_UI16)cleartext_size); bytes_of_encrypted_data.Append((AP4_UI32)(block_count*16)); } return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_CencAdvancedSubSampleEncrypter::GetSubSampleMap +---------------------------------------------------------------------*/ AP4_Result AP4_CencAdvancedSubSampleMapper::GetSubSampleMap(AP4_DataBuffer& sample_data, AP4_Array& bytes_of_cleartext_data, AP4_Array& bytes_of_encrypted_data) { // setup direct pointers to the buffers const AP4_UI08* in = sample_data.GetData(); // process the sample data, one NALU at a time const AP4_UI08* in_end = sample_data.GetData()+sample_data.GetDataSize(); while ((AP4_Size)(in_end-in) > 1+m_NaluLengthSize) { unsigned int nalu_length; switch (m_NaluLengthSize) { case 1: nalu_length = *in; break; case 2: nalu_length = AP4_BytesToUInt16BE(in); break; case 4: nalu_length = AP4_BytesToUInt32BE(in); break; default: return AP4_ERROR_INVALID_FORMAT; } unsigned int nalu_size = m_NaluLengthSize+nalu_length; if (in+nalu_size > in_end) { return AP4_ERROR_INVALID_FORMAT; } // skip encryption if the NAL unit is smaller than the threshold (DECE CFF spec) // or should be left unencrypted for this specific format/type bool skip = false; if (nalu_size < AP4_CENC_NAL_UNIT_ENCRYPTION_MIN_SIZE) { skip = true; } else if (m_Format == AP4_SAMPLE_FORMAT_AVC1 || m_Format == AP4_SAMPLE_FORMAT_AVC2 || m_Format == AP4_SAMPLE_FORMAT_AVC3 || m_Format == AP4_SAMPLE_FORMAT_AVC4 || m_Format == AP4_SAMPLE_FORMAT_DVAV || m_Format == AP4_SAMPLE_FORMAT_DVA1) { unsigned int nalu_type = in[m_NaluLengthSize] & 0x1F; if (nalu_type != AP4_AVC_NAL_UNIT_TYPE_CODED_SLICE_OF_NON_IDR_PICTURE && nalu_type != AP4_AVC_NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_A && nalu_type != AP4_AVC_NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_B && nalu_type != AP4_AVC_NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_C && nalu_type != AP4_AVC_NAL_UNIT_TYPE_CODED_SLICE_OF_IDR_PICTURE) { // this NAL unit is not a VCL NAL unit skip = true; } } else if (m_Format == AP4_SAMPLE_FORMAT_HEV1 || m_Format == AP4_SAMPLE_FORMAT_HVC1 || m_Format == AP4_SAMPLE_FORMAT_DVHE || m_Format == AP4_SAMPLE_FORMAT_DVH1) { unsigned int nalu_type = (in[m_NaluLengthSize] >> 1) & 0x3F; if (nalu_type >= 32) { // this NAL unit is not a VCL NAL unit skip = true; } } const char* cenc_layout = AP4_GlobalOptions::GetString("mpeg-cenc.encryption-layout"); if (cenc_layout && AP4_CompareStrings(cenc_layout, "nalu-length-and-type-only") == 0) { unsigned int cleartext_size = m_NaluLengthSize+1; unsigned int encrypted_size = nalu_size > cleartext_size ? nalu_size-cleartext_size : 0; AP4_CencSubSampleMapAppendEntry(bytes_of_cleartext_data, bytes_of_encrypted_data, cleartext_size, encrypted_size); } else if (skip) { // use cleartext regions to cover the entire NAL unit AP4_CencSubSampleMapAppendEntry(bytes_of_cleartext_data, bytes_of_encrypted_data, nalu_size, 0); } else { // leave some cleartext bytes at the start and encrypt the rest (multiple of blocks) unsigned int encrypted_size = nalu_size-(AP4_CENC_NAL_UNIT_ENCRYPTION_MIN_SIZE-16); encrypted_size -= (encrypted_size % 16); unsigned int cleartext_size = nalu_size-encrypted_size; AP4_ASSERT(encrypted_size >= 16); AP4_ASSERT(cleartext_size >= m_NaluLengthSize); AP4_CencSubSampleMapAppendEntry(bytes_of_cleartext_data, bytes_of_encrypted_data, cleartext_size, encrypted_size); } // move the pointers in += nalu_size; } return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_CencCbcsSubSampleEncrypter::AP4_CencCbcsSubSampleEncrypter +---------------------------------------------------------------------*/ AP4_CencCbcsSubSampleMapper::AP4_CencCbcsSubSampleMapper(AP4_Size nalu_length_size, AP4_UI32 format, AP4_TrakAtom* trak) : AP4_CencSubSampleMapper(nalu_length_size, format), m_AvcParser(NULL), m_HevcParser(NULL) { if (!trak) return; // get the sample description atom AP4_StsdAtom* stsd = AP4_DYNAMIC_CAST(AP4_StsdAtom, trak->FindChild("mdia/minf/stbl/stsd")); if (!stsd) return; if (format == AP4_SAMPLE_FORMAT_AVC1 || format == AP4_SAMPLE_FORMAT_AVC2 || format == AP4_SAMPLE_FORMAT_AVC3 || format == AP4_SAMPLE_FORMAT_AVC4 || format == AP4_SAMPLE_FORMAT_DVAV || format == AP4_SAMPLE_FORMAT_DVA1) { // create the parser m_AvcParser = new AP4_AvcFrameParser(); // look for an avc sample description AP4_AvccAtom* avcc = AP4_DYNAMIC_CAST(AP4_AvccAtom, stsd->FindChild("avc1/avcC")); if (!avcc) avcc = AP4_DYNAMIC_CAST(AP4_AvccAtom, stsd->FindChild("avc2/avcC")); if (!avcc) avcc = AP4_DYNAMIC_CAST(AP4_AvccAtom, stsd->FindChild("avc3/avcC")); if (!avcc) avcc = AP4_DYNAMIC_CAST(AP4_AvccAtom, stsd->FindChild("avc4/avcC")); if (!avcc) return; // parse the sps and pps if we have them AP4_Array& sps_list = avcc->GetSequenceParameters(); for (unsigned int i=0; i& pps_list = avcc->GetPictureParameters(); for (unsigned int i=0; iFindChild("hvc1/hvcC")); if (!hvcc) hvcc = AP4_DYNAMIC_CAST(AP4_HvccAtom, stsd->FindChild("hev1/hvcC")); if (!hvcc) hvcc = AP4_DYNAMIC_CAST(AP4_HvccAtom, stsd->FindChild("dvh1/hvcC")); if (!hvcc) hvcc = AP4_DYNAMIC_CAST(AP4_HvccAtom, stsd->FindChild("dvhe/hvcC")); if (!hvcc) return; // parse the vps, sps and pps if we have them const AP4_Array& sequence_list = hvcc->GetSequences(); for (unsigned int i=0; i& nalus = sequence_list[i].m_Nalus; for (unsigned int j=0; jFeed(data, data_size, access_unit_info); if (AP4_FAILED(result)) return result; // cleanup access_unit_info.Reset(); return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_CencCbcsSubSampleMapper::ParseHevcData +---------------------------------------------------------------------*/ AP4_Result AP4_CencCbcsSubSampleMapper::ParseHevcData(const AP4_UI08* data, AP4_Size data_size) { if (!m_HevcParser) return AP4_ERROR_INVALID_PARAMETERS; AP4_HevcFrameParser::AccessUnitInfo access_unit_info; AP4_Result result = m_HevcParser->Feed(data, data_size, access_unit_info); if (AP4_FAILED(result)) return result; // cleanup access_unit_info.Reset(); return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_CencCbcsSubSampleMapper::GetSubSampleMap +---------------------------------------------------------------------*/ AP4_Result AP4_CencCbcsSubSampleMapper::GetSubSampleMap(AP4_DataBuffer& sample_data, AP4_Array& bytes_of_cleartext_data, AP4_Array& bytes_of_encrypted_data) { // setup direct pointers to the buffers const AP4_UI08* in = sample_data.GetData(); // process the sample data, one NALU at a time const AP4_UI08* in_end = sample_data.GetData()+sample_data.GetDataSize(); while ((AP4_Size)(in_end-in) > 1+m_NaluLengthSize) { unsigned int nalu_length; switch (m_NaluLengthSize) { case 1: nalu_length = *in; break; case 2: nalu_length = AP4_BytesToUInt16BE(in); break; case 4: nalu_length = AP4_BytesToUInt32BE(in); break; default: return AP4_ERROR_INVALID_FORMAT; } unsigned int nalu_size = m_NaluLengthSize+nalu_length; if (in+nalu_size > in_end) { return AP4_ERROR_INVALID_FORMAT; } // skip encryption if the NAL unit should be left unencrypted for this specific format/type bool skip = false; if (m_Format == AP4_SAMPLE_FORMAT_AVC1 || m_Format == AP4_SAMPLE_FORMAT_AVC2 || m_Format == AP4_SAMPLE_FORMAT_AVC3 || m_Format == AP4_SAMPLE_FORMAT_AVC4 || m_Format == AP4_SAMPLE_FORMAT_DVAV || m_Format == AP4_SAMPLE_FORMAT_DVA1) { const AP4_UI08* nalu_data = &in[m_NaluLengthSize]; unsigned int nalu_type = nalu_data[0] & 0x1F; if (nalu_type == AP4_AVC_NAL_UNIT_TYPE_CODED_SLICE_OF_NON_IDR_PICTURE || nalu_type == AP4_AVC_NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_A || nalu_type == AP4_AVC_NAL_UNIT_TYPE_CODED_SLICE_OF_IDR_PICTURE) { // parse the NAL unit to get the slice header size if (m_AvcParser == NULL) return AP4_ERROR_INTERNAL; AP4_AvcSliceHeader slice_header; unsigned int nalu_ref_idc = (nalu_data[0]>>5)&3; AP4_Result result = m_AvcParser->ParseSliceHeader(&nalu_data[1], nalu_length-1, nalu_type, nalu_ref_idc, slice_header); if (AP4_FAILED(result)) { return result; } // round up the slide header size to a multiple of bytes unsigned int header_size = (slice_header.size+7)/8; // account for emulation prevention bytes unsigned int emulation_prevention_bytes = AP4_NalParser::CountEmulationPreventionBytes(&nalu_data[1], nalu_length-1, header_size); header_size += emulation_prevention_bytes; // leave the slice header in the clear, including the NAL type unsigned int cleartext_size = m_NaluLengthSize+1+header_size; unsigned int encrypted_size = nalu_size-cleartext_size; AP4_CencSubSampleMapAppendEntry(bytes_of_cleartext_data, bytes_of_encrypted_data, cleartext_size, encrypted_size); } else { // this NAL unit does not have a slice header skip = true; // parse SPS and PPS NAL units if (nalu_type == AP4_AVC_NAL_UNIT_TYPE_SPS || nalu_type == AP4_AVC_NAL_UNIT_TYPE_PPS) { AP4_Result result = ParseAvcData(nalu_data, nalu_length); if (AP4_FAILED(result)) { return result; } } } } else if (m_Format == AP4_SAMPLE_FORMAT_HEV1 || m_Format == AP4_SAMPLE_FORMAT_HVC1 || m_Format == AP4_SAMPLE_FORMAT_DVHE || m_Format == AP4_SAMPLE_FORMAT_DVH1) { const AP4_UI08* nalu_data = &in[m_NaluLengthSize]; unsigned int nalu_type = (nalu_data[0] >> 1) & 0x3F; if (nalu_type < AP4_HEVC_NALU_TYPE_VPS_NUT) { // this is a VCL NAL Unit if (m_HevcParser == NULL) return AP4_ERROR_INTERNAL; AP4_HevcSliceSegmentHeader slice_header; AP4_Result result = m_HevcParser->ParseSliceSegmentHeader(&nalu_data[2], nalu_length-2, nalu_type, slice_header); if (AP4_FAILED(result)) { return result; } // leave the slice header in the clear, including the NAL type // NOTE: the slice header is always a multiple of 8 bits because of byte_alignment() unsigned int header_size = slice_header.size/8; // account for emulation prevention bytes unsigned int emulation_prevention_bytes = AP4_NalParser::CountEmulationPreventionBytes(&nalu_data[2], nalu_length-2, header_size); header_size += emulation_prevention_bytes; // set the encrypted range unsigned int cleartext_size = m_NaluLengthSize+2+header_size; unsigned int encrypted_size = nalu_size-cleartext_size; AP4_CencSubSampleMapAppendEntry(bytes_of_cleartext_data, bytes_of_encrypted_data, cleartext_size, encrypted_size); } else { skip = true; // parse VPS, SPS and PPS NAL units if (nalu_type == AP4_HEVC_NALU_TYPE_VPS_NUT || nalu_type == AP4_HEVC_NALU_TYPE_SPS_NUT || nalu_type == AP4_HEVC_NALU_TYPE_PPS_NUT) { AP4_Result result = ParseHevcData(nalu_data, nalu_length); if (AP4_FAILED(result)) { return result; } } } } else { // only AVC and HEVC elementary streams are supported. return AP4_ERROR_NOT_SUPPORTED; } if (skip) { // use cleartext regions to cover the entire NAL unit AP4_CencSubSampleMapAppendEntry(bytes_of_cleartext_data, bytes_of_encrypted_data, nalu_size, 0); } // move the pointers in += nalu_size; } return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_CencSampleEncrypter::~AP4_CencSampleEncrypter +---------------------------------------------------------------------*/ AP4_CencSampleEncrypter::~AP4_CencSampleEncrypter() { delete m_Cipher; } /*---------------------------------------------------------------------- | AP4_CencCtrSampleEncrypter::EncryptSampleData +---------------------------------------------------------------------*/ AP4_Result AP4_CencCtrSampleEncrypter::EncryptSampleData(AP4_DataBuffer& data_in, AP4_DataBuffer& data_out, AP4_DataBuffer& /* sample_infos */) { // the output has the same size as the input data_out.SetDataSize(data_in.GetDataSize()); // setup direct pointers to the buffers const AP4_UI08* in = data_in.GetData(); AP4_UI08* out = data_out.UseData(); // setup the IV m_Cipher->SetIV(m_Iv); // process the sample data if (data_in.GetDataSize()) { AP4_Size out_size = data_out.GetDataSize(); AP4_Result result = m_Cipher->ProcessBuffer(in, data_in.GetDataSize(), out, &out_size, false); if (AP4_FAILED(result)) return result; } // update the IV if (m_IvSize == 16) { unsigned int block_count = (data_in.GetDataSize()+15)/16; AP4_UI64 counter = AP4_BytesToUInt64BE(&m_Iv[8]); AP4_BytesFromUInt64BE(&m_Iv[8], counter+block_count); } else if (m_IvSize == 8){ AP4_UI64 counter = AP4_BytesToUInt64BE(&m_Iv[0]); AP4_BytesFromUInt64BE(&m_Iv[0], counter+1); } else { return AP4_ERROR_INTERNAL; } return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_CencCtrSubSampleEncrypter::EncryptSampleData +---------------------------------------------------------------------*/ AP4_Result AP4_CencCtrSubSampleEncrypter::EncryptSampleData(AP4_DataBuffer& data_in, AP4_DataBuffer& data_out, AP4_DataBuffer& sample_infos) { // the output has the same size as the input data_out.SetDataSize(data_in.GetDataSize()); // check some basics if (data_in.GetDataSize() == 0) return AP4_SUCCESS; // setup direct pointers to the buffers const AP4_UI08* in = data_in.GetData(); AP4_UI08* out = data_out.UseData(); // setup the IV unsigned int total_encrypted = 0; m_Cipher->SetIV(m_Iv); // get the subsample map AP4_Array bytes_of_cleartext_data; AP4_Array bytes_of_encrypted_data; AP4_Result result = m_SubSampleMapper->GetSubSampleMap(data_in, bytes_of_cleartext_data, bytes_of_encrypted_data); if (AP4_FAILED(result)) return result; // process the data for (unsigned int i=0; iProcessBuffer(in+bytes_of_cleartext_data[i], bytes_of_encrypted_data[i], out+bytes_of_cleartext_data[i], &out_size); total_encrypted += bytes_of_encrypted_data[i]; } // move the pointers in += bytes_of_cleartext_data[i]+bytes_of_encrypted_data[i]; out += bytes_of_cleartext_data[i]+bytes_of_encrypted_data[i]; } // update the IV if (m_IvSize == 16) { AP4_UI64 counter = AP4_BytesToUInt64BE(&m_Iv[8]); AP4_BytesFromUInt64BE(&m_Iv[8], counter+(total_encrypted+15)/16); } else { AP4_UI64 counter = AP4_BytesToUInt64BE(&m_Iv[0]); AP4_BytesFromUInt64BE(&m_Iv[0], counter+1); } // encode the sample infos unsigned int sample_info_count = bytes_of_cleartext_data.ItemCount(); sample_infos.SetDataSize(2+sample_info_count*6); AP4_UI08* infos = sample_infos.UseData(); AP4_BytesFromUInt16BE(infos, (AP4_UI16)sample_info_count); for (unsigned int i=0; iSetIV(m_Iv); // process the sample data unsigned int block_count = data_in.GetDataSize()/16; if (block_count) { AP4_Size out_size = data_out.GetDataSize(); AP4_Result result = m_Cipher->ProcessBuffer(in, block_count*16, out, &out_size, false); if (AP4_FAILED(result)) return result; in += block_count*16; out += block_count*16; if (!m_ConstantIv) { // update the IV (last cipherblock emitted) AP4_CopyMemory(m_Iv, out-16, 16); } } // any partial block at the end remains in the clear unsigned int partial = data_in.GetDataSize()%16; if (partial) { AP4_CopyMemory(out, in, partial); } return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_CencCbcSubSampleEncrypter::EncryptSampleData +---------------------------------------------------------------------*/ AP4_Result AP4_CencCbcSubSampleEncrypter::EncryptSampleData(AP4_DataBuffer& data_in, AP4_DataBuffer& data_out, AP4_DataBuffer& sample_infos) { // the output has the same size as the input data_out.SetDataSize(data_in.GetDataSize()); // check some basics if (data_in.GetDataSize() == 0) return AP4_SUCCESS; // setup direct pointers to the buffers const AP4_UI08* in = data_in.GetData(); AP4_UI08* out = data_out.UseData(); // setup the IV m_Cipher->SetIV(m_Iv); // get the subsample map AP4_Array bytes_of_cleartext_data; AP4_Array bytes_of_encrypted_data; AP4_Result result = m_SubSampleMapper->GetSubSampleMap(data_in, bytes_of_cleartext_data, bytes_of_encrypted_data); if (AP4_FAILED(result)) return result; for (unsigned int i=0; iSetIV(m_Iv); } if (bytes_of_encrypted_data[i]) { AP4_Size out_size = bytes_of_encrypted_data[i]; result = m_Cipher->ProcessBuffer(in+bytes_of_cleartext_data[i], bytes_of_encrypted_data[i], out+bytes_of_cleartext_data[i], &out_size, false); if (AP4_FAILED(result)) return result; if (!m_ConstantIv) { // update the IV (last cipherblock emitted) AP4_CopyMemory(m_Iv, out+bytes_of_cleartext_data[i]+bytes_of_encrypted_data[i]-16, 16); } } // move the pointers in += bytes_of_cleartext_data[i]+bytes_of_encrypted_data[i]; out += bytes_of_cleartext_data[i]+bytes_of_encrypted_data[i]; } // encode the sample infos unsigned int sample_info_count = bytes_of_cleartext_data.ItemCount(); sample_infos.SetDataSize(2+sample_info_count*6); AP4_UI08* infos = sample_infos.UseData(); AP4_BytesFromUInt16BE(infos, (AP4_UI16)sample_info_count); for (unsigned int i=0; i& sample_entries, AP4_UI32 format); // methods virtual AP4_Result ProcessTrack(); virtual AP4_Result ProcessSample(AP4_DataBuffer& data_in, AP4_DataBuffer& data_out); private: // members AP4_CencVariant m_Variant; AP4_Array m_SampleEntries; AP4_UI32 m_Format; AP4_UI32 m_DefaultIsProtected; AP4_UI08 m_DefaultPerSampleIvSize; AP4_UI08 m_DefaultKid[16]; AP4_UI08 m_DefaultConstantIvSize; AP4_UI08 m_DefaultConstantIv[16]; AP4_UI08 m_DefaultCryptByteBlock; AP4_UI08 m_DefaultSkipByteBlock; }; /*---------------------------------------------------------------------- | AP4_CencTrackEncrypter::AP4_CencTrackEncrypter +---------------------------------------------------------------------*/ AP4_CencTrackEncrypter::AP4_CencTrackEncrypter( AP4_CencVariant variant, AP4_UI32 default_is_protected, AP4_UI08 default_per_sample_iv_size, const AP4_UI08* default_kid, AP4_UI08 default_constant_iv_size, const AP4_UI08* default_constant_iv, AP4_UI08 default_crypt_byte_block, AP4_UI08 default_skip_byte_block, AP4_Array& sample_entries, AP4_UI32 format) : m_Variant(variant), m_Format(format), m_DefaultIsProtected(default_is_protected), m_DefaultPerSampleIvSize(default_per_sample_iv_size), m_DefaultConstantIvSize(default_constant_iv_size), m_DefaultCryptByteBlock(default_crypt_byte_block), m_DefaultSkipByteBlock(default_skip_byte_block) { // copy the KID and IV AP4_CopyMemory(m_DefaultKid, default_kid, 16); if (default_constant_iv) { AP4_CopyMemory(m_DefaultConstantIv, default_constant_iv, 16); } // copy the sample entry list for (unsigned int i=0; iGetType()); // scheme info AP4_SchmAtom* schm = NULL; AP4_Atom* tenc = NULL; switch (m_Variant) { case AP4_CENC_VARIANT_PIFF_CBC: case AP4_CENC_VARIANT_PIFF_CTR: schm = new AP4_SchmAtom(AP4_PROTECTION_SCHEME_TYPE_PIFF, AP4_PROTECTION_SCHEME_VERSION_PIFF_11); tenc = new AP4_PiffTrackEncryptionAtom(m_DefaultIsProtected, m_DefaultPerSampleIvSize, m_DefaultKid); break; case AP4_CENC_VARIANT_MPEG_CENC: schm = new AP4_SchmAtom(AP4_PROTECTION_SCHEME_TYPE_CENC, AP4_PROTECTION_SCHEME_VERSION_CENC_10); tenc = new AP4_TencAtom(m_DefaultIsProtected, m_DefaultPerSampleIvSize, m_DefaultKid); break; case AP4_CENC_VARIANT_MPEG_CBC1: schm = new AP4_SchmAtom(AP4_PROTECTION_SCHEME_TYPE_CBC1, AP4_PROTECTION_SCHEME_VERSION_CENC_10); tenc = new AP4_TencAtom(m_DefaultIsProtected, m_DefaultPerSampleIvSize, m_DefaultKid); break; case AP4_CENC_VARIANT_MPEG_CENS: schm = new AP4_SchmAtom(AP4_PROTECTION_SCHEME_TYPE_CENS, AP4_PROTECTION_SCHEME_VERSION_CENC_10); tenc = new AP4_TencAtom(m_DefaultIsProtected, m_DefaultPerSampleIvSize, m_DefaultKid, m_DefaultConstantIvSize, m_DefaultConstantIv, m_DefaultCryptByteBlock, m_DefaultSkipByteBlock); break; case AP4_CENC_VARIANT_MPEG_CBCS: schm = new AP4_SchmAtom(AP4_PROTECTION_SCHEME_TYPE_CBCS, AP4_PROTECTION_SCHEME_VERSION_CENC_10); tenc = new AP4_TencAtom(m_DefaultIsProtected, m_DefaultPerSampleIvSize, m_DefaultKid, m_DefaultConstantIvSize, m_DefaultConstantIv, m_DefaultCryptByteBlock, m_DefaultSkipByteBlock); break; } // populate the schi container AP4_ContainerAtom* schi = new AP4_ContainerAtom(AP4_ATOM_TYPE_SCHI); schi->AddChild(tenc); // populate the sinf container AP4_ContainerAtom* sinf = new AP4_ContainerAtom(AP4_ATOM_TYPE_SINF); sinf->AddChild(frma); sinf->AddChild(schm); sinf->AddChild(schi); // add the sinf atom to the sample description m_SampleEntries[i]->AddChild(sinf); // change the atom type of the sample description m_SampleEntries[i]->SetType(m_Format); } return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_CencTrackEncrypter::ProcessSample +---------------------------------------------------------------------*/ AP4_Result AP4_CencTrackEncrypter::ProcessSample(AP4_DataBuffer& data_in, AP4_DataBuffer& data_out) { return data_out.SetData(data_in.GetData(), data_in.GetDataSize()); } /*---------------------------------------------------------------------- | AP4_CencFragmentEncrypter +---------------------------------------------------------------------*/ class AP4_CencFragmentEncrypter : public AP4_Processor::FragmentHandler { public: // constructor AP4_CencFragmentEncrypter(AP4_CencVariant variant, AP4_UI32 options, AP4_ContainerAtom* traf, AP4_CencEncryptingProcessor::Encrypter* encrypter, AP4_UI32 cleartext_sample_description_index); // methods virtual AP4_Result ProcessFragment(); virtual AP4_Result ProcessSample(AP4_DataBuffer& data_in, AP4_DataBuffer& data_out); virtual AP4_Result PrepareForSamples(AP4_FragmentSampleTable* sample_table); virtual AP4_Result FinishFragment(); private: // members AP4_CencVariant m_Variant; AP4_UI32 m_Options; AP4_ContainerAtom* m_Traf; AP4_CencSampleEncryption* m_SampleEncryptionAtom; AP4_CencSampleEncryption* m_SampleEncryptionAtomShadow; AP4_SaizAtom* m_Saiz; AP4_SaioAtom* m_Saio; AP4_CencEncryptingProcessor::Encrypter* m_Encrypter; AP4_UI32 m_CleartextSampleDescriptionIndex; }; /*---------------------------------------------------------------------- | AP4_CencFragmentEncrypter::AP4_CencFragmentEncrypter +---------------------------------------------------------------------*/ AP4_CencFragmentEncrypter::AP4_CencFragmentEncrypter(AP4_CencVariant variant, AP4_UI32 options, AP4_ContainerAtom* traf, AP4_CencEncryptingProcessor::Encrypter* encrypter, AP4_UI32 cleartext_sample_description_index) : m_Variant(variant), m_Options(options), m_Traf(traf), m_SampleEncryptionAtom(NULL), m_SampleEncryptionAtomShadow(NULL), m_Saiz(NULL), m_Saio(NULL), m_Encrypter(encrypter), m_CleartextSampleDescriptionIndex(cleartext_sample_description_index) { } /*---------------------------------------------------------------------- | AP4_CencFragmentEncrypter::ProcessFragment +---------------------------------------------------------------------*/ AP4_Result AP4_CencFragmentEncrypter::ProcessFragment() { m_SampleEncryptionAtom = NULL; m_SampleEncryptionAtomShadow = NULL; m_Saiz = NULL; m_Saio = NULL; // set the default-base-is-moof flag AP4_TfhdAtom* tfhd = AP4_DYNAMIC_CAST(AP4_TfhdAtom, m_Traf->GetChild(AP4_ATOM_TYPE_TFHD)); if (tfhd) { if (m_Variant != AP4_CENC_VARIANT_PIFF_CBC && m_Variant != AP4_CENC_VARIANT_PIFF_CTR) { tfhd->SetFlags(tfhd->GetFlags() | AP4_TFHD_FLAG_DEFAULT_BASE_IS_MOOF); } } // if we're still in the cleartext lead, update the tfhd and stop if (m_Encrypter->m_CurrentFragment < m_Encrypter->m_CleartextFragments && m_CleartextSampleDescriptionIndex) { if (tfhd) { tfhd->SetSampleDescriptionIndex(m_CleartextSampleDescriptionIndex); tfhd->SetFlags(tfhd->GetFlags() | AP4_TFHD_FLAG_SAMPLE_DESCRIPTION_INDEX_PRESENT); tfhd->SetSize(AP4_TfhdAtom::ComputeSize(tfhd->GetFlags())); m_Traf->OnChildChanged(tfhd); } return AP4_SUCCESS; } // create a sample encryption atom switch (m_Variant) { case AP4_CENC_VARIANT_PIFF_CBC: m_SampleEncryptionAtom = new AP4_PiffSampleEncryptionAtom(16); break; case AP4_CENC_VARIANT_PIFF_CTR: m_SampleEncryptionAtom = new AP4_PiffSampleEncryptionAtom(8); break; case AP4_CENC_VARIANT_MPEG_CENC: if (m_Options & AP4_CencEncryptingProcessor::OPTION_PIFF_COMPATIBILITY) { AP4_UI08 iv_size = 8; if (m_Options & AP4_CencEncryptingProcessor::OPTION_PIFF_IV_SIZE_16) { iv_size = 16; } m_SampleEncryptionAtom = new AP4_SencAtom(iv_size); m_SampleEncryptionAtomShadow = new AP4_PiffSampleEncryptionAtom(iv_size); } else { AP4_UI08 iv_size = 16; // default if (m_Options & AP4_CencEncryptingProcessor::OPTION_IV_SIZE_8) { iv_size = 8; } m_SampleEncryptionAtom = new AP4_SencAtom(iv_size); } m_Saiz = new AP4_SaizAtom(); m_Saio = new AP4_SaioAtom(); break; case AP4_CENC_VARIANT_MPEG_CBC1: m_SampleEncryptionAtom = new AP4_SencAtom(16); m_Saiz = new AP4_SaizAtom(); m_Saio = new AP4_SaioAtom(); break; case AP4_CENC_VARIANT_MPEG_CENS: m_SampleEncryptionAtom = new AP4_SencAtom(16, 0, NULL, 0, 0); m_Saiz = new AP4_SaizAtom(); m_Saio = new AP4_SaioAtom(); break; case AP4_CENC_VARIANT_MPEG_CBCS: m_SampleEncryptionAtom = new AP4_SencAtom(0, 16, NULL, 0, 0); m_Saiz = new AP4_SaizAtom(); m_Saio = new AP4_SaioAtom(); break; default: return AP4_ERROR_INTERNAL; } if (m_Encrypter->m_SampleEncrypter->UseSubSamples()) { m_SampleEncryptionAtom->GetOuter().SetFlags( m_SampleEncryptionAtom->GetOuter().GetFlags() | AP4_CENC_SAMPLE_ENCRYPTION_FLAG_USE_SUB_SAMPLE_ENCRYPTION); if (m_SampleEncryptionAtomShadow) { m_SampleEncryptionAtomShadow->GetOuter().SetFlags( m_SampleEncryptionAtomShadow->GetOuter().GetFlags() | AP4_CENC_SAMPLE_ENCRYPTION_FLAG_USE_SUB_SAMPLE_ENCRYPTION); } } // this is mostly for testing: forces the clients to parse saio/saiz instead // on relying on 'senc' if (m_Options & AP4_CencEncryptingProcessor::OPTION_NO_SENC) { m_SampleEncryptionAtom->GetOuter().SetType(AP4_ATOM_TYPE('s', 'e', 'n', 'C')); } // add the child atoms to the traf container if (m_Saiz) m_Traf->AddChild(m_Saiz); if (m_Saio) m_Traf->AddChild(m_Saio); m_Traf->AddChild(&m_SampleEncryptionAtom->GetOuter()); if (m_SampleEncryptionAtomShadow) { m_Traf->AddChild(&m_SampleEncryptionAtomShadow->GetOuter()); } return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_CencFragmentEncrypter::PrepareForSamples +---------------------------------------------------------------------*/ AP4_Result AP4_CencFragmentEncrypter::PrepareForSamples(AP4_FragmentSampleTable* sample_table) { // do nothing if we're still in the clear lead part if (m_Encrypter->m_CurrentFragment < m_Encrypter->m_CleartextFragments) { return AP4_SUCCESS; } AP4_Cardinal sample_count = sample_table->GetSampleCount(); // resize the saio atom if we have one if (m_Saio) { m_Saio->AddEntry(0); // we'll compute the offset later } if (!m_Encrypter->m_SampleEncrypter->UseSubSamples()) { m_SampleEncryptionAtom->SetSampleInfosSize(sample_count*m_SampleEncryptionAtom->GetPerSampleIvSize()); if (m_SampleEncryptionAtomShadow) { m_SampleEncryptionAtomShadow->SetSampleInfosSize(sample_count*m_SampleEncryptionAtomShadow->GetPerSampleIvSize()); } if (m_Saiz) { if (m_SampleEncryptionAtom->GetPerSampleIvSize()) { m_Saiz->SetDefaultSampleInfoSize(m_SampleEncryptionAtom->GetPerSampleIvSize()); m_Saiz->SetSampleCount(sample_count); } else { m_Saiz->SetDefaultSampleInfoSize(0); m_Saiz->SetSampleCount(0); } } return AP4_SUCCESS; } // resize the saiz atom if we have one if (m_Saiz) { m_Saiz->SetSampleCount(sample_count); } // prepare for samples AP4_Sample sample; AP4_DataBuffer sample_data; AP4_Array bytes_of_cleartext_data; AP4_Array bytes_of_encrypted_data; AP4_Size sample_info_size = sample_count*(m_SampleEncryptionAtom->GetPerSampleIvSize()); for (unsigned int i=0; iGetSample(i, sample); if (AP4_FAILED(result)) return result; result = sample.ReadData(sample_data); if (AP4_FAILED(result)) return result; bytes_of_cleartext_data.SetItemCount(0); bytes_of_encrypted_data.SetItemCount(0); result = m_Encrypter->m_SampleEncrypter->GetSubSampleMap(sample_data, bytes_of_cleartext_data, bytes_of_encrypted_data); if (AP4_FAILED(result)) return result; sample_info_size += 2+bytes_of_cleartext_data.ItemCount()*6; if (m_Saiz) { m_Saiz->SetSampleInfoSize(i, (AP4_UI08)(m_SampleEncryptionAtom->GetPerSampleIvSize()+2+bytes_of_cleartext_data.ItemCount()*6)); } } m_SampleEncryptionAtom->SetSampleInfosSize(sample_info_size); if (m_SampleEncryptionAtomShadow) { m_SampleEncryptionAtomShadow->SetSampleInfosSize(sample_info_size); } return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_CencFragmentEncrypter::ProcessSample +---------------------------------------------------------------------*/ AP4_Result AP4_CencFragmentEncrypter::ProcessSample(AP4_DataBuffer& data_in, AP4_DataBuffer& data_out) { // just copy data if we're still in the clear lead part if (m_Encrypter->m_CurrentFragment < m_Encrypter->m_CleartextFragments) { data_out.SetData(data_in.GetData(), data_in.GetDataSize()); return AP4_SUCCESS; } // copy the current IV AP4_UI08 iv[16]; AP4_CopyMemory(iv, m_Encrypter->m_SampleEncrypter->GetIv(), 16); // encrypt the sample AP4_DataBuffer sample_infos; AP4_Result result = m_Encrypter->m_SampleEncrypter->EncryptSampleData(data_in, data_out, sample_infos); if (AP4_FAILED(result)) return result; // update the sample info m_SampleEncryptionAtom->AddSampleInfo(iv, sample_infos); if (m_SampleEncryptionAtomShadow) { m_SampleEncryptionAtomShadow->AddSampleInfo(iv, sample_infos); } return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_CencFragmentEncrypter::FinishFragment +---------------------------------------------------------------------*/ AP4_Result AP4_CencFragmentEncrypter::FinishFragment() { if (m_Encrypter->m_CurrentFragment++ < m_Encrypter->m_CleartextFragments) { return AP4_SUCCESS; } if (!m_Saio) return AP4_SUCCESS; // compute the saio offsets AP4_ContainerAtom* moof = AP4_DYNAMIC_CAST(AP4_ContainerAtom, m_Traf->GetParent()); if (moof == NULL) return AP4_ERROR_INTERNAL; AP4_UI64 traf_offset = moof->GetHeaderSize(); AP4_List::Item* child = moof->GetChildren().FirstItem(); while (child) { if (AP4_DYNAMIC_CAST(AP4_ContainerAtom, child->GetData()) == m_Traf) { AP4_UI64 senc_offset = m_Traf->GetHeaderSize(); AP4_Atom* senc_atom = NULL; for (AP4_List::Item* item = m_Traf->GetChildren().FirstItem(); item && !senc_atom; item = item->GetNext()) { AP4_Atom* atom = item->GetData(); if (atom->GetType() == AP4_ATOM_TYPE_SENC || atom->GetType() == AP4_ATOM_TYPE('s', 'e', 'n', 'C')) { // sometimes used instead of 'senc' for testing senc_atom = atom; } else if (atom->GetType() == AP4_ATOM_TYPE_UUID) { AP4_UuidAtom* uuid_atom = AP4_DYNAMIC_CAST(AP4_UuidAtom, atom); if (AP4_CompareMemory(uuid_atom->GetUuid(), AP4_UUID_PIFF_SAMPLE_ENCRYPTION_ATOM, 16) == 0) { senc_atom = atom; } } if (senc_atom) break; senc_offset += atom->GetSize(); } if (senc_atom) { AP4_UI64 saio_offset = traf_offset+senc_offset+senc_atom->GetHeaderSize()+4; m_Saio->SetEntry(0, saio_offset); } } else { traf_offset += child->GetData()->GetSize(); } child = child->GetNext(); } return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_CencEncryptingProcessor:AP4_CencEncryptingProcessor +---------------------------------------------------------------------*/ AP4_CencEncryptingProcessor::AP4_CencEncryptingProcessor(AP4_CencVariant variant, AP4_UI32 options, AP4_BlockCipherFactory* block_cipher_factory) : m_Variant(variant), m_Options(options) { // create a block cipher factory if none is given if (block_cipher_factory == NULL) { m_BlockCipherFactory = &AP4_DefaultBlockCipherFactory::Instance; } else { m_BlockCipherFactory = block_cipher_factory; } } /*---------------------------------------------------------------------- | AP4_CencEncryptingProcessor:~AP4_CencEncryptingProcessor +---------------------------------------------------------------------*/ AP4_CencEncryptingProcessor::~AP4_CencEncryptingProcessor() { m_Encrypters.DeleteReferences(); } /*---------------------------------------------------------------------- | AP4_CencEncryptingProcessor::Initialize +---------------------------------------------------------------------*/ AP4_Result AP4_CencEncryptingProcessor::Initialize(AP4_AtomParent& top_level, AP4_ByteStream& /*stream*/, AP4_Processor::ProgressListener* /*listener*/) { 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 compatible_brands; compatible_brands.EnsureCapacity(ftyp->GetCompatibleBrands().ItemCount()+1); for (unsigned int i=0; iGetCompatibleBrands().ItemCount(); i++) { compatible_brands.Append(ftyp->GetCompatibleBrands()[i]); } // add the compatible brand if it is not already there if (m_Variant == AP4_CENC_VARIANT_PIFF_CBC || m_Variant == AP4_CENC_VARIANT_PIFF_CTR) { if (!ftyp->HasCompatibleBrand(AP4_PIFF_BRAND)) { compatible_brands.Append(AP4_PIFF_BRAND); } } else if (m_Variant == AP4_CENC_VARIANT_MPEG_CENC || m_Variant == AP4_CENC_VARIANT_MPEG_CBC1 || m_Variant == AP4_CENC_VARIANT_MPEG_CENS || m_Variant == AP4_CENC_VARIANT_MPEG_CBCS) { if (!ftyp->HasCompatibleBrand(AP4_FILE_BRAND_ISO6)) { compatible_brands.Append(AP4_FILE_BRAND_ISO6); } } // create a replacement AP4_FtypAtom* new_ftyp = new AP4_FtypAtom(ftyp->GetMajorBrand(), ftyp->GetMinorVersion(), &compatible_brands[0], compatible_brands.ItemCount()); delete ftyp; ftyp = new_ftyp; } else { AP4_Array compatible_brands; compatible_brands.Append(AP4_FILE_BRAND_ISO6); if (m_Variant == AP4_CENC_VARIANT_PIFF_CBC || m_Variant == AP4_CENC_VARIANT_PIFF_CTR) { compatible_brands.Append(AP4_PIFF_BRAND); compatible_brands.Append(AP4_FTYP_BRAND_MSDH); } ftyp = new AP4_FtypAtom(AP4_FTYP_BRAND_MP42, 0, &compatible_brands[0], compatible_brands.ItemCount()); } // insert the ftyp atom as the first child AP4_Result result = top_level.AddChild(ftyp, 0); if (AP4_FAILED(result)) return result; // add pssh atoms if needed AP4_ContainerAtom* moov = AP4_DYNAMIC_CAST(AP4_ContainerAtom, top_level.GetChild(AP4_ATOM_TYPE_MOOV)); if (moov) { // create a 'standard EME' pssh atom AP4_PsshAtom* eme_pssh = NULL; if ((m_Variant == AP4_CENC_VARIANT_MPEG_CENC || m_Variant == AP4_CENC_VARIANT_MPEG_CBC1 || m_Variant == AP4_CENC_VARIANT_MPEG_CENS || m_Variant == AP4_CENC_VARIANT_MPEG_CBCS) && (m_Options & OPTION_EME_PSSH)) { AP4_DataBuffer kids; AP4_UI32 kid_count = 0; const AP4_List& prop_entries = m_PropertyMap.GetEntries(); for (unsigned int i=0; im_TrackId, "KID"); if (kid_hex && AP4_StringLength(kid_hex) == 32) { AP4_UI08 kid[16]; AP4_ParseHex(kid_hex, kid, 16); // only add this entry if there isn't already an entry for this KID bool found = false; for (unsigned int j=0; jSetKids(kids.GetData(), kid_count); } } // check if we need to create a Marlin 'mkid' table AP4_PsshAtom* marlin_pssh = NULL; if (m_Variant == AP4_CENC_VARIANT_MPEG_CENC) { const AP4_List& prop_entries = m_PropertyMap.GetEntries(); AP4_MkidAtom* mkid = NULL; for (unsigned int i=0; im_Name == "ContentId") { if (mkid == NULL) mkid = new AP4_MkidAtom(); const char* kid_hex = m_PropertyMap.GetProperty(entry->m_TrackId, "KID"); if (kid_hex && AP4_StringLength(kid_hex) == 32) { AP4_UI08 kid[16]; AP4_ParseHex(kid_hex, kid, 16); // only add this entry if there isn't already an entry for this KID const AP4_Array& entries = mkid->GetEntries(); bool found = false; for (unsigned int j=0; jAddEntry(kid, entry->m_Value.GetChars()); } } } } if (mkid) { // create a 'marl' container AP4_ContainerAtom* marl = new AP4_ContainerAtom(AP4_ATOM_TYPE_MARL); marl->AddChild(mkid); // see if we need to pad the 'pssh' container const char* padding_str = m_PropertyMap.GetProperty(0, "PsshPadding"); AP4_UI32 padding = 0; if (padding_str) { padding = AP4_ParseIntegerU(padding_str); } // create a 'pssh' atom to contain the 'marl' atom marlin_pssh = new AP4_PsshAtom(AP4_MARLIN_PSSH_SYSTEM_ID); marlin_pssh->SetData(*marl); if (padding > marl->GetSize()+32 && padding < 1024*1024) { padding -= (AP4_UI32)marl->GetSize()+32; AP4_UI08* data = new AP4_UI08[padding]; AP4_SetMemory(data, 0, padding); marlin_pssh->SetPadding(data, padding); delete[] data; } } } // add the 'pssh' atoms at the end of the 'moov' container // but before any 'free' atom, if any int pssh_position = -1; int pssh_current = 0; for (AP4_List::Item* child = moov->GetChildren().FirstItem(); child; child = child->GetNext(), ++pssh_current) { if (child->GetData()->GetType() == AP4_ATOM_TYPE_FREE) { pssh_position = pssh_current; } } if (marlin_pssh) { moov->AddChild(marlin_pssh, pssh_position); if (pssh_position >= 0) { ++pssh_position; } } if (eme_pssh) { moov->AddChild(eme_pssh, pssh_position); if (pssh_position >= 0) { ++pssh_position; } } for (unsigned int i=0; iAddChild(new AP4_PsshAtom(*m_PsshAtoms[i]), pssh_position); } if (pssh_position >= 0) { ++pssh_position; } } } return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_CencEncryptingProcessor:CreateTrackHandler +---------------------------------------------------------------------*/ AP4_Processor::TrackHandler* AP4_CencEncryptingProcessor::CreateTrackHandler(AP4_TrakAtom* trak) { // find the stsd atom AP4_StsdAtom* stsd = AP4_DYNAMIC_CAST(AP4_StsdAtom, trak->FindChild("mdia/minf/stbl/stsd")); // avoid tracks with no stsd atom (should not happen) if (stsd == NULL) return NULL; // get the list of sample entries AP4_Array entries; for (unsigned int i=0; iGetSampleDescriptionCount(); i++) { AP4_SampleEntry* entry = stsd->GetSampleEntry(i); if (entry == NULL) return NULL; entries.Append(entry); } // create a handler for this track if we have a key for it and we know // how to map the type const AP4_DataBuffer* key; const AP4_DataBuffer* iv; if (AP4_FAILED(m_KeyMap.GetKeyAndIv(trak->GetId(), key, iv))) { return NULL; } if (iv == NULL || iv->GetDataSize() != 16) { return NULL; } AP4_UI32 format = entries[0]->GetType(); // only look at the type of the first entry AP4_UI32 enc_format = 0; switch (format) { case AP4_ATOM_TYPE_MP4A: enc_format = AP4_ATOM_TYPE_ENCA; break; case AP4_ATOM_TYPE_MP4V: case AP4_ATOM_TYPE_AVC1: case AP4_ATOM_TYPE_AVC2: case AP4_ATOM_TYPE_AVC3: case AP4_ATOM_TYPE_AVC4: case AP4_ATOM_TYPE_DVAV: case AP4_ATOM_TYPE_DVA1: case AP4_ATOM_TYPE_HEV1: case AP4_ATOM_TYPE_HVC1: case AP4_ATOM_TYPE_DVHE: case AP4_ATOM_TYPE_DVH1: enc_format = AP4_ATOM_TYPE_ENCV; break; default: { // try to find if this is audio or video AP4_HdlrAtom* hdlr = AP4_DYNAMIC_CAST(AP4_HdlrAtom, trak->FindChild("mdia/hdlr")); if (hdlr) { switch (hdlr->GetHandlerType()) { case AP4_HANDLER_TYPE_SOUN: enc_format = AP4_ATOM_TYPE_ENCA; break; case AP4_HANDLER_TYPE_VIDE: enc_format = AP4_ATOM_TYPE_ENCV; break; } } break; } } if (enc_format == 0) return NULL; // get the track properties AP4_UI08 kid[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; const char* kid_hex = m_PropertyMap.GetProperty(trak->GetId(), "KID"); if (kid_hex && AP4_StringLength(kid_hex) == 32) { AP4_ParseHex(kid_hex, kid, 16); } // create the encrypter AP4_Processor::TrackHandler* track_encrypter; AP4_UI08 cipher_iv_size = 16; AP4_BlockCipher::CipherMode cipher_mode; AP4_BlockCipher::CtrParams cipher_ctr_params; const void* cipher_mode_params = NULL; AP4_UI08 crypt_byte_block = 0; AP4_UI08 skip_byte_block = 0; bool constant_iv = false; bool reset_iv_at_each_subsample = false; switch (m_Variant) { case AP4_CENC_VARIANT_PIFF_CTR: cipher_mode = AP4_BlockCipher::CTR; cipher_ctr_params.counter_size = 8; cipher_mode_params = &cipher_ctr_params; cipher_iv_size = 8; track_encrypter = new AP4_CencTrackEncrypter(m_Variant, 1, cipher_iv_size, kid, 0, NULL, 0, 0, entries, enc_format); break; case AP4_CENC_VARIANT_PIFF_CBC: cipher_mode = AP4_BlockCipher::CBC; track_encrypter = new AP4_CencTrackEncrypter(m_Variant, 2, cipher_iv_size, kid, 0, NULL, 0, 0, entries, enc_format); break; case AP4_CENC_VARIANT_MPEG_CENC: cipher_mode = AP4_BlockCipher::CTR; cipher_ctr_params.counter_size = 8; cipher_mode_params = &cipher_ctr_params; if ((m_Options & OPTION_IV_SIZE_8) || ((m_Options & OPTION_PIFF_COMPATIBILITY) && !(m_Options & OPTION_PIFF_IV_SIZE_16))) { cipher_iv_size = 8; } track_encrypter = new AP4_CencTrackEncrypter(m_Variant, 1, cipher_iv_size, kid, 0, NULL, 0, 0, entries, enc_format); break; case AP4_CENC_VARIANT_MPEG_CENS: cipher_mode = AP4_BlockCipher::CTR; cipher_ctr_params.counter_size = 8; cipher_mode_params = &cipher_ctr_params; if (m_Options & OPTION_IV_SIZE_8) { cipher_iv_size = 8; } if (enc_format == AP4_ATOM_TYPE_ENCV) { crypt_byte_block = 1; skip_byte_block = 9; } track_encrypter = new AP4_CencTrackEncrypter(m_Variant, 1, cipher_iv_size, kid, 0, NULL, crypt_byte_block, skip_byte_block, entries, enc_format); break; case AP4_CENC_VARIANT_MPEG_CBC1: cipher_mode = AP4_BlockCipher::CBC; track_encrypter = new AP4_CencTrackEncrypter(m_Variant, 1, cipher_iv_size, kid, 0, NULL, 0, 0, entries, enc_format); break; case AP4_CENC_VARIANT_MPEG_CBCS: cipher_mode = AP4_BlockCipher::CBC; if (enc_format == AP4_ATOM_TYPE_ENCV) { crypt_byte_block = 1; skip_byte_block = 9; } constant_iv = true; reset_iv_at_each_subsample = true; track_encrypter = new AP4_CencTrackEncrypter(m_Variant, 1, 0, kid, cipher_iv_size, iv->GetData(), crypt_byte_block, skip_byte_block, entries, enc_format); break; default: return NULL; } // create a block cipher AP4_BlockCipher* block_cipher = NULL; AP4_Result result = m_BlockCipherFactory->CreateCipher(AP4_BlockCipher::AES_128, AP4_BlockCipher::ENCRYPT, cipher_mode, cipher_mode_params, key->GetData(), key->GetDataSize(), block_cipher); if (AP4_FAILED(result)) { delete track_encrypter; return NULL; } // compute the size of NAL units unsigned int nalu_length_size = 0; if (format == AP4_ATOM_TYPE_AVC1 || format == AP4_ATOM_TYPE_AVC2 || format == AP4_ATOM_TYPE_AVC3 || format == AP4_ATOM_TYPE_AVC4 || format == AP4_ATOM_TYPE_DVAV || format == AP4_ATOM_TYPE_DVA1) { AP4_AvccAtom* avcc = AP4_DYNAMIC_CAST(AP4_AvccAtom, entries[0]->GetChild(AP4_ATOM_TYPE_AVCC)); if (avcc) { nalu_length_size = avcc->GetNaluLengthSize(); } } else if (format == AP4_ATOM_TYPE_HEV1 || format == AP4_ATOM_TYPE_HVC1 || format == AP4_ATOM_TYPE_DVHE || format == AP4_ATOM_TYPE_DVH1) { AP4_HvccAtom* hvcc = AP4_DYNAMIC_CAST(AP4_HvccAtom, entries[0]->GetChild(AP4_ATOM_TYPE_HVCC)); if (hvcc) { nalu_length_size = hvcc->GetNaluLengthSize(); } } // add a new cipher state for this track AP4_CencSampleEncrypter* sample_encrypter = NULL; AP4_StreamCipher* stream_cipher = NULL; switch (cipher_mode) { case AP4_BlockCipher::CBC: stream_cipher = new AP4_CbcStreamCipher(block_cipher); if (crypt_byte_block && skip_byte_block) { stream_cipher = new AP4_PatternStreamCipher(stream_cipher, crypt_byte_block, skip_byte_block); } if (nalu_length_size) { AP4_CencSubSampleMapper* subsample_mapper = NULL; if (m_Variant == AP4_CENC_VARIANT_MPEG_CBCS) { subsample_mapper = new AP4_CencCbcsSubSampleMapper(nalu_length_size, format, trak); } else { subsample_mapper = new AP4_CencBasicSubSampleMapper(nalu_length_size, format); } sample_encrypter = new AP4_CencCbcSubSampleEncrypter(stream_cipher, constant_iv, reset_iv_at_each_subsample, subsample_mapper); } else { sample_encrypter = new AP4_CencCbcSampleEncrypter(stream_cipher, constant_iv); } break; case AP4_BlockCipher::CTR: stream_cipher = new AP4_CtrStreamCipher(block_cipher, 16); if (crypt_byte_block && skip_byte_block) { stream_cipher = new AP4_PatternStreamCipher(stream_cipher, crypt_byte_block, skip_byte_block); } if (nalu_length_size) { AP4_CencSubSampleMapper* subsample_mapper = new AP4_CencAdvancedSubSampleMapper(nalu_length_size, format); sample_encrypter = new AP4_CencCtrSubSampleEncrypter(stream_cipher, constant_iv, reset_iv_at_each_subsample, cipher_iv_size, subsample_mapper); } else { sample_encrypter = new AP4_CencCtrSampleEncrypter(stream_cipher, constant_iv, cipher_iv_size); } break; } if (sample_encrypter == NULL) { delete stream_cipher; delete block_cipher; delete track_encrypter; return NULL; } sample_encrypter->SetIv(iv->GetData()); // if we need to leave some samples unencrypted, create clones of the sample descriptions const char* clear_lead = m_PropertyMap.GetProperty(trak->GetId(), "ClearLeadFragments"); AP4_UI32 clear_fragments = 0; if (clear_lead) { clear_fragments = AP4_ParseIntegerU(clear_lead); unsigned int sample_description_count = stsd->GetSampleDescriptionCount(); for (unsigned int i=0; iAddChild(stsd->GetSampleEntry(i)->Clone()); } } m_Encrypters.Add(new Encrypter(trak->GetId(), clear_fragments, sample_encrypter)); return track_encrypter; } /*---------------------------------------------------------------------- | AP4_CencEncryptingProcessor:CreateFragmentHandler +---------------------------------------------------------------------*/ AP4_Processor::FragmentHandler* AP4_CencEncryptingProcessor::CreateFragmentHandler(AP4_TrakAtom* trak, AP4_TrexAtom* trex, AP4_ContainerAtom* traf, AP4_ByteStream& /* moof_data */, AP4_Position /* moof_offset */) { // get the traf header (tfhd) for this track fragment so we can get the track ID AP4_TfhdAtom* tfhd = AP4_DYNAMIC_CAST(AP4_TfhdAtom, traf->GetChild(AP4_ATOM_TYPE_TFHD)); if (tfhd == NULL) return NULL; // lookup the encrypter for this track Encrypter* encrypter = NULL; for (AP4_List::Item* item = m_Encrypters.FirstItem(); item; item = item->GetNext()) { if (item->GetData()->m_TrackId == tfhd->GetTrackId()) { encrypter = item->GetData(); break; } } if (encrypter == NULL) return NULL; AP4_UI32 clear_sample_description_index = 0; const char* clear_lead = m_PropertyMap.GetProperty(trak->GetId(), "ClearLeadFragments"); if (clear_lead) { if (encrypter->m_CurrentFragment < encrypter->m_CleartextFragments) { // find the stsd atom AP4_StsdAtom* stsd = AP4_DYNAMIC_CAST(AP4_StsdAtom, trak->FindChild("mdia/minf/stbl/stsd")); if (stsd) { AP4_UI32 tfhd_flags = tfhd->GetFlags(); AP4_UI32 sample_description_index = 0; if (tfhd_flags & AP4_TFHD_FLAG_SAMPLE_DESCRIPTION_INDEX_PRESENT) { sample_description_index = tfhd->GetSampleDescriptionIndex(); } else { sample_description_index = trex->GetDefaultSampleDescriptionIndex(); } if (sample_description_index > 0) { clear_sample_description_index = sample_description_index+stsd->GetSampleDescriptionCount()/2; } } } } return new AP4_CencFragmentEncrypter(m_Variant, m_Options, traf, encrypter, clear_sample_description_index); } /*---------------------------------------------------------------------- | AP4_CencSingleSampleDecrypter::Create +---------------------------------------------------------------------*/ AP4_Result AP4_CencSingleSampleDecrypter::Create(AP4_UI32 cipher_type, const AP4_UI08* key, AP4_Size key_size, AP4_UI08 crypt_byte_block, AP4_UI08 skip_byte_block, AP4_BlockCipherFactory* block_cipher_factory, bool reset_iv_at_each_subsample, AP4_CencSingleSampleDecrypter*& decrypter) { // check the parameters if (key == NULL) return AP4_ERROR_INVALID_PARAMETERS; // use the default cipher factory if none was passed if (block_cipher_factory == NULL) { block_cipher_factory = &AP4_DefaultBlockCipherFactory::Instance; } // create the cipher AP4_StreamCipher* stream_cipher = NULL; bool full_blocks_only = false; switch (cipher_type) { case AP4_CENC_CIPHER_NONE: break; case AP4_CENC_CIPHER_AES_128_CTR: { // create the block cipher AP4_BlockCipher* block_cipher = NULL; AP4_BlockCipher::CtrParams ctr_params; ctr_params.counter_size = 8; AP4_Result result = block_cipher_factory->CreateCipher(AP4_BlockCipher::AES_128, AP4_BlockCipher::DECRYPT, AP4_BlockCipher::CTR, &ctr_params, key, key_size, block_cipher); if (AP4_FAILED(result)) return result; // create the stream cipher stream_cipher = new AP4_CtrStreamCipher(block_cipher, 8); break; } case AP4_CENC_CIPHER_AES_128_CBC: { // create the block cipher 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 the stream cipher stream_cipher = new AP4_CbcStreamCipher(block_cipher); // with CBC, we only use full blocks full_blocks_only = true; break; } default: return AP4_ERROR_NOT_SUPPORTED; } // wrap with a pattern processor if needed if (crypt_byte_block && skip_byte_block) { stream_cipher = new AP4_PatternStreamCipher(stream_cipher, crypt_byte_block, skip_byte_block); } // create the decrypter decrypter = new AP4_CencSingleSampleDecrypter(stream_cipher, full_blocks_only, reset_iv_at_each_subsample); return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_CencSingleSampleDecrypter::~AP4_CencSingleSampleDecrypter +---------------------------------------------------------------------*/ AP4_CencSingleSampleDecrypter::~AP4_CencSingleSampleDecrypter() { delete m_Cipher; } /*---------------------------------------------------------------------- | AP4_CencSingleSampleDecrypter::DecryptSampleData +---------------------------------------------------------------------*/ AP4_Result AP4_CencSingleSampleDecrypter::DecryptSampleData(AP4_DataBuffer& data_in, AP4_DataBuffer& data_out, const AP4_UI08* iv, unsigned int subsample_count, const AP4_UI16* bytes_of_cleartext_data, const AP4_UI32* bytes_of_encrypted_data) { // the output has the same size as the input data_out.SetDataSize(data_in.GetDataSize()); // check input parameters if (iv == NULL) return AP4_ERROR_INVALID_PARAMETERS; if (subsample_count) { if (bytes_of_cleartext_data == NULL || bytes_of_encrypted_data == NULL) { return AP4_ERROR_INVALID_PARAMETERS; } } // shortcut for NULL ciphers if (m_Cipher == NULL) { AP4_CopyMemory(data_out.UseData(), data_in.GetData(), data_in.GetDataSize()); return AP4_SUCCESS; } // setup direct pointers to the buffers const AP4_UI08* in = data_in.GetData(); AP4_UI08* out = data_out.UseData(); // setup the IV m_Cipher->SetIV(iv); if (subsample_count) { // process the sample data, one sub-sample at a time const AP4_UI08* in_end = data_in.GetData()+data_in.GetDataSize(); for (unsigned int i=0; iSetIV(iv); } AP4_Result result = m_Cipher->ProcessBuffer(in+cleartext_size, encrypted_size, out+cleartext_size, &encrypted_size, false); if (AP4_FAILED(result)) return result; } // move the pointers and udate counters in += cleartext_size+encrypted_size; out += cleartext_size+encrypted_size; } // copy any leftover partial block unsigned int partial = (unsigned int)(in_end-in); if (partial) { AP4_CopyMemory(out, in, partial); } } else { if (m_FullBlocksOnly) { unsigned int block_count = data_in.GetDataSize()/16; if (block_count) { AP4_Size out_size = data_out.GetDataSize(); AP4_Result result = m_Cipher->ProcessBuffer(in, block_count*16, out, &out_size, false); if (AP4_FAILED(result)) return result; AP4_ASSERT(out_size == block_count*16); in += block_count*16; out += block_count*16; } // any partial block at the end remains in the clear unsigned int partial = data_in.GetDataSize()%16; if (partial) { AP4_CopyMemory(out, in, partial); } } else { // process the entire sample data at once AP4_Size encrypted_size = data_in.GetDataSize(); AP4_Result result = m_Cipher->ProcessBuffer(in, encrypted_size, out, &encrypted_size, true); if (AP4_FAILED(result)) return result; } } return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_CencSampleDecrypter::Create +---------------------------------------------------------------------*/ AP4_Result AP4_CencSampleDecrypter::Create(AP4_ProtectedSampleDescription* sample_description, AP4_ContainerAtom* traf, AP4_ByteStream& aux_info_data, AP4_Position aux_info_data_offset, const AP4_UI08* key, AP4_Size key_size, AP4_BlockCipherFactory* block_cipher_factory, AP4_CencSampleDecrypter*& decrypter) { AP4_SaioAtom* saio = NULL; AP4_SaizAtom* saiz = NULL; AP4_CencSampleEncryption* sample_encryption_atom = NULL; return Create(sample_description, traf, aux_info_data, aux_info_data_offset, key, key_size, block_cipher_factory, saio, saiz, sample_encryption_atom, decrypter); } /*---------------------------------------------------------------------- | AP4_CencSampleDecrypter::Create +---------------------------------------------------------------------*/ AP4_Result AP4_CencSampleDecrypter::Create(AP4_ProtectedSampleDescription* sample_description, AP4_ContainerAtom* traf, AP4_ByteStream& aux_info_data, AP4_Position aux_info_data_offset, const AP4_UI08* key, AP4_Size key_size, AP4_BlockCipherFactory* block_cipher_factory, AP4_SaioAtom*& saio, AP4_SaizAtom*& saiz, AP4_CencSampleEncryption*& sample_encryption_atom, AP4_CencSampleDecrypter*& decrypter) { // default return values saio = NULL; saiz = NULL; sample_encryption_atom = NULL; decrypter = NULL; // create the sample info table AP4_CencSampleInfoTable* sample_info_table = NULL; AP4_UI32 protection_sheme = 0; bool reset_iv_at_each_subsample = false; AP4_Result result = AP4_CencSampleInfoTable::Create(sample_description, traf, saio, saiz, sample_encryption_atom, protection_sheme, reset_iv_at_each_subsample, aux_info_data, aux_info_data_offset, sample_info_table); if (AP4_FAILED(result)) { return result; } // we now have all the info we need to create the decrypter return Create(sample_info_table, protection_sheme, key, key_size, block_cipher_factory, reset_iv_at_each_subsample, decrypter); } /*---------------------------------------------------------------------- | AP4_CencSampleDecrypter::Create +---------------------------------------------------------------------*/ AP4_Result AP4_CencSampleDecrypter::Create(AP4_CencSampleInfoTable* sample_info_table, AP4_UI32 cipher_type, const AP4_UI08* key, AP4_Size key_size, AP4_BlockCipherFactory* block_cipher_factory, bool reset_iv_at_each_subsample, AP4_CencSampleDecrypter*& decrypter) { // default return value decrypter = NULL; // check some basic paramaters unsigned int iv_size = sample_info_table->GetIvSize(); switch (cipher_type) { case AP4_CENC_CIPHER_NONE: break; case AP4_CENC_CIPHER_AES_128_CTR: if (iv_size != 8 && iv_size != 16) { return AP4_ERROR_INVALID_FORMAT; } break; case AP4_CENC_CIPHER_AES_128_CBC: if (iv_size != 16) { return AP4_ERROR_INVALID_FORMAT; } break; default: return AP4_ERROR_NOT_SUPPORTED; } // create a single-sample decrypter AP4_CencSingleSampleDecrypter* single_sample_decrypter = NULL; AP4_Result result = AP4_CencSingleSampleDecrypter::Create(cipher_type, key, key_size, sample_info_table->GetCryptByteBlock(), sample_info_table->GetSkipByteBlock(), block_cipher_factory, reset_iv_at_each_subsample, single_sample_decrypter); if (AP4_FAILED(result)) return result; // create the decrypter decrypter = new AP4_CencSampleDecrypter(single_sample_decrypter, sample_info_table); return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_CencSampleDecrypter::~AP4_CencSampleDecrypter +---------------------------------------------------------------------*/ AP4_CencSampleDecrypter::~AP4_CencSampleDecrypter() { delete m_SampleInfoTable; delete m_SingleSampleDecrypter; } /*---------------------------------------------------------------------- | AP4_CencSampleDecrypter::SetSampleIndex +---------------------------------------------------------------------*/ AP4_Result AP4_CencSampleDecrypter::SetSampleIndex(AP4_Ordinal sample_index) { m_SampleCursor = sample_index; return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_CencSampleDecrypter::DecryptSampleData +---------------------------------------------------------------------*/ AP4_Result AP4_CencSampleDecrypter::DecryptSampleData(AP4_DataBuffer& data_in, AP4_DataBuffer& data_out, const AP4_UI08* iv) { // increment the sample cursor unsigned int sample_cursor = m_SampleCursor++; // setup the IV unsigned char iv_block[16]; if (iv == NULL) { iv = m_SampleInfoTable->GetIv(sample_cursor); } if (iv == NULL) return AP4_ERROR_INVALID_FORMAT; unsigned int iv_size = m_SampleInfoTable->GetIvSize(); AP4_CopyMemory(iv_block, iv, iv_size); if (iv_size != 16) AP4_SetMemory(&iv_block[iv_size], 0, 16-iv_size); // get the subsample info for this sample if needed unsigned int subsample_count = 0; const AP4_UI16* bytes_of_cleartext_data = NULL; const AP4_UI32* bytes_of_encrypted_data = NULL; if (m_SampleInfoTable) { AP4_Result result = m_SampleInfoTable->GetSampleInfo(sample_cursor, subsample_count, bytes_of_cleartext_data, bytes_of_encrypted_data); if (AP4_FAILED(result)) return result; } // decrypt the sample return m_SingleSampleDecrypter->DecryptSampleData(data_in, data_out, iv_block, subsample_count, bytes_of_cleartext_data, bytes_of_encrypted_data); } /*---------------------------------------------------------------------- | AP4_CencTrackDecrypter +---------------------------------------------------------------------*/ class AP4_CencTrackDecrypter : public AP4_Processor::TrackHandler { public: AP4_IMPLEMENT_DYNAMIC_CAST(AP4_CencTrackDecrypter) // constructor static AP4_Result Create(const unsigned char* key, AP4_Size key_size, AP4_Array& sample_descriptions, AP4_Array& sample_entries, AP4_CencTrackDecrypter*& decrypter); // methods virtual AP4_Result ProcessSample(AP4_DataBuffer& data_in, AP4_DataBuffer& data_out); virtual AP4_Result ProcessTrack(); // accessors AP4_ProtectedSampleDescription* GetSampleDescription(unsigned int i) { if (i < m_SampleDescriptions.ItemCount()) { return m_SampleDescriptions[i]; } else { return NULL; } } private: // constructor AP4_CencTrackDecrypter(AP4_Array& sample_descriptions, AP4_Array& sample_entries, AP4_UI32 original_format); // members AP4_Array m_SampleDescriptions; AP4_Array m_SampleEntries; AP4_UI32 m_OriginalFormat; }; /*---------------------------------------------------------------------- | AP4_CencTrackDecrypter Dynamic Cast Anchor +---------------------------------------------------------------------*/ AP4_DEFINE_DYNAMIC_CAST_ANCHOR(AP4_CencTrackDecrypter) /*---------------------------------------------------------------------- | AP4_CencTrackDecrypter::Create +---------------------------------------------------------------------*/ AP4_Result AP4_CencTrackDecrypter::Create(const unsigned char* key, AP4_Size /* key_size */, AP4_Array& sample_descriptions, AP4_Array& sample_entries, AP4_CencTrackDecrypter*& decrypter) { decrypter = NULL; // check and set defaults if (key == NULL) { return AP4_ERROR_INVALID_PARAMETERS; } // instantiate the object decrypter = new AP4_CencTrackDecrypter(sample_descriptions, sample_entries, sample_descriptions[0]->GetOriginalFormat()); return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_CencTrackDecrypter::AP4_CencTrackDecrypter +---------------------------------------------------------------------*/ AP4_CencTrackDecrypter::AP4_CencTrackDecrypter(AP4_Array& sample_descriptions, AP4_Array& sample_entries, AP4_UI32 original_format) : m_OriginalFormat(original_format) { for (unsigned int i=0; iSetType(m_OriginalFormat); m_SampleEntries[i]->DeleteChild(AP4_ATOM_TYPE_SINF); } return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_CencFragmentDecrypter +---------------------------------------------------------------------*/ class AP4_CencFragmentDecrypter : public AP4_Processor::FragmentHandler { public: // constructor AP4_CencFragmentDecrypter(AP4_CencSampleDecrypter* sample_decrypter, // ownership is transfered AP4_SaioAtom* saio_atom, AP4_SaizAtom* saiz_atom, AP4_CencSampleEncryption* sample_encryption_atom) : m_SampleDecrypter(sample_decrypter), m_SaioAtom(saio_atom), m_SaizAtom(saiz_atom), m_SampleEncryptionAtom(sample_encryption_atom) {} ~AP4_CencFragmentDecrypter() { delete m_SampleDecrypter; } // methods virtual AP4_Result ProcessFragment(); virtual AP4_Result FinishFragment(); virtual AP4_Result ProcessSample(AP4_DataBuffer& data_in, AP4_DataBuffer& data_out); private: // members AP4_CencSampleDecrypter* m_SampleDecrypter; AP4_SaioAtom* m_SaioAtom; AP4_SaizAtom* m_SaizAtom; AP4_CencSampleEncryption* m_SampleEncryptionAtom; }; /*---------------------------------------------------------------------- | AP4_CencFragmentDecrypter::ProcessFragment +---------------------------------------------------------------------*/ AP4_Result AP4_CencFragmentDecrypter::ProcessFragment() { // detach the sample encryption atom if (m_SampleDecrypter) { if (m_SaioAtom) m_SaioAtom->Detach(); if (m_SaizAtom) m_SaizAtom->Detach(); if (m_SampleEncryptionAtom) m_SampleEncryptionAtom->GetOuter().Detach(); } return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_CencFragmentDecrypter::FinishFragment +---------------------------------------------------------------------*/ AP4_Result AP4_CencFragmentDecrypter::FinishFragment() { if (m_SampleDecrypter) { delete m_SaioAtom; m_SaioAtom = NULL; delete m_SaizAtom; m_SaizAtom = NULL; delete m_SampleEncryptionAtom; m_SampleEncryptionAtom = NULL; } return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_CencFragmentDecrypter::ProcessSample +---------------------------------------------------------------------*/ AP4_Result AP4_CencFragmentDecrypter::ProcessSample(AP4_DataBuffer& data_in, AP4_DataBuffer& data_out) { // decrypt the sample return m_SampleDecrypter->DecryptSampleData(data_in, data_out, NULL); } /*---------------------------------------------------------------------- | AP4_CencDecryptingProcessor::AP4_CencDecryptingProcessor +---------------------------------------------------------------------*/ AP4_CencDecryptingProcessor::AP4_CencDecryptingProcessor(const AP4_ProtectionKeyMap* key_map, AP4_BlockCipherFactory* block_cipher_factory) : m_KeyMap(key_map) { if (block_cipher_factory) { m_BlockCipherFactory = block_cipher_factory; } else { m_BlockCipherFactory = &AP4_DefaultBlockCipherFactory::Instance; } } /*---------------------------------------------------------------------- | AP4_CencDecryptingProcessor:GetKeyForTrak +---------------------------------------------------------------------*/ const AP4_DataBuffer* AP4_CencDecryptingProcessor::GetKeyForTrak(AP4_UI32 track_id, AP4_ProtectedSampleDescription* sample_description) { // look for the key by track ID const AP4_DataBuffer* key = m_KeyMap->GetKey(track_id); if (!key) { // no key found by track ID, look for a key by KID if (sample_description) { AP4_ProtectionSchemeInfo* scheme_info = sample_description->GetSchemeInfo(); if (scheme_info) { AP4_ContainerAtom* schi = scheme_info->GetSchiAtom(); if (schi) { AP4_TencAtom* tenc = AP4_DYNAMIC_CAST(AP4_TencAtom, schi->FindChild("tenc")); if (tenc) { const AP4_UI08* kid = tenc->GetDefaultKid(); if (kid) { key = m_KeyMap->GetKeyByKid(kid); } } } } } } return key; } /*---------------------------------------------------------------------- | AP4_CencDecryptingProcessor:CreateTrackHandler +---------------------------------------------------------------------*/ AP4_Processor::TrackHandler* AP4_CencDecryptingProcessor::CreateTrackHandler(AP4_TrakAtom* trak) { // find the stsd atom AP4_StsdAtom* stsd = AP4_DYNAMIC_CAST(AP4_StsdAtom, trak->FindChild("mdia/minf/stbl/stsd")); // avoid tracks with no stsd atom (should not happen) if (stsd == NULL) return NULL; // if we don't have keys, don't bother if (m_KeyMap == NULL) return NULL; // build a list of sample descriptions AP4_Array sample_descs; AP4_Array sample_entries; for (unsigned int i=0; iGetSampleDescriptionCount(); i++) { AP4_SampleDescription* sample_desc = stsd->GetSampleDescription(i); AP4_SampleEntry* sample_entry = stsd->GetSampleEntry(i); if (sample_desc == NULL || sample_entry == NULL) continue; if (sample_desc->GetType() == AP4_SampleDescription::TYPE_PROTECTED) { AP4_ProtectedSampleDescription* protected_desc = static_cast(sample_desc); if (protected_desc->GetSchemeType() == AP4_PROTECTION_SCHEME_TYPE_PIFF || protected_desc->GetSchemeType() == AP4_PROTECTION_SCHEME_TYPE_CENC || protected_desc->GetSchemeType() == AP4_PROTECTION_SCHEME_TYPE_CBC1 || protected_desc->GetSchemeType() == AP4_PROTECTION_SCHEME_TYPE_CENS || protected_desc->GetSchemeType() == AP4_PROTECTION_SCHEME_TYPE_CBCS) { sample_descs.Append(protected_desc); sample_entries.Append(sample_entry); } } } if (sample_entries.ItemCount() == 0) return NULL; // get the key for this track const AP4_DataBuffer* key = GetKeyForTrak(trak->GetId(), sample_descs.ItemCount() ? sample_descs[0] : NULL); // create a decrypter with this key if (key) { AP4_CencTrackDecrypter* handler = NULL; AP4_Result result = AP4_CencTrackDecrypter::Create(key->GetData(), key->GetDataSize(), sample_descs, sample_entries, handler); if (AP4_FAILED(result)) return NULL; return handler; } return NULL; } /*---------------------------------------------------------------------- | AP4_CencDecryptingProcessor::CreateFragmentHandler +---------------------------------------------------------------------*/ AP4_Processor::FragmentHandler* AP4_CencDecryptingProcessor::CreateFragmentHandler(AP4_TrakAtom* trak, AP4_TrexAtom* trex, AP4_ContainerAtom* traf, AP4_ByteStream& moof_data, AP4_Position moof_offset) { // find the matching track handler to get to the track sample description const AP4_DataBuffer* key = NULL; AP4_ProtectedSampleDescription* sample_description = NULL; for (unsigned int i=0; iGetChild(AP4_ATOM_TYPE_TFHD)); if (tfhd && m_TrackIds[i] == tfhd->GetTrackId()) { // look for the Track Encryption Box AP4_CencTrackDecrypter* track_decrypter = AP4_DYNAMIC_CAST(AP4_CencTrackDecrypter, m_TrackHandlers[i]); if (track_decrypter) { unsigned int index = trex->GetDefaultSampleDescriptionIndex(); unsigned int tfhd_flags = tfhd->GetFlags(); if (tfhd_flags & AP4_TFHD_FLAG_SAMPLE_DESCRIPTION_INDEX_PRESENT) { index = tfhd->GetSampleDescriptionIndex(); } if (index >= 1) { sample_description = track_decrypter->GetSampleDescription(index-1); } if (sample_description == NULL) return NULL; // get the key for this track key = GetKeyForTrak(tfhd->GetTrackId(), sample_description); } break; } } if (sample_description == NULL || key == NULL) return NULL; // create the sample decrypter for the fragment AP4_CencSampleDecrypter* sample_decrypter = NULL; AP4_SaioAtom* saio = NULL; AP4_SaizAtom* saiz = NULL; AP4_CencSampleEncryption* sample_encryption_atom = NULL; AP4_Result result = AP4_CencSampleDecrypter::Create( sample_description, traf, moof_data, moof_offset, key->GetData(), key->GetDataSize(), m_BlockCipherFactory, saio, saiz, sample_encryption_atom, sample_decrypter); if (AP4_FAILED(result)) return NULL; return new AP4_CencFragmentDecrypter(sample_decrypter, saio, saiz, sample_encryption_atom); } /*---------------------------------------------------------------------- | AP4_CencTrackEncryption Dynamic Cast Anchor +---------------------------------------------------------------------*/ AP4_DEFINE_DYNAMIC_CAST_ANCHOR(AP4_CencTrackEncryption) /*---------------------------------------------------------------------- | AP4_CencTrackEncryption::AP4_CencTrackEncryption +---------------------------------------------------------------------*/ AP4_CencTrackEncryption::AP4_CencTrackEncryption(AP4_UI08 version) : m_Version_(version), m_DefaultIsProtected(0), m_DefaultPerSampleIvSize(0), m_DefaultConstantIvSize(0), m_DefaultCryptByteBlock(0), m_DefaultSkipByteBlock(0) { AP4_SetMemory(m_DefaultKid, 0, 16); AP4_SetMemory(m_DefaultConstantIv, 0, 16); } /*---------------------------------------------------------------------- | AP4_CencTrackEncryption::AP4_CencTrackEncryption +---------------------------------------------------------------------*/ AP4_CencTrackEncryption::AP4_CencTrackEncryption(AP4_UI08 version, AP4_UI08 default_is_protected, AP4_UI08 default_per_sample_iv_size, const AP4_UI08* default_kid, AP4_UI08 default_constant_iv_size, const AP4_UI08* default_constant_iv, AP4_UI08 default_crypt_byte_block, AP4_UI08 default_skip_byte_block) : m_Version_(version), m_DefaultIsProtected(default_is_protected), m_DefaultPerSampleIvSize(default_per_sample_iv_size), m_DefaultConstantIvSize(default_constant_iv_size), m_DefaultCryptByteBlock(default_crypt_byte_block), m_DefaultSkipByteBlock(default_skip_byte_block) { AP4_CopyMemory(m_DefaultKid, default_kid, 16); AP4_SetMemory(m_DefaultConstantIv, 0, 16); if (default_per_sample_iv_size == 0 && default_constant_iv_size && default_constant_iv) { if (default_constant_iv_size > 16) { // too large, truncate default_constant_iv_size = 16; } AP4_CopyMemory(&m_DefaultConstantIv[16-default_constant_iv_size], default_constant_iv, default_constant_iv_size); } } /*---------------------------------------------------------------------- | AP4_CencTrackEncryption::Parse +---------------------------------------------------------------------*/ AP4_Result AP4_CencTrackEncryption::Parse(AP4_ByteStream& stream) { AP4_UI08 reserved; AP4_Result result = stream.ReadUI08(reserved); if (AP4_FAILED(result)) return result; if (m_Version_ == 0) { result = stream.ReadUI08(reserved); if (AP4_FAILED(result)) return result; } else { AP4_UI08 blocks; result = stream.ReadUI08(blocks); if (AP4_FAILED(result)) return result; m_DefaultCryptByteBlock = (blocks >> 4) & 0xF; m_DefaultSkipByteBlock = (blocks ) & 0xF; } result = stream.ReadUI08(m_DefaultIsProtected); if (AP4_FAILED(result)) return result; result = stream.ReadUI08(m_DefaultPerSampleIvSize); if (AP4_FAILED(result)) return result; AP4_SetMemory(m_DefaultKid, 0, 16); result = stream.Read(m_DefaultKid, 16); if (AP4_FAILED(result)) return result; if (m_DefaultPerSampleIvSize == 0) { result = stream.ReadUI08(m_DefaultConstantIvSize); if (AP4_FAILED(result)) return result; if (m_DefaultConstantIvSize > 16) { m_DefaultConstantIvSize = 0; return AP4_ERROR_INVALID_FORMAT; } AP4_SetMemory(m_DefaultConstantIv, 0, 16); result = stream.Read(m_DefaultConstantIv, m_DefaultConstantIvSize); if (AP4_FAILED(result)) return result; } return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_CencTrackEncryption::DoInspectFields +---------------------------------------------------------------------*/ AP4_Result AP4_CencTrackEncryption::DoInspectFields(AP4_AtomInspector& inspector) { // the spelling of these fields is inconsistent, but that's how it appears in the spec! inspector.AddField("default_isProtected", m_DefaultIsProtected); inspector.AddField("default_Per_Sample_IV_Size", m_DefaultPerSampleIvSize); inspector.AddField("default_KID", m_DefaultKid, 16); if (m_Version_ >= 1) { inspector.AddField("default_crypt_byte_block", m_DefaultCryptByteBlock); inspector.AddField("default_skip_byte_block", m_DefaultSkipByteBlock); } if (m_DefaultPerSampleIvSize == 0) { inspector.AddField("default_constant_IV_size", m_DefaultConstantIvSize); if (m_DefaultConstantIvSize <= 16) { inspector.AddField("default_constant_IV", m_DefaultConstantIv, m_DefaultConstantIvSize); } } return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_CencTrackEncryption::DoWriteFields +---------------------------------------------------------------------*/ AP4_Result AP4_CencTrackEncryption::DoWriteFields(AP4_ByteStream& stream) { AP4_Result result; // write the fields result = stream.WriteUI08(0); // reserved if (AP4_FAILED(result)) return result; if (m_Version_ == 0) { result = stream.WriteUI08(0); // reserved if (AP4_FAILED(result)) return result; } else { result = stream.WriteUI08(m_DefaultCryptByteBlock<<4 | m_DefaultSkipByteBlock); if (AP4_FAILED(result)) return result; } result = stream.WriteUI08(m_DefaultIsProtected); if (AP4_FAILED(result)) return result; result = stream.WriteUI08(m_DefaultPerSampleIvSize); if (AP4_FAILED(result)) return result; result = stream.Write(m_DefaultKid, 16); if (AP4_FAILED(result)) return result; if (m_DefaultPerSampleIvSize == 0) { result = stream.WriteUI08(m_DefaultConstantIvSize); if (AP4_FAILED(result)) return result; result = stream.Write(m_DefaultConstantIv, m_DefaultConstantIvSize <= 16 ? m_DefaultConstantIvSize : 16); if (AP4_FAILED(result)) return result; } return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_CencSampleEncryption Dynamic Cast Anchor +---------------------------------------------------------------------*/ AP4_DEFINE_DYNAMIC_CAST_ANCHOR(AP4_CencSampleEncryption) /*---------------------------------------------------------------------- | AP4_CencSampleInfoTable::Create +---------------------------------------------------------------------*/ AP4_Result AP4_CencSampleInfoTable::Create(AP4_ProtectedSampleDescription* sample_description, AP4_ContainerAtom* traf, AP4_UI32& cipher_type, bool& reset_iv_at_each_subsample, AP4_ByteStream& aux_info_data, AP4_Position aux_info_data_offset, AP4_CencSampleInfoTable*& sample_info_table) { AP4_SaioAtom* saio; AP4_SaizAtom* saiz; AP4_CencSampleEncryption* sample_encryption_atom; return Create(sample_description, traf, saio, saiz, sample_encryption_atom, cipher_type, reset_iv_at_each_subsample, aux_info_data, aux_info_data_offset, sample_info_table); } /*---------------------------------------------------------------------- | AP4_CencSampleInfoTable::Create +---------------------------------------------------------------------*/ AP4_Result AP4_CencSampleInfoTable::Create(AP4_ProtectedSampleDescription* sample_description, AP4_ContainerAtom* traf, AP4_SaioAtom*& saio, AP4_SaizAtom*& saiz, AP4_CencSampleEncryption*& sample_encryption_atom, AP4_UI32& cipher_type, bool& reset_iv_at_each_subsample, AP4_ByteStream& aux_info_data, AP4_Position aux_info_data_offset, AP4_CencSampleInfoTable*& sample_info_table) { // default return values saio = NULL; saiz = NULL; sample_encryption_atom = NULL; sample_info_table = NULL; cipher_type = AP4_CENC_CIPHER_NONE; reset_iv_at_each_subsample = false; // get the scheme info atom AP4_ContainerAtom* schi = sample_description->GetSchemeInfo()->GetSchiAtom(); if (schi == NULL) return AP4_ERROR_INVALID_FORMAT; // look for a track encryption atom AP4_CencTrackEncryption* track_encryption_atom = AP4_DYNAMIC_CAST(AP4_CencTrackEncryption, schi->GetChild(AP4_ATOM_TYPE_TENC)); if (track_encryption_atom == NULL) { track_encryption_atom = AP4_DYNAMIC_CAST(AP4_CencTrackEncryption, schi->GetChild(AP4_UUID_PIFF_TRACK_ENCRYPTION_ATOM)); } if (track_encryption_atom == NULL) { return AP4_ERROR_INVALID_FORMAT; } // look for a sample encryption atom if (traf) { sample_encryption_atom = AP4_DYNAMIC_CAST(AP4_SencAtom, traf->GetChild(AP4_ATOM_TYPE_SENC)); if (sample_encryption_atom == NULL) { sample_encryption_atom = AP4_DYNAMIC_CAST(AP4_PiffSampleEncryptionAtom, traf->GetChild(AP4_UUID_PIFF_SAMPLE_ENCRYPTION_ATOM)); } } // check the scheme and compute the cipher switch (sample_description->GetSchemeType()) { case AP4_PROTECTION_SCHEME_TYPE_PIFF: switch (track_encryption_atom->GetDefaultIsProtected()) { case 0: cipher_type = AP4_CENC_CIPHER_NONE; break; case 1: cipher_type = AP4_CENC_CIPHER_AES_128_CTR; break; case 2: cipher_type = AP4_CENC_CIPHER_AES_128_CBC; break; default: return AP4_ERROR_NOT_SUPPORTED; } break; case AP4_PROTECTION_SCHEME_TYPE_CENC: case AP4_PROTECTION_SCHEME_TYPE_CENS: cipher_type = AP4_CENC_CIPHER_AES_128_CTR; break; case AP4_PROTECTION_SCHEME_TYPE_CBC1: cipher_type = AP4_CENC_CIPHER_AES_128_CBC; break; case AP4_PROTECTION_SCHEME_TYPE_CBCS: cipher_type = AP4_CENC_CIPHER_AES_128_CBC; reset_iv_at_each_subsample = true; break; default: // not supported return AP4_ERROR_NOT_SUPPORTED; } if (!track_encryption_atom->GetDefaultIsProtected()) { cipher_type = AP4_CENC_CIPHER_NONE; } // parse the crypto params AP4_UI08 per_sample_iv_size = 0; AP4_UI08 constant_iv_size = 0; const AP4_UI08* constant_iv = NULL; AP4_UI08 crypt_byte_block = 0; AP4_UI08 skip_byte_block = 0; if (sample_encryption_atom && (sample_encryption_atom->GetOuter().GetFlags() & AP4_CENC_SAMPLE_ENCRYPTION_FLAG_OVERRIDE_TRACK_ENCRYPTION_DEFAULTS)) { switch (sample_encryption_atom->GetAlgorithmId()) { case 0: cipher_type = AP4_CENC_CIPHER_NONE; break; case 1: cipher_type = AP4_CENC_CIPHER_AES_128_CTR; break; case 2: cipher_type = AP4_CENC_CIPHER_AES_128_CBC; break; } per_sample_iv_size = sample_encryption_atom->GetPerSampleIvSize(); } else { per_sample_iv_size = track_encryption_atom->GetDefaultPerSampleIvSize(); constant_iv_size = track_encryption_atom->GetDefaultConstantIvSize(); crypt_byte_block = track_encryption_atom->GetDefaultCryptByteBlock(); skip_byte_block = track_encryption_atom->GetDefaultSkipByteBlock(); if (constant_iv_size) { constant_iv = track_encryption_atom->GetDefaultConstantIv(); } } // try to create a sample info table from saio/saiz if (sample_info_table == NULL && traf) { for (AP4_List::Item* child = traf->GetChildren().FirstItem(); child; child = child->GetNext()) { if (child->GetData()->GetType() == AP4_ATOM_TYPE_SAIO) { saio = AP4_DYNAMIC_CAST(AP4_SaioAtom, child->GetData()); if (saio->GetAuxInfoType() != 0 && saio->GetAuxInfoType() != AP4_PROTECTION_SCHEME_TYPE_CENC) { saio = NULL; } } else if (child->GetData()->GetType() == AP4_ATOM_TYPE_SAIZ) { saiz = AP4_DYNAMIC_CAST(AP4_SaizAtom, child->GetData()); if (saiz->GetAuxInfoType() != 0 && saiz->GetAuxInfoType() != AP4_PROTECTION_SCHEME_TYPE_CENC) { saiz = NULL; } } } if (saio && saiz) { AP4_Result result = Create(0, crypt_byte_block, skip_byte_block, per_sample_iv_size, constant_iv_size, constant_iv, *traf, *saio, *saiz, aux_info_data, aux_info_data_offset, sample_info_table); // only abort the processing if the error is not due to an invalid // format: some files have an invalid saio/saiz based construction, // but may still have a valid `senc`, which we can try to parse // below if (!AP4_SUCCEEDED(result) && result != AP4_ERROR_INVALID_FORMAT) { return result; } } } // try to create a sample info table from senc if (sample_info_table == NULL && sample_encryption_atom) { AP4_Result result = sample_encryption_atom->CreateSampleInfoTable(0, crypt_byte_block, skip_byte_block, per_sample_iv_size, constant_iv_size, constant_iv, sample_info_table); if (AP4_FAILED(result)) return result; } if (sample_info_table == NULL) { return AP4_ERROR_INVALID_FORMAT; } return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_CencSampleInfoTable::Create +---------------------------------------------------------------------*/ AP4_Result AP4_CencSampleInfoTable::Create(AP4_UI08 flags, AP4_UI08 crypt_byte_block, AP4_UI08 skip_byte_block, AP4_UI08 per_sample_iv_size, AP4_UI08 constant_iv_size, const AP4_UI08* constant_iv, AP4_ContainerAtom& traf, AP4_SaioAtom& saio, AP4_SaizAtom& saiz, AP4_ByteStream& aux_info_data, AP4_Position aux_info_data_offset, AP4_CencSampleInfoTable*& sample_info_table) { AP4_Result result = AP4_SUCCESS; // remember where we are in the data stream so that we can return to that point // before returning AP4_Position position_before = 0; aux_info_data.Tell(position_before); // count the samples unsigned int sample_info_count = 0; for (AP4_List::Item* item = traf.GetChildren().FirstItem(); item; item = item->GetNext()) { AP4_Atom* atom = item->GetData(); if (atom->GetType() == AP4_ATOM_TYPE_TRUN) { AP4_TrunAtom* trun = AP4_DYNAMIC_CAST(AP4_TrunAtom, atom); sample_info_count += trun->GetEntries().ItemCount(); } } // create the table AP4_UI08 iv_size = per_sample_iv_size; if (iv_size == 0) { iv_size = constant_iv_size; if (iv_size == 0 || constant_iv == NULL) { return AP4_ERROR_INVALID_PARAMETERS; } } AP4_CencSampleInfoTable* table = new AP4_CencSampleInfoTable(flags, crypt_byte_block, skip_byte_block, sample_info_count, iv_size); // process each sample's auxiliary info AP4_Ordinal saio_index = 0; AP4_Ordinal saiz_index = 0; AP4_DataBuffer info; for (AP4_List::Item* item = traf.GetChildren().FirstItem(); item; item = item->GetNext()) { AP4_Atom* atom = item->GetData(); if (atom->GetType() == AP4_ATOM_TYPE_TRUN) { AP4_TrunAtom* trun = AP4_DYNAMIC_CAST(AP4_TrunAtom, atom); if (saio_index == 0) { aux_info_data.Seek(aux_info_data_offset+saio.GetEntries()[0]); } else { if (saio.GetEntries().ItemCount() > 1) { if (saio_index >= saio.GetEntries().ItemCount()) { result = AP4_ERROR_INVALID_FORMAT; goto end; } aux_info_data.Seek(aux_info_data_offset+saio.GetEntries()[saio_index]); } } ++saio_index; for (unsigned int i=0; iGetEntries().ItemCount(); i++) { AP4_UI08 info_size = 0; result = saiz.GetSampleInfoSize(saiz_index, info_size); if (AP4_FAILED(result)) goto end; info.SetDataSize(info_size); result = aux_info_data.Read(info.UseData(), info_size); if (AP4_FAILED(result)) goto end; const AP4_UI08* info_data = info.GetData(); if (per_sample_iv_size) { if (per_sample_iv_size > info_size) { result = AP4_ERROR_INVALID_FORMAT; goto end; } table->SetIv(saiz_index, info_data); } else { table->SetIv(saiz_index, constant_iv); } if (info_size < per_sample_iv_size+2) { result = AP4_ERROR_INVALID_FORMAT; goto end; } AP4_UI16 subsample_count = AP4_BytesToUInt16BE(info_data+per_sample_iv_size); if (info_size < per_sample_iv_size+2+subsample_count*6) { // not enough data result = AP4_ERROR_INVALID_FORMAT; goto end; } table->AddSubSampleData(subsample_count, info_data+per_sample_iv_size+2); saiz_index++; } } } result = AP4_SUCCESS; end: if (AP4_SUCCEEDED(result)) { sample_info_table = table; } else { delete table; sample_info_table = NULL; } aux_info_data.Seek(position_before); return result; } /*---------------------------------------------------------------------- | AP4_CencSampleInfoTable::Create +---------------------------------------------------------------------*/ AP4_Result AP4_CencSampleInfoTable::Create(const AP4_UI08* serialized, unsigned int serialized_size, AP4_CencSampleInfoTable*& sample_info_table) { sample_info_table = NULL; // basic size check if (serialized_size < 4+4) { return AP4_ERROR_INVALID_FORMAT; } AP4_UI32 sample_count = AP4_BytesToUInt32BE(serialized); serialized += 4; serialized_size -= 4; AP4_UI08 flags = serialized[0]; serialized += 1; serialized_size -= 1; AP4_UI08 crypt_byte_block = serialized[0]; serialized += 1; serialized_size -= 1; AP4_UI08 skip_byte_block = serialized[0]; serialized += 1; serialized_size -= 1; AP4_UI08 iv_size = serialized[0]; serialized += 1; serialized_size -= 1; if (serialized_size < sample_count*iv_size) { return AP4_ERROR_INVALID_FORMAT; } AP4_CencSampleInfoTable* table = new AP4_CencSampleInfoTable(flags, crypt_byte_block, skip_byte_block, sample_count, (AP4_UI08)iv_size); table->m_IvData.SetData(serialized, sample_count?sample_count*iv_size:iv_size); serialized += sample_count?sample_count*iv_size:iv_size; serialized_size -= sample_count?sample_count*iv_size:iv_size; if (serialized_size < 4) { delete table; return AP4_ERROR_INVALID_FORMAT; } AP4_UI32 item_count = AP4_BytesToUInt32BE(serialized); serialized += 4; serialized_size -= 4; if (serialized_size < item_count*(2+4)) { delete table; return AP4_ERROR_INVALID_FORMAT; } table->m_BytesOfCleartextData.SetItemCount(item_count); table->m_BytesOfEncryptedData.SetItemCount(item_count); for (unsigned int i=0; im_BytesOfCleartextData[i] = AP4_BytesToUInt16BE(serialized); serialized += 2; serialized_size -= 2; } for (unsigned int i=0; im_BytesOfEncryptedData[i] = AP4_BytesToUInt32BE(serialized); serialized += 4; serialized_size -= 4; } if (serialized_size < 4) { delete table; return AP4_ERROR_INVALID_FORMAT; } AP4_UI32 use_subsamples = AP4_BytesToUInt32BE(serialized) != 0; serialized += 4; serialized_size -= 4; if (!use_subsamples) { sample_info_table = table; return AP4_SUCCESS; } if (serialized_size < sample_count*(4+4)) { delete table; return AP4_ERROR_INVALID_FORMAT; } table->m_SubSampleMapStarts.SetItemCount(sample_count); table->m_SubSampleMapLengths.SetItemCount(sample_count); for (unsigned int i=0; im_SubSampleMapStarts[i] = AP4_BytesToUInt32BE(serialized); serialized += 4; serialized_size -= 4; } for (unsigned int i=0; im_SubSampleMapLengths[i] = AP4_BytesToUInt32BE(serialized); serialized += 4; serialized_size -= 4; } sample_info_table = table; return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_CencSampleInfoTable::AP4_CencSampleInfoTable +---------------------------------------------------------------------*/ AP4_CencSampleInfoTable::AP4_CencSampleInfoTable(AP4_UI08 flags, AP4_UI08 crypt_byte_block, AP4_UI08 skip_byte_block, AP4_UI32 sample_count, AP4_UI08 iv_size) : m_SampleCount(sample_count), m_Flags(flags), m_CryptByteBlock(crypt_byte_block), m_SkipByteBlock(skip_byte_block), m_IvSize(iv_size) { if (sample_count == 0) { // All samples encrypted with a constant IV, reserve some space to // store the constant IV sample_count = 1; } m_IvData.SetDataSize(m_IvSize*sample_count); AP4_SetMemory(m_IvData.UseData(), 0, m_IvSize*sample_count); } /*---------------------------------------------------------------------- | AP4_CencSampleInfoTable::Serialize +---------------------------------------------------------------------*/ AP4_Result AP4_CencSampleInfoTable::Serialize(AP4_DataBuffer& buffer) { unsigned int size = 4 + 4 + (m_SampleCount ? m_SampleCount*m_IvSize : m_IvSize) + 4 + m_BytesOfCleartextData.ItemCount()*2 + m_BytesOfEncryptedData.ItemCount()*4 + 4; bool use_subsamples = m_SubSampleMapStarts.ItemCount() != 0; if (use_subsamples) { size += m_SampleCount*(4+4); } // sanity check if (m_IvData.GetDataSize() != m_SampleCount*m_IvSize || m_BytesOfCleartextData.ItemCount() != m_BytesOfEncryptedData.ItemCount() || m_SubSampleMapStarts.ItemCount() != m_SubSampleMapLengths.ItemCount()) { return AP4_ERROR_INTERNAL; } if (use_subsamples && m_SubSampleMapStarts.ItemCount() != m_SampleCount) { return AP4_ERROR_INTERNAL; } buffer.SetDataSize(size); AP4_UI08* data = buffer.UseData(); AP4_BytesFromUInt32BE(data, m_SampleCount); data += 4; *data++ = m_Flags; *data++ = m_CryptByteBlock; *data++ = m_SkipByteBlock; *data++ = m_IvSize; if (m_SampleCount) { AP4_CopyMemory(data, m_IvData.GetData(), m_SampleCount*m_IvSize); data += m_SampleCount*m_IvSize; } else { // All samples encrypted, no per-sample IV, create one entry for the default IV AP4_CopyMemory(data, m_IvData.GetData(), m_IvSize); data += m_IvSize; } AP4_BytesFromUInt32BE(data, m_BytesOfCleartextData.ItemCount()); data += 4; for (unsigned int i=0; i= m_SampleCount) return AP4_ERROR_OUT_OF_RANGE; } AP4_ASSERT(m_IvData.GetDataSize() >= m_IvSize*(sample_index+1)); AP4_UI08* dst = m_IvData.UseData()+(m_IvSize*sample_index); AP4_CopyMemory(dst, iv, m_IvSize); return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_CencSampleInfoTable::GetIv +---------------------------------------------------------------------*/ const AP4_UI08* AP4_CencSampleInfoTable::GetIv(AP4_Ordinal sample_index) { if (m_SampleCount == 0) { return m_IvData.GetData(); } else { if (sample_index >= m_SampleCount) return NULL; return m_IvData.GetData()+(m_IvSize*sample_index); } } /*---------------------------------------------------------------------- | AP4_CencSampleInfoTable::AddSubSampleData +---------------------------------------------------------------------*/ AP4_Result AP4_CencSampleInfoTable::AddSubSampleData(AP4_Cardinal subsample_count, const AP4_UI08* subsample_data) { unsigned int current = m_SubSampleMapStarts.ItemCount(); if (current == 0) { m_SubSampleMapStarts.Append(0); } else { m_SubSampleMapStarts.Append(m_SubSampleMapStarts[current-1]+ m_SubSampleMapLengths[current-1]); } m_SubSampleMapLengths.Append(subsample_count); for (unsigned int i=0; i= m_SampleCount) { return AP4_ERROR_OUT_OF_RANGE; } if (m_SubSampleMapStarts.ItemCount() == 0) { // no subsamples subsample_count = 0; bytes_of_cleartext_data = NULL; bytes_of_encrypted_data = NULL; return AP4_SUCCESS; } subsample_count = m_SubSampleMapLengths[sample_index]; unsigned int target = m_SubSampleMapStarts[sample_index]; bytes_of_cleartext_data = &m_BytesOfCleartextData[target]; bytes_of_encrypted_data = &m_BytesOfEncryptedData[target]; return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_CencSampleInfoTable::GetSubsampleInfo +---------------------------------------------------------------------*/ AP4_Result AP4_CencSampleInfoTable::GetSubsampleInfo(AP4_Cardinal sample_index, AP4_Cardinal subsample_index, AP4_UI16& bytes_of_cleartext_data, AP4_UI32& bytes_of_encrypted_data) { if (sample_index >= m_SampleCount || subsample_index >= m_SubSampleMapLengths[sample_index]) { return AP4_ERROR_OUT_OF_RANGE; } unsigned int target = m_SubSampleMapStarts[sample_index]+subsample_index; if (target >= m_BytesOfCleartextData.ItemCount() || target >= m_BytesOfEncryptedData.ItemCount()) { return AP4_ERROR_OUT_OF_RANGE; } bytes_of_cleartext_data = m_BytesOfCleartextData[target]; bytes_of_encrypted_data = m_BytesOfEncryptedData[target]; return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_CencSampleEncryption::AP4_CencSampleEncryption +---------------------------------------------------------------------*/ AP4_CencSampleEncryption::AP4_CencSampleEncryption(AP4_Atom& outer, AP4_Size size, AP4_ByteStream& stream) : m_Outer(outer), m_ConstantIvSize(0), m_CryptByteBlock(0), m_SkipByteBlock(0), m_SampleInfoCursor(0) { AP4_SetMemory(m_ConstantIv, 0, 16); if (outer.GetFlags() & AP4_CENC_SAMPLE_ENCRYPTION_FLAG_OVERRIDE_TRACK_ENCRYPTION_DEFAULTS) { stream.ReadUI24(m_AlgorithmId); stream.ReadUI08(m_PerSampleIvSize); stream.Read (m_Kid, 16); } else { m_AlgorithmId = 0; m_PerSampleIvSize = 0; AP4_SetMemory(m_Kid, 0, 16); } stream.ReadUI32(m_SampleInfoCount); AP4_Size payload_size = size-m_Outer.GetHeaderSize()-4; m_SampleInfos.SetDataSize(payload_size); stream.Read(m_SampleInfos.UseData(), payload_size); } /*---------------------------------------------------------------------- | AP4_CencSampleEncryption::AP4_CencSampleEncryption +---------------------------------------------------------------------*/ AP4_CencSampleEncryption::AP4_CencSampleEncryption(AP4_Atom& outer, AP4_UI08 per_sample_iv_size, AP4_UI08 constant_iv_size, const AP4_UI08* constant_iv, AP4_UI08 crypt_byte_block, AP4_UI08 skip_byte_block) : m_Outer(outer), m_AlgorithmId(0), m_PerSampleIvSize(per_sample_iv_size), m_ConstantIvSize(constant_iv_size), m_CryptByteBlock(crypt_byte_block), m_SkipByteBlock(skip_byte_block), m_SampleInfoCount(0), m_SampleInfoCursor(0) { AP4_SetMemory(m_ConstantIv, 0, 16); if (constant_iv_size <= 16 && constant_iv) { AP4_CopyMemory(m_ConstantIv, constant_iv, m_ConstantIvSize); } AP4_SetMemory(m_Kid, 0, 16); } /*---------------------------------------------------------------------- | AP4_CencSampleEncryption::AP4_CencSampleEncryption +---------------------------------------------------------------------*/ AP4_CencSampleEncryption::AP4_CencSampleEncryption(AP4_Atom& outer, AP4_UI32 algorithm_id, AP4_UI08 per_sample_iv_size, const AP4_UI08* kid) : m_Outer(outer), m_AlgorithmId(algorithm_id), m_PerSampleIvSize(per_sample_iv_size), m_ConstantIvSize(0), m_CryptByteBlock(0), m_SkipByteBlock(0), m_SampleInfoCount(0), m_SampleInfoCursor(0) { AP4_SetMemory(m_ConstantIv, 0, 16); AP4_CopyMemory(m_Kid, kid, 16); } /*---------------------------------------------------------------------- | AP4_CencSampleEncryption::AddSampleInfo +---------------------------------------------------------------------*/ AP4_Result AP4_CencSampleEncryption::AddSampleInfo(const AP4_UI08* iv, AP4_DataBuffer& subsample_info) { unsigned int added_size = m_PerSampleIvSize+subsample_info.GetDataSize(); if (m_SampleInfoCursor+added_size > m_SampleInfos.GetDataSize()) { // too much data! return AP4_ERROR_OUT_OF_RANGE; } AP4_UI08* info = m_SampleInfos.UseData()+m_SampleInfoCursor; if (m_PerSampleIvSize) { AP4_CopyMemory(info, iv, m_PerSampleIvSize); } if (subsample_info.GetDataSize()) { AP4_CopyMemory(info+m_PerSampleIvSize, subsample_info.GetData(), subsample_info.GetDataSize()); } m_SampleInfoCursor += added_size; ++m_SampleInfoCount; return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_CencSampleEncryption::SetSampleInfosSize +---------------------------------------------------------------------*/ AP4_Result AP4_CencSampleEncryption::SetSampleInfosSize(AP4_Size size) { m_SampleInfos.SetDataSize(size); AP4_SetMemory(m_SampleInfos.UseData(), 0, size); if (m_Outer.GetFlags() & AP4_CENC_SAMPLE_ENCRYPTION_FLAG_OVERRIDE_TRACK_ENCRYPTION_DEFAULTS) { m_Outer.SetSize(m_Outer.GetHeaderSize()+20+4+size); } else { m_Outer.SetSize(m_Outer.GetHeaderSize()+4+size); } if (m_Outer.GetParent()) { AP4_AtomParent* parent = AP4_DYNAMIC_CAST(AP4_AtomParent, m_Outer.GetParent()); if (parent) { parent->OnChildChanged(&m_Outer); } } return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_CencSampleEncryption::CreateSampleInfoTable +---------------------------------------------------------------------*/ AP4_Result AP4_CencSampleEncryption::CreateSampleInfoTable(AP4_UI08 flags, AP4_UI08 default_crypt_byte_block, AP4_UI08 default_skip_byte_block, AP4_UI08 default_per_sample_iv_size, AP4_UI08 default_constant_iv_size, const AP4_UI08* default_constant_iv, AP4_CencSampleInfoTable*& table) { // default return value table = NULL; // check if some values are overridden AP4_Size per_sample_iv_size = default_per_sample_iv_size; if (m_Outer.GetFlags() & AP4_CENC_SAMPLE_ENCRYPTION_FLAG_OVERRIDE_TRACK_ENCRYPTION_DEFAULTS) { per_sample_iv_size = m_PerSampleIvSize; } bool has_subsamples = false; if (m_Outer.GetFlags() & AP4_CENC_SAMPLE_ENCRYPTION_FLAG_USE_SUB_SAMPLE_ENCRYPTION) { has_subsamples = true; } // check some of the parameters if (per_sample_iv_size == 0 || m_SampleInfoCount == 0) { if (default_constant_iv_size == 0 || default_constant_iv == NULL) { return AP4_ERROR_INVALID_PARAMETERS; } } // create the table AP4_Result result = AP4_ERROR_INVALID_FORMAT; table = new AP4_CencSampleInfoTable(flags, default_crypt_byte_block, default_skip_byte_block, m_SampleInfoCount, per_sample_iv_size?per_sample_iv_size:default_constant_iv_size); // fill the table if (m_SampleInfoCount == 0) { // special case, this means all samples are encrypted with the same IV table->SetIv(0, default_constant_iv); } else { const AP4_UI08* data = m_SampleInfos.GetData(); AP4_UI32 data_size = m_SampleInfos.GetDataSize(); for (unsigned int i=0; iSetIv(i, data); data += per_sample_iv_size; data_size -= per_sample_iv_size; } else { table->SetIv(i, default_constant_iv); } if (has_subsamples) { if (data_size < 2) goto end; AP4_UI16 subsample_count = AP4_BytesToUInt16BE(data); data += 2; data_size -= 2; if (data_size < subsample_count*(unsigned int)6) goto end; result = table->AddSubSampleData(subsample_count, data); if (AP4_FAILED(result)) goto end; data += 6*subsample_count; data_size -= 6*subsample_count; } } } // done result = AP4_SUCCESS; end: if (AP4_FAILED(result)) { delete table; table = NULL; } return result; } /*---------------------------------------------------------------------- | AP4_CencSampleEncryption::DoInspectFields +---------------------------------------------------------------------*/ AP4_Result AP4_CencSampleEncryption::DoInspectFields(AP4_AtomInspector& inspector) { if (m_Outer.GetFlags() & AP4_CENC_SAMPLE_ENCRYPTION_FLAG_OVERRIDE_TRACK_ENCRYPTION_DEFAULTS) { inspector.AddField("AlgorithmID", m_AlgorithmId); inspector.AddField("IV_size", m_PerSampleIvSize); inspector.AddField("KID", m_Kid, 16); } inspector.AddField("sample info count", m_SampleInfoCount); if (inspector.GetVerbosity() < 2) { return AP4_SUCCESS; } // since we don't know the IV size necessarily (we don't have the context), we // will try to guess the IV size (we'll try 16 and 8) unsigned int iv_size = m_PerSampleIvSize; if (iv_size == 0) { if (m_Outer.GetFlags() & AP4_CENC_SAMPLE_ENCRYPTION_FLAG_USE_SUB_SAMPLE_ENCRYPTION) { bool data_ok = false; for (unsigned int k=0; k<=2 && !data_ok; k++) { data_ok = true; iv_size = 8*k; const AP4_UI08* info = m_SampleInfos.GetData(); AP4_Size info_size = m_SampleInfos.GetDataSize(); for (unsigned int i=0; i