fix a couple v5->v6 schema upgrade problems

* Get rid of unused video_sample_entry rows. h264_reader rejected some
  of these; perhaps they were corrupted by some long-fixed bug.
* Use an i64 for cum_duration_90k (oops); an i32 overflows with only 6.6 hours
  of recording, so this was guaranteed to fail on any real setup.
* Add some context to those errors for debugging.

For posterity, a video_sample_entry that failed:

sqlite> select id, hex(sha1), width, height, rfc6381_codec, hex(data) from video_sample_entry where id = 9;
9|B3607B06107E779F57D062331FB54B59E964B9BC|1920|1080|avc1.640028|000000B26176633100000000000000010000000000000000000000000000000007800438004800000048000000000000000100000000000000000000000000000000000000000000000000000000000000000018FFFF0000005C6176634301640028FFE1002967640028AC1B1A80780227E5C05B808080A000007D0000186A1D0C0029FF5DE5C6860014FFAEF2E140010020A886052ACA0500769C28476EFE104A8000F08781320819888E894B5200000000
This commit is contained in:
Scott Lamb 2020-06-10 19:35:52 -07:00
parent 6f9612738c
commit 840524ec83
2 changed files with 35 additions and 7 deletions

View File

@ -230,19 +230,31 @@ mod tests {
insert into video_sample_entry (id, sha1, width, height, data) insert into video_sample_entry (id, sha1, width, height, data)
values (3, X'0000000000000000000000000000000000000002', 704, 480, ?); values (3, X'0000000000000000000000000000000000000002', 704, 480, ?);
"#, params![GOOD_ANAMORPHIC_VIDEO_SAMPLE_ENTRY])?; "#, params![GOOD_ANAMORPHIC_VIDEO_SAMPLE_ENTRY])?;
upgraded.execute(r#"
insert into video_sample_entry (id, sha1, width, height, data)
values (4, X'0000000000000000000000000000000000000003', 704, 480, ?);
"#, params![GOOD_ANAMORPHIC_VIDEO_SAMPLE_ENTRY])?;
upgraded.execute_batch(r#" upgraded.execute_batch(r#"
insert into recording (id, camera_id, sample_file_bytes, start_time_90k, duration_90k, insert into recording (id, camera_id, sample_file_bytes, start_time_90k, duration_90k,
local_time_delta_90k, video_samples, video_sync_samples, local_time_delta_90k, video_samples, video_sync_samples,
video_sample_entry_id, sample_file_uuid, sample_file_sha1, video_sample_entry_id, sample_file_uuid, sample_file_sha1,
video_index) video_index)
values (1, 1, 42, 140063580000000, 90000, 0, 1, 1, 1, values (1, 1, 42, 140063580000000, 90000, 0, 1, 1, 1,
X'E69D45E8CBA64DC1BA2ECB1585983A10', zeroblob(20), X'00'); X'E69D45E8CBA64DC1BA2ECB1585983A10', zeroblob(20), X'00'),
(2, 1, 42, 140063580090000, 90000, 0, 1, 1, 2,
X'94DE8484FF874A5295D488C8038A0312', zeroblob(20), X'00'),
(3, 1, 42, 140063580180000, 90000, 0, 1, 1, 3,
X'C94D4D0B533746059CD40B29039E641E', zeroblob(20), X'00');
insert into reserved_sample_files values (X'51EF700C933E4197AAE4EE8161E94221', 0), insert into reserved_sample_files values (X'51EF700C933E4197AAE4EE8161E94221', 0),
(X'E69D45E8CBA64DC1BA2ECB1585983A10', 1); (X'E69D45E8CBA64DC1BA2ECB1585983A10', 1);
"#)?; "#)?;
let rec1 = tmpdir.path().join("e69d45e8-cba6-4dc1-ba2e-cb1585983a10"); let rec1 = tmpdir.path().join("e69d45e8-cba6-4dc1-ba2e-cb1585983a10");
let rec2 = tmpdir.path().join("94de8484-ff87-4a52-95d4-88c8038a0312");
let rec3 = tmpdir.path().join("c94d4d0b-5337-4605-9cd4-0b29039e641e");
let garbage = tmpdir.path().join("51ef700c-933e-4197-aae4-ee8161e94221"); let garbage = tmpdir.path().join("51ef700c-933e-4197-aae4-ee8161e94221");
std::fs::File::create(&rec1)?; std::fs::File::create(&rec1)?;
std::fs::File::create(&rec2)?;
std::fs::File::create(&rec3)?;
std::fs::File::create(&garbage)?; std::fs::File::create(&garbage)?;
for (ver, fresh_sql) in &[(1, Some(include_str!("v1.sql"))), for (ver, fresh_sql) in &[(1, Some(include_str!("v1.sql"))),
@ -287,6 +299,9 @@ mod tests {
assert_eq!(pasp_by_id.get(&1), Some(&(1, 1))); assert_eq!(pasp_by_id.get(&1), Some(&(1, 1)));
assert_eq!(pasp_by_id.get(&2), Some(&(4, 3))); assert_eq!(pasp_by_id.get(&2), Some(&(4, 3)));
assert_eq!(pasp_by_id.get(&3), Some(&(40, 33))); assert_eq!(pasp_by_id.get(&3), Some(&(40, 33)));
// No recording references this video_sample_entry, so it gets dropped on upgrade.
assert_eq!(pasp_by_id.get(&4), None);
} }
} }

View File

@ -31,7 +31,7 @@
/// Upgrades a version 4 schema to a version 5 schema. /// Upgrades a version 4 schema to a version 5 schema.
use byteorder::{BigEndian, ByteOrder, WriteBytesExt}; use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
use failure::{Error, bail, format_err}; use failure::{Error, ResultExt, bail, format_err};
use h264_reader::avcc::AvcDecoderConfigurationRecord; use h264_reader::avcc::AvcDecoderConfigurationRecord;
use rusqlite::{named_params, params}; use rusqlite::{named_params, params};
use std::convert::{TryFrom, TryInto}; use std::convert::{TryFrom, TryInto};
@ -93,6 +93,10 @@ pub fn run(_args: &super::Args, tx: &rusqlite::Transaction) -> Result<(), Error>
values (:id, :width, :height, :rfc6381_codec, :data, values (:id, :width, :height, :rfc6381_codec, :data,
:pasp_h_spacing, :pasp_v_spacing) :pasp_h_spacing, :pasp_v_spacing)
"#)?; "#)?;
// Only insert still-referenced video sample entries. I've had problems with
// no-longer-referenced ones (perhaps from some ancient, buggy version of Moonfire NVR) for
// which avcc.create_context(()) fails.
let mut stmt = tx.prepare(r#" let mut stmt = tx.prepare(r#"
select select
id, id,
@ -101,7 +105,15 @@ pub fn run(_args: &super::Args, tx: &rusqlite::Transaction) -> Result<(), Error>
rfc6381_codec, rfc6381_codec,
data data
from from
old_video_sample_entry old_video_sample_entry v
where
exists (
select
'x'
from
recording r
where
r.video_sample_entry_id = v.id)
"#)?; "#)?;
let mut rows = stmt.query(params![])?; let mut rows = stmt.query(params![])?;
while let Some(row) = rows.next()? { while let Some(row) = rows.next()? {
@ -115,9 +127,10 @@ pub fn run(_args: &super::Args, tx: &rusqlite::Transaction) -> Result<(), Error>
bail!("Multiple SPSs!"); bail!("Multiple SPSs!");
} }
let ctx = avcc.create_context(()) let ctx = avcc.create_context(())
.map_err(|e| format_err!("Can't load SPS+PPS: {:?}", e))?; .map_err(|e| format_err!("Can't load SPS+PPS for video_sample_entry_id {}: {:?}",
id, e))?;
let sps = ctx.sps_by_id(h264_reader::nal::pps::ParamSetId::from_u32(0).unwrap()) let sps = ctx.sps_by_id(h264_reader::nal::pps::ParamSetId::from_u32(0).unwrap())
.ok_or_else(|| format_err!("No SPS 0"))?; .ok_or_else(|| format_err!("No SPS 0 for video_sample_entry_id {}", id))?;
let pasp = sps.vui_parameters.as_ref() let pasp = sps.vui_parameters.as_ref()
.and_then(|v| v.aspect_ratio_info.as_ref()) .and_then(|v| v.aspect_ratio_info.as_ref())
.and_then(|a| a.clone().get()) .and_then(|a| a.clone().get())
@ -257,8 +270,8 @@ pub fn run(_args: &super::Args, tx: &rusqlite::Transaction) -> Result<(), Error>
":video_samples": video_samples, ":video_samples": video_samples,
":video_sync_samples": video_sync_samples, ":video_sync_samples": video_sync_samples,
":video_sample_entry_id": video_sample_entry_id, ":video_sample_entry_id": video_sample_entry_id,
})?; }).with_context(|_| format!("Unable to insert composite_id {}", composite_id))?;
cum_duration_90k += duration_90k; cum_duration_90k += i64::from(duration_90k);
cum_runs += if run_offset == 0 { 1 } else { 0 }; cum_runs += if run_offset == 0 { 1 } else { 0 };
} }
tx.execute_batch(r#" tx.execute_batch(r#"