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