From 62a544a3fdaf2afaf3a63578bfcb60da95c7c400 Mon Sep 17 00:00:00 2001 From: Jacob Su Date: Mon, 23 Sep 2024 09:32:04 +0800 Subject: [PATCH] hls fmp4: support cbcs subsample encryption. --- trunk/src/app/srs_app_hls.cpp | 84 ++- trunk/src/app/srs_app_hls.hpp | 16 +- trunk/src/kernel/srs_kernel_mp4.cpp | 799 ++++++++++++++++++++++++++++ trunk/src/kernel/srs_kernel_mp4.hpp | 392 ++++++++++++++ 4 files changed, 1273 insertions(+), 18 deletions(-) diff --git a/trunk/src/app/srs_app_hls.cpp b/trunk/src/app/srs_app_hls.cpp index b0ad6f899..43b518a71 100644 --- a/trunk/src/app/srs_app_hls.cpp +++ b/trunk/src/app/srs_app_hls.cpp @@ -81,6 +81,7 @@ SrsInitMp4Segment::SrsInitMp4Segment() { fw_ = new SrsFileWriter(); init_ = new SrsMp4M2tsInitEncoder(); + const_iv_size_ = 0; } SrsInitMp4Segment::~SrsInitMp4Segment() @@ -89,6 +90,20 @@ SrsInitMp4Segment::~SrsInitMp4Segment() srs_freep(fw_); } +srs_error_t SrsInitMp4Segment::config_cipher(unsigned char* kid, unsigned char* const_iv, uint8_t const_iv_size) +{ + srs_error_t err = srs_success; + if (const_iv_size != 8 && const_iv_size != 16) { + return srs_error_new(ERROR_MP4_BOX_STRING, "invalidate const_iv_size"); + } + memcpy(kid_, kid, 16); + memcpy(const_iv_, const_iv, const_iv_size); + const_iv_size_ = const_iv_size; + init_->config_encryption(1, 9, kid_, const_iv, const_iv_size); + + return err; +} + srs_error_t SrsInitMp4Segment::write(SrsFormat* format, int v_tid, int a_tid) { srs_error_t err = srs_success; @@ -153,16 +168,15 @@ srs_error_t SrsInitMp4Segment::init_encoder() } -SrsHlsM4sSegment::SrsHlsM4sSegment() +SrsHlsM4sSegment::SrsHlsM4sSegment(SrsFileWriter* fw) { - fw_ = new SrsFileWriter(); + fw_ = fw; enc_ = new SrsFmp4SegmentEncoder(); } SrsHlsM4sSegment::~SrsHlsM4sSegment() { srs_freep(enc_); - srs_freep(fw_); } srs_error_t SrsHlsM4sSegment::initialize(int64_t time, uint32_t v_tid, uint32_t a_tid, int sequence_number, std::string m4s_path) @@ -188,6 +202,13 @@ srs_error_t SrsHlsM4sSegment::initialize(int64_t time, uint32_t v_tid, uint32_t return err; } +void SrsHlsM4sSegment::config_cipher(unsigned char* key, unsigned char* iv) +{ + // TODO: set key and iv to mp4 box + enc_->config_cipher(key, iv); + memcpy(this->iv, iv,16); +} + srs_error_t SrsHlsM4sSegment::write(SrsSharedPtrMessage* shared_msg, SrsFormat* format) { srs_error_t err = srs_success; @@ -229,8 +250,9 @@ srs_error_t SrsHlsM4sSegment::reap(uint64_t& dts) return srs_error_wrap(err, "Flush encoder failed"); } - srs_freep(fw_); - + // srs_freep(fw_); + fw_->close(); + if ((err = rename()) != srs_success) { return srs_error_wrap(err, "rename"); } @@ -492,6 +514,10 @@ srs_error_t SrsHlsFmp4Muxer::write_init_mp4(SrsFormat* format, bool has_video, b init_mp4->set_path(path); + if (hls_keys_) { + init_mp4->config_cipher(kid_, iv_, 16); + } + if (has_video && has_audio) { if ((err = init_mp4->write(format, video_track_id_, audio_track_id_)) != srs_success) { return srs_error_wrap(err, "write hls init.mp4 with audio and video"); @@ -637,11 +663,7 @@ srs_error_t SrsHlsFmp4Muxer::update_config(SrsRequest* r) } } - if(hls_keys_) { - writer_ = new SrsEncFileWriter(); - } else { - writer_ = new SrsFileWriter(); - } + writer_ = new SrsFileWriter(); return err; } @@ -656,7 +678,7 @@ srs_error_t SrsHlsFmp4Muxer::segment_open(srs_utime_t basetime) } // new segment. - current_ = new SrsHlsM4sSegment(); + current_ = new SrsHlsM4sSegment(writer_); current_->sequence_no = sequence_no_++; if ((err = write_hls_key()) != srs_success) { @@ -831,7 +853,41 @@ srs_error_t SrsHlsFmp4Muxer::do_segment_close() srs_error_t SrsHlsFmp4Muxer::write_hls_key() { - return srs_success; + srs_error_t err = srs_success; + + if (hls_keys_ && current_->sequence_no % hls_fragments_per_key_ == 0) { + if (RAND_bytes(key_, 16) < 0) { + return srs_error_wrap(err, "rand key failed."); + } + if (RAND_bytes(kid_, 16) < 0) { + return srs_error_wrap(err, "rand kid failed."); + } + if (RAND_bytes(iv_, 16) < 0) { + return srs_error_wrap(err, "rand iv failed."); + } + + string key_file = srs_path_build_stream(hls_key_file_, req_->vhost, req_->app, req_->stream); + key_file = srs_string_replace(key_file, "[seq]", srs_int2str(current_->sequence_no)); + string key_url = hls_key_file_path_ + "/" + key_file; + + SrsFileWriter fw; + if ((err = fw.open(key_url)) != srs_success) { + return srs_error_wrap(err, "open file %s", key_url.c_str()); + } + + err = fw.write(key_, 16, NULL); + fw.close(); + + if (err != srs_success) { + return srs_error_wrap(err, "write key"); + } + } + + if (hls_keys_) { + current_->config_cipher(key_, iv_); + } + + return err; } srs_error_t SrsHlsFmp4Muxer::refresh_m3u8() @@ -917,7 +973,7 @@ srs_error_t SrsHlsFmp4Muxer::_refresh_m3u8(std::string m3u8_file) ss << "#EXT-X-DISCONTINUITY" << SRS_CONSTS_LF; } -#if 0 +#if 1 if(hls_keys_ && ((segment->sequence_no % hls_fragments_per_key_) == 0)) { char hexiv[33]; srs_data_to_hex(hexiv, segment->iv, 16); @@ -932,7 +988,7 @@ srs_error_t SrsHlsFmp4Muxer::_refresh_m3u8(std::string m3u8_file) key_path = hls_key_url_ + key_file; } - ss << "#EXT-X-KEY:METHOD=AES-128,URI=" << "\"" << key_path << "\",IV=0x" << hexiv << SRS_CONSTS_LF; + ss << "#EXT-X-KEY:METHOD=SAMPLE-AES,URI=" << "\"" << key_path << "\",IV=0x" << hexiv << SRS_CONSTS_LF; } #endif diff --git a/trunk/src/app/srs_app_hls.hpp b/trunk/src/app/srs_app_hls.hpp index d8282c2c4..4e8144a71 100644 --- a/trunk/src/app/srs_app_hls.hpp +++ b/trunk/src/app/srs_app_hls.hpp @@ -59,7 +59,7 @@ public: SrsHlsSegment(SrsTsContext* c, SrsAudioCodecId ac, SrsVideoCodecId vc, SrsFileWriter* w); virtual ~SrsHlsSegment(); public: - void config_cipher(unsigned char* key,unsigned char* iv); + void config_cipher(unsigned char* key, unsigned char* iv); // replace the placeholder virtual srs_error_t rename(); }; @@ -69,13 +69,18 @@ class SrsInitMp4Segment : public SrsFragment private: SrsFileWriter* fw_; SrsMp4M2tsInitEncoder* init_; + + unsigned char kid_[16]; + unsigned char const_iv_[16]; + uint8_t const_iv_size_; public: SrsInitMp4Segment(); virtual ~SrsInitMp4Segment(); public: - + + virtual srs_error_t config_cipher(unsigned char* kid, unsigned char* const_iv, uint8_t const_iv_size); // Write the init mp4 file, with the v_tid(video track id) and a_tid (audio track id). virtual srs_error_t write(SrsFormat* format, int v_tid, int a_tid); @@ -83,7 +88,6 @@ public: virtual srs_error_t write_audio_only(SrsFormat* format, int a_tid); private: virtual srs_error_t init_encoder(); - }; // TODO: merge this code with SrsFragmentedMp4 in dash @@ -95,11 +99,14 @@ private: public: // sequence number in m3u8. int sequence_no; + // Will be saved in m3u8 file. + unsigned char iv[16]; public: - SrsHlsM4sSegment(); + SrsHlsM4sSegment(SrsFileWriter* fw); virtual ~SrsHlsM4sSegment(); virtual srs_error_t initialize(int64_t time, uint32_t v_tid, uint32_t a_tid, int sequence_number, std::string m4s_path); + virtual void config_cipher(unsigned char* key, unsigned char* iv); virtual srs_error_t write(SrsSharedPtrMessage* shared_msg, SrsFormat* format); virtual srs_error_t reap(uint64_t& dts); }; @@ -305,6 +312,7 @@ private: std::string hls_key_url_; // The key and iv. unsigned char key_[16]; + unsigned char kid_[16]; unsigned char iv_[16]; // The underlayer file writer. SrsFileWriter* writer_; diff --git a/trunk/src/kernel/srs_kernel_mp4.cpp b/trunk/src/kernel/srs_kernel_mp4.cpp index 16c58b55e..d72dd7c97 100644 --- a/trunk/src/kernel/srs_kernel_mp4.cpp +++ b/trunk/src/kernel/srs_kernel_mp4.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -4776,6 +4777,664 @@ stringstream& SrsMp4SegmentIndexBox::dumps_detail(stringstream& ss, SrsMp4DumpCo return ss; } +SrsMp4SampleAuxiliaryInfoSizeBox::SrsMp4SampleAuxiliaryInfoSizeBox() +{ + type = SrsMp4BoxTypeSAIZ; +} + +SrsMp4SampleAuxiliaryInfoSizeBox::~SrsMp4SampleAuxiliaryInfoSizeBox() +{ +} + +int SrsMp4SampleAuxiliaryInfoSizeBox::nb_header() +{ + int size = SrsMp4FullBox::nb_header(); + + if (flags & 0x01) { + size += 8; // add sizeof(aux_info_type) + sizeof(aux_info_type_parameter); + } + + size += 1; // sizeof(default_sample_info_size); + size += 4; // sizeof(sample_count); + + if (default_sample_info_size == 0) { + size += sample_info_sizes.size(); + } + + return size; +} + +srs_error_t SrsMp4SampleAuxiliaryInfoSizeBox::encode_header(SrsBuffer* buf) +{ + srs_error_t err = srs_success; + + if ((err = SrsMp4FullBox::encode_header(buf)) != srs_success) { + return srs_error_wrap(err, "encode header"); + } + + if (flags & 0x01) { + buf->write_4bytes(aux_info_type); + buf->write_4bytes(aux_info_type_parameter); + } + + buf->write_1bytes(default_sample_info_size); + + if (default_sample_info_size == 0) { + buf->write_4bytes(sample_info_sizes.size()); + vector::iterator it; + for (it = sample_info_sizes.begin(); it != sample_info_sizes.end(); ++it) + { + buf->write_1bytes(*it); + } + } else { + buf->write_4bytes(sample_count); + } + + return err; +} + +srs_error_t SrsMp4SampleAuxiliaryInfoSizeBox::decode_header(SrsBuffer* buf) +{ + srs_error_t err = srs_success; + + if ((err = SrsMp4FullBox::decode_header(buf)) != srs_success) { + return srs_error_wrap(err, "decode header"); + } + + if (flags & 0x01) { + aux_info_type = buf->read_4bytes(); + aux_info_type_parameter = buf->read_4bytes(); + } + + default_sample_info_size = buf->read_1bytes(); + sample_count = buf->read_4bytes(); + + if (default_sample_info_size == 0) { + for (int i = 0; i < sample_count; i++) { + sample_info_sizes.push_back(buf->read_1bytes()); + } + } + + return err; +} + +std::stringstream& SrsMp4SampleAuxiliaryInfoSizeBox::dumps_detail(std::stringstream& ss, SrsMp4DumpContext dc) +{ + ss << "default_sample_info_size=" << default_sample_info_size << ", sample_count=" << sample_count; + return ss; +} + +SrsMp4SampleAuxiliaryInfoOffsetBox::SrsMp4SampleAuxiliaryInfoOffsetBox() +{ + type = SrsMp4BoxTypeSAIO; +} + +SrsMp4SampleAuxiliaryInfoOffsetBox::~SrsMp4SampleAuxiliaryInfoOffsetBox() +{ +} + +int SrsMp4SampleAuxiliaryInfoOffsetBox::nb_header() +{ + int size = SrsMp4FullBox::nb_header(); + + if (flags & 0x01) { + size += 8; // sizeof(aux_info_type) + sizeof(aux_info_type_parameter); + } + + size += 4; // sizeof(entry_count); + if (version == 0) { + size += offsets.size() * 4; + } else { + size += offsets.size() * 8; + } + + return size; +} + +srs_error_t SrsMp4SampleAuxiliaryInfoOffsetBox::encode_header(SrsBuffer* buf) +{ + srs_error_t err = srs_success; + + if ((err = SrsMp4FullBox::encode_header(buf)) != srs_success) { + return srs_error_wrap(err, "encode header"); + } + + if (flags & 0x01) { + buf->write_4bytes(aux_info_type); + buf->write_4bytes(aux_info_type_parameter); + } + + buf->write_4bytes(offsets.size()); + vector::iterator it; + for (it = offsets.begin(); it != offsets.end(); ++it) + { + if (version == 0) { + buf->write_4bytes(*it); + } else { + buf->write_8bytes(*it); + } + } + + return err; +} + +srs_error_t SrsMp4SampleAuxiliaryInfoOffsetBox::decode_header(SrsBuffer* buf) +{ + srs_error_t err = srs_success; + + if ((err = SrsMp4FullBox::decode_header(buf)) != srs_success) { + return srs_error_wrap(err, "decode header"); + } + + if (flags & 0x01) { + aux_info_type = buf->read_4bytes(); + aux_info_type_parameter = buf->read_4bytes(); + } + + uint32_t entry_count = buf->read_4bytes(); + for (int i = 0; i < entry_count; i++) + { + if (version == 0) { + offsets.push_back(buf->read_4bytes()); + } else { + offsets.push_back(buf->read_8bytes()); + } + + } + + return err; +} + +std::stringstream& SrsMp4SampleAuxiliaryInfoOffsetBox::dumps_detail(std::stringstream& ss, SrsMp4DumpContext dc) +{ + ss << "entry_count=" << offsets.size(); + return ss; +} + +SrsMp4SubSampleEncryptionInfo::SrsMp4SubSampleEncryptionInfo() +{ + bytes_of_clear_data = 0; + bytes_of_protected_data = 0; +} + +SrsMp4SubSampleEncryptionInfo::~SrsMp4SubSampleEncryptionInfo() +{ +} + +uint64_t SrsMp4SubSampleEncryptionInfo::nb_bytes() +{ + // sizeof(bytes_of_clear_data) + sizeof(bytes_of_protected_data); + return 6; +} + +srs_error_t SrsMp4SubSampleEncryptionInfo::encode(SrsBuffer* buf) +{ + buf->write_2bytes(bytes_of_clear_data); + buf->write_4bytes(bytes_of_protected_data); + + return srs_success; +} + +srs_error_t SrsMp4SubSampleEncryptionInfo::decode(SrsBuffer* buf) +{ + bytes_of_clear_data = buf->read_2bytes(); + bytes_of_protected_data = buf->read_4bytes(); + + return srs_success; +} + +std::stringstream& SrsMp4SubSampleEncryptionInfo::dumps(std::stringstream& ss, SrsMp4DumpContext dc) +{ + ss << "bytes_of_clear_data=" << bytes_of_clear_data << ", bytes_of_protected_data=" << bytes_of_protected_data; + return ss; +} + +SrsMp4SampleEncryptionEntry::SrsMp4SampleEncryptionEntry(SrsMp4FullBox* senc, uint8_t per_sample_iv_size) +{ + senc_ = senc; + srs_assert(per_sample_iv_size == 0 || per_sample_iv_size == 8 || per_sample_iv_size == 16); + per_sample_iv_size_ = per_sample_iv_size; + iv_ = (uint8_t*) malloc(per_sample_iv_size); +} + +SrsMp4SampleEncryptionEntry::~SrsMp4SampleEncryptionEntry() +{ + srs_freep(iv_); +} + +srs_error_t SrsMp4SampleEncryptionEntry::set_iv(uint8_t* iv, uint8_t iv_size) +{ + srs_assert(iv_size == per_sample_iv_size_); + memcpy(iv_, iv, iv_size); + + return srs_success; +} + +uint64_t SrsMp4SampleEncryptionEntry::nb_bytes() +{ + uint64_t size = per_sample_iv_size_; + if (senc_->flags & SrsMp4CencSampleEncryptionUseSubSample) { + size += 2; // size of subsample_count + size += subsample_infos.size() * 6; + } + + return size; +} + +srs_error_t SrsMp4SampleEncryptionEntry::encode(SrsBuffer* buf) +{ + if (per_sample_iv_size_ != 0) { + buf->write_bytes((char*) iv_, per_sample_iv_size_); + } + + if (senc_->flags & SrsMp4CencSampleEncryptionUseSubSample) { + buf->write_2bytes(subsample_infos.size()); + + vector::iterator it; + for (it = subsample_infos.begin(); it != subsample_infos.end(); ++it) { + (*it).encode(buf); + } + } + + return srs_success; +} + +srs_error_t SrsMp4SampleEncryptionEntry::decode(SrsBuffer* buf) +{ + if (per_sample_iv_size_ > 0) { + buf->read_bytes((char*)iv_, per_sample_iv_size_); + } + + if (senc_->flags & SrsMp4CencSampleEncryptionUseSubSample) { + uint16_t subsample_count = buf->read_2bytes(); + for (uint16_t i = 0; i < subsample_count; i++) { + SrsMp4SubSampleEncryptionInfo info; + info.decode(buf); + subsample_infos.push_back(info); + } + } + return srs_success; +} + +std::stringstream& SrsMp4SampleEncryptionEntry::dumps(std::stringstream& ss, SrsMp4DumpContext dc) +{ + // TODO: dump what? + ss << "iv=" << iv_ << endl; + + vector::iterator it; + for (it = subsample_infos.begin(); it != subsample_infos.end(); ++it) { + (*it).dumps(ss, dc); + ss << endl; + } + + return ss; +} + +SrsMp4SampleEncryptionBox::SrsMp4SampleEncryptionBox(uint8_t per_sample_iv_size) +{ + version = 0; + flags = SrsMp4CencSampleEncryptionUseSubSample; + type = SrsMp4BoxTypeSENC; + srs_assert(per_sample_iv_size == 0 || per_sample_iv_size == 8 || per_sample_iv_size == 16); + per_sample_iv_size_ = per_sample_iv_size; +} + +SrsMp4SampleEncryptionBox::~SrsMp4SampleEncryptionBox() +{ + vector::iterator it; + for (it = entries.begin(); it != entries.end(); it++) + { + SrsMp4SampleEncryptionEntry* entry = *it; + srs_freep(entry); + } + entries.clear(); +} + +int SrsMp4SampleEncryptionBox::nb_header() +{ + int size = SrsMp4FullBox::nb_header() + 4; + + vector::iterator it; + for (it = entries.begin(); it < entries.end(); it++) + { + size += (*it)->nb_bytes(); + } + + return size; +} + +srs_error_t SrsMp4SampleEncryptionBox::encode_header(SrsBuffer* buf) +{ + srs_error_t err = srs_success; + + if ((err = SrsMp4FullBox::encode_header(buf)) != srs_success) { + return srs_error_wrap(err, "encode header"); + } + + buf->write_4bytes(entries.size()); + vector::iterator it; + for (it = entries.begin(); it != entries.end(); it++) + { + (*it)->encode(buf); + } + + return err; +} + +srs_error_t SrsMp4SampleEncryptionBox::decode_header(SrsBuffer* buf) +{ + srs_error_t err = srs_success; + + if ((err = SrsMp4FullBox::decode_header(buf)) != srs_success) { + return srs_error_wrap(err, "decode header"); + } + + vector::iterator it; + for (it = entries.begin(); it != entries.end(); it++) + { + SrsMp4SampleEncryptionEntry* entry = *it; + srs_freep(entry); + } + entries.clear(); + + int32_t size = buf->read_4bytes(); + for (int i = 0; i < size; i++) { + SrsMp4SampleEncryptionEntry *entry = new SrsMp4SampleEncryptionEntry(this, per_sample_iv_size_); + entry->decode(buf); + entries.push_back(entry); + } + + return err; +} + +std::stringstream& SrsMp4SampleEncryptionBox::dumps_detail(std::stringstream& ss, SrsMp4DumpContext dc) +{ + ss << "sample_count=" << entries.size() << endl; + return ss; +} + +SrsMp4ProtectionSchemeInfoBox::SrsMp4ProtectionSchemeInfoBox() +{ + type = SrsMp4BoxTypeSINF; +} + +SrsMp4ProtectionSchemeInfoBox::~SrsMp4ProtectionSchemeInfoBox() +{ +} + +SrsMp4OriginalFormatBox* SrsMp4ProtectionSchemeInfoBox::frma() +{ + SrsMp4Box* box = get(SrsMp4BoxTypeFRMA); + return dynamic_cast(box); +} + +void SrsMp4ProtectionSchemeInfoBox::set_frma(SrsMp4OriginalFormatBox* v) +{ + remove(SrsMp4BoxTypeFRMA); + boxes.push_back(v); +} + +SrsMp4SchemeTypeBox* SrsMp4ProtectionSchemeInfoBox::schm() +{ + SrsMp4Box* box = get(SrsMp4BoxTypeSCHM); + return dynamic_cast(box); +} + +void SrsMp4ProtectionSchemeInfoBox::set_schm(SrsMp4SchemeTypeBox* v) +{ + remove(SrsMp4BoxTypeSCHM); + boxes.push_back(v); +} + +SrsMp4SchemeInfoBox* SrsMp4ProtectionSchemeInfoBox::schi() +{ + SrsMp4Box* box = get(SrsMp4BoxTypeSCHI); + return dynamic_cast(box); +} + +void SrsMp4ProtectionSchemeInfoBox::set_schi(SrsMp4SchemeInfoBox* v) +{ + remove(SrsMp4BoxTypeSCHI); + boxes.push_back(v); +} + + +SrsMp4OriginalFormatBox::SrsMp4OriginalFormatBox(uint32_t original_format) +{ + type = SrsMp4BoxTypeFRMA; + data_format_ = original_format; +} + +SrsMp4OriginalFormatBox::~SrsMp4OriginalFormatBox() +{ +} + +int SrsMp4OriginalFormatBox::nb_header() +{ + return SrsMp4Box::nb_header() + 4; +} + +srs_error_t SrsMp4OriginalFormatBox::encode_header(SrsBuffer* buf) +{ + srs_error_t err = srs_success; + + if ((err = SrsMp4Box::encode_header(buf)) != srs_success) { + return srs_error_wrap(err, "encode header"); + } + + buf->write_4bytes(data_format_); + + return err; +} + +srs_error_t SrsMp4OriginalFormatBox::decode_header(SrsBuffer* buf) +{ + srs_error_t err = srs_success; + + if ((err = SrsMp4Box::decode_header(buf)) != srs_success) { + return srs_error_wrap(err, "decode header"); + } + + data_format_ = buf->read_4bytes(); + + return err; +} + +std::stringstream& SrsMp4OriginalFormatBox::dumps_detail(std::stringstream& ss, SrsMp4DumpContext dc) +{ + ss << "original format=" << data_format_ << endl; + return ss; +} + +SrsMp4SchemeTypeBox::SrsMp4SchemeTypeBox() +{ + type = SrsMp4BoxTypeSCHM; +} + +SrsMp4SchemeTypeBox::~SrsMp4SchemeTypeBox() +{ +} + +void SrsMp4SchemeTypeBox::set_scheme_uri(char* uri, uint32_t uri_size) +{ + srs_assert(uri_size < SCHM_SCHEME_URI_MAX_SIZE); + memcpy(scheme_uri, uri, uri_size); + scheme_uri_size = uri_size; + scheme_uri[uri_size] = '\0'; +} + +int SrsMp4SchemeTypeBox::nb_header() +{ + int size = SrsMp4FullBox::nb_header() + 4 + 4; // sizeof(scheme_type) + sizeof(scheme_version) + + if (flags & 0x01) { + size += scheme_uri_size; + } + + return size; +} + +srs_error_t SrsMp4SchemeTypeBox::encode_header(SrsBuffer* buf) +{ + srs_error_t err = srs_success; + + if ((err = SrsMp4FullBox::encode_header(buf)) != srs_success) { + return srs_error_wrap(err, "encode header"); + } + + buf->write_4bytes(scheme_type); + buf->write_4bytes(scheme_version); + + if (flags & 0x01) { + buf->write_bytes(scheme_uri, scheme_uri_size); + buf->write_1bytes(0); + } + + return err; +} + +srs_error_t SrsMp4SchemeTypeBox::decode_header(SrsBuffer* buf) +{ + srs_error_t err = srs_success; + + if ((err = SrsMp4FullBox::decode_header(buf)) != srs_success) { + return srs_error_wrap(err, "encode header"); + } + scheme_type = buf->read_4bytes(); + scheme_version = buf->read_4bytes(); + + if (flags & 0x01) { + memset(scheme_uri, 0, SCHM_SCHEME_URI_MAX_SIZE); + int s = 0; + while (s < SCHM_SCHEME_URI_MAX_SIZE-1) { + char c = buf->read_1bytes(); + scheme_uri[s] = c; + s++; + if (c == '\0') { + break; + } + } + scheme_uri_size = s; + } + + return err; +} + +std::stringstream& SrsMp4SchemeTypeBox::dumps_detail(std::stringstream& ss, SrsMp4DumpContext dc) +{ + ss << "scheme_type=" << scheme_type << ", scheme_version=" << scheme_version << endl; + if (flags & 0x01) { + ss << "scheme_uri=" << scheme_uri << endl; + } + + return ss; +} + +SrsMp4SchemeInfoBox::SrsMp4SchemeInfoBox() +{ + type = SrsMp4BoxTypeSCHI; +} + +SrsMp4SchemeInfoBox::~SrsMp4SchemeInfoBox() +{ +} + +SrsMp4TrackEncryptionBox::SrsMp4TrackEncryptionBox() +{ + type = SrsMp4BoxTypeTENC; +} + +SrsMp4TrackEncryptionBox::~SrsMp4TrackEncryptionBox() +{ +} + +void SrsMp4TrackEncryptionBox::set_default_constant_IV(uint8_t* iv, uint8_t iv_size) +{ + srs_assert(iv_size == 8 || iv_size == 16); + memcpy(default_constant_IV, iv, iv_size); + default_constant_IV_size = iv_size; +} + +int SrsMp4TrackEncryptionBox::nb_header() +{ + int size = SrsMp4FullBox::nb_header(); + size += 1; // sizeof(reserved) + size += 1; // sizeof(reserved_2) or sizeof(default_crypt_byte_block) + sizeof(default_skip_byte_block); + size += 1; // sizeof(default_isProtected); + size += 1; // sizeof(default_Per_Sample_IV_Size; + size += 16; // sizeof(default_KID); + if (default_is_protected == 1 && default_per_sample_IV_size == 0) { + size += 1 + default_constant_IV_size; // sizeof(default_constant_IV_size) + sizeof(default_constant_IV); + } + + return size; +} + +srs_error_t SrsMp4TrackEncryptionBox::encode_header(SrsBuffer* buf) +{ + srs_error_t err = srs_success; + + if ((err = SrsMp4FullBox::encode_header(buf)) != srs_success) { + return srs_error_wrap(err, "encode header"); + } + + buf->write_1bytes(reserved); + if (version == 0) { + buf->write_1bytes(reserved_2); + } else { + buf->write_1bytes( (default_crypt_byte_block << 4) | (default_skip_byte_block & 0x0F)); + } + + buf->write_1bytes(default_is_protected); + buf->write_1bytes(default_per_sample_IV_size); + buf->write_bytes((char*)default_KID, 16); + if (default_is_protected == 1 && default_per_sample_IV_size == 0) { + buf->write_1bytes(default_constant_IV_size); + buf->write_bytes((char*)default_constant_IV, default_constant_IV_size); + } + + return err; +} + +srs_error_t SrsMp4TrackEncryptionBox::decode_header(SrsBuffer* buf) +{ + srs_error_t err = srs_success; + + if ((err = SrsMp4FullBox::decode_header(buf)) != srs_success) { + return srs_error_wrap(err, "encode header"); + } + reserved = buf->read_1bytes(); + if (version == 0) { + reserved_2 = buf->read_1bytes(); + } else { + uint8_t v = buf->read_1bytes(); + default_crypt_byte_block = v >> 4; + default_skip_byte_block = v & 0x0f; + } + + default_is_protected = buf->read_1bytes(); + default_per_sample_IV_size = buf->read_1bytes(); + buf->read_bytes((char*)default_KID, 16); + + if (default_is_protected == 1 && default_per_sample_IV_size == 0) { + default_constant_IV_size = buf->read_1bytes(); + srs_assert(default_constant_IV_size == 8 || default_constant_IV_size == 16); + buf->read_bytes((char*) default_constant_IV, default_constant_IV_size); + } + + return err; +} + +std::stringstream& SrsMp4TrackEncryptionBox::dumps_detail(std::stringstream& ss, SrsMp4DumpContext dc) +{ + if (version != 0) { + ss << "default_crypt_byte_block=" << default_crypt_byte_block << ", default_skip_byte_block=" << default_skip_byte_block << endl; + } + ss << "default_isProtected=" << default_is_protected << ", default_per_sample_IV_size=" << default_per_sample_IV_size << endl; + + return ss; +} + SrsMp4Sample::SrsMp4Sample() { type = SrsFrameTypeForbidden; @@ -6177,6 +6836,10 @@ SrsMp4ObjectType SrsMp4Encoder::get_audio_object_type() SrsMp4M2tsInitEncoder::SrsMp4M2tsInitEncoder() { writer = NULL; + crypt_byte_block_ = 0; + skip_byte_block_ = 0; + iv_size_ = 0; + is_protected_ = false; } SrsMp4M2tsInitEncoder::~SrsMp4M2tsInitEncoder() @@ -6189,6 +6852,18 @@ srs_error_t SrsMp4M2tsInitEncoder::initialize(ISrsWriter* w) return srs_success; } +void SrsMp4M2tsInitEncoder::config_encryption(uint8_t crypt_byte_block, uint8_t skip_byte_block, unsigned char* kid, unsigned char* iv, uint8_t iv_size) +{ + srs_assert(crypt_byte_block + skip_byte_block == 10); + srs_assert(iv_size == 8 || iv_size == 16); + crypt_byte_block_ = crypt_byte_block; + skip_byte_block_ = skip_byte_block; + memcpy(kid_, kid, 16); + memcpy(iv_, iv, iv_size); + iv_size_ = iv_size; + is_protected_ = true; +} + srs_error_t SrsMp4M2tsInitEncoder::write(SrsFormat* format, bool video, int tid) { srs_error_t err = srs_success; @@ -6280,6 +6955,10 @@ srs_error_t SrsMp4M2tsInitEncoder::write(SrsFormat* format, bool video, int tid) avc1->set_avcC(avcC); avcC->avc_config = format->vcodec->avc_extra_data; + + if (is_protected_ && ((err = config_sample_description_encryption(avc1)) != srs_success)) { + return srs_error_wrap(err, "encrypt avc1 box"); + } } else { SrsMp4VisualSampleEntry* hev1 = new SrsMp4VisualSampleEntry(SrsMp4BoxTypeHEV1); stsd->append(hev1); @@ -6292,6 +6971,10 @@ srs_error_t SrsMp4M2tsInitEncoder::write(SrsFormat* format, bool video, int tid) hev1->set_hvcC(hvcC); hvcC->hevc_config = format->vcodec->avc_extra_data; + + if (is_protected_ && ((err = config_sample_description_encryption(hev1)) != srs_success)) { + return srs_error_wrap(err, "encrypt hev1 box"); + } } SrsMp4DecodingTime2SampleBox* stts = new SrsMp4DecodingTime2SampleBox(); @@ -6382,6 +7065,10 @@ srs_error_t SrsMp4M2tsInitEncoder::write(SrsFormat* format, bool video, int tid) SrsMp4EsdsBox* esds = new SrsMp4EsdsBox(); mp4a->set_esds(esds); + + if (is_protected_ && ((err = config_sample_description_encryption(mp4a)) != srs_success)) { + return srs_error_wrap(err, "encrypt mp4a box"); + } SrsMp4ES_Descriptor* es = esds->es; es->ES_ID = 0x02; @@ -6518,6 +7205,10 @@ srs_error_t SrsMp4M2tsInitEncoder::write(SrsFormat* format, int v_tid, int a_tid avc1->set_avcC(avcC); avcC->avc_config = format->vcodec->avc_extra_data; + + if (is_protected_ && ((err = config_sample_description_encryption(avc1)) != srs_success)) { + return srs_error_wrap(err, "encrypt avc1 box"); + } } else { SrsMp4VisualSampleEntry* hev1 = new SrsMp4VisualSampleEntry(SrsMp4BoxTypeHEV1); stsd->append(hev1); @@ -6530,6 +7221,10 @@ srs_error_t SrsMp4M2tsInitEncoder::write(SrsFormat* format, int v_tid, int a_tid hev1->set_hvcC(hvcC); hvcC->hevc_config = format->vcodec->avc_extra_data; + + if (is_protected_ && ((err = config_sample_description_encryption(hev1)) != srs_success)) { + return srs_error_wrap(err, "encrypt hev1 box"); + } } SrsMp4DecodingTime2SampleBox* stts = new SrsMp4DecodingTime2SampleBox(); @@ -6614,6 +7309,9 @@ srs_error_t SrsMp4M2tsInitEncoder::write(SrsFormat* format, int v_tid, int a_tid SrsMp4EsdsBox* esds = new SrsMp4EsdsBox(); mp4a->set_esds(esds); + if (is_protected_ && ((err = config_sample_description_encryption(mp4a)) != srs_success)) { + return srs_error_wrap(err, "encrypt mp4a box."); + } SrsMp4ES_Descriptor* es = esds->es; es->ES_ID = 0x02; @@ -6672,6 +7370,60 @@ srs_error_t SrsMp4M2tsInitEncoder::write(SrsFormat* format, int v_tid, int a_tid return err; } +/** + * box->type = 'encv' or 'enca' + * |encv| + * | |sinf| + * | | |frma| + * | | |schm| + * | | |schi| + * | | | |tenc| + */ +srs_error_t SrsMp4M2tsInitEncoder::config_sample_description_encryption(SrsMp4SampleEntry* box) +{ + srs_error_t err = srs_success; + + bool is_video_sample = false; + SrsMp4BoxType original_type = box->type; + + if (original_type == SrsMp4BoxTypeAVC1 || original_type == SrsMp4BoxTypeHEV1) + { + box->type = SrsMp4BoxTypeENCV; + is_video_sample = true; + } else if (original_type == SrsMp4BoxTypeMP4A) { + box->type = SrsMp4BoxTypeENCA; + } else { + return srs_error_new(ERROR_MP4_BOX_ILLEGAL_TYPE, "unknown sample type 0x%x to encrypt", original_type); + } + + SrsMp4ProtectionSchemeInfoBox* sinf = new SrsMp4ProtectionSchemeInfoBox(); + box->append(sinf); + + SrsMp4OriginalFormatBox* frma = new SrsMp4OriginalFormatBox(original_type); + sinf->set_frma(frma); + + SrsMp4SchemeTypeBox* schm = new SrsMp4SchemeTypeBox(); + schm->scheme_type = SrsMp4CENSchemeCBCS; + schm->scheme_version = 0x00010000; + sinf->set_schm(schm); + + SrsMp4SchemeInfoBox* schi = new SrsMp4SchemeInfoBox(); + SrsMp4TrackEncryptionBox* tenc = new SrsMp4TrackEncryptionBox(); + tenc->version = 1; + tenc->default_crypt_byte_block = is_video_sample ? crypt_byte_block_ : 0 ; + tenc->default_skip_byte_block = is_video_sample ? skip_byte_block_ : 0; + tenc->default_is_protected = 1; + tenc->default_per_sample_IV_size = 0; + tenc->default_constant_IV_size = iv_size_; + memcpy(tenc->default_constant_IV, iv_, iv_size_); + memcpy(tenc->default_KID, kid_, 16); + + schi->append(tenc); + sinf->set_schi(schi); + + return err; +} + SrsMp4M2tsSegmentEncoder::SrsMp4M2tsSegmentEncoder() { writer = NULL; @@ -6879,12 +7631,19 @@ SrsFmp4SegmentEncoder::SrsFmp4SegmentEncoder() mdat_video_bytes_ = 0; audio_samples_ = new SrsMp4SampleManager(); video_samples_ = new SrsMp4SampleManager(); + + memset(iv_,0,16); + key_ = (unsigned char*)new AES_KEY(); + do_sample_encryption_ = false; } SrsFmp4SegmentEncoder::~SrsFmp4SegmentEncoder() { srs_freep(audio_samples_); srs_freep(video_samples_); + + AES_KEY* k = (AES_KEY*)key_; + srs_freep(k); } @@ -6901,6 +7660,21 @@ srs_error_t SrsFmp4SegmentEncoder::initialize(ISrsWriter* w, uint32_t sequence, return err; } +srs_error_t SrsFmp4SegmentEncoder::config_cipher(unsigned char* key, unsigned char* iv) +{ + srs_error_t err = srs_success; + + memcpy(this->iv_, iv, 16); + + AES_KEY* k = (AES_KEY*)this->key_; + if (AES_set_encrypt_key(key, 16 * 8, k)) { + return srs_error_new(ERROR_SYSTEM_FILE_WRITE, "set aes key failed"); + } + do_sample_encryption_ = true; + + return err; +} + srs_error_t SrsFmp4SegmentEncoder::write_sample(SrsMp4HandlerType ht, uint16_t ft, uint32_t dts, uint32_t pts, uint8_t* sample, uint32_t nb_sample) { @@ -6982,6 +7756,22 @@ srs_error_t SrsFmp4SegmentEncoder::flush(uint64_t& dts) if ((err = video_samples_->write(traf, dts)) != srs_success) { return srs_error_wrap(err, "write samples"); } + + // TODO: write senc, and optional saiz & saio + if (do_sample_encryption_) { + SrsMp4SampleEncryptionBox* senc = new SrsMp4SampleEncryptionBox(0); + // video_samples_; + vector::iterator it; + // write video sample data + for (it = video_samples_->samples.begin(); it != video_samples_->samples.end(); ++it) { + SrsMp4Sample* sample = *it; + // TODO: parse hevc|avc, nalu slice header, and calculate + // sample->data; + // sample->nb_data; + } + + traf->append(senc); + } } // write audio traf @@ -7009,6 +7799,13 @@ srs_error_t SrsFmp4SegmentEncoder::flush(uint64_t& dts) if ((err = audio_samples_->write(traf, dts)) != srs_success) { return srs_error_wrap(err, "write samples"); } + + // TODO: write senc, and optional saiz & saio + if (do_sample_encryption_) { + SrsMp4SampleEncryptionBox* senc = new SrsMp4SampleEncryptionBox(0); + // this->iv_; + traf->append(senc); + } } // @remark Remember the data_offset of turn is size(moof)+header(mdat) @@ -7051,6 +7848,7 @@ srs_error_t SrsFmp4SegmentEncoder::flush(uint64_t& dts) SrsMp4Sample* sample = *it; // TODO: FIXME: Ensure all bytes are writen. + // TODO: do cbcs encryption here. sample are nalu_length + nalu data. if ((err = writer_->write(sample->data, sample->nb_data, NULL)) != srs_success) { return srs_error_wrap(err, "write sample"); } @@ -7061,6 +7859,7 @@ srs_error_t SrsFmp4SegmentEncoder::flush(uint64_t& dts) SrsMp4Sample* sample = *it; // TODO: FIXME: Ensure all bytes are writen. + // TODO: do cbcs encryption here if ((err = writer_->write(sample->data, sample->nb_data, NULL)) != srs_success) { return srs_error_wrap(err, "write sample"); } diff --git a/trunk/src/kernel/srs_kernel_mp4.hpp b/trunk/src/kernel/srs_kernel_mp4.hpp index a091d5f93..597c02a6a 100644 --- a/trunk/src/kernel/srs_kernel_mp4.hpp +++ b/trunk/src/kernel/srs_kernel_mp4.hpp @@ -113,6 +113,27 @@ enum SrsMp4BoxType SrsMp4BoxTypeSIDX = 0x73696478, // 'sidx' SrsMp4BoxTypeHEV1 = 0x68657631, // 'hev1' SrsMp4BoxTypeHVCC = 0x68766343, // 'hvcC' + SrsMp4BoxTypeSENC = 0x73656e63, // 'senc' + SrsMp4BoxTypeSAIZ = 0x7361697a, // 'saiz' + SrsMp4BoxTypeSAIO = 0x7361696f, // 'saio' + SrsMp4BoxTypeENCV = 0x656e6376, // 'encv' + SrsMp4BoxTypeENCA = 0x656e6361, // 'enca' + SrsMp4BoxTypeSINF = 0x73696e66, // 'sinf' + SrsMp4BoxTypeSCHI = 0x73636869, // 'schi' + SrsMp4BoxTypeTENC = 0x74656e63, // 'tenc' + SrsMp4BoxTypeFRMA = 0x66726d61, // 'frma' + SrsMp4BoxTypeSCHM = 0x7363686d, // 'schm' +}; + +// Common encryption scheme types +// @see ISO-IEC-23001-7.pdf, 4.2 +enum SrsMp4CENSchemeType +{ + SrsMp4CENSchemeCENC = 0x63656e63, // 'cenc' + SrsMp4CENSchemeCBC1 = 0x63626331, // 'cbc1' + SrsMp4CENSchemeCENS = 0x63656e73, // 'cens' + SrsMp4CENSchemeCBCS = 0x63626373, // 'cbcs' + SrsMp4CENSchemeSVE1 = 0x73766531, // 'sve1' }; // 8.4.3.3 Semantics @@ -1868,6 +1889,348 @@ public: virtual std::stringstream& dumps_detail(std::stringstream& ss, SrsMp4DumpContext dc); }; +// Sample auxiliary information sizes box (saiz) +// @see ISO_IEC_14496-12-base-format-2012.pdf, 8.7.8, page 62 +// @see https://github.com/gpac/mp4box.js/blob/master/src/parsing/saiz.js +// Syntax +// aligned(8) class SampleAuxiliaryInformationSizesBox extends FullBox('saiz', version=0, flags) +// { +// if (flags & 1) { +// unsigned int(32) aux_info_type; +// unsigned int(32) aux_info_type_parameter; +// } +// unsigned int(8) default_sample_info_size; +// unsigned int(32) sample_count; +// if (default_sample_info_size == 0) { +// unsigned int(8) sample_info_size[sample_count]; +// } +// } +class SrsMp4SampleAuxiliaryInfoSizeBox: public SrsMp4FullBox +{ +public: + uint32_t aux_info_type; + uint32_t aux_info_type_parameter; + + uint8_t default_sample_info_size; + uint32_t sample_count; + std::vector sample_info_sizes; + +public: + SrsMp4SampleAuxiliaryInfoSizeBox(); + virtual ~SrsMp4SampleAuxiliaryInfoSizeBox(); + +protected: + virtual int nb_header(); + virtual srs_error_t encode_header(SrsBuffer* buf); + virtual srs_error_t decode_header(SrsBuffer* buf); +public: + virtual std::stringstream& dumps_detail(std::stringstream& ss, SrsMp4DumpContext dc); +}; + +// Sample auxiliary information offsets box (saio) +// @see ISO_IEC_14496-12-base-format-2012.pdf, 8.7.9, page 63 +// @see https://github.com/gpac/mp4box.js/blob/master/src/parsing/saio.js +// Syntax +// aligned(8) class SampleAuxiliaryInformationOffsetsBox extends FullBox('saio', version, flags) +// { +// if (flags & 1) { +// unsigned int(32) aux_info_type; +// unsigned int(32) aux_info_type_parameter; +// } +// unsigned int(32) entry_count; +// if (version == 0) { +// unsigned int(32) offset[entry_count]; +// } else { +// unsigned int(64) offset[entry_count]; +// } +// } +class SrsMp4SampleAuxiliaryInfoOffsetBox: public SrsMp4FullBox +{ +public: + uint32_t aux_info_type; + uint32_t aux_info_type_parameter; + // uint32_t entry_count; + std::vector offsets; + +public: + SrsMp4SampleAuxiliaryInfoOffsetBox(); + virtual ~SrsMp4SampleAuxiliaryInfoOffsetBox(); + +protected: + virtual int nb_header(); + virtual srs_error_t encode_header(SrsBuffer* buf); + virtual srs_error_t decode_header(SrsBuffer* buf); +public: + virtual std::stringstream& dumps_detail(std::stringstream& ss, SrsMp4DumpContext dc); +}; + +enum SrsMp4CencSampleEncryptionFlags +{ + SrsMp4CencSampleEncryptionTrackDefault = 0x01, + SrsMp4CencSampleEncryptionUseSubSample = 0x02, +}; + +struct SrsMp4SubSampleEncryptionInfo : public ISrsCodec +{ + uint16_t bytes_of_clear_data; + uint32_t bytes_of_protected_data; + + SrsMp4SubSampleEncryptionInfo(); + virtual ~SrsMp4SubSampleEncryptionInfo(); + + virtual uint64_t nb_bytes(); + virtual srs_error_t encode(SrsBuffer* buf); + virtual srs_error_t decode(SrsBuffer* buf); + + virtual std::stringstream& dumps(std::stringstream& ss, SrsMp4DumpContext dc); +}; + +class SrsMp4SampleEncryptionEntry : public ISrsCodec +{ +public: + // if flags && 0x02 + std::vector subsample_infos; + +public: + SrsMp4SampleEncryptionEntry(SrsMp4FullBox* senc, uint8_t per_sample_iv_size); + virtual ~SrsMp4SampleEncryptionEntry(); + + virtual srs_error_t set_iv(uint8_t* iv, uint8_t iv_size); + virtual uint64_t nb_bytes(); + virtual srs_error_t encode(SrsBuffer* buf); + virtual srs_error_t decode(SrsBuffer* buf); + + virtual std::stringstream& dumps(std::stringstream& ss, SrsMp4DumpContext dc); + +private: + SrsMp4FullBox* senc_; + uint8_t per_sample_iv_size_; + uint8_t* iv_; +}; + +// Sample encryption box (senc) +// @see ISO-IEC-23001-7.pdf 7.2.1 +// @see https://cdn.standards.iteh.ai/samples/84637/c960c91d60ae4da7a2f9380bd7e08642/ISO-IEC-FDIS-23001-7.pdf +// CENC SAI: sample auxiliary information associated with a sample and containing cryptographic information +// such as initialization vector or subsample information +// @see ISO-IEC-23001-7.pdf 7.2.2 +// Syntax +// aligned(8) class SampleEncryptionBox extend FullBox(`senc`, version=0, flags) +// { +// unsigned int(32) sample_count; +// { +// unsigned int(Per_Sample_IV_Size*8) InitializationVector; +// if (flags & 0x000002) +// { +// unsigned int(16) subsample_count; +// { +// unsigned int(16) BytesOfClearData; +// unsigned int(32) BytesOfProtectedData; +// } [ subsample_count ] +// } +// } [ sample_count ] +// } +class SrsMp4SampleEncryptionBox: public SrsMp4FullBox +{ +public: + std::vector entries; + +private: + uint8_t per_sample_iv_size_; + +public: + // @see ISO-IEC-23001-7.pdf 9.1 + // Per_Sample_IV_Size has supported values: 0, 8, 16. + SrsMp4SampleEncryptionBox(uint8_t per_sample_iv_size); + virtual ~SrsMp4SampleEncryptionBox(); +protected: + virtual int nb_header(); + virtual srs_error_t encode_header(SrsBuffer* buf); + virtual srs_error_t decode_header(SrsBuffer* buf); +public: + virtual std::stringstream& dumps_detail(std::stringstream& ss, SrsMp4DumpContext dc); +}; + +// Original Format Box (frma) +// @see ISO_IEC_14496-12-base-format-2012.pdf, 8.12.2, page 81 +// aligned(8) class OriginalFormatBox(codingname) extends Box ('frma') { +// unsigned int(32) data_format = codingname; +// } +class SrsMp4OriginalFormatBox : public SrsMp4Box +{ +private: + uint32_t data_format_; + +public: + SrsMp4OriginalFormatBox(uint32_t original_format); + virtual ~SrsMp4OriginalFormatBox(); + +protected: + virtual int nb_header(); + virtual srs_error_t encode_header(SrsBuffer* buf); + virtual srs_error_t decode_header(SrsBuffer* buf); +public: + virtual std::stringstream& dumps_detail(std::stringstream& ss, SrsMp4DumpContext dc); +}; + +// Scheme Type Box (schm) +// @see ISO_IEC_14496-12-base-format-2012.pdf, 8.12.5, page 81 +// aligned(8) class SchemeTypeBox extends FullBox('schm', 0, flags) { +// unsigned int(32) scheme_type; // 4CC identifying the scheme +// unsigned int(32) scheme_version; // scheme version +// if (flags & 0x000001) { +// unsigned int(8) scheme_uri[]; // browser uri +// } +// } +// @see @see ISO-IEC-23001-7.pdf 4.1 +// the scheme_version field SHALL be set to 0x00010000 (Major version 1, Minor version 0). +#define SCHM_SCHEME_URI_MAX_SIZE 128 +class SrsMp4SchemeTypeBox : public SrsMp4FullBox +{ +public: + uint32_t scheme_type; + uint32_t scheme_version; + char scheme_uri[SCHM_SCHEME_URI_MAX_SIZE]; + uint32_t scheme_uri_size; + +public: + SrsMp4SchemeTypeBox(); + virtual ~SrsMp4SchemeTypeBox(); + +public: + virtual void set_scheme_uri(char* uri, uint32_t uri_size); +protected: + virtual int nb_header(); + virtual srs_error_t encode_header(SrsBuffer* buf); + virtual srs_error_t decode_header(SrsBuffer* buf); +public: + virtual std::stringstream& dumps_detail(std::stringstream& ss, SrsMp4DumpContext dc); +}; + +// Scheme Information Box (schi) +// @see ISO_IEC_14496-12-base-format-2012.pdf, 8.12.6, page 82 +// aligned(8) class SchemeInformationBox extends Box('schi') { +// Box scheme_specific_data[]; +// } +class SrsMp4SchemeInfoBox : public SrsMp4Box +{ +public: + SrsMp4SchemeInfoBox(); + virtual ~SrsMp4SchemeInfoBox(); +}; + +// Protection Scheme Information Box (sinf) +// @see ISO_IEC_14496-12-base-format-2012.pdf, 8.12.1, page 80 +// aligned(8) class ProtectionSchemeInfoBox(fmt) extends Box('sinf') { +// OriginalFormatBox(fmt) original_format; // frma +// SchemeTypeBox scheme_type_box; // optional +// SchemeInformationBox info; // optional +// } +class SrsMp4ProtectionSchemeInfoBox : public SrsMp4Box +{ +public: + SrsMp4ProtectionSchemeInfoBox(); + virtual ~SrsMp4ProtectionSchemeInfoBox(); + +public: + // Get the Original Format Box (frma) + virtual SrsMp4OriginalFormatBox* frma(); + virtual void set_frma(SrsMp4OriginalFormatBox* v); + // Get the Scheme Type Box (schm) + virtual SrsMp4SchemeTypeBox* schm(); + virtual void set_schm(SrsMp4SchemeTypeBox* v); + // Get the Scheme Information Box (schi) + virtual SrsMp4SchemeInfoBox* schi(); + virtual void set_schi(SrsMp4SchemeInfoBox* v); +}; + +// Track Encryption box (tenc) +// @see ISO-IEC-23001-7.pdf 8.2 +// aligned(8) class TrackEncryptionBox extends FullBox('tenc', version, flags=0) { +// unsigned int(8) reserved = 0; +// if (version == 0) { +// unsigned int(8) reserved = 0; +// } else { // version is 1 or greater +// unsigned int(4) default_crypt_byte_block; +// unsigned int(4) default_skip_byte_block; +// } +// unsigned int(8) default_isProtected; +// unsigned int(8) default_Per_Sample_IV_Size; +// unsigned int(8)[16] default_KID; +// if (default_isProtected == 1 && default_Per_Sample_IV_Size == 0) { +// unsigned int(8) default_constant_IV_size; +// unsigned int(8)[default_constant_IV_size] default_constant_IV; +// } +// } +// @see https://developer.apple.com/documentation/http-live-streaming/about-the-common-media-application-format-with-http-live-streaming-hls +// For fragmented MPEG-4 Segments, an EXT-X-KEY tag with a METHOD=SAMPLE-AES attribute indicates that +// the Segment is encrypted using the `cbcs` scheme in ISO/IEC 23001-7. +// HLS supports unencrypted and encrypted with 'cbcs'. +// @see ISO-IEC-23001-7.pdf 10.4.1 Definition +// 'cbcs' AES-CBC subsample pattern encryption scheme. +// The 'scheme_type' field of the scheme Type Box('schm') SHALL be set to 'cbcs'. +// the version of the Track Encryption Box('tenc') SHALL be 1. +// Encrypted video tracks using NAL Structured Video conforming to ISO/IEC 14496-15 SHALL be +// protected using Subsample encryption specified in 9.5, and SHALL use pattern encryption as specified +// in 9.6. As a result, the fields crypt_byte_block and skip_byte_block SHALL NOT be 0. +// Constant IVs SHALL be used; 'default_Per_Sample_IV_Size' and 'Per_Sample_IV_Size', SHALL be 0. +// Tracks other than video are protected using whole-block full-sample encryption as specified in 9.7 and +// hence skip_byte_block SHALL be 0. +// Pattern Block length, i.e. crypt_byte_block + skip_byte_block SHOULD equal 10. +// For all video NAL units, including in 'avc1', the slice header SHALL be unencrypted. +// The first complete byte of video slice data(following the video slice header) SHALL begin a single +// Subsample protected byte range indicated by the start of BytesOfProtectedData, which extends to +// the end of the video NAL. +// NOTE 1 For AVC VCL NAL units, the encryption pattern starts at an offset rounded to the next byte after +// the slice header, i.e. on the first full byte of slice data. For HEVC, the encryption pattern starts after +// the byte_alignment() field that terminates the slice_segment_header(), i.e. on the first byte of slice data. +// +// @see ISO-IEC-23001-7.pdf 10.4.2 'cbcs' AES-CBC mode pattern encryption scheme application(informative) +// An encrypt:skip pattern of 1:9(i.e. 10% partial encryption) is recommended. Even though the syntax +// allows many different encryption patterns, a pattern of ten Blocks is recommended. This means that the +// skipped Blocks will be (10-N). The number of encrypted cipher blocks N can span multiple contiguous +// 16-byte Blocks(e.g. three encrypted Blocks followed by seven unencrypted Blocks would result in 30% +// partial encryption of the video data). +// For example, to achieve 10 % encryption, the first Block of the pattern is encrypted and the following +// nine Blocks are left unencrypted. The pattern is repeated every 160 bytes of the protected range, until +// the end of the range. If the protected range of the slice body is not a multiple of the pattern length +// (e.g. 160 bytes), then the pattern sequence applies to the included whole 16-byte Blocks and a partial +// 16-byte Block that may remain where the pattern is terminated by the byte length of the range +// BytesOfProtectedData, is left unencrypted. +// +// @see ISO-IEC-23001-7.pdf 9.7 Whole-block full sample encryption +// In whole-block full sample encryption, the entire sample is protected. Every sample is encrypted +// starting at offset 0(there is no unprotected preamble) up to the last 16-byte boundary, leaving any +// trailing 0-15 bytes in the clear. The IV is reset at every sample. +class SrsMp4TrackEncryptionBox : public SrsMp4FullBox +{ +public: + uint8_t reserved; + uint8_t reserved_2; + uint8_t default_crypt_byte_block; + uint8_t default_skip_byte_block; + uint8_t default_is_protected; + uint8_t default_per_sample_IV_size; + uint8_t default_KID[16]; + uint8_t default_constant_IV_size; + uint8_t default_constant_IV[16]; +public: + SrsMp4TrackEncryptionBox(); + virtual ~SrsMp4TrackEncryptionBox(); + +public: + virtual void set_default_constant_IV(uint8_t* iv, uint8_t iv_size); + +protected: + virtual int nb_header(); + virtual srs_error_t encode_header(SrsBuffer* buf); + virtual srs_error_t decode_header(SrsBuffer* buf); +public: + virtual std::stringstream& dumps_detail(std::stringstream& ss, SrsMp4DumpContext dc); +}; + +// TODO: add SchemeTypeBox(schm), set scheme_type=cbcs + // Generally, a MP4 sample contains a frame, for example, a video frame or audio frame. class SrsMp4Sample { @@ -2115,12 +2478,24 @@ class SrsMp4M2tsInitEncoder { private: ISrsWriter* writer; + +private: + uint8_t crypt_byte_block_; + uint8_t skip_byte_block_; + unsigned char kid_[16]; + unsigned char iv_[16]; + uint8_t iv_size_; + bool is_protected_; + public: SrsMp4M2tsInitEncoder(); virtual ~SrsMp4M2tsInitEncoder(); public: // Initialize the encoder with a writer w. virtual srs_error_t initialize(ISrsWriter* w); + // set encryption + // TODO: review kid(map to a key) and iv, which are shared between audio/video tracks. + virtual void config_encryption(uint8_t crypt_byte_block, uint8_t skip_byte_block, unsigned char* kid, unsigned char* iv, uint8_t iv_size); // Write the sequence header. // TODO: merge this method to its sibling. virtual srs_error_t write(SrsFormat* format, bool video, int tid); @@ -2142,6 +2517,18 @@ public: * Write the sequence header with both video and audio track. */ virtual srs_error_t write(SrsFormat* format, int v_tid, int a_tid); + +private: + /** + * box->type = 'encv' or 'enca' + * |encv| + * | |sinf| + * | | |frma| + * | | |schm| + * | | |schi| + * | | | |tenc| + */ + virtual srs_error_t config_sample_description_encryption(SrsMp4SampleEntry* box); }; // A fMP4 encoder, to cache segments then flush to disk, because the fMP4 should write @@ -2200,12 +2587,17 @@ private: uint64_t mdat_video_bytes_; SrsMp4SampleManager* audio_samples_; SrsMp4SampleManager* video_samples_; + unsigned char* key_; + unsigned char iv_[16]; + bool do_sample_encryption_; public: SrsFmp4SegmentEncoder(); virtual ~SrsFmp4SegmentEncoder(); public: // Initialize the encoder with a writer w. virtual srs_error_t initialize(ISrsWriter* w, uint32_t sequence, srs_utime_t basetime, uint32_t v_tid, uint32_t a_tid); + // config cipher + virtual srs_error_t config_cipher(unsigned char* key, unsigned char* iv); // Cache a sample. // @param ht, The sample handler type, audio/soun or video/vide. // @param ft, The frame type. For video, it's SrsVideoAvcFrameType.