1 // license:BSD-3-Clause
2 // copyright-holders:Aaron Giles, Vas Crabb
3 /***************************************************************************
4
5 aviio.c
6
7 AVI movie format parsing helpers.
8
9 ***************************************************************************/
10
11 #include <array>
12 #include <cassert>
13 #include <cstdlib>
14 #include <cstring>
15
16 #include "aviio.h"
17
18
19 /***************************************************************************
20 CONSTANTS
21 ***************************************************************************/
22
23 /**
24 * @def FILETYPE_READ
25 *
26 * @brief A macro that defines filetype read.
27 */
28
29 #define FILETYPE_READ 1
30
31 /**
32 * @def FILETYPE_CREATE
33 *
34 * @brief A macro that defines filetype create.
35 */
36
37 #define FILETYPE_CREATE 2
38
39 /** @brief Size of the maximum riff. */
40 #define MAX_RIFF_SIZE (2UL * 1024 * 1024 * 1024 - 1024) /* just under 2GB */
41 /** @brief The maximum avi size in gigabytes. */
42 #define MAX_AVI_SIZE_IN_GB (1024)
43
44 /**
45 * @def FOUR_GB
46 *
47 * @brief A macro that defines four gigabytes.
48 */
49
50 #define FOUR_GB (std::uint64_t(1) << 32)
51
52 /**
53 * @def MAX_SOUND_CHANNELS
54 *
55 * @brief A macro that defines maximum sound channels.
56 */
57
58 #define MAX_SOUND_CHANNELS 2
59
60 /**
61 * @def SOUND_BUFFER_MSEC
62 *
63 * @brief A macro that defines sound buffer msec.
64 */
65
66 #define SOUND_BUFFER_MSEC 2000 /* milliseconds of sound buffering */
67
68 /** @brief The chunktype riff. */
69 #define CHUNKTYPE_RIFF AVI_FOURCC('R','I','F','F')
70 /** @brief List of chunktypes. */
71 #define CHUNKTYPE_LIST AVI_FOURCC('L','I','S','T')
72 /** @brief The chunktype junk. */
73 #define CHUNKTYPE_JUNK AVI_FOURCC('J','U','N','K')
74 /** @brief The chunktype avih. */
75 #define CHUNKTYPE_AVIH AVI_FOURCC('a','v','i','h')
76 /** @brief The chunktype strh. */
77 #define CHUNKTYPE_STRH AVI_FOURCC('s','t','r','h')
78 /** @brief The chunktype strf. */
79 #define CHUNKTYPE_STRF AVI_FOURCC('s','t','r','f')
80 /** @brief The first chunktype index. */
81 #define CHUNKTYPE_IDX1 AVI_FOURCC('i','d','x','1')
82 /** @brief The chunktype indx. */
83 #define CHUNKTYPE_INDX AVI_FOURCC('i','n','d','x')
84 /** @brief The chunktype xxdb. */
85 #define CHUNKTYPE_XXDB AVI_FOURCC(0x00,0x00,'d','b')
86 /** @brief The chunktype xxdc. */
87 #define CHUNKTYPE_XXDC AVI_FOURCC(0x00,0x00,'d','c')
88 /** @brief The chunktype xxwb. */
89 #define CHUNKTYPE_XXWB AVI_FOURCC(0x00,0x00,'w','b')
90 /** @brief The chunktype ixxx. */
91 #define CHUNKTYPE_IXXX AVI_FOURCC('i','x',0x00,0x00)
92 /** @brief The chunktype xx mask. */
93 #define CHUNKTYPE_XX_MASK AVI_FOURCC(0x00,0x00,0xff,0xff)
94
95 /** @brief The listtype avi. */
96 #define LISTTYPE_AVI AVI_FOURCC('A','V','I',' ')
97 /** @brief The listtype avix. */
98 #define LISTTYPE_AVIX AVI_FOURCC('A','V','I','X')
99 /** @brief The listtype hdrl. */
100 #define LISTTYPE_HDRL AVI_FOURCC('h','d','r','l')
101 /** @brief The listtype strl. */
102 #define LISTTYPE_STRL AVI_FOURCC('s','t','r','l')
103 /** @brief The listtype movi. */
104 #define LISTTYPE_MOVI AVI_FOURCC('m','o','v','i')
105
106 /** @brief The streamtype vids. */
107 #define STREAMTYPE_VIDS AVI_FOURCC('v','i','d','s')
108 /** @brief The streamtype auds. */
109 #define STREAMTYPE_AUDS AVI_FOURCC('a','u','d','s')
110
111 /** @brief The handler bitmap. */
112 #define HANDLER_DIB AVI_FOURCC('D','I','B',' ')
113 /** @brief The handler hfyu. */
114 #define HANDLER_HFYU AVI_FOURCC('h','f','y','u')
115
116 /* main AVI header files */
117
118 /**
119 * @def AVIF_HASINDEX
120 *
121 * @brief A macro that defines avif hasindex.
122 */
123
124 #define AVIF_HASINDEX 0x00000010
125
126 /**
127 * @def AVIF_MUSTUSEINDEX
128 *
129 * @brief A macro that defines avif mustuseindex.
130 */
131
132 #define AVIF_MUSTUSEINDEX 0x00000020
133
134 /**
135 * @def AVIF_ISINTERLEAVED
136 *
137 * @brief A macro that defines avif isinterleaved.
138 */
139
140 #define AVIF_ISINTERLEAVED 0x00000100
141
142 /**
143 * @def AVIF_COPYRIGHTED
144 *
145 * @brief A macro that defines avif copyrighted.
146 */
147
148 #define AVIF_COPYRIGHTED 0x00010000
149
150 /**
151 * @def AVIF_WASCAPTUREFILE
152 *
153 * @brief A macro that defines avif wascapturefile.
154 */
155
156 #define AVIF_WASCAPTUREFILE 0x00020000
157
158 /* index definitions */
159
160 /**
161 * @def AVI_INDEX_OF_INDEXES
162 *
163 * @brief A macro that defines avi index of indexes.
164 */
165
166 #define AVI_INDEX_OF_INDEXES 0x00
167
168 /**
169 * @def AVI_INDEX_OF_CHUNKS
170 *
171 * @brief A macro that defines avi index of chunks.
172 */
173
174 #define AVI_INDEX_OF_CHUNKS 0x01
175
176 /**
177 * @def AVI_INDEX_IS_DATA
178 *
179 * @brief A macro that defines avi index is data.
180 */
181
182 #define AVI_INDEX_IS_DATA 0x80
183
184 /**
185 * @def AVI_INDEX_2FIELD
186 *
187 * @brief A macro that defines avi index 2 field.
188 */
189
190 #define AVI_INDEX_2FIELD 0x01
191
192 /**
193 * @def AVI_INTEGRAL_MULTIPLE
194 *
195 * @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.
196 */
197
198 #define AVI_INTEGRAL_MULTIPLE 4
199
200 /* HuffYUV definitions */
201
202 /**
203 * @def HUFFYUV_PREDICT_LEFT
204 *
205 * @brief A macro that defines huffyuv predict left.
206 */
207
208 #define HUFFYUV_PREDICT_LEFT 0
209
210 /**
211 * @def HUFFYUV_PREDICT_GRADIENT
212 *
213 * @brief A macro that defines huffyuv predict gradient.
214 */
215
216 #define HUFFYUV_PREDICT_GRADIENT 1
217
218 /**
219 * @def HUFFYUV_PREDICT_MEDIAN
220 *
221 * @brief A macro that defines huffyuv predict median.
222 */
223
224 #define HUFFYUV_PREDICT_MEDIAN 2
225
226 /**
227 * @def HUFFYUV_PREDICT_DECORR
228 *
229 * @brief A macro that defines huffyuv predict decorr.
230 */
231
232 #define HUFFYUV_PREDICT_DECORR 0x40
233
234
235 namespace {
236 /***************************************************************************
237 TYPE DEFINITIONS
238 ***************************************************************************/
239
240 /**
241 * @struct avi_chunk
242 *
243 * @brief An avi chunk.
244 */
245
246 struct avi_chunk
247 {
avi_chunk__anon6e7e86150111::avi_chunk248 avi_chunk() : offset(0), size(0), type(0), listtype(0) { }
249
250 std::uint64_t offset; /* file offset of chunk header */
251 std::uint64_t size; /* size of this chunk */
252 std::uint32_t type; /* type of this chunk */
253 std::uint32_t listtype; /* type of this list (if we are a list) */
254 };
255
256 /**
257 * @struct avi_chunk_list
258 *
259 * @brief List of avi chunks.
260 */
261
262 struct avi_chunk_list
263 {
264 std::uint64_t offset; /* offset in the file of header */
265 std::uint32_t length; /* length of the chunk including header */
266 };
267
268 /**
269 * @struct avi_stream
270 *
271 * @brief An avi stream.
272 */
273
274 class avi_stream
275 {
276 public:
avi_stream()277 avi_stream()
278 : m_type(0)
279 , m_format(0)
280 , m_rate(0)
281 , m_scale(0)
282 , m_samples(0)
283 , m_chunks()
284 , m_width(0)
285 , m_height(0)
286 , m_depth(0)
287 , m_huffyuv()
288 , m_channels(0)
289 , m_samplebits(0)
290 , m_samplerate(0)
291 , m_saved_strh_offset(0)
292 , m_saved_indx_offset(0)
293 {
294 }
295
initialize_video(avi_file::movie_info const & info)296 void initialize_video(avi_file::movie_info const &info)
297 {
298 std::uint32_t width = info.video_width;
299 std::uint32_t height = info.video_height;
300
301 auto integal_multiple = std::uint32_t(AVI_INTEGRAL_MULTIPLE);
302 if (integal_multiple > 1)
303 {
304 width = width - (width % integal_multiple);
305 height = height - (height % integal_multiple);
306 }
307
308 m_type = STREAMTYPE_VIDS;
309 m_format = info.video_format;
310 m_rate = info.video_timescale;
311 m_scale = info.video_sampletime;
312 m_width = width;
313 m_height = height;
314 m_depth = info.video_depth;
315 }
316
initialize_audio(avi_file::movie_info const & info)317 void initialize_audio(avi_file::movie_info const &info)
318 {
319 m_type = STREAMTYPE_AUDS;
320 m_format = info.audio_format;
321 m_rate = info.audio_timescale;
322 m_scale = info.audio_sampletime;
323 m_channels = info.audio_channels;
324 m_samplebits = info.audio_samplebits;
325 m_samplerate = info.audio_samplerate;
326 }
327
type() const328 std::uint32_t type() const { return m_type; }
format() const329 std::uint32_t format() const { return m_format; }
330
rate() const331 std::uint32_t rate() const { return m_rate; }
scale() const332 std::uint32_t scale() const { return m_scale; }
samples() const333 std::uint32_t samples() const { return m_samples; }
set_samples(std::uint32_t value)334 void set_samples(std::uint32_t value) { m_samples = value; }
add_samples(std::uint32_t value)335 std::uint32_t add_samples(std::uint32_t value) { return m_samples += value; }
336
chunks() const337 std::uint32_t chunks() const { return m_chunks.size(); }
chunk(std::uint32_t index) const338 avi_chunk_list const &chunk(std::uint32_t index) const { assert(index < m_chunks.size()); return m_chunks[index]; }
chunk(std::uint32_t index)339 avi_chunk_list &chunk(std::uint32_t index) { assert(index < m_chunks.size()); return m_chunks[index]; }
340 avi_file::error set_chunk_info(std::uint32_t index, std::uint64_t offset, std::uint32_t length);
set_chunks(std::uint32_t count)341 void set_chunks(std::uint32_t count) { assert(count <= m_chunks.size()); m_chunks.resize(count); }
342
width() const343 std::uint32_t width() const { return m_width; }
height() const344 std::uint32_t height() const { return m_height; }
depth() const345 std::uint32_t depth() const { return m_depth; }
346
channels() const347 std::uint16_t channels() const { return m_channels; }
samplebits() const348 std::uint16_t samplebits() const { return m_samplebits; }
samplerate() const349 std::uint32_t samplerate() const { return m_samplerate; }
bytes_per_sample() const350 std::uint32_t bytes_per_sample() const { return (m_samplebits / 8) * m_channels; }
351
352 avi_file::error set_strh_data(std::uint8_t const *data, std::uint32_t size);
353 avi_file::error set_strf_data(std::uint8_t const *data, std::uint32_t size);
354
saved_strh_offset()355 std::uint64_t &saved_strh_offset() { return m_saved_strh_offset; }
saved_indx_offset()356 std::uint64_t &saved_indx_offset() { return m_saved_indx_offset; }
357
358 // RGB helpers
359 avi_file::error rgb32_compress_to_rgb(const bitmap_rgb32 &bitmap, std::uint8_t *data, std::uint32_t numbytes) const;
360
361 // YUY helpers
362 avi_file::error yuv_decompress_to_yuy16(const std::uint8_t *data, std::uint32_t numbytes, bitmap_yuy16 &bitmap) const;
363 avi_file::error yuy16_compress_to_yuy(const bitmap_yuy16 &bitmap, std::uint8_t *data, std::uint32_t numbytes) const;
364
365 // HuffYUV helpers
366 avi_file::error huffyuv_decompress_to_yuy16(const std::uint8_t *data, std::uint32_t numbytes, bitmap_yuy16 &bitmap) const;
367
368 // Uncompressed helpers
369 avi_file::error uncompressed_rgb24_to_argb32(const std::uint8_t *data, std::uint32_t numbytes, bitmap_argb32 &bitmap) const;
370 avi_file::error uncompressed_yuv420p_to_argb32(const std::uint8_t *data, std::uint32_t numbytes, bitmap_argb32 &bitmap) const;
371
372 private:
373 struct huffyuv_table
374 {
375 std::uint8_t shift[256]; /* bit shift amounts */
376 std::uint32_t bits[256]; /* bit match values */
377 std::uint32_t mask[256]; /* bit mask values */
378 std::uint16_t baselookup[65536]; /* base lookup table */
379 std::vector<std::uint16_t> extralookup; /* extra lookup tables */
380 };
381
382 struct huffyuv_data
383 {
384 std::uint8_t predictor; /* predictor */
385 huffyuv_table table[3]; /* array of tables */
386 };
387
388 avi_file::error huffyuv_extract_tables(const std::uint8_t *chunkdata, std::uint32_t size);
389
390 std::uint32_t m_type; /* subtype of stream */
391 std::uint32_t m_format; /* format of stream data */
392
393 std::uint32_t m_rate; /* timescale for stream */
394 std::uint32_t m_scale; /* duration of one sample in the stream */
395 std::uint32_t m_samples; /* number of samples */
396
397 std::vector<avi_chunk_list> m_chunks; /* list of chunks */
398
399 std::uint32_t m_width; /* width of video */
400 std::uint32_t m_height; /* height of video */
401 std::uint32_t m_depth; /* depth of video */
402 std::unique_ptr<huffyuv_data const> m_huffyuv; /* huffyuv decompression data */
403
404 std::uint16_t m_channels; /* audio channels */
405 std::uint16_t m_samplebits; /* audio bits per sample */
406 std::uint32_t m_samplerate; /* audio sample rate */
407
408 /* only used when creating */
409 std::uint64_t m_saved_strh_offset; /* writeoffset of strh chunk */
410 std::uint64_t m_saved_indx_offset; /* writeoffset of indx chunk */
411 };
412
413 /**
414 * @class avi_file_impl
415 *
416 * @brief An avi file.
417 */
418
419 class avi_file_impl : public avi_file
420 {
421 public:
avi_file_impl(osd_file::ptr && file,std::uint64_t length)422 avi_file_impl(osd_file::ptr &&file, std::uint64_t length) : avi_file_impl()
423 {
424 m_file = std::move(file);
425 m_type = FILETYPE_READ;
426
427 // make a root atom
428 m_rootchunk.offset = 0;
429 m_rootchunk.size = length;
430 m_rootchunk.type = 0;
431 m_rootchunk.listtype = 0;
432 }
433
avi_file_impl(osd_file::ptr && file,movie_info const & info)434 avi_file_impl(osd_file::ptr &&file, movie_info const &info) : avi_file_impl()
435 {
436 m_file = std::move(file);
437 m_type = FILETYPE_CREATE;
438
439 // copy the movie info
440 m_info = info;
441 m_info.video_numsamples = 0;
442 m_info.audio_numsamples = 0;
443
444 // initialize the video track
445 m_streams.emplace_back();
446 m_streams.back().initialize_video(m_info);
447
448 // initialize the audio track
449 if (m_info.audio_channels > 0)
450 {
451 m_streams.emplace_back();
452 m_streams.back().initialize_audio(m_info);
453 }
454 }
455
456 virtual ~avi_file_impl() override;
457
458 virtual void printf_chunks() override;
459
460 virtual movie_info const &get_movie_info() const override;
461 virtual std::uint32_t first_sample_in_frame(std::uint32_t framenum) const override;
462
463 virtual error read_uncompressed_video_frame(std::uint32_t framenum, bitmap_argb32 &bitmap) override;
464 virtual error read_video_frame(std::uint32_t framenum, bitmap_yuy16 &bitmap) override;
465 virtual error read_sound_samples(int channel, std::uint32_t firstsample, std::uint32_t numsamples, std::int16_t *output) override;
466
467 virtual error append_video_frame(bitmap_yuy16 &bitmap) override;
468 virtual error append_video_frame(bitmap_rgb32 &bitmap) override;
469 virtual error append_sound_samples(int channel, std::int16_t const *samples, std::uint32_t numsamples, std::uint32_t sampleskip) override;
470
471 error read_movie_data();
472 error write_initial_headers();
473 error soundbuf_initialize();
474
475 private:
avi_file_impl()476 avi_file_impl()
477 : m_file()
478 , m_type(0)
479 , m_info({ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 })
480 , m_tempbuffer()
481 , m_streams()
482 , m_rootchunk()
483 , m_writeoffs(0)
484 , m_riffbase(0)
485 , m_chunkstack()
486 , m_chunksp(0)
487 , m_saved_movi_offset(0)
488 , m_saved_avih_offset(0)
489 , m_soundbuf()
490 , m_soundbuf_samples(0)
491 , m_soundbuf_chunks(0)
492 , m_soundbuf_frames(0)
493 {
494 std::fill(std::begin(m_soundbuf_chansamples), std::end(m_soundbuf_chansamples), 0);
495 }
496
497 avi_stream *get_video_stream();
498 avi_stream *get_audio_stream(int channel, int &offset);
499 std::uint32_t compute_idx1_size() const;
500 std::uint32_t get_chunkid_for_stream(const avi_stream *stream) const;
501 std::uint32_t framenum_to_samplenum(std::uint32_t framenum) const;
502 error expand_tempbuffer(std::uint32_t length);
503
504 // core chunk read routines
505 error get_first_chunk(avi_chunk const *parent, avi_chunk &newchunk);
506 error get_next_chunk(avi_chunk const *parent, avi_chunk &newchunk);
507 error find_first_chunk(std::uint32_t findme, avi_chunk const *container, avi_chunk &result);
508 error find_next_chunk(std::uint32_t findme, avi_chunk const *container, avi_chunk &result);
509 error find_first_list(std::uint32_t findme, avi_chunk const *container, avi_chunk &result);
510 error find_next_list(std::uint32_t findme, avi_chunk const *container, avi_chunk &result);
511 error get_next_chunk_internal(avi_chunk const *parent, avi_chunk &newchunk, std::uint64_t offset);
512 error read_chunk_data(avi_chunk const &chunk, std::unique_ptr<std::uint8_t []> &buffer);
513
514 // chunk read helpers
515 error extract_movie_info();
516 error parse_avih_chunk(avi_chunk const &avih);
517 error parse_strh_chunk(avi_stream &stream, avi_chunk const &strh);
518 error parse_strf_chunk(avi_stream &stream, avi_chunk const &strf);
519 error parse_indx_chunk(avi_stream &stream, avi_chunk const &strf);
520 error parse_idx1_chunk(std::uint64_t baseoffset, avi_chunk const &idx1);
521
522 // core chunk write routines
523 error chunk_open(std::uint32_t type, std::uint32_t listtype, std::uint32_t estlength);
524 error chunk_close();
525 error chunk_write(std::uint32_t type, const void *data, std::uint32_t length);
526 error chunk_overwrite(std::uint32_t type, const void *data, std::uint32_t length, std::uint64_t &offset, bool initial_write);
527
528 // chunk write helpers
529 error write_avih_chunk(bool initial_write);
530 error write_strh_chunk(avi_stream &stream, bool initial_write);
531 error write_strf_chunk(avi_stream const &stream);
532 error write_indx_chunk(avi_stream &stream, bool initial_write);
533 error write_idx1_chunk();
534
535 // sound buffering helpers
536 error soundbuf_write_chunk(std::uint32_t framenum);
537 error soundbuf_flush(bool only_flush_full);
538
539 // debugging
540 void printf_chunk_recursive(avi_chunk const *chunk, int indent);
541
542 /* shared data */
543 osd_file::ptr m_file; /* pointer to open file */
544 int m_type; /* type of access (read/create) */
545 movie_info m_info; /* movie info structure */
546 std::vector<std::uint8_t> m_tempbuffer; /* temporary buffer */
547
548 /* only used when reading */
549 std::vector<avi_stream> m_streams; /* allocated array of stream information */
550 avi_chunk m_rootchunk; /* dummy root chunk that wraps the whole file */
551
552 /* only used when creating */
553 std::uint64_t m_writeoffs; /* current file write offset */
554 std::uint64_t m_riffbase; /* base of the current RIFF */
555
556 std::array<avi_chunk, 8> m_chunkstack; /* stack of chunks we are writing */
557 int m_chunksp; /* stack pointer for the current chunk */
558
559 std::uint64_t m_saved_movi_offset; /* writeoffset of movi list */
560 std::uint64_t m_saved_avih_offset; /* writeoffset of avih chunk */
561
562 std::vector<std::int16_t> m_soundbuf; /* buffer for sound data */
563 std::uint32_t m_soundbuf_samples; /* length of sound buffer in samples */
564 std::uint32_t m_soundbuf_chansamples[MAX_SOUND_CHANNELS]; /* samples in buffer for each channel */
565 std::uint32_t m_soundbuf_chunks; /* number of chunks completed so far */
566 std::uint32_t m_soundbuf_frames; /* number of frames ahead of the video */
567 };
568
569
570
571 /***************************************************************************
572 INLINE FUNCTIONS
573 ***************************************************************************/
574
575 /*-------------------------------------------------
576 fetch_16bits - read 16 bits in LSB order
577 from the given buffer
578 -------------------------------------------------*/
579
580 /**
581 * @fn static inline std::uint16_t fetch_16bits(const std::uint8_t *data)
582 *
583 * @brief Fetches the 16bits.
584 *
585 * @param data The data.
586 *
587 * @return The 16bits.
588 */
589
fetch_16bits(const std::uint8_t * data)590 inline std::uint16_t fetch_16bits(const std::uint8_t *data)
591 {
592 return data[0] | (data[1] << 8);
593 }
594
595
596 /*-------------------------------------------------
597 fetch_32bits - read 32 bits in LSB order
598 from the given buffer
599 -------------------------------------------------*/
600
601 /**
602 * @fn static inline std::uint32_t fetch_32bits(const std::uint8_t *data)
603 *
604 * @brief Fetches the 32bits.
605 *
606 * @param data The data.
607 *
608 * @return The 32bits.
609 */
610
fetch_32bits(const std::uint8_t * data)611 inline std::uint32_t fetch_32bits(const std::uint8_t *data)
612 {
613 return data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
614 }
615
616
617 /*-------------------------------------------------
618 fetch_64bits - read 64 bits in LSB order
619 from the given buffer
620 -------------------------------------------------*/
621
622 /**
623 * @fn static inline std::uint64_t fetch_64bits(const std::uint8_t *data)
624 *
625 * @brief Fetches the 64bits.
626 *
627 * @param data The data.
628 *
629 * @return The 64bits.
630 */
631
fetch_64bits(const std::uint8_t * data)632 inline std::uint64_t fetch_64bits(const std::uint8_t *data)
633 {
634 return std::uint64_t(data[0]) | (std::uint64_t(data[1]) << 8) |
635 (std::uint64_t(data[2]) << 16) | (std::uint64_t(data[3]) << 24) |
636 (std::uint64_t(data[4]) << 32) | (std::uint64_t(data[5]) << 40) |
637 (std::uint64_t(data[6]) << 48) | (std::uint64_t(data[7]) << 56);
638 }
639
640
641 /*-------------------------------------------------
642 put_16bits - write 16 bits in LSB order
643 to the given buffer
644 -------------------------------------------------*/
645
646 /**
647 * @fn static inline void put_16bits(std::uint8_t *data, std::uint16_t value)
648 *
649 * @brief Puts the 16bits.
650 *
651 * @param [in,out] data If non-null, the data.
652 * @param value The value.
653 */
654
put_16bits(std::uint8_t * data,std::uint16_t value)655 inline void put_16bits(std::uint8_t *data, std::uint16_t value)
656 {
657 data[0] = value >> 0;
658 data[1] = value >> 8;
659 }
660
661
662 /*-------------------------------------------------
663 put_32bits - write 32 bits in LSB order
664 to the given buffer
665 -------------------------------------------------*/
666
667 /**
668 * @fn static inline void put_32bits(std::uint8_t *data, std::uint32_t value)
669 *
670 * @brief Puts the 32bits.
671 *
672 * @param [in,out] data If non-null, the data.
673 * @param value The value.
674 */
675
put_32bits(std::uint8_t * data,std::uint32_t value)676 inline void put_32bits(std::uint8_t *data, std::uint32_t value)
677 {
678 data[0] = value >> 0;
679 data[1] = value >> 8;
680 data[2] = value >> 16;
681 data[3] = value >> 24;
682 }
683
684
685 /*-------------------------------------------------
686 put_64bits - write 64 bits in LSB order
687 to the given buffer
688 -------------------------------------------------*/
689
690 /**
691 * @fn static inline void put_64bits(std::uint8_t *data, std::uint64_t value)
692 *
693 * @brief Puts the 64bits.
694 *
695 * @param [in,out] data If non-null, the data.
696 * @param value The value.
697 */
698
put_64bits(std::uint8_t * data,std::uint64_t value)699 inline void put_64bits(std::uint8_t *data, std::uint64_t value)
700 {
701 data[0] = value >> 0;
702 data[1] = value >> 8;
703 data[2] = value >> 16;
704 data[3] = value >> 24;
705 data[4] = value >> 32;
706 data[5] = value >> 40;
707 data[6] = value >> 48;
708 data[7] = value >> 56;
709 }
710
711
712 /**
713 * @fn static void u64toa(std::uint64_t val, char *output)
714 *
715 * @brief 64toas.
716 *
717 * @param val The value.
718 * @param [in,out] output If non-null, the output.
719 */
720
u64toa(std::uint64_t val,char * output)721 inline void u64toa(std::uint64_t val, char *output)
722 {
723 auto lo = std::uint32_t(val & 0xffffffff);
724 auto hi = std::uint32_t(val >> 32);
725 if (hi != 0)
726 sprintf(output, "%X%08X", hi, lo);
727 else
728 sprintf(output, "%X", lo);
729 }
730
731
732 /*-------------------------------------------------
733 set_stream_chunk_info - set the chunk info
734 for a given chunk within a stream
735 -------------------------------------------------*/
736
737 /**
738 * @fn static inline avi_error set_stream_chunk_info(avi_stream *stream, std::uint32_t index, std::uint64_t offset, std::uint32_t length)
739 *
740 * @brief Sets stream chunk information.
741 *
742 * @param [in,out] stream If non-null, the stream.
743 * @param index Zero-based index of the.
744 * @param offset The offset.
745 * @param length The length.
746 *
747 * @return An avi_error.
748 */
749
set_chunk_info(std::uint32_t index,std::uint64_t offset,std::uint32_t length)750 inline avi_file::error avi_stream::set_chunk_info(std::uint32_t index, std::uint64_t offset, std::uint32_t length)
751 {
752 /* if we need to allocate more, allocate more */
753 if (index >= m_chunks.capacity())
754 {
755 try { m_chunks.reserve((std::max<std::size_t>)(index, m_chunks.capacity() + 1000)); }
756 catch (...) { return avi_file::error::NO_MEMORY; }
757 }
758
759 /* update the number of chunks */
760 m_chunks.resize((std::max<std::size_t>)(m_chunks.size(), index + 1));
761
762 /* set the data */
763 m_chunks[index].offset = offset;
764 m_chunks[index].length = length;
765
766 return avi_file::error::NONE;
767 }
768
769
set_strh_data(std::uint8_t const * data,std::uint32_t size)770 inline avi_file::error avi_stream::set_strh_data(std::uint8_t const *data, std::uint32_t size)
771 {
772 m_type = fetch_32bits(&data[0]);
773 m_scale = fetch_32bits(&data[20]);
774 m_rate = fetch_32bits(&data[24]);
775 m_samples = fetch_32bits(&data[32]);
776 return avi_file::error::NONE;
777 }
778
779
set_strf_data(std::uint8_t const * data,std::uint32_t size)780 inline avi_file::error avi_stream::set_strf_data(std::uint8_t const *data, std::uint32_t size)
781 {
782 /* audio and video streams have differing headers */
783 if (m_type == STREAMTYPE_VIDS)
784 {
785 m_width = fetch_32bits(&data[4]);
786 m_height = fetch_32bits(&data[8]);
787 m_depth = fetch_16bits(&data[14]);
788 m_format = fetch_32bits(&data[16]);
789
790 /* extra extraction for HuffYUV data */
791 if ((m_format == FORMAT_HFYU) && (size >= 56))
792 {
793 avi_file::error const avierr = huffyuv_extract_tables(data, size);
794 if (avierr != avi_file::error::NONE)
795 return avierr;
796 }
797 }
798 else if (m_type == STREAMTYPE_AUDS)
799 {
800 m_channels = fetch_16bits(&data[2]);
801 m_samplebits = fetch_16bits(&data[14]);
802 m_samplerate = fetch_32bits(&data[4]);
803 }
804 return avi_file::error::NONE;
805 }
806
807
808 /*-------------------------------------------------
809 get_video_stream - return a pointer to the
810 video stream
811 -------------------------------------------------*/
812
813 /**
814 * @fn static inline avi_stream *get_video_stream()
815 *
816 * @brief Gets video stream.
817 *
818 * @return null if it fails, else the video stream.
819 */
820
get_video_stream()821 inline avi_stream *avi_file_impl::get_video_stream()
822 {
823 for (int streamnum = 0; streamnum < m_streams.size(); streamnum++)
824 if (m_streams[streamnum].type() == STREAMTYPE_VIDS)
825 return &m_streams[streamnum];
826
827 return nullptr;
828 }
829
830
831 /*-------------------------------------------------
832 get_audio_stream - return a pointer to the
833 audio stream for the 'n'th channel
834 -------------------------------------------------*/
835
836 /**
837 * @fn static inline avi_stream *get_audio_stream(avi_file *file, int channel, int *offset)
838 *
839 * @brief Gets audio stream.
840 *
841 * @param [in,out] file If non-null, the file.
842 * @param channel The channel.
843 * @param [in,out] offset If non-null, the offset.
844 *
845 * @return null if it fails, else the audio stream.
846 */
847
get_audio_stream(int channel,int & offset)848 inline avi_stream *avi_file_impl::get_audio_stream(int channel, int &offset)
849 {
850 /* find the audios stream */
851 for (int streamnum = 0; streamnum < m_streams.size(); streamnum++)
852 if (m_streams[streamnum].type() == STREAMTYPE_AUDS)
853 {
854 if (channel < m_streams[streamnum].channels())
855 {
856 offset = channel;
857 return &m_streams[streamnum];
858 }
859 channel -= m_streams[streamnum].channels();
860 }
861
862 return nullptr;
863 }
864
865
866 /*-------------------------------------------------
867 compute_idx1_size - compute the size of the
868 idx1 chunk
869 -------------------------------------------------*/
870
871 /**
872 * @fn static inline std::uint32_t compute_idx1_size(avi_file *file)
873 *
874 * @brief Calculates the index 1 size.
875 *
876 * @param [in,out] file If non-null, the file.
877 *
878 * @return The calculated index 1 size.
879 */
880
compute_idx1_size() const881 inline std::uint32_t avi_file_impl::compute_idx1_size() const
882 {
883 int chunks = 0;
884
885 /* count chunks in streams */
886 for (int strnum = 0; strnum < m_streams.size(); strnum++)
887 chunks += m_streams[strnum].chunks();
888
889 return chunks * 16 + 8;
890 }
891
892
893 /*-------------------------------------------------
894 get_chunkid_for_stream - make a chunk id for
895 a given stream
896 -------------------------------------------------*/
897
898 /**
899 * @fn static inline std::uint32_t get_chunkid_for_stream(avi_file *file, avi_stream *stream)
900 *
901 * @brief Gets chunkid for stream.
902 *
903 * @param [in,out] file If non-null, the file.
904 * @param [in,out] stream If non-null, the stream.
905 *
906 * @return The chunkid for stream.
907 */
908
get_chunkid_for_stream(const avi_stream * stream) const909 inline std::uint32_t avi_file_impl::get_chunkid_for_stream(const avi_stream *stream) const
910 {
911 std::uint32_t chunkid;
912
913 chunkid = AVI_FOURCC('0' + (stream - &m_streams[0]) / 10, '0' + (stream - &m_streams[0]) % 10, 0, 0);
914 if (stream->type() == STREAMTYPE_VIDS)
915 chunkid |= (stream->format() == 0) ? CHUNKTYPE_XXDB : CHUNKTYPE_XXDC;
916 else if (stream->type() == STREAMTYPE_AUDS)
917 chunkid |= CHUNKTYPE_XXWB;
918
919 return chunkid;
920 }
921
922
923 /*-------------------------------------------------
924 framenum_to_samplenum - given a video frame
925 number, get the first sample number
926 -------------------------------------------------*/
927
928 /**
929 * @fn static inline std::uint32_t framenum_to_samplenum(avi_file *file, std::uint32_t framenum)
930 *
931 * @brief Framenum to samplenum.
932 *
933 * @param [in,out] file If non-null, the file.
934 * @param framenum The framenum.
935 *
936 * @return An std::uint32_t.
937 */
938
framenum_to_samplenum(std::uint32_t framenum) const939 inline std::uint32_t avi_file_impl::framenum_to_samplenum(std::uint32_t framenum) const
940 {
941 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);
942 }
943
944
945 /*-------------------------------------------------
946 expand_tempbuffer - expand the file's
947 tempbuffer if necessary to contain the
948 requested amount of data
949 -------------------------------------------------*/
950
951 /**
952 * @fn static inline avi_error expand_tempbuffer(avi_file *file, std::uint32_t length)
953 *
954 * @brief Expand tempbuffer.
955 *
956 * @param [in,out] file If non-null, the file.
957 * @param length The length.
958 *
959 * @return An avi_error.
960 */
961
expand_tempbuffer(std::uint32_t length)962 inline avi_file::error avi_file_impl::expand_tempbuffer(std::uint32_t length)
963 {
964 /* expand the tempbuffer to hold the data if necessary */
965 if (length > m_tempbuffer.size())
966 {
967 try { m_tempbuffer.resize(2 * length); }
968 catch (...) { return error::NO_MEMORY; }
969 }
970 return error::NONE;
971 }
972
973
974 /*-------------------------------------------------
975 rgb32_compress_to_rgb - "compress" an RGB32
976 bitmap to an RGB encoded frame
977 -------------------------------------------------*/
978
979 /**
980 * @fn static avi_error rgb32_compress_to_rgb(avi_stream *stream, const bitmap_rgb32 &bitmap, std::uint8_t *data, std::uint32_t numbytes)
981 *
982 * @brief RGB 32 compress to RGB.
983 *
984 * @param [in,out] stream If non-null, the stream.
985 * @param bitmap The bitmap.
986 * @param [in,out] data If non-null, the data.
987 * @param numbytes The numbytes.
988 *
989 * @return An avi_error.
990 */
991
rgb32_compress_to_rgb(const bitmap_rgb32 & bitmap,std::uint8_t * data,std::uint32_t numbytes) const992 avi_file::error avi_stream::rgb32_compress_to_rgb(const bitmap_rgb32 &bitmap, std::uint8_t *data, std::uint32_t numbytes) const
993 {
994 int const height = (std::min<int>)(m_height, bitmap.height());
995 int const width = (std::min<int>)(m_width, bitmap.width());
996 std::uint8_t *const dataend = data + numbytes;
997 int x, y;
998
999 /* compressed video */
1000 for (y = 0; y < height; y++)
1001 {
1002 const std::uint32_t *source = &bitmap.pix(y);
1003 std::uint8_t *dest = data + (m_height - 1 - y) * m_width * 3;
1004
1005 for (x = 0; x < width && dest < dataend; x++)
1006 {
1007 rgb_t pix = *source++;
1008 *dest++ = pix.b();
1009 *dest++ = pix.g();
1010 *dest++ = pix.r();
1011 }
1012
1013 /* fill in any blank space on the right */
1014 for ( ; x < m_width && dest < dataend; x++)
1015 {
1016 *dest++ = 0;
1017 *dest++ = 0;
1018 *dest++ = 0;
1019 }
1020 }
1021
1022 /* fill in any blank space on the bottom */
1023 for ( ; y < m_height; y++)
1024 {
1025 std::uint8_t *dest = data + (m_height - 1 - y) * m_width * 3;
1026 for (x = 0; x < m_width && dest < dataend; x++)
1027 {
1028 *dest++ = 0;
1029 *dest++ = 0;
1030 *dest++ = 0;
1031 }
1032 }
1033
1034 return avi_file::error::NONE;
1035 }
1036
1037
1038 /*-------------------------------------------------
1039 yuv_decompress_to_yuy16 - decompress a YUV
1040 encoded frame to a YUY16 bitmap
1041 -------------------------------------------------*/
1042
1043 /**
1044 * @fn static avi_error yuv_decompress_to_yuy16(avi_stream *stream, const std::uint8_t *data, std::uint32_t numbytes, bitmap_yuy16 &bitmap)
1045 *
1046 * @brief Yuv decompress to yuy 16.
1047 *
1048 * @param [in,out] stream If non-null, the stream.
1049 * @param data The data.
1050 * @param numbytes The numbytes.
1051 * @param [in,out] bitmap The bitmap.
1052 *
1053 * @return An avi_error.
1054 */
1055
yuv_decompress_to_yuy16(const std::uint8_t * data,std::uint32_t numbytes,bitmap_yuy16 & bitmap) const1056 avi_file::error avi_stream::yuv_decompress_to_yuy16(const std::uint8_t *data, std::uint32_t numbytes, bitmap_yuy16 &bitmap) const
1057 {
1058 auto const *const dataend = reinterpret_cast<const std::uint16_t *>(data + numbytes);
1059 int x, y;
1060
1061 /* compressed video */
1062 for (y = 0; y < m_height; y++)
1063 {
1064 const std::uint16_t *source = reinterpret_cast<const std::uint16_t *>(data) + y * m_width;
1065 std::uint16_t *dest = &bitmap.pix(y);
1066
1067 /* switch off the compression */
1068 switch (m_format)
1069 {
1070 case FORMAT_UYVY:
1071 for (x = 0; x < m_width && source < dataend; x++)
1072 *dest++ = *source++;
1073 break;
1074
1075 case FORMAT_VYUY:
1076 case FORMAT_YUY2:
1077 for (x = 0; x < m_width && source < dataend; x++)
1078 {
1079 std::uint16_t pix = *source++;
1080 *dest++ = (pix >> 8) | (pix << 8);
1081 }
1082 break;
1083 }
1084 }
1085
1086 return avi_file::error::NONE;
1087 }
1088
1089
1090 /*-------------------------------------------------
1091 yuy16_compress_to_yuy - "compress" a YUY16
1092 bitmap to a YUV encoded frame
1093 -------------------------------------------------*/
1094
1095 /**
1096 * @fn static avi_error yuy16_compress_to_yuy(avi_stream *stream, const bitmap_yuy16 &bitmap, std::uint8_t *data, std::uint32_t numbytes)
1097 *
1098 * @brief Yuy 16 compress to yuy.
1099 *
1100 * @param [in,out] stream If non-null, the stream.
1101 * @param bitmap The bitmap.
1102 * @param [in,out] data If non-null, the data.
1103 * @param numbytes The numbytes.
1104 *
1105 * @return An avi_error.
1106 */
1107
yuy16_compress_to_yuy(const bitmap_yuy16 & bitmap,std::uint8_t * data,std::uint32_t numbytes) const1108 avi_file::error avi_stream::yuy16_compress_to_yuy(const bitmap_yuy16 &bitmap, std::uint8_t *data, std::uint32_t numbytes) const
1109 {
1110 auto *const dataend = reinterpret_cast<std::uint16_t *>(data + numbytes);
1111 int x, y;
1112
1113 /* compressed video */
1114 for (y = 0; y < m_height; y++)
1115 {
1116 const std::uint16_t *source = &bitmap.pix(y);
1117 std::uint16_t *dest = reinterpret_cast<std::uint16_t *>(data) + y * m_width;
1118
1119 /* switch off the compression */
1120 switch (m_format)
1121 {
1122 case FORMAT_UYVY:
1123 for (x = 0; x < m_width && dest < dataend; x++)
1124 *dest++ = *source++;
1125 break;
1126
1127 case FORMAT_VYUY:
1128 case FORMAT_YUY2:
1129 for (x = 0; x < m_width && dest < dataend; x++)
1130 {
1131 std::uint16_t pix = *source++;
1132 *dest++ = (pix >> 8) | (pix << 8);
1133 }
1134 break;
1135 }
1136 }
1137
1138 return avi_file::error::NONE;
1139 }
1140
1141
1142 /*-------------------------------------------------
1143 huffyuv_extract_tables - extract HuffYUV
1144 tables
1145 -------------------------------------------------*/
1146
1147 /**
1148 * @fn static avi_error huffyuv_extract_tables(avi_stream *stream, const std::uint8_t *chunkdata, std::uint32_t size)
1149 *
1150 * @brief Huffyuv extract tables.
1151 *
1152 * @param [in,out] stream If non-null, the stream.
1153 * @param chunkdata The chunkdata.
1154 * @param size The size.
1155 *
1156 * @return An avi_error.
1157 */
1158
huffyuv_extract_tables(const std::uint8_t * chunkdata,std::uint32_t size)1159 avi_file::error avi_stream::huffyuv_extract_tables(const std::uint8_t *chunkdata, std::uint32_t size)
1160 {
1161 const std::uint8_t *const chunkend = chunkdata + size;
1162
1163 /* allocate memory for the data */
1164 std::unique_ptr<huffyuv_data> huffyuv;
1165 try { huffyuv = std::make_unique<huffyuv_data>(); }
1166 catch (...) { return avi_file::error::NO_MEMORY; }
1167
1168 /* extract predictor information */
1169 if (&chunkdata[40] >= chunkend)
1170 return avi_file::error::INVALID_DATA;
1171 huffyuv->predictor = chunkdata[40];
1172
1173 /* make sure it's the left predictor */
1174 if ((huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) != HUFFYUV_PREDICT_LEFT)
1175 return avi_file::error::UNSUPPORTED_VIDEO_FORMAT;
1176
1177 /* make sure it's 16bpp YUV data */
1178 if (chunkdata[41] != 16)
1179 return avi_file::error::UNSUPPORTED_VIDEO_FORMAT;
1180 chunkdata += 44;
1181
1182 /* loop over tables */
1183 for (int tabnum = 0; tabnum < 3; tabnum++)
1184 {
1185 huffyuv_table &table = huffyuv->table[tabnum];
1186 std::uint32_t curbits, bitadd;
1187 std::uint16_t bitsat16 = 0;
1188 int offset = 0, bits;
1189
1190 /* loop until we populate the whole table */
1191 while (offset < 256)
1192 {
1193 int data, shift, count, i;
1194
1195 /* extract the next run */
1196 if (chunkdata >= chunkend)
1197 return avi_file::error::INVALID_DATA;
1198 data = *chunkdata++;
1199 shift = data & 0x1f;
1200 count = data >> 5;
1201
1202 /* zero count means next whole byte is a count */
1203 if (count == 0)
1204 {
1205 if (chunkdata >= chunkend)
1206 return avi_file::error::INVALID_DATA;
1207 count = *chunkdata++;
1208 }
1209 for (i = 0; i < count; i++)
1210 table.shift[offset++] = shift;
1211 }
1212
1213 /* now determine bit patterns and masks */
1214 curbits = 0;
1215 for (bits = 31; bits >= 0; bits--)
1216 {
1217 bitadd = 1 << (32 - bits);
1218
1219 /* make sure we've cleared out all the bits below */
1220 if ((curbits & (bitadd - 1)) != 0)
1221 return avi_file::error::INVALID_DATA;
1222
1223 /* find all entries with this shift count and assign them */
1224 for (offset = 0; offset < 256; offset++)
1225 if (table.shift[offset] == bits)
1226 {
1227 table.bits[offset] = curbits;
1228 table.mask[offset] = ~(bitadd - 1);
1229 curbits += bitadd;
1230 }
1231
1232 /* remember the bit pattern when we complete all the 17-bit codes */
1233 if (bits == 17)
1234 bitsat16 = curbits >> 16;
1235 }
1236
1237 /* allocate the number of extra lookup tables we need */
1238 if (bitsat16 > 0)
1239 {
1240 try { table.extralookup.resize(bitsat16 * 65536); }
1241 catch (...) { return avi_file::error::NO_MEMORY; }
1242 for (offset = 0; offset < bitsat16; offset++)
1243 table.baselookup[offset] = (offset << 8) | 0;
1244 }
1245
1246 /* then create lookup tables */
1247 for (offset = 0; offset < 256; offset++)
1248 if (table.shift[offset] > 16)
1249 {
1250 std::uint16_t *tablebase = &table.extralookup[(table.bits[offset] >> 16) * 65536];
1251 std::uint32_t start = table.bits[offset] & 0xffff;
1252 std::uint32_t end = start + ((1 << (32 - table.shift[offset])) - 1);
1253 while (start <= end)
1254 tablebase[start++] = (offset << 8) | (table.shift[offset] - 16);
1255 }
1256 else if (table.shift[offset] > 0)
1257 {
1258 std::uint32_t start = table.bits[offset] >> 16;
1259 std::uint32_t end = start + ((1 << (16 - table.shift[offset])) - 1);
1260 while (start <= end)
1261 table.baselookup[start++] = (offset << 8) | table.shift[offset];
1262 }
1263 }
1264
1265 m_huffyuv = std::move(huffyuv);
1266 return avi_file::error::NONE;
1267 }
1268
1269
1270 /*-------------------------------------------------
1271 huffyuv_decompress_to_yuy16 - decompress a
1272 HuffYUV-encoded frame to a YUY16 bitmap
1273 -------------------------------------------------*/
1274
1275 /**
1276 * @fn static avi_error huffyuv_decompress_to_yuy16(avi_stream *stream, const std::uint8_t *data, std::uint32_t numbytes, bitmap_yuy16 &bitmap)
1277 *
1278 * @brief Huffyuv decompress to yuy 16.
1279 *
1280 * @param [in,out] stream If non-null, the stream.
1281 * @param data The data.
1282 * @param numbytes The numbytes.
1283 * @param [in,out] bitmap The bitmap.
1284 *
1285 * @return An avi_error.
1286 */
1287
huffyuv_decompress_to_yuy16(const std::uint8_t * data,std::uint32_t numbytes,bitmap_yuy16 & bitmap) const1288 avi_file::error avi_stream::huffyuv_decompress_to_yuy16(const std::uint8_t *data, std::uint32_t numbytes, bitmap_yuy16 &bitmap) const
1289 {
1290 int prevlines = (m_height > 288) ? 2 : 1;
1291 std::uint8_t lastprevy = 0, lastprevcb = 0, lastprevcr = 0;
1292 std::uint8_t lasty = 0, lastcb = 0, lastcr = 0;
1293 std::uint8_t bitsinbuffer = 0;
1294 std::uint32_t bitbuffer = 0;
1295 std::uint32_t dataoffs = 0;
1296 int x, y;
1297
1298 /* compressed video */
1299 for (y = 0; y < m_height; y++)
1300 {
1301 std::uint16_t *dest = &bitmap.pix(y);
1302
1303 /* handle the first four bytes independently */
1304 x = 0;
1305 if (y == 0)
1306 {
1307 /* first DWORD is stored as YUY2 */
1308 lasty = data[dataoffs++];
1309 lastcb = data[dataoffs++];
1310 *dest++ = (lasty << 8) | lastcb;
1311 lasty = data[dataoffs++];
1312 lastcr = data[dataoffs++];
1313 *dest++ = (lasty << 8) | lastcr;
1314 x = 2;
1315 }
1316
1317 /* loop over pixels */
1318 for ( ; x < m_width; x++)
1319 {
1320 huffyuv_table const &ytable = m_huffyuv->table[0];
1321 huffyuv_table const &ctable = m_huffyuv->table[1 + (x & 1)];
1322 std::uint16_t huffdata;
1323 int shift;
1324
1325 /* fill up the buffer; they store little-endian DWORDs, so we XOR with 3 */
1326 while (bitsinbuffer <= 24 && dataoffs < numbytes)
1327 {
1328 bitbuffer |= data[dataoffs++ ^ 3] << (24 - bitsinbuffer);
1329 bitsinbuffer += 8;
1330 }
1331
1332 /* look up the Y component */
1333 huffdata = ytable.baselookup[bitbuffer >> 16];
1334 shift = huffdata & 0xff;
1335 if (shift == 0)
1336 {
1337 bitsinbuffer -= 16;
1338 bitbuffer <<= 16;
1339
1340 /* fill up the buffer; they store little-endian DWORDs, so we XOR with 3 */
1341 while (bitsinbuffer <= 24 && dataoffs < numbytes)
1342 {
1343 bitbuffer |= data[dataoffs++ ^ 3] << (24 - bitsinbuffer);
1344 bitsinbuffer += 8;
1345 }
1346
1347 huffdata = ytable.extralookup[(huffdata >> 8) * 65536 + (bitbuffer >> 16)];
1348 shift = huffdata & 0xff;
1349 }
1350 bitsinbuffer -= shift;
1351 bitbuffer <<= shift;
1352 std::uint16_t const pixel = huffdata & 0xff00;
1353
1354 /* fill up the buffer; they store little-endian DWORDs, so we XOR with 3 */
1355 while (bitsinbuffer <= 24 && dataoffs < numbytes)
1356 {
1357 bitbuffer |= data[dataoffs++ ^ 3] << (24 - bitsinbuffer);
1358 bitsinbuffer += 8;
1359 }
1360
1361 /* look up the Cb/Cr component */
1362 huffdata = ctable.baselookup[bitbuffer >> 16];
1363 shift = huffdata & 0xff;
1364 if (shift == 0)
1365 {
1366 bitsinbuffer -= 16;
1367 bitbuffer <<= 16;
1368
1369 /* fill up the buffer; they store little-endian DWORDs, so we XOR with 3 */
1370 while (bitsinbuffer <= 24 && dataoffs < numbytes)
1371 {
1372 bitbuffer |= data[dataoffs++ ^ 3] << (24 - bitsinbuffer);
1373 bitsinbuffer += 8;
1374 }
1375
1376 huffdata = ctable.extralookup[(huffdata >> 8) * 65536 + (bitbuffer >> 16)];
1377 shift = huffdata & 0xff;
1378 }
1379 bitsinbuffer -= shift;
1380 bitbuffer <<= shift;
1381 *dest++ = pixel | (huffdata >> 8);
1382 }
1383 }
1384
1385 /* apply deltas */
1386 lastprevy = lastprevcb = lastprevcr = 0;
1387 for (y = 0; y < m_height; y++)
1388 {
1389 std::uint16_t *prevrow = &bitmap.pix(y - prevlines);
1390 std::uint16_t *dest = &bitmap.pix(y);
1391
1392 /* handle the first four bytes independently */
1393 x = 0;
1394 if (y == 0)
1395 {
1396 /* lasty, lastcr, lastcb are set up previously */
1397 x = 2;
1398 }
1399
1400 /* left predict or gradient predict */
1401 if ((m_huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) == HUFFYUV_PREDICT_LEFT ||
1402 ((m_huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) == HUFFYUV_PREDICT_GRADIENT))
1403 {
1404 /* first do left deltas */
1405 for ( ; x < m_width; x += 2)
1406 {
1407 std::uint16_t pixel0 = dest[x + 0];
1408 std::uint16_t pixel1 = dest[x + 1];
1409
1410 lasty += pixel0 >> 8;
1411 lastcb += pixel0;
1412 dest[x + 0] = (lasty << 8) | lastcb;
1413
1414 lasty += pixel1 >> 8;
1415 lastcr += pixel1;
1416 dest[x + 1] = (lasty << 8) | lastcr;
1417 }
1418
1419 /* for gradient, we then add in the previous row */
1420 if ((m_huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) == HUFFYUV_PREDICT_GRADIENT && y >= prevlines)
1421 for (x = 0; x < m_width; x++)
1422 {
1423 std::uint16_t curpix = dest[x];
1424 std::uint16_t prevpix = prevrow[x];
1425 std::uint8_t ysum = (curpix >> 8) + (prevpix >> 8);
1426 std::uint8_t csum = curpix + prevpix;
1427 dest[x] = (ysum << 8) | csum;
1428 }
1429 }
1430
1431 /* median predict on rows > 0 */
1432 else if ((m_huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) == HUFFYUV_PREDICT_MEDIAN && y >= prevlines)
1433 {
1434 for ( ; x < m_width; x += 2)
1435 {
1436 std::uint16_t prevpixel0 = prevrow[x + 0];
1437 std::uint16_t prevpixel1 = prevrow[x + 1];
1438 std::uint16_t pixel0 = dest[x + 0];
1439 std::uint16_t pixel1 = dest[x + 1];
1440 std::uint8_t a, b, c;
1441
1442 /* compute previous, above, and (prev + above - above-left) */
1443 a = lasty;
1444 b = prevpixel0 >> 8;
1445 c = lastprevy;
1446 lastprevy = b;
1447 if (a > b) { std::uint8_t tmp = a; a = b; b = tmp; }
1448 if (a > c) { std::uint8_t tmp = a; a = c; c = tmp; }
1449 if (b > c) { std::uint8_t tmp = b; b = c; c = tmp; }
1450 lasty = (pixel0 >> 8) + b;
1451
1452 /* compute previous, above, and (prev + above - above-left) */
1453 a = lastcb;
1454 b = prevpixel0 & 0xff;
1455 c = lastprevcb;
1456 lastprevcb = b;
1457 if (a > b) { std::uint8_t tmp = a; a = b; b = tmp; }
1458 if (a > c) { std::uint8_t tmp = a; a = c; c = tmp; }
1459 if (b > c) { std::uint8_t tmp = b; b = c; c = tmp; }
1460 lastcb = (pixel0 & 0xff) + b;
1461 dest[x + 0] = (lasty << 8) | lastcb;
1462
1463 /* compute previous, above, and (prev + above - above-left) */
1464 a = lasty;
1465 b = prevpixel1 >> 8;
1466 c = lastprevy;
1467 lastprevy = b;
1468 if (a > b) { std::uint8_t tmp = a; a = b; b = tmp; }
1469 if (a > c) { std::uint8_t tmp = a; a = c; c = tmp; }
1470 if (b > c) { std::uint8_t tmp = b; b = c; c = tmp; }
1471 lasty = (pixel1 >> 8) + b;
1472
1473 /* compute previous, above, and (prev + above - above-left) */
1474 a = lastcr;
1475 b = prevpixel1 & 0xff;
1476 c = lastprevcr;
1477 lastprevcr = b;
1478 if (a > b) { std::uint8_t tmp = a; a = b; b = tmp; }
1479 if (a > c) { std::uint8_t tmp = a; a = c; c = tmp; }
1480 if (b > c) { std::uint8_t tmp = b; b = c; c = tmp; }
1481 lastcr = (pixel1 & 0xff) + b;
1482 dest[x + 1] = (lasty << 8) | lastcr;
1483 }
1484 }
1485 }
1486
1487 return avi_file::error::NONE;
1488 }
1489
1490
1491 /*-------------------------------------------------
1492 uncompressed_rgb24_to_argb32 - convert a raw
1493 RGB24-encoded frame to an ARGB32 bitmap
1494 -------------------------------------------------*/
1495
uncompressed_rgb24_to_argb32(const std::uint8_t * data,std::uint32_t numbytes,bitmap_argb32 & bitmap) const1496 avi_file::error avi_stream::uncompressed_rgb24_to_argb32(const std::uint8_t *data, std::uint32_t numbytes, bitmap_argb32 &bitmap) const
1497 {
1498 std::uint32_t dataoffs = 0;
1499
1500 /* uncompressed video */
1501 for (int y = 0; y < m_height; y++)
1502 {
1503 std::uint32_t *dest = &bitmap.pix(y);
1504
1505 /* loop over pixels */
1506 for (int x = 0; x < m_width; x++)
1507 {
1508 const uint8_t b = data[dataoffs++];
1509 const uint8_t g = data[dataoffs++];
1510 const uint8_t r = data[dataoffs++];
1511 *dest++ = 0xff << 24 | r << 16 | g << 8 | b;
1512 }
1513 }
1514
1515 return avi_file::error::NONE;
1516 }
1517
1518
1519 /*-------------------------------------------------
1520 uncompressed_yuv420p_to_argb32 - convert a
1521 YUV420p-encoded frame to an ARGB32 bitmap
1522 -------------------------------------------------*/
1523
uncompressed_yuv420p_to_argb32(const std::uint8_t * data,std::uint32_t numbytes,bitmap_argb32 & bitmap) const1524 avi_file::error avi_stream::uncompressed_yuv420p_to_argb32(const std::uint8_t *data, std::uint32_t numbytes, bitmap_argb32 &bitmap) const
1525 {
1526 const int width = bitmap.width();
1527 const int height = bitmap.height();
1528 const int size_total = width * height;
1529
1530 /* uncompressed video */
1531 for (int y = 0; y < m_height; y++)
1532 {
1533 std::uint32_t *dest = &bitmap.pix(y);
1534
1535 /* loop over pixels */
1536 for (int x = 0; x < m_width; x++)
1537 {
1538 const uint8_t luma = data[y * width + x];
1539 const uint8_t u = data[(y / 2) * (width / 2) + x / 2 + size_total];
1540 const uint8_t v = data[(y / 2) * (width / 2) + x / 2 + size_total + size_total / 4];
1541
1542 int r = luma + (1.370705f * (v - 0x80));
1543 int g = luma - (0.698001f * (v - 0x80)) - (0.337633f * (u - 0x80));
1544 int b = luma + (1.732446f * (u - 0x80));
1545
1546 r = (r < 0) ? 0 : ((r > 255) ? 255 : r);
1547 g = (g < 0) ? 0 : ((g > 255) ? 255 : g);
1548 b = (b < 0) ? 0 : ((b > 255) ? 255 : b);
1549
1550 *dest++ = 0xff << 24 | r << 16 | g << 8 | b;
1551 }
1552 }
1553
1554 return avi_file::error::NONE;
1555 }
1556
1557
1558 /*-------------------------------------------------
1559 avi_close - close an AVI movie file
1560 -------------------------------------------------*/
1561
1562 /**
1563 * @fn avi_error avi_close(avi_file *file)
1564 *
1565 * @brief Avi close.
1566 *
1567 * @param [in,out] file If non-null, the file.
1568 *
1569 * @return An avi_error.
1570 */
1571
~avi_file_impl()1572 avi_file_impl::~avi_file_impl()
1573 {
1574 error avierr = error::NONE;
1575
1576 /* if we're creating a new file, finalize it by writing out the non-media chunks */
1577 if (m_type == FILETYPE_CREATE)
1578 {
1579 /* flush any pending sound data */
1580 avierr = soundbuf_flush(false);
1581
1582 /* close the movi chunk */
1583 if (avierr == error::NONE)
1584 avierr = chunk_close();
1585
1586 /* if this is the first RIFF chunk, write an idx1 */
1587 if (avierr == error::NONE && m_riffbase == 0)
1588 avierr = write_idx1_chunk();
1589
1590 /* update the strh and indx chunks for each stream */
1591 for (int strnum = 0; strnum < m_streams.size(); strnum++)
1592 {
1593 if (avierr == error::NONE)
1594 avierr = write_strh_chunk(m_streams[strnum], false);
1595 if (avierr == error::NONE)
1596 avierr = write_indx_chunk(m_streams[strnum], false);
1597 }
1598
1599 /* update the avih chunk */
1600 if (avierr == error::NONE)
1601 avierr = write_avih_chunk(false);
1602
1603 /* close the RIFF chunk */
1604 if (avierr == error::NONE)
1605 avierr = chunk_close();
1606 }
1607
1608 /* close the file */
1609 m_file.reset();
1610
1611 //return avierr;
1612 }
1613
1614
1615 /*-------------------------------------------------
1616 avi_printf_chunks - print the chunks in a file
1617 -------------------------------------------------*/
1618
1619 /**
1620 * @fn void avi_printf_chunks(avi_file *file)
1621 *
1622 * @brief Avi printf chunks.
1623 *
1624 * @param [in,out] file If non-null, the file.
1625 */
1626
printf_chunks()1627 void avi_file_impl::printf_chunks()
1628 {
1629 printf_chunk_recursive(&m_rootchunk, 0);
1630 }
1631
1632
1633 /*-------------------------------------------------
1634 get_movie_info - return a pointer to the
1635 movie info
1636 -------------------------------------------------*/
1637
1638 /**
1639 * @fn const avi_movie_info *avi_get_movie_info(avi_file *file)
1640 *
1641 * @brief Avi get movie information.
1642 *
1643 * @param [in,out] file If non-null, the file.
1644 *
1645 * @return null if it fails, else an avi_movie_info*.
1646 */
1647
get_movie_info() const1648 avi_file::movie_info const &avi_file_impl::get_movie_info() const
1649 {
1650 return m_info;
1651 }
1652
1653
1654 /*-------------------------------------------------
1655 avi_frame_to_sample - convert a frame index
1656 to a sample index
1657 -------------------------------------------------*/
1658
1659 /**
1660 * @fn std::uint32_t avi_first_sample_in_frame(avi_file *file, std::uint32_t framenum)
1661 *
1662 * @brief Avi first sample in frame.
1663 *
1664 * @param [in,out] file If non-null, the file.
1665 * @param framenum The framenum.
1666 *
1667 * @return An std::uint32_t.
1668 */
1669
first_sample_in_frame(std::uint32_t framenum) const1670 std::uint32_t avi_file_impl::first_sample_in_frame(std::uint32_t framenum) const
1671 {
1672 return framenum_to_samplenum(framenum);
1673 }
1674
1675
1676 /*-------------------------------------------------
1677 read_uncompressed_video_frame - read raw video
1678 data for a particular frame from the AVI file,
1679 converting to ARGB32 format
1680 -------------------------------------------------*/
1681
read_uncompressed_video_frame(std::uint32_t framenum,bitmap_argb32 & bitmap)1682 avi_file::error avi_file_impl::read_uncompressed_video_frame(std::uint32_t framenum, bitmap_argb32 &bitmap)
1683 {
1684 /* get the video stream */
1685 avi_stream *const stream = get_video_stream();
1686 if (!stream)
1687 return error::INVALID_STREAM;
1688
1689 if (stream->format() != FORMAT_UNCOMPRESSED && stream->format() != FORMAT_DIB && stream->format() != FORMAT_RGB && stream->format() != FORMAT_RAW && stream->format() != FORMAT_I420)
1690 return error::UNSUPPORTED_VIDEO_FORMAT;
1691
1692 if (bitmap.width() < stream->width() || bitmap.height() < stream->height())
1693 bitmap.resize(stream->width(), stream->height());
1694
1695 /* assume one chunk == one frame */
1696 if (framenum >= stream->chunks())
1697 return error::INVALID_FRAME;
1698
1699 /* we only support YUY-style bitmaps (16bpp) */
1700 if (bitmap.width() < stream->width() || bitmap.height() < stream->height())
1701 return error::INVALID_BITMAP;
1702
1703 /* expand the tempbuffer to hold the data if necessary */
1704 error avierr = error::NONE;
1705 avierr = expand_tempbuffer(stream->chunk(framenum).length);
1706 if (avierr != error::NONE)
1707 return avierr;
1708
1709 /* read in the data */
1710 std::uint32_t bytes_read;
1711 osd_file::error const filerr = m_file->read(&m_tempbuffer[0], stream->chunk(framenum).offset, stream->chunk(framenum).length, bytes_read);
1712 if (filerr != osd_file::error::NONE || bytes_read != stream->chunk(framenum).length)
1713 return error::READ_ERROR;
1714
1715 /* validate this is good data */
1716 std::uint32_t const chunkid = fetch_32bits(&m_tempbuffer[0]);
1717 if (chunkid == get_chunkid_for_stream(stream))
1718 {
1719 /* uncompressed YUV420p */
1720 if (stream->format() == FORMAT_I420)
1721 avierr = stream->uncompressed_yuv420p_to_argb32(&m_tempbuffer[8], stream->chunk(framenum).length - 8, bitmap);
1722 else
1723 avierr = stream->uncompressed_rgb24_to_argb32(&m_tempbuffer[8], stream->chunk(framenum).length - 8, bitmap);
1724 }
1725 else
1726 {
1727 avierr = error::INVALID_DATA;
1728 }
1729
1730 return avierr;
1731 }
1732
1733 /*-------------------------------------------------
1734 read_video_frame - read video data for a
1735 particular frame from the AVI file, converting
1736 to YUY16 format
1737 -------------------------------------------------*/
1738
read_video_frame(std::uint32_t framenum,bitmap_yuy16 & bitmap)1739 avi_file::error avi_file_impl::read_video_frame(std::uint32_t framenum, bitmap_yuy16 &bitmap)
1740 {
1741 /* get the video stream */
1742 avi_stream *const stream = get_video_stream();
1743 if (!stream)
1744 return error::INVALID_STREAM;
1745
1746 /* validate our ability to handle the data */
1747 if (stream->format() != FORMAT_UYVY && stream->format() != FORMAT_VYUY && stream->format() != FORMAT_YUY2 && stream->format() != FORMAT_HFYU)
1748 return error::UNSUPPORTED_VIDEO_FORMAT;
1749
1750 /* assume one chunk == one frame */
1751 if (framenum >= stream->chunks())
1752 return error::INVALID_FRAME;
1753
1754 /* we only support YUY-style bitmaps (16bpp) */
1755 if (bitmap.width() < stream->width() || bitmap.height() < stream->height())
1756 return error::INVALID_BITMAP;
1757
1758 /* expand the tempbuffer to hold the data if necessary */
1759 error avierr = error::NONE;
1760 avierr = expand_tempbuffer(stream->chunk(framenum).length);
1761 if (avierr != error::NONE)
1762 return avierr;
1763
1764 /* read in the data */
1765 std::uint32_t bytes_read;
1766 osd_file::error const filerr = m_file->read(&m_tempbuffer[0], stream->chunk(framenum).offset, stream->chunk(framenum).length, bytes_read);
1767 if (filerr != osd_file::error::NONE || bytes_read != stream->chunk(framenum).length)
1768 return error::READ_ERROR;
1769
1770 /* validate this is good data */
1771 std::uint32_t const chunkid = fetch_32bits(&m_tempbuffer[0]);
1772 if (chunkid == get_chunkid_for_stream(stream))
1773 {
1774 /* HuffYUV-compressed */
1775 if (stream->format() == FORMAT_HFYU)
1776 avierr = stream->huffyuv_decompress_to_yuy16(&m_tempbuffer[8], stream->chunk(framenum).length - 8, bitmap);
1777
1778 /* other YUV-compressed */
1779 else
1780 avierr = stream->yuv_decompress_to_yuy16(&m_tempbuffer[8], stream->chunk(framenum).length - 8, bitmap);
1781 }
1782 else
1783 avierr = error::INVALID_DATA;
1784
1785 return avierr;
1786 }
1787
1788
1789 /*-------------------------------------------------
1790 avi_read_sound_samples - read sound sample
1791 data from an AVI file
1792 -------------------------------------------------*/
1793
1794 /**
1795 * @fn avi_error avi_read_sound_samples(avi_file *file, int channel, std::uint32_t firstsample, std::uint32_t numsamples, std::int16_t *output)
1796 *
1797 * @brief Avi read sound samples.
1798 *
1799 * @param [in,out] file If non-null, the file.
1800 * @param channel The channel.
1801 * @param firstsample The firstsample.
1802 * @param numsamples The numsamples.
1803 * @param [in,out] output If non-null, the output.
1804 *
1805 * @return An avi_error.
1806 */
1807
read_sound_samples(int channel,std::uint32_t firstsample,std::uint32_t numsamples,std::int16_t * output)1808 avi_file::error avi_file_impl::read_sound_samples(int channel, std::uint32_t firstsample, std::uint32_t numsamples, std::int16_t *output)
1809 {
1810 /* get the audio stream */
1811 int offset = 0;
1812 avi_stream *const stream = get_audio_stream(channel, offset);
1813 if (!stream)
1814 return error::INVALID_STREAM;
1815
1816 /* validate our ability to handle the data */
1817 if (stream->format() != 0 || (stream->samplebits() != 8 && stream->samplebits() != 16))
1818 return error::UNSUPPORTED_AUDIO_FORMAT;
1819
1820 /* verify we are in range */
1821 if (firstsample >= stream->samples())
1822 return error::INVALID_FRAME;
1823 if (firstsample + numsamples > stream->samples())
1824 numsamples = stream->samples() - firstsample;
1825
1826 /* determine bytes per sample */
1827 std::uint32_t const bytes_per_sample = stream->bytes_per_sample();
1828
1829 /* loop until all samples have been extracted */
1830 while (numsamples > 0)
1831 {
1832 std::uint32_t chunkbase = 0, chunkend = 0, chunkid;
1833 std::uint32_t bytes_read, samples_this_chunk;
1834 int chunknum, sampnum;
1835
1836 /* locate the chunk with the first sample */
1837 for (chunknum = 0; chunknum < stream->chunks(); chunknum++)
1838 {
1839 chunkend = chunkbase + (stream->chunk(chunknum).length - 8) / bytes_per_sample;
1840 if (firstsample < chunkend)
1841 break;
1842 chunkbase = chunkend;
1843 }
1844
1845 /* if we hit the end, fill the rest with silence */
1846 if (chunknum == stream->chunks())
1847 {
1848 std::memset(output, 0, numsamples * 2);
1849 break;
1850 }
1851
1852 /* expand the tempbuffer to hold the data if necessary */
1853 error avierr = expand_tempbuffer(stream->chunk(chunknum).length);
1854 if (avierr != error::NONE)
1855 return avierr;
1856
1857 /* read in the data */
1858 auto const filerr = m_file->read(&m_tempbuffer[0], stream->chunk(chunknum).offset, stream->chunk(chunknum).length, bytes_read);
1859 if (filerr != osd_file::error::NONE || bytes_read != stream->chunk(chunknum).length)
1860 return error::READ_ERROR;
1861
1862 /* validate this is good data */
1863 chunkid = fetch_32bits(&m_tempbuffer[0]);
1864 if (chunkid != get_chunkid_for_stream(stream))
1865 return error::INVALID_DATA;
1866
1867 /* determine how many samples to copy */
1868 samples_this_chunk = chunkend - firstsample;
1869 samples_this_chunk = (std::min)(samples_this_chunk, numsamples);
1870
1871 /* extract 16-bit samples from the chunk */
1872 if (stream->samplebits() == 16)
1873 {
1874 const auto *base = reinterpret_cast<const std::int16_t *>(&m_tempbuffer[8]);
1875 base += stream->channels() * (firstsample - chunkbase) + offset;
1876 for (sampnum = 0; sampnum < samples_this_chunk; sampnum++)
1877 {
1878 *output++ = little_endianize_int16(*base);
1879 base += stream->channels();
1880 }
1881 }
1882
1883 /* extract 8-bit samples from the chunk */
1884 else if (stream->samplebits() == 8)
1885 {
1886 const std::uint8_t *base = &m_tempbuffer[8];
1887 base += stream->channels() * (firstsample - chunkbase) + offset;
1888 for (sampnum = 0; sampnum < samples_this_chunk; sampnum++)
1889 {
1890 *output++ = (*base << 8) - 0x8000;
1891 base += stream->channels();
1892 }
1893 }
1894
1895 /* update our counters */
1896 firstsample += samples_this_chunk;
1897 numsamples -= samples_this_chunk;
1898 }
1899
1900 return error::NONE;
1901 }
1902
1903
1904 /*-------------------------------------------------
1905 avi_append_video_frame_yuy16 - append a frame
1906 of video in YUY16 format
1907 -------------------------------------------------*/
1908
1909 /**
1910 * @fn avi_error avi_append_video_frame(avi_file *file, bitmap_yuy16 &bitmap)
1911 *
1912 * @brief Avi append video frame.
1913 *
1914 * @param [in,out] file If non-null, the file.
1915 * @param [in,out] bitmap The bitmap.
1916 *
1917 * @return An avi_error.
1918 */
1919
append_video_frame(bitmap_yuy16 & bitmap)1920 avi_file::error avi_file_impl::append_video_frame(bitmap_yuy16 &bitmap)
1921 {
1922 avi_stream *const stream = get_video_stream();
1923 error avierr;
1924 std::uint32_t maxlength;
1925
1926 /* validate our ability to handle the data */
1927 if (stream->format() != FORMAT_UYVY && stream->format() != FORMAT_VYUY && stream->format() != FORMAT_YUY2 && stream->format() != FORMAT_HFYU)
1928 return error::UNSUPPORTED_VIDEO_FORMAT;
1929
1930 /* write out any sound data first */
1931 avierr = soundbuf_write_chunk(stream->chunks());
1932 if (avierr != error::NONE)
1933 return avierr;
1934
1935 /* make sure we have enough room */
1936 maxlength = 2 * stream->width() * stream->height();
1937 avierr = expand_tempbuffer(maxlength);
1938 if (avierr != error::NONE)
1939 return avierr;
1940
1941 /* now compress the data */
1942 avierr = stream->yuy16_compress_to_yuy(bitmap, &m_tempbuffer[0], maxlength);
1943 if (avierr != error::NONE)
1944 return avierr;
1945
1946 /* write the data */
1947 avierr = chunk_write(get_chunkid_for_stream(stream), &m_tempbuffer[0], maxlength);
1948 if (avierr != error::NONE)
1949 return avierr;
1950
1951 /* set the info for this new chunk */
1952 avierr = stream->set_chunk_info(stream->chunks(), m_writeoffs - maxlength - 8, maxlength + 8);
1953 if (avierr != error::NONE)
1954 return avierr;
1955
1956 stream->set_samples(m_info.video_numsamples = stream->chunks());
1957
1958 return error::NONE;
1959 }
1960
1961
1962 /*-------------------------------------------------
1963 avi_append_video_frame_rgb32 - append a frame
1964 of video in RGB32 format
1965 -------------------------------------------------*/
1966
1967 /**
1968 * @fn avi_error avi_append_video_frame(avi_file *file, bitmap_rgb32 &bitmap)
1969 *
1970 * @brief Avi append video frame.
1971 *
1972 * @param [in,out] file If non-null, the file.
1973 * @param [in,out] bitmap The bitmap.
1974 *
1975 * @return An avi_error.
1976 */
1977
append_video_frame(bitmap_rgb32 & bitmap)1978 avi_file::error avi_file_impl::append_video_frame(bitmap_rgb32 &bitmap)
1979 {
1980 avi_stream *const stream = get_video_stream();
1981 error avierr;
1982 std::uint32_t maxlength;
1983
1984 /* validate our ability to handle the data */
1985 if (stream->format() != 0)
1986 return error::UNSUPPORTED_VIDEO_FORMAT;
1987
1988 /* depth must be 24 */
1989 if (stream->depth() != 24)
1990 return error::UNSUPPORTED_VIDEO_FORMAT;
1991
1992 /* write out any sound data first */
1993 avierr = soundbuf_write_chunk(stream->chunks());
1994 if (avierr != error::NONE)
1995 return avierr;
1996
1997 /* make sure we have enough room */
1998 maxlength = 3 * stream->width() * stream->height();
1999 avierr = expand_tempbuffer(maxlength);
2000 if (avierr != error::NONE)
2001 return avierr;
2002
2003 /* copy the RGB data to the destination */
2004 avierr = stream->rgb32_compress_to_rgb(bitmap, &m_tempbuffer[0], maxlength);
2005 if (avierr != error::NONE)
2006 return avierr;
2007
2008 /* write the data */
2009 avierr = chunk_write(get_chunkid_for_stream(stream), &m_tempbuffer[0], maxlength);
2010 if (avierr != error::NONE)
2011 return avierr;
2012
2013 /* set the info for this new chunk */
2014 avierr = stream->set_chunk_info(stream->chunks(), m_writeoffs - maxlength - 8, maxlength + 8);
2015 if (avierr != error::NONE)
2016 return avierr;
2017
2018 stream->set_samples(m_info.video_numsamples = stream->chunks());
2019
2020 return error::NONE;
2021 }
2022
2023
2024 /*-------------------------------------------------
2025 avi_append_sound_samples - append sound
2026 samples
2027 -------------------------------------------------*/
2028
2029 /**
2030 * @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)
2031 *
2032 * @brief Avi append sound samples.
2033 *
2034 * @param [in,out] file If non-null, the file.
2035 * @param channel The channel.
2036 * @param samples The samples.
2037 * @param numsamples The numsamples.
2038 * @param sampleskip The sampleskip.
2039 *
2040 * @return An avi_error.
2041 */
2042
append_sound_samples(int channel,const std::int16_t * samples,std::uint32_t numsamples,std::uint32_t sampleskip)2043 avi_file::error avi_file_impl::append_sound_samples(int channel, const std::int16_t *samples, std::uint32_t numsamples, std::uint32_t sampleskip)
2044 {
2045 std::uint32_t sampoffset = m_soundbuf_chansamples[channel];
2046 std::uint32_t sampnum;
2047
2048 /* see if we have enough room in the buffer */
2049 if (sampoffset + numsamples > m_soundbuf_samples)
2050 return error::EXCEEDED_SOUND_BUFFER;
2051
2052 /* append samples to the buffer in little-endian format */
2053 for (sampnum = 0; sampnum < numsamples; sampnum++)
2054 {
2055 std::int16_t data = *samples++;
2056 samples += sampleskip;
2057 data = little_endianize_int16(data);
2058 m_soundbuf[sampoffset++ * m_info.audio_channels + channel] = data;
2059 }
2060 m_soundbuf_chansamples[channel] = sampoffset;
2061
2062 /* flush any full sound chunks to disk */
2063 return soundbuf_flush(true);
2064 }
2065
2066
2067 /*-------------------------------------------------
2068 read_chunk_data - read a chunk's data into
2069 memory
2070 -------------------------------------------------*/
2071
2072 /**
2073 * @fn static avi_error read_chunk_data(avi_file *file, const avi_chunk *chunk, std::uint8_t **buffer)
2074 *
2075 * @brief Reads chunk data.
2076 *
2077 * @param [in,out] file If non-null, the file.
2078 * @param chunk The chunk.
2079 * @param [in,out] buffer If non-null, the buffer.
2080 *
2081 * @return The chunk data.
2082 */
2083
read_chunk_data(avi_chunk const & chunk,std::unique_ptr<std::uint8_t[]> & buffer)2084 avi_file::error avi_file_impl::read_chunk_data(avi_chunk const &chunk, std::unique_ptr<std::uint8_t []> &buffer)
2085 {
2086 /* allocate memory for the data */
2087 try { buffer.reset(new std::uint8_t[chunk.size]); }
2088 catch (...) { return error::NO_MEMORY; }
2089
2090 /* read from the file */
2091 std::uint32_t bytes_read;
2092 osd_file::error const filerr = m_file->read(&buffer[0], chunk.offset + 8, chunk.size, bytes_read);
2093 if (filerr != osd_file::error::NONE || bytes_read != chunk.size)
2094 {
2095 buffer.reset();
2096 return error::READ_ERROR;
2097 }
2098
2099 return error::NONE;
2100 }
2101
2102
2103 /*-------------------------------------------------
2104 get_first_chunk - get information about the
2105 first chunk in a container
2106 -------------------------------------------------*/
2107
2108 /**
2109 * @fn static avi_error get_first_chunk(avi_file *file, const avi_chunk *parent, avi_chunk *newchunk)
2110 *
2111 * @brief Gets the first chunk.
2112 *
2113 * @param [in,out] file If non-null, the file.
2114 * @param parent The parent.
2115 * @param [in,out] newchunk If non-null, the newchunk.
2116 *
2117 * @return The first chunk.
2118 */
2119
get_first_chunk(avi_chunk const * parent,avi_chunk & newchunk)2120 avi_file::error avi_file_impl::get_first_chunk(avi_chunk const *parent, avi_chunk &newchunk)
2121 {
2122 std::uint64_t startoffset = (parent != nullptr && parent->type != 0) ? parent->offset + 12 : 0;
2123 if (parent != nullptr && parent->type != CHUNKTYPE_LIST && parent->type != CHUNKTYPE_RIFF && parent->type != 0)
2124 return error::INVALID_DATA;
2125 return get_next_chunk_internal(parent, newchunk, startoffset);
2126 }
2127
2128
2129 /*-------------------------------------------------
2130 get_next_chunk - get information about the
2131 next chunk in a container
2132 -------------------------------------------------*/
2133
2134 /**
2135 * @fn static avi_error get_next_chunk(avi_file *file, const avi_chunk *parent, avi_chunk *newchunk)
2136 *
2137 * @brief Gets the next chunk.
2138 *
2139 * @param [in,out] file If non-null, the file.
2140 * @param parent The parent.
2141 * @param [in,out] newchunk If non-null, the newchunk.
2142 *
2143 * @return The next chunk.
2144 */
2145
get_next_chunk(avi_chunk const * parent,avi_chunk & newchunk)2146 avi_file::error avi_file_impl::get_next_chunk(avi_chunk const *parent, avi_chunk &newchunk)
2147 {
2148 std::uint64_t nextoffset = newchunk.offset + 8 + newchunk.size + (newchunk.size & 1);
2149 return get_next_chunk_internal(parent, newchunk, nextoffset);
2150 }
2151
2152
2153 /*-------------------------------------------------
2154 find_first_chunk - get information about the
2155 first chunk of a particular type in a container
2156 -------------------------------------------------*/
2157
2158 /**
2159 * @fn static avi_error find_first_chunk(avi_file *file, std::uint32_t findme, const avi_chunk *container, avi_chunk *result)
2160 *
2161 * @brief Searches for the first chunk.
2162 *
2163 * @param [in,out] file If non-null, the file.
2164 * @param findme The findme.
2165 * @param container The container.
2166 * @param [out] result If non-null, the result.
2167 *
2168 * @return The found chunk.
2169 */
2170
find_first_chunk(std::uint32_t findme,const avi_chunk * container,avi_chunk & result)2171 avi_file::error avi_file_impl::find_first_chunk(std::uint32_t findme, const avi_chunk *container, avi_chunk &result)
2172 {
2173 error avierr;
2174
2175 for (avierr = get_first_chunk(container, result); avierr == error::NONE; avierr = get_next_chunk(container, result))
2176 if (result.type == findme)
2177 return error::NONE;
2178
2179 return avierr;
2180 }
2181
2182
2183 /*-------------------------------------------------
2184 find_next_chunk - get information about the
2185 next chunk of a particular type in a container
2186 -------------------------------------------------*/
2187
2188 /**
2189 * @fn static avi_error find_next_chunk(avi_file *file, std::uint32_t findme, const avi_chunk *container, avi_chunk *result)
2190 *
2191 * @brief Searches for the next chunk.
2192 *
2193 * @param [in,out] file If non-null, the file.
2194 * @param findme The findme.
2195 * @param container The container.
2196 * @param [out] result If non-null, the result.
2197 *
2198 * @return The found chunk.
2199 */
2200
find_next_chunk(std::uint32_t findme,const avi_chunk * container,avi_chunk & result)2201 avi_file::error avi_file_impl::find_next_chunk(std::uint32_t findme, const avi_chunk *container, avi_chunk &result)
2202 {
2203 error avierr;
2204
2205 for (avierr = get_next_chunk(container, result); avierr == error::NONE; avierr = get_next_chunk(container, result))
2206 if (result.type == findme)
2207 return error::NONE;
2208
2209 return avierr;
2210 }
2211
2212
2213 /*-------------------------------------------------
2214 find_first_list - get information about the
2215 first list of a particular type in a container
2216 -------------------------------------------------*/
2217
2218 /**
2219 * @fn static avi_error find_first_list(avi_file *file, std::uint32_t findme, const avi_chunk *container, avi_chunk *result)
2220 *
2221 * @brief Searches for the first list.
2222 *
2223 * @param [in,out] file If non-null, the file.
2224 * @param findme The findme.
2225 * @param container The container.
2226 * @param [out] result If non-null, the result.
2227 *
2228 * @return The found list.
2229 */
2230
find_first_list(std::uint32_t findme,const avi_chunk * container,avi_chunk & result)2231 avi_file::error avi_file_impl::find_first_list(std::uint32_t findme, const avi_chunk *container, avi_chunk &result)
2232 {
2233 error avierr;
2234
2235 for (avierr = find_first_chunk(CHUNKTYPE_LIST, container, result); avierr == error::NONE; avierr = find_next_chunk(CHUNKTYPE_LIST, container, result))
2236 if (result.listtype == findme)
2237 return error::NONE;
2238
2239 return avierr;
2240 }
2241
2242
2243 /*-------------------------------------------------
2244 find_next_list - get information about the
2245 next list of a particular type in a container
2246 -------------------------------------------------*/
2247
2248 /**
2249 * @fn static avi_error find_next_list(avi_file *file, std::uint32_t findme, const avi_chunk *container, avi_chunk *result)
2250 *
2251 * @brief Searches for the next list.
2252 *
2253 * @param [in,out] file If non-null, the file.
2254 * @param findme The findme.
2255 * @param container The container.
2256 * @param [out] result If non-null, the result.
2257 *
2258 * @return The found list.
2259 */
2260
find_next_list(std::uint32_t findme,const avi_chunk * container,avi_chunk & result)2261 avi_file::error avi_file_impl::find_next_list(std::uint32_t findme, const avi_chunk *container, avi_chunk &result)
2262 {
2263 error avierr;
2264
2265 for (avierr = find_next_chunk(CHUNKTYPE_LIST, container, result); avierr == error::NONE; avierr = find_next_chunk(CHUNKTYPE_LIST, container, result))
2266 if (result.listtype == findme)
2267 return error::NONE;
2268
2269 return avierr;
2270 }
2271
2272
2273 /*-------------------------------------------------
2274 get_next_chunk_internal - fetch the next
2275 chunk relative to the current one
2276 -------------------------------------------------*/
2277
2278 /**
2279 * @fn static avi_error get_next_chunk_internal(avi_file *file, const avi_chunk *parent, avi_chunk *newchunk, std::uint64_t offset)
2280 *
2281 * @brief Gets the next chunk internal.
2282 *
2283 * @param [in,out] file If non-null, the file.
2284 * @param parent The parent.
2285 * @param [in,out] newchunk If non-null, the newchunk.
2286 * @param offset The offset.
2287 *
2288 * @return The next chunk internal.
2289 */
2290
get_next_chunk_internal(const avi_chunk * parent,avi_chunk & newchunk,std::uint64_t offset)2291 avi_file::error avi_file_impl::get_next_chunk_internal(const avi_chunk *parent, avi_chunk &newchunk, std::uint64_t offset)
2292 {
2293 osd_file::error filerr;
2294 std::uint8_t buffer[12];
2295 std::uint32_t bytesread;
2296
2297 /* nullptr parent implies the root */
2298 if (parent == nullptr)
2299 parent = &m_rootchunk;
2300
2301 /* start at the current offset */
2302 newchunk.offset = offset;
2303
2304 /* if we're past the bounds of the parent, bail */
2305 if (newchunk.offset + 8 >= parent->offset + 8 + parent->size)
2306 return error::END;
2307
2308 /* read the header */
2309 filerr = m_file->read(buffer, newchunk.offset, 8, bytesread);
2310 if (filerr != osd_file::error::NONE || bytesread != 8)
2311 return error::INVALID_DATA;
2312
2313 /* fill in the new chunk */
2314 newchunk.type = fetch_32bits(&buffer[0]);
2315 newchunk.size = fetch_32bits(&buffer[4]);
2316
2317 /* if we are a list, fetch the list type */
2318 if (newchunk.type == CHUNKTYPE_LIST || newchunk.type == CHUNKTYPE_RIFF)
2319 {
2320 filerr = m_file->read(&buffer[8], newchunk.offset + 8, 4, bytesread);
2321 if (filerr != osd_file::error::NONE || bytesread != 4)
2322 return error::INVALID_DATA;
2323 newchunk.listtype = fetch_32bits(&buffer[8]);
2324 }
2325
2326 return error::NONE;
2327 }
2328
2329
2330 /*-------------------------------------------------
2331 read_movie_data - get data about a movie
2332 -------------------------------------------------*/
2333
2334 /**
2335 * @fn static avi_error read_movie_data(avi_file *file)
2336 *
2337 * @brief Reads movie data.
2338 *
2339 * @param [in,out] file If non-null, the file.
2340 *
2341 * @return The movie data.
2342 */
2343
read_movie_data()2344 avi_file::error avi_file_impl::read_movie_data()
2345 {
2346 avi_chunk riff, hdrl, avih, strl, strh, strf, indx, movi, idx1;
2347 error avierr;
2348
2349 /* find the RIFF chunk */
2350 avierr = find_first_chunk(CHUNKTYPE_RIFF, nullptr, riff);
2351 if (avierr != error::NONE)
2352 return avierr;
2353
2354 /* verify that the RIFF type is AVI */
2355 if (riff.listtype != LISTTYPE_AVI)
2356 {
2357 avierr = error::INVALID_DATA;
2358 return avierr;
2359 }
2360
2361 /* find the hdrl LIST chunk within the RIFF */
2362 avierr = find_first_list(LISTTYPE_HDRL, &riff, hdrl);
2363 if (avierr != error::NONE)
2364 return avierr;
2365
2366 /* find the avih chunk */
2367 avierr = find_first_chunk(CHUNKTYPE_AVIH, &hdrl, avih);
2368 if (avierr != error::NONE)
2369 return avierr;
2370
2371 /* parse the avih chunk */
2372 avierr = parse_avih_chunk(avih);
2373 if (avierr != error::NONE)
2374 return avierr;
2375
2376 /* loop over strl LIST chunks */
2377 int strindex = 0;
2378 for (avierr = find_first_list(LISTTYPE_STRL, &hdrl, strl); avierr == error::NONE; avierr = find_next_list(LISTTYPE_STRL, &hdrl, strl))
2379 {
2380 /* if we have too many, it's a bad file */
2381 if (strindex >= m_streams.size())
2382 return avierr;
2383
2384 /* find the strh chunk */
2385 avierr = find_first_chunk(CHUNKTYPE_STRH, &strl, strh);
2386 if (avierr != error::NONE)
2387 return avierr;
2388
2389 /* parse the data */
2390 avierr = parse_strh_chunk(m_streams[strindex], strh);
2391 if (avierr != error::NONE)
2392 return avierr;
2393
2394 /* find the strf chunk */
2395 avierr = find_first_chunk(CHUNKTYPE_STRF, &strl, strf);
2396 if (avierr != error::NONE)
2397 return avierr;
2398
2399 /* parse the data */
2400 avierr = parse_strf_chunk(m_streams[strindex], strf);
2401 if (avierr != error::NONE)
2402 return avierr;
2403
2404 /* find the indx chunk, if present */
2405 avierr = find_first_chunk(CHUNKTYPE_INDX, &strl, indx);
2406 if (avierr == error::NONE)
2407 avierr = parse_indx_chunk(m_streams[strindex], indx);
2408
2409 /* next stream */
2410 strindex++;
2411 }
2412
2413 /* normalize the error after parsing the stream headers */
2414 if (avierr == error::END)
2415 avierr = error::NONE;
2416 if (avierr != error::NONE)
2417 return avierr;
2418
2419 /* find the base of the movi data */
2420 avierr = find_first_list(LISTTYPE_MOVI, &riff, movi);
2421 if (avierr != error::NONE)
2422 return avierr;
2423
2424 /* find and parse the idx1 chunk within the RIFF (if present) */
2425 avierr = find_first_chunk(CHUNKTYPE_IDX1, &riff, idx1);
2426 if (avierr == error::NONE)
2427 avierr = parse_idx1_chunk(movi.offset + 8, idx1);
2428 if (avierr != error::NONE)
2429 return avierr;
2430
2431 /* now extract the movie info */
2432 avierr = extract_movie_info();
2433 return avierr;
2434 }
2435
2436
2437 /*-------------------------------------------------
2438 extract_movie_info - extract the movie info
2439 from the streams we've read
2440 -------------------------------------------------*/
2441
2442 /**
2443 * @fn static avi_error extract_movie_info(avi_file *file)
2444 *
2445 * @brief Extracts the movie information described by file.
2446 *
2447 * @param [in,out] file If non-null, the file.
2448 *
2449 * @return The extracted movie information.
2450 */
2451
extract_movie_info()2452 avi_file::error avi_file_impl::extract_movie_info()
2453 {
2454 avi_stream *stream;
2455 int offset;
2456
2457 /* get the video stream */
2458 stream = get_video_stream();
2459 if (stream != nullptr)
2460 {
2461 /* fill in the info */
2462 m_info.video_format = stream->format();
2463 m_info.video_timescale = stream->rate();
2464 m_info.video_sampletime = stream->scale();
2465 m_info.video_numsamples = stream->samples();
2466 m_info.video_width = stream->width();
2467 m_info.video_height = stream->height();
2468 }
2469
2470 /* get the first audio stream */
2471 stream = get_audio_stream(0, offset);
2472 if (stream != nullptr)
2473 {
2474 /* fill in the info */
2475 m_info.audio_format = stream->format();
2476 m_info.audio_timescale = stream->rate();
2477 m_info.audio_sampletime = stream->scale();
2478 m_info.audio_numsamples = stream->samples();
2479 m_info.audio_channels = 1;
2480 m_info.audio_samplebits = stream->samplebits();
2481 m_info.audio_samplerate = stream->samplerate();
2482 }
2483
2484 /* now make sure all other audio streams are valid */
2485 while (nullptr != (stream = get_audio_stream(m_info.audio_channels, offset)))
2486 {
2487 /* get the stream info */
2488 m_info.audio_channels++;
2489
2490 /* verify compatibility */
2491 if (m_info.audio_format != stream->format() ||
2492 m_info.audio_timescale != stream->rate() ||
2493 m_info.audio_sampletime != stream->scale() ||
2494 m_info.audio_numsamples != stream->samples() ||
2495 m_info.audio_samplebits != stream->samplebits() ||
2496 m_info.audio_samplerate != stream->samplerate())
2497 return error::INCOMPATIBLE_AUDIO_STREAMS;
2498 }
2499
2500 return error::NONE;
2501 }
2502
2503
2504 /*-------------------------------------------------
2505 parse_avih_chunk - parse an avih header
2506 chunk
2507 -------------------------------------------------*/
2508
2509 /**
2510 * @fn static avi_error parse_avih_chunk(avi_file *file, avi_chunk *avih)
2511 *
2512 * @brief Parse avih chunk.
2513 *
2514 * @param [in,out] file If non-null, the file.
2515 * @param [in,out] avih If non-null, the avih.
2516 *
2517 * @return An avi_error.
2518 */
2519
parse_avih_chunk(avi_chunk const & avih)2520 avi_file::error avi_file_impl::parse_avih_chunk(avi_chunk const &avih)
2521 {
2522 /* read the data */
2523 std::unique_ptr<std::uint8_t []> chunkdata;
2524 error avierr = read_chunk_data(avih, chunkdata);
2525 if (avierr != error::NONE)
2526 return avierr;
2527
2528 /* extract the data */
2529 std::uint32_t const streams = fetch_32bits(&chunkdata[24]);
2530
2531 /* allocate memory for the streams */
2532 m_streams.clear();
2533 m_streams.resize(streams);
2534
2535 return avierr;
2536 }
2537
2538
2539 /*-------------------------------------------------
2540 parse_strh_chunk - parse a strh header
2541 chunk
2542 -------------------------------------------------*/
2543
2544 /**
2545 * @fn static avi_error parse_strh_chunk(avi_file *file, avi_stream *stream, avi_chunk *strh)
2546 *
2547 * @brief Parse strh chunk.
2548 *
2549 * @param [in,out] file If non-null, the file.
2550 * @param [in,out] stream If non-null, the stream.
2551 * @param [in,out] strh If non-null, the strh.
2552 *
2553 * @return An avi_error.
2554 */
2555
parse_strh_chunk(avi_stream & stream,avi_chunk const & strh)2556 avi_file::error avi_file_impl::parse_strh_chunk(avi_stream &stream, avi_chunk const &strh)
2557 {
2558 /* read the data */
2559 std::unique_ptr<std::uint8_t []> chunkdata;
2560 error const avierr = read_chunk_data(strh, chunkdata);
2561 if (avierr != error::NONE)
2562 return avierr;
2563
2564 /* extract the data */
2565 return stream.set_strh_data(&chunkdata[0], strh.size);
2566 }
2567
2568
2569 /*-------------------------------------------------
2570 parse_strf_chunk - parse a strf header
2571 chunk
2572 -------------------------------------------------*/
2573
2574 /**
2575 * @fn static avi_error parse_strf_chunk(avi_file *file, avi_stream *stream, avi_chunk *strf)
2576 *
2577 * @brief Parse strf chunk.
2578 *
2579 * @param [in,out] file If non-null, the file.
2580 * @param [in,out] stream If non-null, the stream.
2581 * @param [in,out] strf If non-null, the strf.
2582 *
2583 * @return An avi_error.
2584 */
2585
parse_strf_chunk(avi_stream & stream,avi_chunk const & strf)2586 avi_file::error avi_file_impl::parse_strf_chunk(avi_stream &stream, avi_chunk const &strf)
2587 {
2588 error avierr;
2589
2590 /* read the data */
2591 std::unique_ptr<std::uint8_t []> chunkdata;
2592 avierr = read_chunk_data(strf, chunkdata);
2593 if (avierr != error::NONE)
2594 return avierr;
2595
2596 return stream.set_strf_data(&chunkdata[0], strf.size);
2597 }
2598
2599
2600 /*-------------------------------------------------
2601 parse_indx_chunk - parse an indx chunk
2602 -------------------------------------------------*/
2603
2604 /**
2605 * @fn static avi_error parse_indx_chunk(avi_file *file, avi_stream *stream, avi_chunk *strf)
2606 *
2607 * @brief Parse indx chunk.
2608 *
2609 * @param [in,out] file If non-null, the file.
2610 * @param [in,out] stream If non-null, the stream.
2611 * @param [in,out] strf If non-null, the strf.
2612 *
2613 * @return An avi_error.
2614 */
2615
parse_indx_chunk(avi_stream & stream,avi_chunk const & strf)2616 avi_file::error avi_file_impl::parse_indx_chunk(avi_stream &stream, avi_chunk const &strf)
2617 {
2618 error avierr;
2619
2620 /* read the data */
2621 std::unique_ptr<std::uint8_t []> chunkdata;
2622 avierr = read_chunk_data(strf, chunkdata);
2623 if (avierr != error::NONE)
2624 return avierr;
2625
2626 /* extract the data */
2627 std::uint16_t const longs_per_entry = fetch_16bits(&chunkdata[0]);
2628 //subtype = chunkdata[2];
2629 std::uint8_t const type = chunkdata[3];
2630 std::uint32_t const entries = fetch_32bits(&chunkdata[4]);
2631 //id = fetch_32bits(&chunkdata[8]);
2632 std::uint64_t const baseoffset = fetch_64bits(&chunkdata[12]);
2633
2634 /* if this is a superindex, loop over entries and call ourselves recursively */
2635 if (type == AVI_INDEX_OF_INDEXES)
2636 {
2637 /* validate the size of each entry */
2638 if (longs_per_entry != 4)
2639 return error::INVALID_DATA;
2640
2641 /* loop over entries and create subchunks for each */
2642 for (std::uint32_t entry = 0; entry < entries; entry++)
2643 {
2644 const std::uint8_t *base = &chunkdata[24 + entry * 16];
2645 osd_file::error filerr;
2646 avi_chunk subchunk;
2647 std::uint32_t bytes_read;
2648 std::uint8_t buffer[8];
2649
2650 /* go read the subchunk */
2651 subchunk.offset = fetch_64bits(&base[0]);
2652 filerr = m_file->read(buffer, subchunk.offset, sizeof(buffer), bytes_read);
2653 if (filerr != osd_file::error::NONE || bytes_read != sizeof(buffer))
2654 {
2655 avierr = error::READ_ERROR;
2656 break;
2657 }
2658
2659 /* parse the data */
2660 subchunk.type = fetch_32bits(&buffer[0]);
2661 subchunk.size = fetch_32bits(&buffer[4]);
2662
2663 /* recursively parse each referenced chunk; stop if we hit an error */
2664 avierr = parse_indx_chunk(stream, subchunk);
2665 if (avierr != error::NONE)
2666 break;
2667 }
2668 }
2669
2670 /* otherwise, this is a standard index */
2671 else if (type == AVI_INDEX_OF_CHUNKS)
2672 {
2673 /* validate the size of each entry */
2674 if (longs_per_entry != 2 && longs_per_entry != 3)
2675 return error::INVALID_DATA;
2676
2677 /* loop over entries and parse out the data */
2678 for (std::uint32_t entry = 0; entry < entries; entry++)
2679 {
2680 const std::uint8_t *base = &chunkdata[24 + entry * 4 * longs_per_entry];
2681 std::uint32_t offset = fetch_32bits(&base[0]);
2682 std::uint32_t size = fetch_32bits(&base[4]) & 0x7fffffff; // bit 31 == NOT a keyframe
2683
2684 /* set the info for this chunk and advance */
2685 avierr = stream.set_chunk_info(stream.chunks(), baseoffset + offset - 8, size + 8);
2686 if (avierr != error::NONE)
2687 break;
2688 }
2689 }
2690
2691 return avierr;
2692 }
2693
2694
2695 /*-------------------------------------------------
2696 parse_idx1_chunk - parse an idx1 chunk
2697 -------------------------------------------------*/
2698
2699 /**
2700 * @fn static avi_error parse_idx1_chunk(avi_file *file, std::uint64_t baseoffset, avi_chunk *idx1)
2701 *
2702 * @brief Parse index 1 chunk.
2703 *
2704 * @param [in,out] file If non-null, the file.
2705 * @param baseoffset The baseoffset.
2706 * @param [in,out] idx1 If non-null, the first index.
2707 *
2708 * @return An avi_error.
2709 */
2710
parse_idx1_chunk(std::uint64_t baseoffset,avi_chunk const & idx1)2711 avi_file::error avi_file_impl::parse_idx1_chunk(std::uint64_t baseoffset, avi_chunk const &idx1)
2712 {
2713 error avierr;
2714
2715 /* read the data */
2716 std::unique_ptr<std::uint8_t []> chunkdata;
2717 avierr = read_chunk_data(idx1, chunkdata);
2718 if (avierr != error::NONE)
2719 return avierr;
2720
2721 /* loop over entries */
2722 std::uint32_t const entries = idx1.size / 16;
2723 for (std::uint32_t entry = 0; entry < entries; entry++)
2724 {
2725 std::uint8_t const *const base = &chunkdata[entry * 16];
2726 std::uint32_t const chunkid = fetch_32bits(&base[0]);
2727 std::uint32_t const offset = fetch_32bits(&base[8]);
2728 std::uint32_t const size = fetch_32bits(&base[12]);
2729 int streamnum;
2730
2731 /* determine the stream index */
2732 streamnum = ((chunkid >> 8) & 0xff) - '0';
2733 streamnum += 10 * ((chunkid & 0xff) - '0');
2734 if (streamnum >= m_streams.size())
2735 return error::INVALID_DATA;
2736 avi_stream &stream = m_streams[streamnum];
2737
2738 /* set the appropriate entry */
2739 avierr = stream.set_chunk_info(stream.chunks(), baseoffset + offset, size + 8);
2740 if (avierr != error::NONE)
2741 return avierr;
2742 }
2743
2744 return avierr;
2745 }
2746
2747
2748 /*-------------------------------------------------
2749 chunk_open - open a new chunk for writing
2750 -------------------------------------------------*/
2751
2752 /**
2753 * @fn static avi_error chunk_open(avi_file *file, std::uint32_t type, std::uint32_t listtype, std::uint32_t estlength)
2754 *
2755 * @brief Queries if a given chunk open.
2756 *
2757 * @param [in,out] file If non-null, the file.
2758 * @param type The type.
2759 * @param listtype The listtype.
2760 * @param estlength The estlength.
2761 *
2762 * @return An avi_error.
2763 */
2764
chunk_open(std::uint32_t type,std::uint32_t listtype,std::uint32_t estlength)2765 avi_file::error avi_file_impl::chunk_open(std::uint32_t type, std::uint32_t listtype, std::uint32_t estlength)
2766 {
2767 /* if we're out of stack entries, bail */
2768 if (m_chunksp >= m_chunkstack.size())
2769 return error::STACK_TOO_DEEP;
2770 avi_chunk &chunk = m_chunkstack[m_chunksp++];
2771
2772 /* set up the chunk information */
2773 chunk.offset = m_writeoffs;
2774 chunk.size = estlength;
2775 chunk.type = type;
2776 chunk.listtype = listtype;
2777
2778 /* non-list types */
2779 if (type != CHUNKTYPE_RIFF && type != CHUNKTYPE_LIST)
2780 {
2781 std::uint8_t buffer[8];
2782
2783 /* populate the header */
2784 put_32bits(&buffer[0], chunk.type);
2785 put_32bits(&buffer[4], chunk.size);
2786
2787 /* write the header */
2788 std::uint32_t written;
2789 osd_file::error const filerr = m_file->write(buffer, m_writeoffs, sizeof(buffer), written);
2790 if (filerr != osd_file::error::NONE || written != sizeof(buffer))
2791 return error::WRITE_ERROR;
2792 m_writeoffs += written;
2793 }
2794
2795 /* list types */
2796 else
2797 {
2798 std::uint8_t buffer[12];
2799
2800 /* populate the header */
2801 put_32bits(&buffer[0], chunk.type);
2802 put_32bits(&buffer[4], chunk.size);
2803 put_32bits(&buffer[8], chunk.listtype);
2804
2805 /* write the header */
2806 std::uint32_t written;
2807 osd_file::error const filerr = m_file->write(buffer, m_writeoffs, sizeof(buffer), written);
2808 if (filerr != osd_file::error::NONE || written != sizeof(buffer))
2809 return error::WRITE_ERROR;
2810 m_writeoffs += written;
2811 }
2812
2813 return error::NONE;
2814 }
2815
2816
2817 /*-------------------------------------------------
2818 chunk_close - finish writing an chunk
2819 -------------------------------------------------*/
2820
2821 /**
2822 * @fn static avi_error chunk_close(avi_file *file)
2823 *
2824 * @brief Chunk close.
2825 *
2826 * @param [in,out] file If non-null, the file.
2827 *
2828 * @return An avi_error.
2829 */
2830
chunk_close()2831 avi_file::error avi_file_impl::chunk_close()
2832 {
2833 avi_chunk const &chunk = m_chunkstack[--m_chunksp];
2834 std::uint64_t const chunksize = m_writeoffs - (chunk.offset + 8);
2835
2836 /* error if we don't fit into 32 bits */
2837 if (chunksize != std::uint32_t(chunksize))
2838 return error::INVALID_DATA;
2839
2840 /* write the final size if it is different from the guess */
2841 if (chunk.size != chunksize)
2842 {
2843 std::uint8_t buffer[4];
2844
2845 put_32bits(&buffer[0], std::uint32_t(chunksize));
2846 std::uint32_t written;
2847 osd_file::error const filerr = m_file->write(buffer, chunk.offset + 4, sizeof(buffer), written);
2848 if (filerr != osd_file::error::NONE || written != sizeof(buffer))
2849 return error::WRITE_ERROR;
2850 }
2851
2852 /* round up to the next word */
2853 m_writeoffs += chunksize & 1;
2854
2855 return error::NONE;
2856 }
2857
2858
2859 /*-------------------------------------------------
2860 chunk_write - write an chunk and its data
2861 -------------------------------------------------*/
2862
2863 /**
2864 * @fn static avi_error chunk_write(avi_file *file, std::uint32_t type, const void *data, std::uint32_t length)
2865 *
2866 * @brief Chunk write.
2867 *
2868 * @param [in,out] file If non-null, the file.
2869 * @param type The type.
2870 * @param data The data.
2871 * @param length The length.
2872 *
2873 * @return An avi_error.
2874 */
2875
chunk_write(std::uint32_t type,const void * data,std::uint32_t length)2876 avi_file::error avi_file_impl::chunk_write(std::uint32_t type, const void *data, std::uint32_t length)
2877 {
2878 error avierr;
2879
2880 /* if we are the first RIFF, we must reserve enough space for the IDX chunk */
2881 std::uint32_t const idxreserve = (m_riffbase == 0 && type != CHUNKTYPE_IDX1) ? compute_idx1_size() : 0;
2882
2883 /* if we are getting too big, split the RIFF */
2884 /* note that we ignore writes before the current RIFF base, as those are assumed to be
2885 overwrites of a chunk from the previous RIFF */
2886 if ((m_writeoffs >= m_riffbase) && ((m_writeoffs + length + idxreserve - m_riffbase) >= MAX_RIFF_SIZE))
2887 {
2888 /* close the movi list */
2889 avierr = chunk_close();
2890 if (avierr != error::NONE)
2891 return avierr;
2892
2893 /* write the idx1 chunk if this is the first */
2894 if (m_riffbase == 0)
2895 {
2896 avierr = write_idx1_chunk();
2897 if (avierr != error::NONE)
2898 return avierr;
2899 }
2900
2901 /* close the RIFF */
2902 avierr = chunk_close();
2903 if (avierr != error::NONE)
2904 return avierr;
2905
2906 /* open a new RIFF */
2907 m_riffbase = m_writeoffs;
2908 avierr = chunk_open(CHUNKTYPE_RIFF, LISTTYPE_AVIX, 0);
2909 if (avierr != error::NONE)
2910 return avierr;
2911
2912 /* open a nested movi list */
2913 m_saved_movi_offset = m_writeoffs;
2914 avierr = chunk_open(CHUNKTYPE_LIST, LISTTYPE_MOVI, 0);
2915 if (avierr != error::NONE)
2916 return avierr;
2917 }
2918
2919 /* open the chunk */
2920 avierr = chunk_open(type, 0, length);
2921 if (avierr != error::NONE)
2922 return avierr;
2923
2924 /* write the data */
2925 std::uint32_t written;
2926 osd_file::error const filerr = m_file->write(data, m_writeoffs, length, written);
2927 if (filerr != osd_file::error::NONE || written != length)
2928 return error::WRITE_ERROR;
2929 m_writeoffs += written;
2930
2931 /* close the chunk */
2932 return chunk_close();
2933 }
2934
2935
2936 /*-------------------------------------------------
2937 chunk_overwrite - write a chunk in two passes;
2938 first pass writes to the end of file and
2939 saves the offset; second pass overwrites the
2940 original
2941 -------------------------------------------------*/
2942
2943 /**
2944 * @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)
2945 *
2946 * @brief Chunk overwrite.
2947 *
2948 * @param [in,out] file If non-null, the file.
2949 * @param type The type.
2950 * @param data The data.
2951 * @param length The length.
2952 * @param [in,out] offset If non-null, the offset.
2953 * @param initial_write The initial write.
2954 *
2955 * @return An avi_error.
2956 */
2957
chunk_overwrite(std::uint32_t type,const void * data,std::uint32_t length,std::uint64_t & offset,bool initial_write)2958 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)
2959 {
2960 std::uint64_t savedoffset = 0;
2961
2962 /* if this is our initial write, save the offset */
2963 if (initial_write)
2964 offset = m_writeoffs;
2965
2966 /* otherwise, remember the current write offset and replace it with the original */
2967 else
2968 {
2969 savedoffset = m_writeoffs;
2970 m_writeoffs = offset;
2971 }
2972
2973 /* write the chunk */
2974 error const avierr = chunk_write(type, data, length);
2975
2976 /* if this isn't the initial write, restore the previous offset */
2977 if (!initial_write)
2978 m_writeoffs = savedoffset;
2979
2980 return avierr;
2981 }
2982
2983
2984 /*-------------------------------------------------
2985 write_initial_headers - write out the initial
2986 set of AVI and stream headers
2987 -------------------------------------------------*/
2988
2989 /**
2990 * @fn static avi_error write_initial_headers(avi_file *file)
2991 *
2992 * @brief Writes an initial headers.
2993 *
2994 * @param [in,out] file If non-null, the file.
2995 *
2996 * @return An avi_error.
2997 */
2998
write_initial_headers()2999 avi_file::error avi_file_impl::write_initial_headers()
3000 {
3001 error avierr;
3002
3003 /* reset the write pointer */
3004 m_writeoffs = 0;
3005
3006 /* open a RIFF chunk */
3007 avierr = chunk_open(CHUNKTYPE_RIFF, LISTTYPE_AVI, 0);
3008 if (avierr != error::NONE)
3009 return avierr;
3010
3011 /* open a hdlr LIST */
3012 avierr = chunk_open(CHUNKTYPE_LIST, LISTTYPE_HDRL, 0);
3013 if (avierr != error::NONE)
3014 return avierr;
3015
3016 /* write an avih chunk */
3017 avierr = write_avih_chunk(true);
3018 if (avierr != error::NONE)
3019 return avierr;
3020
3021 /* for each stream, write a strl LIST */
3022 for (int strnum = 0; strnum < m_streams.size(); strnum++)
3023 {
3024 /* open a strl LIST */
3025 avierr = chunk_open(CHUNKTYPE_LIST, LISTTYPE_STRL, 0);
3026 if (avierr != error::NONE)
3027 return avierr;
3028
3029 /* write the strh chunk */
3030 avierr = write_strh_chunk(m_streams[strnum], true);
3031 if (avierr != error::NONE)
3032 return avierr;
3033
3034 /* write the strf chunk */
3035 avierr = write_strf_chunk(m_streams[strnum]);
3036 if (avierr != error::NONE)
3037 return avierr;
3038
3039 /* write the indx chunk */
3040 avierr = write_indx_chunk(m_streams[strnum], true);
3041 if (avierr != error::NONE)
3042 return avierr;
3043
3044 /* close the strl LIST */
3045 avierr = chunk_close();
3046 if (avierr != error::NONE)
3047 return avierr;
3048 }
3049
3050 /* close the hdlr LIST */
3051 avierr = chunk_close();
3052 if (avierr != error::NONE)
3053 return avierr;
3054
3055 /* open a movi LIST */
3056 m_saved_movi_offset = m_writeoffs;
3057 avierr = chunk_open(CHUNKTYPE_LIST, LISTTYPE_MOVI, 0);
3058 if (avierr != error::NONE)
3059 return avierr;
3060
3061 return avierr;
3062 }
3063
3064
3065 /*-------------------------------------------------
3066 write_avih_chunk - write the avih header
3067 chunk
3068 -------------------------------------------------*/
3069
3070 /**
3071 * @fn static avi_error write_avih_chunk(avi_file *file, int initial_write)
3072 *
3073 * @brief Writes an avih chunk.
3074 *
3075 * @param [in,out] file If non-null, the file.
3076 * @param initial_write The initial write.
3077 *
3078 * @return An avi_error.
3079 */
3080
write_avih_chunk(bool initial_write)3081 avi_file::error avi_file_impl::write_avih_chunk(bool initial_write)
3082 {
3083 avi_stream *video = get_video_stream();
3084 std::uint8_t buffer[56];
3085
3086 /* reset the buffer */
3087 std::memset(buffer, 0, sizeof(buffer));
3088
3089 put_32bits(&buffer[0], 1000000 * std::int64_t(video->scale()) / video->rate()); /* dwMicroSecPerFrame */
3090 put_32bits(&buffer[12], AVIF_HASINDEX | AVIF_ISINTERLEAVED); /* dwFlags */
3091 put_32bits(&buffer[16], video->samples()); /* dwTotalFrames */
3092 put_32bits(&buffer[24], m_streams.size()); /* dwStreams */
3093 put_32bits(&buffer[32], video->width()); /* dwWidth */
3094 put_32bits(&buffer[36], video->height()); /* dwHeight */
3095
3096 /* (over)write the chunk */
3097 return chunk_overwrite(CHUNKTYPE_AVIH, buffer, sizeof(buffer), m_saved_avih_offset, initial_write);
3098 }
3099
3100
3101 /*-------------------------------------------------
3102 write_strh_chunk - write the strh header
3103 chunk
3104 -------------------------------------------------*/
3105
3106 /**
3107 * @fn static avi_error write_strh_chunk(avi_file *file, avi_stream *stream, int initial_write)
3108 *
3109 * @brief Writes a strh chunk.
3110 *
3111 * @param [in,out] file If non-null, the file.
3112 * @param [in,out] stream If non-null, the stream.
3113 * @param initial_write The initial write.
3114 *
3115 * @return An avi_error.
3116 */
3117
write_strh_chunk(avi_stream & stream,bool initial_write)3118 avi_file::error avi_file_impl::write_strh_chunk(avi_stream &stream, bool initial_write)
3119 {
3120 std::uint8_t buffer[56];
3121
3122 /* reset the buffer */
3123 std::memset(buffer, 0, sizeof(buffer));
3124
3125 put_32bits(&buffer[0], stream.type()); /* fccType */
3126 put_32bits(&buffer[20], stream.scale()); /* dwScale */
3127 put_32bits(&buffer[24], stream.rate()); /* dwRate */
3128 put_32bits(&buffer[32], stream.samples()); /* dwLength */
3129 put_32bits(&buffer[40], 10000); /* dwQuality */
3130
3131 /* video-stream specific data */
3132 if (stream.type() == STREAMTYPE_VIDS)
3133 {
3134 put_32bits(&buffer[4], /* fccHandler */
3135 (stream.format() == FORMAT_HFYU) ? HANDLER_HFYU : HANDLER_DIB);
3136 put_32bits(&buffer[36], /* dwSuggestedBufferSize */
3137 stream.width() * stream.height() * 4);
3138 put_16bits(&buffer[52], stream.width()); /* rcFrame.right */
3139 put_16bits(&buffer[54], stream.height()); /* rcFrame.bottom */
3140 }
3141
3142 /* audio-stream specific data */
3143 if (stream.type() == STREAMTYPE_AUDS)
3144 {
3145 put_32bits(&buffer[36], /* dwSuggestedBufferSize */
3146 stream.samplerate() * stream.bytes_per_sample());
3147 put_32bits(&buffer[44], /* dwSampleSize */
3148 stream.bytes_per_sample());
3149 }
3150
3151 /* write the chunk */
3152 return chunk_overwrite(CHUNKTYPE_STRH, buffer, sizeof(buffer), stream.saved_strh_offset(), initial_write);
3153 }
3154
3155
3156 /*-------------------------------------------------
3157 write_strf_chunk - write the strf header
3158 chunk
3159 -------------------------------------------------*/
3160
3161 /**
3162 * @fn static avi_error write_strf_chunk(avi_file *file, avi_stream *stream)
3163 *
3164 * @brief Writes a strf chunk.
3165 *
3166 * @param [in,out] file If non-null, the file.
3167 * @param [in,out] stream If non-null, the stream.
3168 *
3169 * @return An avi_error.
3170 */
3171
write_strf_chunk(avi_stream const & stream)3172 avi_file::error avi_file_impl::write_strf_chunk(avi_stream const &stream)
3173 {
3174 /* video stream */
3175 if (stream.type() == STREAMTYPE_VIDS)
3176 {
3177 std::uint8_t buffer[40];
3178
3179 /* reset the buffer */
3180 std::memset(buffer, 0, sizeof(buffer));
3181
3182 put_32bits(&buffer[0], sizeof(buffer)); /* biSize */
3183 put_32bits(&buffer[4], stream.width()); /* biWidth */
3184 put_32bits(&buffer[8], stream.height()); /* biHeight */
3185 put_16bits(&buffer[12], 1); /* biPlanes */
3186 put_16bits(&buffer[14], stream.depth()); /* biBitCount */
3187 put_32bits(&buffer[16], stream.format()); /* biCompression */
3188 put_32bits(&buffer[20], /* biSizeImage */
3189 stream.width() * stream.height() * (stream.depth() + 7) / 8);
3190
3191 /* write the chunk */
3192 return chunk_write(CHUNKTYPE_STRF, buffer, sizeof(buffer));
3193 }
3194
3195 /* audio stream */
3196 if (stream.type() == STREAMTYPE_AUDS)
3197 {
3198 std::uint8_t buffer[16];
3199
3200 /* reset the buffer */
3201 std::memset(buffer, 0, sizeof(buffer));
3202
3203 put_16bits(&buffer[0], 1); /* wFormatTag */
3204 put_16bits(&buffer[2], stream.channels()); /* nChannels */
3205 put_32bits(&buffer[4], stream.samplerate()); /* nSamplesPerSec */
3206 put_32bits(&buffer[8], /* nAvgBytesPerSec */
3207 stream.samplerate() * stream.bytes_per_sample());
3208 put_16bits(&buffer[12], /* nBlockAlign */
3209 stream.bytes_per_sample());
3210 put_16bits(&buffer[14], stream.samplebits()); /* wBitsPerSample */
3211
3212 /* write the chunk */
3213 return chunk_write(CHUNKTYPE_STRF, buffer, sizeof(buffer));
3214 }
3215
3216 return error::INVALID_DATA;
3217 }
3218
3219
3220 /*-------------------------------------------------
3221 write_indx_chunk - write the indx header
3222 chunk
3223 -------------------------------------------------*/
3224
3225 /**
3226 * @fn static avi_error write_indx_chunk(avi_file *file, avi_stream *stream, int initial_write)
3227 *
3228 * @brief Writes an indx chunk.
3229 *
3230 * @param [in,out] file If non-null, the file.
3231 * @param [in,out] stream If non-null, the stream.
3232 * @param initial_write The initial write.
3233 *
3234 * @return An avi_error.
3235 */
3236
write_indx_chunk(avi_stream & stream,bool initial_write)3237 avi_file::error avi_file_impl::write_indx_chunk(avi_stream &stream, bool initial_write)
3238 {
3239 std::uint8_t buffer[24 + 16 * MAX_AVI_SIZE_IN_GB / 4];
3240 std::uint32_t master_entries = 0;
3241
3242 /* reset the buffer */
3243 std::memset(buffer, 0, sizeof(buffer));
3244
3245 /* construct the chunk ID and index chunk ID */
3246 std::uint32_t const chunkid = get_chunkid_for_stream(&stream);
3247 std::uint32_t const indexchunkid = AVI_FOURCC('i', 'x', '0' + (&stream - &m_streams[0]) / 10, '0' + (&stream - &m_streams[0]) % 10);
3248
3249 /* loop over chunks of 4GB and write out indexes for them first */
3250 if (!initial_write && m_riffbase != 0)
3251 {
3252 for (std::uint64_t currentbase = 0; currentbase < m_writeoffs; currentbase += FOUR_GB)
3253 {
3254 std::uint64_t const currentend = currentbase + FOUR_GB;
3255 std::uint32_t chunks_this_index = 0;
3256 std::uint32_t bytes_this_index = 0;
3257
3258 /* count chunks that are in this region */
3259 for (std::uint32_t chunknum = 0; chunknum < stream.chunks(); chunknum++)
3260 if (stream.chunk(chunknum).offset >= currentbase && stream.chunk(chunknum).offset < currentend)
3261 chunks_this_index++;
3262
3263 /* if no chunks, skip */
3264 if (chunks_this_index == 0)
3265 continue;
3266
3267 if (master_entries >= MAX_AVI_SIZE_IN_GB / 4)
3268 return error::WRITE_ERROR;
3269
3270 /* allocate memory */
3271 std::unique_ptr<std::uint8_t []> tempbuf;
3272 try { tempbuf.reset(new std::uint8_t[24 + 8 * chunks_this_index]); }
3273 catch (...) { return error::NO_MEMORY; }
3274 std::memset(&tempbuf[0], 0, 24 + 8 * chunks_this_index);
3275
3276 /* make a regular index */
3277 put_16bits(&tempbuf[0], 2); /* wLongsPerEntry */
3278 tempbuf[2] = 0; /* bIndexSubType */
3279 tempbuf[3] = AVI_INDEX_OF_CHUNKS; /* bIndexType */
3280 put_32bits(&tempbuf[4], chunks_this_index); /* nEntriesInUse */
3281 put_32bits(&tempbuf[8], chunkid); /* dwChunkId */
3282 put_64bits(&tempbuf[12], currentbase); /* qwBaseOffset */
3283
3284 /* now fill in the indexes */
3285 chunks_this_index = 0;
3286 for (std::uint32_t chunknum = 0; chunknum < stream.chunks(); chunknum++)
3287 if (stream.chunk(chunknum).offset >= currentbase && stream.chunk(chunknum).offset < currentend)
3288 {
3289 put_32bits(&tempbuf[24 + 8 * chunks_this_index + 0], stream.chunk(chunknum).offset + 8 - currentbase);
3290 put_32bits(&tempbuf[24 + 8 * chunks_this_index + 4], stream.chunk(chunknum).length - 8);
3291 bytes_this_index += stream.chunk(chunknum).length;
3292 chunks_this_index++;
3293 }
3294
3295 /* write the offset of this index to the master table */
3296 put_64bits(&buffer[24 + 16 * master_entries + 0], m_writeoffs);
3297 put_32bits(&buffer[24 + 16 * master_entries + 8], 24 + 8 * chunks_this_index + 8);
3298 if (stream.type() == STREAMTYPE_VIDS)
3299 put_32bits(&buffer[24 + 16 * master_entries + 12], chunks_this_index);
3300 else if (stream.type() == STREAMTYPE_AUDS)
3301 put_32bits(&buffer[24 + 16 * master_entries + 12], bytes_this_index / stream.bytes_per_sample());
3302 master_entries++;
3303
3304 /* write the index */
3305 error const avierr = chunk_write(indexchunkid, &tempbuf[0], 24 + 8 * chunks_this_index);
3306 if (avierr != error::NONE)
3307 return avierr;
3308 }
3309 }
3310
3311 /* build up the master index */
3312 if (master_entries != 0)
3313 {
3314 put_16bits(&buffer[0], 4); /* wLongsPerEntry */
3315 buffer[2] = 0; /* bIndexSubType */
3316 buffer[3] = AVI_INDEX_OF_INDEXES; /* bIndexType */
3317 put_32bits(&buffer[4], master_entries); /* nEntriesInUse */
3318 put_32bits(&buffer[8], chunkid); /* dwChunkId */
3319 }
3320
3321 /* (over)write the chunk */
3322 return chunk_overwrite((master_entries == 0) ? CHUNKTYPE_JUNK : CHUNKTYPE_INDX, buffer, sizeof(buffer), stream.saved_indx_offset(), initial_write);
3323 }
3324
3325
3326 /*-------------------------------------------------
3327 write_idx1_chunk - write the idx1 chunk
3328 -------------------------------------------------*/
3329
3330 /**
3331 * @fn static avi_error write_idx1_chunk(avi_file *file)
3332 *
3333 * @brief Writes an index 1 chunk.
3334 *
3335 * @param [in,out] file If non-null, the file.
3336 *
3337 * @return An avi_error.
3338 */
3339
write_idx1_chunk()3340 avi_file::error avi_file_impl::write_idx1_chunk()
3341 {
3342 std::uint32_t const tempbuflength = compute_idx1_size() - 8;
3343 std::uint32_t curchunk[2] = { 0 };
3344
3345 /* allocate a temporary buffer */
3346 std::unique_ptr<std::uint8_t []> tempbuf;
3347 try { tempbuf.reset(new std::uint8_t[tempbuflength]); }
3348 catch (...) { return error::NO_MEMORY; }
3349
3350 /* fill it in */
3351 for (std::uint32_t curoffset = 0; curoffset < tempbuflength; curoffset += 16)
3352 {
3353 std::uint64_t minoffset = ~std::uint64_t(0);
3354 int minstr = 0;
3355
3356 /* determine which stream has the next chunk */
3357 for (int strnum = 0; strnum < m_streams.size(); strnum++)
3358 if (curchunk[strnum] < m_streams[strnum].chunks() && m_streams[strnum].chunk(curchunk[strnum]).offset < minoffset)
3359 {
3360 minoffset = m_streams[strnum].chunk(curchunk[strnum]).offset;
3361 minstr = strnum;
3362 }
3363
3364 /* make an entry for this index */
3365 put_32bits(&tempbuf[curoffset + 0], get_chunkid_for_stream(&m_streams[minstr]));
3366 put_32bits(&tempbuf[curoffset + 4], 0x0010 /* AVIIF_KEYFRAME */);
3367 put_32bits(&tempbuf[curoffset + 8], minoffset - (m_saved_movi_offset + 8));
3368 put_32bits(&tempbuf[curoffset + 12], m_streams[minstr].chunk(curchunk[minstr]).length - 8);
3369
3370 /* advance the chunk counter for this stream */
3371 curchunk[minstr]++;
3372 }
3373
3374 /* write the chunk */
3375 return chunk_write(CHUNKTYPE_IDX1, &tempbuf[0], tempbuflength);
3376 }
3377
3378
3379 /*-------------------------------------------------
3380 soundbuf_initialize - initialize the sound
3381 buffering system
3382 -------------------------------------------------*/
3383
3384 /**
3385 * @fn static avi_error soundbuf_initialize(avi_file *file)
3386 *
3387 * @brief Soundbuf initialize.
3388 *
3389 * @param [in,out] file If non-null, the file.
3390 *
3391 * @return An avi_error.
3392 */
3393
soundbuf_initialize()3394 avi_file::error avi_file_impl::soundbuf_initialize()
3395 {
3396 int offset;
3397 avi_stream *const audio = get_audio_stream(0, offset);
3398 avi_stream *const video = get_video_stream();
3399
3400 /* we require a video stream */
3401 if (video == nullptr)
3402 return error::UNSUPPORTED_VIDEO_FORMAT;
3403
3404 /* skip if no audio stream */
3405 if (audio == nullptr)
3406 return error::NONE;
3407
3408 /* determine the number of samples we want in our buffer; 2 seconds should be enough */
3409 m_soundbuf_samples = m_info.audio_samplerate * SOUND_BUFFER_MSEC / 1000;
3410
3411 /* allocate a buffer */
3412 m_soundbuf.clear();
3413 try { m_soundbuf.resize(m_soundbuf_samples * m_info.audio_channels, 0); }
3414 catch (...) { return error::NO_MEMORY; }
3415
3416 /* determine the number of frames to be ahead (0.75secs) */
3417 m_soundbuf_frames = (std::uint64_t(video->rate()) * 75) / (std::uint64_t(video->scale()) * 100) + 1;
3418 return error::NONE;
3419 }
3420
3421
3422 /*-------------------------------------------------
3423 soundbuf_write_chunk - write the initial
3424 chunk data
3425 -------------------------------------------------*/
3426
3427 /**
3428 * @fn static avi_error soundbuf_write_chunk(avi_file *file, std::uint32_t framenum)
3429 *
3430 * @brief Soundbuf write chunk.
3431 *
3432 * @param [in,out] file If non-null, the file.
3433 * @param framenum The framenum.
3434 *
3435 * @return An avi_error.
3436 */
3437
soundbuf_write_chunk(std::uint32_t framenum)3438 avi_file::error avi_file_impl::soundbuf_write_chunk(std::uint32_t framenum)
3439 {
3440 int offset;
3441 avi_stream *const stream = get_audio_stream(0, offset);
3442 std::uint32_t length;
3443
3444 /* skip if no audio stream */
3445 if (stream == nullptr)
3446 return error::NONE;
3447
3448 /* determine the length of this chunk */
3449 if (framenum == 0)
3450 length = framenum_to_samplenum(m_soundbuf_frames);
3451 else
3452 length = framenum_to_samplenum(framenum + 1 + m_soundbuf_frames) - framenum_to_samplenum(framenum + m_soundbuf_frames);
3453 length *= stream->channels() * sizeof(std::int16_t);
3454
3455 /* then do the initial write */
3456 error const avierr = chunk_write(get_chunkid_for_stream(stream), &m_soundbuf[0], length);
3457 if (avierr != error::NONE)
3458 return avierr;
3459
3460 /* set the info for this new chunk */
3461 return stream->set_chunk_info(stream->chunks(), m_writeoffs - length - 8, length + 8);
3462 }
3463
3464
3465 /*-------------------------------------------------
3466 soundbuf_flush - flush data from the sound
3467 buffers
3468 -------------------------------------------------*/
3469
3470 /**
3471 * @fn static avi_error soundbuf_flush(avi_file *file, int only_flush_full)
3472 *
3473 * @brief Soundbuf flush.
3474 *
3475 * @param [in,out] file If non-null, the file.
3476 * @param only_flush_full The only flush full.
3477 *
3478 * @return An avi_error.
3479 */
3480
soundbuf_flush(bool only_flush_full)3481 avi_file::error avi_file_impl::soundbuf_flush(bool only_flush_full)
3482 {
3483 int offset;
3484 avi_stream *const stream = get_audio_stream(0, offset);
3485 std::int32_t channelsamples = m_soundbuf_samples;
3486 std::int32_t processedsamples = 0;
3487 std::uint32_t finalchunks;
3488 error avierr;
3489 std::uint32_t chunknum;
3490 std::uint32_t chunkid;
3491
3492 /* skip if no stream */
3493 if (stream == nullptr)
3494 return error::NONE;
3495
3496 /* get the chunk ID for this stream */
3497 chunkid = get_chunkid_for_stream(stream);
3498 std::uint32_t const bytes_per_sample = stream->channels() * sizeof(std::int16_t);
3499 finalchunks = stream->chunks();
3500
3501 /* find out how many samples we've accumulated */
3502 for (int channel = 0; channel < stream->channels(); channel++)
3503 channelsamples = (std::min<std::int32_t>)(channelsamples, m_soundbuf_chansamples[channel]);
3504
3505 /* loop over pending sound chunks */
3506 for (chunknum = m_soundbuf_chunks; chunknum < stream->chunks(); chunknum++)
3507 {
3508 avi_chunk_list &chunk = stream->chunk(chunknum);
3509 std::uint32_t const chunksamples = (chunk.length - 8) / bytes_per_sample;
3510
3511 /* stop if we don't have enough to satisfy this chunk */
3512 if (only_flush_full && channelsamples < chunksamples)
3513 break;
3514
3515 /* if we don't have all the samples we need, pad with 0's */
3516 if (channelsamples > 0 && channelsamples < chunksamples)
3517 {
3518 if (processedsamples + chunksamples > m_soundbuf_samples)
3519 return error::EXCEEDED_SOUND_BUFFER;
3520 std::memset(&m_soundbuf[(processedsamples + channelsamples) * stream->channels()], 0, (chunksamples - channelsamples) * bytes_per_sample);
3521 }
3522
3523 /* if we're completely out of samples, clear the buffer entirely and use the end */
3524 else if (channelsamples <= 0)
3525 {
3526 processedsamples = m_soundbuf_samples - chunksamples;
3527 std::memset(&m_soundbuf[processedsamples * stream->channels()], 0, chunksamples * bytes_per_sample);
3528 chunkid = CHUNKTYPE_JUNK;
3529 finalchunks--;
3530 }
3531
3532 /* copy the sample data in */
3533 avierr = chunk_overwrite(chunkid, &m_soundbuf[processedsamples * stream->channels()], chunk.length - 8, chunk.offset, false);
3534 if (avierr != error::NONE)
3535 return avierr;
3536
3537 /* add up the samples */
3538 if (channelsamples > chunksamples)
3539 m_info.audio_numsamples = stream->add_samples(chunksamples);
3540 else if (channelsamples > 0)
3541 m_info.audio_numsamples = stream->add_samples(channelsamples);
3542
3543 /* advance past those */
3544 processedsamples += chunksamples;
3545 channelsamples -= chunksamples;
3546 channelsamples = (std::max)(0, channelsamples);
3547 }
3548
3549 /* if we have a non-zero offset, shift everything down */
3550 if (processedsamples > 0)
3551 {
3552 /* first account for the samples we processed */
3553 if (m_soundbuf_samples > processedsamples)
3554 std::memmove(&m_soundbuf[0], &m_soundbuf[processedsamples * stream->channels()], (m_soundbuf_samples - processedsamples) * bytes_per_sample);
3555 for (int channel = 0; channel < stream->channels(); channel++)
3556 m_soundbuf_chansamples[channel] -= processedsamples;
3557 }
3558
3559 /* update the final chunk count */
3560 if (!only_flush_full)
3561 stream->set_chunks(finalchunks);
3562
3563 /* account for flushed chunks */
3564 m_soundbuf_chunks = chunknum;
3565 return error::NONE;
3566 }
3567
3568
3569 /*-------------------------------------------------
3570 printf_chunk_recursive - print information
3571 about a chunk recursively
3572 -------------------------------------------------*/
3573
3574 /**
3575 * @fn static void printf_chunk_recursive(avi_file *file, avi_chunk *container, int indent)
3576 *
3577 * @brief Printf chunk recursive.
3578 *
3579 * @param [in,out] file If non-null, the file.
3580 * @param [in,out] container If non-null, the container.
3581 * @param indent The indent.
3582 */
3583
printf_chunk_recursive(avi_chunk const * container,int indent)3584 void avi_file_impl::printf_chunk_recursive(avi_chunk const *container, int indent)
3585 {
3586 char size[20], offset[20];
3587 avi_chunk curchunk;
3588 error avierr;
3589
3590 /* iterate over chunks in this container */
3591 for (avierr = get_first_chunk(container, curchunk); avierr == error::NONE; avierr = get_next_chunk(container, curchunk))
3592 {
3593 std::uint32_t chunksize = curchunk.size;
3594 bool recurse = false;
3595
3596 u64toa(curchunk.size, size);
3597 u64toa(curchunk.offset, offset);
3598 printf("%*schunk = %c%c%c%c, size=%s (%s)\n", indent, "",
3599 std::uint8_t(curchunk.type >> 0),
3600 std::uint8_t(curchunk.type >> 8),
3601 std::uint8_t(curchunk.type >> 16),
3602 std::uint8_t(curchunk.type >> 24),
3603 size, offset);
3604
3605 /* certain chunks are just containers; recurse into them */
3606 switch (curchunk.type)
3607 {
3608 /* basic containers */
3609 case CHUNKTYPE_RIFF:
3610 case CHUNKTYPE_LIST:
3611 printf("%*stype = %c%c%c%c\n", indent, "",
3612 std::uint8_t(curchunk.listtype >> 0),
3613 std::uint8_t(curchunk.listtype >> 8),
3614 std::uint8_t(curchunk.listtype >> 16),
3615 std::uint8_t(curchunk.listtype >> 24));
3616 recurse = true;
3617 chunksize = 0;
3618 break;
3619 }
3620
3621 /* print data within the chunk */
3622 if (chunksize > 0 && curchunk.size < 1024 * 1024)
3623 {
3624 std::unique_ptr<std::uint8_t []> data;
3625
3626 /* read the data for a chunk */
3627 avierr = read_chunk_data(curchunk, data);
3628 if (avierr == error::NONE)
3629 {
3630 std::uint32_t const bytes = (std::min)(std::uint32_t(512), chunksize);
3631 for (std::uint32_t i = 0; i < bytes; i++)
3632 {
3633 if (i % 16 == 0) printf("%*s ", indent, "");
3634 printf("%02X ", data[i]);
3635 if (i % 16 == 15) printf("\n");
3636 }
3637 if (chunksize % 16 != 0) printf("\n");
3638 }
3639 }
3640
3641 /* if we're recursing, dive down */
3642 if (recurse)
3643 printf_chunk_recursive(&curchunk, indent + 4);
3644 }
3645
3646 /* if we didn't get a legitimate error, indicate that */
3647 if (avierr != error::END)
3648 printf("[chunk error %d]\n", int(avierr));
3649 }
3650
3651 } // anonymous namespace
3652
3653
3654
3655 /***************************************************************************
3656 IMPLEMENTATION
3657 ***************************************************************************/
3658
3659 /*-------------------------------------------------
3660 avi_open - open an AVI movie file for read
3661 -------------------------------------------------*/
3662
3663 /**
3664 * @fn avi_error avi_open(const char *filename, avi_file **file)
3665 *
3666 * @brief Queries if a given avi open.
3667 *
3668 * @param filename Filename of the file.
3669 * @param [in,out] file If non-null, the file.
3670 *
3671 * @return An avi_error.
3672 */
3673
open(std::string const & filename,ptr & file)3674 avi_file::error avi_file::open(std::string const &filename, ptr &file)
3675 {
3676 /* open the file */
3677 osd_file::ptr f;
3678 std::uint64_t length;
3679 if (osd_file::open(filename, OPEN_FLAG_READ, f, length) != osd_file::error::NONE)
3680 return error::CANT_OPEN_FILE;
3681
3682 /* allocate the file */
3683 std::unique_ptr<avi_file_impl> newfile;
3684 try { newfile = std::make_unique<avi_file_impl>(std::move(f), length); }
3685 catch (...) { return error::NO_MEMORY; }
3686
3687 /* parse the data */
3688 error const avierr = newfile->read_movie_data();
3689 if (avierr != error::NONE)
3690 return avierr;
3691
3692 file = std::move(newfile);
3693 return error::NONE;
3694 }
3695
3696
3697 /*-------------------------------------------------
3698 avi_create - create a new AVI movie file
3699 -------------------------------------------------*/
3700
3701 /**
3702 * @fn avi_error avi_create(const char *filename, const avi_movie_info *info, avi_file **file)
3703 *
3704 * @brief Avi create.
3705 *
3706 * @param filename Filename of the file.
3707 * @param info The information.
3708 * @param [in,out] file If non-null, the file.
3709 *
3710 * @return An avi_error.
3711 */
3712
create(std::string const & filename,movie_info const & info,ptr & file)3713 avi_file::error avi_file::create(std::string const &filename, movie_info const &info, ptr &file)
3714 {
3715 /* validate video info */
3716 if ((info.video_format != 0 && info.video_format != FORMAT_UYVY && info.video_format != FORMAT_VYUY && info.video_format != FORMAT_YUY2) ||
3717 (info.video_width == 0) ||
3718 (info.video_height == 0) ||
3719 (info.video_depth == 0) ||
3720 (info.video_depth % 8 != 0))
3721 return error::UNSUPPORTED_VIDEO_FORMAT;
3722
3723 /* validate audio info */
3724 if (info.audio_format != 0 ||
3725 info.audio_channels > MAX_SOUND_CHANNELS ||
3726 info.audio_samplebits != 16)
3727 return error::UNSUPPORTED_AUDIO_FORMAT;
3728
3729 /* open the file */
3730 osd_file::ptr f;
3731 std::uint64_t length;
3732 osd_file::error const filerr = osd_file::open(filename, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS, f, length);
3733 if (filerr != osd_file::error::NONE)
3734 return error::CANT_OPEN_FILE;
3735
3736 /* allocate the file */
3737 error avierr;
3738 std::unique_ptr<avi_file_impl> newfile;
3739 try { newfile = std::make_unique<avi_file_impl>(std::move(f), info); }
3740 catch (...)
3741 {
3742 avierr = error::NO_MEMORY;
3743 goto error;
3744 }
3745
3746 /* initialize the sound buffering */
3747 avierr = newfile->soundbuf_initialize();
3748 if (avierr != error::NONE)
3749 goto error;
3750
3751 /* write the initial headers */
3752 avierr = newfile->write_initial_headers();
3753 if (avierr != error::NONE)
3754 goto error;
3755
3756 file = std::move(newfile);
3757 return error::NONE;
3758
3759 error:
3760 f.reset();
3761 newfile.reset();
3762 osd_file::remove(filename);
3763 return avierr;
3764 }
3765
3766
3767 /*-------------------------------------------------
3768 avi_error_string - get the error string for
3769 an avi_error
3770 -------------------------------------------------*/
3771
3772 /**
3773 * @fn const char *avi_error_string(avi_error err)
3774 *
3775 * @brief Avi error string.
3776 *
3777 * @param err The error.
3778 *
3779 * @return null if it fails, else a char*.
3780 */
3781
error_string(error err)3782 const char *avi_file::error_string(error err)
3783 {
3784 switch (err)
3785 {
3786 case error::NONE: return "success";
3787 case error::END: return "hit end of file";
3788 case error::INVALID_DATA: return "invalid data";
3789 case error::NO_MEMORY: return "out of memory";
3790 case error::READ_ERROR: return "read error";
3791 case error::WRITE_ERROR: return "write error";
3792 case error::STACK_TOO_DEEP: return "stack overflow";
3793 case error::UNSUPPORTED_FEATURE: return "unsupported feature";
3794 case error::CANT_OPEN_FILE: return "unable to open file";
3795 case error::INCOMPATIBLE_AUDIO_STREAMS: return "found incompatible audio streams";
3796 case error::INVALID_SAMPLERATE: return "found invalid sample rate";
3797 case error::INVALID_STREAM: return "invalid stream";
3798 case error::INVALID_FRAME: return "invalid frame index";
3799 case error::INVALID_BITMAP: return "invalid bitmap";
3800 case error::UNSUPPORTED_VIDEO_FORMAT: return "unsupported video format";
3801 case error::UNSUPPORTED_AUDIO_FORMAT: return "unsupported audio format";
3802 case error::EXCEEDED_SOUND_BUFFER: return "sound buffer overflow";
3803 default: return "undocumented error";
3804 }
3805 }
3806
3807
avi_file()3808 avi_file::avi_file()
3809 {
3810 }
3811
3812
~avi_file()3813 avi_file::~avi_file()
3814 {
3815 }
3816