X-Git-Url: https://git.nihav.org/?p=nihav.git;a=blobdiff_plain;f=nihav-commonfmt%2Fsrc%2Fdemuxers%2Fmov.rs;h=9236baa1b0667d0132745e31e3465d959dafe193;hp=63280375ad59e1eb20e590b6171475fe559dcec6;hb=a480a0de101483d802a11e72d758dae00fa4860a;hpb=58309c73093e56494720028a2feddb11ceb2c0bc diff --git a/nihav-commonfmt/src/demuxers/mov.rs b/nihav-commonfmt/src/demuxers/mov.rs index 6328037..9236baa 100644 --- a/nihav-commonfmt/src/demuxers/mov.rs +++ b/nihav-commonfmt/src/demuxers/mov.rs @@ -1,5 +1,6 @@ use nihav_core::demuxers::*; use nihav_registry::register::*; +use nihav_core::compr::deflate::*; macro_rules! mktag { ($a:expr, $b:expr, $c:expr, $d:expr) => ({ @@ -40,6 +41,25 @@ fn read_chunk_header(br: &mut ByteReader) -> DemuxerResult<(u32, u64)> { } } +fn read_palette(br: &mut ByteReader, size: u64, pal: &mut [u8; 1024]) -> DemuxerResult { + let _seed = br.read_u32be()?; + let _flags = br.read_u16be()?; + let palsize = (br.read_u16be()? as usize) + 1; + validate!(palsize <= 256); + validate!((palsize as u64) * 8 + 8 == size); + for i in 0..palsize { + let a = br.read_u16be()?; + let r = br.read_u16be()?; + let g = br.read_u16be()?; + let b = br.read_u16be()?; + pal[i * 4] = (r >> 8) as u8; + pal[i * 4 + 1] = (g >> 8) as u8; + pal[i * 4 + 2] = (b >> 8) as u8; + pal[i * 4 + 3] = (a >> 8) as u8; + } + Ok(size) +} + struct RootChunkHandler { ctype: u32, parse: fn(dmx: &mut MOVDemuxer, strmgr: &mut StreamManager, size: u64) -> DemuxerResult, @@ -55,6 +75,7 @@ const IGNORED_CHUNKS: &[u32] = &[ ]; const ROOT_CHUNK_HANDLERS: &[RootChunkHandler] = &[ + RootChunkHandler { ctype: mktag!(b"ftyp"), parse: read_ftyp }, RootChunkHandler { ctype: mktag!(b"mdat"), parse: read_mdat }, RootChunkHandler { ctype: mktag!(b"moov"), parse: read_moov }, ]; @@ -130,6 +151,11 @@ fn skip_chunk(_track: &mut Track, br: &mut ByteReader, size: u64) -> DemuxerResu Ok(size) } +fn read_ftyp(dmx: &mut MOVDemuxer, _strmgr: &mut StreamManager, size: u64) -> DemuxerResult { + dmx.src.skip64(size)?; + Ok(size) +} + fn read_mdat(dmx: &mut MOVDemuxer, _strmgr: &mut StreamManager, size: u64) -> DemuxerResult { dmx.mdat_pos = dmx.src.tell(); dmx.mdat_size = size; @@ -144,7 +170,10 @@ fn read_moov(dmx: &mut MOVDemuxer, strmgr: &mut StreamManager, size: u64) -> Dem const MOOV_CHUNK_HANDLERS: &[RootChunkHandler] = &[ RootChunkHandler { ctype: mktag!(b"mvhd"), parse: read_mvhd }, + RootChunkHandler { ctype: mktag!(b"cmov"), parse: read_cmov }, + RootChunkHandler { ctype: mktag!(b"ctab"), parse: read_ctab }, RootChunkHandler { ctype: mktag!(b"trak"), parse: read_trak }, + RootChunkHandler { ctype: mktag!(b"meta"), parse: read_meta }, ]; fn read_mvhd(dmx: &mut MOVDemuxer, _strmgr: &mut StreamManager, size: u64) -> DemuxerResult { @@ -175,6 +204,57 @@ fn read_mvhd(dmx: &mut MOVDemuxer, _strmgr: &mut StreamManager, size: u64) -> De Ok(KNOWN_MVHD_SIZE) } +fn read_cmov(dmx: &mut MOVDemuxer, strmgr: &mut StreamManager, size: u64) -> DemuxerResult { + let br = &mut dmx.src; + validate!(size > 24); + let dcom_size = br.read_u32be()?; + let dcom_tag = br.read_tag()?; + let compr_type = br.read_tag()?; + validate!(&dcom_tag == b"dcom" && dcom_size == 12); + if &compr_type != b"zlib" { + return Err(DemuxerError::NotImplemented); + } + let cmvd_size = u64::from(br.read_u32be()?); + let cmvd_tag = br.read_tag()?; + validate!(&cmvd_tag == b"cmvd" && cmvd_size > 14 && cmvd_size == size - 12); + let comp_size = (cmvd_size - 12) as usize; + let uncomp_size = br.read_u32be()? as usize; + validate!(uncomp_size > 8); + let mut sbuf = vec![0; comp_size]; + let mut dbuf = vec![0; uncomp_size]; + br.read_buf(sbuf.as_mut_slice())?; + let ret = Inflate::uncompress(sbuf.as_slice(), dbuf.as_mut_slice()); + if ret.is_err() { + return Err(DemuxerError::InvalidData); + } + let len = ret.unwrap(); + validate!(len == uncomp_size); + let mut mr = MemoryReader::new_read(dbuf.as_slice()); + let mut br = ByteReader::new(&mut mr); + let (ctype, csize) = read_chunk_header(&mut br)?; + validate!(ctype == mktag!(b"moov")); + let mut ddmx = MOVDemuxer::new(&mut br); + ddmx.read_moov(strmgr, csize)?; + std::mem::swap(&mut dmx.tracks, &mut ddmx.tracks); + dmx.duration = ddmx.duration; + dmx.tb_den = ddmx.tb_den; + std::mem::swap(&mut dmx.pal, &mut ddmx.pal); + + Ok(size) +} + +fn read_ctab(dmx: &mut MOVDemuxer, _strmgr: &mut StreamManager, size: u64) -> DemuxerResult { + let mut pal = [0; 1024]; + let size = read_palette(&mut dmx.src, size, &mut pal)?; + dmx.pal = Some(Arc::new(pal)); + Ok(size) +} + +fn read_meta(dmx: &mut MOVDemuxer, _strmgr: &mut StreamManager, size: u64) -> DemuxerResult { + dmx.src.skip64(size)?; + Ok(size) +} + fn read_trak(dmx: &mut MOVDemuxer, strmgr: &mut StreamManager, size: u64) -> DemuxerResult { let mut track = Track::new(dmx.cur_track as u32, dmx.tb_den); track.read_trak(&mut dmx.src, size)?; @@ -212,7 +292,7 @@ fn read_tkhd(track: &mut Track, br: &mut ByteReader, size: u64) -> DemuxerResult let _mtime = br.read_u32be()?; let track_id = br.read_u32be()?; br.read_skip(4)?; - let _duration = br.read_u32be()?; + let duration = br.read_u32be()?; br.read_skip(8)?; let _layer = br.read_u16be()?; let _alt_group = br.read_u16be()?; @@ -224,6 +304,7 @@ fn read_tkhd(track: &mut Track, br: &mut ByteReader, size: u64) -> DemuxerResult track.width = width >> 16; track.height = height >> 16; track.track_id = track_id; + track.duration = duration; track.tkhd_found = true; Ok(KNOWN_TKHD_SIZE) @@ -268,7 +349,7 @@ fn read_hdlr(track: &mut Track, br: &mut ByteReader, size: u64) -> DemuxerResult println!("Unknown stream type"); track.stream_type = StreamType::Data; } - + Ok(KNOWN_HDLR_SIZE) } @@ -345,6 +426,47 @@ const STBL_CHUNK_HANDLERS: &[TrackChunkHandler] = &[ TrackChunkHandler { ctype: mktag!(b"stsh"), parse: skip_chunk }, ]; +fn parse_audio_edata(br: &mut ByteReader, start_pos: u64, size: u64) -> DemuxerResult>> { + let read_part = br.tell() - start_pos; + if read_part + 8 < size { + let mut buf = [0; 8]; + br.peek_buf(&mut buf)?; + if &buf[4..8] != b"wave" { + let mut buf = vec![0; (size - read_part) as usize]; + br.read_buf(&mut buf)?; + return Ok(Some(buf)); + } + + let csize = br.read_u32be()? as u64; + let ctag = br.read_u32be()?; + validate!(read_part + csize <= size); + validate!(ctag == mktag!(b"wave")); + if csize == 8 { + return Ok(None); + } + let mut buf = [0; 8]; + br.peek_buf(&mut buf)?; + if &buf[4..8] == b"frma" { + br.read_skip(12)?; + if csize > 20 { + let mut buf = vec![0; (csize - 20) as usize]; + br.read_buf(&mut buf)?; + Ok(Some(buf)) + } else { + Ok(None) + } + } else if csize > 8 { + let mut buf = vec![0; (csize as usize) - 8]; + br.read_buf(&mut buf)?; + Ok(Some(buf)) + } else { + Ok(None) + } + } else { + Ok(None) + } +} + fn read_stsd(track: &mut Track, br: &mut ByteReader, size: u64) -> DemuxerResult { const KNOWN_STSD_SIZE: u64 = 24; validate!(size >= KNOWN_STSD_SIZE); @@ -354,7 +476,7 @@ fn read_stsd(track: &mut Track, br: &mut ByteReader, size: u64) -> DemuxerResult let _flags = br.read_u24be()?; let entries = br.read_u32be()?; validate!(entries > 0); - let esize = br.read_u32be()? as u64; + let esize = u64::from(br.read_u32be()?); validate!(esize + 8 <= size); let mut fcc = [0u8; 4]; br.read_buf(&mut fcc)?; @@ -382,9 +504,50 @@ fn read_stsd(track: &mut Track, br: &mut ByteReader, size: u64) -> DemuxerResult br.read_skip(31)?; // actual compressor name let depth = br.read_u16be()?; let ctable_id = br.read_u16be()?; - validate!((depth <= 8) || (ctable_id == 0xFFFF)); + let grayscale = depth > 0x20 || depth == 1; + let depth = if grayscale { depth & 0x1F } else { depth }; + validate!(depth <= 8 || (ctable_id == 0xFFFF)); if ctable_id == 0 { -unimplemented!(); + let max_pal_size = start_pos + size - br.tell(); + let mut pal = [0; 1024]; + read_palette(br, max_pal_size, &mut pal)?; + track.pal = Some(Arc::new(pal)); + } else if (depth <= 8) && !grayscale { + match depth & 0x1F { + 2 => { + let mut pal = [0; 1024]; + (&mut pal[..4 * 4]).copy_from_slice(&MOV_DEFAULT_PAL_2BIT); + track.pal = Some(Arc::new(pal)); + }, + 4 => { + let mut pal = [0; 1024]; + (&mut pal[..16 * 4]).copy_from_slice(&MOV_DEFAULT_PAL_4BIT); + track.pal = Some(Arc::new(pal)); + }, + 8 => { + track.pal = Some(Arc::new(MOV_DEFAULT_PAL_8BIT)); + }, + _ => {}, + }; + } else if grayscale && ctable_id != 0xFFFF { + let mut pal = [0; 1024]; + let cdepth = depth & 0x1F; + let size = 1 << cdepth; + for i in 0..size { + let mut clr = ((size - 1 - i) as u8) << (8 - cdepth); + let mut off = 8 - cdepth; + while off >= cdepth { + clr |= clr >> (8 - off); + off -= cdepth; + } + if off > 0 { + clr |= clr >> (8 - off); + } + pal[i * 4] = clr; + pal[i * 4 + 1] = clr; + pal[i * 4 + 2] = clr; + } + track.pal = Some(Arc::new(pal)); } // todo other atoms, put as extradata let cname = if let Some(name) = find_codec_from_mov_video_fourcc(&fcc) { @@ -395,21 +558,22 @@ unimplemented!(); "unknown" }; let format = if depth > 8 { RGB24_FORMAT } else { PAL8_FORMAT }; - let vhdr = NAVideoInfo::new(width, height, false, format); - let edata; - if br.tell() - start_pos + 4 < size { + let mut vhdr = NAVideoInfo::new(width, height, false, format); + vhdr.bits = depth as u8; + let edata = if br.tell() - start_pos + 4 < size { //todo skip various common atoms - let edata_size = br.read_u32be()? as usize; - let mut buf = vec![0; edata_size]; + let edata_size = br.read_u32be()? as usize; + validate!(edata_size >= 4); + let mut buf = vec![0; edata_size - 4]; br.read_buf(buf.as_mut_slice())?; - edata = Some(buf); - } else { - edata = None; - } + Some(buf) + } else { + None + }; codec_info = NACodecInfo::new(cname, NACodecTypeInfo::Video(vhdr), edata); }, StreamType::Audio => { - let _ver = br.read_u16be()?; + let sver = br.read_u16be()?; let _revision = br.read_u16le()?; let _vendor = br.read_u32be()?; let nchannels = br.read_u16be()?; @@ -431,8 +595,18 @@ unimplemented!(); //todo adjust format for various PCM kinds let soniton = NASoniton::new(sample_size as u8, SONITON_FLAG_SIGNED | SONITON_FLAG_BE); let block_align = 1; + if sver == 1 { + let samples_per_packet = br.read_u32be()?; + let _bytes_per_packet = br.read_u32be()?; + let bytes_per_frame = br.read_u32be()?; + let _bytes_per_sample = br.read_u32be()?; + track.bsize = bytes_per_frame as usize; + track.frame_samples = samples_per_packet as usize; + } else { + track.bsize = sample_size as usize; + } let ahdr = NAAudioInfo::new(sample_rate >> 16, nchannels as u8, soniton, block_align); - let edata = None; + let edata = parse_audio_edata(br, start_pos, size)?; codec_info = NACodecInfo::new(cname, NACodecTypeInfo::Audio(ahdr), edata); track.channels = nchannels as usize; track.bits = sample_size as usize; @@ -448,7 +622,7 @@ unimplemented!(); }; let read_size = br.tell() - start_pos; validate!(read_size <= size); - track.stream = Some(NAStream::new(track.stream_type, track.track_no, codec_info, 1, track.tb_den)); + track.stream = Some(NAStream::new(track.stream_type, track.track_no, codec_info, 1, track.tb_den, u64::from(track.duration))); track.stsd_found = true; Ok(read_size) } @@ -498,6 +672,9 @@ fn read_stsz(track: &mut Track, br: &mut ByteReader, size: u64) -> DemuxerResult let sample_size = br.read_u32be()?; if sample_size != 0 { track.sample_size = sample_size; + if track.sample_size != 1 || track.bsize == 0 { + track.bsize = sample_size as usize; + } Ok(8) } else { let entries = br.read_u32be()? as usize; @@ -534,6 +711,7 @@ struct MOVDemuxer<'a> { cur_track: usize, tb_den: u32, duration: u32, + pal: Option>, } struct Track { @@ -541,6 +719,7 @@ struct Track { track_str_id: usize, track_no: u32, tb_den: u32, + duration: u32, depth: u8, tkhd_found: bool, stsd_found: bool, @@ -549,17 +728,20 @@ struct Track { height: usize, channels: usize, bits: usize, + bsize: usize, fcc: [u8; 4], keyframes: Vec, chunk_sizes: Vec, chunk_offsets: Vec, sample_map: Vec<(u32, u32)>, sample_size: u32, + frame_samples: usize, stream: Option, cur_chunk: usize, cur_sample: usize, samples_left: usize, last_offset: u64, + pal: Option>, } impl Track { @@ -571,23 +753,27 @@ impl Track { track_str_id: 0, track_no, tb_den, + duration: 0, stream_type: StreamType::None, width: 0, height: 0, channels: 0, bits: 0, + bsize: 0, fcc: [0; 4], keyframes: Vec::new(), chunk_sizes: Vec::new(), chunk_offsets: Vec::new(), sample_map: Vec::new(), sample_size: 0, + frame_samples: 0, stream: None, depth: 0, cur_chunk: 0, cur_sample: 0, samples_left: 0, last_offset: 0, + pal: None, } } read_chunk_list!(track; "trak", read_trak, TRAK_CHUNK_HANDLERS); @@ -595,7 +781,7 @@ impl Track { read_chunk_list!(track; "minf", read_minf, MINF_CHUNK_HANDLERS); read_chunk_list!(track; "stbl", read_stbl, STBL_CHUNK_HANDLERS); fn fill_seek_index(&self, seek_index: &mut SeekIndex) { - if self.keyframes.len() > 0 { + if !self.keyframes.is_empty() { seek_index.mode = SeekIndexMode::Present; } for kf_time in self.keyframes.iter() { @@ -610,7 +796,7 @@ impl Track { } fn calculate_chunk_size(&self, nsamp: usize) -> usize { if nsamp == 0 { - self.sample_size as usize + self.bsize } else { match &self.fcc { b"NONE" | b"raw " | b"twos" | b"sowt" => { @@ -636,7 +822,7 @@ impl Track { b"ms\x00\x21" => { //IMA ADPCM (nsamp / 2 + 4) * self.channels }, - _ => self.sample_size as usize, + _ => self.bsize, } } } @@ -671,6 +857,14 @@ impl Track { self.last_offset += size as u64; if self.stream_type == StreamType::Video { self.samples_left -= 1; + } else if self.frame_samples != 0 && self.bsize != 0 { + let nblocks = size / self.bsize; + if nblocks > 0 { + let consumed = (nblocks * self.frame_samples).min(self.samples_left); + self.samples_left -= consumed; + } else { + self.samples_left = 0; + } } else { self.samples_left = 0; } @@ -679,9 +873,9 @@ impl Track { } } fn get_size(&self, sample_no: usize) -> usize { - if self.chunk_sizes.len() > 0 { + if !self.chunk_sizes.is_empty() { self.chunk_sizes[sample_no] as usize - } else if self.sample_map.len() > 0 { + } else if !self.sample_map.is_empty() { let mut nsamp = 0; for (idx, samples) in self.sample_map.iter() { if *idx as usize <= self.cur_chunk { @@ -692,7 +886,7 @@ impl Track { } self.calculate_chunk_size(nsamp as usize) } else { - self.sample_size as usize + self.bsize } } fn seek(&mut self, pts: u64) { @@ -700,7 +894,7 @@ impl Track { self.samples_left = 0; if self.stream_type == StreamType::Audio { self.cur_chunk = self.cur_sample; - } else if self.chunk_offsets.len() != self.chunk_sizes.len() && self.sample_map.len() > 0{ + } else if self.chunk_offsets.len() != self.chunk_sizes.len() && !self.sample_map.is_empty() { let mut csamp = 0; self.cur_chunk = 0; let mut cmap = self.sample_map.iter(); @@ -735,7 +929,7 @@ impl<'a> DemuxCore<'a> for MOVDemuxer<'a> { fn open(&mut self, strmgr: &mut StreamManager, seek_index: &mut SeekIndex) -> DemuxerResult<()> { self.read_root(strmgr)?; validate!(self.mdat_pos > 0); - validate!(self.tracks.len() > 0); + validate!(!self.tracks.is_empty()); for track in self.tracks.iter() { track.fill_seek_index(seek_index); } @@ -745,7 +939,7 @@ impl<'a> DemuxCore<'a> for MOVDemuxer<'a> { } fn get_frame(&mut self, strmgr: &mut StreamManager) -> DemuxerResult { - if self.tracks.len() == 0 { + if self.tracks.is_empty() { return Err(DemuxerError::EOF); } for _ in 0..self.tracks.len() { @@ -754,19 +948,24 @@ impl<'a> DemuxCore<'a> for MOVDemuxer<'a> { } let track = &mut self.tracks[self.cur_track]; self.cur_track += 1; + let first = track.cur_sample == 0; if let Some((pts, offset, size)) = track.get_next_chunk() { let str = strmgr.get_stream(track.track_str_id); if str.is_none() { return Err(DemuxerError::InvalidData); } let stream = str.unwrap(); self.src.seek(SeekFrom::Start(offset))?; - let pkt = self.src.read_packet(stream, pts, false, size)?; + let mut pkt = self.src.read_packet(stream, pts, false, size)?; + if let Some(ref pal) = track.pal { + let side_data = NASideData::Palette(first, pal.clone()); + pkt.add_side_data(side_data); + } return Ok(pkt); } } - return Err(DemuxerError::EOF); + Err(DemuxerError::EOF) } - fn seek(&mut self, time: u64, seek_index: &SeekIndex) -> DemuxerResult<()> { + fn seek(&mut self, time: NATimePoint, seek_index: &SeekIndex) -> DemuxerResult<()> { let ret = seek_index.find_pos(time); if ret.is_none() { return Err(DemuxerError::SeekError); @@ -777,6 +976,19 @@ impl<'a> DemuxCore<'a> for MOVDemuxer<'a> { } Ok(()) } + fn get_duration(&self) -> u64 { + if self.tb_den != 0 { + u64::from(self.duration) * 1000 / u64::from(self.tb_den) + } else { + 0 + } + } +} + +impl<'a> NAOptionHandler for MOVDemuxer<'a> { + fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] } + fn set_options(&mut self, _options: &[NAOption]) { } + fn query_option_value(&self, _name: &str) -> Option { None } } impl<'a> MOVDemuxer<'a> { @@ -790,6 +1002,7 @@ impl<'a> MOVDemuxer<'a> { cur_track: 0, tb_den: 0, duration: 0, + pal: None, } } fn read_root(&mut self, strmgr: &mut StreamManager) -> DemuxerResult<()> { @@ -828,6 +1041,289 @@ impl DemuxerCreator for MOVDemuxerCreator { fn get_name(&self) -> &'static str { "mov" } } +const MOV_DEFAULT_PAL_2BIT: [u8; 4 * 4] = [ + 0x93, 0x65, 0x5E, 0x00, + 0xFF, 0xFF, 0xFF, 0x00, + 0xDF, 0xD0, 0xAB, 0x00, + 0x00, 0x00, 0x00, 0x00 +]; +const MOV_DEFAULT_PAL_4BIT: [u8; 16 * 4] = [ + 0xFF, 0xFB, 0xFF, 0x00, + 0xEF, 0xD9, 0xBB, 0x00, + 0xE8, 0xC9, 0xB1, 0x00, + 0x93, 0x65, 0x5E, 0x00, + 0xFC, 0xDE, 0xE8, 0x00, + 0x9D, 0x88, 0x91, 0x00, + 0xFF, 0xFF, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, 0x00, + 0x47, 0x48, 0x37, 0x00, + 0x7A, 0x5E, 0x55, 0x00, + 0xDF, 0xD0, 0xAB, 0x00, + 0xFF, 0xFB, 0xF9, 0x00, + 0xE8, 0xCA, 0xC5, 0x00, + 0x8A, 0x7C, 0x77, 0x00, + 0x00, 0x00, 0x00, 0x00 +]; +const MOV_DEFAULT_PAL_8BIT: [u8; 256 * 4] = [ + 0xFF, 0xFF, 0xFF, 0x00, + 0xFF, 0xFF, 0xCC, 0x00, + 0xFF, 0xFF, 0x99, 0x00, + 0xFF, 0xFF, 0x66, 0x00, + 0xFF, 0xFF, 0x33, 0x00, + 0xFF, 0xFF, 0x00, 0x00, + 0xFF, 0xCC, 0xFF, 0x00, + 0xFF, 0xCC, 0xCC, 0x00, + 0xFF, 0xCC, 0x99, 0x00, + 0xFF, 0xCC, 0x66, 0x00, + 0xFF, 0xCC, 0x33, 0x00, + 0xFF, 0xCC, 0x00, 0x00, + 0xFF, 0x99, 0xFF, 0x00, + 0xFF, 0x99, 0xCC, 0x00, + 0xFF, 0x99, 0x99, 0x00, + 0xFF, 0x99, 0x66, 0x00, + 0xFF, 0x99, 0x33, 0x00, + 0xFF, 0x99, 0x00, 0x00, + 0xFF, 0x66, 0xFF, 0x00, + 0xFF, 0x66, 0xCC, 0x00, + 0xFF, 0x66, 0x99, 0x00, + 0xFF, 0x66, 0x66, 0x00, + 0xFF, 0x66, 0x33, 0x00, + 0xFF, 0x66, 0x00, 0x00, + 0xFF, 0x33, 0xFF, 0x00, + 0xFF, 0x33, 0xCC, 0x00, + 0xFF, 0x33, 0x99, 0x00, + 0xFF, 0x33, 0x66, 0x00, + 0xFF, 0x33, 0x33, 0x00, + 0xFF, 0x33, 0x00, 0x00, + 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xCC, 0x00, + 0xFF, 0x00, 0x99, 0x00, + 0xFF, 0x00, 0x66, 0x00, + 0xFF, 0x00, 0x33, 0x00, + 0xFF, 0x00, 0x00, 0x00, + 0xCC, 0xFF, 0xFF, 0x00, + 0xCC, 0xFF, 0xCC, 0x00, + 0xCC, 0xFF, 0x99, 0x00, + 0xCC, 0xFF, 0x66, 0x00, + 0xCC, 0xFF, 0x33, 0x00, + 0xCC, 0xFF, 0x00, 0x00, + 0xCC, 0xCC, 0xFF, 0x00, + 0xCC, 0xCC, 0xCC, 0x00, + 0xCC, 0xCC, 0x99, 0x00, + 0xCC, 0xCC, 0x66, 0x00, + 0xCC, 0xCC, 0x33, 0x00, + 0xCC, 0xCC, 0x00, 0x00, + 0xCC, 0x99, 0xFF, 0x00, + 0xCC, 0x99, 0xCC, 0x00, + 0xCC, 0x99, 0x99, 0x00, + 0xCC, 0x99, 0x66, 0x00, + 0xCC, 0x99, 0x33, 0x00, + 0xCC, 0x99, 0x00, 0x00, + 0xCC, 0x66, 0xFF, 0x00, + 0xCC, 0x66, 0xCC, 0x00, + 0xCC, 0x66, 0x99, 0x00, + 0xCC, 0x66, 0x66, 0x00, + 0xCC, 0x66, 0x33, 0x00, + 0xCC, 0x66, 0x00, 0x00, + 0xCC, 0x33, 0xFF, 0x00, + 0xCC, 0x33, 0xCC, 0x00, + 0xCC, 0x33, 0x99, 0x00, + 0xCC, 0x33, 0x66, 0x00, + 0xCC, 0x33, 0x33, 0x00, + 0xCC, 0x33, 0x00, 0x00, + 0xCC, 0x00, 0xFF, 0x00, + 0xCC, 0x00, 0xCC, 0x00, + 0xCC, 0x00, 0x99, 0x00, + 0xCC, 0x00, 0x66, 0x00, + 0xCC, 0x00, 0x33, 0x00, + 0xCC, 0x00, 0x00, 0x00, + 0x99, 0xFF, 0xFF, 0x00, + 0x99, 0xFF, 0xCC, 0x00, + 0x99, 0xFF, 0x99, 0x00, + 0x99, 0xFF, 0x66, 0x00, + 0x99, 0xFF, 0x33, 0x00, + 0x99, 0xFF, 0x00, 0x00, + 0x99, 0xCC, 0xFF, 0x00, + 0x99, 0xCC, 0xCC, 0x00, + 0x99, 0xCC, 0x99, 0x00, + 0x99, 0xCC, 0x66, 0x00, + 0x99, 0xCC, 0x33, 0x00, + 0x99, 0xCC, 0x00, 0x00, + 0x99, 0x99, 0xFF, 0x00, + 0x99, 0x99, 0xCC, 0x00, + 0x99, 0x99, 0x99, 0x00, + 0x99, 0x99, 0x66, 0x00, + 0x99, 0x99, 0x33, 0x00, + 0x99, 0x99, 0x00, 0x00, + 0x99, 0x66, 0xFF, 0x00, + 0x99, 0x66, 0xCC, 0x00, + 0x99, 0x66, 0x99, 0x00, + 0x99, 0x66, 0x66, 0x00, + 0x99, 0x66, 0x33, 0x00, + 0x99, 0x66, 0x00, 0x00, + 0x99, 0x33, 0xFF, 0x00, + 0x99, 0x33, 0xCC, 0x00, + 0x99, 0x33, 0x99, 0x00, + 0x99, 0x33, 0x66, 0x00, + 0x99, 0x33, 0x33, 0x00, + 0x99, 0x33, 0x00, 0x00, + 0x99, 0x00, 0xFF, 0x00, + 0x99, 0x00, 0xCC, 0x00, + 0x99, 0x00, 0x99, 0x00, + 0x99, 0x00, 0x66, 0x00, + 0x99, 0x00, 0x33, 0x00, + 0x99, 0x00, 0x00, 0x00, + 0x66, 0xFF, 0xFF, 0x00, + 0x66, 0xFF, 0xCC, 0x00, + 0x66, 0xFF, 0x99, 0x00, + 0x66, 0xFF, 0x66, 0x00, + 0x66, 0xFF, 0x33, 0x00, + 0x66, 0xFF, 0x00, 0x00, + 0x66, 0xCC, 0xFF, 0x00, + 0x66, 0xCC, 0xCC, 0x00, + 0x66, 0xCC, 0x99, 0x00, + 0x66, 0xCC, 0x66, 0x00, + 0x66, 0xCC, 0x33, 0x00, + 0x66, 0xCC, 0x00, 0x00, + 0x66, 0x99, 0xFF, 0x00, + 0x66, 0x99, 0xCC, 0x00, + 0x66, 0x99, 0x99, 0x00, + 0x66, 0x99, 0x66, 0x00, + 0x66, 0x99, 0x33, 0x00, + 0x66, 0x99, 0x00, 0x00, + 0x66, 0x66, 0xFF, 0x00, + 0x66, 0x66, 0xCC, 0x00, + 0x66, 0x66, 0x99, 0x00, + 0x66, 0x66, 0x66, 0x00, + 0x66, 0x66, 0x33, 0x00, + 0x66, 0x66, 0x00, 0x00, + 0x66, 0x33, 0xFF, 0x00, + 0x66, 0x33, 0xCC, 0x00, + 0x66, 0x33, 0x99, 0x00, + 0x66, 0x33, 0x66, 0x00, + 0x66, 0x33, 0x33, 0x00, + 0x66, 0x33, 0x00, 0x00, + 0x66, 0x00, 0xFF, 0x00, + 0x66, 0x00, 0xCC, 0x00, + 0x66, 0x00, 0x99, 0x00, + 0x66, 0x00, 0x66, 0x00, + 0x66, 0x00, 0x33, 0x00, + 0x66, 0x00, 0x00, 0x00, + 0x33, 0xFF, 0xFF, 0x00, + 0x33, 0xFF, 0xCC, 0x00, + 0x33, 0xFF, 0x99, 0x00, + 0x33, 0xFF, 0x66, 0x00, + 0x33, 0xFF, 0x33, 0x00, + 0x33, 0xFF, 0x00, 0x00, + 0x33, 0xCC, 0xFF, 0x00, + 0x33, 0xCC, 0xCC, 0x00, + 0x33, 0xCC, 0x99, 0x00, + 0x33, 0xCC, 0x66, 0x00, + 0x33, 0xCC, 0x33, 0x00, + 0x33, 0xCC, 0x00, 0x00, + 0x33, 0x99, 0xFF, 0x00, + 0x33, 0x99, 0xCC, 0x00, + 0x33, 0x99, 0x99, 0x00, + 0x33, 0x99, 0x66, 0x00, + 0x33, 0x99, 0x33, 0x00, + 0x33, 0x99, 0x00, 0x00, + 0x33, 0x66, 0xFF, 0x00, + 0x33, 0x66, 0xCC, 0x00, + 0x33, 0x66, 0x99, 0x00, + 0x33, 0x66, 0x66, 0x00, + 0x33, 0x66, 0x33, 0x00, + 0x33, 0x66, 0x00, 0x00, + 0x33, 0x33, 0xFF, 0x00, + 0x33, 0x33, 0xCC, 0x00, + 0x33, 0x33, 0x99, 0x00, + 0x33, 0x33, 0x66, 0x00, + 0x33, 0x33, 0x33, 0x00, + 0x33, 0x33, 0x00, 0x00, + 0x33, 0x00, 0xFF, 0x00, + 0x33, 0x00, 0xCC, 0x00, + 0x33, 0x00, 0x99, 0x00, + 0x33, 0x00, 0x66, 0x00, + 0x33, 0x00, 0x33, 0x00, + 0x33, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0xFF, 0xCC, 0x00, + 0x00, 0xFF, 0x99, 0x00, + 0x00, 0xFF, 0x66, 0x00, + 0x00, 0xFF, 0x33, 0x00, + 0x00, 0xFF, 0x00, 0x00, + 0x00, 0xCC, 0xFF, 0x00, + 0x00, 0xCC, 0xCC, 0x00, + 0x00, 0xCC, 0x99, 0x00, + 0x00, 0xCC, 0x66, 0x00, + 0x00, 0xCC, 0x33, 0x00, + 0x00, 0xCC, 0x00, 0x00, + 0x00, 0x99, 0xFF, 0x00, + 0x00, 0x99, 0xCC, 0x00, + 0x00, 0x99, 0x99, 0x00, + 0x00, 0x99, 0x66, 0x00, + 0x00, 0x99, 0x33, 0x00, + 0x00, 0x99, 0x00, 0x00, + 0x00, 0x66, 0xFF, 0x00, + 0x00, 0x66, 0xCC, 0x00, + 0x00, 0x66, 0x99, 0x00, + 0x00, 0x66, 0x66, 0x00, + 0x00, 0x66, 0x33, 0x00, + 0x00, 0x66, 0x00, 0x00, + 0x00, 0x33, 0xFF, 0x00, + 0x00, 0x33, 0xCC, 0x00, + 0x00, 0x33, 0x99, 0x00, + 0x00, 0x33, 0x66, 0x00, + 0x00, 0x33, 0x33, 0x00, + 0x00, 0x33, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0x00, + 0x00, 0x00, 0xCC, 0x00, + 0x00, 0x00, 0x99, 0x00, + 0x00, 0x00, 0x66, 0x00, + 0x00, 0x00, 0x33, 0x00, + 0xEE, 0x00, 0x00, 0x00, + 0xDD, 0x00, 0x00, 0x00, + 0xBB, 0x00, 0x00, 0x00, + 0xAA, 0x00, 0x00, 0x00, + 0x88, 0x00, 0x00, 0x00, + 0x77, 0x00, 0x00, 0x00, + 0x55, 0x00, 0x00, 0x00, + 0x44, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, + 0x00, 0xEE, 0x00, 0x00, + 0x00, 0xDD, 0x00, 0x00, + 0x00, 0xBB, 0x00, 0x00, + 0x00, 0xAA, 0x00, 0x00, + 0x00, 0x88, 0x00, 0x00, + 0x00, 0x77, 0x00, 0x00, + 0x00, 0x55, 0x00, 0x00, + 0x00, 0x44, 0x00, 0x00, + 0x00, 0x22, 0x00, 0x00, + 0x00, 0x11, 0x00, 0x00, + 0x00, 0x00, 0xEE, 0x00, + 0x00, 0x00, 0xDD, 0x00, + 0x00, 0x00, 0xBB, 0x00, + 0x00, 0x00, 0xAA, 0x00, + 0x00, 0x00, 0x88, 0x00, + 0x00, 0x00, 0x77, 0x00, + 0x00, 0x00, 0x55, 0x00, + 0x00, 0x00, 0x44, 0x00, + 0x00, 0x00, 0x22, 0x00, + 0x00, 0x00, 0x11, 0x00, + 0xEE, 0xEE, 0xEE, 0x00, + 0xDD, 0xDD, 0xDD, 0x00, + 0xBB, 0xBB, 0xBB, 0x00, + 0xAA, 0xAA, 0xAA, 0x00, + 0x88, 0x88, 0x88, 0x00, + 0x77, 0x77, 0x77, 0x00, + 0x55, 0x55, 0x55, 0x00, + 0x44, 0x44, 0x44, 0x00, + 0x22, 0x22, 0x22, 0x00, + 0x11, 0x11, 0x11, 0x00, + 0x00, 0x00, 0x00, 0x00 +]; + #[cfg(test)] mod test { use super::*;