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