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

View File

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

View File

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

View File

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

View File

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