// This file is part of Moonfire NVR, a security camera network video recorder.
// Copyright (C) 2020 The Moonfire NVR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// In addition, as a special exception, the copyright holders give
// permission to link the code of portions of this program with the
// OpenSSL library under certain conditions as described in each
// individual source file, and distribute linked combinations including
// the two.
//
// You must obey the GNU General Public License in all respects for all
// of the code used other than OpenSSL. If you modify file(s) with this
// exception, you may extend this exception to your version of the
// file(s), but you are not obligated to do so. If you do not wish to do
// so, delete this exception statement from your version. If you delete
// this exception statement from all source files in the program, then
// also delete it here.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
/// Upgrades a version 4 schema to a version 5 schema.
use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
use failure::{Error, bail, format_err};
use h264_reader::avcc::AvcDecoderConfigurationRecord;
use rusqlite::{named_params, params};
use std::convert::{TryFrom, TryInto};
// Copied from src/h264.rs. h264 stuff really doesn't belong in the db crate, but we do what we
// must for schema upgrades.
const PIXEL_ASPECT_RATIOS: [((u16, u16), (u16, u16)); 4] = [
((320, 240), ( 4, 3)),
((352, 240), (40, 33)),
((640, 480), ( 4, 3)),
((704, 480), (40, 33)),
];
fn default_pixel_aspect_ratio(width: u16, height: u16) -> (u16, u16) {
let dims = (width, height);
for r in &PIXEL_ASPECT_RATIOS {
if r.0 == dims {
return r.1;
}
}
(1, 1)
}
fn parse(data: &[u8]) -> Result {
if data.len() < 94 || &data[4..8] != b"avc1" || &data[90..94] != b"avcC" {
bail!("data of len {} doesn't have an avcC", data.len());
}
let avcc_len = BigEndian::read_u32(&data[86..90]);
if avcc_len < 8 { // length and type.
bail!("invalid avcc len {}", avcc_len);
}
let end_pos = 86 + usize::try_from(avcc_len)?;
if end_pos != data.len() {
bail!("expected avcC to be end of extradata; there are {} more bytes.",
data.len() - end_pos);
}
AvcDecoderConfigurationRecord::try_from(&data[94..end_pos])
.map_err(|e| format_err!("Bad AvcDecoderConfigurationRecord: {:?}", e))
}
pub fn run(_args: &super::Args, tx: &rusqlite::Transaction) -> Result<(), Error> {
// These create statements match the schema.sql when version 5 was the latest.
tx.execute_batch(r#"
alter table video_sample_entry add column pasp_h_spacing integer not null default 1 check (pasp_h_spacing > 0);
alter table video_sample_entry add column pasp_v_spacing integer not null default 1 check (pasp_v_spacing > 0);
"#)?;
let mut update = tx.prepare(r#"
update video_sample_entry
set data = :data,
sha1 = :sha1,
pasp_h_spacing = :pasp_h_spacing,
pasp_v_spacing = :pasp_v_spacing
where id = :id
"#)?;
let mut stmt = tx.prepare(r#"
select
id,
width,
height,
data
from
video_sample_entry
"#)?;
let mut rows = stmt.query(params![])?;
while let Some(row) = rows.next()? {
let id: i32 = row.get(0)?;
let width: u16 = row.get::<_, i32>(1)?.try_into()?;
let height: u16 = row.get::<_, i32>(2)?.try_into()?;
let mut data: Vec = row.get(3)?;
let avcc = parse(&data)?;
if avcc.num_of_sequence_parameter_sets() != 1 {
bail!("Multiple SPSs!");
}
let ctx = avcc.create_context(())
.map_err(|e| format_err!("Can't load SPS+PPS: {:?}", e))?;
let sps = ctx.sps_by_id(h264_reader::nal::pps::ParamSetId::from_u32(0).unwrap())
.ok_or_else(|| format_err!("No SPS 0"))?;
let pasp = sps.vui_parameters.as_ref()
.and_then(|v| v.aspect_ratio_info.as_ref())
.and_then(|a| a.clone().get())
.unwrap_or_else(|| default_pixel_aspect_ratio(width, height));
if pasp != (1, 1) {
data.extend_from_slice(b"\x00\x00\x00\x10pasp"); // length + box name
data.write_u32::(pasp.0.into())?;
data.write_u32::(pasp.1.into())?;
let len = data.len();
BigEndian::write_u32(&mut data[0..4], u32::try_from(len)?);
}
update.execute_named(named_params!{
":id": id,
":data": &data,
":sha1": &crate::sha1(&data)?[..],
":pasp_h_spacing": pasp.0,
":pasp_v_spacing": pasp.1,
})?;
}
Ok(())
}