diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index eef0d15..55ef983 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -46,6 +46,7 @@ set(MOONFIRE_NVR_SRCS crypto.cc ffmpeg.cc filesystem.cc + h264.cc http.cc moonfire-nvr.cc profiler.cc @@ -65,7 +66,7 @@ install_programs(/bin FILES moonfire-nvr) include_directories(${GTest_INCLUDE_DIR}) include_directories(${GMock_INCLUDE_DIR}) -foreach(test coding crypto http moonfire-nvr recording sqlite string) +foreach(test coding crypto h264 http moonfire-nvr recording sqlite string) add_executable(${test}-test ${test}-test.cc testutil.cc) target_link_libraries(${test}-test GTest GMock moonfire-nvr-lib) add_test(NAME ${test}-test diff --git a/src/coding.h b/src/coding.h index 5dbffcb..f31efe0 100644 --- a/src/coding.h +++ b/src/coding.h @@ -134,6 +134,16 @@ inline int32_t Unzigzag32(uint32_t in) { return (in >> 1) ^ -static_cast(in & 1); } +inline void AppendU16(uint16_t in, std::string *out) { + uint16_t net = ToNetworkU16(in); + out->append(reinterpret_cast(&net), sizeof(uint16_t)); +} + +inline void AppendU32(uint32_t in, std::string *out) { + uint32_t net = ToNetworkU32(in); + out->append(reinterpret_cast(&net), sizeof(uint32_t)); +} + } // namespace moonfire_nvr #endif // MOONFIRE_NVR_CODING_H diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..72cbd8a --- /dev/null +++ b/src/common.h @@ -0,0 +1,46 @@ +// This file is part of Moonfire NVR, a security camera network video recorder. +// Copyright (C) 2016 Scott Lamb +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// In addition, as a special exception, the copyright holders give +// permission to link the code of portions of this program with the +// OpenSSL library under certain conditions as described in each +// individual source file, and distribute linked combinations including +// the two. +// +// You must obey the GNU General Public License in all respects for all +// of the code used other than OpenSSL. If you modify file(s) with this +// exception, you may extend this exception to your version of the +// file(s), but you are not obligated to do so. If you do not wish to do +// so, delete this exception statement from your version. If you delete +// this exception statement from all source files in the program, then +// also delete it here. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// common.h: basic enums/defines/whatever. + +#ifndef MOONFIRE_NVR_COMMON_H +#define MOONFIRE_NVR_COMMON_H + +namespace moonfire_nvr { + +// Return value for *ForEach callbacks. +enum class IterationControl { + kContinue, // indicates the caller should proceed with the loop. + kBreak // indicates the caller should terminate the loop with success. +}; + +} // namespace moonfire_nvr + +#endif // MOONFIRE_NVR_COMMON_H diff --git a/src/filesystem.h b/src/filesystem.h index 062fe23..2455f88 100644 --- a/src/filesystem.h +++ b/src/filesystem.h @@ -47,13 +47,9 @@ #include #include -namespace moonfire_nvr { +#include "common.h" -// Return value for *ForEach callbacks. -enum class IterationControl { - kContinue, // indicates the caller should proceed with the loop. - kBreak // indicates the caller should terminate the loop with success. -}; +namespace moonfire_nvr { // Represents an open file. All methods but Close() are thread-safe. class File { diff --git a/src/h264-test.cc b/src/h264-test.cc new file mode 100644 index 0000000..5cb72f6 --- /dev/null +++ b/src/h264-test.cc @@ -0,0 +1,97 @@ +// This file is part of Moonfire NVR, a security camera network video recorder. +// Copyright (C) 2016 Scott Lamb +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// In addition, as a special exception, the copyright holders give +// permission to link the code of portions of this program with the +// OpenSSL library under certain conditions as described in each +// individual source file, and distribute linked combinations including +// the two. +// +// You must obey the GNU General Public License in all respects for all +// of the code used other than OpenSSL. If you modify file(s) with this +// exception, you may extend this exception to your version of the +// file(s), but you are not obligated to do so. If you do not wish to do +// so, delete this exception statement from your version. If you delete +// this exception statement from all source files in the program, then +// also delete it here. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// h264-test.cc: tests of the h264.h interface. + +#include +#include +#include +#include + +#include "h264.h" +#include "string.h" + +DECLARE_bool(alsologtostderr); + +namespace moonfire_nvr { +namespace { + +const uint8_t kTestInput[] = { + 0x00, 0x00, 0x00, 0x01, 0x67, 0x4d, 0x00, 0x1f, 0x9a, 0x66, 0x02, 0x80, + 0x2d, 0xff, 0x35, 0x01, 0x01, 0x01, 0x40, 0x00, 0x00, 0xfa, 0x00, 0x00, + 0x1d, 0x4c, 0x01, 0x00, 0x00, 0x00, 0x01, 0x68, 0xee, 0x3c, 0x80}; + +TEST(H264Test, DecodeOnly) { + std::vector nal_units_hexed; + re2::StringPiece test_input(reinterpret_cast(kTestInput), + sizeof(kTestInput)); + internal::NalUnitFunction fn = [&nal_units_hexed](re2::StringPiece nal_unit) { + nal_units_hexed.push_back(ToHex(nal_unit)); + return IterationControl::kContinue; + }; + std::string error_message; + ASSERT_TRUE(internal::DecodeH264AnnexB(test_input, fn, &error_message)) + << error_message; + EXPECT_THAT(nal_units_hexed, + testing::ElementsAre("67 4d 00 1f 9a 66 02 80 2d ff 35 01 01 01 " + "40 00 00 fa 00 00 1d 4c 01", + "68 ee 3c 80")); +} + +TEST(H264Test, SampleData) { + const uint8_t kTestOutput[] = {0x01, 0x4d, 0x00, 0x1f, 0xff, 0xe1, 0x00, 0x17, + 0x67, 0x4d, 0x00, 0x1f, 0x9a, 0x66, 0x02, 0x80, + 0x2d, 0xff, 0x35, 0x01, 0x01, 0x01, 0x40, 0x00, + 0x00, 0xfa, 0x00, 0x00, 0x1d, 0x4c, 0x01, 0x01, + 0x00, 0x04, 0x68, 0xee, 0x3c, 0x80}; + + re2::StringPiece test_input(reinterpret_cast(kTestInput), + sizeof(kTestInput)); + re2::StringPiece test_output(reinterpret_cast(kTestOutput), + sizeof(kTestOutput)); + std::string avc_decoder_config; + std::string error_message; + ASSERT_TRUE( + ParseH264ExtraData(test_input, &avc_decoder_config, &error_message)) + << error_message; + + EXPECT_EQ(ToHex(test_output), ToHex(avc_decoder_config)); +} + +} // namespace +} // namespace moonfire_nvr + +int main(int argc, char **argv) { + FLAGS_alsologtostderr = true; + google::ParseCommandLineFlags(&argc, &argv, true); + testing::InitGoogleTest(&argc, argv); + google::InitGoogleLogging(argv[0]); + return RUN_ALL_TESTS(); +} diff --git a/src/h264.cc b/src/h264.cc new file mode 100644 index 0000000..dd454ef --- /dev/null +++ b/src/h264.cc @@ -0,0 +1,170 @@ +// This file is part of Moonfire NVR, a security camera network video recorder. +// Copyright (C) 2016 Scott Lamb +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// In addition, as a special exception, the copyright holders give +// permission to link the code of portions of this program with the +// OpenSSL library under certain conditions as described in each +// individual source file, and distribute linked combinations including +// the two. +// +// You must obey the GNU General Public License in all respects for all +// of the code used other than OpenSSL. If you modify file(s) with this +// exception, you may extend this exception to your version of the +// file(s), but you are not obligated to do so. If you do not wish to do +// so, delete this exception statement from your version. If you delete +// this exception statement from all source files in the program, then +// also delete it here. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// h264.cc: see h264.h. + +#include "h264.h" + +#include + +#include "coding.h" +#include "string.h" + +namespace moonfire_nvr { + +namespace { + +// See ISO/IEC 14496-10 section 7.1. +const int kNalUnitSeqParameterSet = 7; +const int kNalUnitPicParameterSet = 8; + +} // namespace + +// See T-REC-H.264-201003-S||PDF-E.PDF page 325 for byte stream NAL unit +// syntax + +// See page 42 for nal_unit. + +namespace internal { + +// See ISO/IEC 14496-10 section B.2: Byte stream NAL unit decoding process. +// This is a relatively simple, unoptimized implementation given that it +// only processes a few dozen bytes per recording. +bool DecodeH264AnnexB(re2::StringPiece data, NalUnitFunction process_nal_unit, + std::string *error_message) { + static const RE2 kStartCode("(\\x00{2,}\\x01)"); + + if (!RE2::Consume(&data, kStartCode)) { + *error_message = "stream does not start with Annex B start code"; + return false; + } + + while (!data.empty()) { + // Now at the start of a NAL unit. Find the end. + re2::StringPiece next_start; + re2::StringPiece this_nal = data; + if (RE2::FindAndConsume(&data, kStartCode, &next_start)) { + // It ends where another start code is found. + this_nal = re2::StringPiece(this_nal.data(), + next_start.data() - this_nal.data()); + } else { + // It ends at the end of |data|. |this_nal| is already correct. + // Set |data| to be empty so the while loop exits after this iteration. + data = re2::StringPiece(); + } + + if (this_nal.empty()) { + *error_message = "NAL unit can't be empty"; + return false; + } + + if (process_nal_unit(this_nal) == IterationControl::kBreak) { + break; + } + } + return true; +} + +} // namespace internal + +bool ParseH264ExtraData(re2::StringPiece extra_data, + std::string *avc_decoder_config, + std::string *error_message) { + std::string sps; + std::string pps; + bool ok = true; + internal::NalUnitFunction fn = [&ok, &sps, &pps, + error_message](re2::StringPiece nal_unit) { + uint8_t nal_type = nal_unit[0] & 0x1F; // bottom 5 bits of first byte. + switch (nal_type) { + case kNalUnitSeqParameterSet: + sps = nal_unit.as_string(); + break; + case kNalUnitPicParameterSet: + pps = nal_unit.as_string(); + break; + default: + *error_message = + StrCat("Expected only SPS and PPS; got type ", nal_type); + ok = false; + return IterationControl::kBreak; + } + return IterationControl::kContinue; + }; + if (!internal::DecodeH264AnnexB(extra_data, fn, error_message) || !ok) { + return false; + } + if (sps.empty() || pps.empty()) { + *error_message = "SPS and PPS must be specified."; + return false; + } + if (sps.size() < 4) { + *error_message = "SPS record is too short."; + return false; + } + if (sps.size() > std::numeric_limits::max() || + pps.size() > std::numeric_limits::max()) { + *error_message = "SPS or PPS is too long."; + return false; + } + + // The beginning of the AVCDecoderConfiguration takes a few values from + // the SPS (ISO/IEC 14496-10 section 7.3.2.1.1). One caveat: that section + // defines the syntax in terms of RBSP, not NAL. The difference is the + // escaping of 00 00 01 and 00 00 02; see notes about + // "emulation_prevention_three_byte" in ISO/IEC 14496-10 section 7.4. + // It looks like 00 is not a valid value of profile_idc, so this distinction + // shouldn't be relevant here. And ffmpeg seems to ignore it. + avc_decoder_config->clear(); + avc_decoder_config->push_back(1); // configurationVersion + avc_decoder_config->push_back(sps[1]); // profile_idc -> AVCProfileIndication + avc_decoder_config->push_back(sps[2]); // ... -> profile_compatibility + avc_decoder_config->push_back(sps[3]); // level_idc -> AVCLevelIndication + + // Hardcode lengthSizeMinusOne to 3. This needs to match what ffmpeg uses + // when generating AVCParameterSamples (ISO/IEC 14496-15 section 5.3.2). + // There doesn't seem to be a clean way to get this from ffmpeg, but it's + // always 3. + avc_decoder_config->push_back(static_cast(0xff)); + + // Only support one SPS and PPS. + // ffmpeg's ff_isom_write_avcc has the same limitation, so it's probably fine. + // This next byte is a reserved 0b111 + a 5-bit # of SPSs (1). + avc_decoder_config->push_back(static_cast(0xe1)); + AppendU16(sps.size(), avc_decoder_config); + avc_decoder_config->append(sps.data(), sps.size()); + avc_decoder_config->push_back(1); // # of PPSs. + AppendU16(pps.size(), avc_decoder_config); + avc_decoder_config->append(pps.data(), pps.size()); + + return true; +} + +} // namespace moonfire_nvr diff --git a/src/h264.h b/src/h264.h new file mode 100644 index 0000000..f2ba81a --- /dev/null +++ b/src/h264.h @@ -0,0 +1,83 @@ +// This file is part of Moonfire NVR, a security camera network video recorder. +// Copyright (C) 2016 Scott Lamb +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// In addition, as a special exception, the copyright holders give +// permission to link the code of portions of this program with the +// OpenSSL library under certain conditions as described in each +// individual source file, and distribute linked combinations including +// the two. +// +// You must obey the GNU General Public License in all respects for all +// of the code used other than OpenSSL. If you modify file(s) with this +// exception, you may extend this exception to your version of the +// file(s), but you are not obligated to do so. If you do not wish to do +// so, delete this exception statement from your version. If you delete +// this exception statement from all source files in the program, then +// also delete it here. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// h264.h: H.264 decoding. For the most part, Moonfire NVR does not try to +// understand the video codec. There's one exception. It must construct the +// .mp4 sample description table, and for AVC, this includes the ISO/IEC +// 14496-15 section 5.2.4.1 AVCDecoderConfigurationRecord. ffmpeg supplies (as +// "extra data") an ISO/IEC 14496-10 Annex B byte stream containing SPS +// (sequence parameter set) and PPS (picture parameter set) NAL units from +// which this can be constructed. +// +// ffmpeg of course also has logic for converting "extra data" to the +// AVCDecoderConfigurationRecord, but unfortunately it is not exposed except +// through ffmpeg's own generated .mp4 file. Extracting just this part of +// their .mp4 files would be more trouble than it's worth. + +#ifndef MOONFIRE_NVR_H264_H +#define MOONFIRE_NVR_H264_H + +#include +#include + +#include + +#include "common.h" + +namespace moonfire_nvr { + +namespace internal { + +using NalUnitFunction = + std::function; + +// Decode a H.264 Annex B byte stream into NAL units. +// For ParseH264ExtraData; exposed for testing. +// Calls |process_nal_unit| for each NAL unit in the byte stream. +// +// Note: this won't spot all invalid byte streams. For example, several 0x00s +// not followed by a 0x01 will just be considered part of a NAL unit rather +// than proof of an invalid stream. +bool DecodeH264AnnexB(re2::StringPiece data, NalUnitFunction process_nal_unit, + std::string *error_message); + +} // namespace + +// Parse H.264 "extra data" (as supplied by ffmpeg, an Annex B byte stream +// containing SPS and PPS NAL units). On success, fills |avc_decoder_config| +// with an AVCDecoderConfigurationRecord as in ISO/IEC 14496-15 section +// 5.2.4.1. +bool ParseH264ExtraData(re2::StringPiece extra_data, + std::string *avc_decoder_config, + std::string *error_message); + +} // namespace moonfire_nvr + +#endif // MOONFIRE_NVR_H264_H