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:
Scott Lamb 2018-02-05 11:57:59 -08:00
parent cc6579b211
commit 6f309e432f
5 changed files with 87 additions and 32 deletions

View File

@ -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.

View File

@ -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))
}

View File

@ -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,
})); }));

View File

@ -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.

View File

@ -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)