// license:BSD-3-Clause // copyright-holders:Aaron Giles, Vas Crabb /*************************************************************************** aviio.c AVI movie format parsing helpers. ***************************************************************************/ #include "aviio.h" #include "multibyte.h" #include "strformat.h" #include "osdcomm.h" #include "osdfile.h" #include #include #include #include #include #include #include /*************************************************************************** CONSTANTS ***************************************************************************/ /** @brief Size of the maximum riff. */ static constexpr std::uint64_t MAX_RIFF_SIZE = 2UL * 1024 * 1024 * 1024 - 1024; // just under 2GB /** @brief The maximum avi size in gigabytes. */ static constexpr unsigned MAX_AVI_SIZE_IN_GB = 1024; /** * @def FOUR_GB * * @brief A constant that defines four gigabytes. */ static constexpr std::uint64_t FOUR_GB = std::uint64_t(1) << 32; /** * @def MAX_SOUND_CHANNELS * * @brief A constant that defines maximum sound channels. */ static constexpr unsigned MAX_SOUND_CHANNELS = 2; /** * @def SOUND_BUFFER_MSEC * * @brief A constant that defines sound buffer msec. */ static constexpr int SOUND_BUFFER_MSEC = 2000; // milliseconds of sound buffering /** * @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. */ static constexpr int AVI_INTEGRAL_MULTIPLE = 4; 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: enum : std::uint32_t { STREAMTYPE_VIDS = AVI_FOURCC('v','i','d','s'), STREAMTYPE_AUDS = AVI_FOURCC('a','u','d','s') }; 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: enum : std::uint8_t { HUFFYUV_PREDICT_LEFT = 0, HUFFYUV_PREDICT_GRADIENT = 1, HUFFYUV_PREDICT_MEDIAN = 2, HUFFYUV_PREDICT_DECORR = 0x40 }; 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 { friend class avi_stream; 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 display_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: enum : std::uint32_t { FILETYPE_READ = 1, FILETYPE_CREATE = 2 }; enum : std::uint32_t { CHUNKTYPE_RIFF = AVI_FOURCC('R','I','F','F'), CHUNKTYPE_LIST = AVI_FOURCC('L','I','S','T'), CHUNKTYPE_JUNK = AVI_FOURCC('J','U','N','K'), CHUNKTYPE_AVIH = AVI_FOURCC('a','v','i','h'), CHUNKTYPE_STRH = AVI_FOURCC('s','t','r','h'), CHUNKTYPE_STRF = AVI_FOURCC('s','t','r','f'), CHUNKTYPE_IDX1 = AVI_FOURCC('i','d','x','1'), CHUNKTYPE_INDX = AVI_FOURCC('i','n','d','x'), CHUNKTYPE_XXDB = AVI_FOURCC(0x00,0x00,'d','b'), CHUNKTYPE_XXDC = AVI_FOURCC(0x00,0x00,'d','c'), CHUNKTYPE_XXWB = AVI_FOURCC(0x00,0x00,'w','b'), CHUNKTYPE_IXXX = AVI_FOURCC('i','x',0x00,0x00), CHUNKTYPE_XX_MASK = 0xffff0000 }; enum : std::uint32_t { LISTTYPE_AVI = AVI_FOURCC('A','V','I',' '), LISTTYPE_AVIX = AVI_FOURCC('A','V','I','X'), LISTTYPE_HDRL = AVI_FOURCC('h','d','r','l'), LISTTYPE_STRL = AVI_FOURCC('s','t','r','l'), LISTTYPE_MOVI = AVI_FOURCC('m','o','v','i') }; enum : std::uint32_t { HANDLER_DIB = AVI_FOURCC('D','I','B',' '), HANDLER_HFYU = AVI_FOURCC('h','f','y','u') }; enum : std::uint32_t { AVIF_HASINDEX = 0x00000010, AVIF_MUSTUSEINDEX = 0x00000020, AVIF_ISINTERLEAVED = 0x00000100, AVIF_COPYRIGHTED = 0x00010000, AVIF_WASCAPTUREFILE = 0x00020000 }; enum : std::uint8_t { AVI_INDEX_OF_INDEXES = 0x00, AVI_INDEX_OF_CHUNKS = 0x01, AVI_INDEX_IS_DATA = 0x80, AVI_INDEX_2FIELD = 0x01 }; 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 display_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 ***************************************************************************/ /*------------------------------------------------- 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 = get_u32le(&data[0]); m_scale = get_u32le(&data[20]); m_rate = get_u32le(&data[24]); m_samples = get_u32le(&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 = get_u32le(&data[4]); m_height = get_u32le(&data[8]); m_depth = get_u16le(&data[14]); m_format = get_u32le(&data[16]); // extra extraction for HuffYUV data if ((m_format == avi_file::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 = get_u16le(&data[2]); m_samplebits = get_u16le(&data[14]); m_samplerate = get_u32le(&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() == avi_stream::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() == avi_stream::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() == avi_stream::STREAMTYPE_VIDS) chunkid |= (stream->format() == 0) ? CHUNKTYPE_XXDB : CHUNKTYPE_XXDC; else if (stream->type() == avi_stream::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); // compressed video for (int 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 avi_file::FORMAT_UYVY: for (int x = 0; x < m_width && source < dataend; x++) *dest++ = *source++; break; case avi_file::FORMAT_VYUY: case avi_file::FORMAT_YUY2: for (int x = 0; x < m_width && source < dataend; x++) *dest++ = swapendian_int16(*source++); 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); // compressed video for (int 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 avi_file::FORMAT_UYVY: for (int x = 0; x < m_width && dest < dataend; x++) *dest++ = *source++; break; case avi_file::FORMAT_VYUY: case avi_file::FORMAT_YUY2: for (int x = 0; x < m_width && dest < dataend; x++) *dest++ = swapendian_int16(*source++); 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; // compressed video for (int y = 0; y < m_height; y++) { std::uint16_t *dest = &bitmap.pix(y); // handle the first four bytes independently int 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 (int 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 int 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::swap(a, b); if (a > c) std::swap(a, c); if (b > c) std::swap(b, c); 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::swap(a, b); if (a > c) std::swap(a, c); if (b > c) std::swap(b, c); 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::swap(a, b); if (a > c) std::swap(a, c); if (b > c) std::swap(b, c); 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::swap(a, b); if (a > c) std::swap(a, c); if (b > c) std::swap(b, c); 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 = std::clamp(r, 0, 255); g = std::clamp(g, 0, 255); b = std::clamp(b, 0, 255); *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; } /*------------------------------------------------- display_chunks - print the chunks in a file -------------------------------------------------*/ /** * @fn void display_chunks(avi_file *file) * * @brief Avi printf chunks. * * @param [in,out] file If non-null, the file. */ void avi_file_impl::display_chunks() { display_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; std::error_condition const filerr = m_file->read(&m_tempbuffer[0], stream->chunk(framenum).offset, stream->chunk(framenum).length, bytes_read); if (filerr || bytes_read != stream->chunk(framenum).length) return error::READ_ERROR; // validate this is good data std::uint32_t const chunkid = get_u32le(&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; std::error_condition const filerr = m_file->read(&m_tempbuffer[0], stream->chunk(framenum).offset, stream->chunk(framenum).length, bytes_read); if (filerr || bytes_read != stream->chunk(framenum).length) return error::READ_ERROR; // validate this is good data std::uint32_t const chunkid = get_u32le(&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 std::error_condition const filerr = m_file->read(&m_tempbuffer[0], stream->chunk(chunknum).offset, stream->chunk(chunknum).length, bytes_read); if (filerr || bytes_read != stream->chunk(chunknum).length) return error::READ_ERROR; // validate this is good data chunkid = get_u32le(&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 buffer.reset(new (std::nothrow) std::uint8_t[chunk.size]); if (!buffer) return error::NO_MEMORY; // read from the file std::uint32_t bytes_read; std::error_condition const filerr = m_file->read(&buffer[0], chunk.offset + 8, chunk.size, bytes_read); if (filerr || 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) { // nullptr parent implies the root if (!parent) 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 std::uint8_t buffer[12]; std::uint32_t bytesread; std::error_condition filerr = m_file->read(buffer, newchunk.offset, 8, bytesread); if (filerr || bytesread != 8) return error::INVALID_DATA; // fill in the new chunk newchunk.type = get_u32le(&buffer[0]); newchunk.size = get_u32le(&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 || bytesread != 4) return error::INVALID_DATA; newchunk.listtype = get_u32le(&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 = get_u32le(&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 = get_u16le(&chunkdata[0]); //subtype = chunkdata[2]; std::uint8_t const type = chunkdata[3]; std::uint32_t const entries = get_u32le(&chunkdata[4]); //id = get_u32le(&chunkdata[8]); std::uint64_t const baseoffset = get_u64le(&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++) { std::uint8_t const *const base = &chunkdata[24 + entry * 16]; avi_chunk subchunk; // go read the subchunk subchunk.offset = get_u64le(&base[0]); std::uint32_t bytes_read; std::uint8_t buffer[8]; std::error_condition const filerr = m_file->read(buffer, subchunk.offset, sizeof(buffer), bytes_read); if (filerr || bytes_read != sizeof(buffer)) { avierr = error::READ_ERROR; break; } // parse the data subchunk.type = get_u32le(&buffer[0]); subchunk.size = get_u32le(&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 = get_u32le(&base[0]); std::uint32_t size = get_u32le(&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 = get_u32le(&base[0]); std::uint32_t const offset = get_u32le(&base[8]); std::uint32_t const size = get_u32le(&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_u32le(&buffer[0], chunk.type); put_u32le(&buffer[4], chunk.size); // write the header std::uint32_t written; std::error_condition const filerr = m_file->write(buffer, m_writeoffs, sizeof(buffer), written); if (filerr || written != sizeof(buffer)) return error::WRITE_ERROR; m_writeoffs += written; } // list types else { std::uint8_t buffer[12]; // populate the header put_u32le(&buffer[0], chunk.type); put_u32le(&buffer[4], chunk.size); put_u32le(&buffer[8], chunk.listtype); // write the header std::uint32_t written; std::error_condition const filerr = m_file->write(buffer, m_writeoffs, sizeof(buffer), written); if (filerr || 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_u32le(&buffer[0], std::uint32_t(chunksize)); std::uint32_t written; std::error_condition const filerr = m_file->write(buffer, chunk.offset + 4, sizeof(buffer), written); if (filerr || 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; std::error_condition const filerr = m_file->write(data, m_writeoffs, length, written); if (filerr || 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_u32le(&buffer[0], 1000000 * std::int64_t(video->scale()) / video->rate()); // dwMicroSecPerFrame put_u32le(&buffer[12], AVIF_HASINDEX | AVIF_ISINTERLEAVED); // dwFlags put_u32le(&buffer[16], video->samples()); // dwTotalFrames put_u32le(&buffer[24], m_streams.size()); // dwStreams put_u32le(&buffer[32], video->width()); // dwWidth put_u32le(&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_u32le(&buffer[0], stream.type()); // fccType put_u32le(&buffer[20], stream.scale()); // dwScale put_u32le(&buffer[24], stream.rate()); // dwRate put_u32le(&buffer[32], stream.samples()); // dwLength put_u32le(&buffer[40], 10000); // dwQuality // video-stream specific data if (stream.type() == avi_stream::STREAMTYPE_VIDS) { put_u32le(&buffer[4], // fccHandler (stream.format() == FORMAT_HFYU) ? HANDLER_HFYU : HANDLER_DIB); put_u32le(&buffer[36], // dwSuggestedBufferSize stream.width() * stream.height() * 4); put_u16le(&buffer[52], stream.width()); // rcFrame.right put_u16le(&buffer[54], stream.height()); // rcFrame.bottom } // audio-stream specific data if (stream.type() == avi_stream::STREAMTYPE_AUDS) { put_u32le(&buffer[36], // dwSuggestedBufferSize stream.samplerate() * stream.bytes_per_sample()); put_u32le(&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() == avi_stream::STREAMTYPE_VIDS) { std::uint8_t buffer[40]; // reset the buffer std::memset(buffer, 0, sizeof(buffer)); put_u32le(&buffer[0], sizeof(buffer)); // biSize put_u32le(&buffer[4], stream.width()); // biWidth put_u32le(&buffer[8], stream.height()); // biHeight put_u16le(&buffer[12], 1); // biPlanes put_u16le(&buffer[14], stream.depth()); // biBitCount put_u32le(&buffer[16], stream.format()); // biCompression put_u32le(&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() == avi_stream::STREAMTYPE_AUDS) { std::uint8_t buffer[16]; // reset the buffer std::memset(buffer, 0, sizeof(buffer)); put_u16le(&buffer[0], 1); // wFormatTag put_u16le(&buffer[2], stream.channels()); // nChannels put_u32le(&buffer[4], stream.samplerate()); // nSamplesPerSec put_u32le(&buffer[8], // nAvgBytesPerSec stream.samplerate() * stream.bytes_per_sample()); put_u16le(&buffer[12], // nBlockAlign stream.bytes_per_sample()); put_u16le(&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_u16le(&tempbuf[0], 2); // wLongsPerEntry tempbuf[2] = 0; // bIndexSubType tempbuf[3] = AVI_INDEX_OF_CHUNKS; // bIndexType put_u32le(&tempbuf[4], chunks_this_index); // nEntriesInUse put_u32le(&tempbuf[8], chunkid); // dwChunkId put_u64le(&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_u32le(&tempbuf[24 + 8 * chunks_this_index + 0], stream.chunk(chunknum).offset + 8 - currentbase); put_u32le(&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_u64le(&buffer[24 + 16 * master_entries + 0], m_writeoffs); put_u32le(&buffer[24 + 16 * master_entries + 8], 24 + 8 * chunks_this_index + 8); if (stream.type() == avi_stream::STREAMTYPE_VIDS) put_u32le(&buffer[24 + 16 * master_entries + 12], chunks_this_index); else if (stream.type() == avi_stream::STREAMTYPE_AUDS) put_u32le(&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_u16le(&buffer[0], 4); // wLongsPerEntry buffer[2] = 0; // bIndexSubType buffer[3] = AVI_INDEX_OF_INDEXES; // bIndexType put_u32le(&buffer[4], master_entries); // nEntriesInUse put_u32le(&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_u32le(&tempbuf[curoffset + 0], get_chunkid_for_stream(&m_streams[minstr])); put_u32le(&tempbuf[curoffset + 4], 0x0010 /* AVIIF_KEYFRAME */); put_u32le(&tempbuf[curoffset + 8], minoffset - (m_saved_movi_offset + 8)); put_u32le(&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; } /*------------------------------------------------- display_chunk_recursive - print information about a chunk recursively -------------------------------------------------*/ /** * @fn static void display_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::display_chunk_recursive(avi_chunk const *container, int indent) { 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; util::stream_format(std::cout, "%*schunk = %c%c%c%c, size=%X (%X)\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), curchunk.size, curchunk.offset); // certain chunks are just containers; recurse into them switch (curchunk.type) { // basic containers case CHUNKTYPE_RIFF: case CHUNKTYPE_LIST: util::stream_format(std::cout, "%*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) { auto bytes = std::min(512, chunksize); for (std::uint32_t i = 0; i < bytes; i++) { if (i % 16 == 0) util::stream_format(std::cout, "%*s ", indent, ""); util::stream_format(std::cout, "%02X ", data[i]); if (i % 16 == 15) std::cout << "\n"; } if (chunksize % 16 != 0) std::cout << "\n"; } } // if we're recursing, dive down if (recurse) display_chunk_recursive(&curchunk, indent + 4); } // if we didn't get a legitimate error, indicate that if (avierr != error::END) std::cout << "[chunk error " << int(avierr) << "]\n"; } } // 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)) 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; std::error_condition const filerr = osd_file::open(filename, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS, f, length); if (filerr) 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() { }