Moved Nalu data pointer position.

Now the Nalu data pointer points to the start of the NALU header
rather than pointing to the start code.  Added a new method on
NaluReader to check whether the stream starts with a start code.

Change-Id: Ifaecbe0c911aa5cffdf0a966028e6cada8621cc3
This commit is contained in:
Jacob Trimble 2016-02-22 11:11:29 -08:00
parent 96abd90ca2
commit 0d3951ff74
10 changed files with 76 additions and 83 deletions

View File

@ -49,7 +49,7 @@ bool AVCDecoderConfiguration::Parse(const std::vector<uint8_t>& data) {
H264Parser parser;
int sps_id = 0;
Nalu nalu;
RCHECK(nalu.InitializeFromH264(reader.data() + reader.pos(), sps_length, 0));
RCHECK(nalu.InitializeFromH264(reader.data() + reader.pos(), sps_length));
RCHECK(parser.ParseSPS(nalu, &sps_id) == H264Parser::kOk);
return ExtractResolutionFromSps(*parser.GetSPS(sps_id), &coded_width_,
&coded_height_, &pixel_width_,

View File

@ -34,26 +34,16 @@ bool H264ByteToUnitStreamConverter::ConvertByteStreamToNalUnitStream(
BufferWriter output_buffer(input_frame_size + kStreamConversionOverhead);
bool first_nalu(true);
Nalu nalu;
NaluReader reader(kIsAnnexbByteStream, input_frame, input_frame_size);
while (reader.Advance(&nalu) == NaluReader::kOk) {
if (first_nalu) {
if (nalu.data() != input_frame) {
if (!reader.StartsWithStartCode()) {
LOG(ERROR) << "H.264 byte stream frame did not begin with start code.";
return false;
}
first_nalu = false;
}
while (reader.Advance(&nalu) == NaluReader::kOk) {
ProcessNalu(nalu, &output_buffer);
}
if (first_nalu) {
LOG(ERROR) << "H.264 byte stream frame did not contain start codes.";
return false;
}
output_buffer.SwapBuffer(output_frame);
return true;
}

View File

@ -12,12 +12,6 @@
namespace edash_packager {
namespace media {
namespace {
// The test data does not include a start code, the start of the data is the
// NALU header.
const uint8_t kStartCodeSize = 0;
}
TEST(H264ParserTest, StreamFileParsing) {
std::vector<uint8_t> buffer = ReadTestDataFile("test-25fps.h264");
@ -81,7 +75,7 @@ TEST(H264ParserTest, ExtractResolutionFromSpsData) {
H264Parser parser;
int sps_id = 0;
Nalu nalu;
ASSERT_TRUE(nalu.InitializeFromH264(kSps, arraysize(kSps), kStartCodeSize));
ASSERT_TRUE(nalu.InitializeFromH264(kSps, arraysize(kSps)));
ASSERT_EQ(H264Parser::kOk, parser.ParseSPS(nalu, &sps_id));
uint32_t coded_width = 0;
@ -106,7 +100,7 @@ TEST(H264ParserTest, ExtractResolutionFromSpsDataWithCropping) {
H264Parser parser;
int sps_id = 0;
Nalu nalu;
ASSERT_TRUE(nalu.InitializeFromH264(kSps, arraysize(kSps), kStartCodeSize));
ASSERT_TRUE(nalu.InitializeFromH264(kSps, arraysize(kSps)));
ASSERT_EQ(H264Parser::kOk, parser.ParseSPS(nalu, &sps_id));
uint32_t coded_width = 0;

View File

@ -27,18 +27,16 @@ Nalu::Nalu()
type_(0),
is_video_slice_(false) {}
bool Nalu::InitializeFromH264(const uint8_t* data,
uint64_t size,
uint8_t start_code_size) {
bool Nalu::InitializeFromH264(const uint8_t* data, uint64_t size) {
DCHECK(data);
DCHECK_GT(size, start_code_size);
uint8_t header = data[start_code_size];
DCHECK_GT(size, 0u);
uint8_t header = data[0];
if ((header & 0x80) != 0)
return false;
data_ = data;
header_size_ = start_code_size + 1;
data_size_ = size - start_code_size - 1;
header_size_ = 1;
data_size_ = size - 1;
ref_idc_ = (header >> 5) & 0x3;
type_ = header & 0x1F;
is_video_slice_ = (type_ >= Nalu::H264_NonIDRSlice &&
@ -63,9 +61,10 @@ NaluReader::Result NaluReader::Advance(Nalu* nalu) {
return NaluReader::kEOStream;
uint8_t nalu_length_size_or_start_code_size;
uint64_t nalu_length_with_header;
uint64_t nalu_length;
if (format_ == kAnnexbByteStreamFormat) {
// This will move |stream_| to the start code.
uint64_t nalu_length_with_header;
if (!LocateNaluByStartCode(&nalu_length_with_header,
&nalu_length_size_or_start_code_size)) {
LOG(ERROR) << "Could not find next NALU, bytes left in stream: "
@ -75,8 +74,8 @@ NaluReader::Result NaluReader::Advance(Nalu* nalu) {
// and there are no start codes in the stream.
return NaluReader::kInvalidStream;
}
nalu_length = nalu_length_with_header - nalu_length_size_or_start_code_size;
} else {
uint64_t nalu_length;
BufferReader reader(stream_, stream_size_);
if (!reader.ReadNBytesInto8(&nalu_length, nalu_length_size_))
return NaluReader::kInvalidStream;
@ -91,17 +90,16 @@ NaluReader::Result NaluReader::Advance(Nalu* nalu) {
LOG(ERROR) << "NALU size 0";
return NaluReader::kInvalidStream;
}
nalu_length_with_header = nalu_length + nalu_length_size_;
}
if (!nalu->InitializeFromH264(stream_, nalu_length_with_header,
nalu_length_size_or_start_code_size))
const uint8_t* nalu_data = stream_ + nalu_length_size_or_start_code_size;
if (!nalu->InitializeFromH264(nalu_data, nalu_length))
return NaluReader::kInvalidStream;
// Move parser state to after this NALU, so next time Advance
// is called, we will effectively be skipping it.
stream_ += nalu_length_with_header;
stream_size_ -= nalu_length_with_header;
stream_ += nalu_length_size_or_start_code_size + nalu_length;
stream_size_ -= nalu_length_size_or_start_code_size + nalu_length;
DVLOG(4) << "NALU type: " << static_cast<int>(nalu->type())
<< " at: " << reinterpret_cast<const void*>(nalu->data())
@ -111,6 +109,18 @@ NaluReader::Result NaluReader::Advance(Nalu* nalu) {
return NaluReader::kOk;
}
bool NaluReader::StartsWithStartCode() {
if (stream_size_ >= 3) {
if (IsStartCode(stream_))
return true;
}
if (stream_size_ >= 4) {
if (stream_[0] == 0x00 && IsStartCode(stream_ + 1))
return true;
}
return false;
}
// static
bool NaluReader::FindStartCode(const uint8_t* data,
uint64_t data_size,

View File

@ -40,8 +40,7 @@ class Nalu {
Nalu();
bool InitializeFromH264(const uint8_t* data,
uint64_t size,
uint8_t start_code_size) WARN_UNUSED_RESULT;
uint64_t size) WARN_UNUSED_RESULT;
const uint8_t* data() const { return data_; }
uint64_t data_size() const { return data_size_; }
@ -105,6 +104,9 @@ class NaluReader {
/// end-of-stream; kInvalidStream on error.
Result Advance(Nalu* nalu);
/// @returns true if the current position points to a start code.
bool StartsWithStartCode();
private:
enum Format {
kAnnexbByteStreamFormat,

View File

@ -24,16 +24,16 @@ TEST(NaluReaderTest, StartCodeSearch) {
Nalu nalu;
ASSERT_EQ(NaluReader::kOk, reader.Advance(&nalu));
EXPECT_EQ(kNaluData + 6, nalu.data());
EXPECT_EQ(kNaluData + 9, nalu.data());
EXPECT_EQ(3u, nalu.data_size());
EXPECT_EQ(4u, nalu.header_size());
EXPECT_EQ(1u, nalu.header_size());
EXPECT_EQ(0, nalu.ref_idc());
EXPECT_EQ(0x12, nalu.type());
ASSERT_EQ(NaluReader::kOk, reader.Advance(&nalu));
EXPECT_EQ(kNaluData + 13, nalu.data());
EXPECT_EQ(kNaluData + 17, nalu.data());
EXPECT_EQ(3u, nalu.data_size());
EXPECT_EQ(5u, nalu.header_size());
EXPECT_EQ(1u, nalu.header_size());
EXPECT_EQ(3, nalu.ref_idc());
EXPECT_EQ(7, nalu.type());
@ -52,44 +52,44 @@ TEST(NaluReaderTest, OneByteNaluLength) {
Nalu nalu;
ASSERT_EQ(NaluReader::kOk, reader.Advance(&nalu));
EXPECT_EQ(kNaluData, nalu.data());
EXPECT_EQ(kNaluData + 1, nalu.data());
EXPECT_EQ(4u, nalu.data_size());
EXPECT_EQ(2u, nalu.header_size());
EXPECT_EQ(1u, nalu.header_size());
EXPECT_EQ(0, nalu.ref_idc());
EXPECT_EQ(8, nalu.type());
ASSERT_EQ(NaluReader::kOk, reader.Advance(&nalu));
EXPECT_EQ(kNaluData + 6, nalu.data());
EXPECT_EQ(kNaluData + 7, nalu.data());
EXPECT_EQ(5u, nalu.data_size());
EXPECT_EQ(2u, nalu.header_size());
EXPECT_EQ(1u, nalu.header_size());
EXPECT_EQ(3, nalu.ref_idc());
EXPECT_EQ(7, nalu.type());
EXPECT_EQ(NaluReader::kEOStream, reader.Advance(&nalu));
}
TEST(NaluReaderTest, ThreeByteNaluLength) {
TEST(NaluReaderTest, FourByteNaluLength) {
const uint8_t kNaluData[] = {
// First NALU
0x00, 0x00, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
0x00, 0x00, 0x00, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
// Second NALU
0x00, 0x00, 0x03, 0x67, 0x0a, 0x0b
0x00, 0x00, 0x00, 0x03, 0x67, 0x0a, 0x0b
};
NaluReader reader(3, kNaluData, arraysize(kNaluData));
NaluReader reader(4, kNaluData, arraysize(kNaluData));
Nalu nalu;
ASSERT_EQ(NaluReader::kOk, reader.Advance(&nalu));
EXPECT_EQ(kNaluData, nalu.data());
EXPECT_EQ(kNaluData + 4, nalu.data());
EXPECT_EQ(6u, nalu.data_size());
EXPECT_EQ(4u, nalu.header_size());
EXPECT_EQ(1u, nalu.header_size());
EXPECT_EQ(0, nalu.ref_idc());
EXPECT_EQ(8, nalu.type());
ASSERT_EQ(NaluReader::kOk, reader.Advance(&nalu));
EXPECT_EQ(kNaluData + 10, nalu.data());
EXPECT_EQ(kNaluData + 15, nalu.data());
EXPECT_EQ(2u, nalu.data_size());
EXPECT_EQ(4u, nalu.header_size());
EXPECT_EQ(1u, nalu.header_size());
EXPECT_EQ(3, nalu.ref_idc());
EXPECT_EQ(7, nalu.type());

View File

@ -49,6 +49,15 @@ VideoCodec GetVideoCodec(const StreamInfo& stream_info) {
static_cast<const VideoStreamInfo&>(stream_info);
return video_stream_info.codec();
}
uint8_t GetNaluLengthSize(const StreamInfo& stream_info) {
if (stream_info.stream_type() != kStreamVideo)
return 0;
const VideoStreamInfo& video_stream_info =
static_cast<const VideoStreamInfo&>(stream_info);
return video_stream_info.nalu_length_size();
}
} // namespace
EncryptingFragmenter::EncryptingFragmenter(
@ -59,6 +68,7 @@ EncryptingFragmenter::EncryptingFragmenter(
: Fragmenter(traf),
info_(info),
encryption_key_(encryption_key.Pass()),
nalu_length_size_(GetNaluLengthSize(*info)),
clear_time_(clear_time) {
DCHECK(encryption_key_);
VideoCodec video_codec = GetVideoCodec(*info);
@ -213,7 +223,7 @@ Status EncryptingFragmenter::EncryptSample(scoped_refptr<MediaSample> sample) {
data += frame.frame_size;
}
} else {
NaluReader reader(GetNaluLengthSize(), data, sample->data_size());
NaluReader reader(nalu_length_size_, data, sample->data_size());
// Store the current length of clear data. This is used to squash
// multiple unencrypted NAL units into fewer subsample entries.
@ -231,14 +241,15 @@ Status EncryptingFragmenter::EncryptSample(scoped_refptr<MediaSample> sample) {
if (video_slice_header_size < 0)
return Status(error::MUXER_FAILURE, "Failed to read slice header.");
const uint64_t current_clear_bytes =
nalu.header_size() + video_slice_header_size;
const uint64_t current_clear_bytes = nalu.header_size() +
video_slice_header_size;
const uint64_t cipher_bytes =
nalu.data_size() - video_slice_header_size;
const uint8_t* nalu_data = nalu.data() + current_clear_bytes;
EncryptBytes(const_cast<uint8_t*>(nalu_data), cipher_bytes);
AddSubsamples(accumulated_clear_bytes + current_clear_bytes,
AddSubsamples(
accumulated_clear_bytes + nalu_length_size_ + current_clear_bytes,
cipher_bytes, &sample_encryption_entry.subsamples);
accumulated_clear_bytes = 0;
} else {
@ -265,17 +276,8 @@ Status EncryptingFragmenter::EncryptSample(scoped_refptr<MediaSample> sample) {
return Status::OK;
}
uint8_t EncryptingFragmenter::GetNaluLengthSize() {
if (info_->stream_type() != kStreamVideo)
return 0;
const VideoStreamInfo& video_stream_info =
static_cast<const VideoStreamInfo&>(*info_);
return video_stream_info.nalu_length_size();
}
bool EncryptingFragmenter::IsSubsampleEncryptionRequired() {
return vpx_parser_ || GetNaluLengthSize() != 0;
return vpx_parser_ || nalu_length_size_ != 0;
}
} // namespace mp4

View File

@ -66,16 +66,16 @@ class EncryptingFragmenter : public Fragmenter {
void EncryptBytes(uint8_t* data, uint32_t size);
Status EncryptSample(scoped_refptr<MediaSample> sample);
// If this stream contains AVC, subsample encryption specifies that the size
// and type of NAL units remain unencrypted. This function returns the size of
// the size field in bytes. Can be 1, 2 or 4 bytes.
uint8_t GetNaluLengthSize();
// Should we enable subsample encryption?
bool IsSubsampleEncryptionRequired();
scoped_refptr<StreamInfo> info_;
scoped_ptr<EncryptionKey> encryption_key_;
scoped_ptr<AesCtrEncryptor> encryptor_;
// If this stream contains AVC, subsample encryption specifies that the size
// and type of NAL units remain unencrypted. This function returns the size of
// the size field in bytes. Can be 1, 2 or 4 bytes.
const uint8_t nalu_length_size_;
int64_t clear_time_;
scoped_ptr<VPxParser> vpx_parser_;

View File

@ -13,10 +13,6 @@ namespace edash_packager {
namespace media {
namespace mp4 {
namespace {
const uint8_t kStartCodeSize = 0;
}
H264VideoSliceHeaderParser::H264VideoSliceHeaderParser() {}
H264VideoSliceHeaderParser::~H264VideoSliceHeaderParser() {}
@ -39,7 +35,7 @@ bool H264VideoSliceHeaderParser::Initialize(
int id;
Nalu nalu;
RCHECK(nalu.InitializeFromH264(data, size, kStartCodeSize));
RCHECK(nalu.InitializeFromH264(data, size));
RCHECK(parser_.ParseSPS(nalu, &id) == H264Parser::kOk);
}
@ -53,7 +49,7 @@ bool H264VideoSliceHeaderParser::Initialize(
int id;
Nalu nalu;
RCHECK(nalu.InitializeFromH264(data, size, kStartCodeSize));
RCHECK(nalu.InitializeFromH264(data, size));
RCHECK(parser_.ParsePPS(nalu, &id) == H264Parser::kOk);
}

View File

@ -31,7 +31,6 @@ TEST(H264VideoSliceHeaderParserTest, BasicSupport) {
0x00, 0x06, // Size
0x68, 0xeb, 0xe3, 0xcb, 0x22, 0xc0
};
const uint8_t kStartCodeSize = 0;
const uint8_t kData[] = {
// Incomplete data, but we only care about the header size.
0x65, 0x88, 0x84, 0x00, 0x21, 0xff, 0xcf, 0x73, 0xc7, 0x24,
@ -44,7 +43,7 @@ TEST(H264VideoSliceHeaderParserTest, BasicSupport) {
ASSERT_TRUE(parser.Initialize(extra_data));
Nalu nalu;
ASSERT_TRUE(nalu.InitializeFromH264(kData, arraysize(kData), kStartCodeSize));
ASSERT_TRUE(nalu.InitializeFromH264(kData, arraysize(kData)));
// Real header size is 34 bits, but we round up to 5 bytes.
EXPECT_EQ(5, parser.GetHeaderSize(nalu));
}