diff --git a/src/mp4-test.cc b/src/mp4-test.cc
index 2ecd6f4..3678427 100644
--- a/src/mp4-test.cc
+++ b/src/mp4-test.cc
@@ -28,7 +28,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
-// mp4_test.cc: tests of the mp4.h interface.
+// mp4-test.cc: tests of the mp4.h interface.
#include
#include
@@ -83,79 +83,111 @@ std::string Digest(const FileSlice *slice) {
return ::moonfire_nvr::ToHex(digest->Finalize());
}
-TEST(Mp4SampleTablePiecesTest, Stts) {
+TEST(Mp4SampleTablePiecesTest, AllSyncFrames) {
+ Recording recording;
SampleIndexEncoder encoder;
+ encoder.Init(&recording, 42);
for (int i = 1; i <= 5; ++i) {
- encoder.AddSample(i, 2 * i, true);
+ int64_t sample_duration_90k = 2 * i;
+ int64_t sample_bytes = 3 * i;
+ encoder.AddSample(sample_duration_90k, sample_bytes, true);
}
Mp4SampleTablePieces pieces;
std::string error_message;
- // Time range [1, 1 + 2 + 3 + 4) means the 2nd, 3rd, 4th samples should be
+ // Time range [2, 2 + 4 + 6 + 8) means the 2nd, 3rd, 4th samples should be
// included.
- ASSERT_TRUE(
- pieces.Init(encoder.data(), 2, 10, 1, 1 + 2 + 3 + 4, &error_message))
+ ASSERT_TRUE(pieces.Init(&recording, 2, 10, 2, 2 + 4 + 6 + 8, &error_message))
<< error_message;
+
EXPECT_EQ(3, pieces.stts_entry_count());
- const char kExpectedEntries[] =
- "00 00 00 01 00 00 00 02 "
- "00 00 00 01 00 00 00 03 "
- "00 00 00 01 00 00 00 04";
- EXPECT_EQ(kExpectedEntries, ToHex(pieces.stts_entries(), true));
-}
+ const char kExpectedStts[] =
+ "00 00 00 01 00 00 00 04 " // run length / timestamps.
+ "00 00 00 01 00 00 00 06 "
+ "00 00 00 01 00 00 00 08";
+ EXPECT_EQ(kExpectedStts, ToHex(pieces.stts_entries(), true));
-TEST(Mp4SampleTablePiecesTest, SttsAfterSyncSample) {
- SampleIndexEncoder encoder;
- for (int i = 1; i <= 5; ++i) {
- encoder.AddSample(i, 2 * i, i == 1);
- }
+ // Initial index "10" as given above.
+ EXPECT_EQ(3, pieces.stss_entry_count());
+ const char kExpectedStss[] = "00 00 00 0a 00 00 00 0b 00 00 00 0c";
+ EXPECT_EQ(kExpectedStss, ToHex(pieces.stss_entries(), true));
- Mp4SampleTablePieces pieces;
- std::string error_message;
- // Because only the 1st frame is a sync sample, it will be included also.
- ASSERT_TRUE(
- pieces.Init(encoder.data(), 2, 10, 1, 1 + 2 + 3 + 4, &error_message))
- << error_message;
- EXPECT_EQ(4, pieces.stts_entry_count());
- const char kExpectedEntries[] =
- "00 00 00 01 00 00 00 01 "
- "00 00 00 01 00 00 00 02 "
- "00 00 00 01 00 00 00 03 "
- "00 00 00 01 00 00 00 04";
- EXPECT_EQ(kExpectedEntries, ToHex(pieces.stts_entries(), true));
-}
-
-TEST(Mp4SampleTablePiecesTest, Stss) {
- SampleIndexEncoder encoder;
- encoder.AddSample(1, 1, true);
- encoder.AddSample(1, 1, false);
- encoder.AddSample(1, 1, true);
- encoder.AddSample(1, 1, false);
- Mp4SampleTablePieces pieces;
- std::string error_message;
- ASSERT_TRUE(pieces.Init(encoder.data(), 2, 10, 0, 4, &error_message))
- << error_message;
- EXPECT_EQ(2, pieces.stss_entry_count());
- const char kExpectedSampleNumbers[] = "00 00 00 0a 00 00 00 0c";
- EXPECT_EQ(kExpectedSampleNumbers, ToHex(pieces.stss_entries(), true));
-}
-
-TEST(Mp4SampleTablePiecesTest, Stsz) {
- SampleIndexEncoder encoder;
- for (int i = 1; i <= 5; ++i) {
- encoder.AddSample(i, 2 * i, true);
- }
-
- Mp4SampleTablePieces pieces;
- std::string error_message;
- // Time range [1, 1 + 2 + 3 + 4) means the 2nd, 3rd, 4th samples should be
- // included.
- ASSERT_TRUE(
- pieces.Init(encoder.data(), 2, 10, 1, 1 + 2 + 3 + 4, &error_message))
- << error_message;
EXPECT_EQ(3, pieces.stsz_entry_count());
- const char kExpectedEntries[] = "00 00 00 04 00 00 00 06 00 00 00 08";
- EXPECT_EQ(kExpectedEntries, ToHex(pieces.stsz_entries(), true));
+ const char kExpectedStsz[] = "00 00 00 06 00 00 00 09 00 00 00 0c";
+ EXPECT_EQ(kExpectedStsz, ToHex(pieces.stsz_entries(), true));
+}
+
+TEST(Mp4SampleTablePiecesTest, HalfSyncFrames) {
+ Recording recording;
+ SampleIndexEncoder encoder;
+ encoder.Init(&recording, 42);
+ for (int i = 1; i <= 5; ++i) {
+ int64_t sample_duration_90k = 2 * i;
+ int64_t sample_bytes = 3 * i;
+ encoder.AddSample(sample_duration_90k, sample_bytes, (i % 2) == 1);
+ }
+
+ Mp4SampleTablePieces pieces;
+ std::string error_message;
+ // Time range [2 + 4 + 6, 2 + 4 + 6 + 8) means the 4th samples should be
+ // included. The 3rd gets pulled in also because it is a sync frame and the
+ // 4th is not.
+ ASSERT_TRUE(
+ pieces.Init(&recording, 2, 10, 2 + 4 + 6, 2 + 4 + 6 + 8, &error_message))
+ << error_message;
+
+ EXPECT_EQ(2, pieces.stts_entry_count());
+ const char kExpectedStts[] =
+ "00 00 00 01 00 00 00 06 "
+ "00 00 00 01 00 00 00 08";
+ EXPECT_EQ(kExpectedStts, ToHex(pieces.stts_entries(), true));
+
+ EXPECT_EQ(1, pieces.stss_entry_count());
+ const char kExpectedStss[] = "00 00 00 0a";
+ EXPECT_EQ(kExpectedStss, ToHex(pieces.stss_entries(), true));
+
+ EXPECT_EQ(2, pieces.stsz_entry_count());
+ const char kExpectedStsz[] = "00 00 00 09 00 00 00 0c";
+ EXPECT_EQ(kExpectedStsz, ToHex(pieces.stsz_entries(), true));
+}
+
+TEST(Mp4SampleTablePiecesTest, FastPath) {
+ Recording recording;
+ SampleIndexEncoder encoder;
+ encoder.Init(&recording, 42);
+ for (int i = 1; i <= 5; ++i) {
+ int64_t sample_duration_90k = 2 * i;
+ int64_t sample_bytes = 3 * i;
+ encoder.AddSample(sample_duration_90k, sample_bytes, (i % 2) == 1);
+ }
+ auto total_duration_90k = recording.end_time_90k - recording.start_time_90k;
+
+ Mp4SampleTablePieces pieces;
+ std::string error_message;
+ // Time range [0, end - start) means to pull in everything.
+ // This uses a fast path which can determine the size without examining the
+ // index.
+ ASSERT_TRUE(
+ pieces.Init(&recording, 2, 10, 0, total_duration_90k, &error_message))
+ << error_message;
+
+ EXPECT_EQ(5, pieces.stts_entry_count());
+ const char kExpectedStts[] =
+ "00 00 00 01 00 00 00 02 "
+ "00 00 00 01 00 00 00 04 "
+ "00 00 00 01 00 00 00 06 "
+ "00 00 00 01 00 00 00 08 "
+ "00 00 00 01 00 00 00 0a";
+ EXPECT_EQ(kExpectedStts, ToHex(pieces.stts_entries(), true));
+
+ EXPECT_EQ(3, pieces.stss_entry_count());
+ const char kExpectedStss[] = "00 00 00 0a 00 00 00 0c 00 00 00 0e";
+ EXPECT_EQ(kExpectedStss, ToHex(pieces.stss_entries(), true));
+
+ EXPECT_EQ(5, pieces.stsz_entry_count());
+ const char kExpectedStsz[] =
+ "00 00 00 03 00 00 00 06 00 00 00 09 00 00 00 0c 00 00 00 0f";
+ EXPECT_EQ(kExpectedStsz, ToHex(pieces.stsz_entries(), true));
}
class IntegrationTest : public testing::Test {
@@ -167,20 +199,24 @@ class IntegrationTest : public testing::Test {
CHECK_EQ(0, ret) << strerror(ret);
}
- void CopyMp4ToSingleRecording() {
+ Recording CopyMp4ToSingleRecording() {
std::string error_message;
+ Recording recording;
SampleIndexEncoder index;
+
+ // Set start time to 2015-04-26 00:00:00 UTC.
+ index.Init(&recording, UINT64_C(1430006400) * kTimeUnitsPerSecond);
SampleFileWriter writer(tmpdir_.get());
- recording_.sample_file_path = StrCat(tmpdir_path_, "/clip.sample");
+ recording.sample_file_path = StrCat(tmpdir_path_, "/clip.sample");
if (!writer.Open("clip.sample", &error_message)) {
ADD_FAILURE() << "open clip.sample: " << error_message;
- return;
+ return recording;
}
auto in = GetRealVideoSource()->OpenFile("../src/testdata/clip.mp4",
&error_message);
if (in == nullptr) {
ADD_FAILURE() << "open clip.mp4" << error_message;
- return;
+ return recording;
}
video_sample_entry_.width = in->stream()->codec->width;
@@ -189,7 +225,7 @@ class IntegrationTest : public testing::Test {
in->stream()->codec->height,
&video_sample_entry_.data, &error_message)) {
ADD_FAILURE() << "GetH264SampleEntry: " << error_message;
- return;
+ return recording;
}
while (true) {
@@ -197,30 +233,28 @@ class IntegrationTest : public testing::Test {
if (!in->GetNext(&pkt, &error_message)) {
if (!error_message.empty()) {
ADD_FAILURE() << "GetNext: " << error_message;
- return;
+ return recording;
}
break;
}
if (!writer.Write(GetData(pkt), &error_message)) {
ADD_FAILURE() << "Write: " << error_message;
- return;
+ return recording;
}
index.AddSample(pkt.pkt()->duration, pkt.pkt()->size, pkt.is_key());
}
- if (!writer.Close(&recording_.sample_file_sha1, &error_message)) {
+ if (!writer.Close(&recording.sample_file_sha1, &error_message)) {
ADD_FAILURE() << "Close: " << error_message;
}
- recording_.video_index = index.data().as_string();
-
- // Set start time to 2015-04-26 00:00:00 UTC.
- recording_.start_time_90k = UINT64_C(1430006400) * kTimeUnitsPerSecond;
+ return recording;
}
- std::unique_ptr CreateMp4FromSingleRecording() {
+ std::unique_ptr CreateMp4FromSingleRecording(
+ const Recording &recording) {
Mp4FileBuilder builder;
builder.SetSampleEntry(video_sample_entry_);
- builder.Append(Recording(recording_), 0,
+ builder.Append(Recording(recording), 0,
std::numeric_limits::max());
std::string error_message;
auto mp4 = builder.Build(&error_message);
@@ -283,20 +317,25 @@ class IntegrationTest : public testing::Test {
std::string tmpdir_path_;
std::unique_ptr tmpdir_;
std::string etag_;
- Recording recording_;
VideoSampleEntry video_sample_entry_;
};
TEST_F(IntegrationTest, RoundTrip) {
- CopyMp4ToSingleRecording();
- auto f = CreateMp4FromSingleRecording();
+ Recording recording = CopyMp4ToSingleRecording();
+ if (HasFailure()) {
+ return;
+ }
+ auto f = CreateMp4FromSingleRecording(recording);
WriteMp4(f.get());
CompareMp4s();
}
TEST_F(IntegrationTest, Metadata) {
- CopyMp4ToSingleRecording();
- auto f = CreateMp4FromSingleRecording();
+ Recording recording = CopyMp4ToSingleRecording();
+ if (HasFailure()) {
+ return;
+ }
+ auto f = CreateMp4FromSingleRecording(recording);
// This test is brittle, which is the point. Any time the digest comparison
// here fails, it can be updated, but the etag must change as well!
diff --git a/src/mp4.cc b/src/mp4.cc
index fc22594..8d6f05e 100644
--- a/src/mp4.cc
+++ b/src/mp4.cc
@@ -576,45 +576,57 @@ class Mp4File : public VirtualFile {
namespace internal {
-bool Mp4SampleTablePieces::Init(re2::StringPiece video_index_blob,
+bool Mp4SampleTablePieces::Init(const Recording *recording,
int sample_entry_index, int32_t sample_offset,
int32_t start_90k, int32_t end_90k,
std::string *error_message) {
- video_index_blob_ = video_index_blob;
sample_entry_index_ = sample_entry_index;
sample_offset_ = sample_offset;
desired_end_90k_ = end_90k;
- SampleIndexIterator it = SampleIndexIterator(video_index_blob_);
- if (!it.done() && !it.is_key()) {
- *error_message = "First frame must be a key frame.";
- return false;
- }
- for (; !it.done(); it.Next()) {
- VLOG(3) << "Processing frame with start " << it.start_90k()
- << (it.is_key() ? " (key)" : " (non-key)");
- // Find boundaries.
- if (it.start_90k() <= start_90k && it.is_key()) {
- VLOG(3) << "...new start candidate.";
- begin_ = it;
- sample_pos_.begin = begin_.pos();
- frames_ = 0;
- key_frames_ = 0;
- }
- if (it.start_90k() >= end_90k) {
- VLOG(3) << "...past end.";
- break;
+ SampleIndexIterator it = SampleIndexIterator(recording->video_index);
+ auto recording_duration_90k =
+ recording->end_time_90k - recording->start_time_90k;
+ bool fast_path = start_90k == 0 && end_90k >= recording_duration_90k;
+ if (fast_path) {
+ VLOG(1) << "Fast path, frames=" << recording->video_samples
+ << ", key=" << recording->video_sync_samples;
+ sample_pos_.end = recording->sample_file_bytes;
+ begin_ = it;
+ frames_ = recording->video_samples;
+ key_frames_ = recording->video_sync_samples;
+ actual_end_90k_ = recording_duration_90k;
+ } else {
+ if (!it.done() && !it.is_key()) {
+ *error_message = "First frame must be a key frame.";
+ return false;
}
+ for (; !it.done(); it.Next()) {
+ VLOG(3) << "Processing frame with start " << it.start_90k()
+ << (it.is_key() ? " (key)" : " (non-key)");
+ // Find boundaries.
+ if (it.start_90k() <= start_90k && it.is_key()) {
+ VLOG(3) << "...new start candidate.";
+ begin_ = it;
+ sample_pos_.begin = begin_.pos();
+ frames_ = 0;
+ key_frames_ = 0;
+ }
+ if (it.start_90k() >= end_90k) {
+ VLOG(3) << "...past end.";
+ break;
+ }
- // Process this frame.
- frames_++;
- if (it.is_key()) {
- key_frames_++;
- }
+ // Process this frame.
+ frames_++;
+ if (it.is_key()) {
+ key_frames_++;
+ }
- // This is the current best candidate to end.
- actual_end_90k_ = it.end_90k();
+ // This is the current best candidate to end.
+ actual_end_90k_ = it.end_90k();
+ }
+ sample_pos_.end = it.pos();
}
- sample_pos_.end = it.pos();
if (it.has_error()) {
*error_message = it.error();
return false;
@@ -724,7 +736,7 @@ std::unique_ptr Mp4FileBuilder::Build(std::string *error_message) {
return std::unique_ptr();
}
- if (!segment->pieces.Init(segment->recording.video_index,
+ if (!segment->pieces.Init(&segment->recording,
1, // sample entry index
sample_offset, segment->rel_start_90k,
segment->rel_end_90k, error_message)) {
diff --git a/src/mp4.h b/src/mp4.h
index 47a1b80..63d3464 100644
--- a/src/mp4.h
+++ b/src/mp4.h
@@ -55,8 +55,7 @@ class Mp4SampleTablePieces {
Mp4SampleTablePieces(const Mp4SampleTablePieces &) = delete;
void operator=(const Mp4SampleTablePieces &) = delete;
- // |video_index_blob|, which must outlive the Mp4SampleTablePieces, should
- // be the contents of the video_index field for this recording.
+ // |recording| must outlive the Mp4SampleTablePieces.
//
// |sample_entry_index| should be the (1-based) index into the "stsd" box
// of an entry matching this recording's video_sample_entry_sha1. It may
@@ -71,7 +70,7 @@ class Mp4SampleTablePieces {
// from the last sync sample <= |start_90k| to the last sample with start time
// <= |end_90k|. TODO: support edit lists and duration trimming to produce
// the exact correct time range.
- bool Init(re2::StringPiece video_index_blob, int sample_entry_index,
+ bool Init(const Recording *recording, int sample_entry_index,
int32_t sample_offset, int32_t start_90k, int32_t end_90k,
std::string *error_message);
@@ -100,8 +99,6 @@ class Mp4SampleTablePieces {
bool FillStscEntries(std::string *s, std::string *error_message) const;
bool FillStszEntries(std::string *s, std::string *error_message) const;
- re2::StringPiece video_index_blob_;
-
// After Init(), |begin_| will be on the first sample after the start of the
// range (or it will be done()).
SampleIndexIterator begin_;
diff --git a/src/recording-test.cc b/src/recording-test.cc
index cc57665..e6d1d51 100644
--- a/src/recording-test.cc
+++ b/src/recording-test.cc
@@ -54,23 +54,33 @@ namespace {
// Example from design/schema.md.
TEST(SampleIndexTest, EncodeExample) {
+ Recording recording;
SampleIndexEncoder encoder;
+ encoder.Init(&recording, 1000);
encoder.AddSample(10, 1000, true);
encoder.AddSample(9, 10, false);
encoder.AddSample(11, 15, false);
encoder.AddSample(10, 12, false);
encoder.AddSample(10, 1050, true);
- ASSERT_EQ("29 d0 0f 02 14 08 0a 02 05 01 64", ToHex(encoder.data(), true));
+ EXPECT_EQ("29 d0 0f 02 14 08 0a 02 05 01 64",
+ ToHex(recording.video_index, true));
+ EXPECT_EQ(1000, recording.start_time_90k);
+ EXPECT_EQ(1000 + 10 + 9 + 11 + 10 + 10, recording.end_time_90k);
+ EXPECT_EQ(1000 + 10 + 15 + 12 + 1050, recording.sample_file_bytes);
+ EXPECT_EQ(5, recording.video_samples);
+ EXPECT_EQ(2, recording.video_sync_samples);
}
TEST(SampleIndexTest, RoundTrip) {
+ Recording recording;
SampleIndexEncoder encoder;
+ encoder.Init(&recording, 1000);
encoder.AddSample(10, 30000, true);
encoder.AddSample(9, 1000, false);
encoder.AddSample(11, 1100, false);
encoder.AddSample(18, 31000, true);
- SampleIndexIterator it = SampleIndexIterator(encoder.data());
+ SampleIndexIterator it = SampleIndexIterator(recording.video_index);
std::string error_message;
ASSERT_FALSE(it.done()) << it.error();
EXPECT_EQ(10, it.duration_90k());
diff --git a/src/recording.cc b/src/recording.cc
index 1cfb2a9..af1e2ca 100644
--- a/src/recording.cc
+++ b/src/recording.cc
@@ -41,6 +41,19 @@
namespace moonfire_nvr {
+void SampleIndexEncoder::Init(Recording *recording, int64_t start_time_90k) {
+ recording_ = recording;
+ recording_->start_time_90k = start_time_90k;
+ recording_->end_time_90k = start_time_90k;
+ recording_->sample_file_bytes = 0;
+ recording_->video_samples = 0;
+ recording_->video_sync_samples = 0;
+ recording_->video_index.clear();
+ prev_duration_90k_ = 0;
+ prev_bytes_key_ = 0;
+ prev_bytes_nonkey_ = 0;
+}
+
void SampleIndexEncoder::AddSample(int32_t duration_90k, int32_t bytes,
bool is_key) {
CHECK_GE(duration_90k, 0);
@@ -48,23 +61,21 @@ void SampleIndexEncoder::AddSample(int32_t duration_90k, int32_t bytes,
int32_t duration_delta = duration_90k - prev_duration_90k_;
prev_duration_90k_ = duration_90k;
int32_t bytes_delta;
+ recording_->end_time_90k += duration_90k;
+ recording_->sample_file_bytes += bytes;
+ ++recording_->video_samples;
if (is_key) {
bytes_delta = bytes - prev_bytes_key_;
prev_bytes_key_ = bytes;
+ ++recording_->video_sync_samples;
} else {
bytes_delta = bytes - prev_bytes_nonkey_;
prev_bytes_nonkey_ = bytes;
}
uint32_t zigzagged_bytes_delta = Zigzag32(bytes_delta);
- AppendVar32((Zigzag32(duration_delta) << 1) | is_key, &data_);
- AppendVar32(zigzagged_bytes_delta, &data_);
-}
-
-void SampleIndexEncoder::Clear() {
- data_.clear();
- prev_duration_90k_ = 0;
- prev_bytes_key_ = 0;
- prev_bytes_nonkey_ = 0;
+ AppendVar32((Zigzag32(duration_delta) << 1) | is_key,
+ &recording_->video_index);
+ AppendVar32(zigzagged_bytes_delta, &recording_->video_index);
}
void SampleIndexIterator::Next() {
@@ -194,6 +205,7 @@ bool SampleFileWriter::Close(std::string *sha1, std::string *error_message) {
bool ok = !corrupt_;
file_.reset();
*sha1 = sha1_->Finalize();
+ sha1_ = Digest::SHA1();
pos_ = 0;
corrupt_ = false;
return ok;
diff --git a/src/recording.h b/src/recording.h
index 2f074b0..43c89b6 100644
--- a/src/recording.h
+++ b/src/recording.h
@@ -44,27 +44,45 @@
#include "crypto.h"
#include "filesystem.h"
+#include "uuid.h"
namespace moonfire_nvr {
-constexpr uint32_t kTimeUnitsPerSecond = 90000;
+constexpr int64_t kTimeUnitsPerSecond = 90000;
-// Encodes a sample index.
+// Various fields from the "recording" table which are useful when viewing
+// recordings.
+struct Recording {
+ int64_t rowid = -1;
+ std::string sample_file_path;
+ std::string sample_file_sha1;
+ Uuid sample_file_uuid;
+
+ // Fields populated by SampleIndexEncoder.
+ int64_t start_time_90k = -1;
+ int64_t end_time_90k = -1;
+ int64_t sample_file_bytes = -1;
+ int64_t video_samples = -1;
+ int64_t video_sync_samples = -1;
+ std::string video_sample_entry_sha1;
+ std::string video_index;
+};
+
+// Reusable object to encode sample index data to a Recording object.
class SampleIndexEncoder {
public:
- SampleIndexEncoder() { Clear(); }
- void AddSample(int32_t duration_90k, int32_t bytes, bool is_key);
- void Clear();
+ SampleIndexEncoder() {}
+ SampleIndexEncoder(const SampleIndexEncoder &) = delete;
+ void operator=(const SampleIndexEncoder &) = delete;
- // Return the current data, which is invalidated by the next call to
- // AddSample() or Clear().
- re2::StringPiece data() { return data_; }
+ void Init(Recording *recording, int64_t start_time_90k);
+ void AddSample(int32_t duration_90k, int32_t bytes, bool is_key);
private:
- std::string data_;
- int32_t prev_duration_90k_;
- int32_t prev_bytes_key_;
- int32_t prev_bytes_nonkey_;
+ Recording *recording_;
+ int32_t prev_duration_90k_ = 0;
+ int32_t prev_bytes_key_ = 0;
+ int32_t prev_bytes_nonkey_ = 0;
};
// Iterates through an encoded index, decoding on the fly. Copyable.
@@ -176,19 +194,6 @@ struct VideoSampleEntry {
uint16_t height = 0;
};
-// Various fields from the "recording" table which are useful when viewing
-// recordings.
-struct Recording {
- int64_t start_time_90k = -1;
- int64_t end_time_90k = -1;
- int64_t sample_file_bytes = -1;
- std::string sample_file_path;
- std::string sample_file_uuid;
- std::string sample_file_sha1;
- std::string video_sample_entry_sha1;
- std::string video_index;
-};
-
} // namespace moonfire_nvr
#endif // MOONFIRE_NVR_RECORDING_H
diff --git a/src/schema.sql b/src/schema.sql
index 1238b5f..09a1744 100644
--- a/src/schema.sql
+++ b/src/schema.sql
@@ -81,13 +81,14 @@ create table recording (
end_time_90k integer,
video_samples integer,
- video_sample_entry_sha1 blob references visual_sample_entry (sha1),
+ video_sync_samples integer,
+ video_sample_entry_sha1 blob references video_sample_entry (sha1),
video_index blob
);
-- A concrete box derived from a ISO/IEC 14496-12 section 8.5.2
-- VisualSampleEntry box. Describes the codec, width, height, etc.
-create table visual_sample_entry (
+create table video_sample_entry (
-- A SHA-1 hash of |bytes|.
sha1 blob primary key,