DOSBox-X
|
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