Add encryption information in demuxer.
- Add decrypt_config in media sample and stream. - Update subsamples when converting NAL unit to bytestream. Change-Id: I7b8975a453f81b22cf74bee3c9a58b7e458cbaae
This commit is contained in:
parent
6e8aa27e74
commit
28828b8a15
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
#include "packager/base/logging.h"
|
#include "packager/base/logging.h"
|
||||||
#include "packager/base/memory/ref_counted.h"
|
#include "packager/base/memory/ref_counted.h"
|
||||||
|
#include "packager/media/base/decrypt_config.h"
|
||||||
|
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
namespace media {
|
namespace media {
|
||||||
|
@ -117,6 +118,10 @@ class MediaSample : public base::RefCountedThreadSafe<MediaSample> {
|
||||||
return side_data_.size();
|
return side_data_.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DecryptConfig* decrypt_config() const {
|
||||||
|
return decrypt_config_.get();
|
||||||
|
}
|
||||||
|
|
||||||
void set_data(const uint8_t* data, const size_t data_size) {
|
void set_data(const uint8_t* data, const size_t data_size) {
|
||||||
data_.assign(data, data + data_size);
|
data_.assign(data, data + data_size);
|
||||||
}
|
}
|
||||||
|
@ -133,6 +138,10 @@ class MediaSample : public base::RefCountedThreadSafe<MediaSample> {
|
||||||
is_encrypted_ = value;
|
is_encrypted_ = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_decrypt_config(std::unique_ptr<DecryptConfig> decrypt_config) {
|
||||||
|
decrypt_config_ = std::move(decrypt_config);
|
||||||
|
}
|
||||||
|
|
||||||
// If there's no data in this buffer, it represents end of stream.
|
// If there's no data in this buffer, it represents end of stream.
|
||||||
bool end_of_stream() const { return data_.size() == 0; }
|
bool end_of_stream() const { return data_.size() == 0; }
|
||||||
|
|
||||||
|
@ -178,6 +187,9 @@ class MediaSample : public base::RefCountedThreadSafe<MediaSample> {
|
||||||
// For now this is the cue identifier for WebVTT.
|
// For now this is the cue identifier for WebVTT.
|
||||||
std::string config_id_;
|
std::string config_id_;
|
||||||
|
|
||||||
|
// Decrypt configuration.
|
||||||
|
std::unique_ptr<DecryptConfig> decrypt_config_;
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(MediaSample);
|
DISALLOW_COPY_AND_ASSIGN(MediaSample);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ MediaStream::MediaStream(scoped_refptr<StreamInfo> info, Demuxer* demuxer)
|
||||||
MediaStream::~MediaStream() {}
|
MediaStream::~MediaStream() {}
|
||||||
|
|
||||||
Status MediaStream::PullSample(scoped_refptr<MediaSample>* sample) {
|
Status MediaStream::PullSample(scoped_refptr<MediaSample>* sample) {
|
||||||
DCHECK_EQ(state_, kPulling);
|
DCHECK(state_ == kPulling || state_ == kIdle);
|
||||||
|
|
||||||
// Trigger a new parse in demuxer if no more samples.
|
// Trigger a new parse in demuxer if no more samples.
|
||||||
while (samples_.empty()) {
|
while (samples_.empty()) {
|
||||||
|
|
|
@ -42,11 +42,16 @@ class VideoStreamInfo : public StreamInfo {
|
||||||
uint32_t pixel_height() const { return pixel_height_; }
|
uint32_t pixel_height() const { return pixel_height_; }
|
||||||
uint8_t nalu_length_size() const { return nalu_length_size_; }
|
uint8_t nalu_length_size() const { return nalu_length_size_; }
|
||||||
int16_t trick_play_rate() const { return trick_play_rate_; }
|
int16_t trick_play_rate() const { return trick_play_rate_; }
|
||||||
|
const std::vector<uint8_t>& eme_init_data() const { return eme_init_data_; }
|
||||||
|
|
||||||
void set_width(uint32_t width) { width_ = width; }
|
void set_width(uint32_t width) { width_ = width; }
|
||||||
void set_height(uint32_t height) { height_ = height; }
|
void set_height(uint32_t height) { height_ = height; }
|
||||||
void set_pixel_width(uint32_t pixel_width) { pixel_width_ = pixel_width; }
|
void set_pixel_width(uint32_t pixel_width) { pixel_width_ = pixel_width; }
|
||||||
void set_pixel_height(uint32_t pixel_height) { pixel_height_ = pixel_height; }
|
void set_pixel_height(uint32_t pixel_height) { pixel_height_ = pixel_height; }
|
||||||
|
void set_eme_init_data(const uint8_t* eme_init_data,
|
||||||
|
size_t eme_init_data_size) {
|
||||||
|
eme_init_data_.assign(eme_init_data, eme_init_data + eme_init_data_size);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
~VideoStreamInfo() override;
|
~VideoStreamInfo() override;
|
||||||
|
@ -65,6 +70,10 @@ class VideoStreamInfo : public StreamInfo {
|
||||||
// (H.264).
|
// (H.264).
|
||||||
uint8_t nalu_length_size_;
|
uint8_t nalu_length_size_;
|
||||||
|
|
||||||
|
// Container-specific data used by CDM to generate a license request:
|
||||||
|
// https://w3c.github.io/encrypted-media/#initialization-data.
|
||||||
|
std::vector<uint8_t> eme_init_data_;
|
||||||
|
|
||||||
// Not using DISALLOW_COPY_AND_ASSIGN here intentionally to allow the compiler
|
// Not using DISALLOW_COPY_AND_ASSIGN here intentionally to allow the compiler
|
||||||
// generated copy constructor and assignment operator. Since the extra data is
|
// generated copy constructor and assignment operator. Since the extra data is
|
||||||
// typically small, the performance impact is minimal.
|
// typically small, the performance impact is minimal.
|
||||||
|
|
|
@ -46,6 +46,27 @@ void AddAccessUnitDelimiter(BufferWriter* buffer_writer) {
|
||||||
buffer_writer->AppendInt(kAccessUnitDelimiterRbspAnyPrimaryPicType);
|
buffer_writer->AppendInt(kAccessUnitDelimiterRbspAnyPrimaryPicType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CheckSubsampleValid(const std::vector<SubsampleEntry>* subsamples,
|
||||||
|
size_t subsample_id,
|
||||||
|
size_t nalu_size,
|
||||||
|
bool* is_nalu_all_clear) {
|
||||||
|
if (subsample_id >= subsamples->size()) {
|
||||||
|
LOG(ERROR) << "Subsample index exceeds subsamples' size.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const SubsampleEntry& subsample = subsamples->at(subsample_id);
|
||||||
|
if (nalu_size == subsample.clear_bytes + subsample.cipher_bytes) {
|
||||||
|
*is_nalu_all_clear = false;
|
||||||
|
} else if (nalu_size < subsample.clear_bytes) {
|
||||||
|
*is_nalu_all_clear = true;
|
||||||
|
} else {
|
||||||
|
LOG(ERROR) << "Unexpected subsample entry " << subsample.clear_bytes << ":"
|
||||||
|
<< subsample.cipher_bytes << " nalu size: " << nalu_size;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void EscapeNalByteSequence(const uint8_t* input,
|
void EscapeNalByteSequence(const uint8_t* input,
|
||||||
|
@ -144,17 +165,39 @@ bool NalUnitToByteStreamConverter::ConvertUnitToByteStream(
|
||||||
size_t sample_size,
|
size_t sample_size,
|
||||||
bool is_key_frame,
|
bool is_key_frame,
|
||||||
std::vector<uint8_t>* output) {
|
std::vector<uint8_t>* output) {
|
||||||
|
return ConvertUnitToByteStreamWithSubsamples(
|
||||||
|
sample, sample_size, is_key_frame, output,
|
||||||
|
nullptr); // Skip subsample update.
|
||||||
|
}
|
||||||
|
|
||||||
|
// This ignores all AUD, SPS, and PPS in the sample. Instead uses the data
|
||||||
|
// parsed in Initialize().
|
||||||
|
bool NalUnitToByteStreamConverter::ConvertUnitToByteStreamWithSubsamples(
|
||||||
|
const uint8_t* sample,
|
||||||
|
size_t sample_size,
|
||||||
|
bool is_key_frame,
|
||||||
|
std::vector<uint8_t>* output,
|
||||||
|
std::vector<SubsampleEntry>* subsamples) {
|
||||||
if (!sample || sample_size == 0) {
|
if (!sample || sample_size == 0) {
|
||||||
LOG(WARNING) << "Sample is empty.";
|
LOG(WARNING) << "Sample is empty.";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (subsamples && escape_data_) {
|
||||||
|
LOG(ERROR) << "escape_data_ should not be set when updating subsamples.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
BufferWriter buffer_writer(sample_size);
|
BufferWriter buffer_writer(sample_size);
|
||||||
buffer_writer.AppendArray(kNaluStartCode, arraysize(kNaluStartCode));
|
buffer_writer.AppendArray(kNaluStartCode, arraysize(kNaluStartCode));
|
||||||
AddAccessUnitDelimiter(&buffer_writer);
|
AddAccessUnitDelimiter(&buffer_writer);
|
||||||
if (is_key_frame)
|
if (is_key_frame)
|
||||||
buffer_writer.AppendVector(decoder_configuration_in_byte_stream_);
|
buffer_writer.AppendVector(decoder_configuration_in_byte_stream_);
|
||||||
|
|
||||||
|
int adjustment = buffer_writer.Size();
|
||||||
|
size_t subsample_id = 0;
|
||||||
|
|
||||||
NaluReader nalu_reader(Nalu::kH264, nalu_length_size_, sample, sample_size);
|
NaluReader nalu_reader(Nalu::kH264, nalu_length_size_, sample, sample_size);
|
||||||
Nalu nalu;
|
Nalu nalu;
|
||||||
NaluReader::Result result = nalu_reader.Advance(&nalu);
|
NaluReader::Result result = nalu_reader.Advance(&nalu);
|
||||||
|
@ -166,12 +209,50 @@ bool NalUnitToByteStreamConverter::ConvertUnitToByteStream(
|
||||||
case Nalu::H264_SPS:
|
case Nalu::H264_SPS:
|
||||||
FALLTHROUGH_INTENDED;
|
FALLTHROUGH_INTENDED;
|
||||||
case Nalu::H264_PPS:
|
case Nalu::H264_PPS:
|
||||||
|
if (subsamples) {
|
||||||
|
const size_t old_nalu_size =
|
||||||
|
nalu_length_size_ + nalu.header_size() + nalu.payload_size();
|
||||||
|
bool is_nalu_all_clear;
|
||||||
|
if (!CheckSubsampleValid(subsamples, subsample_id, old_nalu_size,
|
||||||
|
&is_nalu_all_clear)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (is_nalu_all_clear) {
|
||||||
|
// If AUD/SPS/PPS is all clear, reduce the clear bytes.
|
||||||
|
subsamples->at(subsample_id).clear_bytes -= old_nalu_size;
|
||||||
|
} else {
|
||||||
|
// If AUD/SPS/PPS has cipher, drop the corresponding subsample.
|
||||||
|
subsamples->erase(subsamples->begin() + subsample_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
buffer_writer.AppendArray(kNaluStartCode, arraysize(kNaluStartCode));
|
buffer_writer.AppendArray(kNaluStartCode, arraysize(kNaluStartCode));
|
||||||
AppendNalu(nalu, nalu_length_size_, escape_data_, &buffer_writer);
|
AppendNalu(nalu, nalu_length_size_, escape_data_, &buffer_writer);
|
||||||
|
|
||||||
|
if (subsamples) {
|
||||||
|
const size_t old_nalu_size =
|
||||||
|
nalu_length_size_ + nalu.header_size() + nalu.payload_size();
|
||||||
|
bool is_nalu_all_clear;
|
||||||
|
if (!CheckSubsampleValid(subsamples, subsample_id, old_nalu_size,
|
||||||
|
&is_nalu_all_clear)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (is_nalu_all_clear) {
|
||||||
|
// Add this nalu to the adjustment and remove it from clear_bytes.
|
||||||
|
subsamples->at(subsample_id).clear_bytes -= old_nalu_size;
|
||||||
|
adjustment += old_nalu_size;
|
||||||
|
} else {
|
||||||
|
// Apply the adjustment on the current subsample, reset the
|
||||||
|
// adjustment and move to the next subsample.
|
||||||
|
subsamples->at(subsample_id).clear_bytes += adjustment;
|
||||||
|
subsample_id++;
|
||||||
|
adjustment = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = nalu_reader.Advance(&nalu);
|
result = nalu_reader.Advance(&nalu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
#include "packager/base/macros.h"
|
#include "packager/base/macros.h"
|
||||||
#include "packager/base/memory/ref_counted.h"
|
#include "packager/base/memory/ref_counted.h"
|
||||||
|
#include "packager/media/base/decrypt_config.h"
|
||||||
|
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
namespace media {
|
namespace media {
|
||||||
|
@ -53,13 +54,30 @@ class NalUnitToByteStreamConverter {
|
||||||
/// SAMPLE-AES encryption.
|
/// SAMPLE-AES encryption.
|
||||||
/// @param sample is the sample to be converted.
|
/// @param sample is the sample to be converted.
|
||||||
/// @param sample_size is the size of @a sample.
|
/// @param sample_size is the size of @a sample.
|
||||||
/// @param output is set to the the converted sample, on success.
|
/// @param[out] output is set to the the converted sample, on success.
|
||||||
/// @return true on success, false otherwise.
|
/// @return true on success, false otherwise.
|
||||||
virtual bool ConvertUnitToByteStream(const uint8_t* sample,
|
virtual bool ConvertUnitToByteStream(const uint8_t* sample,
|
||||||
size_t sample_size,
|
size_t sample_size,
|
||||||
bool is_key_frame,
|
bool is_key_frame,
|
||||||
std::vector<uint8_t>* output);
|
std::vector<uint8_t>* output);
|
||||||
|
|
||||||
|
/// Converts unit stream to byte stream using the data passed to Initialize()
|
||||||
|
/// and update the corresponding subsamples of the media sample.
|
||||||
|
/// The method will function correctly even if @a sample is encrypted using
|
||||||
|
/// SAMPLE-AES encryption.
|
||||||
|
/// @param sample is the sample to be converted.
|
||||||
|
/// @param sample_size is the size of @a sample.
|
||||||
|
/// @param[out] output is set to the the converted sample, on success.
|
||||||
|
/// @param[in,out] subsamples has the input subsamples and output updated
|
||||||
|
/// subsamples, on sucess.
|
||||||
|
/// @return true on success, false otherwise.
|
||||||
|
virtual bool ConvertUnitToByteStreamWithSubsamples(
|
||||||
|
const uint8_t* sample,
|
||||||
|
size_t sample_size,
|
||||||
|
bool is_key_frame,
|
||||||
|
std::vector<uint8_t>* output,
|
||||||
|
std::vector<SubsampleEntry>* subsamples);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class NalUnitToByteStreamConverterTest;
|
friend class NalUnitToByteStreamConverterTest;
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
#include "packager/media/base/media_sample.h"
|
#include "packager/media/base/media_sample.h"
|
||||||
#include "packager/media/codecs/nal_unit_to_byte_stream_converter.h"
|
#include "packager/media/codecs/nal_unit_to_byte_stream_converter.h"
|
||||||
|
#include "packager/media/formats/mp4/box_definitions_comparison.h"
|
||||||
|
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
namespace media {
|
namespace media {
|
||||||
|
@ -236,7 +237,7 @@ TEST(NalUnitToByteStreamConverterTest, ConvertUnitToByteStreamWithEscape) {
|
||||||
// NALU ending with 0 must have 3 appended.
|
// NALU ending with 0 must have 3 appended.
|
||||||
TEST(NalUnitToByteStreamConverterTest, NaluEndingWithZero) {
|
TEST(NalUnitToByteStreamConverterTest, NaluEndingWithZero) {
|
||||||
const uint8_t kNaluEndingWithZero[] = {
|
const uint8_t kNaluEndingWithZero[] = {
|
||||||
0x00, 0x00, 0x00, 0x03, // Size 10 NALU.
|
0x00, 0x00, 0x00, 0x03, // Size 3 NALU.
|
||||||
0x06, // NAL unit type.
|
0x06, // NAL unit type.
|
||||||
0xAA, 0x00, // Ends with 0.
|
0xAA, 0x00, // Ends with 0.
|
||||||
};
|
};
|
||||||
|
@ -270,7 +271,7 @@ TEST(NalUnitToByteStreamConverterTest, NaluEndingWithZero) {
|
||||||
// configuration is not used.
|
// configuration is not used.
|
||||||
TEST(NalUnitToByteStreamConverterTest, NonKeyFrameSample) {
|
TEST(NalUnitToByteStreamConverterTest, NonKeyFrameSample) {
|
||||||
const uint8_t kNonKeyFrameStream[] = {
|
const uint8_t kNonKeyFrameStream[] = {
|
||||||
0x00, 0x00, 0x00, 0x03, // Size 10 NALU.
|
0x00, 0x00, 0x00, 0x03, // Size 3 NALU.
|
||||||
0x06, // NAL unit type.
|
0x06, // NAL unit type.
|
||||||
0x33, 0x88,
|
0x33, 0x88,
|
||||||
};
|
};
|
||||||
|
@ -304,7 +305,7 @@ TEST(NalUnitToByteStreamConverterTest, NonKeyFrameSample) {
|
||||||
// The zeros aren't contiguous but the escape byte was inserted.
|
// The zeros aren't contiguous but the escape byte was inserted.
|
||||||
TEST(NalUnitToByteStreamConverterTest, DispersedZeros) {
|
TEST(NalUnitToByteStreamConverterTest, DispersedZeros) {
|
||||||
const uint8_t kDispersedZeros[] = {
|
const uint8_t kDispersedZeros[] = {
|
||||||
0x00, 0x00, 0x00, 0x08, // Size 10 NALU.
|
0x00, 0x00, 0x00, 0x08, // Size 8 NALU.
|
||||||
0x06, // NAL unit type.
|
0x06, // NAL unit type.
|
||||||
// After 2 zeros (including the first byte of the NALU followed by 0, 1,
|
// After 2 zeros (including the first byte of the NALU followed by 0, 1,
|
||||||
// 2, or 3 caused it to insert the escape byte.
|
// 2, or 3 caused it to insert the escape byte.
|
||||||
|
@ -370,5 +371,241 @@ TEST(NalUnitToByteStreamConverterTest, DoNotEscape) {
|
||||||
output);
|
output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// All NAL units have both clear and ciper text
|
||||||
|
TEST(NalUnitToByteStreamConverterTest, NoClearNAL) {
|
||||||
|
// Only the type of the NAL units are checked.
|
||||||
|
// This does not contain AUD, SPS, nor PPS.
|
||||||
|
const uint8_t kUnitStreamLikeMediaSample[] = {
|
||||||
|
0x00, 0x00, 0x00, 0x0A, // Size 10 NALU.
|
||||||
|
0x02, // NAL unit type.
|
||||||
|
0xFD, 0x78, 0xA4, 0xC3, 0x82, 0x62, 0x11, 0x29, 0x77, // Slice data
|
||||||
|
0x00, 0x00, 0x00, 0x08, // Size 8 NALU.
|
||||||
|
0x02, // NAL unit type.
|
||||||
|
0xFD, 0x78, 0xA4, 0x82, 0x62, 0x29, 0x77, // Slice data
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<SubsampleEntry> subsamples{SubsampleEntry(5, 9),
|
||||||
|
SubsampleEntry(5, 7)};
|
||||||
|
|
||||||
|
NalUnitToByteStreamConverter converter;
|
||||||
|
EXPECT_TRUE(converter.Initialize(
|
||||||
|
kTestAVCDecoderConfigurationRecord,
|
||||||
|
arraysize(kTestAVCDecoderConfigurationRecord), !kEscapeData));
|
||||||
|
|
||||||
|
std::vector<uint8_t> output;
|
||||||
|
EXPECT_TRUE(converter.ConvertUnitToByteStreamWithSubsamples(
|
||||||
|
kUnitStreamLikeMediaSample, arraysize(kUnitStreamLikeMediaSample),
|
||||||
|
kIsKeyFrame, &output, &subsamples));
|
||||||
|
|
||||||
|
const uint8_t kExpectedOutput[] = {
|
||||||
|
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||||
|
0x09, // AUD type.
|
||||||
|
0xF0, // primary pic type is anything.
|
||||||
|
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||||
|
// Some valid SPS data.
|
||||||
|
0x67, 0x64, 0x00, 0x1E, 0xAC, 0xD9, 0x40, 0xB4,
|
||||||
|
0x2F, 0xF9, 0x7F, 0xF0, 0x00, 0x80, 0x00, 0x91,
|
||||||
|
0x00, 0x00, 0x03, 0x03, 0xE9, 0x00, 0x00, 0xEA,
|
||||||
|
0x60, 0x0F, 0x16, 0x2D, 0x96,
|
||||||
|
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||||
|
0x68, 0xFE, 0xFD, 0xFC, 0xFB, 0x11, 0x12, 0x13, 0x14, 0x15, // PPS.
|
||||||
|
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||||
|
// The input NALU 1.
|
||||||
|
0x02, // NALU type.
|
||||||
|
0xFD, 0x78, 0xA4, 0xC3, 0x82, 0x62, 0x11, 0x29, 0x77,
|
||||||
|
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||||
|
// The input NALU 2.
|
||||||
|
0x02, // NALU type.
|
||||||
|
0xFD, 0x78, 0xA4, 0x82, 0x62, 0x29, 0x77,
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::vector<SubsampleEntry> kExpectedOutputSubsamples{
|
||||||
|
SubsampleEntry(58, 9), SubsampleEntry(5, 7)};
|
||||||
|
|
||||||
|
EXPECT_EQ(std::vector<uint8_t>(kExpectedOutput,
|
||||||
|
kExpectedOutput + arraysize(kExpectedOutput)),
|
||||||
|
output);
|
||||||
|
EXPECT_EQ(kExpectedOutputSubsamples, subsamples);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some NAL units have all clear text
|
||||||
|
TEST(NalUnitToByteStreamConverterTest, WithSomeClearNAL) {
|
||||||
|
// Only the type of the NAL units are checked.
|
||||||
|
// This does not contain AUD, SPS, nor PPS.
|
||||||
|
const uint8_t kUnitStreamLikeMediaSample[] = {
|
||||||
|
0x00, 0x00, 0x00, 0x0A, // Size 10 NALU.
|
||||||
|
0x06, // NAL unit type.
|
||||||
|
0xFD, 0x78, 0xA4, 0xC3, 0x82, 0x62, 0x11, 0x29, 0x77,
|
||||||
|
0x00, 0x00, 0x00, 0x08, // Size 8 NALU.
|
||||||
|
0x02, // NAL unit type.
|
||||||
|
0xFD, 0x78, 0xA4, 0x82, 0x62, 0x29, 0x77, // Slice data
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<SubsampleEntry> subsamples{SubsampleEntry(19, 7)};
|
||||||
|
|
||||||
|
NalUnitToByteStreamConverter converter;
|
||||||
|
EXPECT_TRUE(converter.Initialize(
|
||||||
|
kTestAVCDecoderConfigurationRecord,
|
||||||
|
arraysize(kTestAVCDecoderConfigurationRecord), !kEscapeData));
|
||||||
|
|
||||||
|
std::vector<uint8_t> output;
|
||||||
|
EXPECT_TRUE(converter.ConvertUnitToByteStreamWithSubsamples(
|
||||||
|
kUnitStreamLikeMediaSample, arraysize(kUnitStreamLikeMediaSample),
|
||||||
|
kIsKeyFrame, &output, &subsamples));
|
||||||
|
|
||||||
|
const uint8_t kExpectedOutput[] = {
|
||||||
|
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||||
|
0x09, // AUD type.
|
||||||
|
0xF0, // primary pic type is anything.
|
||||||
|
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||||
|
// Some valid SPS data.
|
||||||
|
0x67, 0x64, 0x00, 0x1E, 0xAC, 0xD9, 0x40, 0xB4,
|
||||||
|
0x2F, 0xF9, 0x7F, 0xF0, 0x00, 0x80, 0x00, 0x91,
|
||||||
|
0x00, 0x00, 0x03, 0x03, 0xE9, 0x00, 0x00, 0xEA,
|
||||||
|
0x60, 0x0F, 0x16, 0x2D, 0x96,
|
||||||
|
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||||
|
0x68, 0xFE, 0xFD, 0xFC, 0xFB, 0x11, 0x12, 0x13, 0x14, 0x15, // PPS.
|
||||||
|
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||||
|
// The input NALU 1.
|
||||||
|
0x06, // NALU type.
|
||||||
|
0xFD, 0x78, 0xA4, 0xC3, 0x82, 0x62, 0x11, 0x29, 0x77,
|
||||||
|
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||||
|
// The input NALU 2.
|
||||||
|
0x02, // NALU type.
|
||||||
|
0xFD, 0x78, 0xA4, 0x82, 0x62, 0x29, 0x77,
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::vector<SubsampleEntry> kExpectedOutputSubsamples{
|
||||||
|
SubsampleEntry(72, 7)};
|
||||||
|
|
||||||
|
EXPECT_EQ(std::vector<uint8_t>(kExpectedOutput,
|
||||||
|
kExpectedOutput + arraysize(kExpectedOutput)),
|
||||||
|
output);
|
||||||
|
EXPECT_EQ(kExpectedOutputSubsamples, subsamples);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A encrypted PPS NALU follows a clear NALU, the PPS will be removed. So the
|
||||||
|
// corresponding subsample needs to be removed.
|
||||||
|
TEST(NalUnitToByteStreamConverterTest, EncryptedPps) {
|
||||||
|
// Only the type of the NAL units are checked.
|
||||||
|
// This does not contain AUD, SPS, nor PPS.
|
||||||
|
const uint8_t kUnitStreamLikeMediaSample[] = {
|
||||||
|
0x00, 0x00, 0x00, 0x0A, // Size 10 NALU.
|
||||||
|
0x06, // NAL unit type.
|
||||||
|
0xFD, 0x78, 0xA4, 0xC3, 0x82, 0x62, 0x11, 0x29, 0x77, // clear
|
||||||
|
0x00, 0x00, 0x00, 0x0B, // Size 11 NALU.
|
||||||
|
0x68, // PPS, will be removed after convertion
|
||||||
|
// The content of PPS is not checked.
|
||||||
|
0x68, 0xFE, 0xFD, 0xFC, 0xFB, 0x12, 0x12, 0x13, 0x14, 0x15, // cipher
|
||||||
|
0x00, 0x00, 0x00, 0x08, // Size 8 NALU.
|
||||||
|
0x02, // NAL unit type.
|
||||||
|
0xFD, 0x78, 0xA4, 0x82, 0x62, 0x29, 0x77, // Slice data, cipher
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<SubsampleEntry> subsamples{SubsampleEntry(19, 10),
|
||||||
|
SubsampleEntry(5, 7)};
|
||||||
|
|
||||||
|
NalUnitToByteStreamConverter converter;
|
||||||
|
EXPECT_TRUE(converter.Initialize(
|
||||||
|
kTestAVCDecoderConfigurationRecord,
|
||||||
|
arraysize(kTestAVCDecoderConfigurationRecord), !kEscapeData));
|
||||||
|
|
||||||
|
std::vector<uint8_t> output;
|
||||||
|
EXPECT_TRUE(converter.ConvertUnitToByteStreamWithSubsamples(
|
||||||
|
kUnitStreamLikeMediaSample, arraysize(kUnitStreamLikeMediaSample),
|
||||||
|
kIsKeyFrame, &output, &subsamples));
|
||||||
|
|
||||||
|
const uint8_t kExpectedOutput[] = {
|
||||||
|
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||||
|
0x09, // AUD type.
|
||||||
|
0xF0, // primary pic type is anything.
|
||||||
|
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||||
|
// Some valid SPS data.
|
||||||
|
0x67, 0x64, 0x00, 0x1E, 0xAC, 0xD9, 0x40, 0xB4,
|
||||||
|
0x2F, 0xF9, 0x7F, 0xF0, 0x00, 0x80, 0x00, 0x91,
|
||||||
|
0x00, 0x00, 0x03, 0x03, 0xE9, 0x00, 0x00, 0xEA,
|
||||||
|
0x60, 0x0F, 0x16, 0x2D, 0x96,
|
||||||
|
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||||
|
0x68, 0xFE, 0xFD, 0xFC, 0xFB, 0x11, 0x12, 0x13, 0x14, 0x15, // PPS.
|
||||||
|
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||||
|
// The input NALU 1.
|
||||||
|
0x06, // NALU type.
|
||||||
|
0xFD, 0x78, 0xA4, 0xC3, 0x82, 0x62, 0x11, 0x29, 0x77,
|
||||||
|
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||||
|
// The input NALU 2.
|
||||||
|
0x02, // NALU type.
|
||||||
|
0xFD, 0x78, 0xA4, 0x82, 0x62, 0x29, 0x77,
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::vector<SubsampleEntry> kExpectedOutputSubsamples{
|
||||||
|
SubsampleEntry(72, 7)};
|
||||||
|
|
||||||
|
EXPECT_EQ(std::vector<uint8_t>(kExpectedOutput,
|
||||||
|
kExpectedOutput + arraysize(kExpectedOutput)),
|
||||||
|
output);
|
||||||
|
EXPECT_EQ(kExpectedOutputSubsamples, subsamples);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A clear PPS NALU follows a clear NALU, the PPS will be removed. So the
|
||||||
|
// corresponding subsample's clear bytes may be reduced.
|
||||||
|
TEST(NalUnitToByteStreamConverterTest, ClearPps) {
|
||||||
|
// Only the type of the NAL units are checked.
|
||||||
|
// This does not contain AUD, SPS, nor PPS.
|
||||||
|
const uint8_t kUnitStreamLikeMediaSample[] = {
|
||||||
|
0x00, 0x00, 0x00, 0x0A, // Size 10 NALU.
|
||||||
|
0x06, // NAL unit type.
|
||||||
|
0xFD, 0x78, 0xA4, 0xC3, 0x82, 0x62, 0x11, 0x29, 0x77, // clear
|
||||||
|
0x00, 0x00, 0x00, 0x0B, // Size 11 NALU.
|
||||||
|
0x68, // PPS, will be removed after convertion
|
||||||
|
// The content of PPS is not checked.
|
||||||
|
0x68, 0xFE, 0xFD, 0xFC, 0xFB, 0x12, 0x12, 0x13, 0x14, 0x15, // clear
|
||||||
|
0x00, 0x00, 0x00, 0x08, // Size 8 NALU.
|
||||||
|
0x02, // NAL unit type.
|
||||||
|
0xFD, 0x78, 0xA4, 0x82, 0x62, 0x29, 0x77, // Slice data, cipher
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<SubsampleEntry> subsamples{SubsampleEntry(34, 7)};
|
||||||
|
|
||||||
|
NalUnitToByteStreamConverter converter;
|
||||||
|
EXPECT_TRUE(converter.Initialize(
|
||||||
|
kTestAVCDecoderConfigurationRecord,
|
||||||
|
arraysize(kTestAVCDecoderConfigurationRecord), !kEscapeData));
|
||||||
|
|
||||||
|
std::vector<uint8_t> output;
|
||||||
|
EXPECT_TRUE(converter.ConvertUnitToByteStreamWithSubsamples(
|
||||||
|
kUnitStreamLikeMediaSample, arraysize(kUnitStreamLikeMediaSample),
|
||||||
|
kIsKeyFrame, &output, &subsamples));
|
||||||
|
|
||||||
|
const uint8_t kExpectedOutput[] = {
|
||||||
|
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||||
|
0x09, // AUD type.
|
||||||
|
0xF0, // primary pic type is anything.
|
||||||
|
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||||
|
// Some valid SPS data.
|
||||||
|
0x67, 0x64, 0x00, 0x1E, 0xAC, 0xD9, 0x40, 0xB4,
|
||||||
|
0x2F, 0xF9, 0x7F, 0xF0, 0x00, 0x80, 0x00, 0x91,
|
||||||
|
0x00, 0x00, 0x03, 0x03, 0xE9, 0x00, 0x00, 0xEA,
|
||||||
|
0x60, 0x0F, 0x16, 0x2D, 0x96,
|
||||||
|
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||||
|
0x68, 0xFE, 0xFD, 0xFC, 0xFB, 0x11, 0x12, 0x13, 0x14, 0x15, // PPS.
|
||||||
|
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||||
|
// The input NALU 1.
|
||||||
|
0x06, // NALU type.
|
||||||
|
0xFD, 0x78, 0xA4, 0xC3, 0x82, 0x62, 0x11, 0x29, 0x77,
|
||||||
|
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||||
|
// The input NALU 2.
|
||||||
|
0x02, // NALU type.
|
||||||
|
0xFD, 0x78, 0xA4, 0x82, 0x62, 0x29, 0x77,
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::vector<SubsampleEntry> kExpectedOutputSubsamples{
|
||||||
|
SubsampleEntry(72, 7)};
|
||||||
|
|
||||||
|
EXPECT_EQ(std::vector<uint8_t>(kExpectedOutput,
|
||||||
|
kExpectedOutput + arraysize(kExpectedOutput)),
|
||||||
|
output);
|
||||||
|
EXPECT_EQ(kExpectedOutputSubsamples, subsamples);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace media
|
} // namespace media
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -562,13 +562,26 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) {
|
||||||
const bool is_encrypted =
|
const bool is_encrypted =
|
||||||
entry.sinf.info.track_encryption.default_is_protected == 1;
|
entry.sinf.info.track_encryption.default_is_protected == 1;
|
||||||
DVLOG(1) << "is_video_track_encrypted_: " << is_encrypted;
|
DVLOG(1) << "is_video_track_encrypted_: " << is_encrypted;
|
||||||
streams.push_back(new VideoStreamInfo(
|
scoped_refptr<VideoStreamInfo> video_stream_info(new VideoStreamInfo(
|
||||||
track->header.track_id, timescale, duration, video_codec,
|
track->header.track_id, timescale, duration, video_codec,
|
||||||
codec_string, entry.codec_configuration.data.data(),
|
codec_string, entry.codec_configuration.data.data(),
|
||||||
entry.codec_configuration.data.size(), coded_width, coded_height,
|
entry.codec_configuration.data.size(), coded_width, coded_height,
|
||||||
pixel_width, pixel_height,
|
pixel_width, pixel_height,
|
||||||
0, // trick_play_rate
|
0, // trick_play_rate
|
||||||
nalu_length_size, track->media.header.language.code, is_encrypted));
|
nalu_length_size, track->media.header.language.code, is_encrypted));
|
||||||
|
|
||||||
|
// Set pssh raw data if it has.
|
||||||
|
if (moov_->pssh.size() > 0) {
|
||||||
|
std::vector<uint8_t> pssh_raw_data;
|
||||||
|
for (const auto& pssh : moov_->pssh) {
|
||||||
|
pssh_raw_data.insert(pssh_raw_data.end(), pssh.raw_box.begin(),
|
||||||
|
pssh.raw_box.end());
|
||||||
|
}
|
||||||
|
video_stream_info->set_eme_init_data(pssh_raw_data.data(),
|
||||||
|
pssh_raw_data.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
streams.push_back(video_stream_info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -684,17 +697,20 @@ bool MP4MediaParser::EnqueueSample(bool* err) {
|
||||||
scoped_refptr<MediaSample> stream_sample(MediaSample::CopyFrom(
|
scoped_refptr<MediaSample> stream_sample(MediaSample::CopyFrom(
|
||||||
buf, runs_->sample_size(), runs_->is_keyframe()));
|
buf, runs_->sample_size(), runs_->is_keyframe()));
|
||||||
if (runs_->is_encrypted()) {
|
if (runs_->is_encrypted()) {
|
||||||
if (!decryptor_source_) {
|
std::unique_ptr<DecryptConfig> decrypt_config = runs_->GetDecryptConfig();
|
||||||
|
if (!decrypt_config) {
|
||||||
*err = true;
|
*err = true;
|
||||||
LOG(ERROR) << "Encrypted media sample encountered, but decryption is not "
|
LOG(ERROR) << "Missing decrypt config.";
|
||||||
"enabled";
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<DecryptConfig> decrypt_config = runs_->GetDecryptConfig();
|
if (!decryptor_source_) {
|
||||||
if (!decrypt_config ||
|
// If the demuxer does not have the decryptor_source_, store
|
||||||
!decryptor_source_->DecryptSampleBuffer(decrypt_config.get(),
|
// decrypt_config so that the demuxed sample can be decrypted later.
|
||||||
stream_sample->writable_data(),
|
stream_sample->set_decrypt_config(std::move(decrypt_config));
|
||||||
|
stream_sample->set_is_encrypted(true);
|
||||||
|
} else if (!decryptor_source_->DecryptSampleBuffer(
|
||||||
|
decrypt_config.get(), stream_sample->writable_data(),
|
||||||
stream_sample->data_size())) {
|
stream_sample->data_size())) {
|
||||||
*err = true;
|
*err = true;
|
||||||
LOG(ERROR) << "Cannot decrypt samples.";
|
LOG(ERROR) << "Cannot decrypt samples.";
|
||||||
|
|
|
@ -228,9 +228,13 @@ TEST_F(MP4MediaParserTest, NON_FRAGMENTED_MP4) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MP4MediaParserTest, CencWithoutDecryptionSource) {
|
TEST_F(MP4MediaParserTest, CencWithoutDecryptionSource) {
|
||||||
// Parsing should fail but it will get the streams successfully.
|
EXPECT_TRUE(ParseMP4File("bear-640x360-v_frag-cenc-aux.mp4", 512));
|
||||||
EXPECT_FALSE(ParseMP4File("bear-640x360-v_frag-cenc-aux.mp4", 512));
|
|
||||||
EXPECT_EQ(1u, num_streams_);
|
EXPECT_EQ(1u, num_streams_);
|
||||||
|
// Check if pssh is present.
|
||||||
|
const int kVideoTrackId = 1;
|
||||||
|
EXPECT_NE(0u,
|
||||||
|
reinterpret_cast<VideoStreamInfo*>(stream_map_[kVideoTrackId].get())
|
||||||
|
->eme_init_data().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MP4MediaParserTest, CencInitWithoutDecryptionSource) {
|
TEST_F(MP4MediaParserTest, CencInitWithoutDecryptionSource) {
|
||||||
|
|
|
@ -373,12 +373,12 @@ bool WebMClusterParser::OnBlock(bool is_simple_block,
|
||||||
|
|
||||||
if (decrypt_config) {
|
if (decrypt_config) {
|
||||||
if (!decryptor_source_) {
|
if (!decryptor_source_) {
|
||||||
LOG(ERROR) << "Encrypted media sample encountered, but decryption is "
|
// If the demuxer does not have the decryptor_source_, store
|
||||||
"not enabled";
|
// decrypt_config so that the demuxed sample can be decrypted later.
|
||||||
return false;
|
buffer->set_decrypt_config(std::move(decrypt_config));
|
||||||
}
|
buffer->set_is_encrypted(true);
|
||||||
if (!decryptor_source_->DecryptSampleBuffer(decrypt_config.get(),
|
} else if (!decryptor_source_->DecryptSampleBuffer(
|
||||||
buffer->writable_data(),
|
decrypt_config.get(), buffer->writable_data(),
|
||||||
buffer->data_size())) {
|
buffer->data_size())) {
|
||||||
LOG(ERROR) << "Cannot decrypt samples";
|
LOG(ERROR) << "Cannot decrypt samples";
|
||||||
return false;
|
return false;
|
||||||
|
|
Loading…
Reference in New Issue