Commit Graph

205 Commits

Author SHA1 Message Date
Scott Lamb
cd1c536efe Export the calendar days map. 2016-05-02 08:38:52 -07:00
Scott Lamb
1d55567384 Populate MoonfireDatabase::CameraData::days.
Also expose it via GetCameraRow.
2016-05-02 08:24:22 -07:00
Scott Lamb
28fa458982 A helper to find the bounds of a day. 2016-05-01 22:12:55 -07:00
Scott Lamb
292bcbaad5 Add helper for adjusting date-to-duration map.
The helper isn't used yet. The goal is to export this on /camera/<uuid>/ as
described in a TODO in design/api.md.

The next step is to keep MoonfireDatabase::CameraData::days up-to-date:

* Init: call on every recording (replacing the current aggregated query with
  a recording-by-recording query)
* InsertRecording, DeleteRecordings: call for added/removed recordings

then return it from GetCamera and pass it along to the client in
WebInterface::HandleJsonCameraDetail.
2016-05-01 21:26:17 -07:00
Scott Lamb
a7bfb00083 More logging tweaks. 2016-05-01 10:06:31 -07:00
Scott Lamb
cefbcedc9c Logging tweak. 2016-05-01 10:01:07 -07:00
Scott Lamb
52b6b8272c Remove a stray LOG(INFO). 2016-05-01 09:42:55 -07:00
Scott Lamb
3030e3fb32 Support trimming video segments.
* If the end of a segment is between samples, the last included sample will
  have a shortened duration.

* If the beginning of a segment not on a key frame (aka sync sample), the
  prefix will be included but trimmed using an edit list. (It seems like a
  ctts box might be able to accomplish the same thing, fwiw.)
2016-05-01 09:16:14 -07:00
Scott Lamb
713d7863de Improve warning message on open failure.
Before:

W0430 08:26:53.958887 41576 moonfire-nvr.cc:123]
driveway: Output error; sleeping before retrying: open
031e423c-2a0c-4450-b6cc-8af629606a90: Permission denied

After:

W0430 08:50:06.315666 43514 moonfire-nvr.cc:123]
driveway: Output error; sleeping before retrying: open
98592dfa-4ab4-427a-8ad0-033325f0f0b3 (within dir /home/slamb/moonfire/sample):
Permission denied
2016-04-30 08:51:58 -07:00
Scott Lamb
374975a73c On startup, ensure --sample_file_dir is writable. 2016-04-30 08:38:29 -07:00
Scott Lamb
138db4f491 Compile fixes for Raspbian 8.
* gcc (Raspbian 4.9.2-10) 4.9.2 complains about -1 in const char[]s.
  gcc (Ubuntu 5.2.1-22ubuntu2) 5.2.1 20151010 was fine with this.
  Use '\xff' instead.

* libjsoncpp-dev 0.6.0~rc2-3.1 doesn't have Json::writeValue.
  Use an older interface instead.

* libre2-dev 20140304+dfsg-2 has a bug in which custom RE2 parsers don't
  compile because the relevant constructor is only declared, not defined as
  trivial. (This is fixed on my Ubuntu's libre2-dev 20150701+dfsg-2.)
  Avoid using this.
2016-04-25 04:54:36 -07:00
Scott Lamb
ff08118001 Support for timetamp subtitles.
I tested these in VLC and QuickTime. Both players appear to ignore the
as the track dimensions, track transformation matrix, box dimensions, and box
justification. I just left them at default values then.

Automated testing is minimal. There's a new test that the resulting .mp4
parses, but I didn't actually ensure correctness of the subtitles in any way.
2016-04-25 04:17:43 -07:00
Scott Lamb
5dd0dca51f Add a simple JSON API.
This is a work in progress. There are no tests yet.
2016-04-23 13:55:36 -07:00
Dolf Starreveld
e7456643cd Added prep.sh script for automated builds
* Changed README.md commensurately
* Add cameras.sql to .gitignore to not commit personal camera data
* Change CMakeLists.txt to explicitly refer to hand-built libevent dirs
2016-02-07 22:59:29 -08:00
Scott Lamb
3b0dc5368e Write using the shiny new schema
There's a lot of work left to do on this:

* important latency optimization: the recording threads block
  while fsync()ing sample files, which can take 250+ ms. This
  should be moved to a separate thread to happen asynchronously.

* write cycle optimizations: several SQLite commits per camera per minute.

* test coverage: this drops testing of the file rotation, and
  there are several error paths worth testing.

* ffmpeg oddities to investigate:

  * the out-of-order first frame's pts
  * measurable delay before returning packets
  * it sometimes returns an initial packet it calls a "key" frame that actually
    has an SEI recovery point NAL but not an IDR-coded slice NAL, even though
    in the input these always seem to come together. This makes playback
    starting from this recording not work at all on Chrome. The symptom is
    that it loads a player-looking thing with the proper dimensions but
    playback never actually starts.

  I imagine these are all related but haven't taken the time to dig through
  ffmpeg code and understand them. The right thing anyway may be to ditch
  ffmpeg for RTSP streaming (perhaps in favor of the live555 library), as
  it seems to have other omissions like making it hard/impossible to take
  advantage of Sender Reports. In the meantime, I attempted to mitigate
  problems by decreasing ffmpeg's probesize.

* handling overlapping recordings: right now if there's too much time drift or
  a time jump, you can end up with recordings that the UI won't play without
  manual database changes. It's not obvious what the right thing to do is.

* easy camera setup: currently you have to manually insert rows in the SQLite
  database and restart.

but I think it's best to get something in to iterate from.

This deletes a lot of code, including:

* the ffmpeg video sink code (instead now using a bit of extra code in Stream
  on top of the SampleFileWriter, SampleIndexEncoder, and MoonfireDatabase
  code that's been around for a while)

* FileManager (in favor of new code using the database)

* the old UI

* RealFile and friends

* the dependency on protocol buffers, which was used for the config file
  (though I'll likely have other reasons for using protocol buffers later)

* even some utilities like IsWord that were just for validating the config
2016-02-03 23:22:37 -08:00
Scott Lamb
ca368944ec AnnexB->AVC sample data converter
I discovered that the mp4 files I was writing were viewable in VLC and in
Chrome-on-desktop (ffmpeg-based) but not in Chrome-on-Android
(libstagefright-based). It turns out that I was writing Annex B sample data
rather than the correct AVCParameterSample format. ffmpeg gives both the
"extradata" and the actual frames in Annex B format when reading from rtsp.

This is still my simple, unoptimized implementation of the Annex B parser. My
Raspberry Pi 2 is still able to record my six streams using about 30% of 1
core, so it will do for the moment at least.
2016-02-02 20:18:41 -08:00
Scott Lamb
8ee1ab1c7b Extend ListCameras to return more ifno
In particular, this returns all the extra configuration data that will be
necessary to actually instantiate streams from the database rather than the
soon-to-be-removed configuration file.
2016-01-31 23:27:52 -08:00
Scott Lamb
1bd5c8aafe Sanify sample directory references.
Before, I had a gross hardcoded path in moonfire-db.cc + a hacky
Recording::sample_file_path (which is StrCat(sample_file_dir, "/", uuid),
essentially). Now, things expect to take a File* to the sample file directory
and use openat(2). Several things had to change in the process:

* RealFileSlice now takes a File* dir.
* File has an Open that returns an fd (for RealFileSlice's benefit).
* BuildMp4 now is in WebInterface rather than MoonfireDatabase. The latter
  only manages the SQLite database, so it shouldn't know anything about the
  sample file directory.
2016-01-31 22:41:30 -08:00
Scott Lamb
09e1023b6a clang-format-3.7 --style=Google -i
(This time for real.)
2016-01-31 21:56:29 -08:00
Scott Lamb
708bbf5eb2 Revert "clang-format-3.7 --style=Google -i."
This reverts commit ad4beac464.
That commit wasn't as advertised; I had several other changes mixed in my
working copy. I'd also copied a working copy from one path to another, and
it turns out the cmake build subdir was still referring to the original, so
I hadn't realized this commit didn't even build. :(
2016-01-31 21:53:43 -08:00
Scott Lamb
1928569540 Small fix: correct duration in top-level display. 2016-01-31 21:17:20 -08:00
Scott Lamb
ad4beac464 clang-format-3.7 --style=Google -i. 2016-01-31 21:16:33 -08:00
Scott Lamb
77f3a57416 Fix recording display splitting.
I didn't properly update the new duration calculation when switching from
ascending to descending order.

Also, on the Pi, 1-hour recordings are noticeably faster to load.
2016-01-24 20:19:45 -08:00
Scott Lamb
699ffe7777 Expanded, reasonably efficient SQL operations.
* Schema revisions. The most dramatic is the addition of a covering index on
  (camera_id, start_time_90k) that avoids the need to make sparse accesses
  into the recording table (where the desired data is intermixed with both
  the large blobs and rows from other cameras). A query over a year's data
  previously took many seconds (6+ even in a form without the video_index)
  and now is roughly 10X faster. Queries for a couple weeks now should be
  unnoticeably fast.

  Other changes to shrink the rows, such as duration_90k instead of
  end_time_90k (more compact varint encoding) and video_sample_entry_id
  (typically 1 byte) instead of video_sample_entry_sha1 (20 bytes).
  And more CHECK constraints for good measure.

* Caching of expensive computations and logic to keep them up to date.
  The top-level web view previously went through the entire recording table,
  which was even slower. Now it is served from a small map in RAM.

* Expanded the scope of operations to cover (hopefully) everything needed for
  recording into the SQLite database.

* Added tests of MoonfireDatabase. These are basic tests that don't
  exercise a lot of error cases, but at least they exist.

The main MoonfireDatabase functionality still missing is support for quickly
seeing what calendar days have data over the full timespan of a camera. This
is more data to compute and cache.
2016-01-24 17:57:46 -08:00
Scott Lamb
b9d6526492 Optimize the SQLite query for building .mp4s.
On my laptop, with a month's data, a test query would take 0.1 to 0.2 seconds
before. Now it takes 0.001 to 0.004 seconds.

I improved this by creating and taking advantage of an index on start time.
It's a little more complicated than that because the desired timespan is
specified in terms of a recording's start and end time, not start time alone.
I defined a maximum duration of a recording (5 minutes) and specified this
with an extra condition in the query so that the end time can be used to
narrow the valid range of start times.

"explain query plan select ..." output confirms it's using the index with
both > and < comparisons:

0|0|0|SEARCH TABLE recording USING INDEX recording_start_time_90k (start_time_90k>? AND start_time_90k<?)
0|1|1|SEARCH TABLE video_sample_entry USING INDEX sqlite_autoindex_video_sample_entry_1 (sha1=?)

I also refactored ListMp4Recordings out of BuildMp4File to make the measurement
easier.
2016-01-17 01:14:29 -08:00
Scott Lamb
40cd983355 Web interface to the new SQLite schema.
This is almost certain to have performance problems with large databases,
but it's a useful starting point.

No tests yet. It shouldn't be too hard to add some for moonfire-db.h, but
I'm impatient to fake up enough data to check on the performance and see
what needs to change there first.
2016-01-16 22:54:16 -08:00
Scott Lamb
055883d248 Add a QueryParameters class.
This wraps libevent's evhttp_parse_query_str and friends. It's easier to use
than the raw libevent stuff because it handles initialization (formerly not
done properly in profiler.cc) and cleans up with RAII.
2016-01-16 18:00:58 -08:00
Scott Lamb
b18f6ba237 Add a simple Atoi64.
This will be useful in parsing numeric HTTP params.
2016-01-16 17:59:39 -08:00
Scott Lamb
442b953f28 Redo the SQLite wrapper.
I wrote the old interface before playing much with SQLite. Now that I've
played around with it a bit, I found many ways to make the interface more
pleasant and fool-proof:

* it opens the database in a mode that honors foreign keys and
  returns extended result codes.
* it forces locking to avoid SQLITE_BUSY and
  sqlite3_{changes,last_insert_rowid} race conditions.
* it supports named bind parameters.
* it defers some errors until Step() to reduce caller verbosity.
* it automatically handles calling reset, which was quite easy to forget.
* it remembers the Step() return value, which makes the row loop every so
  slightly more pleasant.
* it tracks transaction status.
2016-01-15 23:48:30 -08:00
Scott Lamb
4c7eed293f Construct HTTP responses incrementally.
This isn't as much of a speed-up as you might imagine; most of the large HTTP
content was mmap()ed files which are relatively efficient. The big improvement
here is that it's now possible to serve large files (4 GiB and up) on 32-bit
machines. This actually works: I was just able to browse a 25-hour, 37 GiB
.mp4 file on my Raspberry Pi 2 Model B. It takes about 400 ms to start serving
each request, which isn't exactly zippy but might be forgivable for such a
large file. I still intend for the common request from the web interface to be
for much smaller fragmented .mp4 files.

Speed could be improved later through caching. Right now my test code is
creating a fresh VirtualFile from a database query on each request, even
though it hasn't changed. The tricky part will be doing cache invalidation
cleanly if it does change---new recordings are added to the requested time
range, recordings are deleted, or existing recordings' timestamps are changed.

The downside to the approach here is that it requires libevent 2.1 for
evhttp_send_reply_chunk_with_cb. Unfortunately, Ubuntu 15.10 and Debian Jessie
still bundle libevent 2.0. There are a few possible improvements here:

1. fall back to assuming chunks are added immediately, so that people with
   libevent 2.0 get the old bad behavior and people with libevent 2.1 get the
   better behavior. This is kind of lame, though; it's easy to go through
   the whole address space pretty fast, particularly when the browsers send
   out requests so quickly so there may be some unintentional concurrency.

2. alter the FileSlice interface to return a pointer/destructor rather than
   add something to the evbuffer. HttpServe would then add each chunk via
   evbuffer_add_reference, and it'd supply a cleanupfn that (in addition to
   calling the FileSlice-supplied destructor) notes that this chunk has been
   fully sent. For all the currently-used FileSlices, this shouldn't be too
   hard, and there are a few other reasons it might be beneficial:

   * RealFileSlice could call madvise() to control the OS buffering
   * RealFileSlice could track when file descriptors are open and thus
     FileManager's unlink() calls don't actually free up space
   * It feels dirty to expose libevent stuff through the otherwise-nice
     FileSlice interface.

3. support building libevent 2.1 statically in-tree if the OS-supplied
   libevent is unsuitable.

I'm tempted to go with #2, but probably not right now. More urgent to commit
support for writing the new format and the wrapper bits for viewing it.
2016-01-14 22:41:49 -08:00
Scott Lamb
84406a8123 Add a fast path to Mp4SampleTablePieces::Init.
This avoids iteration through the video index for the "interior" recordings of
a virtual file. This takes generating the size of a ~8-hour / 15 fps file from
about 60 ms to about 10 ms. I expect better savings on a Raspberry Pi 2, for
longer records, and for higher frame rates. The total time here can be
significant; one one ~day-long recording on the Pi, it was several seconds.
I'm optimistic this will help with that.

It'd also be possible to optimize DecodeVar32 (perhaps by unrolling the loop)
but better to remove a call than to optimize one.

To add the fast path, we need a new field "video_sync_samples" in the
recording table to calculate the length of the stss table. Storage cost should
be minimal; I think typically two bytes in SQLite's record format (serial type
1, value < 128), described here: <https://www.sqlite.org/fileformat2.html>.
2016-01-14 15:41:45 -08:00
Scott Lamb
78c3b8dafa Fixes/improvements to mp4 VirtualFile generation.
* Fix the mdat box size, which was not properly including the length of the
  header itself. (The "mp4file" tool nicely diagnosed this corruption.)

* Fix the stsc box. The first number of each entry is meant to be a chunk
  index, not a sample index. This was causing strange behavior in basically
  any video player for multi-recording videos.

* Populate etag and last-modified so that Range: requests can work properly.
  The etag must be changed every time the generated file format changes.
  There's a serial number constant for this purpose and a test meant to help
  catch such problems.
2016-01-13 07:50:13 -08:00
Scott Lamb
d38eb9103e Helper for making large values human-readable. 2016-01-13 07:20:39 -08:00
Scott Lamb
95523c3522 ToHex shouldn't require padding between bytes.
This was getting obnoxious for SHA-1s, and particularly so when serving them
as etags.
2016-01-13 06:51:23 -08:00
Scott Lamb
29696688b5 Small Uuid class wrapping libuuid.
This will be used to generate the names of sample files,
as well as camera ids.
2016-01-12 09:46:21 -08:00
Scott Lamb
85b7027803 Logic for generating .mp4 virtual files.
This is still pretty rough. For example, there's no test coverage of virtual
files based on multiple recordings. The etag and last modified code are stubs.
And various other conditions aren't tested at all. But it does appear to work
in a test that does a round-trip from a .mp4 file, so it should be a decent
starting point.
2016-01-11 00:17:56 -08:00
Scott Lamb
798f1db039 h264.cc: handle both kinds of ffmpeg extradata. 2016-01-10 21:28:07 -08:00
Scott Lamb
29054d42a0 A few types of FileSlice useful for mp4 building. 2016-01-09 23:26:02 -08:00
Scott Lamb
eed2a69f7a Work around libevent bug 306 in evbuffer_add_file.
See <https://github.com/libevent/libevent/issues/306> for details.
2016-01-09 22:51:27 -08:00
Scott Lamb
bb7fb95b57 Helper for composing a VirtualFile from "slices". 2016-01-09 22:15:22 -08:00
Scott Lamb
c89907d785 Nit: fix typo in reserve. 2016-01-09 21:42:00 -08:00
Scott Lamb
1ca6e2a665 Bit less string copying in H.264 code. 2016-01-09 21:41:12 -08:00
Scott Lamb
15b1ee54a6 Compute the full AVCSampleEntry (avc1 box).
This code isn't pretty exactly---particularly the hardcoded lengths---but it
does work. I'll have a different mechanism for calculating the length and
nesting structure forthe more dynamic parts of the moov atom. This way is
convenient when generating a single string of mostly static data.
2016-01-09 21:13:05 -08:00
Scott Lamb
48d0473a4c Small helper for writing sample files safely.
Handles partial writes + checksumming.
2016-01-09 17:16:55 -08:00
Scott Lamb
30e0f73ae0 First portion of .mp4 generation logic. 2016-01-09 12:02:36 -08:00
Scott Lamb
c294d751b6 Add logic to create an AVCDecoderConfiguration. 2016-01-08 21:44:19 -08:00
Scott Lamb
dca9642c51 Remove unused/untested ffmpeg-test.cc.
This was a half-done thing added by mistake.
2016-01-08 21:40:59 -08:00
Scott Lamb
d00c0b2f12 Add ReadFileOrDie test util function.
This fixes a compilation error; I'd left it out of the previous commit adding
a sqlite-test.cc method which depends on it.
2016-01-08 21:39:42 -08:00
Scott Lamb
9af7eb8c14 Add small sqlite3 wrapper + start of schema. 2016-01-07 22:59:34 -08:00
Scott Lamb
a46df2c2e5 Minor File interface changes.
These are intended to make the sample file writing easier.
2016-01-06 23:38:46 -08:00
Scott Lamb
7b45f48027 Add (openssl-based) SHA-1 hashing. 2016-01-06 23:27:44 -08:00
Scott Lamb
60988f0646 Add sample index codec; fix schema doc. 2016-01-05 11:01:36 -08:00
Scott Lamb
23ba5e0049 Add util functions for binary encoding/decoding.
These will be used by the new sample index format.
2016-01-05 08:29:12 -08:00
Scott Lamb
7968952295 Add a Filesystem interface for testability.
Not immediately adding any tests that take advantage of it.
The new storage schema should handle ENOSPC correctly, and this will aid in
testing it.
2016-01-02 10:51:04 -08:00
Scott Lamb
c9eda8ac15 Initial commit, with basic functionality. 2016-01-01 22:06:47 -08:00