blob: 96a8a971d6fcc2886624f04ba1f7dfe55acc4748 [file] [log] [blame]
// Copyright 2014 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 <string.h>
#include "base/basictypes.h"
#include "base/strings/string_util.h"
#include "media/base/decrypt_config.h"
#include "media/base/stream_parser_buffer.h"
#include "media/filters/h264_parser.h"
#include "media/formats/mp4/avc.h"
#include "media/formats/mp4/box_definitions.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media {
namespace mp4 {
static const uint8 kNALU1[] = { 0x01, 0x02, 0x03 };
static const uint8 kNALU2[] = { 0x04, 0x05, 0x06, 0x07 };
static const uint8 kExpected[] = {
0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x03,
0x00, 0x00, 0x00, 0x01, 0x04, 0x05, 0x06, 0x07 };
static const uint8 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 == "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:
return "P";
case H264NALU::kSliceDataA:
return "SDA";
case H264NALU::kSliceDataB:
return "SDB";
case H264NALU::kSliceDataC:
return "SDC";
case H264NALU::kIDRSlice:
return "I";
case H264NALU::kSEIMessage:
return "SEI";
case H264NALU::kSPS:
return "SPS";
case H264NALU::kSPSExt:
return "SPSExt";
case H264NALU::kPPS:
return "PPS";
case H264NALU::kAUD:
return "AUD";
case H264NALU::kEOSeq:
return "EOSeq";
case H264NALU::kEOStream:
return "EOStr";
case H264NALU::kFiller:
return "FILL";
case H264NALU::kReserved14:
return "R14";
case H264NALU::kUnspecified:
case H264NALU::kReserved15:
case H264NALU::kReserved16:
case H264NALU::kReserved17:
case H264NALU::kReserved18:
case H264NALU::kCodedSliceAux:
case H264NALU::kCodedSliceExtension:
CHECK(false) << "Unexpected type: " << type;
break;
};
return "UnsupportedType";
}
static void WriteStartCodeAndNALUType(std::vector<uint8>* 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));
}
void StringToAnnexB(const std::string& str, std::vector<uint8>* buffer,
std::vector<SubsampleEntry>* subsamples) {
DCHECK(!str.empty());
std::vector<std::string> tokens;
EXPECT_GT(Tokenize(str, " ", &tokens), 0u);
buffer->clear();
for (size_t i = 0; i < tokens.size(); ++i) {
SubsampleEntry entry;
size_t start = buffer->size();
WriteStartCodeAndNALUType(buffer, tokens[i]);
entry.clear_bytes = buffer->size() - start;
// 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);
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);
}
}
}
std::string AnnexBToString(const std::vector<uint8>& buffer,
const std::vector<SubsampleEntry>& subsamples) {
std::stringstream ss;
H264Parser parser;
parser.SetEncryptedStream(&buffer[0], buffer.size(), subsamples);
H264NALU nalu;
bool first = true;
while (parser.AdvanceToNextNALU(&nalu) == H264Parser::kOk) {
if (!first)
ss << " ";
else
first = false;
ss << NALUTypeToString(nalu.nal_unit_type);
}
return ss.str();
}
class AVCConversionTest : public testing::TestWithParam<int> {
protected:
void WriteLength(int length_size, int length, std::vector<uint8>* buf) {
DCHECK_GE(length, 0);
DCHECK_LE(length, 255);
for (int i = 1; i < length_size; i++)
buf->push_back(0);
buf->push_back(length);
}
void MakeInputForLength(int length_size, std::vector<uint8>* buf) {
buf->clear();
WriteLength(length_size, sizeof(kNALU1), buf);
buf->insert(buf->end(), kNALU1, kNALU1 + sizeof(kNALU1));
WriteLength(length_size, sizeof(kNALU2), buf);
buf->insert(buf->end(), kNALU2, kNALU2 + sizeof(kNALU2));
}
};
TEST_P(AVCConversionTest, ParseCorrectly) {
std::vector<uint8> buf;
std::vector<SubsampleEntry> subsamples;
MakeInputForLength(GetParam(), &buf);
EXPECT_TRUE(AVC::ConvertFrameToAnnexB(GetParam(), &buf));
EXPECT_TRUE(AVC::IsValidAnnexB(buf, subsamples));
EXPECT_EQ(buf.size(), sizeof(kExpected));
EXPECT_EQ(0, memcmp(kExpected, &buf[0], sizeof(kExpected)));
EXPECT_EQ("P SDC", AnnexBToString(buf, subsamples));
}
// Intentionally write NALU sizes that are larger than the buffer.
TEST_P(AVCConversionTest, NALUSizeTooLarge) {
std::vector<uint8> buf;
WriteLength(GetParam(), 10 * sizeof(kNALU1), &buf);
buf.insert(buf.end(), kNALU1, kNALU1 + sizeof(kNALU1));
EXPECT_FALSE(AVC::ConvertFrameToAnnexB(GetParam(), &buf));
}
TEST_P(AVCConversionTest, NALUSizeIsZero) {
std::vector<uint8> buf;
WriteLength(GetParam(), 0, &buf);
WriteLength(GetParam(), sizeof(kNALU1), &buf);
buf.insert(buf.end(), kNALU1, kNALU1 + sizeof(kNALU1));
WriteLength(GetParam(), 0, &buf);
WriteLength(GetParam(), sizeof(kNALU2), &buf);
buf.insert(buf.end(), kNALU2, kNALU2 + sizeof(kNALU2));
EXPECT_FALSE(AVC::ConvertFrameToAnnexB(GetParam(), &buf));
}
TEST_P(AVCConversionTest, ParsePartial) {
std::vector<uint8> buf;
MakeInputForLength(GetParam(), &buf);
buf.pop_back();
EXPECT_FALSE(AVC::ConvertFrameToAnnexB(GetParam(), &buf));
// This tests a buffer ending in the middle of a NAL length. For length size
// of one, this can't happen, so we skip that case.
if (GetParam() != 1) {
MakeInputForLength(GetParam(), &buf);
buf.erase(buf.end() - (sizeof(kNALU2) + 1), buf.end());
EXPECT_FALSE(AVC::ConvertFrameToAnnexB(GetParam(), &buf));
}
}
TEST_P(AVCConversionTest, ParseEmpty) {
std::vector<uint8> buf;
EXPECT_TRUE(AVC::ConvertFrameToAnnexB(GetParam(), &buf));
EXPECT_EQ(0u, buf.size());
}
INSTANTIATE_TEST_CASE_P(AVCConversionTestValues,
AVCConversionTest,
::testing::Values(1, 2, 4));
TEST_F(AVCConversionTest, ConvertConfigToAnnexB) {
AVCDecoderConfigurationRecord avc_config;
avc_config.sps_list.resize(2);
avc_config.sps_list[0].push_back(0x67);
avc_config.sps_list[0].push_back(0x12);
avc_config.sps_list[1].push_back(0x67);
avc_config.sps_list[1].push_back(0x34);
avc_config.pps_list.resize(1);
avc_config.pps_list[0].push_back(0x68);
avc_config.pps_list[0].push_back(0x56);
avc_config.pps_list[0].push_back(0x78);
std::vector<uint8> buf;
std::vector<SubsampleEntry> subsamples;
EXPECT_TRUE(AVC::ConvertConfigToAnnexB(avc_config, &buf, &subsamples));
EXPECT_EQ(0, memcmp(kExpectedParamSets, &buf[0],
sizeof(kExpectedParamSets)));
EXPECT_EQ("SPS SPS PPS", AnnexBToString(buf, subsamples));
}
// Verify that we can round trip string -> Annex B -> string.
TEST_F(AVCConversionTest, StringConversionFunctions) {
std::string str =
"AUD SPS SPSExt SPS PPS SEI SEI R14 I P FILL EOSeq EOStr";
std::vector<uint8> buf;
std::vector<SubsampleEntry> subsamples;
StringToAnnexB(str, &buf, &subsamples);
EXPECT_TRUE(AVC::IsValidAnnexB(buf, subsamples));
EXPECT_EQ(str, AnnexBToString(buf, subsamples));
}
TEST_F(AVCConversionTest, ValidAnnexBConstructs) {
const char* test_cases[] = {
"I",
"I I I I",
"AUD I",
"AUD SPS PPS I",
"I EOSeq",
"I EOSeq EOStr",
"I EOStr",
"P",
"P P P P",
"AUD SPS PPS P",
"SEI SEI I",
"SEI SEI R14 I",
"SPS SPSExt SPS PPS I P",
"R14 SEI I",
};
for (size_t i = 0; i < arraysize(test_cases); ++i) {
std::vector<uint8> buf;
std::vector<SubsampleEntry> subsamples;
StringToAnnexB(test_cases[i], &buf, NULL);
EXPECT_TRUE(AVC::IsValidAnnexB(buf, subsamples)) << "'" << test_cases[i]
<< "' failed";
}
}
TEST_F(AVCConversionTest, InvalidAnnexBConstructs) {
static const char* test_cases[] = {
"AUD", // No VCL present.
"SPS PPS", // No VCL present.
"SPS PPS AUD I", // Parameter sets must come after AUD.
"SPSExt SPS P", // SPS must come before SPSExt.
"SPS PPS SPSExt P", // SPSExt must follow an SPS.
"EOSeq", // EOSeq must come after a VCL.
"EOStr", // EOStr must come after a VCL.
"I EOStr EOSeq", // EOSeq must come before EOStr.
"I R14", // Reserved14-18 must come before first VCL.
"I SEI", // SEI must come before first VCL.
"P SPS P", // SPS after first VCL would indicate a new access unit.
};
for (size_t i = 0; i < arraysize(test_cases); ++i) {
std::vector<uint8> buf;
std::vector<SubsampleEntry> subsamples;
StringToAnnexB(test_cases[i], &buf, NULL);
EXPECT_FALSE(AVC::IsValidAnnexB(buf, subsamples)) << "'" << test_cases[i]
<< "' failed";
}
}
typedef struct {
const char* input;
const char* expected;
} InsertTestCases;
TEST_F(AVCConversionTest, InsertParamSetsAnnexB) {
static const InsertTestCases test_cases[] = {
{ "I", "SPS SPS PPS I" },
{ "AUD I", "AUD SPS SPS PPS I" },
// Cases where param sets in |avc_config| are placed before
// the existing ones.
{ "SPS PPS I", "SPS SPS PPS SPS PPS I" },
{ "AUD SPS PPS I", "AUD SPS SPS PPS SPS PPS I" }, // Note: params placed
// after AUD.
};
AVCDecoderConfigurationRecord avc_config;
avc_config.sps_list.resize(2);
avc_config.sps_list[0].push_back(0x67);
avc_config.sps_list[0].push_back(0x12);
avc_config.sps_list[1].push_back(0x67);
avc_config.sps_list[1].push_back(0x34);
avc_config.pps_list.resize(1);
avc_config.pps_list[0].push_back(0x68);
avc_config.pps_list[0].push_back(0x56);
avc_config.pps_list[0].push_back(0x78);
for (size_t i = 0; i < arraysize(test_cases); ++i) {
std::vector<uint8> buf;
std::vector<SubsampleEntry> subsamples;
StringToAnnexB(test_cases[i].input, &buf, &subsamples);
EXPECT_TRUE(AVC::InsertParamSetsAnnexB(avc_config, &buf, &subsamples))
<< "'" << test_cases[i].input << "' insert failed.";
EXPECT_TRUE(AVC::IsValidAnnexB(buf, subsamples))
<< "'" << test_cases[i].input << "' created invalid AnnexB.";
EXPECT_EQ(test_cases[i].expected, AnnexBToString(buf, subsamples))
<< "'" << test_cases[i].input << "' generated unexpected output.";
}
}
} // namespace mp4
} // namespace media