mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2025-02-05 02:38:08 -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 1 to version 2
|
||||||
|
|
||||||
Version 2 adds support for recording of sub streams. It adds a new table for
|
Version 2 adds:
|
||||||
this purpose.
|
|
||||||
|
* 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.
|
The general upgrade procedure applies to this upgrade.
|
||||||
|
@ -38,6 +38,7 @@ pub fn run(tx: &rusqlite::Transaction) -> Result<(), Error> {
|
|||||||
tx.execute_batch(r#"
|
tx.execute_batch(r#"
|
||||||
alter table camera rename to old_camera;
|
alter table camera rename to old_camera;
|
||||||
alter table recording rename to old_recording;
|
alter table recording rename to old_recording;
|
||||||
|
alter table video_sample_entry rename to old_video_sample_entry;
|
||||||
drop index recording_cover;
|
drop index recording_cover;
|
||||||
|
|
||||||
create table camera (
|
create table camera (
|
||||||
@ -89,6 +90,15 @@ pub fn run(tx: &rusqlite::Transaction) -> Result<(), Error> {
|
|||||||
flags
|
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
|
insert into camera
|
||||||
select
|
select
|
||||||
id,
|
id,
|
||||||
@ -142,9 +152,57 @@ pub fn run(tx: &rusqlite::Transaction) -> Result<(), Error> {
|
|||||||
video_sample_entry_id
|
video_sample_entry_id
|
||||||
from
|
from
|
||||||
old_recording;
|
old_recording;
|
||||||
|
"#)?;
|
||||||
|
|
||||||
|
fix_video_sample_entry(tx)?;
|
||||||
|
|
||||||
|
tx.execute_batch(r#"
|
||||||
drop table old_camera;
|
drop table old_camera;
|
||||||
drop table old_recording;
|
drop table old_recording;
|
||||||
|
drop table old_video_sample_entry;
|
||||||
"#)?;
|
"#)?;
|
||||||
|
|
||||||
Ok(())
|
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 error::{Error, ResultExt};
|
||||||
use fnv;
|
use fnv;
|
||||||
use h264;
|
|
||||||
use lru_cache::LruCache;
|
use lru_cache::LruCache;
|
||||||
use openssl::hash;
|
use openssl::hash;
|
||||||
use parking_lot::{Mutex,MutexGuard};
|
use parking_lot::{Mutex,MutexGuard};
|
||||||
@ -106,8 +105,8 @@ enum ReservationState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const INSERT_VIDEO_SAMPLE_ENTRY_SQL: &'static str = r#"
|
const INSERT_VIDEO_SAMPLE_ENTRY_SQL: &'static str = r#"
|
||||||
insert into video_sample_entry (sha1, width, height, data)
|
insert into video_sample_entry (sha1, width, height, rfc6381_codec, data)
|
||||||
values (:sha1, :width, :height, :data)
|
values (:sha1, :width, :height, :rfc6381_codec, :data)
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
const INSERT_RECORDING_SQL: &'static str = r#"
|
const INSERT_RECORDING_SQL: &'static str = r#"
|
||||||
@ -1166,6 +1165,7 @@ impl LockedDatabase {
|
|||||||
sha1,
|
sha1,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
rfc6381_codec,
|
||||||
data
|
data
|
||||||
from
|
from
|
||||||
video_sample_entry
|
video_sample_entry
|
||||||
@ -1182,10 +1182,7 @@ impl LockedDatabase {
|
|||||||
id, sha1_vec.len())));
|
id, sha1_vec.len())));
|
||||||
}
|
}
|
||||||
sha1.copy_from_slice(&sha1_vec);
|
sha1.copy_from_slice(&sha1_vec);
|
||||||
let data: Vec<u8> = row.get_checked(4)?;
|
let data: Vec<u8> = row.get_checked(5)?;
|
||||||
|
|
||||||
// TODO: store this in the database rather than have codec-specific dispatch logic here.
|
|
||||||
let rfc6381_codec = h264::rfc6381_codec_from_sample_entry(&data)?;
|
|
||||||
|
|
||||||
self.state.video_sample_entries.insert(id, Arc::new(VideoSampleEntry {
|
self.state.video_sample_entries.insert(id, Arc::new(VideoSampleEntry {
|
||||||
id: id as i32,
|
id: id as i32,
|
||||||
@ -1193,7 +1190,7 @@ impl LockedDatabase {
|
|||||||
height: row.get_checked::<_, i32>(3)? as u16,
|
height: row.get_checked::<_, i32>(3)? as u16,
|
||||||
sha1,
|
sha1,
|
||||||
data,
|
data,
|
||||||
rfc6381_codec,
|
rfc6381_codec: row.get_checked(4)?,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
info!("Loaded {} video sample entries",
|
info!("Loaded {} video sample entries",
|
||||||
@ -1285,7 +1282,7 @@ impl LockedDatabase {
|
|||||||
|
|
||||||
/// Inserts the specified video sample entry if absent.
|
/// Inserts the specified video sample entry if absent.
|
||||||
/// On success, returns the id of a new or existing row.
|
/// 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> {
|
rfc6381_codec: String) -> Result<i32, Error> {
|
||||||
let sha1 = hash::hash(hash::MessageDigest::sha1(), &data)?;
|
let sha1 = hash::hash(hash::MessageDigest::sha1(), &data)?;
|
||||||
let mut sha1_bytes = [0u8; 20];
|
let mut sha1_bytes = [0u8; 20];
|
||||||
@ -1297,9 +1294,9 @@ impl LockedDatabase {
|
|||||||
if v.sha1 == sha1_bytes {
|
if v.sha1 == sha1_bytes {
|
||||||
// The width and height should match given that they're also specified within data
|
// The width and height should match given that they're also specified within data
|
||||||
// and thus included in the just-compared hash.
|
// 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{}",
|
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);
|
return Ok(id);
|
||||||
}
|
}
|
||||||
@ -1308,18 +1305,19 @@ impl LockedDatabase {
|
|||||||
let mut stmt = self.conn.prepare_cached(INSERT_VIDEO_SAMPLE_ENTRY_SQL)?;
|
let mut stmt = self.conn.prepare_cached(INSERT_VIDEO_SAMPLE_ENTRY_SQL)?;
|
||||||
stmt.execute_named(&[
|
stmt.execute_named(&[
|
||||||
(":sha1", &&sha1_bytes[..]),
|
(":sha1", &&sha1_bytes[..]),
|
||||||
(":width", &(w as i64)),
|
(":width", &(width as i64)),
|
||||||
(":height", &(h as i64)),
|
(":height", &(height as i64)),
|
||||||
|
(":rfc6381_codec", &rfc6381_codec),
|
||||||
(":data", &data),
|
(":data", &data),
|
||||||
])?;
|
])?;
|
||||||
|
|
||||||
let id = self.conn.last_insert_rowid() as i32;
|
let id = self.conn.last_insert_rowid() as i32;
|
||||||
self.state.video_sample_entries.insert(id, Arc::new(VideoSampleEntry {
|
self.state.video_sample_entries.insert(id, Arc::new(VideoSampleEntry {
|
||||||
id: id,
|
id,
|
||||||
width: w,
|
width,
|
||||||
height: h,
|
height,
|
||||||
sha1: sha1_bytes,
|
sha1: sha1_bytes,
|
||||||
data: data,
|
data,
|
||||||
rfc6381_codec,
|
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,
|
length {}", avc1_len, sample_entry.len() - avc1_len_pos,
|
||||||
avc_decoder_config_len)));
|
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 {
|
Ok(ExtraData {
|
||||||
sample_entry,
|
sample_entry,
|
||||||
rfc6381_codec,
|
rfc6381_codec: codec,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
need_transform,
|
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
|
/// 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
|
/// `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.
|
/// 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),
|
width integer not null check (width > 0),
|
||||||
height integer not null check (height > 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 serialized box, including the leading length and box type (avcC in
|
||||||
-- the case of H.264).
|
-- the case of H.264).
|
||||||
data blob not null check (length(data) > 86)
|
data blob not null check (length(data) > 86)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user