// license:BSD-3-Clause // copyright-holders:Aaron Giles, Vas Crabb /*************************************************************************** aviio.c AVI movie format parsing helpers. ***************************************************************************/ #include #include #include #include #include "aviio.h" /*************************************************************************** CONSTANTS ***************************************************************************/ /** * @def FILETYPE_READ * * @brief A macro that defines filetype read. */ #define FILETYPE_READ 1 /** * @def FILETYPE_CREATE * * @brief A macro that defines filetype create. */ #define FILETYPE_CREATE 2 /** @brief Size of the maximum riff. */ #define MAX_RIFF_SIZE (2UL * 1024 * 1024 * 1024 - 1024) /* just under 2GB */ /** @brief The maximum avi size in gigabytes. */ #define MAX_AVI_SIZE_IN_GB (1024) /** * @def FOUR_GB * * @brief A macro that defines four gigabytes. */ #define FOUR_GB (std::uint64_t(1) << 32) /** * @def MAX_SOUND_CHANNELS * * @brief A macro that defines maximum sound channels. */ #define MAX_SOUND_CHANNELS 2 /** * @def SOUND_BUFFER_MSEC * * @brief A macro that defines sound buffer msec. */ #define SOUND_BUFFER_MSEC 2000 /* milliseconds of sound buffering */ /** @brief The chunktype riff. */ #define CHUNKTYPE_RIFF AVI_FOURCC('R','I','F','F') /** @brief List of chunktypes. */ #define CHUNKTYPE_LIST AVI_FOURCC('L','I','S','T') /** @brief The chunktype junk. */ #define CHUNKTYPE_JUNK AVI_FOURCC('J','U','N','K') /** @brief The chunktype avih. */ #define CHUNKTYPE_AVIH AVI_FOURCC('a','v','i','h') /** @brief The chunktype strh. */ #define CHUNKTYPE_STRH AVI_FOURCC('s','t','r','h') /** @brief The chunktype strf. */ #define CHUNKTYPE_STRF AVI_FOURCC('s','t','r','f') /** @brief The first chunktype index. */ #define CHUNKTYPE_IDX1 AVI_FOURCC('i','d','x','1') /** @brief The chunktype indx. */ #define CHUNKTYPE_INDX AVI_FOURCC('i','n','d','x') /** @brief The chunktype xxdb. */ #define CHUNKTYPE_XXDB AVI_FOURCC(0x00,0x00,'d','b') /** @brief The chunktype xxdc. */ #define CHUNKTYPE_XXDC AVI_FOURCC(0x00,0x00,'d','c') /** @brief The chunktype xxwb. */ #define CHUNKTYPE_XXWB AVI_FOURCC(0x00,0x00,'w','b') /** @brief The chunktype ixxx. */ #define CHUNKTYPE_IXXX AVI_FOURCC('i','x',0x00,0x00) /** @brief The chunktype xx mask. */ #define CHUNKTYPE_XX_MASK AVI_FOURCC(0x00,0x00,0xff,0xff) /** @brief The listtype avi. */ #define LISTTYPE_AVI AVI_FOURCC('A','V','I',' ') /** @brief The listtype avix. */ #define LISTTYPE_AVIX AVI_FOURCC('A','V','I','X') /** @brief The listtype hdrl. */ #define LISTTYPE_HDRL AVI_FOURCC('h','d','r','l') /** @brief The listtype strl. */ #define LISTTYPE_STRL AVI_FOURCC('s','t','r','l') /** @brief The listtype movi. */ #define LISTTYPE_MOVI AVI_FOURCC('m','o','v','i') /** @brief The streamtype vids. */ #define STREAMTYPE_VIDS AVI_FOURCC('v','i','d','s') /** @brief The streamtype auds. */ #define STREAMTYPE_AUDS AVI_FOURCC('a','u','d','s') /** @brief The handler bitmap. */ #define HANDLER_DIB AVI_FOURCC('D','I','B',' ') /** @brief The handler hfyu. */ #define HANDLER_HFYU AVI_FOURCC('h','f','y','u') /* main AVI header files */ /** * @def AVIF_HASINDEX * * @brief A macro that defines avif hasindex. */ #define AVIF_HASINDEX 0x00000010 /** * @def AVIF_MUSTUSEINDEX * * @brief A macro that defines avif mustuseindex. */ #define AVIF_MUSTUSEINDEX 0x00000020 /** * @def AVIF_ISINTERLEAVED * * @brief A macro that defines avif isinterleaved. */ #define AVIF_ISINTERLEAVED 0x00000100 /** * @def AVIF_COPYRIGHTED * * @brief A macro that defines avif copyrighted. */ #define AVIF_COPYRIGHTED 0x00010000 /** * @def AVIF_WASCAPTUREFILE * * @brief A macro that defines avif wascapturefile. */ #define AVIF_WASCAPTUREFILE 0x00020000 /* index definitions */ /** * @def AVI_INDEX_OF_INDEXES * * @brief A macro that defines avi index of indexes. */ #define AVI_INDEX_OF_INDEXES 0x00 /** * @def AVI_INDEX_OF_CHUNKS * * @brief A macro that defines avi index of chunks. */ #define AVI_INDEX_OF_CHUNKS 0x01 /** * @def AVI_INDEX_IS_DATA * * @brief A macro that defines avi index is data. */ #define AVI_INDEX_IS_DATA 0x80 /** * @def AVI_INDEX_2FIELD * * @brief A macro that defines avi index 2 field. */ #define AVI_INDEX_2FIELD 0x01 /** * @def AVI_INTEGRAL_MULTIPLE * * @brief Ensures the integral multiple of the video dimension, because most video players are not capable to playback a video stream with a lower multiple. */ #define AVI_INTEGRAL_MULTIPLE 4 /* HuffYUV definitions */ /** * @def HUFFYUV_PREDICT_LEFT * * @brief A macro that defines huffyuv predict left. */ #define HUFFYUV_PREDICT_LEFT 0 /** * @def HUFFYUV_PREDICT_GRADIENT * * @brief A macro that defines huffyuv predict gradient. */ #define HUFFYUV_PREDICT_GRADIENT 1 /** * @def HUFFYUV_PREDICT_MEDIAN * * @brief A macro that defines huffyuv predict median. */ #define HUFFYUV_PREDICT_MEDIAN 2 /** * @def HUFFYUV_PREDICT_DECORR * * @brief A macro that defines huffyuv predict decorr. */ #define HUFFYUV_PREDICT_DECORR 0x40 namespace { /*************************************************************************** TYPE DEFINITIONS ***************************************************************************/ /** * @struct avi_chunk * * @brief An avi chunk. */ struct avi_chunk { avi_chunk() : offset(0), size(0), type(0), listtype(0) { } std::uint64_t offset; /* file offset of chunk header */ std::uint64_t size; /* size of this chunk */ std::uint32_t type; /* type of this chunk */ std::uint32_t listtype; /* type of this list (if we are a list) */ }; /** * @struct avi_chunk_list * * @brief List of avi chunks. */ struct avi_chunk_list { std::uint64_t offset; /* offset in the file of header */ std::uint32_t length; /* length of the chunk including header */ }; /** * @struct avi_stream * * @brief An avi stream. */ class avi_stream { public: avi_stream() : m_type(0) , m_format(0) , m_rate(0) , m_scale(0) , m_samples(0) , m_chunks() , m_width(0) , m_height(0) , m_depth(0) , m_huffyuv() , m_channels(0) , m_samplebits(0) , m_samplerate(0) , m_saved_strh_offset(0) , m_saved_indx_offset(0) { } void initialize_video(avi_file::movie_info const &info) { std::uint32_t width = info.video_width; std::uint32_t height = info.video_height; auto integal_multiple = std::uint32_t(AVI_INTEGRAL_MULTIPLE); if (integal_multiple > 1) { width = width - (width % integal_multiple); height = height - (height % integal_multiple); } m_type = STREAMTYPE_VIDS; m_format = info.video_format; m_rate = info.video_timescale; m_scale = info.video_sampletime; m_width = width; m_height = height; m_depth = info.video_depth; } void initialize_audio(avi_file::movie_info const &info) { m_type = STREAMTYPE_AUDS; m_format = info.audio_format; m_rate = info.audio_timescale; m_scale = info.audio_sampletime; m_channels = info.audio_channels; m_samplebits = info.audio_samplebits; m_samplerate = info.audio_samplerate; } std::uint32_t type() const { return m_type; } std::uint32_t format() const { return m_format; } std::uint32_t rate() const { return m_rate; } std::uint32_t scale() const { return m_scale; } std::uint32_t samples() const { return m_samples; } void set_samples(std::uint32_t value) { m_samples = value; } std::uint32_t add_samples(std::uint32_t value) { return m_samples += value; } std::uint32_t chunks() const { return m_chunks.size(); } avi_chunk_list const &chunk(std::uint32_t index) const { assert(index < m_chunks.size()); return m_chunks[index]; } avi_chunk_list &chunk(std::uint32_t index) { assert(index < m_chunks.size()); return m_chunks[index]; } avi_file::error set_chunk_info(std::uint32_t index, std::uint64_t offset, std::uint32_t length); void set_chunks(std::uint32_t count) { assert(count <= m_chunks.size()); m_chunks.resize(count); } std::uint32_t width() const { return m_width; } std::uint32_t height() const { return m_height; } std::uint32_t depth() const { return m_depth; } std::uint16_t channels() const { return m_channels; } std::uint16_t samplebits() const { return m_samplebits; } std::uint32_t samplerate() const { return m_samplerate; } std::uint32_t bytes_per_sample() const { return (m_samplebits / 8) * m_channels; } avi_file::error set_strh_data(std::uint8_t const *data, std::uint32_t size); avi_file::error set_strf_data(std::uint8_t const *data, std::uint32_t size); std::uint64_t &saved_strh_offset() { return m_saved_strh_offset; } std::uint64_t &saved_indx_offset() { return m_saved_indx_offset; } // RGB helpers avi_file::error rgb32_compress_to_rgb(const bitmap_rgb32 &bitmap, std::uint8_t *data, std::uint32_t numbytes) const; // YUY helpers avi_file::error yuv_decompress_to_yuy16(const std::uint8_t *data, std::uint32_t numbytes, bitmap_yuy16 &bitmap) const; avi_file::error yuy16_compress_to_yuy(const bitmap_yuy16 &bitmap, std::uint8_t *data, std::uint32_t numbytes) const; // HuffYUV helpers avi_file::error huffyuv_decompress_to_yuy16(const std::uint8_t *data, std::uint32_t numbytes, bitmap_yuy16 &bitmap) const; // Uncompressed helpers avi_file::error uncompressed_rgb24_to_argb32(const std::uint8_t *data, std::uint32_t numbytes, bitmap_argb32 &bitmap) const; avi_file::error uncompressed_yuv420p_to_argb32(const std::uint8_t *data, std::uint32_t numbytes, bitmap_argb32 &bitmap) const; private: struct huffyuv_table { std::uint8_t shift[256]; /* bit shift amounts */ std::uint32_t bits[256]; /* bit match values */ std::uint32_t mask[256]; /* bit mask values */ std::uint16_t baselookup[65536]; /* base lookup table */ std::vector extralookup; /* extra lookup tables */ }; struct huffyuv_data { std::uint8_t predictor; /* predictor */ huffyuv_table table[3]; /* array of tables */ }; avi_file::error huffyuv_extract_tables(const std::uint8_t *chunkdata, std::uint32_t size); std::uint32_t m_type; /* subtype of stream */ std::uint32_t m_format; /* format of stream data */ std::uint32_t m_rate; /* timescale for stream */ std::uint32_t m_scale; /* duration of one sample in the stream */ std::uint32_t m_samples; /* number of samples */ std::vector m_chunks; /* list of chunks */ std::uint32_t m_width; /* width of video */ std::uint32_t m_height; /* height of video */ std::uint32_t m_depth; /* depth of video */ std::unique_ptr m_huffyuv; /* huffyuv decompression data */ std::uint16_t m_channels; /* audio channels */ std::uint16_t m_samplebits; /* audio bits per sample */ std::uint32_t m_samplerate; /* audio sample rate */ /* only used when creating */ std::uint64_t m_saved_strh_offset; /* writeoffset of strh chunk */ std::uint64_t m_saved_indx_offset; /* writeoffset of indx chunk */ }; /** * @class avi_file_impl * * @brief An avi file. */ class avi_file_impl : public avi_file { public: avi_file_impl(osd_file::ptr &&file, std::uint64_t length) : avi_file_impl() { m_file = std::move(file); m_type = FILETYPE_READ; // make a root atom m_rootchunk.offset = 0; m_rootchunk.size = length; m_rootchunk.type = 0; m_rootchunk.listtype = 0; } avi_file_impl(osd_file::ptr &&file, movie_info const &info) : avi_file_impl() { m_file = std::move(file); m_type = FILETYPE_CREATE; // copy the movie info m_info = info; m_info.video_numsamples = 0; m_info.audio_numsamples = 0; // initialize the video track m_streams.emplace_back(); m_streams.back().initialize_video(m_info); // initialize the audio track if (m_info.audio_channels > 0) { m_streams.emplace_back(); m_streams.back().initialize_audio(m_info); } } virtual ~avi_file_impl() override; virtual void printf_chunks() override; virtual movie_info const &get_movie_info() const override; virtual std::uint32_t first_sample_in_frame(std::uint32_t framenum) const override; virtual error read_uncompressed_video_frame(std::uint32_t framenum, bitmap_argb32 &bitmap) override; virtual error read_video_frame(std::uint32_t framenum, bitmap_yuy16 &bitmap) override; virtual error read_sound_samples(int channel, std::uint32_t firstsample, std::uint32_t numsamples, std::int16_t *output) override; virtual error append_video_frame(bitmap_yuy16 &bitmap) override; virtual error append_video_frame(bitmap_rgb32 &bitmap) override; virtual error append_sound_samples(int channel, std::int16_t const *samples, std::uint32_t numsamples, std::uint32_t sampleskip) override; error read_movie_data(); error write_initial_headers(); error soundbuf_initialize(); private: avi_file_impl() : m_file() , m_type(0) , m_info({ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }) , m_tempbuffer() , m_streams() , m_rootchunk() , m_writeoffs(0) , m_riffbase(0) , m_chunkstack() , m_chunksp(0) , m_saved_movi_offset(0) , m_saved_avih_offset(0) , m_soundbuf() , m_soundbuf_samples(0) , m_soundbuf_chunks(0) , m_soundbuf_frames(0) { std::fill(std::begin(m_soundbuf_chansamples), std::end(m_soundbuf_chansamples), 0); } avi_stream *get_video_stream(); avi_stream *get_audio_stream(int channel, int &offset); std::uint32_t compute_idx1_size() const; std::uint32_t get_chunkid_for_stream(const avi_stream *stream) const; std::uint32_t framenum_to_samplenum(std::uint32_t framenum) const; error expand_tempbuffer(std::uint32_t length); // core chunk read routines error get_first_chunk(avi_chunk const *parent, avi_chunk &newchunk); error get_next_chunk(avi_chunk const *parent, avi_chunk &newchunk); error find_first_chunk(std::uint32_t findme, avi_chunk const *container, avi_chunk &result); error find_next_chunk(std::uint32_t findme, avi_chunk const *container, avi_chunk &result); error find_first_list(std::uint32_t findme, avi_chunk const *container, avi_chunk &result); error find_next_list(std::uint32_t findme, avi_chunk const *container, avi_chunk &result); error get_next_chunk_internal(avi_chunk const *parent, avi_chunk &newchunk, std::uint64_t offset); error read_chunk_data(avi_chunk const &chunk, std::unique_ptr &buffer); // chunk read helpers error extract_movie_info(); error parse_avih_chunk(avi_chunk const &avih); error parse_strh_chunk(avi_stream &stream, avi_chunk const &strh); error parse_strf_chunk(avi_stream &stream, avi_chunk const &strf); error parse_indx_chunk(avi_stream &stream, avi_chunk const &strf); error parse_idx1_chunk(std::uint64_t baseoffset, avi_chunk const &idx1); // core chunk write routines error chunk_open(std::uint32_t type, std::uint32_t listtype, std::uint32_t estlength); error chunk_close(); error chunk_write(std::uint32_t type, const void *data, std::uint32_t length); error chunk_overwrite(std::uint32_t type, const void *data, std::uint32_t length, std::uint64_t &offset, bool initial_write); // chunk write helpers error write_avih_chunk(bool initial_write); error write_strh_chunk(avi_stream &stream, bool initial_write); error write_strf_chunk(avi_stream const &stream); error write_indx_chunk(avi_stream &stream, bool initial_write); error write_idx1_chunk(); // sound buffering helpers error soundbuf_write_chunk(std::uint32_t framenum); error soundbuf_flush(bool only_flush_full); // debugging void printf_chunk_recursive(avi_chunk const *chunk, int indent); /* shared data */ osd_file::ptr m_file; /* pointer to open file */ int m_type; /* type of access (read/create) */ movie_info m_info; /* movie info structure */ std::vector m_tempbuffer; /* temporary buffer */ /* only used when reading */ std::vector m_streams; /* allocated array of stream information */ avi_chunk m_rootchunk; /* dummy root chunk that wraps the whole file */ /* only used when creating */ std::uint64_t m_writeoffs; /* current file write offset */ std::uint64_t m_riffbase; /* base of the current RIFF */ std::array m_chunkstack; /* stack of chunks we are writing */ int m_chunksp; /* stack pointer for the current chunk */ std::uint64_t m_saved_movi_offset; /* writeoffset of movi list */ std::uint64_t m_saved_avih_offset; /* writeoffset of avih chunk */ std::vector m_soundbuf; /* buffer for sound data */ std::uint32_t m_soundbuf_samples; /* length of sound buffer in samples */ std::uint32_t m_soundbuf_chansamples[MAX_SOUND_CHANNELS]; /* samples in buffer for each channel */ std::uint32_t m_soundbuf_chunks; /* number of chunks completed so far */ std::uint32_t m_soundbuf_frames; /* number of frames ahead of the video */ }; /*************************************************************************** INLINE FUNCTIONS ***************************************************************************/ /*------------------------------------------------- fetch_16bits - read 16 bits in LSB order from the given buffer -------------------------------------------------*/ /** * @fn static inline std::uint16_t fetch_16bits(const std::uint8_t *data) * * @brief Fetches the 16bits. * * @param data The data. * * @return The 16bits. */ inline std::uint16_t fetch_16bits(const std::uint8_t *data) { return data[0] | (data[1] << 8); } /*------------------------------------------------- fetch_32bits - read 32 bits in LSB order from the given buffer -------------------------------------------------*/ /** * @fn static inline std::uint32_t fetch_32bits(const std::uint8_t *data) * * @brief Fetches the 32bits. * * @param data The data. * * @return The 32bits. */ inline std::uint32_t fetch_32bits(const std::uint8_t *data) { return data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); } /*------------------------------------------------- fetch_64bits - read 64 bits in LSB order from the given buffer -------------------------------------------------*/ /** * @fn static inline std::uint64_t fetch_64bits(const std::uint8_t *data) * * @brief Fetches the 64bits. * * @param data The data. * * @return The 64bits. */ inline std::uint64_t fetch_64bits(const std::uint8_t *data) { return std::uint64_t(data[0]) | (std::uint64_t(data[1]) << 8) | (std::uint64_t(data[2]) << 16) | (std::uint64_t(data[3]) << 24) | (std::uint64_t(data[4]) << 32) | (std::uint64_t(data[5]) << 40) | (std::uint64_t(data[6]) << 48) | (std::uint64_t(data[7]) << 56); } /*------------------------------------------------- put_16bits - write 16 bits in LSB order to the given buffer -------------------------------------------------*/ /** * @fn static inline void put_16bits(std::uint8_t *data, std::uint16_t value) * * @brief Puts the 16bits. * * @param [in,out] data If non-null, the data. * @param value The value. */ inline void put_16bits(std::uint8_t *data, std::uint16_t value) { data[0] = value >> 0; data[1] = value >> 8; } /*------------------------------------------------- put_32bits - write 32 bits in LSB order to the given buffer -------------------------------------------------*/ /** * @fn static inline void put_32bits(std::uint8_t *data, std::uint32_t value) * * @brief Puts the 32bits. * * @param [in,out] data If non-null, the data. * @param value The value. */ inline void put_32bits(std::uint8_t *data, std::uint32_t value) { data[0] = value >> 0; data[1] = value >> 8; data[2] = value >> 16; data[3] = value >> 24; } /*------------------------------------------------- put_64bits - write 64 bits in LSB order to the given buffer -------------------------------------------------*/ /** * @fn static inline void put_64bits(std::uint8_t *data, std::uint64_t value) * * @brief Puts the 64bits. * * @param [in,out] data If non-null, the data. * @param value The value. */ inline void put_64bits(std::uint8_t *data, std::uint64_t value) { data[0] = value >> 0; data[1] = value >> 8; data[2] = value >> 16; data[3] = value >> 24; data[4] = value >> 32; data[5] = value >> 40; data[6] = value >> 48; data[7] = value >> 56; } /** * @fn static void u64toa(std::uint64_t val, char *output) * * @brief 64toas. * * @param val The value. * @param [in,out] output If non-null, the output. */ inline void u64toa(std::uint64_t val, char *output) { auto lo = std::uint32_t(val & 0xffffffff); auto hi = std::uint32_t(val >> 32); if (hi != 0) sprintf(output, "%X%08X", hi, lo); else sprintf(output, "%X", lo); } /*------------------------------------------------- set_stream_chunk_info - set the chunk info for a given chunk within a stream -------------------------------------------------*/ /** * @fn static inline avi_error set_stream_chunk_info(avi_stream *stream, std::uint32_t index, std::uint64_t offset, std::uint32_t length) * * @brief Sets stream chunk information. * * @param [in,out] stream If non-null, the stream. * @param index Zero-based index of the. * @param offset The offset. * @param length The length. * * @return An avi_error. */ inline avi_file::error avi_stream::set_chunk_info(std::uint32_t index, std::uint64_t offset, std::uint32_t length) { /* if we need to allocate more, allocate more */ if (index >= m_chunks.capacity()) { try { m_chunks.reserve((std::max)(index, m_chunks.capacity() + 1000)); } catch (...) { return avi_file::error::NO_MEMORY; } } /* update the number of chunks */ m_chunks.resize((std::max)(m_chunks.size(), index + 1)); /* set the data */ m_chunks[index].offset = offset; m_chunks[index].length = length; return avi_file::error::NONE; } inline avi_file::error avi_stream::set_strh_data(std::uint8_t const *data, std::uint32_t size) { m_type = fetch_32bits(&data[0]); m_scale = fetch_32bits(&data[20]); m_rate = fetch_32bits(&data[24]); m_samples = fetch_32bits(&data[32]); return avi_file::error::NONE; } inline avi_file::error avi_stream::set_strf_data(std::uint8_t const *data, std::uint32_t size) { /* audio and video streams have differing headers */ if (m_type == STREAMTYPE_VIDS) { m_width = fetch_32bits(&data[4]); m_height = fetch_32bits(&data[8]); m_depth = fetch_16bits(&data[14]); m_format = fetch_32bits(&data[16]); /* extra extraction for HuffYUV data */ if ((m_format == FORMAT_HFYU) && (size >= 56)) { avi_file::error const avierr = huffyuv_extract_tables(data, size); if (avierr != avi_file::error::NONE) return avierr; } } else if (m_type == STREAMTYPE_AUDS) { m_channels = fetch_16bits(&data[2]); m_samplebits = fetch_16bits(&data[14]); m_samplerate = fetch_32bits(&data[4]); } return avi_file::error::NONE; } /*------------------------------------------------- get_video_stream - return a pointer to the video stream -------------------------------------------------*/ /** * @fn static inline avi_stream *get_video_stream() * * @brief Gets video stream. * * @return null if it fails, else the video stream. */ inline avi_stream *avi_file_impl::get_video_stream() { for (int streamnum = 0; streamnum < m_streams.size(); streamnum++) if (m_streams[streamnum].type() == STREAMTYPE_VIDS) return &m_streams[streamnum]; return nullptr; } /*------------------------------------------------- get_audio_stream - return a pointer to the audio stream for the 'n'th channel -------------------------------------------------*/ /** * @fn static inline avi_stream *get_audio_stream(avi_file *file, int channel, int *offset) * * @brief Gets audio stream. * * @param [in,out] file If non-null, the file. * @param channel The channel. * @param [in,out] offset If non-null, the offset. * * @return null if it fails, else the audio stream. */ inline avi_stream *avi_file_impl::get_audio_stream(int channel, int &offset) { /* find the audios stream */ for (int streamnum = 0; streamnum < m_streams.size(); streamnum++) if (m_streams[streamnum].type() == STREAMTYPE_AUDS) { if (channel < m_streams[streamnum].channels()) { offset = channel; return &m_streams[streamnum]; } channel -= m_streams[streamnum].channels(); } return nullptr; } /*------------------------------------------------- compute_idx1_size - compute the size of the idx1 chunk -------------------------------------------------*/ /** * @fn static inline std::uint32_t compute_idx1_size(avi_file *file) * * @brief Calculates the index 1 size. * * @param [in,out] file If non-null, the file. * * @return The calculated index 1 size. */ inline std::uint32_t avi_file_impl::compute_idx1_size() const { int chunks = 0; /* count chunks in streams */ for (int strnum = 0; strnum < m_streams.size(); strnum++) chunks += m_streams[strnum].chunks(); return chunks * 16 + 8; } /*------------------------------------------------- get_chunkid_for_stream - make a chunk id for a given stream -------------------------------------------------*/ /** * @fn static inline std::uint32_t get_chunkid_for_stream(avi_file *file, avi_stream *stream) * * @brief Gets chunkid for stream. * * @param [in,out] file If non-null, the file. * @param [in,out] stream If non-null, the stream. * * @return The chunkid for stream. */ inline std::uint32_t avi_file_impl::get_chunkid_for_stream(const avi_stream *stream) const { std::uint32_t chunkid; chunkid = AVI_FOURCC('0' + (stream - &m_streams[0]) / 10, '0' + (stream - &m_streams[0]) % 10, 0, 0); if (stream->type() == STREAMTYPE_VIDS) chunkid |= (stream->format() == 0) ? CHUNKTYPE_XXDB : CHUNKTYPE_XXDC; else if (stream->type() == STREAMTYPE_AUDS) chunkid |= CHUNKTYPE_XXWB; return chunkid; } /*------------------------------------------------- framenum_to_samplenum - given a video frame number, get the first sample number -------------------------------------------------*/ /** * @fn static inline std::uint32_t framenum_to_samplenum(avi_file *file, std::uint32_t framenum) * * @brief Framenum to samplenum. * * @param [in,out] file If non-null, the file. * @param framenum The framenum. * * @return An std::uint32_t. */ inline std::uint32_t avi_file_impl::framenum_to_samplenum(std::uint32_t framenum) const { return (std::uint64_t(m_info.audio_samplerate) * std::uint64_t(framenum) * std::uint64_t(m_info.video_sampletime) + m_info.video_timescale - 1) / std::uint64_t(m_info.video_timescale); } /*------------------------------------------------- expand_tempbuffer - expand the file's tempbuffer if necessary to contain the requested amount of data -------------------------------------------------*/ /** * @fn static inline avi_error expand_tempbuffer(avi_file *file, std::uint32_t length) * * @brief Expand tempbuffer. * * @param [in,out] file If non-null, the file. * @param length The length. * * @return An avi_error. */ inline avi_file::error avi_file_impl::expand_tempbuffer(std::uint32_t length) { /* expand the tempbuffer to hold the data if necessary */ if (length > m_tempbuffer.size()) { try { m_tempbuffer.resize(2 * length); } catch (...) { return error::NO_MEMORY; } } return error::NONE; } /*------------------------------------------------- rgb32_compress_to_rgb - "compress" an RGB32 bitmap to an RGB encoded frame -------------------------------------------------*/ /** * @fn static avi_error rgb32_compress_to_rgb(avi_stream *stream, const bitmap_rgb32 &bitmap, std::uint8_t *data, std::uint32_t numbytes) * * @brief RGB 32 compress to RGB. * * @param [in,out] stream If non-null, the stream. * @param bitmap The bitmap. * @param [in,out] data If non-null, the data. * @param numbytes The numbytes. * * @return An avi_error. */ avi_file::error avi_stream::rgb32_compress_to_rgb(const bitmap_rgb32 &bitmap, std::uint8_t *data, std::uint32_t numbytes) const { int const height = (std::min)(m_height, bitmap.height()); int const width = (std::min)(m_width, bitmap.width()); std::uint8_t *const dataend = data + numbytes; int x, y; /* compressed video */ for (y = 0; y < height; y++) { const std::uint32_t *source = &bitmap.pix(y); std::uint8_t *dest = data + (m_height - 1 - y) * m_width * 3; for (x = 0; x < width && dest < dataend; x++) { rgb_t pix = *source++; *dest++ = pix.b(); *dest++ = pix.g(); *dest++ = pix.r(); } /* fill in any blank space on the right */ for ( ; x < m_width && dest < dataend; x++) { *dest++ = 0; *dest++ = 0; *dest++ = 0; } } /* fill in any blank space on the bottom */ for ( ; y < m_height; y++) { std::uint8_t *dest = data + (m_height - 1 - y) * m_width * 3; for (x = 0; x < m_width && dest < dataend; x++) { *dest++ = 0; *dest++ = 0; *dest++ = 0; } } return avi_file::error::NONE; } /*------------------------------------------------- yuv_decompress_to_yuy16 - decompress a YUV encoded frame to a YUY16 bitmap -------------------------------------------------*/ /** * @fn static avi_error yuv_decompress_to_yuy16(avi_stream *stream, const std::uint8_t *data, std::uint32_t numbytes, bitmap_yuy16 &bitmap) * * @brief Yuv decompress to yuy 16. * * @param [in,out] stream If non-null, the stream. * @param data The data. * @param numbytes The numbytes. * @param [in,out] bitmap The bitmap. * * @return An avi_error. */ avi_file::error avi_stream::yuv_decompress_to_yuy16(const std::uint8_t *data, std::uint32_t numbytes, bitmap_yuy16 &bitmap) const { auto const *const dataend = reinterpret_cast(data + numbytes); int x, y; /* compressed video */ for (y = 0; y < m_height; y++) { const std::uint16_t *source = reinterpret_cast(data) + y * m_width; std::uint16_t *dest = &bitmap.pix(y); /* switch off the compression */ switch (m_format) { case FORMAT_UYVY: for (x = 0; x < m_width && source < dataend; x++) *dest++ = *source++; break; case FORMAT_VYUY: case FORMAT_YUY2: for (x = 0; x < m_width && source < dataend; x++) { std::uint16_t pix = *source++; *dest++ = (pix >> 8) | (pix << 8); } break; } } return avi_file::error::NONE; } /*------------------------------------------------- yuy16_compress_to_yuy - "compress" a YUY16 bitmap to a YUV encoded frame -------------------------------------------------*/ /** * @fn static avi_error yuy16_compress_to_yuy(avi_stream *stream, const bitmap_yuy16 &bitmap, std::uint8_t *data, std::uint32_t numbytes) * * @brief Yuy 16 compress to yuy. * * @param [in,out] stream If non-null, the stream. * @param bitmap The bitmap. * @param [in,out] data If non-null, the data. * @param numbytes The numbytes. * * @return An avi_error. */ avi_file::error avi_stream::yuy16_compress_to_yuy(const bitmap_yuy16 &bitmap, std::uint8_t *data, std::uint32_t numbytes) const { auto *const dataend = reinterpret_cast(data + numbytes); int x, y; /* compressed video */ for (y = 0; y < m_height; y++) { const std::uint16_t *source = &bitmap.pix(y); std::uint16_t *dest = reinterpret_cast(data) + y * m_width; /* switch off the compression */ switch (m_format) { case FORMAT_UYVY: for (x = 0; x < m_width && dest < dataend; x++) *dest++ = *source++; break; case FORMAT_VYUY: case FORMAT_YUY2: for (x = 0; x < m_width && dest < dataend; x++) { std::uint16_t pix = *source++; *dest++ = (pix >> 8) | (pix << 8); } break; } } return avi_file::error::NONE; } /*------------------------------------------------- huffyuv_extract_tables - extract HuffYUV tables -------------------------------------------------*/ /** * @fn static avi_error huffyuv_extract_tables(avi_stream *stream, const std::uint8_t *chunkdata, std::uint32_t size) * * @brief Huffyuv extract tables. * * @param [in,out] stream If non-null, the stream. * @param chunkdata The chunkdata. * @param size The size. * * @return An avi_error. */ avi_file::error avi_stream::huffyuv_extract_tables(const std::uint8_t *chunkdata, std::uint32_t size) { const std::uint8_t *const chunkend = chunkdata + size; /* allocate memory for the data */ std::unique_ptr huffyuv; try { huffyuv = std::make_unique(); } catch (...) { return avi_file::error::NO_MEMORY; } /* extract predictor information */ if (&chunkdata[40] >= chunkend) return avi_file::error::INVALID_DATA; huffyuv->predictor = chunkdata[40]; /* make sure it's the left predictor */ if ((huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) != HUFFYUV_PREDICT_LEFT) return avi_file::error::UNSUPPORTED_VIDEO_FORMAT; /* make sure it's 16bpp YUV data */ if (chunkdata[41] != 16) return avi_file::error::UNSUPPORTED_VIDEO_FORMAT; chunkdata += 44; /* loop over tables */ for (int tabnum = 0; tabnum < 3; tabnum++) { huffyuv_table &table = huffyuv->table[tabnum]; std::uint32_t curbits, bitadd; std::uint16_t bitsat16 = 0; int offset = 0, bits; /* loop until we populate the whole table */ while (offset < 256) { int data, shift, count, i; /* extract the next run */ if (chunkdata >= chunkend) return avi_file::error::INVALID_DATA; data = *chunkdata++; shift = data & 0x1f; count = data >> 5; /* zero count means next whole byte is a count */ if (count == 0) { if (chunkdata >= chunkend) return avi_file::error::INVALID_DATA; count = *chunkdata++; } for (i = 0; i < count; i++) table.shift[offset++] = shift; } /* now determine bit patterns and masks */ curbits = 0; for (bits = 31; bits >= 0; bits--) { bitadd = 1 << (32 - bits); /* make sure we've cleared out all the bits below */ if ((curbits & (bitadd - 1)) != 0) return avi_file::error::INVALID_DATA; /* find all entries with this shift count and assign them */ for (offset = 0; offset < 256; offset++) if (table.shift[offset] == bits) { table.bits[offset] = curbits; table.mask[offset] = ~(bitadd - 1); curbits += bitadd; } /* remember the bit pattern when we complete all the 17-bit codes */ if (bits == 17) bitsat16 = curbits >> 16; } /* allocate the number of extra lookup tables we need */ if (bitsat16 > 0) { try { table.extralookup.resize(bitsat16 * 65536); } catch (...) { return avi_file::error::NO_MEMORY; } for (offset = 0; offset < bitsat16; offset++) table.baselookup[offset] = (offset << 8) | 0; } /* then create lookup tables */ for (offset = 0; offset < 256; offset++) if (table.shift[offset] > 16) { std::uint16_t *tablebase = &table.extralookup[(table.bits[offset] >> 16) * 65536]; std::uint32_t start = table.bits[offset] & 0xffff; std::uint32_t end = start + ((1 << (32 - table.shift[offset])) - 1); while (start <= end) tablebase[start++] = (offset << 8) | (table.shift[offset] - 16); } else if (table.shift[offset] > 0) { std::uint32_t start = table.bits[offset] >> 16; std::uint32_t end = start + ((1 << (16 - table.shift[offset])) - 1); while (start <= end) table.baselookup[start++] = (offset << 8) | table.shift[offset]; } } m_huffyuv = std::move(huffyuv); return avi_file::error::NONE; } /*------------------------------------------------- huffyuv_decompress_to_yuy16 - decompress a HuffYUV-encoded frame to a YUY16 bitmap -------------------------------------------------*/ /** * @fn static avi_error huffyuv_decompress_to_yuy16(avi_stream *stream, const std::uint8_t *data, std::uint32_t numbytes, bitmap_yuy16 &bitmap) * * @brief Huffyuv decompress to yuy 16. * * @param [in,out] stream If non-null, the stream. * @param data The data. * @param numbytes The numbytes. * @param [in,out] bitmap The bitmap. * * @return An avi_error. */ avi_file::error avi_stream::huffyuv_decompress_to_yuy16(const std::uint8_t *data, std::uint32_t numbytes, bitmap_yuy16 &bitmap) const { int prevlines = (m_height > 288) ? 2 : 1; std::uint8_t lastprevy = 0, lastprevcb = 0, lastprevcr = 0; std::uint8_t lasty = 0, lastcb = 0, lastcr = 0; std::uint8_t bitsinbuffer = 0; std::uint32_t bitbuffer = 0; std::uint32_t dataoffs = 0; int x, y; /* compressed video */ for (y = 0; y < m_height; y++) { std::uint16_t *dest = &bitmap.pix(y); /* handle the first four bytes independently */ x = 0; if (y == 0) { /* first DWORD is stored as YUY2 */ lasty = data[dataoffs++]; lastcb = data[dataoffs++]; *dest++ = (lasty << 8) | lastcb; lasty = data[dataoffs++]; lastcr = data[dataoffs++]; *dest++ = (lasty << 8) | lastcr; x = 2; } /* loop over pixels */ for ( ; x < m_width; x++) { huffyuv_table const &ytable = m_huffyuv->table[0]; huffyuv_table const &ctable = m_huffyuv->table[1 + (x & 1)]; std::uint16_t huffdata; int shift; /* fill up the buffer; they store little-endian DWORDs, so we XOR with 3 */ while (bitsinbuffer <= 24 && dataoffs < numbytes) { bitbuffer |= data[dataoffs++ ^ 3] << (24 - bitsinbuffer); bitsinbuffer += 8; } /* look up the Y component */ huffdata = ytable.baselookup[bitbuffer >> 16]; shift = huffdata & 0xff; if (shift == 0) { bitsinbuffer -= 16; bitbuffer <<= 16; /* fill up the buffer; they store little-endian DWORDs, so we XOR with 3 */ while (bitsinbuffer <= 24 && dataoffs < numbytes) { bitbuffer |= data[dataoffs++ ^ 3] << (24 - bitsinbuffer); bitsinbuffer += 8; } huffdata = ytable.extralookup[(huffdata >> 8) * 65536 + (bitbuffer >> 16)]; shift = huffdata & 0xff; } bitsinbuffer -= shift; bitbuffer <<= shift; std::uint16_t const pixel = huffdata & 0xff00; /* fill up the buffer; they store little-endian DWORDs, so we XOR with 3 */ while (bitsinbuffer <= 24 && dataoffs < numbytes) { bitbuffer |= data[dataoffs++ ^ 3] << (24 - bitsinbuffer); bitsinbuffer += 8; } /* look up the Cb/Cr component */ huffdata = ctable.baselookup[bitbuffer >> 16]; shift = huffdata & 0xff; if (shift == 0) { bitsinbuffer -= 16; bitbuffer <<= 16; /* fill up the buffer; they store little-endian DWORDs, so we XOR with 3 */ while (bitsinbuffer <= 24 && dataoffs < numbytes) { bitbuffer |= data[dataoffs++ ^ 3] << (24 - bitsinbuffer); bitsinbuffer += 8; } huffdata = ctable.extralookup[(huffdata >> 8) * 65536 + (bitbuffer >> 16)]; shift = huffdata & 0xff; } bitsinbuffer -= shift; bitbuffer <<= shift; *dest++ = pixel | (huffdata >> 8); } } /* apply deltas */ lastprevy = lastprevcb = lastprevcr = 0; for (y = 0; y < m_height; y++) { std::uint16_t *prevrow = &bitmap.pix(y - prevlines); std::uint16_t *dest = &bitmap.pix(y); /* handle the first four bytes independently */ x = 0; if (y == 0) { /* lasty, lastcr, lastcb are set up previously */ x = 2; } /* left predict or gradient predict */ if ((m_huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) == HUFFYUV_PREDICT_LEFT || ((m_huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) == HUFFYUV_PREDICT_GRADIENT)) { /* first do left deltas */ for ( ; x < m_width; x += 2) { std::uint16_t pixel0 = dest[x + 0]; std::uint16_t pixel1 = dest[x + 1]; lasty += pixel0 >> 8; lastcb += pixel0; dest[x + 0] = (lasty << 8) | lastcb; lasty += pixel1 >> 8; lastcr += pixel1; dest[x + 1] = (lasty << 8) | lastcr; } /* for gradient, we then add in the previous row */ if ((m_huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) == HUFFYUV_PREDICT_GRADIENT && y >= prevlines) for (x = 0; x < m_width; x++) { std::uint16_t curpix = dest[x]; std::uint16_t prevpix = prevrow[x]; std::uint8_t ysum = (curpix >> 8) + (prevpix >> 8); std::uint8_t csum = curpix + prevpix; dest[x] = (ysum << 8) | csum; } } /* median predict on rows > 0 */ else if ((m_huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) == HUFFYUV_PREDICT_MEDIAN && y >= prevlines) { for ( ; x < m_width; x += 2) { std::uint16_t prevpixel0 = prevrow[x + 0]; std::uint16_t prevpixel1 = prevrow[x + 1]; std::uint16_t pixel0 = dest[x + 0]; std::uint16_t pixel1 = dest[x + 1]; std::uint8_t a, b, c; /* compute previous, above, and (prev + above - above-left) */ a = lasty; b = prevpixel0 >> 8; c = lastprevy; lastprevy = b; if (a > b) { std::uint8_t tmp = a; a = b; b = tmp; } if (a > c) { std::uint8_t tmp = a; a = c; c = tmp; } if (b > c) { std::uint8_t tmp = b; b = c; c = tmp; } lasty = (pixel0 >> 8) + b; /* compute previous, above, and (prev + above - above-left) */ a = lastcb; b = prevpixel0 & 0xff; c = lastprevcb; lastprevcb = b; if (a > b) { std::uint8_t tmp = a; a = b; b = tmp; } if (a > c) { std::uint8_t tmp = a; a = c; c = tmp; } if (b > c) { std::uint8_t tmp = b; b = c; c = tmp; } lastcb = (pixel0 & 0xff) + b; dest[x + 0] = (lasty << 8) | lastcb; /* compute previous, above, and (prev + above - above-left) */ a = lasty; b = prevpixel1 >> 8; c = lastprevy; lastprevy = b; if (a > b) { std::uint8_t tmp = a; a = b; b = tmp; } if (a > c) { std::uint8_t tmp = a; a = c; c = tmp; } if (b > c) { std::uint8_t tmp = b; b = c; c = tmp; } lasty = (pixel1 >> 8) + b; /* compute previous, above, and (prev + above - above-left) */ a = lastcr; b = prevpixel1 & 0xff; c = lastprevcr; lastprevcr = b; if (a > b) { std::uint8_t tmp = a; a = b; b = tmp; } if (a > c) { std::uint8_t tmp = a; a = c; c = tmp; } if (b > c) { std::uint8_t tmp = b; b = c; c = tmp; } lastcr = (pixel1 & 0xff) + b; dest[x + 1] = (lasty << 8) | lastcr; } } } return avi_file::error::NONE; } /*------------------------------------------------- uncompressed_rgb24_to_argb32 - convert a raw RGB24-encoded frame to an ARGB32 bitmap -------------------------------------------------*/ avi_file::error avi_stream::uncompressed_rgb24_to_argb32(const std::uint8_t *data, std::uint32_t numbytes, bitmap_argb32 &bitmap) const { std::uint32_t dataoffs = 0; /* uncompressed video */ for (int y = 0; y < m_height; y++) { std::uint32_t *dest = &bitmap.pix(y); /* loop over pixels */ for (int x = 0; x < m_width; x++) { const uint8_t b = data[dataoffs++]; const uint8_t g = data[dataoffs++]; const uint8_t r = data[dataoffs++]; *dest++ = 0xff << 24 | r << 16 | g << 8 | b; } } return avi_file::error::NONE; } /*------------------------------------------------- uncompressed_yuv420p_to_argb32 - convert a YUV420p-encoded frame to an ARGB32 bitmap -------------------------------------------------*/ avi_file::error avi_stream::uncompressed_yuv420p_to_argb32(const std::uint8_t *data, std::uint32_t numbytes, bitmap_argb32 &bitmap) const { const int width = bitmap.width(); const int height = bitmap.height(); const int size_total = width * height; /* uncompressed video */ for (int y = 0; y < m_height; y++) { std::uint32_t *dest = &bitmap.pix(y); /* loop over pixels */ for (int x = 0; x < m_width; x++) { const uint8_t luma = data[y * width + x]; const uint8_t u = data[(y / 2) * (width / 2) + x / 2 + size_total]; const uint8_t v = data[(y / 2) * (width / 2) + x / 2 + size_total + size_total / 4]; int r = luma + (1.370705f * (v - 0x80)); int g = luma - (0.698001f * (v - 0x80)) - (0.337633f * (u - 0x80)); int b = luma + (1.732446f * (u - 0x80)); r = (r < 0) ? 0 : ((r > 255) ? 255 : r); g = (g < 0) ? 0 : ((g > 255) ? 255 : g); b = (b < 0) ? 0 : ((b > 255) ? 255 : b); *dest++ = 0xff << 24 | r << 16 | g << 8 | b; } } return avi_file::error::NONE; } /*------------------------------------------------- avi_close - close an AVI movie file -------------------------------------------------*/ /** * @fn avi_error avi_close(avi_file *file) * * @brief Avi close. * * @param [in,out] file If non-null, the file. * * @return An avi_error. */ avi_file_impl::~avi_file_impl() { error avierr = error::NONE; /* if we're creating a new file, finalize it by writing out the non-media chunks */ if (m_type == FILETYPE_CREATE) { /* flush any pending sound data */ avierr = soundbuf_flush(false); /* close the movi chunk */ if (avierr == error::NONE) avierr = chunk_close(); /* if this is the first RIFF chunk, write an idx1 */ if (avierr == error::NONE && m_riffbase == 0) avierr = write_idx1_chunk(); /* update the strh and indx chunks for each stream */ for (int strnum = 0; strnum < m_streams.size(); strnum++) { if (avierr == error::NONE) avierr = write_strh_chunk(m_streams[strnum], false); if (avierr == error::NONE) avierr = write_indx_chunk(m_streams[strnum], false); } /* update the avih chunk */ if (avierr == error::NONE) avierr = write_avih_chunk(false); /* close the RIFF chunk */ if (avierr == error::NONE) avierr = chunk_close(); } /* close the file */ m_file.reset(); //return avierr; } /*------------------------------------------------- avi_printf_chunks - print the chunks in a file -------------------------------------------------*/ /** * @fn void avi_printf_chunks(avi_file *file) * * @brief Avi printf chunks. * * @param [in,out] file If non-null, the file. */ void avi_file_impl::printf_chunks() { printf_chunk_recursive(&m_rootchunk, 0); } /*------------------------------------------------- get_movie_info - return a pointer to the movie info -------------------------------------------------*/ /** * @fn const avi_movie_info *avi_get_movie_info(avi_file *file) * * @brief Avi get movie information. * * @param [in,out] file If non-null, the file. * * @return null if it fails, else an avi_movie_info*. */ avi_file::movie_info const &avi_file_impl::get_movie_info() const { return m_info; } /*------------------------------------------------- avi_frame_to_sample - convert a frame index to a sample index -------------------------------------------------*/ /** * @fn std::uint32_t avi_first_sample_in_frame(avi_file *file, std::uint32_t framenum) * * @brief Avi first sample in frame. * * @param [in,out] file If non-null, the file. * @param framenum The framenum. * * @return An std::uint32_t. */ std::uint32_t avi_file_impl::first_sample_in_frame(std::uint32_t framenum) const { return framenum_to_samplenum(framenum); } /*------------------------------------------------- read_uncompressed_video_frame - read raw video data for a particular frame from the AVI file, converting to ARGB32 format -------------------------------------------------*/ avi_file::error avi_file_impl::read_uncompressed_video_frame(std::uint32_t framenum, bitmap_argb32 &bitmap) { /* get the video stream */ avi_stream *const stream = get_video_stream(); if (!stream) return error::INVALID_STREAM; if (stream->format() != FORMAT_UNCOMPRESSED && stream->format() != FORMAT_DIB && stream->format() != FORMAT_RGB && stream->format() != FORMAT_RAW && stream->format() != FORMAT_I420) return error::UNSUPPORTED_VIDEO_FORMAT; if (bitmap.width() < stream->width() || bitmap.height() < stream->height()) bitmap.resize(stream->width(), stream->height()); /* assume one chunk == one frame */ if (framenum >= stream->chunks()) return error::INVALID_FRAME; /* we only support YUY-style bitmaps (16bpp) */ if (bitmap.width() < stream->width() || bitmap.height() < stream->height()) return error::INVALID_BITMAP; /* expand the tempbuffer to hold the data if necessary */ error avierr = error::NONE; avierr = expand_tempbuffer(stream->chunk(framenum).length); if (avierr != error::NONE) return avierr; /* read in the data */ std::uint32_t bytes_read; osd_file::error const filerr = m_file->read(&m_tempbuffer[0], stream->chunk(framenum).offset, stream->chunk(framenum).length, bytes_read); if (filerr != osd_file::error::NONE || bytes_read != stream->chunk(framenum).length) return error::READ_ERROR; /* validate this is good data */ std::uint32_t const chunkid = fetch_32bits(&m_tempbuffer[0]); if (chunkid == get_chunkid_for_stream(stream)) { /* uncompressed YUV420p */ if (stream->format() == FORMAT_I420) avierr = stream->uncompressed_yuv420p_to_argb32(&m_tempbuffer[8], stream->chunk(framenum).length - 8, bitmap); else avierr = stream->uncompressed_rgb24_to_argb32(&m_tempbuffer[8], stream->chunk(framenum).length - 8, bitmap); } else { avierr = error::INVALID_DATA; } return avierr; } /*------------------------------------------------- read_video_frame - read video data for a particular frame from the AVI file, converting to YUY16 format -------------------------------------------------*/ avi_file::error avi_file_impl::read_video_frame(std::uint32_t framenum, bitmap_yuy16 &bitmap) { /* get the video stream */ avi_stream *const stream = get_video_stream(); if (!stream) return error::INVALID_STREAM; /* validate our ability to handle the data */ if (stream->format() != FORMAT_UYVY && stream->format() != FORMAT_VYUY && stream->format() != FORMAT_YUY2 && stream->format() != FORMAT_HFYU) return error::UNSUPPORTED_VIDEO_FORMAT; /* assume one chunk == one frame */ if (framenum >= stream->chunks()) return error::INVALID_FRAME; /* we only support YUY-style bitmaps (16bpp) */ if (bitmap.width() < stream->width() || bitmap.height() < stream->height()) return error::INVALID_BITMAP; /* expand the tempbuffer to hold the data if necessary */ error avierr = error::NONE; avierr = expand_tempbuffer(stream->chunk(framenum).length); if (avierr != error::NONE) return avierr; /* read in the data */ std::uint32_t bytes_read; osd_file::error const filerr = m_file->read(&m_tempbuffer[0], stream->chunk(framenum).offset, stream->chunk(framenum).length, bytes_read); if (filerr != osd_file::error::NONE || bytes_read != stream->chunk(framenum).length) return error::READ_ERROR; /* validate this is good data */ std::uint32_t const chunkid = fetch_32bits(&m_tempbuffer[0]); if (chunkid == get_chunkid_for_stream(stream)) { /* HuffYUV-compressed */ if (stream->format() == FORMAT_HFYU) avierr = stream->huffyuv_decompress_to_yuy16(&m_tempbuffer[8], stream->chunk(framenum).length - 8, bitmap); /* other YUV-compressed */ else avierr = stream->yuv_decompress_to_yuy16(&m_tempbuffer[8], stream->chunk(framenum).length - 8, bitmap); } else avierr = error::INVALID_DATA; return avierr; } /*------------------------------------------------- avi_read_sound_samples - read sound sample data from an AVI file -------------------------------------------------*/ /** * @fn avi_error avi_read_sound_samples(avi_file *file, int channel, std::uint32_t firstsample, std::uint32_t numsamples, std::int16_t *output) * * @brief Avi read sound samples. * * @param [in,out] file If non-null, the file. * @param channel The channel. * @param firstsample The firstsample. * @param numsamples The numsamples. * @param [in,out] output If non-null, the output. * * @return An avi_error. */ avi_file::error avi_file_impl::read_sound_samples(int channel, std::uint32_t firstsample, std::uint32_t numsamples, std::int16_t *output) { /* get the audio stream */ int offset = 0; avi_stream *const stream = get_audio_stream(channel, offset); if (!stream) return error::INVALID_STREAM; /* validate our ability to handle the data */ if (stream->format() != 0 || (stream->samplebits() != 8 && stream->samplebits() != 16)) return error::UNSUPPORTED_AUDIO_FORMAT; /* verify we are in range */ if (firstsample >= stream->samples()) return error::INVALID_FRAME; if (firstsample + numsamples > stream->samples()) numsamples = stream->samples() - firstsample; /* determine bytes per sample */ std::uint32_t const bytes_per_sample = stream->bytes_per_sample(); /* loop until all samples have been extracted */ while (numsamples > 0) { std::uint32_t chunkbase = 0, chunkend = 0, chunkid; std::uint32_t bytes_read, samples_this_chunk; int chunknum, sampnum; /* locate the chunk with the first sample */ for (chunknum = 0; chunknum < stream->chunks(); chunknum++) { chunkend = chunkbase + (stream->chunk(chunknum).length - 8) / bytes_per_sample; if (firstsample < chunkend) break; chunkbase = chunkend; } /* if we hit the end, fill the rest with silence */ if (chunknum == stream->chunks()) { std::memset(output, 0, numsamples * 2); break; } /* expand the tempbuffer to hold the data if necessary */ error avierr = expand_tempbuffer(stream->chunk(chunknum).length); if (avierr != error::NONE) return avierr; /* read in the data */ auto const filerr = m_file->read(&m_tempbuffer[0], stream->chunk(chunknum).offset, stream->chunk(chunknum).length, bytes_read); if (filerr != osd_file::error::NONE || bytes_read != stream->chunk(chunknum).length) return error::READ_ERROR; /* validate this is good data */ chunkid = fetch_32bits(&m_tempbuffer[0]); if (chunkid != get_chunkid_for_stream(stream)) return error::INVALID_DATA; /* determine how many samples to copy */ samples_this_chunk = chunkend - firstsample; samples_this_chunk = (std::min)(samples_this_chunk, numsamples); /* extract 16-bit samples from the chunk */ if (stream->samplebits() == 16) { const auto *base = reinterpret_cast(&m_tempbuffer[8]); base += stream->channels() * (firstsample - chunkbase) + offset; for (sampnum = 0; sampnum < samples_this_chunk; sampnum++) { *output++ = little_endianize_int16(*base); base += stream->channels(); } } /* extract 8-bit samples from the chunk */ else if (stream->samplebits() == 8) { const std::uint8_t *base = &m_tempbuffer[8]; base += stream->channels() * (firstsample - chunkbase) + offset; for (sampnum = 0; sampnum < samples_this_chunk; sampnum++) { *output++ = (*base << 8) - 0x8000; base += stream->channels(); } } /* update our counters */ firstsample += samples_this_chunk; numsamples -= samples_this_chunk; } return error::NONE; } /*------------------------------------------------- avi_append_video_frame_yuy16 - append a frame of video in YUY16 format -------------------------------------------------*/ /** * @fn avi_error avi_append_video_frame(avi_file *file, bitmap_yuy16 &bitmap) * * @brief Avi append video frame. * * @param [in,out] file If non-null, the file. * @param [in,out] bitmap The bitmap. * * @return An avi_error. */ avi_file::error avi_file_impl::append_video_frame(bitmap_yuy16 &bitmap) { avi_stream *const stream = get_video_stream(); error avierr; std::uint32_t maxlength; /* validate our ability to handle the data */ if (stream->format() != FORMAT_UYVY && stream->format() != FORMAT_VYUY && stream->format() != FORMAT_YUY2 && stream->format() != FORMAT_HFYU) return error::UNSUPPORTED_VIDEO_FORMAT; /* write out any sound data first */ avierr = soundbuf_write_chunk(stream->chunks()); if (avierr != error::NONE) return avierr; /* make sure we have enough room */ maxlength = 2 * stream->width() * stream->height(); avierr = expand_tempbuffer(maxlength); if (avierr != error::NONE) return avierr; /* now compress the data */ avierr = stream->yuy16_compress_to_yuy(bitmap, &m_tempbuffer[0], maxlength); if (avierr != error::NONE) return avierr; /* write the data */ avierr = chunk_write(get_chunkid_for_stream(stream), &m_tempbuffer[0], maxlength); if (avierr != error::NONE) return avierr; /* set the info for this new chunk */ avierr = stream->set_chunk_info(stream->chunks(), m_writeoffs - maxlength - 8, maxlength + 8); if (avierr != error::NONE) return avierr; stream->set_samples(m_info.video_numsamples = stream->chunks()); return error::NONE; } /*------------------------------------------------- avi_append_video_frame_rgb32 - append a frame of video in RGB32 format -------------------------------------------------*/ /** * @fn avi_error avi_append_video_frame(avi_file *file, bitmap_rgb32 &bitmap) * * @brief Avi append video frame. * * @param [in,out] file If non-null, the file. * @param [in,out] bitmap The bitmap. * * @return An avi_error. */ avi_file::error avi_file_impl::append_video_frame(bitmap_rgb32 &bitmap) { avi_stream *const stream = get_video_stream(); error avierr; std::uint32_t maxlength; /* validate our ability to handle the data */ if (stream->format() != 0) return error::UNSUPPORTED_VIDEO_FORMAT; /* depth must be 24 */ if (stream->depth() != 24) return error::UNSUPPORTED_VIDEO_FORMAT; /* write out any sound data first */ avierr = soundbuf_write_chunk(stream->chunks()); if (avierr != error::NONE) return avierr; /* make sure we have enough room */ maxlength = 3 * stream->width() * stream->height(); avierr = expand_tempbuffer(maxlength); if (avierr != error::NONE) return avierr; /* copy the RGB data to the destination */ avierr = stream->rgb32_compress_to_rgb(bitmap, &m_tempbuffer[0], maxlength); if (avierr != error::NONE) return avierr; /* write the data */ avierr = chunk_write(get_chunkid_for_stream(stream), &m_tempbuffer[0], maxlength); if (avierr != error::NONE) return avierr; /* set the info for this new chunk */ avierr = stream->set_chunk_info(stream->chunks(), m_writeoffs - maxlength - 8, maxlength + 8); if (avierr != error::NONE) return avierr; stream->set_samples(m_info.video_numsamples = stream->chunks()); return error::NONE; } /*------------------------------------------------- avi_append_sound_samples - append sound samples -------------------------------------------------*/ /** * @fn avi_error avi_append_sound_samples(avi_file *file, int channel, const std::int16_t *samples, std::uint32_t numsamples, std::uint32_t sampleskip) * * @brief Avi append sound samples. * * @param [in,out] file If non-null, the file. * @param channel The channel. * @param samples The samples. * @param numsamples The numsamples. * @param sampleskip The sampleskip. * * @return An avi_error. */ avi_file::error avi_file_impl::append_sound_samples(int channel, const std::int16_t *samples, std::uint32_t numsamples, std::uint32_t sampleskip) { std::uint32_t sampoffset = m_soundbuf_chansamples[channel]; std::uint32_t sampnum; /* see if we have enough room in the buffer */ if (sampoffset + numsamples > m_soundbuf_samples) return error::EXCEEDED_SOUND_BUFFER; /* append samples to the buffer in little-endian format */ for (sampnum = 0; sampnum < numsamples; sampnum++) { std::int16_t data = *samples++; samples += sampleskip; data = little_endianize_int16(data); m_soundbuf[sampoffset++ * m_info.audio_channels + channel] = data; } m_soundbuf_chansamples[channel] = sampoffset; /* flush any full sound chunks to disk */ return soundbuf_flush(true); } /*------------------------------------------------- read_chunk_data - read a chunk's data into memory -------------------------------------------------*/ /** * @fn static avi_error read_chunk_data(avi_file *file, const avi_chunk *chunk, std::uint8_t **buffer) * * @brief Reads chunk data. * * @param [in,out] file If non-null, the file. * @param chunk The chunk. * @param [in,out] buffer If non-null, the buffer. * * @return The chunk data. */ avi_file::error avi_file_impl::read_chunk_data(avi_chunk const &chunk, std::unique_ptr &buffer) { /* allocate memory for the data */ try { buffer.reset(new std::uint8_t[chunk.size]); } catch (...) { return error::NO_MEMORY; } /* read from the file */ std::uint32_t bytes_read; osd_file::error const filerr = m_file->read(&buffer[0], chunk.offset + 8, chunk.size, bytes_read); if (filerr != osd_file::error::NONE || bytes_read != chunk.size) { buffer.reset(); return error::READ_ERROR; } return error::NONE; } /*------------------------------------------------- get_first_chunk - get information about the first chunk in a container -------------------------------------------------*/ /** * @fn static avi_error get_first_chunk(avi_file *file, const avi_chunk *parent, avi_chunk *newchunk) * * @brief Gets the first chunk. * * @param [in,out] file If non-null, the file. * @param parent The parent. * @param [in,out] newchunk If non-null, the newchunk. * * @return The first chunk. */ avi_file::error avi_file_impl::get_first_chunk(avi_chunk const *parent, avi_chunk &newchunk) { std::uint64_t startoffset = (parent != nullptr && parent->type != 0) ? parent->offset + 12 : 0; if (parent != nullptr && parent->type != CHUNKTYPE_LIST && parent->type != CHUNKTYPE_RIFF && parent->type != 0) return error::INVALID_DATA; return get_next_chunk_internal(parent, newchunk, startoffset); } /*------------------------------------------------- get_next_chunk - get information about the next chunk in a container -------------------------------------------------*/ /** * @fn static avi_error get_next_chunk(avi_file *file, const avi_chunk *parent, avi_chunk *newchunk) * * @brief Gets the next chunk. * * @param [in,out] file If non-null, the file. * @param parent The parent. * @param [in,out] newchunk If non-null, the newchunk. * * @return The next chunk. */ avi_file::error avi_file_impl::get_next_chunk(avi_chunk const *parent, avi_chunk &newchunk) { std::uint64_t nextoffset = newchunk.offset + 8 + newchunk.size + (newchunk.size & 1); return get_next_chunk_internal(parent, newchunk, nextoffset); } /*------------------------------------------------- find_first_chunk - get information about the first chunk of a particular type in a container -------------------------------------------------*/ /** * @fn static avi_error find_first_chunk(avi_file *file, std::uint32_t findme, const avi_chunk *container, avi_chunk *result) * * @brief Searches for the first chunk. * * @param [in,out] file If non-null, the file. * @param findme The findme. * @param container The container. * @param [out] result If non-null, the result. * * @return The found chunk. */ avi_file::error avi_file_impl::find_first_chunk(std::uint32_t findme, const avi_chunk *container, avi_chunk &result) { error avierr; for (avierr = get_first_chunk(container, result); avierr == error::NONE; avierr = get_next_chunk(container, result)) if (result.type == findme) return error::NONE; return avierr; } /*------------------------------------------------- find_next_chunk - get information about the next chunk of a particular type in a container -------------------------------------------------*/ /** * @fn static avi_error find_next_chunk(avi_file *file, std::uint32_t findme, const avi_chunk *container, avi_chunk *result) * * @brief Searches for the next chunk. * * @param [in,out] file If non-null, the file. * @param findme The findme. * @param container The container. * @param [out] result If non-null, the result. * * @return The found chunk. */ avi_file::error avi_file_impl::find_next_chunk(std::uint32_t findme, const avi_chunk *container, avi_chunk &result) { error avierr; for (avierr = get_next_chunk(container, result); avierr == error::NONE; avierr = get_next_chunk(container, result)) if (result.type == findme) return error::NONE; return avierr; } /*------------------------------------------------- find_first_list - get information about the first list of a particular type in a container -------------------------------------------------*/ /** * @fn static avi_error find_first_list(avi_file *file, std::uint32_t findme, const avi_chunk *container, avi_chunk *result) * * @brief Searches for the first list. * * @param [in,out] file If non-null, the file. * @param findme The findme. * @param container The container. * @param [out] result If non-null, the result. * * @return The found list. */ avi_file::error avi_file_impl::find_first_list(std::uint32_t findme, const avi_chunk *container, avi_chunk &result) { error avierr; for (avierr = find_first_chunk(CHUNKTYPE_LIST, container, result); avierr == error::NONE; avierr = find_next_chunk(CHUNKTYPE_LIST, container, result)) if (result.listtype == findme) return error::NONE; return avierr; } /*------------------------------------------------- find_next_list - get information about the next list of a particular type in a container -------------------------------------------------*/ /** * @fn static avi_error find_next_list(avi_file *file, std::uint32_t findme, const avi_chunk *container, avi_chunk *result) * * @brief Searches for the next list. * * @param [in,out] file If non-null, the file. * @param findme The findme. * @param container The container. * @param [out] result If non-null, the result. * * @return The found list. */ avi_file::error avi_file_impl::find_next_list(std::uint32_t findme, const avi_chunk *container, avi_chunk &result) { error avierr; for (avierr = find_next_chunk(CHUNKTYPE_LIST, container, result); avierr == error::NONE; avierr = find_next_chunk(CHUNKTYPE_LIST, container, result)) if (result.listtype == findme) return error::NONE; return avierr; } /*------------------------------------------------- get_next_chunk_internal - fetch the next chunk relative to the current one -------------------------------------------------*/ /** * @fn static avi_error get_next_chunk_internal(avi_file *file, const avi_chunk *parent, avi_chunk *newchunk, std::uint64_t offset) * * @brief Gets the next chunk internal. * * @param [in,out] file If non-null, the file. * @param parent The parent. * @param [in,out] newchunk If non-null, the newchunk. * @param offset The offset. * * @return The next chunk internal. */ avi_file::error avi_file_impl::get_next_chunk_internal(const avi_chunk *parent, avi_chunk &newchunk, std::uint64_t offset) { osd_file::error filerr; std::uint8_t buffer[12]; std::uint32_t bytesread; /* nullptr parent implies the root */ if (parent == nullptr) parent = &m_rootchunk; /* start at the current offset */ newchunk.offset = offset; /* if we're past the bounds of the parent, bail */ if (newchunk.offset + 8 >= parent->offset + 8 + parent->size) return error::END; /* read the header */ filerr = m_file->read(buffer, newchunk.offset, 8, bytesread); if (filerr != osd_file::error::NONE || bytesread != 8) return error::INVALID_DATA; /* fill in the new chunk */ newchunk.type = fetch_32bits(&buffer[0]); newchunk.size = fetch_32bits(&buffer[4]); /* if we are a list, fetch the list type */ if (newchunk.type == CHUNKTYPE_LIST || newchunk.type == CHUNKTYPE_RIFF) { filerr = m_file->read(&buffer[8], newchunk.offset + 8, 4, bytesread); if (filerr != osd_file::error::NONE || bytesread != 4) return error::INVALID_DATA; newchunk.listtype = fetch_32bits(&buffer[8]); } return error::NONE; } /*------------------------------------------------- read_movie_data - get data about a movie -------------------------------------------------*/ /** * @fn static avi_error read_movie_data(avi_file *file) * * @brief Reads movie data. * * @param [in,out] file If non-null, the file. * * @return The movie data. */ avi_file::error avi_file_impl::read_movie_data() { avi_chunk riff, hdrl, avih, strl, strh, strf, indx, movi, idx1; error avierr; /* find the RIFF chunk */ avierr = find_first_chunk(CHUNKTYPE_RIFF, nullptr, riff); if (avierr != error::NONE) return avierr; /* verify that the RIFF type is AVI */ if (riff.listtype != LISTTYPE_AVI) { avierr = error::INVALID_DATA; return avierr; } /* find the hdrl LIST chunk within the RIFF */ avierr = find_first_list(LISTTYPE_HDRL, &riff, hdrl); if (avierr != error::NONE) return avierr; /* find the avih chunk */ avierr = find_first_chunk(CHUNKTYPE_AVIH, &hdrl, avih); if (avierr != error::NONE) return avierr; /* parse the avih chunk */ avierr = parse_avih_chunk(avih); if (avierr != error::NONE) return avierr; /* loop over strl LIST chunks */ int strindex = 0; for (avierr = find_first_list(LISTTYPE_STRL, &hdrl, strl); avierr == error::NONE; avierr = find_next_list(LISTTYPE_STRL, &hdrl, strl)) { /* if we have too many, it's a bad file */ if (strindex >= m_streams.size()) return avierr; /* find the strh chunk */ avierr = find_first_chunk(CHUNKTYPE_STRH, &strl, strh); if (avierr != error::NONE) return avierr; /* parse the data */ avierr = parse_strh_chunk(m_streams[strindex], strh); if (avierr != error::NONE) return avierr; /* find the strf chunk */ avierr = find_first_chunk(CHUNKTYPE_STRF, &strl, strf); if (avierr != error::NONE) return avierr; /* parse the data */ avierr = parse_strf_chunk(m_streams[strindex], strf); if (avierr != error::NONE) return avierr; /* find the indx chunk, if present */ avierr = find_first_chunk(CHUNKTYPE_INDX, &strl, indx); if (avierr == error::NONE) avierr = parse_indx_chunk(m_streams[strindex], indx); /* next stream */ strindex++; } /* normalize the error after parsing the stream headers */ if (avierr == error::END) avierr = error::NONE; if (avierr != error::NONE) return avierr; /* find the base of the movi data */ avierr = find_first_list(LISTTYPE_MOVI, &riff, movi); if (avierr != error::NONE) return avierr; /* find and parse the idx1 chunk within the RIFF (if present) */ avierr = find_first_chunk(CHUNKTYPE_IDX1, &riff, idx1); if (avierr == error::NONE) avierr = parse_idx1_chunk(movi.offset + 8, idx1); if (avierr != error::NONE) return avierr; /* now extract the movie info */ avierr = extract_movie_info(); return avierr; } /*------------------------------------------------- extract_movie_info - extract the movie info from the streams we've read -------------------------------------------------*/ /** * @fn static avi_error extract_movie_info(avi_file *file) * * @brief Extracts the movie information described by file. * * @param [in,out] file If non-null, the file. * * @return The extracted movie information. */ avi_file::error avi_file_impl::extract_movie_info() { avi_stream *stream; int offset; /* get the video stream */ stream = get_video_stream(); if (stream != nullptr) { /* fill in the info */ m_info.video_format = stream->format(); m_info.video_timescale = stream->rate(); m_info.video_sampletime = stream->scale(); m_info.video_numsamples = stream->samples(); m_info.video_width = stream->width(); m_info.video_height = stream->height(); } /* get the first audio stream */ stream = get_audio_stream(0, offset); if (stream != nullptr) { /* fill in the info */ m_info.audio_format = stream->format(); m_info.audio_timescale = stream->rate(); m_info.audio_sampletime = stream->scale(); m_info.audio_numsamples = stream->samples(); m_info.audio_channels = 1; m_info.audio_samplebits = stream->samplebits(); m_info.audio_samplerate = stream->samplerate(); } /* now make sure all other audio streams are valid */ while (nullptr != (stream = get_audio_stream(m_info.audio_channels, offset))) { /* get the stream info */ m_info.audio_channels++; /* verify compatibility */ if (m_info.audio_format != stream->format() || m_info.audio_timescale != stream->rate() || m_info.audio_sampletime != stream->scale() || m_info.audio_numsamples != stream->samples() || m_info.audio_samplebits != stream->samplebits() || m_info.audio_samplerate != stream->samplerate()) return error::INCOMPATIBLE_AUDIO_STREAMS; } return error::NONE; } /*------------------------------------------------- parse_avih_chunk - parse an avih header chunk -------------------------------------------------*/ /** * @fn static avi_error parse_avih_chunk(avi_file *file, avi_chunk *avih) * * @brief Parse avih chunk. * * @param [in,out] file If non-null, the file. * @param [in,out] avih If non-null, the avih. * * @return An avi_error. */ avi_file::error avi_file_impl::parse_avih_chunk(avi_chunk const &avih) { /* read the data */ std::unique_ptr chunkdata; error avierr = read_chunk_data(avih, chunkdata); if (avierr != error::NONE) return avierr; /* extract the data */ std::uint32_t const streams = fetch_32bits(&chunkdata[24]); /* allocate memory for the streams */ m_streams.clear(); m_streams.resize(streams); return avierr; } /*------------------------------------------------- parse_strh_chunk - parse a strh header chunk -------------------------------------------------*/ /** * @fn static avi_error parse_strh_chunk(avi_file *file, avi_stream *stream, avi_chunk *strh) * * @brief Parse strh chunk. * * @param [in,out] file If non-null, the file. * @param [in,out] stream If non-null, the stream. * @param [in,out] strh If non-null, the strh. * * @return An avi_error. */ avi_file::error avi_file_impl::parse_strh_chunk(avi_stream &stream, avi_chunk const &strh) { /* read the data */ std::unique_ptr chunkdata; error const avierr = read_chunk_data(strh, chunkdata); if (avierr != error::NONE) return avierr; /* extract the data */ return stream.set_strh_data(&chunkdata[0], strh.size); } /*------------------------------------------------- parse_strf_chunk - parse a strf header chunk -------------------------------------------------*/ /** * @fn static avi_error parse_strf_chunk(avi_file *file, avi_stream *stream, avi_chunk *strf) * * @brief Parse strf chunk. * * @param [in,out] file If non-null, the file. * @param [in,out] stream If non-null, the stream. * @param [in,out] strf If non-null, the strf. * * @return An avi_error. */ avi_file::error avi_file_impl::parse_strf_chunk(avi_stream &stream, avi_chunk const &strf) { error avierr; /* read the data */ std::unique_ptr chunkdata; avierr = read_chunk_data(strf, chunkdata); if (avierr != error::NONE) return avierr; return stream.set_strf_data(&chunkdata[0], strf.size); } /*------------------------------------------------- parse_indx_chunk - parse an indx chunk -------------------------------------------------*/ /** * @fn static avi_error parse_indx_chunk(avi_file *file, avi_stream *stream, avi_chunk *strf) * * @brief Parse indx chunk. * * @param [in,out] file If non-null, the file. * @param [in,out] stream If non-null, the stream. * @param [in,out] strf If non-null, the strf. * * @return An avi_error. */ avi_file::error avi_file_impl::parse_indx_chunk(avi_stream &stream, avi_chunk const &strf) { error avierr; /* read the data */ std::unique_ptr chunkdata; avierr = read_chunk_data(strf, chunkdata); if (avierr != error::NONE) return avierr; /* extract the data */ std::uint16_t const longs_per_entry = fetch_16bits(&chunkdata[0]); //subtype = chunkdata[2]; std::uint8_t const type = chunkdata[3]; std::uint32_t const entries = fetch_32bits(&chunkdata[4]); //id = fetch_32bits(&chunkdata[8]); std::uint64_t const baseoffset = fetch_64bits(&chunkdata[12]); /* if this is a superindex, loop over entries and call ourselves recursively */ if (type == AVI_INDEX_OF_INDEXES) { /* validate the size of each entry */ if (longs_per_entry != 4) return error::INVALID_DATA; /* loop over entries and create subchunks for each */ for (std::uint32_t entry = 0; entry < entries; entry++) { const std::uint8_t *base = &chunkdata[24 + entry * 16]; osd_file::error filerr; avi_chunk subchunk; std::uint32_t bytes_read; std::uint8_t buffer[8]; /* go read the subchunk */ subchunk.offset = fetch_64bits(&base[0]); filerr = m_file->read(buffer, subchunk.offset, sizeof(buffer), bytes_read); if (filerr != osd_file::error::NONE || bytes_read != sizeof(buffer)) { avierr = error::READ_ERROR; break; } /* parse the data */ subchunk.type = fetch_32bits(&buffer[0]); subchunk.size = fetch_32bits(&buffer[4]); /* recursively parse each referenced chunk; stop if we hit an error */ avierr = parse_indx_chunk(stream, subchunk); if (avierr != error::NONE) break; } } /* otherwise, this is a standard index */ else if (type == AVI_INDEX_OF_CHUNKS) { /* validate the size of each entry */ if (longs_per_entry != 2 && longs_per_entry != 3) return error::INVALID_DATA; /* loop over entries and parse out the data */ for (std::uint32_t entry = 0; entry < entries; entry++) { const std::uint8_t *base = &chunkdata[24 + entry * 4 * longs_per_entry]; std::uint32_t offset = fetch_32bits(&base[0]); std::uint32_t size = fetch_32bits(&base[4]) & 0x7fffffff; // bit 31 == NOT a keyframe /* set the info for this chunk and advance */ avierr = stream.set_chunk_info(stream.chunks(), baseoffset + offset - 8, size + 8); if (avierr != error::NONE) break; } } return avierr; } /*------------------------------------------------- parse_idx1_chunk - parse an idx1 chunk -------------------------------------------------*/ /** * @fn static avi_error parse_idx1_chunk(avi_file *file, std::uint64_t baseoffset, avi_chunk *idx1) * * @brief Parse index 1 chunk. * * @param [in,out] file If non-null, the file. * @param baseoffset The baseoffset. * @param [in,out] idx1 If non-null, the first index. * * @return An avi_error. */ avi_file::error avi_file_impl::parse_idx1_chunk(std::uint64_t baseoffset, avi_chunk const &idx1) { error avierr; /* read the data */ std::unique_ptr chunkdata; avierr = read_chunk_data(idx1, chunkdata); if (avierr != error::NONE) return avierr; /* loop over entries */ std::uint32_t const entries = idx1.size / 16; for (std::uint32_t entry = 0; entry < entries; entry++) { std::uint8_t const *const base = &chunkdata[entry * 16]; std::uint32_t const chunkid = fetch_32bits(&base[0]); std::uint32_t const offset = fetch_32bits(&base[8]); std::uint32_t const size = fetch_32bits(&base[12]); int streamnum; /* determine the stream index */ streamnum = ((chunkid >> 8) & 0xff) - '0'; streamnum += 10 * ((chunkid & 0xff) - '0'); if (streamnum >= m_streams.size()) return error::INVALID_DATA; avi_stream &stream = m_streams[streamnum]; /* set the appropriate entry */ avierr = stream.set_chunk_info(stream.chunks(), baseoffset + offset, size + 8); if (avierr != error::NONE) return avierr; } return avierr; } /*------------------------------------------------- chunk_open - open a new chunk for writing -------------------------------------------------*/ /** * @fn static avi_error chunk_open(avi_file *file, std::uint32_t type, std::uint32_t listtype, std::uint32_t estlength) * * @brief Queries if a given chunk open. * * @param [in,out] file If non-null, the file. * @param type The type. * @param listtype The listtype. * @param estlength The estlength. * * @return An avi_error. */ avi_file::error avi_file_impl::chunk_open(std::uint32_t type, std::uint32_t listtype, std::uint32_t estlength) { /* if we're out of stack entries, bail */ if (m_chunksp >= m_chunkstack.size()) return error::STACK_TOO_DEEP; avi_chunk &chunk = m_chunkstack[m_chunksp++]; /* set up the chunk information */ chunk.offset = m_writeoffs; chunk.size = estlength; chunk.type = type; chunk.listtype = listtype; /* non-list types */ if (type != CHUNKTYPE_RIFF && type != CHUNKTYPE_LIST) { std::uint8_t buffer[8]; /* populate the header */ put_32bits(&buffer[0], chunk.type); put_32bits(&buffer[4], chunk.size); /* write the header */ std::uint32_t written; osd_file::error const filerr = m_file->write(buffer, m_writeoffs, sizeof(buffer), written); if (filerr != osd_file::error::NONE || written != sizeof(buffer)) return error::WRITE_ERROR; m_writeoffs += written; } /* list types */ else { std::uint8_t buffer[12]; /* populate the header */ put_32bits(&buffer[0], chunk.type); put_32bits(&buffer[4], chunk.size); put_32bits(&buffer[8], chunk.listtype); /* write the header */ std::uint32_t written; osd_file::error const filerr = m_file->write(buffer, m_writeoffs, sizeof(buffer), written); if (filerr != osd_file::error::NONE || written != sizeof(buffer)) return error::WRITE_ERROR; m_writeoffs += written; } return error::NONE; } /*------------------------------------------------- chunk_close - finish writing an chunk -------------------------------------------------*/ /** * @fn static avi_error chunk_close(avi_file *file) * * @brief Chunk close. * * @param [in,out] file If non-null, the file. * * @return An avi_error. */ avi_file::error avi_file_impl::chunk_close() { avi_chunk const &chunk = m_chunkstack[--m_chunksp]; std::uint64_t const chunksize = m_writeoffs - (chunk.offset + 8); /* error if we don't fit into 32 bits */ if (chunksize != std::uint32_t(chunksize)) return error::INVALID_DATA; /* write the final size if it is different from the guess */ if (chunk.size != chunksize) { std::uint8_t buffer[4]; put_32bits(&buffer[0], std::uint32_t(chunksize)); std::uint32_t written; osd_file::error const filerr = m_file->write(buffer, chunk.offset + 4, sizeof(buffer), written); if (filerr != osd_file::error::NONE || written != sizeof(buffer)) return error::WRITE_ERROR; } /* round up to the next word */ m_writeoffs += chunksize & 1; return error::NONE; } /*------------------------------------------------- chunk_write - write an chunk and its data -------------------------------------------------*/ /** * @fn static avi_error chunk_write(avi_file *file, std::uint32_t type, const void *data, std::uint32_t length) * * @brief Chunk write. * * @param [in,out] file If non-null, the file. * @param type The type. * @param data The data. * @param length The length. * * @return An avi_error. */ avi_file::error avi_file_impl::chunk_write(std::uint32_t type, const void *data, std::uint32_t length) { error avierr; /* if we are the first RIFF, we must reserve enough space for the IDX chunk */ std::uint32_t const idxreserve = (m_riffbase == 0 && type != CHUNKTYPE_IDX1) ? compute_idx1_size() : 0; /* if we are getting too big, split the RIFF */ /* note that we ignore writes before the current RIFF base, as those are assumed to be overwrites of a chunk from the previous RIFF */ if ((m_writeoffs >= m_riffbase) && ((m_writeoffs + length + idxreserve - m_riffbase) >= MAX_RIFF_SIZE)) { /* close the movi list */ avierr = chunk_close(); if (avierr != error::NONE) return avierr; /* write the idx1 chunk if this is the first */ if (m_riffbase == 0) { avierr = write_idx1_chunk(); if (avierr != error::NONE) return avierr; } /* close the RIFF */ avierr = chunk_close(); if (avierr != error::NONE) return avierr; /* open a new RIFF */ m_riffbase = m_writeoffs; avierr = chunk_open(CHUNKTYPE_RIFF, LISTTYPE_AVIX, 0); if (avierr != error::NONE) return avierr; /* open a nested movi list */ m_saved_movi_offset = m_writeoffs; avierr = chunk_open(CHUNKTYPE_LIST, LISTTYPE_MOVI, 0); if (avierr != error::NONE) return avierr; } /* open the chunk */ avierr = chunk_open(type, 0, length); if (avierr != error::NONE) return avierr; /* write the data */ std::uint32_t written; osd_file::error const filerr = m_file->write(data, m_writeoffs, length, written); if (filerr != osd_file::error::NONE || written != length) return error::WRITE_ERROR; m_writeoffs += written; /* close the chunk */ return chunk_close(); } /*------------------------------------------------- chunk_overwrite - write a chunk in two passes; first pass writes to the end of file and saves the offset; second pass overwrites the original -------------------------------------------------*/ /** * @fn static avi_error chunk_overwrite(avi_file *file, std::uint32_t type, const void *data, std::uint32_t length, std::uint64_t *offset, int initial_write) * * @brief Chunk overwrite. * * @param [in,out] file If non-null, the file. * @param type The type. * @param data The data. * @param length The length. * @param [in,out] offset If non-null, the offset. * @param initial_write The initial write. * * @return An avi_error. */ avi_file::error avi_file_impl::chunk_overwrite(std::uint32_t type, const void *data, std::uint32_t length, std::uint64_t &offset, bool initial_write) { std::uint64_t savedoffset = 0; /* if this is our initial write, save the offset */ if (initial_write) offset = m_writeoffs; /* otherwise, remember the current write offset and replace it with the original */ else { savedoffset = m_writeoffs; m_writeoffs = offset; } /* write the chunk */ error const avierr = chunk_write(type, data, length); /* if this isn't the initial write, restore the previous offset */ if (!initial_write) m_writeoffs = savedoffset; return avierr; } /*------------------------------------------------- write_initial_headers - write out the initial set of AVI and stream headers -------------------------------------------------*/ /** * @fn static avi_error write_initial_headers(avi_file *file) * * @brief Writes an initial headers. * * @param [in,out] file If non-null, the file. * * @return An avi_error. */ avi_file::error avi_file_impl::write_initial_headers() { error avierr; /* reset the write pointer */ m_writeoffs = 0; /* open a RIFF chunk */ avierr = chunk_open(CHUNKTYPE_RIFF, LISTTYPE_AVI, 0); if (avierr != error::NONE) return avierr; /* open a hdlr LIST */ avierr = chunk_open(CHUNKTYPE_LIST, LISTTYPE_HDRL, 0); if (avierr != error::NONE) return avierr; /* write an avih chunk */ avierr = write_avih_chunk(true); if (avierr != error::NONE) return avierr; /* for each stream, write a strl LIST */ for (int strnum = 0; strnum < m_streams.size(); strnum++) { /* open a strl LIST */ avierr = chunk_open(CHUNKTYPE_LIST, LISTTYPE_STRL, 0); if (avierr != error::NONE) return avierr; /* write the strh chunk */ avierr = write_strh_chunk(m_streams[strnum], true); if (avierr != error::NONE) return avierr; /* write the strf chunk */ avierr = write_strf_chunk(m_streams[strnum]); if (avierr != error::NONE) return avierr; /* write the indx chunk */ avierr = write_indx_chunk(m_streams[strnum], true); if (avierr != error::NONE) return avierr; /* close the strl LIST */ avierr = chunk_close(); if (avierr != error::NONE) return avierr; } /* close the hdlr LIST */ avierr = chunk_close(); if (avierr != error::NONE) return avierr; /* open a movi LIST */ m_saved_movi_offset = m_writeoffs; avierr = chunk_open(CHUNKTYPE_LIST, LISTTYPE_MOVI, 0); if (avierr != error::NONE) return avierr; return avierr; } /*------------------------------------------------- write_avih_chunk - write the avih header chunk -------------------------------------------------*/ /** * @fn static avi_error write_avih_chunk(avi_file *file, int initial_write) * * @brief Writes an avih chunk. * * @param [in,out] file If non-null, the file. * @param initial_write The initial write. * * @return An avi_error. */ avi_file::error avi_file_impl::write_avih_chunk(bool initial_write) { avi_stream *video = get_video_stream(); std::uint8_t buffer[56]; /* reset the buffer */ std::memset(buffer, 0, sizeof(buffer)); put_32bits(&buffer[0], 1000000 * std::int64_t(video->scale()) / video->rate()); /* dwMicroSecPerFrame */ put_32bits(&buffer[12], AVIF_HASINDEX | AVIF_ISINTERLEAVED); /* dwFlags */ put_32bits(&buffer[16], video->samples()); /* dwTotalFrames */ put_32bits(&buffer[24], m_streams.size()); /* dwStreams */ put_32bits(&buffer[32], video->width()); /* dwWidth */ put_32bits(&buffer[36], video->height()); /* dwHeight */ /* (over)write the chunk */ return chunk_overwrite(CHUNKTYPE_AVIH, buffer, sizeof(buffer), m_saved_avih_offset, initial_write); } /*------------------------------------------------- write_strh_chunk - write the strh header chunk -------------------------------------------------*/ /** * @fn static avi_error write_strh_chunk(avi_file *file, avi_stream *stream, int initial_write) * * @brief Writes a strh chunk. * * @param [in,out] file If non-null, the file. * @param [in,out] stream If non-null, the stream. * @param initial_write The initial write. * * @return An avi_error. */ avi_file::error avi_file_impl::write_strh_chunk(avi_stream &stream, bool initial_write) { std::uint8_t buffer[56]; /* reset the buffer */ std::memset(buffer, 0, sizeof(buffer)); put_32bits(&buffer[0], stream.type()); /* fccType */ put_32bits(&buffer[20], stream.scale()); /* dwScale */ put_32bits(&buffer[24], stream.rate()); /* dwRate */ put_32bits(&buffer[32], stream.samples()); /* dwLength */ put_32bits(&buffer[40], 10000); /* dwQuality */ /* video-stream specific data */ if (stream.type() == STREAMTYPE_VIDS) { put_32bits(&buffer[4], /* fccHandler */ (stream.format() == FORMAT_HFYU) ? HANDLER_HFYU : HANDLER_DIB); put_32bits(&buffer[36], /* dwSuggestedBufferSize */ stream.width() * stream.height() * 4); put_16bits(&buffer[52], stream.width()); /* rcFrame.right */ put_16bits(&buffer[54], stream.height()); /* rcFrame.bottom */ } /* audio-stream specific data */ if (stream.type() == STREAMTYPE_AUDS) { put_32bits(&buffer[36], /* dwSuggestedBufferSize */ stream.samplerate() * stream.bytes_per_sample()); put_32bits(&buffer[44], /* dwSampleSize */ stream.bytes_per_sample()); } /* write the chunk */ return chunk_overwrite(CHUNKTYPE_STRH, buffer, sizeof(buffer), stream.saved_strh_offset(), initial_write); } /*------------------------------------------------- write_strf_chunk - write the strf header chunk -------------------------------------------------*/ /** * @fn static avi_error write_strf_chunk(avi_file *file, avi_stream *stream) * * @brief Writes a strf chunk. * * @param [in,out] file If non-null, the file. * @param [in,out] stream If non-null, the stream. * * @return An avi_error. */ avi_file::error avi_file_impl::write_strf_chunk(avi_stream const &stream) { /* video stream */ if (stream.type() == STREAMTYPE_VIDS) { std::uint8_t buffer[40]; /* reset the buffer */ std::memset(buffer, 0, sizeof(buffer)); put_32bits(&buffer[0], sizeof(buffer)); /* biSize */ put_32bits(&buffer[4], stream.width()); /* biWidth */ put_32bits(&buffer[8], stream.height()); /* biHeight */ put_16bits(&buffer[12], 1); /* biPlanes */ put_16bits(&buffer[14], stream.depth()); /* biBitCount */ put_32bits(&buffer[16], stream.format()); /* biCompression */ put_32bits(&buffer[20], /* biSizeImage */ stream.width() * stream.height() * (stream.depth() + 7) / 8); /* write the chunk */ return chunk_write(CHUNKTYPE_STRF, buffer, sizeof(buffer)); } /* audio stream */ if (stream.type() == STREAMTYPE_AUDS) { std::uint8_t buffer[16]; /* reset the buffer */ std::memset(buffer, 0, sizeof(buffer)); put_16bits(&buffer[0], 1); /* wFormatTag */ put_16bits(&buffer[2], stream.channels()); /* nChannels */ put_32bits(&buffer[4], stream.samplerate()); /* nSamplesPerSec */ put_32bits(&buffer[8], /* nAvgBytesPerSec */ stream.samplerate() * stream.bytes_per_sample()); put_16bits(&buffer[12], /* nBlockAlign */ stream.bytes_per_sample()); put_16bits(&buffer[14], stream.samplebits()); /* wBitsPerSample */ /* write the chunk */ return chunk_write(CHUNKTYPE_STRF, buffer, sizeof(buffer)); } return error::INVALID_DATA; } /*------------------------------------------------- write_indx_chunk - write the indx header chunk -------------------------------------------------*/ /** * @fn static avi_error write_indx_chunk(avi_file *file, avi_stream *stream, int initial_write) * * @brief Writes an indx chunk. * * @param [in,out] file If non-null, the file. * @param [in,out] stream If non-null, the stream. * @param initial_write The initial write. * * @return An avi_error. */ avi_file::error avi_file_impl::write_indx_chunk(avi_stream &stream, bool initial_write) { std::uint8_t buffer[24 + 16 * MAX_AVI_SIZE_IN_GB / 4]; std::uint32_t master_entries = 0; /* reset the buffer */ std::memset(buffer, 0, sizeof(buffer)); /* construct the chunk ID and index chunk ID */ std::uint32_t const chunkid = get_chunkid_for_stream(&stream); std::uint32_t const indexchunkid = AVI_FOURCC('i', 'x', '0' + (&stream - &m_streams[0]) / 10, '0' + (&stream - &m_streams[0]) % 10); /* loop over chunks of 4GB and write out indexes for them first */ if (!initial_write && m_riffbase != 0) { for (std::uint64_t currentbase = 0; currentbase < m_writeoffs; currentbase += FOUR_GB) { std::uint64_t const currentend = currentbase + FOUR_GB; std::uint32_t chunks_this_index = 0; std::uint32_t bytes_this_index = 0; /* count chunks that are in this region */ for (std::uint32_t chunknum = 0; chunknum < stream.chunks(); chunknum++) if (stream.chunk(chunknum).offset >= currentbase && stream.chunk(chunknum).offset < currentend) chunks_this_index++; /* if no chunks, skip */ if (chunks_this_index == 0) continue; if (master_entries >= MAX_AVI_SIZE_IN_GB / 4) return error::WRITE_ERROR; /* allocate memory */ std::unique_ptr tempbuf; try { tempbuf.reset(new std::uint8_t[24 + 8 * chunks_this_index]); } catch (...) { return error::NO_MEMORY; } std::memset(&tempbuf[0], 0, 24 + 8 * chunks_this_index); /* make a regular index */ put_16bits(&tempbuf[0], 2); /* wLongsPerEntry */ tempbuf[2] = 0; /* bIndexSubType */ tempbuf[3] = AVI_INDEX_OF_CHUNKS; /* bIndexType */ put_32bits(&tempbuf[4], chunks_this_index); /* nEntriesInUse */ put_32bits(&tempbuf[8], chunkid); /* dwChunkId */ put_64bits(&tempbuf[12], currentbase); /* qwBaseOffset */ /* now fill in the indexes */ chunks_this_index = 0; for (std::uint32_t chunknum = 0; chunknum < stream.chunks(); chunknum++) if (stream.chunk(chunknum).offset >= currentbase && stream.chunk(chunknum).offset < currentend) { put_32bits(&tempbuf[24 + 8 * chunks_this_index + 0], stream.chunk(chunknum).offset + 8 - currentbase); put_32bits(&tempbuf[24 + 8 * chunks_this_index + 4], stream.chunk(chunknum).length - 8); bytes_this_index += stream.chunk(chunknum).length; chunks_this_index++; } /* write the offset of this index to the master table */ put_64bits(&buffer[24 + 16 * master_entries + 0], m_writeoffs); put_32bits(&buffer[24 + 16 * master_entries + 8], 24 + 8 * chunks_this_index + 8); if (stream.type() == STREAMTYPE_VIDS) put_32bits(&buffer[24 + 16 * master_entries + 12], chunks_this_index); else if (stream.type() == STREAMTYPE_AUDS) put_32bits(&buffer[24 + 16 * master_entries + 12], bytes_this_index / stream.bytes_per_sample()); master_entries++; /* write the index */ error const avierr = chunk_write(indexchunkid, &tempbuf[0], 24 + 8 * chunks_this_index); if (avierr != error::NONE) return avierr; } } /* build up the master index */ if (master_entries != 0) { put_16bits(&buffer[0], 4); /* wLongsPerEntry */ buffer[2] = 0; /* bIndexSubType */ buffer[3] = AVI_INDEX_OF_INDEXES; /* bIndexType */ put_32bits(&buffer[4], master_entries); /* nEntriesInUse */ put_32bits(&buffer[8], chunkid); /* dwChunkId */ } /* (over)write the chunk */ return chunk_overwrite((master_entries == 0) ? CHUNKTYPE_JUNK : CHUNKTYPE_INDX, buffer, sizeof(buffer), stream.saved_indx_offset(), initial_write); } /*------------------------------------------------- write_idx1_chunk - write the idx1 chunk -------------------------------------------------*/ /** * @fn static avi_error write_idx1_chunk(avi_file *file) * * @brief Writes an index 1 chunk. * * @param [in,out] file If non-null, the file. * * @return An avi_error. */ avi_file::error avi_file_impl::write_idx1_chunk() { std::uint32_t const tempbuflength = compute_idx1_size() - 8; std::uint32_t curchunk[2] = { 0 }; /* allocate a temporary buffer */ std::unique_ptr tempbuf; try { tempbuf.reset(new std::uint8_t[tempbuflength]); } catch (...) { return error::NO_MEMORY; } /* fill it in */ for (std::uint32_t curoffset = 0; curoffset < tempbuflength; curoffset += 16) { std::uint64_t minoffset = ~std::uint64_t(0); int minstr = 0; /* determine which stream has the next chunk */ for (int strnum = 0; strnum < m_streams.size(); strnum++) if (curchunk[strnum] < m_streams[strnum].chunks() && m_streams[strnum].chunk(curchunk[strnum]).offset < minoffset) { minoffset = m_streams[strnum].chunk(curchunk[strnum]).offset; minstr = strnum; } /* make an entry for this index */ put_32bits(&tempbuf[curoffset + 0], get_chunkid_for_stream(&m_streams[minstr])); put_32bits(&tempbuf[curoffset + 4], 0x0010 /* AVIIF_KEYFRAME */); put_32bits(&tempbuf[curoffset + 8], minoffset - (m_saved_movi_offset + 8)); put_32bits(&tempbuf[curoffset + 12], m_streams[minstr].chunk(curchunk[minstr]).length - 8); /* advance the chunk counter for this stream */ curchunk[minstr]++; } /* write the chunk */ return chunk_write(CHUNKTYPE_IDX1, &tempbuf[0], tempbuflength); } /*------------------------------------------------- soundbuf_initialize - initialize the sound buffering system -------------------------------------------------*/ /** * @fn static avi_error soundbuf_initialize(avi_file *file) * * @brief Soundbuf initialize. * * @param [in,out] file If non-null, the file. * * @return An avi_error. */ avi_file::error avi_file_impl::soundbuf_initialize() { int offset; avi_stream *const audio = get_audio_stream(0, offset); avi_stream *const video = get_video_stream(); /* we require a video stream */ if (video == nullptr) return error::UNSUPPORTED_VIDEO_FORMAT; /* skip if no audio stream */ if (audio == nullptr) return error::NONE; /* determine the number of samples we want in our buffer; 2 seconds should be enough */ m_soundbuf_samples = m_info.audio_samplerate * SOUND_BUFFER_MSEC / 1000; /* allocate a buffer */ m_soundbuf.clear(); try { m_soundbuf.resize(m_soundbuf_samples * m_info.audio_channels, 0); } catch (...) { return error::NO_MEMORY; } /* determine the number of frames to be ahead (0.75secs) */ m_soundbuf_frames = (std::uint64_t(video->rate()) * 75) / (std::uint64_t(video->scale()) * 100) + 1; return error::NONE; } /*------------------------------------------------- soundbuf_write_chunk - write the initial chunk data -------------------------------------------------*/ /** * @fn static avi_error soundbuf_write_chunk(avi_file *file, std::uint32_t framenum) * * @brief Soundbuf write chunk. * * @param [in,out] file If non-null, the file. * @param framenum The framenum. * * @return An avi_error. */ avi_file::error avi_file_impl::soundbuf_write_chunk(std::uint32_t framenum) { int offset; avi_stream *const stream = get_audio_stream(0, offset); std::uint32_t length; /* skip if no audio stream */ if (stream == nullptr) return error::NONE; /* determine the length of this chunk */ if (framenum == 0) length = framenum_to_samplenum(m_soundbuf_frames); else length = framenum_to_samplenum(framenum + 1 + m_soundbuf_frames) - framenum_to_samplenum(framenum + m_soundbuf_frames); length *= stream->channels() * sizeof(std::int16_t); /* then do the initial write */ error const avierr = chunk_write(get_chunkid_for_stream(stream), &m_soundbuf[0], length); if (avierr != error::NONE) return avierr; /* set the info for this new chunk */ return stream->set_chunk_info(stream->chunks(), m_writeoffs - length - 8, length + 8); } /*------------------------------------------------- soundbuf_flush - flush data from the sound buffers -------------------------------------------------*/ /** * @fn static avi_error soundbuf_flush(avi_file *file, int only_flush_full) * * @brief Soundbuf flush. * * @param [in,out] file If non-null, the file. * @param only_flush_full The only flush full. * * @return An avi_error. */ avi_file::error avi_file_impl::soundbuf_flush(bool only_flush_full) { int offset; avi_stream *const stream = get_audio_stream(0, offset); std::int32_t channelsamples = m_soundbuf_samples; std::int32_t processedsamples = 0; std::uint32_t finalchunks; error avierr; std::uint32_t chunknum; std::uint32_t chunkid; /* skip if no stream */ if (stream == nullptr) return error::NONE; /* get the chunk ID for this stream */ chunkid = get_chunkid_for_stream(stream); std::uint32_t const bytes_per_sample = stream->channels() * sizeof(std::int16_t); finalchunks = stream->chunks(); /* find out how many samples we've accumulated */ for (int channel = 0; channel < stream->channels(); channel++) channelsamples = (std::min)(channelsamples, m_soundbuf_chansamples[channel]); /* loop over pending sound chunks */ for (chunknum = m_soundbuf_chunks; chunknum < stream->chunks(); chunknum++) { avi_chunk_list &chunk = stream->chunk(chunknum); std::uint32_t const chunksamples = (chunk.length - 8) / bytes_per_sample; /* stop if we don't have enough to satisfy this chunk */ if (only_flush_full && channelsamples < chunksamples) break; /* if we don't have all the samples we need, pad with 0's */ if (channelsamples > 0 && channelsamples < chunksamples) { if (processedsamples + chunksamples > m_soundbuf_samples) return error::EXCEEDED_SOUND_BUFFER; std::memset(&m_soundbuf[(processedsamples + channelsamples) * stream->channels()], 0, (chunksamples - channelsamples) * bytes_per_sample); } /* if we're completely out of samples, clear the buffer entirely and use the end */ else if (channelsamples <= 0) { processedsamples = m_soundbuf_samples - chunksamples; std::memset(&m_soundbuf[processedsamples * stream->channels()], 0, chunksamples * bytes_per_sample); chunkid = CHUNKTYPE_JUNK; finalchunks--; } /* copy the sample data in */ avierr = chunk_overwrite(chunkid, &m_soundbuf[processedsamples * stream->channels()], chunk.length - 8, chunk.offset, false); if (avierr != error::NONE) return avierr; /* add up the samples */ if (channelsamples > chunksamples) m_info.audio_numsamples = stream->add_samples(chunksamples); else if (channelsamples > 0) m_info.audio_numsamples = stream->add_samples(channelsamples); /* advance past those */ processedsamples += chunksamples; channelsamples -= chunksamples; channelsamples = (std::max)(0, channelsamples); } /* if we have a non-zero offset, shift everything down */ if (processedsamples > 0) { /* first account for the samples we processed */ if (m_soundbuf_samples > processedsamples) std::memmove(&m_soundbuf[0], &m_soundbuf[processedsamples * stream->channels()], (m_soundbuf_samples - processedsamples) * bytes_per_sample); for (int channel = 0; channel < stream->channels(); channel++) m_soundbuf_chansamples[channel] -= processedsamples; } /* update the final chunk count */ if (!only_flush_full) stream->set_chunks(finalchunks); /* account for flushed chunks */ m_soundbuf_chunks = chunknum; return error::NONE; } /*------------------------------------------------- printf_chunk_recursive - print information about a chunk recursively -------------------------------------------------*/ /** * @fn static void printf_chunk_recursive(avi_file *file, avi_chunk *container, int indent) * * @brief Printf chunk recursive. * * @param [in,out] file If non-null, the file. * @param [in,out] container If non-null, the container. * @param indent The indent. */ void avi_file_impl::printf_chunk_recursive(avi_chunk const *container, int indent) { char size[20], offset[20]; avi_chunk curchunk; error avierr; /* iterate over chunks in this container */ for (avierr = get_first_chunk(container, curchunk); avierr == error::NONE; avierr = get_next_chunk(container, curchunk)) { std::uint32_t chunksize = curchunk.size; bool recurse = false; u64toa(curchunk.size, size); u64toa(curchunk.offset, offset); printf("%*schunk = %c%c%c%c, size=%s (%s)\n", indent, "", std::uint8_t(curchunk.type >> 0), std::uint8_t(curchunk.type >> 8), std::uint8_t(curchunk.type >> 16), std::uint8_t(curchunk.type >> 24), size, offset); /* certain chunks are just containers; recurse into them */ switch (curchunk.type) { /* basic containers */ case CHUNKTYPE_RIFF: case CHUNKTYPE_LIST: printf("%*stype = %c%c%c%c\n", indent, "", std::uint8_t(curchunk.listtype >> 0), std::uint8_t(curchunk.listtype >> 8), std::uint8_t(curchunk.listtype >> 16), std::uint8_t(curchunk.listtype >> 24)); recurse = true; chunksize = 0; break; } /* print data within the chunk */ if (chunksize > 0 && curchunk.size < 1024 * 1024) { std::unique_ptr data; /* read the data for a chunk */ avierr = read_chunk_data(curchunk, data); if (avierr == error::NONE) { std::uint32_t const bytes = (std::min)(std::uint32_t(512), chunksize); for (std::uint32_t i = 0; i < bytes; i++) { if (i % 16 == 0) printf("%*s ", indent, ""); printf("%02X ", data[i]); if (i % 16 == 15) printf("\n"); } if (chunksize % 16 != 0) printf("\n"); } } /* if we're recursing, dive down */ if (recurse) printf_chunk_recursive(&curchunk, indent + 4); } /* if we didn't get a legitimate error, indicate that */ if (avierr != error::END) printf("[chunk error %d]\n", int(avierr)); } } // anonymous namespace /*************************************************************************** IMPLEMENTATION ***************************************************************************/ /*------------------------------------------------- avi_open - open an AVI movie file for read -------------------------------------------------*/ /** * @fn avi_error avi_open(const char *filename, avi_file **file) * * @brief Queries if a given avi open. * * @param filename Filename of the file. * @param [in,out] file If non-null, the file. * * @return An avi_error. */ avi_file::error avi_file::open(std::string const &filename, ptr &file) { /* open the file */ osd_file::ptr f; std::uint64_t length; if (osd_file::open(filename, OPEN_FLAG_READ, f, length) != osd_file::error::NONE) return error::CANT_OPEN_FILE; /* allocate the file */ std::unique_ptr newfile; try { newfile = std::make_unique(std::move(f), length); } catch (...) { return error::NO_MEMORY; } /* parse the data */ error const avierr = newfile->read_movie_data(); if (avierr != error::NONE) return avierr; file = std::move(newfile); return error::NONE; } /*------------------------------------------------- avi_create - create a new AVI movie file -------------------------------------------------*/ /** * @fn avi_error avi_create(const char *filename, const avi_movie_info *info, avi_file **file) * * @brief Avi create. * * @param filename Filename of the file. * @param info The information. * @param [in,out] file If non-null, the file. * * @return An avi_error. */ avi_file::error avi_file::create(std::string const &filename, movie_info const &info, ptr &file) { /* validate video info */ if ((info.video_format != 0 && info.video_format != FORMAT_UYVY && info.video_format != FORMAT_VYUY && info.video_format != FORMAT_YUY2) || (info.video_width == 0) || (info.video_height == 0) || (info.video_depth == 0) || (info.video_depth % 8 != 0)) return error::UNSUPPORTED_VIDEO_FORMAT; /* validate audio info */ if (info.audio_format != 0 || info.audio_channels > MAX_SOUND_CHANNELS || info.audio_samplebits != 16) return error::UNSUPPORTED_AUDIO_FORMAT; /* open the file */ osd_file::ptr f; std::uint64_t length; osd_file::error const filerr = osd_file::open(filename, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS, f, length); if (filerr != osd_file::error::NONE) return error::CANT_OPEN_FILE; /* allocate the file */ error avierr; std::unique_ptr newfile; try { newfile = std::make_unique(std::move(f), info); } catch (...) { avierr = error::NO_MEMORY; goto error; } /* initialize the sound buffering */ avierr = newfile->soundbuf_initialize(); if (avierr != error::NONE) goto error; /* write the initial headers */ avierr = newfile->write_initial_headers(); if (avierr != error::NONE) goto error; file = std::move(newfile); return error::NONE; error: f.reset(); newfile.reset(); osd_file::remove(filename); return avierr; } /*------------------------------------------------- avi_error_string - get the error string for an avi_error -------------------------------------------------*/ /** * @fn const char *avi_error_string(avi_error err) * * @brief Avi error string. * * @param err The error. * * @return null if it fails, else a char*. */ const char *avi_file::error_string(error err) { switch (err) { case error::NONE: return "success"; case error::END: return "hit end of file"; case error::INVALID_DATA: return "invalid data"; case error::NO_MEMORY: return "out of memory"; case error::READ_ERROR: return "read error"; case error::WRITE_ERROR: return "write error"; case error::STACK_TOO_DEEP: return "stack overflow"; case error::UNSUPPORTED_FEATURE: return "unsupported feature"; case error::CANT_OPEN_FILE: return "unable to open file"; case error::INCOMPATIBLE_AUDIO_STREAMS: return "found incompatible audio streams"; case error::INVALID_SAMPLERATE: return "found invalid sample rate"; case error::INVALID_STREAM: return "invalid stream"; case error::INVALID_FRAME: return "invalid frame index"; case error::INVALID_BITMAP: return "invalid bitmap"; case error::UNSUPPORTED_VIDEO_FORMAT: return "unsupported video format"; case error::UNSUPPORTED_AUDIO_FORMAT: return "unsupported audio format"; case error::EXCEEDED_SOUND_BUFFER: return "sound buffer overflow"; default: return "undocumented error"; } } avi_file::avi_file() { } avi_file::~avi_file() { } 8' href='#n3068'>3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621 3622 3623 3624 3625 3626 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 3709 3710 3711 3712 3713 3714 3715 3716 3717 3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 3750 3751 3752 3753 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798 3799 3800 3801 3802 3803 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3833 3834 3835 3836 3837 3838 3839 3840 3841 3842 3843 3844 3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856 3857 3858 3859 3860 3861 3862 3863 3864 3865 3866 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879 3880 3881 3882 3883 3884 3885 3886 3887 3888 3889 3890 3891 3892 3893 3894 3895 3896 3897 3898 3899 3900 3901 3902 3903 3904 3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924 3925 3926 3927 3928 3929 3930 3931 3932 3933 3934 3935 3936 3937 3938 3939 3940 3941 3942 3943 3944 3945 3946 3947 3948 3949 3950 3951 3952 3953 3954 3955 3956 3957 3958 3959 3960 3961 3962 3963 3964 3965 3966 3967 3968 3969 3970 3971 3972 3973 3974 3975 3976 3977 3978 3979 3980 3981 3982 3983 3984 3985 3986 3987 3988 3989 3990 3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002 4003 4004 4005 4006 4007 4008 4009 4010 4011 4012 4013 4014 4015 4016 4017 4018 4019 4020 4021 4022 4023 4024 4025 4026 4027 4028 4029 4030 4031 4032 4033 4034 4035 4036 4037 4038 4039 4040 4041 4042 4043 4044 4045 4046 4047 4048 4049 4050 4051 4052 4053 4054 4055 4056 4057 4058 4059 4060 4061 4062 4063 4064 4065 4066 4067 4068 4069 4070 4071 4072 4073 4074 4075 4076 4077 4078 4079 4080 4081 4082 4083 4084 4085 4086 4087 4088 4089 4090 4091 4092 4093 4094 4095 4096 4097 4098 4099 4100 4101 4102 4103 4104 4105 4106 4107 4108 4109 4110 4111 4112 4113 4114 4115 4116 4117 4118 4119 4120 4121 4122 4123 4124 4125 4126 4127 4128 4129 4130 4131 4132 4133 4134 4135 4136 4137 4138 4139 4140 4141 4142 4143 4144 4145 4146 4147 4148 4149 4150 4151 4152 4153 4154 4155 4156 4157 4158 4159 4160 4161 4162 4163 4164 4165 4166 4167 4168 4169 4170 4171 4172 4173 4174 4175 4176 4177 4178 4179 4180 4181 4182 4183 4184 4185 4186 4187 4188 4189 4190 4191 4192 4193 4194 4195 4196 4197 4198 4199 4200 4201 4202 4203 4204 4205 4206 4207 4208 4209 4210 4211 4212 4213 4214 4215 4216 4217 4218 4219 4220 4221 4222 4223 4224 4225 4226 4227 4228 4229 4230 4231 4232 4233 4234 4235 4236 4237 4238 4239 4240 4241 4242 4243 4244 4245 4246 4247 4248 4249 4250 4251 4252 4253 4254 4255 4256 4257 4258 4259 4260 4261 4262 4263 4264 4265 4266 4267 4268 4269 4270 4271 4272 4273 4274 4275 4276 4277 4278 4279 4280 4281 4282 4283 4284 4285 4286 4287 4288 4289 4290 4291 4292 4293 4294 4295 4296 4297 4298 4299 4300 4301 4302 4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 4319 4320 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347 4348 4349 4350 4351 4352 4353 4354 4355 4356 4357 4358 4359 4360 4361 4362 4363 4364 4365 4366 4367 4368 4369 4370 4371 4372 4373 4374 4375 4376 4377 4378 4379 4380 4381 4382 4383 4384 4385 4386 4387 4388 4389 4390 4391 4392 4393 4394 4395 4396 4397 4398 4399 4400 4401 4402 4403 4404 4405 4406 4407 4408 4409 4410 4411 4412 4413 4414 4415 4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 4432 4433 4434 4435 4436 4437 4438 4439 4440 4441 4442 4443 4444 4445 4446 4447 4448 4449 4450 4451 4452 4453 4454 4455 4456 4457 4458 4459 4460 4461 4462 4463 4464 4465 4466 4467 4468 4469 4470 4471 4472 4473 4474 4475 4476 4477 4478 4479 4480 4481 4482 4483 4484 4485 4486 4487 4488 4489 4490 4491 4492 4493 4494 4495 4496 4497 4498 4499 4500 4501 4502 4503 4504 4505 4506 4507 4508 4509 4510 4511 4512 4513 4514 4515 4516 4517 4518 4519 4520 4521 4522 4523 4524 4525 4526 4527 4528 4529 4530 4531 4532 4533 4534 4535 4536 4537 4538 4539 4540 4541 4542 4543 4544 4545 4546 4547 4548 4549 4550 4551 4552 4553 4554 4555 4556 4557 4558 4559 4560 4561 4562 4563 4564 4565 4566 4567 4568 4569 4570 4571 4572 4573 4574 4575 4576 4577 4578 4579 4580 4581 4582 4583 4584 4585 4586 4587 4588 4589 4590 4591 4592 4593 4594 4595 4596 4597 4598 4599 4600 4601 4602 4603 4604 4605 4606 4607 4608 4609 4610 4611 4612 4613 4614 4615 4616 4617 4618 4619 4620 4621 4622 4623 4624 4625 4626 4627 4628 4629 4630 4631 4632 4633 4634 4635 4636 4637 4638 4639 4640 4641 4642 4643 4644 4645 4646 4647 4648 4649 4650 4651 4652 4653 4654 4655 4656 4657 4658 4659 4660 4661 4662 4663 4664 4665 4666 4667 4668 4669 4670 4671 4672 4673 4674 4675 4676 4677 4678 4679 4680 4681 4682 4683 4684 4685 4686 4687 4688 4689 4690 4691 4692 4693 4694 4695 4696 4697 4698