Commit d8ee5f85 authored by Yuchen Liu's avatar Yuchen Liu Committed by Commit Bot

HEVC bit stream analyzer to detect key frame

Analyze HEVC stream with class H265Parser. It will be used in mp4 parser
to get the correct information on whether the current frame is key frame.

Currently it performs check of ITU-T Rec. H.265 (02/2018), section
7.4.2.4.4, Order or NAL units and coded pictures and their association
to access units.

Bug: 527595
Bug: internal b/114239680
Test: Play mp4 which marks all the video frames as key frame.
Change-Id: I975140241f47bcac21afa648f06207fddff3ec19
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1629755Reviewed-by: default avatarMatthew Wolenetz <wolenetz@chromium.org>
Commit-Queue: Yuchen Liu <yucliu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#665022}
parent 2ef3bab9
......@@ -188,11 +188,18 @@ static_library("test_support") {
"//testing/gtest",
]
if (proprietary_codecs && enable_mse_mpeg2ts_stream_parser) {
if (proprietary_codecs) {
sources += [
"mp2t/es_parser_test_base.cc",
"mp2t/es_parser_test_base.h",
"mp4/nalu_test_helper.cc",
"mp4/nalu_test_helper.h",
]
if (enable_mse_mpeg2ts_stream_parser) {
sources += [
"mp2t/es_parser_test_base.cc",
"mp2t/es_parser_test_base.h",
]
}
}
}
......@@ -252,5 +259,9 @@ source_set("unit_tests") {
if (enable_dolby_vision_demuxing) {
sources += [ "mp4/dolby_vision_unittest.cc" ]
}
if (enable_hevc_demuxing) {
sources += [ "mp4/hevc_unittest.cc" ]
}
}
}
......@@ -17,6 +17,7 @@
#include "media/formats/mp4/avc.h"
#include "media/formats/mp4/bitstream_converter.h"
#include "media/formats/mp4/box_definitions.h"
#include "media/formats/mp4/nalu_test_helper.h"
#include "media/video/h264_parser.h"
#include "testing/gtest/include/gtest/gtest.h"
......@@ -33,53 +34,6 @@ static const uint8_t kExpectedParamSets[] = {
0x00, 0x00, 0x00, 0x01, 0x67, 0x12, 0x00, 0x00, 0x00, 0x01,
0x67, 0x34, 0x00, 0x00, 0x00, 0x01, 0x68, 0x56, 0x78};
static H264NALU::Type StringToNALUType(const std::string& name) {
if (name == "P")
return H264NALU::kNonIDRSlice;
if (name == "I")
return H264NALU::kIDRSlice;
if (name == "SDA")
return H264NALU::kSliceDataA;
if (name == "SDB")
return H264NALU::kSliceDataB;
if (name == "SDC")
return H264NALU::kSliceDataC;
if (name == "SEI")
return H264NALU::kSEIMessage;
if (name == "SPS")
return H264NALU::kSPS;
if (name == "SPSExt")
return H264NALU::kSPSExt;
if (name == "PPS")
return H264NALU::kPPS;
if (name == "AUD")
return H264NALU::kAUD;
if (name == "EOSeq")
return H264NALU::kEOSeq;
if (name == "EOStr")
return H264NALU::kEOStream;
if (name == "FILL")
return H264NALU::kFiller;
if (name == "R14")
return H264NALU::kReserved14;
CHECK(false) << "Unexpected name: " << name;
return H264NALU::kUnspecified;
}
static std::string NALUTypeToString(int type) {
switch (type) {
case H264NALU::kNonIDRSlice:
......@@ -125,77 +79,6 @@ static std::string NALUTypeToString(int type) {
return "UnsupportedType";
}
static void WriteStartCodeAndNALUType(std::vector<uint8_t>* buffer,
const std::string& nal_unit_type) {
buffer->push_back(0x00);
buffer->push_back(0x00);
buffer->push_back(0x00);
buffer->push_back(0x01);
buffer->push_back(StringToNALUType(nal_unit_type));
}
// Input string should be one or more NALU types separated with spaces or
// commas. NALU grouped together and separated by commas are placed into the
// same subsample, NALU groups separated by spaces are placed into separate
// subsamples.
// For example: input string "SPS PPS I" produces Annex B buffer containing
// SPS, PPS and I NALUs, each in a separate subsample. While input string
// "SPS,PPS I" produces Annex B buffer where the first subsample contains SPS
// and PPS NALUs and the second subsample contains the I-slice NALU.
// The output buffer will contain a valid-looking Annex B (it's valid-looking in
// the sense that it has start codes and correct NALU types, but the actual NALU
// payload is junk).
static void StringToAnnexB(const std::string& str,
std::vector<uint8_t>* buffer,
std::vector<SubsampleEntry>* subsamples) {
DCHECK(!str.empty());
std::vector<std::string> subsample_specs = base::SplitString(
str, " ", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
EXPECT_GT(subsample_specs.size(), 0u);
buffer->clear();
for (size_t i = 0; i < subsample_specs.size(); ++i) {
SubsampleEntry entry;
size_t start = buffer->size();
std::vector<std::string> subsample_nalus = base::SplitString(
subsample_specs[i], ",", base::KEEP_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
EXPECT_GT(subsample_nalus.size(), 0u);
for (size_t j = 0; j < subsample_nalus.size(); ++j) {
WriteStartCodeAndNALUType(buffer, subsample_nalus[j]);
// Write junk for the payload since the current code doesn't
// actually look at it.
buffer->push_back(0x32);
buffer->push_back(0x12);
buffer->push_back(0x67);
}
entry.clear_bytes = buffer->size() - start;
if (subsamples) {
// Simulate the encrypted bits containing something that looks
// like a SPS NALU.
WriteStartCodeAndNALUType(buffer, "SPS");
}
entry.cypher_bytes = buffer->size() - start - entry.clear_bytes;
if (subsamples) {
subsamples->push_back(entry);
}
}
}
// Helper to compare two results of AVC::Analyze().
static bool AnalysesMatch(const BitstreamConverter::AnalysisResult& r1,
const BitstreamConverter::AnalysisResult& r2) {
return r1.is_conformant == r2.is_conformant &&
r1.is_keyframe == r2.is_keyframe;
}
// Helper output operator, for debugging/testability.
std::ostream& operator<<(std::ostream& os,
const BitstreamConverter::AnalysisResult& r) {
......@@ -391,7 +274,7 @@ TEST_F(AVCConversionTest, StringConversionFunctions) {
"AUD SPS SPSExt SPS PPS SEI SEI R14 I P FILL EOSeq EOStr";
std::vector<uint8_t> buf;
std::vector<SubsampleEntry> subsamples;
StringToAnnexB(str, &buf, &subsamples);
AvcStringToAnnexB(str, &buf, &subsamples);
BitstreamConverter::AnalysisResult expected;
expected.is_conformant = true;
......@@ -439,7 +322,7 @@ TEST_F(AVCConversionTest, ValidAnnexBConstructs) {
for (size_t i = 0; i < base::size(test_cases); ++i) {
std::vector<uint8_t> buf;
std::vector<SubsampleEntry> subsamples;
StringToAnnexB(test_cases[i].case_string, &buf, NULL);
AvcStringToAnnexB(test_cases[i].case_string, &buf, NULL);
BitstreamConverter::AnalysisResult expected;
expected.is_conformant = true;
......@@ -487,7 +370,7 @@ TEST_F(AVCConversionTest, InvalidAnnexBConstructs) {
for (size_t i = 0; i < base::size(test_cases); ++i) {
std::vector<uint8_t> buf;
std::vector<SubsampleEntry> subsamples;
StringToAnnexB(test_cases[i].case_string, &buf, NULL);
AvcStringToAnnexB(test_cases[i].case_string, &buf, NULL);
expected.is_keyframe = test_cases[i].is_keyframe;
EXPECT_PRED2(AnalysesMatch,
AVC::AnalyzeAnnexB(buf.data(), buf.size(), subsamples),
......@@ -536,7 +419,7 @@ TEST_F(AVCConversionTest, InsertParamSetsAnnexB) {
std::vector<uint8_t> buf;
std::vector<SubsampleEntry> subsamples;
StringToAnnexB(test_cases[i].input, &buf, &subsamples);
AvcStringToAnnexB(test_cases[i].input, &buf, &subsamples);
EXPECT_TRUE(AVC::InsertParamSetsAnnexB(avc_config, &buf, &subsamples))
<< "'" << test_cases[i].input << "' insert failed.";
......
......@@ -214,17 +214,220 @@ BitstreamConverter::AnalysisResult HEVC::AnalyzeAnnexB(
const uint8_t* buffer,
size_t size,
const std::vector<SubsampleEntry>& subsamples) {
DVLOG(3) << __func__;
DCHECK(buffer);
BitstreamConverter::AnalysisResult result;
result.is_conformant = false; // Will change if needed before return.
if (size == 0) {
result.is_conformant = true;
return result;
}
// TODO(servolk): Implement this, see https://crbug.com/527595. For now, we
// report that neither conformance nor keyframe analyses were performed.
H265Parser parser;
parser.SetEncryptedStream(buffer, size, subsamples);
enum NALUOrderState {
kAUDAllowed,
kBeforeFirstVCL,
kAfterFirstVCL,
kEOBitstreamAllowed,
kNoMoreDataAllowed,
};
H265NALU nalu;
NALUOrderState order_state = kAUDAllowed;
// Rec. ITU-T H.265 v5 (02/2018)
// 7.4.2.4.4 Order of NAL units and coded pictures and their association to
// access units
while (true) {
H265Parser::Result h265_result = parser.AdvanceToNextNALU(&nalu);
if (h265_result == H265Parser::kEOStream) {
break;
}
if (h265_result != H265Parser::kOk) {
DCHECK_NE(h265_result, H265Parser::kUnsupportedStream)
<< "AdvanceToNextNALU() returned kUnsupportedStream!";
return result;
}
DVLOG(3) << "nal_unit_type " << nalu.nal_unit_type;
// Definition of "access unit" and "base layer" is only applied to NALs with
// nuh_layer_id equals 0.
if (nalu.nuh_layer_id != 0) {
LOG(WARNING) << "Unrecognized layer ID " << nalu.nuh_layer_id
<< ", skip.";
continue;
}
if (order_state == kNoMoreDataAllowed) {
DVLOG(1) << "No more data is allowed after EOB_NUT.";
return result;
}
if (order_state == kEOBitstreamAllowed &&
nalu.nal_unit_type != H265NALU::EOB_NUT) {
DVLOG(1) << "Only EOB_NUT is allowed after EOS_NUT.";
return result;
}
switch (nalu.nal_unit_type) {
// When an access unit delimiter NAL unit with nuh_layer_id equal to 0 is
// present, it shall be the first NAL unit. There shall be at most one
// access unit delimiter NAL unit with nuh_layer_id equal to 0 in any
// access unit.
case H265NALU::AUD_NUT:
if (order_state > kAUDAllowed) {
DVLOG(1) << "Unexpected AUD in order_state " << order_state;
return result;
}
order_state = kBeforeFirstVCL;
break;
// When any VPS NAL units, SPS NAL units, PPS NAL units, prefix SEI NAL
// units, NAL units with nal_unit_type in the range of
// RSV_NVCL41..RSV_NVCL44, or NAL units with nal_unit_type in the range of
// UNSPEC48..UNSPEC55 are present, they shall not follow the last VCL NAL
// unit of the access unit.
case H265NALU::VPS_NUT:
case H265NALU::SPS_NUT:
case H265NALU::PPS_NUT:
case H265NALU::PREFIX_SEI_NUT:
case H265NALU::RSV_NVCL41:
case H265NALU::RSV_NVCL42:
case H265NALU::RSV_NVCL43:
case H265NALU::RSV_NVCL44:
case H265NALU::UNSPEC48:
case H265NALU::UNSPEC49:
case H265NALU::UNSPEC50:
case H265NALU::UNSPEC51:
case H265NALU::UNSPEC52:
case H265NALU::UNSPEC53:
case H265NALU::UNSPEC54:
case H265NALU::UNSPEC55:
if (order_state > kBeforeFirstVCL) {
DVLOG(1) << "Unexpected NALU type " << nalu.nal_unit_type
<< " in order_state " << order_state;
return result;
}
order_state = kBeforeFirstVCL;
break;
// NAL units having nal_unit_type equal to FD_NUT or SUFFIX_SEI_NUT or in
// the range of RSV_NVCL45..RSV_NVCL47 or UNSPEC56..UNSPEC63 shall not
// precede the first VCL NAL unit of the access unit.
case H265NALU::FD_NUT:
case H265NALU::SUFFIX_SEI_NUT:
case H265NALU::RSV_NVCL45:
case H265NALU::RSV_NVCL46:
case H265NALU::RSV_NVCL47:
case H265NALU::UNSPEC56:
case H265NALU::UNSPEC57:
case H265NALU::UNSPEC58:
case H265NALU::UNSPEC59:
case H265NALU::UNSPEC60:
case H265NALU::UNSPEC61:
case H265NALU::UNSPEC62:
case H265NALU::UNSPEC63:
if (order_state < kAfterFirstVCL) {
DVLOG(1) << "Unexpected NALU type " << nalu.nal_unit_type
<< " in order_state " << order_state;
return result;
}
break;
// When an end of sequence NAL unit with nuh_layer_id equal to 0 is
// present, it shall be the last NAL unit among all NAL units with
// nuh_layer_id equal to 0 in the access unit other than an end of
// bitstream NAL unit (when present).
case H265NALU::EOS_NUT:
if (order_state != kAfterFirstVCL) {
DVLOG(1) << "Unexpected EOS in order_state " << order_state;
return result;
}
order_state = kEOBitstreamAllowed;
break;
// When an end of bitstream NAL unit is present, it shall be the last NAL
// unit in the access unit.
case H265NALU::EOB_NUT:
if (order_state < kAfterFirstVCL) {
DVLOG(1) << "Unexpected EOB in order_state " << order_state;
return result;
}
order_state = kNoMoreDataAllowed;
break;
// VCL, non-IRAP
case H265NALU::TRAIL_N:
case H265NALU::TRAIL_R:
case H265NALU::TSA_N:
case H265NALU::TSA_R:
case H265NALU::STSA_N:
case H265NALU::STSA_R:
case H265NALU::RADL_N:
case H265NALU::RADL_R:
case H265NALU::RASL_N:
case H265NALU::RASL_R:
case H265NALU::RSV_VCL_N10:
case H265NALU::RSV_VCL_R11:
case H265NALU::RSV_VCL_N12:
case H265NALU::RSV_VCL_R13:
case H265NALU::RSV_VCL_N14:
case H265NALU::RSV_VCL_R15:
case H265NALU::RSV_VCL24:
case H265NALU::RSV_VCL25:
case H265NALU::RSV_VCL26:
case H265NALU::RSV_VCL27:
case H265NALU::RSV_VCL28:
case H265NALU::RSV_VCL29:
case H265NALU::RSV_VCL30:
case H265NALU::RSV_VCL31:
if (order_state > kAfterFirstVCL) {
DVLOG(1) << "Unexpected VCL in order_state " << order_state;
return result;
}
if (!result.is_keyframe.has_value())
result.is_keyframe = false;
order_state = kAfterFirstVCL;
break;
// VCL, IRAP
case H265NALU::BLA_W_LP:
case H265NALU::BLA_W_RADL:
case H265NALU::BLA_N_LP:
case H265NALU::IDR_W_RADL:
case H265NALU::IDR_N_LP:
case H265NALU::CRA_NUT:
case H265NALU::RSV_IRAP_VCL22:
case H265NALU::RSV_IRAP_VCL23:
if (order_state > kAfterFirstVCL) {
DVLOG(1) << "Unexpected VCL in order_state " << order_state;
return result;
}
if (!result.is_keyframe.has_value())
result.is_keyframe = true;
order_state = kAfterFirstVCL;
break;
default:
NOTREACHED() << "Unsupported NALU type " << nalu.nal_unit_type;
}
}
if (order_state < kAfterFirstVCL)
return result;
result.is_conformant = true;
DCHECK(result.is_keyframe.has_value());
return result;
}
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/formats/mp4/hevc.h"
#include "base/stl_util.h"
#include "media/formats/mp4/nalu_test_helper.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media {
namespace mp4 {
TEST(HEVCAnalyzeAnnexBTest, ValidAnnexBConstructs) {
struct {
const char* case_string;
const bool is_keyframe;
} test_cases[] = {
{"I", true}, {"I I I I", true}, {"AUD I", true},
{"AUD SPS I", true}, {"I EOS", true}, {"I EOS EOB", true},
{"I EOB", true}, {"P", false}, {"P P P P", false},
{"AUD SPS P", false}, {"AUD,I", true}, {"AUD,SPS,I", true},
};
for (size_t i = 0; i < base::size(test_cases); ++i) {
std::vector<uint8_t> buf;
std::vector<SubsampleEntry> subsamples;
HevcStringToAnnexB(test_cases[i].case_string, &buf, nullptr);
BitstreamConverter::AnalysisResult expected;
expected.is_conformant = true;
expected.is_keyframe = test_cases[i].is_keyframe;
EXPECT_PRED2(AnalysesMatch,
HEVC::AnalyzeAnnexB(buf.data(), buf.size(), subsamples),
expected)
<< "'" << test_cases[i].case_string << "' failed";
}
}
TEST(HEVCAnalyzeAnnexBTest, InvalidAnnexBConstructs) {
struct {
const char* case_string;
const base::Optional<bool> is_keyframe;
} test_cases[] = {
// For these cases, lack of conformance is determined before detecting any
// IDR or non-IDR slices, so the non-conformant frames' keyframe analysis
// reports base::nullopt (which means undetermined analysis result).
{"AUD", base::nullopt}, // No VCL present.
{"AUD,SPS", base::nullopt}, // No VCL present.
{"SPS AUD I", base::nullopt}, // Parameter sets must come after AUD.
{"EOS", base::nullopt}, // EOS must come after a VCL.
{"EOB", base::nullopt}, // EOB must come after a VCL.
// For these cases, IDR slice is first VCL and is detected before
// conformance failure, so the non-conformant frame is reported as a
// keyframe.
{"I EOB EOS", true}, // EOS must come before EOB.
{"I SPS", true}, // SPS must come before first VCL.
// For this case, P slice is first VCL and is detected before conformance
// failure, so the non-conformant frame is reported as a non-keyframe.
{"P SPS P",
false}, // SPS after first VCL would indicate a new access unit.
};
BitstreamConverter::AnalysisResult expected;
expected.is_conformant = false;
for (size_t i = 0; i < base::size(test_cases); ++i) {
std::vector<uint8_t> buf;
std::vector<SubsampleEntry> subsamples;
HevcStringToAnnexB(test_cases[i].case_string, &buf, nullptr);
expected.is_keyframe = test_cases[i].is_keyframe;
EXPECT_PRED2(AnalysesMatch,
HEVC::AnalyzeAnnexB(buf.data(), buf.size(), subsamples),
expected)
<< "'" << test_cases[i].case_string << "' failed";
}
}
} // namespace mp4
} // namespace media
......@@ -353,7 +353,7 @@ TEST_F(MP4StreamParserTest, AVC_KeyAndNonKeyframeness_Match_Container) {
}
TEST_F(MP4StreamParserTest, AVC_Keyframeness_Mismatches_Container) {
// The first AVC video frame's keyframe-ness metadata matches the MP4:
// The first AVC video frame's keyframe-ness metadata mismatches the MP4:
// Frame 0: AVC IDR, trun.first_sample_flags: NOT sync sample, DEPENDS on
// others.
// Frame 1: AVC Non-IDR, tfhd.default_sample_flags: not sync sample, depends
......@@ -373,7 +373,7 @@ TEST_F(MP4StreamParserTest, AVC_Keyframeness_Mismatches_Container) {
}
TEST_F(MP4StreamParserTest, AVC_NonKeyframeness_Mismatches_Container) {
// The second AVC video frame's keyframe-ness metadata matches the MP4:
// The second AVC video frame's keyframe-ness metadata mismatches the MP4:
// Frame 0: AVC IDR, trun.first_sample_flags: sync sample that doesn't
// depend on others.
// Frame 1: AVC Non-IDR, tfhd.default_sample_flags: SYNC sample, DOES NOT
......@@ -472,6 +472,65 @@ TEST_F(MP4StreamParserTest, HEVC_in_MP4_container) {
#endif
}
#if BUILDFLAG(ENABLE_HEVC_DEMUXING)
TEST_F(MP4StreamParserTest, HEVC_KeyAndNonKeyframeness_Match_Container) {
// Both HEVC video frames' keyframe-ness metadata matches the MP4:
// Frame 0: HEVC IDR, trun.first_sample_flags: sync sample that doesn't
// depend on others.
// Frame 1: HEVC Non-IDR, tfhd.default_sample_flags: not sync sample, depends
// on others.
// This is the base case; see also the "Mismatches" cases, below.
InSequence s; // The EXPECT* sequence matters for this test.
auto params = GetDefaultInitParametersExpectations();
params.detected_audio_track_count = 0;
InitializeParserWithInitParametersExpectations(params);
verifying_keyframeness_sequence_ = true;
EXPECT_CALL(*this, ParsedKeyframe());
EXPECT_CALL(*this, ParsedNonKeyframe());
ParseMP4File("bear-320x240-v-2frames_frag-hevc.mp4", 256);
}
TEST_F(MP4StreamParserTest, HEVC_Keyframeness_Mismatches_Container) {
// The first HEVC video frame's keyframe-ness metadata mismatches the MP4:
// Frame 0: HEVC IDR, trun.first_sample_flags: NOT sync sample, DEPENDS on
// others.
// Frame 1: HEVC Non-IDR, tfhd.default_sample_flags: not sync sample, depends
// on others.
InSequence s; // The EXPECT* sequence matters for this test.
auto params = GetDefaultInitParametersExpectations();
params.detected_audio_track_count = 0;
InitializeParserWithInitParametersExpectations(params);
verifying_keyframeness_sequence_ = true;
EXPECT_MEDIA_LOG(DebugLog(
"ISO-BMFF container metadata for video frame indicates that the frame is "
"not a keyframe, but the video frame contents indicate the opposite."));
EXPECT_CALL(*this, ParsedKeyframe());
EXPECT_CALL(*this, ParsedNonKeyframe());
ParseMP4File(
"bear-320x240-v-2frames-keyframe-is-non-sync-sample_frag-hevc.mp4", 256);
}
TEST_F(MP4StreamParserTest, HEVC_NonKeyframeness_Mismatches_Container) {
// The second HEVC video frame's keyframe-ness metadata mismatches the MP4:
// Frame 0: HEVC IDR, trun.first_sample_flags: sync sample that doesn't
// depend on others.
// Frame 1: HEVC Non-IDR, tfhd.default_sample_flags: SYNC sample, DOES NOT
// depend on others.
InSequence s; // The EXPECT* sequence matters for this test.
auto params = GetDefaultInitParametersExpectations();
params.detected_audio_track_count = 0;
InitializeParserWithInitParametersExpectations(params);
verifying_keyframeness_sequence_ = true;
EXPECT_CALL(*this, ParsedKeyframe());
EXPECT_MEDIA_LOG(DebugLog(
"ISO-BMFF container metadata for video frame indicates that the frame is "
"a keyframe, but the video frame contents indicate the opposite."));
EXPECT_CALL(*this, ParsedNonKeyframe());
ParseMP4File(
"bear-320x240-v-2frames-nonkeyframe-is-sync-sample_frag-hevc.mp4", 256);
}
#endif
// Sample encryption information is stored as CencSampleAuxiliaryDataFormat
// (ISO/IEC 23001-7:2015 8) inside 'mdat' box. No SampleEncryption ('senc') box.
TEST_F(MP4StreamParserTest, CencWithEncryptionInfoStoredAsAuxDataInMdat) {
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/formats/mp4/nalu_test_helper.h"
#include "base/logging.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "media/video/h264_parser.h"
#if BUILDFLAG(ENABLE_HEVC_DEMUXING)
#include "media/video/h265_parser.h"
#endif // BUILDFLAG(ENABLE_HEVC_DEMUXING)
namespace media {
namespace mp4 {
namespace {
template <typename T>
void WriteNALUType(std::vector<uint8_t>* buffer,
const std::string& nal_unit_type);
H264NALU::Type H264StringToNALUType(const std::string& name) {
if (name == "P")
return H264NALU::kNonIDRSlice;
if (name == "I")
return H264NALU::kIDRSlice;
if (name == "SDA")
return H264NALU::kSliceDataA;
if (name == "SDB")
return H264NALU::kSliceDataB;
if (name == "SDC")
return H264NALU::kSliceDataC;
if (name == "SEI")
return H264NALU::kSEIMessage;
if (name == "SPS")
return H264NALU::kSPS;
if (name == "SPSExt")
return H264NALU::kSPSExt;
if (name == "PPS")
return H264NALU::kPPS;
if (name == "AUD")
return H264NALU::kAUD;
if (name == "EOSeq")
return H264NALU::kEOSeq;
if (name == "EOStr")
return H264NALU::kEOStream;
if (name == "FILL")
return H264NALU::kFiller;
if (name == "R14")
return H264NALU::kReserved14;
CHECK(false) << "Unexpected name: " << name;
return H264NALU::kUnspecified;
}
template <>
void WriteNALUType<H264NALU>(std::vector<uint8_t>* buffer,
const std::string& nal_unit_type) {
buffer->push_back(H264StringToNALUType(nal_unit_type));
}
#if BUILDFLAG(ENABLE_HEVC_DEMUXING)
// Convert NALU type string to NALU type. It only supports a subset of all the
// NALU types for testing purpose.
H265NALU::Type H265StringToNALUType(const std::string& name) {
if (name == "AUD")
return H265NALU::AUD_NUT;
if (name == "SPS")
return H265NALU::SPS_NUT;
if (name == "FD")
return H265NALU::FD_NUT;
if (name == "EOS")
return H265NALU::EOS_NUT;
if (name == "EOB")
return H265NALU::EOB_NUT;
// There're lots of H265 NALU I/P frames, return one from all possible types
// for testing purpose, since we only care about the order of I/P frames and
// other non VCL NALU.
if (name == "P")
return H265NALU::TRAIL_N;
if (name == "I")
return H265NALU::IDR_W_RADL;
CHECK(false) << "Unexpected name: " << name;
return H265NALU::EOB_NUT;
}
template <>
void WriteNALUType<H265NALU>(std::vector<uint8_t>* buffer,
const std::string& nal_unit_type) {
uint8_t header1 = 0;
uint8_t header2 = 0;
uint8_t type = static_cast<uint8_t>(H265StringToNALUType(nal_unit_type));
DCHECK_LT(type, 64);
header1 |= (type << 1);
buffer->push_back(header1);
buffer->push_back(header2);
}
#endif // BUILDFLAG(ENABLE_HEVC_DEMUXING)
template <typename T>
void WriteStartCodeAndNALUType(std::vector<uint8_t>* buffer,
const std::string& nal_unit_type) {
buffer->push_back(0x00);
buffer->push_back(0x00);
buffer->push_back(0x00);
buffer->push_back(0x01);
WriteNALUType<T>(buffer, nal_unit_type);
}
template <typename T>
void StringToAnnexB(const std::string& str,
std::vector<uint8_t>* buffer,
std::vector<SubsampleEntry>* subsamples) {
DCHECK(!str.empty());
std::vector<std::string> subsample_specs = base::SplitString(
str, " ", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
DCHECK_GT(subsample_specs.size(), 0u);
buffer->clear();
for (size_t i = 0; i < subsample_specs.size(); ++i) {
SubsampleEntry entry;
size_t start = buffer->size();
std::vector<std::string> subsample_nalus =
base::SplitString(subsample_specs[i], ",", base::KEEP_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
DCHECK_GT(subsample_nalus.size(), 0u);
for (size_t j = 0; j < subsample_nalus.size(); ++j) {
WriteStartCodeAndNALUType<T>(buffer, subsample_nalus[j]);
// Write junk for the payload since the current code doesn't
// actually look at it.
buffer->push_back(0x32);
buffer->push_back(0x12);
buffer->push_back(0x67);
}
entry.clear_bytes = buffer->size() - start;
if (subsamples) {
// Simulate the encrypted bits containing something that looks
// like a SPS NALU.
WriteStartCodeAndNALUType<T>(buffer, "SPS");
}
entry.cypher_bytes = buffer->size() - start - entry.clear_bytes;
if (subsamples) {
subsamples->push_back(entry);
}
}
}
} // namespace
void AvcStringToAnnexB(const std::string& str,
std::vector<uint8_t>* buffer,
std::vector<SubsampleEntry>* subsamples) {
StringToAnnexB<H264NALU>(str, buffer, subsamples);
}
#if BUILDFLAG(ENABLE_HEVC_DEMUXING)
void HevcStringToAnnexB(const std::string& str,
std::vector<uint8_t>* buffer,
std::vector<SubsampleEntry>* subsamples) {
StringToAnnexB<H265NALU>(str, buffer, subsamples);
}
#endif // BUILDFLAG(ENABLE_HEVC_DEMUXING)
bool AnalysesMatch(const BitstreamConverter::AnalysisResult& r1,
const BitstreamConverter::AnalysisResult& r2) {
return r1.is_conformant == r2.is_conformant &&
r1.is_keyframe == r2.is_keyframe;
}
} // namespace mp4
} // namespace media
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef MEDIA_FORMATS_MP4_NALU_TEST_HELPER_H_
#define MEDIA_FORMATS_MP4_NALU_TEST_HELPER_H_
#include <string>
#include <vector>
#include "media/base/subsample_entry.h"
#include "media/formats/mp4/bitstream_converter.h"
#include "media/media_buildflags.h"
namespace media {
namespace mp4 {
// Input string should be one or more NALU types separated with spaces or
// commas. NALU grouped together and separated by commas are placed into the
// same subsample, NALU groups separated by spaces are placed into separate
// subsamples.
// For example: in AVC, input string "SPS PPS I" produces Annex B buffer
// containing SPS, PPS and I NALUs, each in a separate subsample. While input
// string "SPS,PPS I" produces Annex B buffer where the first subsample contains
// SPS and PPS NALUs and the second subsample contains the I-slice NALU.
// The output buffer will contain a valid-looking Annex B (it's valid-looking in
// the sense that it has start codes and correct NALU types, but the actual NALU
// payload is junk).
void AvcStringToAnnexB(const std::string& str,
std::vector<uint8_t>* buffer,
std::vector<SubsampleEntry>* subsamples);
#if BUILDFLAG(ENABLE_HEVC_DEMUXING)
void HevcStringToAnnexB(const std::string& str,
std::vector<uint8_t>* buffer,
std::vector<SubsampleEntry>* subsamples);
#endif // BUILDFLAG(ENABLE_HEVC_DEMUXING)
// Helper to compare two results of AVC::Analyze().
bool AnalysesMatch(const BitstreamConverter::AnalysisResult& r1,
const BitstreamConverter::AnalysisResult& r2);
} // namespace mp4
} // namespace media
#endif // MEDIA_FORMATS_MP4_NALU_TEST_HELPER_H_
......@@ -926,6 +926,25 @@ HEVC video stream in fragmented MP4 container, generated with
ffmpeg -i bear-320x240.webm -c:v libx265 -an -movflags faststart+frag_keyframe bear-320x240-v_frag-hevc.mp4
```
#### bear-320x240-v-2frames_frag-hevc.mp4
HEVC video stream in fragmented MP4 container, including the first 2 frames, generated with
```
ffmpeg -i bear-320x240.webm -c:v libx265 -an -movflags frag_keyframe+empty_moov+default_base_moof \
-vframes 2 bear-320x240-v-2frames_frag-hevc.mp4
```
#### bear-320x240-v-2frames-keyframe-is-non-sync-sample_frag-hevc.mp4
This is bear-320x240-v-2frames_frag-hevc.mp4, with manually updated
trun.first_sample_flags: s/0x02000000/0x01010000 (first frame is
non-sync-sample, depends on another frame, mismatches compressed h265 first
frame's keyframe-ness).
#### bear-320x240-v-2frames-nonkeyframe-is-sync-sample_frag-hevc.mp4
This is bear-320x240-v-2frames_frag-hevc.mp4, with manually updated
tfhd.default_sample_flags: s/0x01010000/0x02000000 (second frame is sync-sample,
doesn't depend on other frames, mismatches compressed h265 second frame's
nonkeyframe-ness).
### Multi-track MP4 file
(c) copyright 2008, Blender Foundation / www.bigbuckbunny.org
......
......@@ -79,6 +79,22 @@ struct MEDIA_EXPORT H265NALU {
RSV_NVCL45 = 45,
RSV_NVCL46 = 46,
RSV_NVCL47 = 47,
UNSPEC48 = 48,
UNSPEC49 = 49,
UNSPEC50 = 50,
UNSPEC51 = 51,
UNSPEC52 = 52,
UNSPEC53 = 53,
UNSPEC54 = 54,
UNSPEC55 = 55,
UNSPEC56 = 56,
UNSPEC57 = 57,
UNSPEC58 = 58,
UNSPEC59 = 59,
UNSPEC60 = 60,
UNSPEC61 = 61,
UNSPEC62 = 62,
UNSPEC63 = 63,
};
// After (without) start code; we don't own the underlying memory
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment