mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2025-01-27 06:33:20 -05:00
store rfc6381_codec in the database
This avoids having codec-specific logic to synthesize it in db.rs. It's not too much of a problem now with only H.264 support, but it'd be a pain when supporting H.265 and other codecs.
This commit is contained in:
parent
cc6579b211
commit
6f309e432f
@ -194,7 +194,11 @@ The general upgrade procedure applies to this upgrade.
|
||||
|
||||
### Version 1 to version 2
|
||||
|
||||
Version 2 adds support for recording of sub streams. It adds a new table for
|
||||
this purpose.
|
||||
Version 2 adds:
|
||||
|
||||
* recording of sub streams (splits a new `stream` table out of `camera`)
|
||||
* records the RFC-6381 codec associated with a video sample entry, so that
|
||||
logic for determining this is no longer needed as part of the database
|
||||
layer.
|
||||
|
||||
The general upgrade procedure applies to this upgrade.
|
||||
|
@ -38,6 +38,7 @@ pub fn run(tx: &rusqlite::Transaction) -> Result<(), Error> {
|
||||
tx.execute_batch(r#"
|
||||
alter table camera rename to old_camera;
|
||||
alter table recording rename to old_recording;
|
||||
alter table video_sample_entry rename to old_video_sample_entry;
|
||||
drop index recording_cover;
|
||||
|
||||
create table camera (
|
||||
@ -89,6 +90,15 @@ pub fn run(tx: &rusqlite::Transaction) -> Result<(), Error> {
|
||||
flags
|
||||
);
|
||||
|
||||
create table video_sample_entry (
|
||||
id integer primary key,
|
||||
sha1 blob unique not null check (length(sha1) = 20),
|
||||
width integer not null check (width > 0),
|
||||
height integer not null check (height > 0),
|
||||
rfc6381_codec text not null,
|
||||
data blob not null check (length(data) > 86)
|
||||
);
|
||||
|
||||
insert into camera
|
||||
select
|
||||
id,
|
||||
@ -142,9 +152,57 @@ pub fn run(tx: &rusqlite::Transaction) -> Result<(), Error> {
|
||||
video_sample_entry_id
|
||||
from
|
||||
old_recording;
|
||||
"#)?;
|
||||
|
||||
fix_video_sample_entry(tx)?;
|
||||
|
||||
tx.execute_batch(r#"
|
||||
drop table old_camera;
|
||||
drop table old_recording;
|
||||
drop table old_video_sample_entry;
|
||||
"#)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fix_video_sample_entry(tx: &rusqlite::Transaction) -> Result<(), Error> {
|
||||
let mut select = tx.prepare(r#"
|
||||
select
|
||||
id,
|
||||
sha1,
|
||||
width,
|
||||
height,
|
||||
data
|
||||
from
|
||||
old_video_sample_entry
|
||||
"#)?;
|
||||
let mut insert = tx.prepare(r#"
|
||||
insert into video_sample_entry values (:id, :sha1, :width, :height, :rfc6381_codec, :data)
|
||||
"#)?;
|
||||
let mut rows = select.query(&[])?;
|
||||
while let Some(row) = rows.next() {
|
||||
let row = row?;
|
||||
let data: Vec<u8> = row.get_checked(4)?;
|
||||
insert.execute_named(&[
|
||||
(":id", &row.get_checked::<_, i32>(0)?),
|
||||
(":sha1", &row.get_checked::<_, Vec<u8>>(1)?),
|
||||
(":width", &row.get_checked::<_, i32>(2)?),
|
||||
(":height", &row.get_checked::<_, i32>(3)?),
|
||||
(":rfc6381_codec", &rfc6381_codec_from_sample_entry(&data)?),
|
||||
(":data", &data),
|
||||
])?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// This previously lived in h264.rs. As of version 1, H.264 is the only supported codec.
|
||||
fn rfc6381_codec_from_sample_entry(sample_entry: &[u8]) -> Result<String, Error> {
|
||||
if sample_entry.len() < 99 || &sample_entry[4..8] != b"avc1" ||
|
||||
&sample_entry[90..94] != b"avcC" {
|
||||
return Err(Error::new("not a valid AVCSampleEntry".to_owned()));
|
||||
}
|
||||
let profile_idc = sample_entry[103];
|
||||
let constraint_flags_byte = sample_entry[104];
|
||||
let level_idc = sample_entry[105];
|
||||
Ok(format!("avc1.{:02x}{:02x}{:02x}", profile_idc, constraint_flags_byte, level_idc))
|
||||
}
|
||||
|
32
src/db.rs
32
src/db.rs
@ -53,7 +53,6 @@
|
||||
|
||||
use error::{Error, ResultExt};
|
||||
use fnv;
|
||||
use h264;
|
||||
use lru_cache::LruCache;
|
||||
use openssl::hash;
|
||||
use parking_lot::{Mutex,MutexGuard};
|
||||
@ -106,8 +105,8 @@ enum ReservationState {
|
||||
}
|
||||
|
||||
const INSERT_VIDEO_SAMPLE_ENTRY_SQL: &'static str = r#"
|
||||
insert into video_sample_entry (sha1, width, height, data)
|
||||
values (:sha1, :width, :height, :data)
|
||||
insert into video_sample_entry (sha1, width, height, rfc6381_codec, data)
|
||||
values (:sha1, :width, :height, :rfc6381_codec, :data)
|
||||
"#;
|
||||
|
||||
const INSERT_RECORDING_SQL: &'static str = r#"
|
||||
@ -1166,6 +1165,7 @@ impl LockedDatabase {
|
||||
sha1,
|
||||
width,
|
||||
height,
|
||||
rfc6381_codec,
|
||||
data
|
||||
from
|
||||
video_sample_entry
|
||||
@ -1182,10 +1182,7 @@ impl LockedDatabase {
|
||||
id, sha1_vec.len())));
|
||||
}
|
||||
sha1.copy_from_slice(&sha1_vec);
|
||||
let data: Vec<u8> = row.get_checked(4)?;
|
||||
|
||||
// TODO: store this in the database rather than have codec-specific dispatch logic here.
|
||||
let rfc6381_codec = h264::rfc6381_codec_from_sample_entry(&data)?;
|
||||
let data: Vec<u8> = row.get_checked(5)?;
|
||||
|
||||
self.state.video_sample_entries.insert(id, Arc::new(VideoSampleEntry {
|
||||
id: id as i32,
|
||||
@ -1193,7 +1190,7 @@ impl LockedDatabase {
|
||||
height: row.get_checked::<_, i32>(3)? as u16,
|
||||
sha1,
|
||||
data,
|
||||
rfc6381_codec,
|
||||
rfc6381_codec: row.get_checked(4)?,
|
||||
}));
|
||||
}
|
||||
info!("Loaded {} video sample entries",
|
||||
@ -1285,7 +1282,7 @@ impl LockedDatabase {
|
||||
|
||||
/// Inserts the specified video sample entry if absent.
|
||||
/// On success, returns the id of a new or existing row.
|
||||
pub fn insert_video_sample_entry(&mut self, w: u16, h: u16, data: Vec<u8>,
|
||||
pub fn insert_video_sample_entry(&mut self, width: u16, height: u16, data: Vec<u8>,
|
||||
rfc6381_codec: String) -> Result<i32, Error> {
|
||||
let sha1 = hash::hash(hash::MessageDigest::sha1(), &data)?;
|
||||
let mut sha1_bytes = [0u8; 20];
|
||||
@ -1297,9 +1294,9 @@ impl LockedDatabase {
|
||||
if v.sha1 == sha1_bytes {
|
||||
// The width and height should match given that they're also specified within data
|
||||
// and thus included in the just-compared hash.
|
||||
if v.width != w || v.height != h {
|
||||
if v.width != width || v.height != height {
|
||||
return Err(Error::new(format!("database entry for {:?} is {}x{}, not {}x{}",
|
||||
&sha1[..], v.width, v.height, w, h)));
|
||||
&sha1[..], v.width, v.height, width, height)));
|
||||
}
|
||||
return Ok(id);
|
||||
}
|
||||
@ -1308,18 +1305,19 @@ impl LockedDatabase {
|
||||
let mut stmt = self.conn.prepare_cached(INSERT_VIDEO_SAMPLE_ENTRY_SQL)?;
|
||||
stmt.execute_named(&[
|
||||
(":sha1", &&sha1_bytes[..]),
|
||||
(":width", &(w as i64)),
|
||||
(":height", &(h as i64)),
|
||||
(":width", &(width as i64)),
|
||||
(":height", &(height as i64)),
|
||||
(":rfc6381_codec", &rfc6381_codec),
|
||||
(":data", &data),
|
||||
])?;
|
||||
|
||||
let id = self.conn.last_insert_rowid() as i32;
|
||||
self.state.video_sample_entries.insert(id, Arc::new(VideoSampleEntry {
|
||||
id: id,
|
||||
width: w,
|
||||
height: h,
|
||||
id,
|
||||
width,
|
||||
height,
|
||||
sha1: sha1_bytes,
|
||||
data: data,
|
||||
data,
|
||||
rfc6381_codec,
|
||||
}));
|
||||
|
||||
|
18
src/h264.rs
18
src/h264.rs
@ -216,10 +216,13 @@ impl ExtraData {
|
||||
length {}", avc1_len, sample_entry.len() - avc1_len_pos,
|
||||
avc_decoder_config_len)));
|
||||
}
|
||||
let rfc6381_codec = rfc6381_codec_from_sample_entry(&sample_entry)?;
|
||||
let profile_idc = sample_entry[103];
|
||||
let constraint_flags = sample_entry[104];
|
||||
let level_idc = sample_entry[105];
|
||||
let codec = format!("avc1.{:02x}{:02x}{:02x}", profile_idc, constraint_flags, level_idc);
|
||||
Ok(ExtraData {
|
||||
sample_entry,
|
||||
rfc6381_codec,
|
||||
rfc6381_codec: codec,
|
||||
width,
|
||||
height,
|
||||
need_transform,
|
||||
@ -227,17 +230,6 @@ impl ExtraData {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rfc6381_codec_from_sample_entry(sample_entry: &[u8]) -> Result<String> {
|
||||
if sample_entry.len() < 99 || &sample_entry[4..8] != b"avc1" ||
|
||||
&sample_entry[90..94] != b"avcC" {
|
||||
return Err(Error::new("not a valid AVCSampleEntry".to_owned()));
|
||||
}
|
||||
let profile_idc = sample_entry[103];
|
||||
let constraint_flags_byte = sample_entry[104];
|
||||
let level_idc = sample_entry[105];
|
||||
Ok(format!("avc1.{:02x}{:02x}{:02x}", profile_idc, constraint_flags_byte, level_idc))
|
||||
}
|
||||
|
||||
/// Transforms sample data from Annex B format to AVC format. Should be called on samples iff
|
||||
/// `ExtraData::need_transform` is true. Uses an out parameter `avc_sample` rather than a return
|
||||
/// so that memory allocations can be reused from sample to sample.
|
||||
|
@ -204,6 +204,9 @@ create table video_sample_entry (
|
||||
width integer not null check (width > 0),
|
||||
height integer not null check (height > 0),
|
||||
|
||||
-- The codec in RFC-6381 format, such as "avc1.4d001f".
|
||||
rfc6381_codec text not null,
|
||||
|
||||
-- The serialized box, including the leading length and box type (avcC in
|
||||
-- the case of H.264).
|
||||
data blob not null check (length(data) > 86)
|
||||
|
Loading…
x
Reference in New Issue
Block a user