//! Module for parsing ISO Base Media Format aka video/mp4 streams.

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
#![cfg_attr(feature = "fuzz", feature(plugin))]
#![cfg_attr(feature = "fuzz", plugin(afl_plugin))]
#[cfg(feature = "fuzz")]
extern crate afl;

extern crate byteorder;
use byteorder::ReadBytesExt;
use std::io::{Read, Take};
use std::io::Cursor;
use std::cmp;

mod boxes;
use boxes::BoxType;

// Unit tests.
#[cfg(test)]
mod tests;

// Arbitrary buffer size limit used for raw read_bufs on a box.
const BUF_SIZE_LIMIT: u64 = 1024 * 1024;

static DEBUG_MODE: std::sync::atomic::AtomicBool = std::sync::atomic::ATOMIC_BOOL_INIT;

pub fn set_debug_mode(mode: bool) {
    DEBUG_MODE.store(mode, std::sync::atomic::Ordering::SeqCst);
}

#[inline(always)]
fn get_debug_mode() -> bool {
    DEBUG_MODE.load(std::sync::atomic::Ordering::Relaxed)
}

macro_rules! log {
    ($($args:tt)*) => (
        if get_debug_mode() {
            println!( $( $args )* );
        }
    )
}

/// Describes parser failures.
///
/// This enum wraps the standard `io::Error` type, unified with
/// our own parser error states and those of crates we use.
#[derive(Debug)]
pub enum Error {
    /// Parse error caused by corrupt or malformed data.
    InvalidData(&'static str),
    /// Parse error caused by limited parser support rather than invalid data.
    Unsupported(&'static str),
    /// Reflect `std::io::ErrorKind::UnexpectedEof` for short data.
    UnexpectedEOF,
    /// Propagate underlying errors from `std::io`.
    Io(std::io::Error),
    /// read_mp4 terminated without detecting a moov box.
    NoMoov,
}

impl From<std::io::Error> for Error {
    fn from(err: std::io::Error) -> Error {
        match err.kind() {
            std::io::ErrorKind::UnexpectedEof => Error::UnexpectedEOF,
            _ => Error::Io(err),
        }
    }
}

impl From<std::string::FromUtf8Error> for Error {
    fn from(_: std::string::FromUtf8Error) -> Error {
        Error::InvalidData("invalid utf8")
    }
}

/// Result shorthand using our Error enum.
pub type Result<T> = std::result::Result<T, Error>;

/// Basic ISO box structure.
///
/// mp4 files are a sequence of possibly-nested 'box' structures.  Each box
/// begins with a header describing the length of the box's data and a
/// four-byte box type which identifies the type of the box. Together these
/// are enough to interpret the contents of that section of the file.
#[derive(Debug, Clone, Copy)]
struct BoxHeader {
    /// Box type.
    name: BoxType,
    /// Size of the box in bytes.
    size: u64,
    /// Offset to the start of the contained data (or header size).
    offset: u64,
}

/// File type box 'ftyp'.
#[derive(Debug)]
struct FileTypeBox {
    major_brand: u32,
    minor_version: u32,
    compatible_brands: Vec<u32>,
}

/// Movie header box 'mvhd'.
#[derive(Debug)]
struct MovieHeaderBox {
    pub timescale: u32,
    duration: u64,
}

/// Track header box 'tkhd'
#[derive(Debug, Clone)]
pub struct TrackHeaderBox {
    track_id: u32,
    pub disabled: bool,
    pub duration: u64,
    pub width: u32,
    pub height: u32,
}

/// Edit list box 'elst'
#[derive(Debug)]
struct EditListBox {
    edits: Vec<Edit>,
}

#[derive(Debug)]
struct Edit {
    segment_duration: u64,
    media_time: i64,
    media_rate_integer: i16,
    media_rate_fraction: i16,
}

/// Media header box 'mdhd'
#[derive(Debug)]
struct MediaHeaderBox {
    timescale: u32,
    duration: u64,
}

// Chunk offset box 'stco' or 'co64'
#[derive(Debug)]
struct ChunkOffsetBox {
    offsets: Vec<u64>,
}

// Sync sample box 'stss'
#[derive(Debug)]
struct SyncSampleBox {
    samples: Vec<u32>,
}

// Sample to chunk box 'stsc'
#[derive(Debug)]
struct SampleToChunkBox {
    samples: Vec<SampleToChunk>,
}

#[derive(Debug)]
struct SampleToChunk {
    first_chunk: u32,
    samples_per_chunk: u32,
    sample_description_index: u32,
}

// Sample size box 'stsz'
#[derive(Debug)]
struct SampleSizeBox {
    sample_size: u32,
    sample_sizes: Vec<u32>,
}

// Time to sample box 'stts'
#[derive(Debug)]
struct TimeToSampleBox {
    samples: Vec<Sample>,
}

#[derive(Debug)]
struct Sample {
    sample_count: u32,
    sample_delta: u32,
}

// Handler reference box 'hdlr'
#[derive(Debug)]
struct HandlerBox {
    handler_type: u32,
}

// Sample description box 'stsd'
#[derive(Debug)]
struct SampleDescriptionBox {
    descriptions: Vec<SampleEntry>,
}

#[derive(Debug, Clone)]
pub enum SampleEntry {
    Audio(AudioSampleEntry),
    Video(VideoSampleEntry),
    Unknown,
}

#[allow(non_camel_case_types)]
#[derive(Debug, Clone)]
pub struct ES_Descriptor {
    pub audio_codec: CodecType,
    pub audio_sample_rate: Option<u32>,
    pub codec_specific_config: Vec<u8>,
}

#[allow(non_camel_case_types)]
#[derive(Debug, Clone)]
pub enum AudioCodecSpecific {
    ES_Descriptor(ES_Descriptor),
    FLACSpecificBox(FLACSpecificBox),
    OpusSpecificBox(OpusSpecificBox),
}

#[derive(Debug, Clone)]
pub struct AudioSampleEntry {
    data_reference_index: u16,
    pub channelcount: u16,
    pub samplesize: u16,
    pub samplerate: u32,
    pub codec_specific: AudioCodecSpecific,
}

#[derive(Debug, Clone)]
pub enum VideoCodecSpecific {
    AVCConfig(Vec<u8>),
    VPxConfig(VPxConfigBox),
}

#[derive(Debug, Clone)]
pub struct VideoSampleEntry {
    data_reference_index: u16,
    pub width: u16,
    pub height: u16,
    pub codec_specific: VideoCodecSpecific,
}

/// Represent a Video Partition Codec Configuration 'vpcC' box (aka vp9).
#[derive(Debug, Clone)]
pub struct VPxConfigBox {
    profile: u8,
    level: u8,
    pub bit_depth: u8,
    pub color_space: u8, // Really an enum
    pub chroma_subsampling: u8,
    transfer_function: u8,
    video_full_range: bool,
    pub codec_init: Vec<u8>, // Empty for vp8/vp9.
}

#[derive(Debug, Clone)]
pub struct FLACMetadataBlock {
    pub block_type: u8,
    pub data: Vec<u8>,
}

/// Represet a FLACSpecificBox 'dfLa'
#[derive(Debug, Clone)]
pub struct FLACSpecificBox {
    version: u8,
    pub blocks: Vec<FLACMetadataBlock>,
}

#[derive(Debug, Clone)]
struct ChannelMappingTable {
    stream_count: u8,
    coupled_count: u8,
    channel_mapping: Vec<u8>,
}

/// Represent an OpusSpecificBox 'dOps'
#[derive(Debug, Clone)]
pub struct OpusSpecificBox {
    pub version: u8,
    output_channel_count: u8,
    pre_skip: u16,
    input_sample_rate: u32,
    output_gain: i16,
    channel_mapping_family: u8,
    channel_mapping_table: Option<ChannelMappingTable>,
}

#[derive(Debug)]
pub struct MovieExtendsBox {
    pub fragment_duration: Option<MediaScaledTime>,
}

/// Internal data structures.
#[derive(Debug, Default)]
pub struct MediaContext {
    pub timescale: Option<MediaTimeScale>,
    /// Tracks found in the file.
    pub tracks: Vec<Track>,
    pub mvex: Option<MovieExtendsBox>,
}

impl MediaContext {
    pub fn new() -> MediaContext {
        Default::default()
    }
}

#[derive(Debug, PartialEq)]
pub enum TrackType {
    Audio,
    Video,
    Unknown,
}

impl Default for TrackType {
    fn default() -> Self { TrackType::Unknown }
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CodecType {
    Unknown,
    MP3,
    AAC,
    FLAC,
    Opus,
    H264,
    VP9,
    VP8,
    EncryptedVideo,
    EncryptedAudio,
}

impl Default for CodecType {
    fn default() -> Self { CodecType::Unknown }
}

/// The media's global (mvhd) timescale in units per second.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct MediaTimeScale(pub u64);

/// A time to be scaled by the media's global (mvhd) timescale.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct MediaScaledTime(pub u64);

/// The track's local (mdhd) timescale.
/// Members are timescale units per second and the track id.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct TrackTimeScale(pub u64, pub usize);

/// A time to be scaled by the track's local (mdhd) timescale.
/// Members are time in scale units and the track id.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct TrackScaledTime(pub u64, pub usize);

/// A fragmented file contains no sample data in stts, stsc, and stco.
#[derive(Debug, Default)]
pub struct EmptySampleTableBoxes {
    pub empty_stts : bool,
    pub empty_stsc : bool,
    pub empty_stco : bool,
}

/// Check boxes contain data.
impl EmptySampleTableBoxes {
    pub fn all_empty(&self) -> bool {
        self.empty_stts & self.empty_stsc & self.empty_stco
    }
}

#[derive(Debug, Default)]
pub struct Track {
    id: usize,
    pub track_type: TrackType,
    pub empty_duration: Option<MediaScaledTime>,
    pub media_time: Option<TrackScaledTime>,
    pub timescale: Option<TrackTimeScale>,
    pub duration: Option<TrackScaledTime>,
    pub track_id: Option<u32>,
    pub codec_type: CodecType,
    pub empty_sample_boxes: EmptySampleTableBoxes,
    pub data: Option<SampleEntry>,
    pub tkhd: Option<TrackHeaderBox>, // TODO(kinetik): find a nicer way to export this.
}

impl Track {
    fn new(id: usize) -> Track {
        Track { id: id, ..Default::default() }
    }
}

struct BMFFBox<'a, T: 'a + Read> {
    head: BoxHeader,
    content: Take<&'a mut T>,
}

struct BoxIter<'a, T: 'a + Read> {
    src: &'a mut T,
}

impl<'a, T: Read> BoxIter<'a, T> {
    fn new(src: &mut T) -> BoxIter<T> {
        BoxIter { src: src }
    }

    fn next_box(&mut self) -> Result<Option<BMFFBox<T>>> {
        let r = read_box_header(self.src);
        match r {
            Ok(h) => Ok(Some(BMFFBox {
                head: h,
                content: self.src.take(h.size - h.offset),
            })),
            Err(Error::UnexpectedEOF) => Ok(None),
            Err(e) => Err(e),
        }
    }
}

impl<'a, T: Read> Read for BMFFBox<'a, T> {
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
        self.content.read(buf)
    }
}

impl<'a, T: Read> BMFFBox<'a, T> {
    fn bytes_left(&self) -> usize {
        self.content.limit() as usize
    }

    fn get_header(&self) -> &BoxHeader {
        &self.head
    }

    fn box_iter<'b>(&'b mut self) -> BoxIter<BMFFBox<'a, T>> {
        BoxIter::new(self)
    }
}

/// Read and parse a box header.
///
/// Call this first to determine the type of a particular mp4 box
/// and its length. Used internally for dispatching to specific
/// parsers for the internal content, or to get the length to
/// skip unknown or uninteresting boxes.
fn read_box_header<T: ReadBytesExt>(src: &mut T) -> Result<BoxHeader> {
    let size32 = try!(be_u32(src));
    let name = BoxType::from(try!(be_u32(src)));
    let size = match size32 {
        // valid only for top-level box and indicates it's the last box in the file.  usually mdat.
        0 => return Err(Error::Unsupported("unknown sized box")),
        1 => {
            let size64 = try!(be_u64(src));
            if size64 < 16 {
                return Err(Error::InvalidData("malformed wide size"));
            }
            size64
        }
        2...7 => return Err(Error::InvalidData("malformed size")),
        _ => size32 as u64,
    };
    let offset = match size32 {
        1 => 4 + 4 + 8,
        _ => 4 + 4,
    };
    assert!(offset <= size);
    Ok(BoxHeader {
        name: name,
        size: size,
        offset: offset,
    })
}

/// Parse the extra header fields for a full box.
fn read_fullbox_extra<T: ReadBytesExt>(src: &mut T) -> Result<(u8, u32)> {
    let version = try!(src.read_u8());
    let flags_a = try!(src.read_u8());
    let flags_b = try!(src.read_u8());
    let flags_c = try!(src.read_u8());
    Ok((version,
        (flags_a as u32) << 16 | (flags_b as u32) << 8 | (flags_c as u32)))
}

/// Skip over the entire contents of a box.
fn skip_box_content<T: Read>(src: &mut BMFFBox<T>) -> Result<()> {
    // Skip the contents of unknown chunks.
    let to_skip = {
        let header = src.get_header();
        log!("{:?} (skipped)", header);
        (header.size - header.offset) as usize
    };
    assert!(to_skip == src.bytes_left());
    skip(src, to_skip)
}

/// Skip over the remain data of a box.
fn skip_box_remain<T: Read>(src: &mut BMFFBox<T>) -> Result<()> {
    let remain = {
        let header = src.get_header();
        let len = src.bytes_left();
        log!("remain {} (skipped) in {:?}", len, header);
        len
    };
    skip(src, remain)
}

macro_rules! check_parser_state {
    ( $src:expr ) => {
        if $src.limit() > 0 {
            log!("bad parser state: {} content bytes left", $src.limit());
            return Err(Error::InvalidData("unread box content or bad parser sync"));
        }
    }
}

/// Read the contents of a box, including sub boxes.
///
/// Metadata is accumulated in the passed-through `MediaContext` struct,
/// which can be examined later.
pub fn read_mp4<T: Read>(f: &mut T, context: &mut MediaContext) -> Result<()> {
    let mut found_ftyp = false;
    let mut found_moov = false;
    // TODO(kinetik): Top-level parsing should handle zero-sized boxes
    // rather than throwing an error.
    let mut iter = BoxIter::new(f);
    while let Some(mut b) = try!(iter.next_box()) {
        // box ordering: ftyp before any variable length box (inc. moov),
        // but may not be first box in file if file signatures etc. present
        // fragmented mp4 order: ftyp, moov, pairs of moof/mdat (1-multiple), mfra

        // "special": uuid, wide (= 8 bytes)
        // isom: moov, mdat, free, skip, udta, ftyp, moof, mfra
        // iso2: pdin, meta
        // iso3: meco
        // iso5: styp, sidx, ssix, prft
        // unknown, maybe: id32

        // qt: pnot

        // possibly allow anything where all printable and/or all lowercase printable
        // "four printable characters from the ISO 8859-1 character set"
        match b.head.name {
            BoxType::FileTypeBox => {
                let ftyp = try!(read_ftyp(&mut b));
                found_ftyp = true;
                log!("{:?}", ftyp);
            }
            BoxType::MovieBox => {
                try!(read_moov(&mut b, context));
                found_moov = true;
            }
            _ => try!(skip_box_content(&mut b)),
        };
        check_parser_state!(b.content);
        if found_moov {
            log!("found moov {}, could stop pure 'moov' parser now", if found_ftyp {
                "and ftyp"
            } else {
                "but no ftyp"
            });
        }
    }

    // XXX(kinetik): This isn't perfect, as a "moov" with no contents is
    // treated as okay but we haven't found anything useful.  Needs more
    // thought for clearer behaviour here.
    if found_moov {
        Ok(())
    } else {
        Err(Error::NoMoov)
    }
}

fn parse_mvhd<T: Read>(f: &mut BMFFBox<T>) -> Result<(MovieHeaderBox, Option<MediaTimeScale>)> {
    let mvhd = try!(read_mvhd(f));
    if mvhd.timescale == 0 {
        return Err(Error::InvalidData("zero timescale in mdhd"));
    }
    let timescale = Some(MediaTimeScale(mvhd.timescale as u64));
    Ok((mvhd, timescale))
}

fn read_moov<T: Read>(f: &mut BMFFBox<T>, context: &mut MediaContext) -> Result<()> {
    let mut iter = f.box_iter();
    while let Some(mut b) = try!(iter.next_box()) {
        match b.head.name {
            BoxType::MovieHeaderBox => {
                let (mvhd, timescale) = try!(parse_mvhd(&mut b));
                context.timescale = timescale;
                log!("{:?}", mvhd);
            }
            BoxType::TrackBox => {
                let mut track = Track::new(context.tracks.len());
                try!(read_trak(&mut b, &mut track));
                context.tracks.push(track);
            }
            BoxType::MovieExtendsBox => {
                let mvex = try!(read_mvex(&mut b));
                log!("{:?}", mvex);
                context.mvex = Some(mvex);
            }
            _ => try!(skip_box_content(&mut b)),
        };
        check_parser_state!(b.content);
    }
    Ok(())
}

fn read_mvex<T: Read>(src: &mut BMFFBox<T>) -> Result<MovieExtendsBox> {
    let mut iter = src.box_iter();
    let mut fragment_duration = None;
    while let Some(mut b) = try!(iter.next_box()) {
        match b.head.name {
            BoxType::MovieExtendsHeaderBox => {
                let duration = try!(read_mehd(&mut b));
                fragment_duration = Some(duration);
            },
            _ => try!(skip_box_content(&mut b)),
        }
    }
    Ok(MovieExtendsBox {
        fragment_duration: fragment_duration,
    })
}

fn read_mehd<T: Read>(src: &mut BMFFBox<T>) -> Result<MediaScaledTime> {
    let (version, _) = try!(read_fullbox_extra(src));
    let fragment_duration = match version {
        1 => try!(be_u64(src)),
        0 => try!(be_u32(src)) as u64,
        _ => return Err(Error::InvalidData("unhandled mehd version")),
    };
    Ok(MediaScaledTime(fragment_duration))
}

fn read_trak<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<()> {
    let mut iter = f.box_iter();
    while let Some(mut b) = try!(iter.next_box()) {
        match b.head.name {
            BoxType::TrackHeaderBox => {
                let tkhd = try!(read_tkhd(&mut b));
                track.track_id = Some(tkhd.track_id);
                track.tkhd = Some(tkhd.clone());
                log!("{:?}", tkhd);
            }
            BoxType::EditBox => try!(read_edts(&mut b, track)),
            BoxType::MediaBox => try!(read_mdia(&mut b, track)),
            _ => try!(skip_box_content(&mut b)),
        };
        check_parser_state!(b.content);
    }
    Ok(())
}

fn read_edts<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<()> {
    let mut iter = f.box_iter();
    while let Some(mut b) = try!(iter.next_box()) {
        match b.head.name {
            BoxType::EditListBox => {
                let elst = try!(read_elst(&mut b));
                let mut empty_duration = 0;
                let mut idx = 0;
                if elst.edits.len() > 2 {
                    return Err(Error::Unsupported("more than two edits"));
                }
                if elst.edits[idx].media_time == -1 {
                    empty_duration = elst.edits[idx].segment_duration;
                    if elst.edits.len() < 2 {
                        return Err(Error::InvalidData("expected additional edit"));
                    }
                    idx += 1;
                }
                track.empty_duration = Some(MediaScaledTime(empty_duration));
                if elst.edits[idx].media_time < 0 {
                    return Err(Error::InvalidData("unexpected negative media time in edit"));
                }
                track.media_time = Some(TrackScaledTime(elst.edits[idx].media_time as u64,
                                                        track.id));
                log!("{:?}", elst);
            }
            _ => try!(skip_box_content(&mut b)),
        };
        check_parser_state!(b.content);
    }
    Ok(())
}

fn parse_mdhd<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<(MediaHeaderBox, Option<TrackScaledTime>, Option<TrackTimeScale>)> {
    let mdhd = try!(read_mdhd(f));
    let duration = match mdhd.duration {
        std::u64::MAX => None,
        duration => Some(TrackScaledTime(duration, track.id)),
    };
    if mdhd.timescale == 0 {
        return Err(Error::InvalidData("zero timescale in mdhd"));
    }
    let timescale = Some(TrackTimeScale(mdhd.timescale as u64, track.id));
    Ok((mdhd, duration, timescale))
}

fn read_mdia<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<()> {
    let mut iter = f.box_iter();
    while let Some(mut b) = try!(iter.next_box()) {
        match b.head.name {
            BoxType::MediaHeaderBox => {
                let (mdhd, duration, timescale) = try!(parse_mdhd(&mut b, track));
                track.duration = duration;
                track.timescale = timescale;
                log!("{:?}", mdhd);
            }
            BoxType::HandlerBox => {
                let hdlr = try!(read_hdlr(&mut b));
                match hdlr.handler_type {
                    0x76696465 /* 'vide' */ => track.track_type = TrackType::Video,
                    0x736f756e /* 'soun' */ => track.track_type = TrackType::Audio,
                    _ => (),
                }
                log!("{:?}", hdlr);
            }
            BoxType::MediaInformationBox => try!(read_minf(&mut b, track)),
            _ => try!(skip_box_content(&mut b)),
        };
        check_parser_state!(b.content);
    }
    Ok(())
}

fn read_minf<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<()> {
    let mut iter = f.box_iter();
    while let Some(mut b) = try!(iter.next_box()) {
        match b.head.name {
            BoxType::SampleTableBox => try!(read_stbl(&mut b, track)),
            _ => try!(skip_box_content(&mut b)),
        };
        check_parser_state!(b.content);
    }
    Ok(())
}

fn read_stbl<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<()> {
    let mut iter = f.box_iter();
    while let Some(mut b) = try!(iter.next_box()) {
        match b.head.name {
            BoxType::SampleDescriptionBox => {
                let stsd = try!(read_stsd(&mut b, track));
                log!("{:?}", stsd);
            }
            BoxType::TimeToSampleBox => {
                let stts = try!(read_stts(&mut b));
                track.empty_sample_boxes.empty_stts = stts.samples.is_empty();
                log!("{:?}", stts);
            }
            BoxType::SampleToChunkBox => {
                let stsc = try!(read_stsc(&mut b));
                track.empty_sample_boxes.empty_stsc = stsc.samples.is_empty();
                log!("{:?}", stsc);
            }
            BoxType::SampleSizeBox => {
                let stsz = try!(read_stsz(&mut b));
                log!("{:?}", stsz);
            }
            BoxType::ChunkOffsetBox => {
                let stco = try!(read_stco(&mut b));
                track.empty_sample_boxes.empty_stco = stco.offsets.is_empty();
                log!("{:?}", stco);
            }
            BoxType::ChunkLargeOffsetBox => {
                let co64 = try!(read_co64(&mut b));
                log!("{:?}", co64);
            }
            BoxType::SyncSampleBox => {
                let stss = try!(read_stss(&mut b));
                log!("{:?}", stss);
            }
            _ => try!(skip_box_content(&mut b)),
        };
        check_parser_state!(b.content);
    }
    Ok(())
}

/// Parse an ftyp box.
fn read_ftyp<T: Read>(src: &mut BMFFBox<T>) -> Result<FileTypeBox> {
    let major = try!(be_u32(src));
    let minor = try!(be_u32(src));
    let bytes_left = src.bytes_left();
    if bytes_left % 4 != 0 {
        return Err(Error::InvalidData("invalid ftyp size"));
    }
    // Is a brand_count of zero valid?
    let brand_count = bytes_left / 4;
    let mut brands = Vec::new();
    for _ in 0..brand_count {
        brands.push(try!(be_u32(src)));
    }
    Ok(FileTypeBox {
        major_brand: major,
        minor_version: minor,
        compatible_brands: brands,
    })
}

/// Parse an mvhd box.
fn read_mvhd<T: Read>(src: &mut BMFFBox<T>) -> Result<MovieHeaderBox> {
    let (version, _) = try!(read_fullbox_extra(src));
    match version {
        // 64 bit creation and modification times.
        1 => {
            try!(skip(src, 16));
        }
        // 32 bit creation and modification times.
        0 => {
            try!(skip(src, 8));
        }
        _ => return Err(Error::InvalidData("unhandled mvhd version")),
    }
    let timescale = try!(be_u32(src));
    let duration = match version {
        1 => try!(be_u64(src)),
        0 => {
            let d = try!(be_u32(src));
            if d == std::u32::MAX {
                std::u64::MAX
            } else {
                d as u64
            }
        }
        _ => return Err(Error::InvalidData("unhandled mvhd version")),
    };
    // Skip remaining fields.
    try!(skip(src, 80));
    Ok(MovieHeaderBox {
        timescale: timescale,
        duration: duration,
    })
}

/// Parse a tkhd box.
fn read_tkhd<T: Read>(src: &mut BMFFBox<T>) -> Result<TrackHeaderBox> {
    let (version, flags) = try!(read_fullbox_extra(src));
    let disabled = flags & 0x1u32 == 0 || flags & 0x2u32 == 0;
    match version {
        // 64 bit creation and modification times.
        1 => {
            try!(skip(src, 16));
        }
        // 32 bit creation and modification times.
        0 => {
            try!(skip(src, 8));
        }
        _ => return Err(Error::InvalidData("unhandled tkhd version")),
    }
    let track_id = try!(be_u32(src));
    try!(skip(src, 4));
    let duration = match version {
        1 => try!(be_u64(src)),
        0 => try!(be_u32(src)) as u64,
        _ => return Err(Error::InvalidData("unhandled tkhd version")),
    };
    // Skip uninteresting fields.
    try!(skip(src, 52));
    let width = try!(be_u32(src));
    let height = try!(be_u32(src));
    Ok(TrackHeaderBox {
        track_id: track_id,
        disabled: disabled,
        duration: duration,
        width: width,
        height: height,
    })
}

/// Parse a elst box.
fn read_elst<T: Read>(src: &mut BMFFBox<T>) -> Result<EditListBox> {
    let (version, _) = try!(read_fullbox_extra(src));
    let edit_count = try!(be_u32(src));
    if edit_count == 0 {
        return Err(Error::InvalidData("invalid edit count"));
    }
    let mut edits = Vec::new();
    for _ in 0..edit_count {
        let (segment_duration, media_time) = match version {
            1 => {
                // 64 bit segment duration and media times.
                (try!(be_u64(src)), try!(be_i64(src)))
            }
            0 => {
                // 32 bit segment duration and media times.
                (try!(be_u32(src)) as u64, try!(be_i32(src)) as i64)
            }
            _ => return Err(Error::InvalidData("unhandled elst version")),
        };
        let media_rate_integer = try!(be_i16(src));
        let media_rate_fraction = try!(be_i16(src));
        edits.push(Edit {
            segment_duration: segment_duration,
            media_time: media_time,
            media_rate_integer: media_rate_integer,
            media_rate_fraction: media_rate_fraction,
        })
    }

    Ok(EditListBox {
        edits: edits,
    })
}

/// Parse a mdhd box.
fn read_mdhd<T: Read>(src: &mut BMFFBox<T>) -> Result<MediaHeaderBox> {
    let (version, _) = try!(read_fullbox_extra(src));
    let (timescale, duration) = match version {
        1 => {
            // Skip 64-bit creation and modification times.
            try!(skip(src, 16));

            // 64 bit duration.
            (try!(be_u32(src)), try!(be_u64(src)))
        }
        0 => {
            // Skip 32-bit creation and modification times.
            try!(skip(src, 8));

            // 32 bit duration.
            let timescale = try!(be_u32(src));
            let duration = {
                // Since we convert the 32-bit duration to 64-bit by
                // upcasting, we need to preserve the special all-1s
                // ("unknown") case by hand.
                let d = try!(be_u32(src));
                if d == std::u32::MAX {
                    std::u64::MAX
                } else {
                    d as u64
                }
            };
            (timescale, duration)
        }
        _ => return Err(Error::InvalidData("unhandled mdhd version")),
    };

    // Skip uninteresting fields.
    try!(skip(src, 4));

    Ok(MediaHeaderBox {
        timescale: timescale,
        duration: duration,
    })
}

/// Parse a stco box.
fn read_stco<T: Read>(src: &mut BMFFBox<T>) -> Result<ChunkOffsetBox> {
    let (_, _) = try!(read_fullbox_extra(src));
    let offset_count = try!(be_u32(src));
    let mut offsets = Vec::new();
    for _ in 0..offset_count {
        offsets.push(try!(be_u32(src)) as u64);
    }

    // Padding could be added in some contents.
    try!(skip_box_remain(src));

    Ok(ChunkOffsetBox {
        offsets: offsets,
    })
}

/// Parse a co64 box.
fn read_co64<T: Read>(src: &mut BMFFBox<T>) -> Result<ChunkOffsetBox> {
    let (_, _) = try!(read_fullbox_extra(src));
    let offset_count = try!(be_u32(src));
    let mut offsets = Vec::new();
    for _ in 0..offset_count {
        offsets.push(try!(be_u64(src)));
    }

    // Padding could be added in some contents.
    try!(skip_box_remain(src));

    Ok(ChunkOffsetBox {
        offsets: offsets,
    })
}

/// Parse a stss box.
fn read_stss<T: Read>(src: &mut BMFFBox<T>) -> Result<SyncSampleBox> {
    let (_, _) = try!(read_fullbox_extra(src));
    let sample_count = try!(be_u32(src));
    let mut samples = Vec::new();
    for _ in 0..sample_count {
        samples.push(try!(be_u32(src)));
    }

    // Padding could be added in some contents.
    try!(skip_box_remain(src));

    Ok(SyncSampleBox {
        samples: samples,
    })
}

/// Parse a stsc box.
fn read_stsc<T: Read>(src: &mut BMFFBox<T>) -> Result<SampleToChunkBox> {
    let (_, _) = try!(read_fullbox_extra(src));
    let sample_count = try!(be_u32(src));
    let mut samples = Vec::new();
    for _ in 0..sample_count {
        let first_chunk = try!(be_u32(src));
        let samples_per_chunk = try!(be_u32(src));
        let sample_description_index = try!(be_u32(src));
        samples.push(SampleToChunk {
            first_chunk: first_chunk,
            samples_per_chunk: samples_per_chunk,
            sample_description_index: sample_description_index,
        });
    }

    // Padding could be added in some contents.
    try!(skip_box_remain(src));

    Ok(SampleToChunkBox {
        samples: samples,
    })
}

/// Parse a stsz box.
fn read_stsz<T: Read>(src: &mut BMFFBox<T>) -> Result<SampleSizeBox> {
    let (_, _) = try!(read_fullbox_extra(src));
    let sample_size = try!(be_u32(src));
    let sample_count = try!(be_u32(src));
    let mut sample_sizes = Vec::new();
    if sample_size == 0 {
        for _ in 0..sample_count {
            sample_sizes.push(try!(be_u32(src)));
        }
    }

    // Padding could be added in some contents.
    try!(skip_box_remain(src));

    Ok(SampleSizeBox {
        sample_size: sample_size,
        sample_sizes: sample_sizes,
    })
}

/// Parse a stts box.
fn read_stts<T: Read>(src: &mut BMFFBox<T>) -> Result<TimeToSampleBox> {
    let (_, _) = try!(read_fullbox_extra(src));
    let sample_count = try!(be_u32(src));
    let mut samples = Vec::new();
    for _ in 0..sample_count {
        let sample_count = try!(be_u32(src));
        let sample_delta = try!(be_u32(src));
        samples.push(Sample {
            sample_count: sample_count,
            sample_delta: sample_delta,
        });
    }

    // Padding could be added in some contents.
    try!(skip_box_remain(src));

    Ok(TimeToSampleBox {
        samples: samples,
    })
}

/// Parse a VPx Config Box.
fn read_vpcc<T: Read>(src: &mut BMFFBox<T>) -> Result<VPxConfigBox> {
    let (version, _) = try!(read_fullbox_extra(src));
    if version != 0 {
        return Err(Error::Unsupported("unknown vpcC version"));
    }

    let profile = try!(src.read_u8());
    let level = try!(src.read_u8());
    let (bit_depth, color_space) = {
        let byte = try!(src.read_u8());
        ((byte >> 4) & 0x0f, byte & 0x0f)
    };
    let (chroma_subsampling, transfer_function, video_full_range) = {
        let byte = try!(src.read_u8());
        ((byte >> 4) & 0x0f, (byte >> 1) & 0x07, (byte & 1) == 1)
    };

    let codec_init_size = try!(be_u16(src));
    let codec_init = try!(read_buf(src, codec_init_size as usize));

    // TODO(rillian): validate field value ranges.
    Ok(VPxConfigBox {
        profile: profile,
        level: level,
        bit_depth: bit_depth,
        color_space: color_space,
        chroma_subsampling: chroma_subsampling,
        transfer_function: transfer_function,
        video_full_range: video_full_range,
        codec_init: codec_init,
    })
}

fn read_flac_metadata<T: Read>(src: &mut BMFFBox<T>) -> Result<FLACMetadataBlock> {
    let temp = try!(src.read_u8());
    let block_type = temp & 0x7f;
    let length = try!(be_u24(src));
    if length as usize > src.bytes_left() {
        return Err(Error::InvalidData(
                "FLACMetadataBlock larger than parent box"));
    }
    let data = try!(read_buf(src, length as usize));
    Ok(FLACMetadataBlock {
        block_type: block_type,
        data: data,
    })
}

fn read_esds<T: Read>(src: &mut BMFFBox<T>) -> Result<ES_Descriptor> {
    // Tags for elementary stream description
    const ESDESCR_TAG: u8          = 0x03;
    const DECODER_CONFIG_TAG: u8   = 0x04;
    const DECODER_SPECIFIC_TAG: u8 = 0x05;

    let frequency_table =
        vec![(0x1, 96000), (0x1, 88200), (0x2, 64000), (0x3, 48000),
             (0x4, 44100), (0x5, 32000), (0x6, 24000), (0x7, 22050),
             (0x8, 16000), (0x9, 12000), (0xa, 11025), (0xb, 8000),
             (0xc, 7350)];

    let (_, _) = try!(read_fullbox_extra(src));

    let esds_size = src.head.size - src.head.offset - 4;
    if esds_size > BUF_SIZE_LIMIT {
        return Err(Error::InvalidData("esds box exceeds BUF_SIZE_LIMIT"));
    }
    let esds_array = try!(read_buf(src, esds_size as usize));

    // Parsing DecoderConfig descriptor to get the object_profile_indicator
    // for correct codec type and audio sample rate.
    let (object_profile_indicator, sample_frequency) = {
        let mut object_profile: u8 = 0;
        let mut sample_frequency = None;

        // clone a esds cursor for parsing.
        let esds = &mut Cursor::new(&esds_array);
        let next_tag = try!(esds.read_u8());

        if next_tag != ESDESCR_TAG {
            return Err(Error::Unsupported("fail to parse ES descriptor"));
        }

        let esds_extend = try!(esds.read_u8());
        // extension tag start from 0x80.
        let esds_end = if esds_extend >= 0x80 {
            // skip remaining extension.
            try!(skip(esds, 2));
            esds.position() + try!(esds.read_u8()) as u64
        } else {
            esds.position() + esds_extend as u64
        };
        try!(skip(esds, 2));

        let esds_flags = try!(esds.read_u8());

        // Stream dependency flag, first bit from left most.
        if esds_flags & 0x80 > 0 {
            // Skip uninteresting fields.
            try!(skip(esds, 2));
        }

        // Url flag, second bit from left most.
        if esds_flags & 0x40 > 0 {
            // Skip uninteresting fields.
            let skip_es_len: usize = try!(esds.read_u8()) as usize + 2;
            try!(skip(esds, skip_es_len));
        }

        // find DecoderConfig descriptor (tag = DECODER_CONFIG_TAG)
        if esds_end > esds.position() {
            let next_tag = try!(esds.read_u8());
            if next_tag == DECODER_CONFIG_TAG {
                let dcds_extend = try!(esds.read_u8());
                // extension tag start from 0x80.
                if dcds_extend >= 0x80 {
                    // skip remains extension and length.
                    try!(skip(esds, 3));
                }

                object_profile = try!(esds.read_u8());

                // Skip uninteresting fields.
                try!(skip(esds, 12));
            }
        }


        // find DecoderSpecific descriptor (tag = DECODER_SPECIFIC_TAG)
        if esds_end > esds.position() {
            let next_tag = try!(esds.read_u8());
            if next_tag == DECODER_SPECIFIC_TAG {
                let dsds_extend = try!(esds.read_u8());
                // extension tag start from 0x80.
                if dsds_extend >= 0x80 {
                    // skip remains extension and length.
                    try!(skip(esds, 3));
                }

                let audio_specific_config = try!(be_u16(esds));

                let sample_index = (audio_specific_config & 0x07FF) >> 7;

                sample_frequency =
                    frequency_table.iter().find(|item| item.0 == sample_index).map(|x| x.1);
            }
        }

        (object_profile, sample_frequency)
    };

    let codec = match object_profile_indicator {
        0x40 | 0x41 => CodecType::AAC,
        0x6B => CodecType::MP3,
        _ => CodecType::Unknown,
    };

    if codec == CodecType::Unknown {
        return Err(Error::Unsupported("unknown audio codec"));
    }

    Ok(ES_Descriptor {
        audio_codec: codec,
        audio_sample_rate: sample_frequency,
        codec_specific_config: esds_array,
    })
}

/// Parse `FLACSpecificBox`.
fn read_dfla<T: Read>(src: &mut BMFFBox<T>) -> Result<FLACSpecificBox> {
    let (version, flags) = try!(read_fullbox_extra(src));
    if version != 0 {
        return Err(Error::Unsupported("unknown dfLa (FLAC) version"));
    }
    if flags != 0 {
        return Err(Error::InvalidData("no-zero dfLa (FLAC) flags"));
    }
    let mut blocks = Vec::new();
    while src.bytes_left() > 0 {
        let block = try!(read_flac_metadata(src));
        blocks.push(block);
    }
    // The box must have at least one meta block, and the first block
    // must be the METADATA_BLOCK_STREAMINFO
    if blocks.is_empty() {
        return Err(Error::InvalidData("FLACSpecificBox missing metadata"));
    } else if blocks[0].block_type != 0 {
        println!("flac metadata block:\n  {:?}", blocks[0]);
        return Err(Error::InvalidData(
                "FLACSpecificBox must have STREAMINFO metadata first"));
    } else if blocks[0].data.len() != 34 {
        return Err(Error::InvalidData(
                "FLACSpecificBox STREAMINFO block is the wrong size"));
    }
    Ok(FLACSpecificBox {
        version: version,
        blocks: blocks,
    })
}

/// Parse `OpusSpecificBox`.
fn read_dops<T: Read>(src: &mut BMFFBox<T>) -> Result<OpusSpecificBox> {
    let version = try!(src.read_u8());
    if version != 0 {
        return Err(Error::Unsupported("unknown dOps (Opus) version"));
    }

    let output_channel_count = try!(src.read_u8());
    let pre_skip = try!(be_u16(src));
    let input_sample_rate = try!(be_u32(src));
    let output_gain = try!(be_i16(src));
    let channel_mapping_family = try!(src.read_u8());

    let channel_mapping_table = if channel_mapping_family == 0 {
        None
    } else {
        let stream_count = try!(src.read_u8());
        let coupled_count = try!(src.read_u8());
        let channel_mapping = try!(read_buf(src, output_channel_count as usize));

        Some(ChannelMappingTable {
            stream_count: stream_count,
            coupled_count: coupled_count,
            channel_mapping: channel_mapping,
        })
    };

    // TODO(kinetik): validate field value ranges.
    Ok(OpusSpecificBox {
        version: version,
        output_channel_count: output_channel_count,
        pre_skip: pre_skip,
        input_sample_rate: input_sample_rate,
        output_gain: output_gain,
        channel_mapping_family: channel_mapping_family,
        channel_mapping_table: channel_mapping_table,
    })
}

/// Re-serialize the Opus codec-specific config data as an `OpusHead` packet.
///
/// Some decoders expect the initialization data in the format used by the
/// Ogg and WebM encapsulations. To support this we prepend the `OpusHead`
/// tag and byte-swap the data from big- to little-endian relative to the
/// dOps box.
pub fn serialize_opus_header<W: byteorder::WriteBytesExt + std::io::Write>(opus: &OpusSpecificBox, dst: &mut W) -> Result<()> {
    match dst.write(b"OpusHead") {
        Err(e) => return Err(Error::from(e)),
        Ok(bytes) => {
            if bytes != 8 {
                return Err(Error::InvalidData("Couldn't write OpusHead tag."));
            }
        }
    }
    // In mp4 encapsulation, the version field is 0, but in ogg
    // it is 1. While decoders generally accept zero as well, write
    // out the version of the header we're supporting rather than
    // whatever we parsed out of mp4.
    try!(dst.write_u8(1));
    try!(dst.write_u8(opus.output_channel_count));
    try!(dst.write_u16::<byteorder::LittleEndian>(opus.pre_skip));
    try!(dst.write_u32::<byteorder::LittleEndian>(opus.input_sample_rate));
    try!(dst.write_i16::<byteorder::LittleEndian>(opus.output_gain));
    try!(dst.write_u8(opus.channel_mapping_family));
    match opus.channel_mapping_table {
        None => {}
        Some(ref table) => {
            try!(dst.write_u8(table.stream_count));
            try!(dst.write_u8(table.coupled_count));
            match dst.write(&table.channel_mapping) {
                Err(e) => return Err(Error::from(e)),
                Ok(bytes) => {
                    if bytes != table.channel_mapping.len() {
                        return Err(Error::InvalidData("Couldn't write channel mapping table data."));
                    }
                }
            }
        }
    };
    Ok(())
}

/// Parse a hdlr box.
fn read_hdlr<T: Read>(src: &mut BMFFBox<T>) -> Result<HandlerBox> {
    let (_, _) = try!(read_fullbox_extra(src));

    // Skip uninteresting fields.
    try!(skip(src, 4));

    let handler_type = try!(be_u32(src));

    // Skip uninteresting fields.
    try!(skip(src, 12));

    let bytes_left = src.bytes_left();
    let _name = try!(read_null_terminated_string(src, bytes_left));

    Ok(HandlerBox {
        handler_type: handler_type,
    })
}

/// Parse an video description inside an stsd box.
fn read_video_sample_entry<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) -> Result<SampleEntry> {
    let name = src.get_header().name;
    track.codec_type = match name {
        BoxType::AVCSampleEntry | BoxType::AVC3SampleEntry => CodecType::H264,
        BoxType::VP8SampleEntry => CodecType::VP8,
        BoxType::VP9SampleEntry => CodecType::VP9,
        BoxType::ProtectedVisualSampleEntry => CodecType::EncryptedVideo,
        _ => CodecType::Unknown,
    };

    // Skip uninteresting fields.
    try!(skip(src, 6));

    let data_reference_index = try!(be_u16(src));

    // Skip uninteresting fields.
    try!(skip(src, 16));

    let width = try!(be_u16(src));
    let height = try!(be_u16(src));

    // Skip uninteresting fields.
    try!(skip(src, 14));

    let _compressorname = try!(read_fixed_length_pascal_string(src, 32));

    // Skip uninteresting fields.
    try!(skip(src, 4));

    // Skip clap/pasp/etc. for now.
    let mut codec_specific = None;
    let mut iter = src.box_iter();
    while let Some(mut b) = try!(iter.next_box()) {
        match b.head.name {
            BoxType::AVCConfigurationBox => {
                if (name != BoxType::AVCSampleEntry &&
                    name != BoxType::AVC3SampleEntry &&
                    name != BoxType::ProtectedVisualSampleEntry) ||
                    codec_specific.is_some() {
                        return Err(Error::InvalidData("malformed video sample entry"));
                    }
                let avcc_size = b.head.size - b.head.offset;
                if avcc_size > BUF_SIZE_LIMIT {
                    return Err(Error::InvalidData("avcC box exceeds BUF_SIZE_LIMIT"));
                }
                let avcc = try!(read_buf(&mut b.content, avcc_size as usize));
                // TODO(kinetik): Parse avcC box?  For now we just stash the data.
                codec_specific = Some(VideoCodecSpecific::AVCConfig(avcc));
            }
            BoxType::VPCodecConfigurationBox => { // vpcC
                if (name != BoxType::VP8SampleEntry &&
                    name != BoxType::VP9SampleEntry) ||
                    codec_specific.is_some() {
                        return Err(Error::InvalidData("malformed video sample entry"));
                    }
                let vpcc = try!(read_vpcc(&mut b));
                codec_specific = Some(VideoCodecSpecific::VPxConfig(vpcc));
            }
            _ => try!(skip_box_content(&mut b)),
        }
        check_parser_state!(b.content);
    }

    codec_specific
        .map(|codec_specific| SampleEntry::Video(VideoSampleEntry {
            data_reference_index: data_reference_index,
            width: width,
            height: height,
            codec_specific: codec_specific,
        }))
        .ok_or_else(|| Error::InvalidData("malformed video sample entry"))
}

fn read_qt_wave_atom<T: Read>(src: &mut BMFFBox<T>) -> Result<ES_Descriptor> {
    let mut codec_specific = None;
    let mut iter = src.box_iter();
    while let Some(mut b) = try!(iter.next_box()) {
        match b.head.name {
            BoxType::ESDBox => {
                let esds = try!(read_esds(&mut b));
                codec_specific = Some(esds);
            },
            _ => try!(skip_box_content(&mut b)),
        }
    }

    codec_specific.ok_or_else(|| Error::InvalidData("malformed audio sample entry"))
}

/// Parse an audio description inside an stsd box.
fn read_audio_sample_entry<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) -> Result<SampleEntry> {
    let name = src.get_header().name;
    track.codec_type = match name {
        // TODO(kinetik): stagefright inspects ESDS to detect MP3 (audio/mpeg).
        BoxType::MP4AudioSampleEntry => CodecType::AAC,
        BoxType::FLACSampleEntry => CodecType::FLAC,
        BoxType::OpusSampleEntry => CodecType::Opus,
        BoxType::ProtectedAudioSampleEntry => CodecType::EncryptedAudio,
        _ => CodecType::Unknown,
    };

    // Skip uninteresting fields.
    try!(skip(src, 6));

    let data_reference_index = try!(be_u16(src));

    // XXX(kinetik): This is "reserved" in BMFF, but some old QT MOV variant
    // uses it, need to work out if we have to support it.  Without checking
    // here and reading extra fields after samplerate (or bailing with an
    // error), the parser loses sync completely.
    let version = try!(be_u16(src));

    // Skip uninteresting fields.
    try!(skip(src, 6));

    let channelcount = try!(be_u16(src));
    let samplesize = try!(be_u16(src));

    // Skip uninteresting fields.
    try!(skip(src, 4));

    let samplerate = try!(be_u32(src));

    match version {
        0 => (),
        1 => {
            // Quicktime sound sample description version 1.
            // Skip uninteresting fields.
            try!(skip(src, 16));
        },
        _ => return Err(Error::Unsupported("unsupported non-isom audio sample entry")),
    }

    // Skip chan/etc. for now.
    let mut codec_specific = None;
    let mut iter = src.box_iter();
    while let Some(mut b) = try!(iter.next_box()) {
        match b.head.name {
            BoxType::ESDBox => {
                if (name != BoxType::MP4AudioSampleEntry &&
                    name != BoxType::ProtectedAudioSampleEntry) ||
                    codec_specific.is_some() {
                        return Err(Error::InvalidData("malformed audio sample entry"));
                }

                let esds = try!(read_esds(&mut b));
                track.codec_type = esds.audio_codec;
                codec_specific = Some(AudioCodecSpecific::ES_Descriptor(esds));
            }
            BoxType::FLACSpecificBox => {
                if name != BoxType::FLACSampleEntry ||
                    codec_specific.is_some() {
                    return Err(Error::InvalidData("malformed audio sample entry"));
                }
                let dfla = try!(read_dfla(&mut b));
                track.codec_type = CodecType::FLAC;
                codec_specific = Some(AudioCodecSpecific::FLACSpecificBox(dfla));
            }
            BoxType::OpusSpecificBox => {
                if name != BoxType::OpusSampleEntry ||
                    codec_specific.is_some() {
                    return Err(Error::InvalidData("malformed audio sample entry"));
                }
                let dops = try!(read_dops(&mut b));
                track.codec_type = CodecType::Opus;
                codec_specific = Some(AudioCodecSpecific::OpusSpecificBox(dops));
            }
            BoxType::QTWaveAtom => {
                let qt_esds = try!(read_qt_wave_atom(&mut b));
                track.codec_type = qt_esds.audio_codec;
                codec_specific = Some(AudioCodecSpecific::ES_Descriptor(qt_esds));
            }
            _ => try!(skip_box_content(&mut b)),
        }
        check_parser_state!(b.content);
    }

    codec_specific
        .map(|codec_specific| SampleEntry::Audio(AudioSampleEntry {
            data_reference_index: data_reference_index,
            channelcount: channelcount,
            samplesize: samplesize,
            samplerate: samplerate,
            codec_specific: codec_specific,
        }))
        .ok_or_else(|| Error::InvalidData("malformed audio sample entry"))
}

/// Parse a stsd box.
fn read_stsd<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) -> Result<SampleDescriptionBox> {
    let (_, _) = try!(read_fullbox_extra(src));

    let description_count = try!(be_u32(src));
    let mut descriptions = Vec::new();

    {
        // TODO(kinetik): check if/when more than one desc per track? do we need to support?
        let mut iter = src.box_iter();
        while let Some(mut b) = try!(iter.next_box()) {
            let description = match track.track_type {
                TrackType::Video => read_video_sample_entry(&mut b, track),
                TrackType::Audio => read_audio_sample_entry(&mut b, track),
                TrackType::Unknown => Err(Error::Unsupported("unknown track type")),
            };
            let description = match description {
                Ok(desc) => desc,
                Err(Error::Unsupported(_)) => {
                    // read_{audio,video}_desc may have returned Unsupported
                    // after partially reading the box content, so we can't
                    // simply use skip_box_content here.
                    let to_skip = b.bytes_left();
                    try!(skip(&mut b, to_skip));
                    SampleEntry::Unknown
                }
                Err(e) => return Err(e),
            };
            if track.data.is_none() {
                track.data = Some(description.clone());
            } else {
                log!("** don't know how to handle multiple descriptions **");
            }
            descriptions.push(description);
            check_parser_state!(b.content);
            if descriptions.len() == description_count as usize {
                break;
            }
        }
    }

    // Padding could be added in some contents.
    try!(skip_box_remain(src));

    Ok(SampleDescriptionBox {
        descriptions: descriptions,
    })
}

/// Skip a number of bytes that we don't care to parse.
fn skip<T: Read>(src: &mut T, mut bytes: usize) -> Result<()> {
    const BUF_SIZE: usize = 64 * 1024;
    let mut buf = vec![0; BUF_SIZE];
    while bytes > 0 {
        let buf_size = cmp::min(bytes, BUF_SIZE);
        let len = try!(src.take(buf_size as u64).read(&mut buf));
        if len == 0 {
            return Err(Error::UnexpectedEOF);
        }
        bytes -= len;
    }
    Ok(())
}

/// Read size bytes into a Vector or return error.
fn read_buf<T: ReadBytesExt>(src: &mut T, size: usize) -> Result<Vec<u8>> {
    let mut buf = vec![0; size];
    let r = try!(src.read(&mut buf));
    if r != size {
        return Err(Error::InvalidData("failed buffer read"));
    }
    Ok(buf)
}

// TODO(kinetik): Find a copy of ISO/IEC 14496-1 to confirm various string encodings.
// XXX(kinetik): definition of "null-terminated" string is fuzzy, we have:
// - zero or more byte strings, with a single null terminating the string.
// - zero byte strings with no null terminator (i.e. zero space in the box for the string)
// - length-prefixed strings with no null terminator (e.g. bear_rotate_0.mp4)
fn read_null_terminated_string<T: ReadBytesExt>(src: &mut T, mut size: usize) -> Result<String> {
    let mut buf = Vec::new();
    while size > 0 {
        let c = try!(src.read_u8());
        if c == 0 {
            break;
        }
        buf.push(c);
        size -= 1;
    }
    String::from_utf8(buf).map_err(From::from)
}

#[allow(dead_code)]
fn read_pascal_string<T: ReadBytesExt>(src: &mut T) -> Result<String> {
    let len = try!(src.read_u8());
    let buf = try!(read_buf(src, len as usize));
    String::from_utf8(buf).map_err(From::from)
}

// Weird string encoding with a length prefix and a fixed sized buffer which
// contains padding if the string doesn't fill the buffer.
fn read_fixed_length_pascal_string<T: Read>(src: &mut T, size: usize) -> Result<String> {
    assert!(size > 0);
    let len = cmp::min(try!(src.read_u8()) as usize, size - 1);
    let buf = try!(read_buf(src, len));
    try!(skip(src, size - 1 - buf.len()));
    String::from_utf8(buf).map_err(From::from)
}

fn be_i16<T: ReadBytesExt>(src: &mut T) -> Result<i16> {
    src.read_i16::<byteorder::BigEndian>().map_err(From::from)
}

fn be_i32<T: ReadBytesExt>(src: &mut T) -> Result<i32> {
    src.read_i32::<byteorder::BigEndian>().map_err(From::from)
}

fn be_i64<T: ReadBytesExt>(src: &mut T) -> Result<i64> {
    src.read_i64::<byteorder::BigEndian>().map_err(From::from)
}

fn be_u16<T: ReadBytesExt>(src: &mut T) -> Result<u16> {
    src.read_u16::<byteorder::BigEndian>().map_err(From::from)
}

fn be_u24<T: ReadBytesExt>(src: &mut T) -> Result<u32> {
    src.read_uint::<byteorder::BigEndian>(3)
        .map(|v| v as u32)
        .map_err(From::from)
}

fn be_u32<T: ReadBytesExt>(src: &mut T) -> Result<u32> {
    src.read_u32::<byteorder::BigEndian>().map_err(From::from)
}

fn be_u64<T: ReadBytesExt>(src: &mut T) -> Result<u64> {
    src.read_u64::<byteorder::BigEndian>().map_err(From::from)
}
