From 6f309e432fb8836ba26b5eae07eba14e1b1a43c0 Mon Sep 17 00:00:00 2001 From: Scott Lamb Date: Mon, 5 Feb 2018 11:57:59 -0800 Subject: [PATCH] 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. --- guide/schema.md | 8 +++-- src/cmds/upgrade/v1_to_v2.rs | 58 ++++++++++++++++++++++++++++++++++++ src/db.rs | 32 ++++++++++---------- src/h264.rs | 18 ++++------- src/schema.sql | 3 ++ 5 files changed, 87 insertions(+), 32 deletions(-) diff --git a/guide/schema.md b/guide/schema.md index eb8a6eb..5580c24 100644 --- a/guide/schema.md +++ b/guide/schema.md @@ -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. diff --git a/src/cmds/upgrade/v1_to_v2.rs b/src/cmds/upgrade/v1_to_v2.rs index 9a59754..bbfbe04 100644 --- a/src/cmds/upgrade/v1_to_v2.rs +++ b/src/cmds/upgrade/v1_to_v2.rs @@ -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 = row.get_checked(4)?; + insert.execute_named(&[ + (":id", &row.get_checked::<_, i32>(0)?), + (":sha1", &row.get_checked::<_, Vec>(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 { + 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)) +} diff --git a/src/db.rs b/src/db.rs index 6b950d1..4832e73 100644 --- a/src/db.rs +++ b/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 = 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 = 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, + pub fn insert_video_sample_entry(&mut self, width: u16, height: u16, data: Vec, rfc6381_codec: String) -> Result { 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, })); diff --git a/src/h264.rs b/src/h264.rs index eb70fc0..0a4fe0d 100644 --- a/src/h264.rs +++ b/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 { - 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. diff --git a/src/schema.sql b/src/schema.sql index 42dd5b0..62c64bc 100644 --- a/src/schema.sql +++ b/src/schema.sql @@ -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)