mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2025-01-12 15:33:22 -05:00
First portion of .mp4 generation logic.
This commit is contained in:
parent
c294d751b6
commit
30e0f73ae0
@ -49,6 +49,7 @@ set(MOONFIRE_NVR_SRCS
|
||||
h264.cc
|
||||
http.cc
|
||||
moonfire-nvr.cc
|
||||
mp4.cc
|
||||
profiler.cc
|
||||
recording.cc
|
||||
sqlite.cc
|
||||
@ -66,7 +67,7 @@ install_programs(/bin FILES moonfire-nvr)
|
||||
include_directories(${GTest_INCLUDE_DIR})
|
||||
include_directories(${GMock_INCLUDE_DIR})
|
||||
|
||||
foreach(test coding crypto h264 http moonfire-nvr recording sqlite string)
|
||||
foreach(test coding crypto h264 http moonfire-nvr mp4 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
|
||||
|
@ -144,6 +144,11 @@ inline void AppendU32(uint32_t in, std::string *out) {
|
||||
out->append(reinterpret_cast<const char *>(&net), sizeof(uint32_t));
|
||||
}
|
||||
|
||||
inline void Append32(int32_t in, std::string *out) {
|
||||
int32_t net = ToNetwork32(in);
|
||||
out->append(reinterpret_cast<const char *>(&net), sizeof(int32_t));
|
||||
}
|
||||
|
||||
} // namespace moonfire_nvr
|
||||
|
||||
#endif // MOONFIRE_NVR_CODING_H
|
||||
|
22
src/http.cc
22
src/http.cc
@ -40,6 +40,8 @@
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/event.h>
|
||||
#include <event2/keyvalq_struct.h>
|
||||
@ -183,6 +185,26 @@ bool EvBuffer::AddFile(int fd, ev_off_t offset, ev_off_t length,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FillerFileSlice::AddRange(ByteRange range, EvBuffer *buf,
|
||||
std::string *error_message) const {
|
||||
std::unique_ptr<std::string> s(new std::string);
|
||||
s->reserve(size_);
|
||||
if (!fn_(s.get(), error_message)) {
|
||||
return false;
|
||||
}
|
||||
if (s->size() != size_) {
|
||||
*error_message = StrCat("Expected filled slice to be ", size_,
|
||||
" bytes; got ", s->size(), " bytes.");
|
||||
return false;
|
||||
}
|
||||
std::string *unowned_s = s.release();
|
||||
buf->AddReference(unowned_s->data() + range.begin,
|
||||
range.size(), [](const void *, size_t, void *s) {
|
||||
delete reinterpret_cast<std::string *>(s);
|
||||
}, unowned_s);
|
||||
return true;
|
||||
}
|
||||
|
||||
void HttpSendError(evhttp_request *req, int http_err, const std::string &prefix,
|
||||
int posix_err) {
|
||||
evhttp_send_error(req, http_err,
|
||||
|
43
src/http.h
43
src/http.h
@ -82,6 +82,13 @@ class EvBuffer {
|
||||
bool AddFile(int fd, ev_off_t offset, ev_off_t length,
|
||||
std::string *error_message);
|
||||
|
||||
void AddReference(const void *data, size_t datlen,
|
||||
evbuffer_ref_cleanup_cb cleanupfn, void *cleanupfn_arg) {
|
||||
CHECK_EQ(
|
||||
0, evbuffer_add_reference(buf_, data, datlen, cleanupfn, cleanupfn_arg))
|
||||
<< strerror(errno);
|
||||
}
|
||||
|
||||
private:
|
||||
struct evbuffer *buf_;
|
||||
};
|
||||
@ -91,6 +98,7 @@ struct ByteRange {
|
||||
ByteRange(int64_t begin, int64_t end) : begin(begin), end(end) {}
|
||||
int64_t begin = 0;
|
||||
int64_t end = 0; // exclusive.
|
||||
int64_t size() const { return end - begin; }
|
||||
bool operator==(const ByteRange &o) const {
|
||||
return begin == o.begin && end == o.end;
|
||||
}
|
||||
@ -105,20 +113,45 @@ inline std::ostream &operator<<(std::ostream &out, const ByteRange &range) {
|
||||
void HttpSendError(evhttp_request *req, int http_err, const std::string &prefix,
|
||||
int posix_errno);
|
||||
|
||||
class VirtualFile {
|
||||
class FileSlice {
|
||||
public:
|
||||
virtual ~FileSlice() {}
|
||||
virtual int64_t size() const = 0;
|
||||
virtual bool AddRange(ByteRange range, EvBuffer *buf,
|
||||
std::string *error_message) const = 0;
|
||||
};
|
||||
|
||||
class VirtualFile : public FileSlice {
|
||||
public:
|
||||
virtual ~VirtualFile() {}
|
||||
|
||||
// Return the given property of the file.
|
||||
virtual int64_t size() const = 0;
|
||||
virtual time_t last_modified() const = 0;
|
||||
virtual std::string etag() const = 0;
|
||||
virtual std::string mime_type() const = 0;
|
||||
virtual std::string filename() const = 0; // for logging.
|
||||
};
|
||||
|
||||
// Add the given range of the file to the buffer.
|
||||
virtual bool AddRange(ByteRange range, EvBuffer *buf,
|
||||
std::string *error_message) const = 0;
|
||||
// A FileSlice of a pre-defined length which calls a function which fills the
|
||||
// slice on demand. The FillerFileSlice is responsible for subsetting.
|
||||
class FillerFileSlice : public FileSlice {
|
||||
public:
|
||||
using FillFunction =
|
||||
std::function<bool(std::string *slice, std::string *error_message)>;
|
||||
|
||||
void Init(size_t size, FillFunction fn) {
|
||||
fn_ = fn;
|
||||
size_ = size;
|
||||
}
|
||||
|
||||
int64_t size() const final { return size_; }
|
||||
|
||||
bool AddRange(ByteRange range, EvBuffer *buf,
|
||||
std::string *error_message) const final;
|
||||
|
||||
private:
|
||||
FillFunction fn_;
|
||||
size_t size_;
|
||||
};
|
||||
|
||||
// Serve an HTTP request |req| from |file|, handling byte range and
|
||||
|
158
src/mp4-test.cc
Normal file
158
src/mp4-test.cc
Normal file
@ -0,0 +1,158 @@
|
||||
// This file is part of Moonfire DVR, a security camera digital video recorder.
|
||||
// Copyright (C) 2015 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/>.
|
||||
//
|
||||
// mp4_test.cc: tests of the mp4.h interface.
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "http.h"
|
||||
#include "mp4.h"
|
||||
#include "string.h"
|
||||
|
||||
DECLARE_bool(alsologtostderr);
|
||||
|
||||
using moonfire_nvr::internal::Mp4SampleTablePieces;
|
||||
|
||||
namespace moonfire_nvr {
|
||||
namespace {
|
||||
|
||||
std::string ToHex(const FileSlice *slice) {
|
||||
EvBuffer buf;
|
||||
std::string error_message;
|
||||
size_t size = slice->size();
|
||||
CHECK(slice->AddRange(ByteRange(0, size), &buf, &error_message))
|
||||
<< error_message;
|
||||
CHECK_EQ(size, evbuffer_get_length(buf.get()));
|
||||
return ::moonfire_nvr::ToHex(re2::StringPiece(
|
||||
reinterpret_cast<const char *>(evbuffer_pullup(buf.get(), size)), size));
|
||||
}
|
||||
|
||||
TEST(Mp4SampleTablePiecesTest, Stts) {
|
||||
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.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()));
|
||||
}
|
||||
|
||||
TEST(Mp4SampleTablePiecesTest, SttsAfterSyncSample) {
|
||||
SampleIndexEncoder encoder;
|
||||
for (int i = 1; i <= 5; ++i) {
|
||||
encoder.AddSample(i, 2 * i, i == 1);
|
||||
}
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
TEST(Mp4SampleTablePiecesTest, Stsc) {
|
||||
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(1, pieces.stsc_entry_count());
|
||||
const char kExpectedEntries[] = "00 00 00 0a 00 00 00 04 00 00 00 02";
|
||||
EXPECT_EQ(kExpectedEntries, ToHex(pieces.stsc_entries()));
|
||||
}
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
} // 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();
|
||||
}
|
165
src/mp4.cc
Normal file
165
src/mp4.cc
Normal file
@ -0,0 +1,165 @@
|
||||
// This file is part of Moonfire DVR, 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/>.
|
||||
//
|
||||
// mp4.cc: implementation of mp4.h interface.
|
||||
|
||||
#include "mp4.h"
|
||||
|
||||
#include "coding.h"
|
||||
|
||||
namespace moonfire_nvr {
|
||||
|
||||
namespace internal {
|
||||
|
||||
bool Mp4SampleTablePieces::Init(re2::StringPiece video_index_blob,
|
||||
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;
|
||||
}
|
||||
|
||||
// Process this frame.
|
||||
frames_++;
|
||||
if (it.is_key()) {
|
||||
key_frames_++;
|
||||
}
|
||||
|
||||
// This is the current best candidate to end.
|
||||
actual_end_90k_ = it.end_90k();
|
||||
}
|
||||
sample_pos_.end = it.pos();
|
||||
if (it.has_error()) {
|
||||
*error_message = it.error();
|
||||
return false;
|
||||
}
|
||||
VLOG(1) << "requested ts [" << start_90k << ", " << end_90k << "), got ts ["
|
||||
<< begin_.start_90k() << ", " << actual_end_90k_ << "), " << frames_
|
||||
<< " frames (" << key_frames_
|
||||
<< " key), byte positions: " << sample_pos_;
|
||||
|
||||
stts_entries_.Init(2 * sizeof(int32_t) * stts_entry_count(),
|
||||
[this](std::string *s, std::string *error_message) {
|
||||
return FillSttsEntries(s, error_message);
|
||||
});
|
||||
stss_entries_.Init(sizeof(int32_t) * stss_entry_count(),
|
||||
[this](std::string *s, std::string *error_message) {
|
||||
return FillStssEntries(s, error_message);
|
||||
});
|
||||
stsc_entries_.Init(3 * sizeof(int32_t) * stsc_entry_count(),
|
||||
[this](std::string *s, std::string *error_message) {
|
||||
return FillStscEntries(s, error_message);
|
||||
});
|
||||
stsz_entries_.Init(sizeof(int32_t) * stsz_entry_count(),
|
||||
[this](std::string *s, std::string *error_message) {
|
||||
return FillStszEntries(s, error_message);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Mp4SampleTablePieces::FillSttsEntries(std::string *s,
|
||||
std::string *error_message) const {
|
||||
SampleIndexIterator it;
|
||||
for (it = begin_; !it.done() && it.start_90k() < desired_end_90k_;
|
||||
it.Next()) {
|
||||
AppendU32(1, s);
|
||||
AppendU32(it.duration_90k(), s);
|
||||
}
|
||||
if (it.has_error()) {
|
||||
*error_message = it.error();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Mp4SampleTablePieces::FillStssEntries(std::string *s,
|
||||
std::string *error_message) const {
|
||||
SampleIndexIterator it;
|
||||
uint32_t sample_num = sample_offset_;
|
||||
for (it = begin_; !it.done() && it.start_90k() < desired_end_90k_;
|
||||
it.Next()) {
|
||||
if (it.is_key()) {
|
||||
Append32(sample_num, s);
|
||||
}
|
||||
sample_num++;
|
||||
}
|
||||
if (it.has_error()) {
|
||||
*error_message = it.error();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Mp4SampleTablePieces::FillStscEntries(std::string *s,
|
||||
std::string *error_message) const {
|
||||
Append32(sample_offset_, s);
|
||||
Append32(frames_, s);
|
||||
Append32(sample_entry_index_, s);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Mp4SampleTablePieces::FillStszEntries(std::string *s,
|
||||
std::string *error_message) const {
|
||||
SampleIndexIterator it;
|
||||
for (it = begin_; !it.done() && it.start_90k() < desired_end_90k_;
|
||||
it.Next()) {
|
||||
Append32(it.bytes(), s);
|
||||
}
|
||||
if (it.has_error()) {
|
||||
*error_message = it.error();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
} // namespace moonfire_nvr
|
125
src/mp4.h
Normal file
125
src/mp4.h
Normal file
@ -0,0 +1,125 @@
|
||||
// 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/>.
|
||||
//
|
||||
// mp4.h: interface for building VirtualFiles representing ISO/IEC 14496-12
|
||||
// (ISO base media format / MPEG-4 / .mp4) video. These can be constructed
|
||||
// from one or more recordings and are suitable for HTTP range serving or
|
||||
// download.
|
||||
|
||||
#ifndef MOONFIRE_NVR_MP4_H
|
||||
#define MOONFIRE_NVR_MP4_H
|
||||
|
||||
#include "recording.h"
|
||||
#include "http.h"
|
||||
|
||||
namespace moonfire_nvr {
|
||||
|
||||
namespace internal {
|
||||
|
||||
// Represents pieces of .mp4 sample tables for one recording. Many recordings,
|
||||
// and thus many of these objects, may be spliced together into a single
|
||||
// virtual .mp4 file. For internal use by Mp4FileBuilder. Exposed for testing.
|
||||
class Mp4SampleTablePieces {
|
||||
public:
|
||||
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.
|
||||
//
|
||||
// |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
|
||||
// be shared with other recordings.
|
||||
//
|
||||
// |sample_offset| should be the (1-based) index of the first sample in
|
||||
// this file. It should be 1 + the sum of all previous Mp4SampleTablePieces'
|
||||
// samples() values.
|
||||
//
|
||||
// |start_90k| and |end_90k| should be relative to the start of the recording.
|
||||
// They indicate the *desired* time range. The *actual* time range will be
|
||||
// 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,
|
||||
int32_t sample_offset, int32_t start_90k, int32_t end_90k,
|
||||
std::string *error_message);
|
||||
|
||||
int32_t stts_entry_count() const { return frames_; }
|
||||
const FileSlice *stts_entries() const { return &stts_entries_; }
|
||||
|
||||
int32_t stss_entry_count() const { return key_frames_; }
|
||||
const FileSlice *stss_entries() const { return &stss_entries_; }
|
||||
|
||||
int32_t stsc_entry_count() const { return 1; }
|
||||
const FileSlice *stsc_entries() const { return &stsc_entries_; }
|
||||
|
||||
int32_t stsz_entry_count() const { return frames_; }
|
||||
const FileSlice *stsz_entries() const { return &stsz_entries_; }
|
||||
|
||||
int32_t samples() const { return frames_; }
|
||||
|
||||
// Return the byte range in the sample file of the frames represented here.
|
||||
ByteRange sample_pos() const { return sample_pos_; }
|
||||
|
||||
uint64_t duration_90k() const { return actual_end_90k_ - begin_.start_90k(); }
|
||||
|
||||
private:
|
||||
bool FillSttsEntries(std::string *s, std::string *error_message) const;
|
||||
bool FillStssEntries(std::string *s, std::string *error_message) const;
|
||||
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_;
|
||||
|
||||
ByteRange sample_pos_;
|
||||
|
||||
FillerFileSlice stts_entries_;
|
||||
FillerFileSlice stss_entries_;
|
||||
FillerFileSlice stsc_entries_;
|
||||
FillerFileSlice stsz_entries_;
|
||||
|
||||
int sample_entry_index_ = -1;
|
||||
int32_t sample_offset_ = -1;
|
||||
int32_t desired_end_90k_ = -1;
|
||||
int32_t actual_end_90k_ = -1;
|
||||
int32_t frames_ = 0;
|
||||
int32_t key_frames_ = 0;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
} // namespace moonfire_nvr
|
||||
|
||||
#endif // MOONFIRE_NVR_MP4_H
|
Loading…
Reference in New Issue
Block a user