mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2025-01-26 06:03:18 -05:00
Add logic to create an AVCDecoderConfiguration.
This commit is contained in:
parent
dca9642c51
commit
c294d751b6
@ -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
|
||||
|
10
src/coding.h
10
src/coding.h
@ -134,6 +134,16 @@ inline int32_t Unzigzag32(uint32_t in) {
|
||||
return (in >> 1) ^ -static_cast<int32_t>(in & 1);
|
||||
}
|
||||
|
||||
inline void AppendU16(uint16_t in, std::string *out) {
|
||||
uint16_t net = ToNetworkU16(in);
|
||||
out->append(reinterpret_cast<const char *>(&net), sizeof(uint16_t));
|
||||
}
|
||||
|
||||
inline void AppendU32(uint32_t in, std::string *out) {
|
||||
uint32_t net = ToNetworkU32(in);
|
||||
out->append(reinterpret_cast<const char *>(&net), sizeof(uint32_t));
|
||||
}
|
||||
|
||||
} // namespace moonfire_nvr
|
||||
|
||||
#endif // MOONFIRE_NVR_CODING_H
|
||||
|
46
src/common.h
Normal file
46
src/common.h
Normal file
@ -0,0 +1,46 @@
|
||||
// This file is part of Moonfire NVR, a security camera network video recorder.
|
||||
// Copyright (C) 2016 Scott Lamb <slamb@slamb.org>
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// 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
|
@ -47,13 +47,9 @@
|
||||
#include <glog/logging.h>
|
||||
#include <re2/stringpiece.h>
|
||||
|
||||
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 {
|
||||
|
97
src/h264-test.cc
Normal file
97
src/h264-test.cc
Normal file
@ -0,0 +1,97 @@
|
||||
// This file is part of Moonfire NVR, a security camera network video recorder.
|
||||
// Copyright (C) 2016 Scott Lamb <slamb@slamb.org>
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// h264-test.cc: tests of the h264.h interface.
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include <glog/logging.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#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<std::string> nal_units_hexed;
|
||||
re2::StringPiece test_input(reinterpret_cast<const char *>(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<const char *>(kTestInput),
|
||||
sizeof(kTestInput));
|
||||
re2::StringPiece test_output(reinterpret_cast<const char *>(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();
|
||||
}
|
170
src/h264.cc
Normal file
170
src/h264.cc
Normal file
@ -0,0 +1,170 @@
|
||||
// This file is part of Moonfire NVR, a security camera network video recorder.
|
||||
// Copyright (C) 2016 Scott Lamb <slamb@slamb.org>
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// h264.cc: see h264.h.
|
||||
|
||||
#include "h264.h"
|
||||
|
||||
#include <re2/re2.h>
|
||||
|
||||
#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<uint16_t>::max() ||
|
||||
pps.size() > std::numeric_limits<uint16_t>::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<char>(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<char>(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
|
83
src/h264.h
Normal file
83
src/h264.h
Normal file
@ -0,0 +1,83 @@
|
||||
// This file is part of Moonfire NVR, a security camera network video recorder.
|
||||
// Copyright (C) 2016 Scott Lamb <slamb@slamb.org>
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// 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 <functional>
|
||||
#include <string>
|
||||
|
||||
#include <re2/stringpiece.h>
|
||||
|
||||
#include "common.h"
|
||||
|
||||
namespace moonfire_nvr {
|
||||
|
||||
namespace internal {
|
||||
|
||||
using NalUnitFunction =
|
||||
std::function<IterationControl(re2::StringPiece nal_unit)>;
|
||||
|
||||
// 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
|
Loading…
x
Reference in New Issue
Block a user