DOSBox-X
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
src/aviwriter/avi_writer.cpp
00001 /*
00002  *  Copyright (C) 2018-2020 Jon Campbell
00003  *
00004  *  This program is free software; you can redistribute it and/or modify
00005  *  it under the terms of the GNU General Public License as published by
00006  *  the Free Software Foundation; either version 2 of the License, or
00007  *  (at your option) any later version.
00008  *
00009  *  This program is distributed in the hope that it will be useful,
00010  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  *  GNU General Public License for more details.
00013  *
00014  *  You should have received a copy of the GNU General Public License along
00015  *  with this program; if not, write to the Free Software Foundation, Inc.,
00016  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00017  */
00018 
00019 /* Shut up! */
00020 #define _CRT_NONSTDC_NO_DEPRECATE
00021 
00022 #include "rawint.h"
00023 #include "avi.h"
00024 #include "avi_writer.h"
00025 #include "avi_rw_iobuf.h"
00026 #include <unistd.h>
00027 #include <stdlib.h>
00028 #include <assert.h>
00029 #include <fcntl.h>
00030 #ifdef _MSC_VER
00031 # include <io.h>
00032 #endif
00033 
00034 #ifndef O_BINARY
00035 # define O_BINARY 0
00036 #endif
00037 
00038 /* FIXME: I made the mistake of putting critical calls in assert() calls, which under MSVC++ may evaluate to nothing in Release builds */
00039 #if defined(_MSC_VER) || defined (__MINGW32__)
00040 # ifdef NDEBUG
00041 #  undef assert
00042 #  define assert(x) x
00043 # endif
00044 #endif
00045 
00046 int avi_writer_stream_check_samplecount(avi_writer_stream *s,unsigned int len) {
00047     if (s == NULL) return 0;
00048 
00049     if (s->sample_index == NULL) {
00050         s->sample_index_alloc = len + 256;
00051         s->sample_index = (avi_writer_stream_index*)
00052             malloc(sizeof(avi_writer_stream_index) * s->sample_index_alloc);
00053         if (s->sample_index == NULL) {
00054             s->sample_index_alloc = 0;
00055             s->sample_index_max = 0;
00056             return 0;
00057         }
00058         s->sample_index_max = 0;
00059     }
00060     else if (len > s->sample_index_alloc) {
00061         unsigned int na = len + 8192;
00062         avi_writer_stream_index *n = (avi_writer_stream_index*)
00063             realloc((void*)(s->sample_index),
00064                 sizeof(avi_writer_stream_index) * na);
00065         if (n == NULL) return 0;
00066         s->sample_index = n;
00067         s->sample_index_alloc = na;
00068     }
00069 
00070     return 1;
00071 }
00072 
00073 int avi_writer_stream_set_format(avi_writer_stream *s,void *data,size_t len) {
00074     if (s == NULL) return 0;
00075     if (s->format) free(s->format);
00076     s->format_len = 0;
00077     s->format = NULL;
00078 
00079     if (len == 0)
00080         return 1;
00081 
00082     if ((s->format = malloc(len)) == NULL)
00083         return 0;
00084 
00085     s->format_len = len;
00086     if (data) memcpy(s->format,data,len);
00087     else      memset(s->format,0,len);
00088     return 1;
00089 }
00090 
00091 riff_strh_AVISTREAMHEADER *avi_writer_stream_header(avi_writer_stream *s) {
00092     if (s == NULL) return NULL;
00093     return &s->header;
00094 }
00095 
00096 riff_avih_AVIMAINHEADER *avi_writer_main_header(avi_writer *w) {
00097     if (w == NULL) return NULL;
00098     return &w->main_header;
00099 }
00100 
00101 avi_writer_stream *avi_writer_new_stream(avi_writer *w) { /* reminder: you are not required to free this pointer, the writer does it for you on close_file() */
00102     avi_writer_stream *s;
00103 
00104     if (w == NULL) return NULL;
00105     if (w->state != AVI_WRITER_STATE_INIT) return NULL;
00106 
00107     if (w->avi_stream == NULL) {
00108         w->avi_stream_max = 1;
00109         w->avi_stream_alloc = 8;
00110         w->avi_stream = (avi_writer_stream*)
00111             malloc(sizeof(avi_writer_stream) * (unsigned int)w->avi_stream_alloc);
00112         if (w->avi_stream == NULL) {
00113             w->avi_stream_max = 0;
00114             w->avi_stream_alloc = 0;
00115             return NULL;
00116         }
00117         s = w->avi_stream;
00118     }
00119     else if (w->avi_stream_max >= w->avi_stream_alloc) {
00120         unsigned int na = (unsigned int)w->avi_stream_alloc + 64U;
00121         avi_writer_stream *n = (avi_writer_stream*)
00122             realloc((void*)(w->avi_stream),sizeof(avi_writer_stream) * (unsigned int)na);
00123         if (n == NULL)
00124             return NULL;
00125         w->avi_stream = n;
00126         w->avi_stream_alloc = (int)na;
00127         s = w->avi_stream + (w->avi_stream_max++);
00128     }
00129     else {
00130         s = w->avi_stream + (w->avi_stream_max++);
00131     }
00132 
00133     memset(s,0,sizeof(*s));
00134     s->index = (int)(s - w->avi_stream); /* NTS: C compiler converts pointer math to # of elements since w->avi_stream */
00135     return s;
00136 }
00137 
00138 void avi_writer_free_stream(avi_writer_stream *s) {
00139     if (s->sample_index) free(s->sample_index);
00140     s->sample_index_max = 0;
00141     s->sample_index_alloc = 0;
00142     if (s->format) free(s->format);
00143     s->format_len = 0;
00144     s->format = NULL;
00145 }
00146 
00147 void avi_writer_free_streams(avi_writer *w) {
00148     if (w->avi_stream) {
00149         for (int i=0;i < w->avi_stream_max;i++)
00150             avi_writer_free_stream(&w->avi_stream[i]);
00151         free(w->avi_stream);
00152     }
00153 
00154     w->avi_stream = NULL;
00155     w->avi_stream_max = 0;
00156     w->avi_stream_alloc = 0;
00157 }
00158 
00159 avi_writer *avi_writer_create() {
00160     avi_writer *w = (avi_writer*)malloc(sizeof(avi_writer));
00161     if (w == NULL) return NULL;
00162     memset(w,0,sizeof(*w));
00163     w->enable_opendml_index = 1;
00164     w->enable_stream_writing = 0;
00165     w->enable_avioldindex = 1;
00166     w->enable_opendml = 1;
00167     w->fd = -1;
00168     return w;
00169 }
00170 
00171 void avi_writer_close_file(avi_writer *w) {
00172     avi_writer_free_streams(w);
00173     if (w->riff) {
00174         riff_stack_writing_sync(w->riff);
00175         w->riff = riff_stack_destroy(w->riff);
00176     }
00177     if (w->fd >= 0) {
00178         if (w->own_fd) close(w->fd);
00179         w->fd = -1;
00180     }
00181     w->state = AVI_WRITER_STATE_DONE;
00182 }
00183 
00184 int avi_writer_open_file(avi_writer *w,const char *path) {
00185     avi_writer_close_file(w);
00186 
00187     w->own_fd = 1;
00188     if ((w->fd = open(path,O_WRONLY|O_CREAT|O_TRUNC|O_BINARY,0644)) < 0)
00189         return 0;
00190 
00191     if ((w->riff = riff_stack_create(256)) == NULL)
00192         goto errout;
00193 
00194     assert(riff_stack_assign_fd(w->riff,w->fd));
00195     assert(riff_stack_empty(w->riff));
00196     assert(riff_stack_prepare_for_writing(w->riff,1));
00197 
00198     w->state = AVI_WRITER_STATE_INIT;
00199     return 1;
00200 errout:
00201     avi_writer_close_file(w);
00202     return 0;
00203 }
00204 
00205 avi_writer *avi_writer_destroy(avi_writer *w) {
00206     if (w) {
00207         avi_writer_close_file(w);
00208         free(w);
00209     }
00210 
00211     return NULL;
00212 }
00213 
00214 int avi_writer_begin_data(avi_writer *w) {
00215     riff_chunk chunk;
00216 
00217     if (w == NULL) return 0;
00218     if (w->state != AVI_WRITER_STATE_HEADER) return 0;
00219 
00220     /* in case additional headers inserted are off-track, pop them.
00221      * AND pop out of the LIST:hdrl chunk too */
00222     while (w->riff->current > 0)
00223         riff_stack_pop(w->riff);
00224 
00225     /* start the movi chunk */
00226     assert(riff_stack_begin_new_chunk_here(w->riff,&chunk));
00227     assert(riff_stack_set_chunk_list_type(&chunk,riff_LIST,riff_fourcc_const('m','o','v','i')));
00228     if (w->enable_stream_writing) {
00229         assert(riff_stack_enable_placeholder(w->riff,&chunk));
00230         chunk.disable_sync = 1;
00231     }
00232     assert(riff_stack_push(w->riff,&chunk)); /* NTS: we can reuse chunk, the stack copies it here */
00233     w->movi = chunk;
00234 
00235     w->state = AVI_WRITER_STATE_BODY;
00236     riff_stack_header_sync_all(w->riff);
00237     return 1;
00238 }
00239 
00240 int avi_writer_begin_header(avi_writer *w) {
00241     riff_chunk chunk;
00242     int stream;
00243 
00244     if (w == NULL) return 0;
00245     if (w->state != AVI_WRITER_STATE_INIT) return 0;
00246 
00247     /* update the main header */
00248     __w_le_u32(&w->main_header.dwStreams,(unsigned int)w->avi_stream_max);
00249 
00250     /* [1] RIFF:AVI */
00251     assert(riff_stack_begin_new_chunk_here(w->riff,&chunk));
00252     assert(riff_stack_set_chunk_list_type(&chunk,riff_RIFF,riff_fourcc_const('A','V','I',' ')));
00253     if (w->enable_stream_writing) {
00254         /* if stream writing we encourage (up until OpenDML AVIX chunk is written)
00255          * the RIFF library to keep the size field set to the 0x7FFFFFFF placeholder
00256          * so that if this AVI file is left incomplete it still remains (mostly)
00257          * playable. Most media players including VLC player will not read an AVI
00258          * file past the length given in the LIST:AVI chunk and forcing the placeholder
00259          * ensure that VLC player will gracefully handle incomplete files written
00260          * by this library. */
00261         assert(riff_stack_enable_placeholder(w->riff,&chunk));
00262         chunk.disable_sync = 1;
00263     }
00264     assert(riff_stack_push(w->riff,&chunk)); /* NTS: we can reuse chunk, the stack copies it here */
00265 
00266     /* [2] LIST:hdrl */
00267     assert(riff_stack_begin_new_chunk_here(w->riff,&chunk));
00268     assert(riff_stack_set_chunk_list_type(&chunk,riff_LIST,riff_fourcc_const('h','d','r','l')));
00269     assert(riff_stack_push(w->riff,&chunk)); /* NTS: we can reuse chunk, the stack copies it here */
00270 
00271     /* [3] avih */
00272     assert(riff_stack_begin_new_chunk_here(w->riff,&chunk));
00273     assert(riff_stack_set_chunk_data_type(&chunk,riff_fourcc_const('a','v','i','h')));
00274     assert(riff_stack_push(w->riff,&chunk)); /* NTS: we can reuse chunk, the stack copies it here */
00275     assert(riff_stack_write(w->riff,riff_stack_top(w->riff),&w->main_header,sizeof(w->main_header)) ==
00276         (int)sizeof(w->main_header));
00277     w->avih = *riff_stack_top(w->riff);
00278     riff_stack_pop(w->riff); /* end avih */
00279 
00280     /* write out the streams */
00281     for (stream=0;stream < w->avi_stream_max;stream++) {
00282         avi_writer_stream *s = w->avi_stream + stream;
00283 
00284         /* Auto-generate chunk fourcc */
00285         if (s->chunk_fourcc == 0) {
00286             if (s->header.fccType == avi_fccType_video || s->header.fccType == avi_fccType_iavs) {
00287                 if (s->format != NULL && s->format_len >= sizeof(windows_BITMAPINFOHEADER)) {
00288                     windows_BITMAPINFOHEADER *h = (windows_BITMAPINFOHEADER*)(s->format);
00289                     if (h->biCompression == 0)
00290                         s->chunk_fourcc = riff_fourcc_const(0,0,'d','b');
00291                     else
00292                         s->chunk_fourcc = riff_fourcc_const(0,0,'d','c');
00293                 }
00294             }
00295             else if (s->header.fccType == avi_fccType_audio) {
00296                 s->chunk_fourcc = riff_fourcc_const(0,0,'w','b');
00297             }
00298             else {
00299                 /* TODO */
00300             }
00301 
00302             s->chunk_fourcc |= ((((unsigned int)s->index / 10U) % 10U) + (unsigned char)('0')) << 0UL;
00303             s->chunk_fourcc |= ( ((unsigned int)s->index % 10U)        + (unsigned char)('0')) << 8UL;
00304         }
00305 
00306         /* [3] LIST:strl */
00307         assert(riff_stack_begin_new_chunk_here(w->riff,&chunk));
00308         assert(riff_stack_set_chunk_list_type(&chunk,riff_LIST,riff_fourcc_const('s','t','r','l')));
00309         assert(riff_stack_push(w->riff,&chunk)); /* NTS: we can reuse chunk, the stack copies it here */
00310 
00311         /* [4] strh */
00312         assert(riff_stack_begin_new_chunk_here(w->riff,&chunk));
00313         assert(riff_stack_set_chunk_data_type(&chunk,riff_fourcc_const('s','t','r','h')));
00314         assert(riff_stack_push(w->riff,&chunk)); /* NTS: we can reuse chunk, the stack copies it here */
00315         assert(riff_stack_write(w->riff,riff_stack_top(w->riff),&s->header,sizeof(s->header)) ==
00316             (int)sizeof(s->header));
00317         s->strh = *riff_stack_top(w->riff);
00318         riff_stack_pop(w->riff);
00319 
00320         /* [4] strf */
00321         assert(riff_stack_begin_new_chunk_here(w->riff,&chunk));
00322         assert(riff_stack_set_chunk_data_type(&chunk,riff_fourcc_const('s','t','r','f')));
00323         assert(riff_stack_push(w->riff,&chunk)); /* NTS: we can reuse chunk, the stack copies it here */
00324         if (s->format && s->format_len > 0)
00325             assert((int)riff_stack_write(w->riff,riff_stack_top(w->riff),s->format,(size_t)s->format_len) == (int)s->format_len);
00326         riff_stack_pop(w->riff);
00327 
00328         /* [5] strn (if name given) */
00329         if (s->name != NULL) {
00330             size_t len = strlen(s->name) + 1; /* must include NUL */
00331 
00332             assert(riff_stack_begin_new_chunk_here(w->riff,&chunk));
00333             assert(riff_stack_set_chunk_data_type(&chunk,riff_fourcc_const('s','t','r','n')));
00334             assert(riff_stack_push(w->riff,&chunk)); /* NTS: we can reuse chunk, the stack copies it here */
00335             assert((int)riff_stack_write(w->riff,riff_stack_top(w->riff),s->name,(size_t)len) == (int)len);
00336             riff_stack_pop(w->riff);
00337         }
00338 
00339         if (w->enable_opendml_index) {
00340             unsigned char tmp[512];
00341             int i;
00342 
00343             /* JUNK indx */
00344             assert(riff_stack_begin_new_chunk_here(w->riff,&chunk));
00345             assert(riff_stack_set_chunk_data_type(&chunk,riff_fourcc_const('J','U','N','K')));
00346             assert(riff_stack_push(w->riff,&chunk)); /* NTS: we can reuse chunk, the stack copies it here */
00347             memset(tmp,0,sizeof(tmp));
00348             for (i=0;i < (16384/512);i++) assert(riff_stack_write(w->riff,riff_stack_top(w->riff),tmp,512) == 512);
00349             s->indx_junk = *riff_stack_top(w->riff);
00350             riff_stack_pop(w->riff);
00351         }
00352 
00353         /* end strl */
00354         riff_stack_pop(w->riff);
00355     }
00356 
00357     riff_stack_header_sync_all(w->riff);
00358     w->state = AVI_WRITER_STATE_HEADER;
00359     return 1;
00360 }
00361 
00362 int avi_writer_stream_repeat_last_chunk(avi_writer *w,avi_writer_stream *s) {
00363     avi_writer_stream_index *si,*psi;
00364     riff_chunk chunk;
00365 
00366     if (w == NULL || s == NULL)
00367         return 0;
00368     if (w->state != AVI_WRITER_STATE_BODY)
00369         return 0;
00370     if (s->sample_write_chunk == 0) /* if there *IS* no previous chunk, then bail */
00371         return 0;
00372 
00373     /* make sure we're down into the 'movi' chunk */
00374     while (w->riff->current > 1)
00375         riff_stack_pop(w->riff);
00376 
00377     /* make sure this is the movi chunk */
00378     if (w->riff->current != 1)
00379         return 0;
00380     if (w->riff->top->fourcc != avi_riff_movi)
00381         return 0;
00382 
00383     if (w->enable_opendml) {
00384         /* if we're writing an OpenDML 2.0 compliant file, and we're approaching a movi size of 1GB,
00385          * then split the movi chunk and start another RIFF:AVIX */
00386         if ((unsigned long long)(w->riff->top->write_offset + 8) >= 0x3FF00000ULL) { /* 1GB - 16MB */
00387             riff_stack_writing_sync(w->riff); /* sync all headers and pop all chunks */
00388             assert(w->riff->current == -1); /* should be at top level */
00389 
00390             /* at the first 1GB boundary emit AVIOLDINDEX for older AVI applications */
00391             if (w->group == 0 && w->enable_avioldindex)
00392                 avi_writer_emit_avioldindex(w);
00393 
00394             /* [1] RIFF:AVIX */
00395             assert(riff_stack_begin_new_chunk_here(w->riff,&chunk));
00396             assert(riff_stack_set_chunk_list_type(&chunk,riff_RIFF,riff_fourcc_const('A','V','I','X')));
00397             if (w->enable_stream_writing) {
00398                 assert(riff_stack_enable_placeholder(w->riff,&chunk));
00399                 chunk.disable_sync = 1;
00400             }
00401             assert(riff_stack_push(w->riff,&chunk)); /* NTS: we can reuse chunk, the stack copies it here */
00402             if (w->enable_stream_writing) riff_stack_header_sync(w->riff,riff_stack_top(w->riff));
00403 
00404             /* start the movi chunk */
00405             assert(riff_stack_begin_new_chunk_here(w->riff,&chunk));
00406             assert(riff_stack_set_chunk_list_type(&chunk,riff_LIST,riff_fourcc_const('m','o','v','i')));
00407             if (w->enable_stream_writing) {
00408                 assert(riff_stack_enable_placeholder(w->riff,&chunk));
00409                 chunk.disable_sync = 1;
00410             }
00411             assert(riff_stack_push(w->riff,&chunk)); /* NTS: we can reuse chunk, the stack copies it here */
00412             if (w->enable_stream_writing) riff_stack_header_sync(w->riff,riff_stack_top(w->riff));
00413             w->movi = chunk;
00414 
00415             w->group++;
00416         }
00417     }
00418     else {
00419         /* else, if we're about to pass 2GB, then stop allowing any more data, because the traditional
00420          * AVI format uses 32-bit integers and most implementations treat them as signed. */
00421         if ((w->movi.absolute_data_offset + w->riff->top->write_offset + 8) >= 0x7FF00000LL) /* 2GB - 16MB */
00422             return 0;
00423     }
00424 
00425     /* write chunk into movi (for consistent timekeeping with older AVI apps that don't read the index) */
00426     assert(riff_stack_begin_new_chunk_here(w->riff,&chunk));
00427     assert(riff_stack_set_chunk_data_type(&chunk,s->chunk_fourcc));
00428     assert(riff_stack_push(w->riff,&chunk));
00429     riff_stack_pop(w->riff);
00430 
00431     /* put the data into the index */
00432     if (!avi_writer_stream_check_samplecount(s,s->sample_write_chunk+16))
00433         return 0;
00434 
00435     /* lookup the previous chunk */
00436     /* NTS: this must come after the check_samplecount() because check_samplecount()
00437      *      uses realloc() to extend the array and realloc() may move the data around
00438      *      to fullfill the request */
00439     assert(s->sample_index != NULL);
00440     assert(s->sample_index_max >= s->sample_write_chunk);
00441     psi = s->sample_index + s->sample_write_chunk - 1;
00442 
00443     s->sample_index_max = s->sample_write_chunk+1;
00444     assert(s->sample_index_max < s->sample_index_alloc);
00445     si = s->sample_index + s->sample_write_chunk;
00446 
00447     *si = *psi;
00448     si->stream_offset = s->sample_write_offset;
00449 
00450     s->sample_write_offset += si->length;
00451     s->sample_write_chunk++;
00452     riff_stack_header_sync_all(w->riff);
00453     return 1;
00454 }
00455 
00456 /* NTS: this code makes no attempt to optimize chunk sizes and combine samples/frames---if you're
00457  *      stupid enough to call this routine with 4-byte long data then 4-byte long chunks is what
00458  *      you'll get, don't blame me if doing that overruns the allocated space set aside for the
00459  *      indexes. */
00460 int avi_writer_stream_write(avi_writer *w,avi_writer_stream *s,void *data,size_t len,uint32_t flags) {
00461     avi_writer_stream_index *si;
00462     riff_chunk chunk;
00463 
00464     if (w == NULL || s == NULL)
00465         return 0;
00466     if (w->state != AVI_WRITER_STATE_BODY)
00467         return 0;
00468 
00469     /* calling this function with data == NULL is perfectly valid, it simply means no data */
00470     if (data == NULL)
00471         len = 0;
00472 
00473     /* make sure we're down into the 'movi' chunk */
00474     while (w->riff->current > 1)
00475         riff_stack_pop(w->riff);
00476 
00477     /* make sure this is the movi chunk */
00478     if (w->riff->current != 1)
00479         return 0;
00480     if (w->riff->top->fourcc != avi_riff_movi)
00481         return 0;
00482 
00483     if (w->enable_opendml) {
00484         /* if we're writing an OpenDML 2.0 compliant file, and we're approaching a movi size of 1GB,
00485          * then split the movi chunk and start another RIFF:AVIX */
00486         if (((unsigned long long)w->riff->top->write_offset + (unsigned long long)len) >= 0x3FF00000ULL) { /* 1GB - 16MB */
00487             riff_stack_writing_sync(w->riff); /* sync all headers and pop all chunks */
00488             assert(w->riff->current == -1); /* should be at top level */
00489 
00490             /* at the first 1GB boundary emit AVIOLDINDEX for older AVI applications */
00491             if (w->group == 0 && w->enable_avioldindex)
00492                 avi_writer_emit_avioldindex(w);
00493 
00494             /* [1] RIFF:AVIX */
00495             assert(riff_stack_begin_new_chunk_here(w->riff,&chunk));
00496             assert(riff_stack_set_chunk_list_type(&chunk,riff_RIFF,riff_fourcc_const('A','V','I','X')));
00497             if (w->enable_stream_writing) {
00498                 assert(riff_stack_enable_placeholder(w->riff,&chunk));
00499                 chunk.disable_sync = 1;
00500             }
00501             assert(riff_stack_push(w->riff,&chunk)); /* NTS: we can reuse chunk, the stack copies it here */
00502             if (w->enable_stream_writing) riff_stack_header_sync(w->riff,riff_stack_top(w->riff));
00503 
00504             /* start the movi chunk */
00505             assert(riff_stack_begin_new_chunk_here(w->riff,&chunk));
00506             assert(riff_stack_set_chunk_list_type(&chunk,riff_LIST,riff_fourcc_const('m','o','v','i')));
00507             if (w->enable_stream_writing) {
00508                 assert(riff_stack_enable_placeholder(w->riff,&chunk));
00509                 chunk.disable_sync = 1;
00510             }
00511             assert(riff_stack_push(w->riff,&chunk)); /* NTS: we can reuse chunk, the stack copies it here */
00512             if (w->enable_stream_writing) riff_stack_header_sync(w->riff,riff_stack_top(w->riff));
00513             w->movi = chunk;
00514 
00515             w->group++;
00516         }
00517     }
00518     else {
00519         /* else, if we're about to pass 2GB, then stop allowing any more data, because the traditional
00520          * AVI format uses 32-bit integers and most implementations treat them as signed. */
00521         if (((unsigned long long)w->movi.absolute_data_offset +
00522              (unsigned long long)w->riff->top->write_offset +
00523              (unsigned long long)len) >= 0x7FF00000ULL) /* 2GB - 16MB */
00524             return 0;
00525     }
00526 
00527     /* write chunk into movi */
00528     assert(riff_stack_begin_new_chunk_here(w->riff,&chunk));
00529     assert(riff_stack_set_chunk_data_type(&chunk,s->chunk_fourcc));
00530     assert(riff_stack_push(w->riff,&chunk));
00531     if (w->enable_stream_writing) {
00532         /* use an optimized version of riff_stack_write() that blasts the RIFF chunk header + data in one go */
00533         if (data != NULL && len > 0)
00534             assert((int)riff_stack_streamwrite(w->riff,riff_stack_top(w->riff),data,(size_t)len) == (int)len);
00535         else
00536             assert((int)riff_stack_streamwrite(w->riff,riff_stack_top(w->riff),NULL,(size_t)0) == (int)0);
00537     }
00538     else {
00539         if (data != NULL && len > 0)
00540             assert((int)riff_stack_write(w->riff,riff_stack_top(w->riff),data,(size_t)len) == (int)len);
00541     }
00542     riff_stack_pop(w->riff);
00543 
00544     /* put the data into the index */
00545     if (!avi_writer_stream_check_samplecount(s,s->sample_write_chunk+16))
00546         return 0;
00547 
00548     s->sample_index_max = s->sample_write_chunk+1;
00549     assert(s->sample_index_max < s->sample_index_alloc);
00550     si = s->sample_index + s->sample_write_chunk;
00551 
00552     si->stream_offset = s->sample_write_offset;
00553     si->offset = (uint64_t)chunk.absolute_data_offset;
00554     si->length = (uint32_t)len;
00555     si->dwFlags = flags;
00556 
00557     s->sample_write_offset += (unsigned int)len;
00558     s->sample_write_chunk++;
00559 
00560     /* if stream writing is not enabled, then rewrite all RIFF parent chunks to reflect the new data */
00561     if (!w->enable_stream_writing)
00562         riff_stack_header_sync_all(w->riff);
00563 
00564     return 1;
00565 }
00566 
00567 /* caller must ensure that we're at the RIFF:AVI(X) level, not anywhere else! */
00568 int avi_writer_emit_avioldindex(avi_writer *w) {
00569     riff_idx1_AVIOLDINDEX *ie;
00570     unsigned int i,c,co;
00571     riff_chunk chunk;
00572 
00573     if (w == NULL) return 0;
00574     if (w->wrote_idx1) return 0;
00575     if (w->group != 0) return 0;
00576     if (avi_io_buffer_init(sizeof(*ie)) == NULL) return 0;
00577 
00578     /* write chunk into movi */
00579     assert(riff_stack_begin_new_chunk_here(w->riff,&chunk));
00580     assert(riff_stack_set_chunk_data_type(&chunk,avi_riff_idx1));
00581     assert(riff_stack_push(w->riff,&chunk));
00582 
00583     /* scan all frames, stopping when all streams have been scanned */
00584     i=0U;
00585     do {
00586         co=0U;
00587         for (c=0U;c < (unsigned int)w->avi_stream_max;c++) {
00588             avi_writer_stream *s = w->avi_stream + c;
00589             if (i < s->sample_index_max) {
00590                 avi_writer_stream_index *sie = s->sample_index + i;
00591                 /* AVIOLDINDEX: offsets are relative to movi chunk header offset + 8 */
00592                 long long ofs = ((long long)sie->offset - 8LL) - ((long long)w->movi.absolute_header_offset + 8LL);
00593                 assert(ofs >= 0);
00594                 if (ofs < 0x80000000LL) { /* the AVIOLDINDEX can only support 32-bit offsets */
00595                     if ((avi_io_write+sizeof(*ie)) > avi_io_fence) {
00596                         size_t sz = (size_t)(avi_io_write - avi_io_buf);
00597                         /* flush to disk */
00598                         assert(riff_stack_write(w->riff,riff_stack_top(w->riff),avi_io_buf,sz) == (int)sz);
00599                         /* reset pointer */
00600                         avi_io_write = avi_io_buf;
00601                     }
00602 
00603                     /* TODO: FIXME This needs to use the rawint.h macros to set the variables! */
00604                     ie = (riff_idx1_AVIOLDINDEX*)avi_io_write;
00605                     avi_io_write += sizeof(*ie);
00606                     ie->dwChunkId = s->chunk_fourcc;
00607                     ie->dwFlags = sie->dwFlags;
00608                     ie->dwOffset = (uint32_t)(ofs);
00609                     ie->dwSize = sie->length;
00610                     co++;
00611                 }
00612             }
00613         }
00614         if (co != 0) i++;
00615     } while (co != 0);
00616 
00617     if (avi_io_write != avi_io_fence) {
00618         size_t sz = (size_t)(avi_io_write - avi_io_buf);
00619         /* flush to disk */
00620         assert(riff_stack_write(w->riff,riff_stack_top(w->riff),avi_io_buf,sz) == (int)sz);
00621         /* reset pointer */
00622         avi_io_write = avi_io_buf;
00623     }
00624 
00625     riff_stack_pop(w->riff);
00626     avi_io_buffer_free();
00627     w->wrote_idx1 = 1;
00628     return 1;
00629 }
00630 
00631 int avi_writer_update_avi_and_stream_headers(avi_writer *w) {
00632     avi_writer_stream *s;
00633     int stream;
00634 
00635     if (w == NULL) return 0;
00636     if (w->state == AVI_WRITER_STATE_INIT || w->state == AVI_WRITER_STATE_DONE) return 0;
00637 
00638     if (w->enable_avioldindex || w->enable_opendml_index) {
00639         /* FIXME: This needs to use the rawint.h macros to read and set the value */
00640         w->main_header.dwFlags |=
00641             riff_avih_AVIMAINHEADER_flags_HASINDEX |
00642             riff_avih_AVIMAINHEADER_flags_MUSTUSEINDEX |
00643             riff_avih_AVIMAINHEADER_flags_ISINTERLEAVED;
00644     }
00645 
00646     /* NTS: As of 2013/02/06 we now allow the caller to pre-set
00647      *      the dwLength value, and we update it with our own if
00648      *      the actual frame count is larger */
00649     /* FIXME: When writing OpenDML files, we're actually supposed to set the stream header's
00650      *        dwLength to what would be the maximum length for older programs that do not
00651      *        read OpenDML extensions, and then we write an extended chunk into the stream
00652      *        header LIST that holds the true length. When will we implement this? */
00653     for (stream=0;stream < w->avi_stream_max;stream++) {
00654         s = w->avi_stream + stream;
00655         if (s->header.fccType == avi_fccType_video || s->header.fccType == avi_fccType_iavs) {
00656             if (s->header.dwLength < s->sample_index_max)
00657                 s->header.dwLength = s->sample_index_max;
00658         }
00659         else if (s->header.fccType == avi_fccType_audio) {
00660             unsigned int nlength=0;
00661 
00662             if (s->format != NULL && s->format_len >= sizeof(windows_WAVEFORMAT)) {
00663                 windows_WAVEFORMAT *wf = (windows_WAVEFORMAT*)(s->format);
00664                 unsigned int nss = __le_u16(&wf->nBlockAlign);
00665                 if (nss != 0) nlength = s->sample_write_offset / nss;
00666             }
00667             else {
00668                 nlength = s->sample_write_offset;
00669             }
00670 
00671             if (s->header.dwLength < nlength)
00672                 s->header.dwLength = nlength;
00673         }
00674 
00675         if (s->strh.absolute_data_offset != 0LL && s->strh.data_length >= sizeof(riff_strh_AVISTREAMHEADER)) {
00676             riff_chunk r = s->strh;
00677             assert(riff_stack_seek(w->riff,&r,0) == 0);
00678             assert(riff_stack_write(w->riff,&r,&s->header,sizeof(s->header)) == (int)sizeof(s->header));
00679         }
00680     }
00681 
00682     /* FIXME: This needs to use the rawint.h macros to set the value */
00683     w->main_header.dwTotalFrames = 0;
00684     for (stream=0;stream < w->avi_stream_max;stream++) {
00685         s = w->avi_stream + stream;
00686         if (s->header.fccType == avi_fccType_video || s->header.fccType == avi_fccType_iavs) {
00687             /* FIXME: This needs to use the rawint.h macros to set the value */
00688             w->main_header.dwTotalFrames = s->header.dwLength;
00689             break;
00690         }
00691     }
00692 
00693     if (w->avih.absolute_data_offset != 0LL && w->avih.data_length >= sizeof(riff_avih_AVIMAINHEADER)) {
00694         riff_chunk r = w->avih;
00695         assert(riff_stack_seek(w->riff,&r,0) == 0);
00696         assert(riff_stack_write(w->riff,&r,&w->main_header,sizeof(w->main_header)) == (int)sizeof(w->main_header));
00697     }
00698 
00699     return 1;
00700 }
00701 
00702 /* NTS: this writer keeps the AVISUPERINDEXes in the header, therefore the file offset
00703  *      returned is never larger than about 2MB or so */
00704 uint64_t avi_writer_stream_alloc_superindex(avi_writer *w,avi_writer_stream *s) {
00705     riff_chunk chunk;
00706 
00707     if (w == NULL || s == NULL)
00708         return 0ULL;
00709 
00710     if (s->indx_junk.absolute_data_offset != 0LL) {
00711         if (s->indx_junk.data_length < 256) return 0ULL;
00712 
00713         /* convert the JUNK chunk into an indx chunk */
00714         {
00715             assert(riff_stack_seek(w->riff,NULL,s->indx_junk.absolute_header_offset) == s->indx_junk.absolute_header_offset);
00716             assert(riff_stack_write(w->riff,NULL,"indx",4) == 4);
00717         }
00718 
00719         /* emit the header */
00720         {
00721             s->superindex.wLongsPerEntry = 4;
00722             s->superindex.bIndexType = riff_indx_type_AVI_INDEX_OF_INDEXES;
00723             s->superindex.nEntriesInUse = 1;
00724             s->superindex.dwChunkId = s->chunk_fourcc;
00725             chunk = s->indx_junk;
00726             assert(riff_stack_seek(w->riff,&chunk,0) == 0);
00727             assert(riff_stack_write(w->riff,&chunk,&s->superindex,sizeof(s->superindex)) == (int)sizeof(s->superindex));
00728             s->indx_entryofs = sizeof(s->superindex);
00729         }
00730 
00731         /* now it's an indx chunk we treat it as such */
00732         s->indx = s->indx_junk;
00733         s->indx_junk.absolute_data_offset = 0LL;
00734     }
00735     else if (s->indx.absolute_data_offset != 0LL) {
00736         if ((s->indx_entryofs + sizeof(riff_indx_AVISUPERINDEX_entry)) > s->indx.data_length)
00737             return 0ULL;
00738 
00739         chunk = s->indx;
00740         s->superindex.nEntriesInUse++;
00741         assert(riff_stack_seek(w->riff,&chunk,0) == 0);
00742         assert(riff_stack_write(w->riff,&chunk,&s->superindex,sizeof(s->superindex)) == (int)sizeof(s->superindex));
00743     }
00744 
00745     if (s->indx.absolute_data_offset != 0LL) {
00746         if ((s->indx_entryofs + sizeof(riff_indx_AVISUPERINDEX_entry)) > s->indx.data_length)
00747             return 0ULL;
00748 
00749         uint64_t ofs = (uint64_t)s->indx.absolute_data_offset + (uint64_t)s->indx_entryofs;
00750         s->indx_entryofs += (unsigned int)sizeof(riff_indx_AVISUPERINDEX_entry);
00751         return ofs;
00752     }
00753 
00754     return 0ULL;
00755 }
00756 
00757 /* caller must ensure we're at the movi chunk level */
00758 int avi_writer_emit_opendml_indexes(avi_writer *w) {
00759     unsigned long long chunk_ofs=0,chunk_max;
00760     riff_indx_AVISUPERINDEX_entry suie;
00761     avi_writer_stream_index *si;
00762     unsigned int chunk,chks,out_chunks;
00763     riff_indx_AVISTDINDEX_entry *stdie;
00764     unsigned long long superindex;
00765     riff_indx_AVISTDINDEX stdh;
00766     avi_writer_stream *s;
00767     riff_chunk newchunk;
00768     int stream,in1,in2;
00769     long long offset;
00770 
00771     if (w == NULL) return 0;
00772     if (!w->enable_opendml_index) return 0;
00773     if (avi_io_buffer_init(sizeof(*stdie)) == NULL) return 0;
00774 
00775     for (stream=0;stream < w->avi_stream_max;stream++) {
00776         s = w->avi_stream + stream;
00777         if (s->indx_entryofs != 0) break;
00778         if (s->sample_index == NULL) continue;
00779         in1 = ((stream / 10) % 10) + '0';
00780         in2 = (stream % 10) + '0';
00781 
00782         for (chunk=0;chunk < s->sample_index_max;) {
00783             /* scan up to 2000 samples, and determine a good base offset for them */
00784             si = s->sample_index + chunk;
00785             chunk_ofs = chunk_max = si->offset;
00786             chks = chunk + 1; si++;
00787             while (chks < (chunk + 2000) && chks < s->sample_index_max) {
00788                 if (chunk_max < si->offset) {
00789                     if (si->offset > (chunk_ofs + 0x7FFF0000ULL))
00790                         break;
00791 
00792                     chunk_max = si->offset;
00793                 }
00794                 else if (chunk_ofs > si->offset) {
00795                     if ((si->offset + 0x7FFF0000ULL) <= chunk_max)
00796                         break;
00797 
00798                     chunk_ofs = si->offset;
00799                 }
00800 
00801                 chks++;
00802                 si++;
00803             }
00804 
00805             /* make sure the above loop does it's job */
00806             assert((chunk_ofs + 0x7FFF0000ULL) > chunk_max);
00807 
00808             /* start an AVISUPERINDEX */
00809             out_chunks = 0;
00810             if ((superindex = avi_writer_stream_alloc_superindex(w,s)) == 0ULL) {
00811                 fprintf(stderr,"Cannot alloc superindex for %d\n",s->index);
00812                 break;
00813             }
00814 
00815             /* start an index chunk */
00816             assert(riff_stack_begin_new_chunk_here(w->riff,&newchunk));
00817             assert(riff_stack_set_chunk_data_type(&newchunk,riff_fourcc_const('i','x',in1,in2)));
00818             assert(riff_stack_push(w->riff,&newchunk)); /* NTS: we can reuse chunk, the stack copies it here */
00819 
00820             memset(&stdh,0,sizeof(stdh));
00821             stdh.wLongsPerEntry = 2;
00822             stdh.bIndexType = riff_indx_type_AVI_INDEX_OF_CHUNKS;
00823             stdh.dwChunkId = s->chunk_fourcc;
00824             stdh.qwBaseOffset = chunk_ofs;
00825             assert(riff_stack_write(w->riff,riff_stack_top(w->riff),&stdh,sizeof(stdh)) == (int)sizeof(stdh));
00826 
00827             avi_io_write = avi_io_buf;
00828             while (chunk < s->sample_index_max) {
00829                 si = s->sample_index + chunk;
00830 
00831                 offset = (long long)si->offset - (long long)chunk_ofs;
00832                 if (offset < 0LL || offset >= 0x7FFF0000LL)
00833                     break;
00834 
00835                 if ((avi_io_write+sizeof(*stdie)) > avi_io_fence) {
00836                     size_t sz = (size_t)(avi_io_write - avi_io_buf);
00837                     /* flush to disk */
00838                     assert(riff_stack_write(w->riff,riff_stack_top(w->riff),avi_io_buf,sz) == (int)sz);
00839                     /* reset pointer */
00840                     avi_io_write = avi_io_buf;
00841                 }
00842 
00843                 stdie = (riff_indx_AVISTDINDEX_entry*)avi_io_write;
00844                 avi_io_write += sizeof(*stdie);
00845 
00846                 stdie->dwOffset = (uint32_t)offset;
00847                 stdie->dwSize = si->length;
00848                 if ((si->dwFlags & riff_idx1_AVIOLDINDEX_flags_KEYFRAME) == 0) stdie->dwSize |= (1UL << 31UL);
00849                 out_chunks++;
00850                 chunk++;
00851             }
00852 
00853             if (avi_io_write != avi_io_fence) {
00854                 size_t sz = (size_t)(avi_io_write - avi_io_buf);
00855                 /* flush to disk */
00856                 assert(riff_stack_write(w->riff,riff_stack_top(w->riff),avi_io_buf,sz) == (int)sz);
00857                 /* reset pointer */
00858                 avi_io_write = avi_io_buf;
00859             }
00860 
00861             assert(out_chunks != 0);
00862             stdh.nEntriesInUse = out_chunks;
00863             assert(riff_stack_seek(w->riff,riff_stack_top(w->riff),0) == 0);
00864             assert(riff_stack_write(w->riff,riff_stack_top(w->riff),&stdh,sizeof(stdh)) == (int)sizeof(stdh));
00865 
00866             newchunk = *riff_stack_top(w->riff);
00867             riff_stack_pop(w->riff); /* end ix## */
00868 
00869             suie.qwOffset = (uint64_t)newchunk.absolute_header_offset;
00870             suie.dwDuration = out_chunks;
00871             suie.dwSize = newchunk.data_length + 8;
00872             assert(riff_stack_seek(w->riff,NULL,(int64_t)superindex) == (int64_t)superindex);
00873             assert(riff_stack_write(w->riff,NULL,&suie,sizeof(suie)) == (int)sizeof(suie));
00874         }
00875     }
00876 
00877     avi_io_buffer_free();
00878     return 1;
00879 }
00880 
00881 int avi_writer_end_data(avi_writer *w) {
00882     if (w == NULL) return 0;
00883     if (w->state != AVI_WRITER_STATE_BODY) return 0;
00884 
00885     /* if we're still in the movi chunk, and we're asked to write the OpenDML 2.0
00886      * AVI index, do it now */
00887     while (w->riff->current > 1) riff_stack_pop(w->riff);
00888     if (w->riff->current == 1 && w->riff->top->fourcc == avi_riff_movi)
00889         avi_writer_emit_opendml_indexes(w);
00890 
00891     /* in case additional headers inserted are off-track, pop them.
00892      * AND pop out of the LIST:movi chunk too */
00893     while (w->riff->current > 0)
00894         riff_stack_pop(w->riff);
00895 
00896     /* now, while at this level, emit the AVIOLDINDEX if this is still the first movi */
00897     if (w->group == 0 && w->enable_avioldindex)
00898         avi_writer_emit_avioldindex(w);
00899 
00900     /* stay at this level, if the caller wants to add more chunks of his own */
00901     w->state = AVI_WRITER_STATE_FOOTER;
00902     riff_stack_header_sync_all(w->riff);
00903     avi_writer_update_avi_and_stream_headers(w);
00904     return 1;
00905 }
00906 
00907 int avi_writer_finish(avi_writer *w) {
00908     if (w == NULL) return 0;
00909     if (w->state != AVI_WRITER_STATE_FOOTER) return 0;
00910 
00911     /* pop it all down */
00912     while (w->riff->current > 0)
00913         riff_stack_pop(w->riff);
00914 
00915     riff_stack_header_sync_all(w->riff);
00916     w->state = AVI_WRITER_STATE_DONE;
00917     return 1;
00918 }
00919 
00920 /* if the caller is daring he can set stream writing mode where
00921  * this code minimizes disk seeking and enforces writing the
00922  * AVI file in a continuous stream.
00923  *
00924  * - Be aware that enabling this may make the AVI structure less
00925  *   coherent if this code is never given the chance to complete
00926  *   the writing process (i.e. because you crashed...)
00927  *
00928  * - Once you start the writing process, you cannot change stream
00929  *   writing mode */
00930 int avi_writer_set_stream_writing(avi_writer *w) {
00931     if (w == NULL) return 0;
00932     if (w->state != AVI_WRITER_STATE_INIT) return 0;
00933     w->enable_stream_writing = 1;
00934     return 1;
00935 }
00936