| 1 | /* |
| 2 | Copyright (c) 2012, Broadcom Europe Ltd |
| 3 | All rights reserved. |
| 4 | |
| 5 | Redistribution and use in source and binary forms, with or without |
| 6 | modification, are permitted provided that the following conditions are met: |
| 7 | * Redistributions of source code must retain the above copyright |
| 8 | notice, this list of conditions and the following disclaimer. |
| 9 | * Redistributions in binary form must reproduce the above copyright |
| 10 | notice, this list of conditions and the following disclaimer in the |
| 11 | documentation and/or other materials provided with the distribution. |
| 12 | * Neither the name of the copyright holder nor the |
| 13 | names of its contributors may be used to endorse or promote products |
| 14 | derived from this software without specific prior written permission. |
| 15 | |
| 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
| 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY |
| 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 26 | */ |
| 27 | #include <limits.h> |
| 28 | #include <stdlib.h> |
| 29 | #include <stdio.h> |
| 30 | #include <string.h> |
| 31 | |
| 32 | #define CONTAINER_IS_LITTLE_ENDIAN |
| 33 | //#define ENABLE_CONTAINERS_LOG_FORMAT |
| 34 | //#define ENABLE_CONTAINERS_LOG_FORMAT_VERBOSE |
| 35 | #define CONTAINER_HELPER_LOG_INDENT(a) 0 |
| 36 | #include "containers/core/containers_private.h" |
| 37 | #include "containers/core/containers_io_helpers.h" |
| 38 | #include "containers/core/containers_utils.h" |
| 39 | #include "containers/core/containers_writer_utils.h" |
| 40 | #include "containers/core/containers_logging.h" |
| 41 | |
| 42 | /****************************************************************************** |
| 43 | Function prototypes |
| 44 | ******************************************************************************/ |
| 45 | VC_CONTAINER_STATUS_T avi_writer_open( VC_CONTAINER_T *p_ctx ); |
| 46 | |
| 47 | /****************************************************************************** |
| 48 | Defines. |
| 49 | ******************************************************************************/ |
| 50 | #define AVISF_DISABLED 0x00000001 /*< If set stream should not be enabled by default. */ |
| 51 | #define AVIF_HASINDEX 0x00000010 |
| 52 | #define AVIF_TRUSTCKTYPE 0x00000800 |
| 53 | #define AVIIF_KEYFRAME 0x00000010 |
| 54 | |
| 55 | #define AVI_INDEX_OF_INDEXES 0x00 |
| 56 | #define AVI_INDEX_OF_CHUNKS 0x01 |
| 57 | #define AVI_INDEX_DELTAFRAME 0x80000000 |
| 58 | |
| 59 | #define AVI_INDEX_ENTRY_SIZE 16 |
| 60 | #define AVI_SUPER_INDEX_ENTRY_SIZE 16 |
| 61 | #define AVI_STD_INDEX_ENTRY_SIZE 8 |
| 62 | #define AVI_FRAME_BUFFER_SIZE 100000 |
| 63 | |
| 64 | #define AVI_TRACKS_MAX 3 |
| 65 | |
| 66 | #define AVI_AUDIO_CHUNK_SIZE_LIMIT 16384 /*< Watermark limit for data chunks when 'dwSampleSize' |
| 67 | is non-zero */ |
| 68 | |
| 69 | #define AVI_END_CHUNK(ctx) \ |
| 70 | do { \ |
| 71 | if(STREAM_POSITION(ctx) & 1) WRITE_U8(ctx, 0, "AVI_END_CHUNK"); \ |
| 72 | } while(0) |
| 73 | |
| 74 | #define AVI_PACKET_KEYFRAME (VC_CONTAINER_PACKET_FLAG_KEYFRAME | VC_CONTAINER_PACKET_FLAG_FRAME_END) |
| 75 | #define AVI_PACKET_IS_KEYFRAME(flags) (((flags) & AVI_PACKET_KEYFRAME) == AVI_PACKET_KEYFRAME) |
| 76 | |
| 77 | /****************************************************************************** |
| 78 | Type definitions. |
| 79 | ******************************************************************************/ |
| 80 | typedef struct VC_CONTAINER_TRACK_MODULE_T |
| 81 | { |
| 82 | uint32_t chunk_index; /**< index of current chunk */ |
| 83 | uint32_t chunk_offs; /**< current offset into bytestream consisting of all |
| 84 | chunks for this track */ |
| 85 | uint32_t sample_size; /**< i.e. 'dwSampleSize' in 'strh' */ |
| 86 | uint32_t max_chunk_size; /**< largest chunk written so far */ |
| 87 | uint64_t index_offset; /**< Offset to the start of an OpenDML index for this track |
| 88 | i.e. 'indx' */ |
| 89 | uint32_t index_size; /**< Size of the OpenDML index for this track i.e. 'indx' */ |
| 90 | } VC_CONTAINER_TRACK_MODULE_T; |
| 91 | |
| 92 | typedef struct VC_CONTAINER_MODULE_T |
| 93 | { |
| 94 | VC_CONTAINER_TRACK_T *tracks[AVI_TRACKS_MAX]; |
| 95 | VC_CONTAINER_WRITER_EXTRAIO_T null_io; /**< Null I/O for calculating chunk sizes, etc. */ |
| 96 | VC_CONTAINER_WRITER_EXTRAIO_T temp_io; /**< I/O for temporary storage of index data */ |
| 97 | int ; |
| 98 | |
| 99 | uint32_t ; /**< Offset to the header list chunk ('hdrl') */ |
| 100 | uint32_t ; /**< Size of the header list chunk ('hdrl') */ |
| 101 | uint32_t data_offset; /**< Offset to the start of data packets i.e. |
| 102 | the data in the AVI RIFF 'movi' list */ |
| 103 | uint64_t data_size; /**< Size of the chunk containing data packets */ |
| 104 | uint32_t index_offset; /**< Offset to the start of index data e.g. |
| 105 | the data in an 'idx1' list */ |
| 106 | unsigned current_track_num; /**< Number of track currently being written */ |
| 107 | uint32_t chunk_size; /**< Final size of the current chunk being written (if known) */ |
| 108 | uint32_t chunk_data_written; /**< Data written to the current chunk so far */ |
| 109 | uint8_t *avi_frame_buffer; /**< For accumulating whole frames when seeking isn't available. */ |
| 110 | VC_CONTAINER_PACKET_T frame_packet; /**< Packet header for whole frame. */ |
| 111 | |
| 112 | VC_CONTAINER_STATUS_T index_status; |
| 113 | } VC_CONTAINER_MODULE_T; |
| 114 | |
| 115 | /****************************************************************************** |
| 116 | Local Functions |
| 117 | ******************************************************************************/ |
| 118 | static void avi_chunk_id_from_track_num( VC_CONTAINER_T *p_ctx, |
| 119 | VC_CONTAINER_FOURCC_T *p_chunk_id, unsigned int track_num ) |
| 120 | { |
| 121 | VC_CONTAINER_TRACK_T *track = p_ctx->tracks[track_num]; |
| 122 | VC_CONTAINER_FOURCC_T chunk_id = 0; |
| 123 | char track_num_buf[3]; |
| 124 | |
| 125 | if(track->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO) |
| 126 | chunk_id = VC_FOURCC('0','0','d','c'); |
| 127 | else if(track->format->es_type == VC_CONTAINER_ES_TYPE_AUDIO) |
| 128 | chunk_id = VC_FOURCC('0','0','w','b'); |
| 129 | else |
| 130 | { |
| 131 | /* Note that avi_writer_add_track should ensure this |
| 132 | can't happen */ |
| 133 | *p_chunk_id = VC_FOURCC('J','U','N','K'); return; |
| 134 | } |
| 135 | |
| 136 | snprintf(track_num_buf, sizeof(track_num_buf), "%02d" , track_num); |
| 137 | memcpy(&chunk_id, track_num_buf, 2); |
| 138 | |
| 139 | *p_chunk_id = chunk_id; |
| 140 | } |
| 141 | |
| 142 | /*****************************************************************************/ |
| 143 | static void avi_index_chunk_id_from_track_num(VC_CONTAINER_FOURCC_T *p_chunk_id, |
| 144 | unsigned int track_num ) |
| 145 | { |
| 146 | VC_CONTAINER_FOURCC_T chunk_id = 0; |
| 147 | char track_num_buf[3]; |
| 148 | |
| 149 | chunk_id = VC_FOURCC('i','x','0','0'); |
| 150 | |
| 151 | snprintf(track_num_buf, sizeof(track_num_buf), "%02d" , track_num); |
| 152 | memcpy(((uint8_t*)&chunk_id) + 2, track_num_buf, 2); |
| 153 | |
| 154 | *p_chunk_id = chunk_id; |
| 155 | } |
| 156 | |
| 157 | /*****************************************************************************/ |
| 158 | static uint32_t avi_num_chunks( VC_CONTAINER_T *p_ctx ) |
| 159 | { |
| 160 | unsigned int i; |
| 161 | uint32_t num_chunks = 0; |
| 162 | for (i = 0; i < p_ctx->tracks_num; i++) |
| 163 | num_chunks += p_ctx->tracks[i]->priv->module->chunk_index; |
| 164 | |
| 165 | return num_chunks; |
| 166 | } |
| 167 | |
| 168 | /*****************************************************************************/ |
| 169 | static VC_CONTAINER_STATUS_T avi_finish_data_chunk( VC_CONTAINER_T *p_ctx, uint32_t chunk_size ) |
| 170 | { |
| 171 | VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; |
| 172 | |
| 173 | if (chunk_size) |
| 174 | { |
| 175 | /* Rewrite the chunk size, this won't be efficient if it happens often */ |
| 176 | if (STREAM_SEEKABLE(p_ctx)) |
| 177 | { |
| 178 | SEEK(p_ctx, STREAM_POSITION(p_ctx) - chunk_size - 4); |
| 179 | WRITE_U32(p_ctx, chunk_size, "Chunk Size" ); |
| 180 | SKIP_BYTES(p_ctx, chunk_size); |
| 181 | } |
| 182 | else |
| 183 | { |
| 184 | LOG_DEBUG(p_ctx, "warning, can't rewrite chunk size, data will be malformed" ); |
| 185 | status = VC_CONTAINER_ERROR_FAILED; |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | AVI_END_CHUNK(p_ctx); |
| 190 | |
| 191 | if (status != VC_CONTAINER_SUCCESS) status = STREAM_STATUS(p_ctx); |
| 192 | |
| 193 | return status; |
| 194 | } |
| 195 | |
| 196 | /*****************************************************************************/ |
| 197 | static VC_CONTAINER_STATUS_T avi_write_index_entry( VC_CONTAINER_T *p_ctx, uint8_t track_num, |
| 198 | uint32_t chunk_size, int keyframe ) |
| 199 | { |
| 200 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
| 201 | uint32_t deltaframe = keyframe ? 0 : AVI_INDEX_DELTAFRAME; |
| 202 | |
| 203 | vc_container_io_write_uint8(module->temp_io.io, track_num); |
| 204 | vc_container_io_write_be_uint32(module->temp_io.io, chunk_size | deltaframe); |
| 205 | |
| 206 | if (module->temp_io.io->status != VC_CONTAINER_SUCCESS) |
| 207 | { |
| 208 | module->index_status = module->temp_io.io->status; |
| 209 | LOG_DEBUG(p_ctx, "warning, couldn't store index data, index data will be incorrect" ); |
| 210 | } |
| 211 | |
| 212 | return module->temp_io.io->status; |
| 213 | } |
| 214 | |
| 215 | /*****************************************************************************/ |
| 216 | static VC_CONTAINER_STATUS_T avi_read_index_entry( VC_CONTAINER_T *p_ctx, |
| 217 | unsigned int *p_track_num, uint32_t *p_chunk_size ) |
| 218 | { |
| 219 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
| 220 | uint32_t chunk_size; |
| 221 | uint8_t track_num; |
| 222 | |
| 223 | track_num = vc_container_io_read_uint8(module->temp_io.io); |
| 224 | chunk_size = vc_container_io_read_be_uint32(module->temp_io.io); |
| 225 | |
| 226 | /* This shouldn't really happen if the temporary I/O is reliable */ |
| 227 | if (track_num >= p_ctx->tracks_num) return VC_CONTAINER_ERROR_FAILED; |
| 228 | |
| 229 | *p_track_num = track_num; |
| 230 | *p_chunk_size = chunk_size; |
| 231 | |
| 232 | return module->temp_io.io->status; |
| 233 | } |
| 234 | |
| 235 | /*****************************************************************************/ |
| 236 | static VC_CONTAINER_STATUS_T avi_write_stream_format_chunk(VC_CONTAINER_T *p_ctx, |
| 237 | VC_CONTAINER_TRACK_T *track, uint32_t chunk_size) |
| 238 | { |
| 239 | VC_CONTAINER_STATUS_T status; |
| 240 | |
| 241 | WRITE_FOURCC(p_ctx, VC_FOURCC('s','t','r','f'), "Chunk ID" ); |
| 242 | WRITE_U32(p_ctx, chunk_size, "Chunk Size" ); |
| 243 | |
| 244 | if ((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) return status; |
| 245 | |
| 246 | if(track->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO) |
| 247 | status = vc_container_write_bitmapinfoheader(p_ctx, track->format); |
| 248 | else if(track->format->es_type == VC_CONTAINER_ES_TYPE_AUDIO) |
| 249 | status = vc_container_write_waveformatex(p_ctx, track->format); |
| 250 | |
| 251 | if (status != VC_CONTAINER_SUCCESS) return status; |
| 252 | |
| 253 | AVI_END_CHUNK(p_ctx); |
| 254 | |
| 255 | return STREAM_STATUS(p_ctx); |
| 256 | } |
| 257 | |
| 258 | /*****************************************************************************/ |
| 259 | static VC_CONTAINER_STATUS_T (VC_CONTAINER_T *p_ctx, |
| 260 | VC_CONTAINER_TRACK_T *track) |
| 261 | { |
| 262 | VC_CONTAINER_FOURCC_T fourcc_type = 0, fourcc_handler = 0; |
| 263 | uint32_t flags, scale = 0, rate = 0, div, start = 0, sample_size = 0; |
| 264 | uint16_t left = 0, right = 0, top = 0, bottom = 0; |
| 265 | uint32_t max_chunk_size, length = 0; |
| 266 | |
| 267 | WRITE_FOURCC(p_ctx, VC_FOURCC('s','t','r','h'), "Chunk ID" ); |
| 268 | WRITE_U32(p_ctx, 56, "Chunk Size" ); |
| 269 | |
| 270 | if (!track->is_enabled) |
| 271 | flags = 0; /* AVISF_DISABLED; FIXME: write_media should set this correctly! */ |
| 272 | else |
| 273 | flags = 0; |
| 274 | |
| 275 | if (track->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO) |
| 276 | { |
| 277 | fourcc_type = VC_FOURCC('v','i','d','s'); |
| 278 | sample_size = 0; |
| 279 | scale = track->format->type->video.frame_rate_den; |
| 280 | rate = track->format->type->video.frame_rate_num; |
| 281 | if (rate == 0 || scale == 0) |
| 282 | { |
| 283 | LOG_DEBUG(p_ctx, "invalid video framerate (%d/%d)" , rate, scale); |
| 284 | LOG_DEBUG(p_ctx, "using 30/1 (playback timing will almost certainly be incorrect)" ); |
| 285 | scale = 1; rate = 30; |
| 286 | } |
| 287 | |
| 288 | top = track->format->type->video.y_offset; |
| 289 | left = track->format->type->video.x_offset; |
| 290 | bottom = track->format->type->video.y_offset + track->format->type->video.visible_height; |
| 291 | right = track->format->type->video.x_offset + track->format->type->video.visible_width; |
| 292 | } |
| 293 | else if (track->format->es_type == VC_CONTAINER_ES_TYPE_AUDIO) |
| 294 | { |
| 295 | fourcc_type = VC_FOURCC('a','u','d','s'); |
| 296 | sample_size = track->format->type->audio.block_align; |
| 297 | scale = 1; |
| 298 | |
| 299 | if (track->format->type->audio.block_align) |
| 300 | rate = (track->format->bitrate / track->format->type->audio.block_align) >> 3; |
| 301 | |
| 302 | if (rate == 0) |
| 303 | { |
| 304 | rate = track->format->type->audio.sample_rate ? track->format->type->audio.sample_rate : 32000; |
| 305 | LOG_DEBUG(p_ctx, "invalid audio rate, using %d (playback timing will almost certainly be incorrect)" , |
| 306 | rate); |
| 307 | } |
| 308 | } |
| 309 | else |
| 310 | { |
| 311 | /* avi_writer_add_track should ensure this can't happen */ |
| 312 | vc_container_assert(0); |
| 313 | } |
| 314 | |
| 315 | fourcc_handler = codec_to_vfw_fourcc(track->format->codec); |
| 316 | |
| 317 | div = vc_container_maths_gcd((int64_t)scale, (int64_t)rate); |
| 318 | scale /= div; |
| 319 | rate /= div; |
| 320 | |
| 321 | length = sample_size ? track->priv->module->chunk_offs : track->priv->module->chunk_index; |
| 322 | max_chunk_size = track->priv->module->max_chunk_size; |
| 323 | track->priv->module->sample_size = sample_size; |
| 324 | |
| 325 | WRITE_FOURCC(p_ctx, fourcc_type, "fccType" ); |
| 326 | WRITE_FOURCC(p_ctx, fourcc_handler, "fccHandler" ); |
| 327 | WRITE_U32(p_ctx, flags, "dwFlags" ); |
| 328 | WRITE_U16(p_ctx, 0, "wPriority" ); |
| 329 | WRITE_U16(p_ctx, 0, "wLanguage" ); |
| 330 | WRITE_U32(p_ctx, 0, "dwInitialFrames" ); |
| 331 | WRITE_U32(p_ctx, scale, "dwScale" ); |
| 332 | WRITE_U32(p_ctx, rate, "dwRate" ); |
| 333 | WRITE_U32(p_ctx, start, "dwStart" ); |
| 334 | WRITE_U32(p_ctx, length, "dwLength" ); |
| 335 | WRITE_U32(p_ctx, max_chunk_size, "dwSuggestedBufferSize" ); |
| 336 | WRITE_U32(p_ctx, 0, "dwQuality" ); |
| 337 | WRITE_U32(p_ctx, sample_size, "dwSampleSize" ); |
| 338 | WRITE_U16(p_ctx, left, "rcFrame.left" ); |
| 339 | WRITE_U16(p_ctx, top, "rcFrame.top" ); |
| 340 | WRITE_U16(p_ctx, right, "rcFrame.right" ); |
| 341 | WRITE_U16(p_ctx, bottom, "rcFrame.bottom" ); |
| 342 | |
| 343 | return STREAM_STATUS(p_ctx); |
| 344 | } |
| 345 | |
| 346 | /*****************************************************************************/ |
| 347 | static VC_CONTAINER_STATUS_T avi_write_super_index_chunk(VC_CONTAINER_T *p_ctx, unsigned int index_track_num, |
| 348 | uint32_t index_size) |
| 349 | { |
| 350 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
| 351 | VC_CONTAINER_TRACK_MODULE_T *track_module = p_ctx->tracks[index_track_num]->priv->module; |
| 352 | VC_CONTAINER_FOURCC_T chunk_id; |
| 353 | uint32_t num_indices = 1; /* FIXME: support for multiple RIFF chunks (AVIX) */ |
| 354 | unsigned int i; |
| 355 | |
| 356 | if(module->null_io.refcount) |
| 357 | { |
| 358 | /* Assume that we're not actually writing the data, just want know the index chunk size */ |
| 359 | WRITE_BYTES(p_ctx, NULL, 8 + 24 + num_indices * (int64_t)AVI_SUPER_INDEX_ENTRY_SIZE); |
| 360 | return STREAM_STATUS(p_ctx); |
| 361 | } |
| 362 | |
| 363 | if (track_module->index_offset) |
| 364 | WRITE_FOURCC(p_ctx, VC_FOURCC('i','n','d','x'), "Chunk ID" ); |
| 365 | else |
| 366 | WRITE_FOURCC(p_ctx, VC_FOURCC('J','U','N','K'), "Chunk ID" ); |
| 367 | |
| 368 | WRITE_U32(p_ctx, index_size, "Chunk Size" ); |
| 369 | |
| 370 | avi_chunk_id_from_track_num(p_ctx, &chunk_id, index_track_num); |
| 371 | WRITE_U16(p_ctx, 4, "wLongsPerEntry" ); |
| 372 | WRITE_U8(p_ctx, 0, "bIndexSubType" ); |
| 373 | WRITE_U8(p_ctx, AVI_INDEX_OF_INDEXES, "bIndexType" ); |
| 374 | WRITE_U32(p_ctx, num_indices, "nEntriesInUse" ); |
| 375 | WRITE_FOURCC(p_ctx, chunk_id, "dwChunkId" ); |
| 376 | WRITE_U32(p_ctx, 0, "dwReserved0" ); |
| 377 | WRITE_U32(p_ctx, 0, "dwReserved1" ); |
| 378 | WRITE_U32(p_ctx, 0, "dwReserved2" ); |
| 379 | |
| 380 | for (i = 0; i < num_indices; ++i) |
| 381 | { |
| 382 | uint64_t index_offset = track_module->index_offset; |
| 383 | uint32_t chunk_size = track_module->index_size; |
| 384 | uint32_t length = track_module->sample_size ? |
| 385 | track_module->chunk_offs : track_module->chunk_index; |
| 386 | WRITE_U64(p_ctx, index_offset, "qwOffset" ); |
| 387 | WRITE_U32(p_ctx, chunk_size, "dwSize" ); |
| 388 | WRITE_U32(p_ctx, length, "dwDuration" ); |
| 389 | } |
| 390 | |
| 391 | AVI_END_CHUNK(p_ctx); |
| 392 | |
| 393 | return STREAM_STATUS(p_ctx); |
| 394 | } |
| 395 | |
| 396 | /*****************************************************************************/ |
| 397 | static VC_CONTAINER_STATUS_T (VC_CONTAINER_T *p_ctx, |
| 398 | unsigned int track_num, uint32_t list_size) |
| 399 | { |
| 400 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
| 401 | VC_CONTAINER_TRACK_T *track = p_ctx->tracks[track_num]; |
| 402 | VC_CONTAINER_STATUS_T status; |
| 403 | uint32_t chunk_size = 0; |
| 404 | |
| 405 | WRITE_FOURCC(p_ctx, VC_FOURCC('L','I','S','T'), "Chunk ID" ); |
| 406 | WRITE_U32(p_ctx, list_size, "LIST Size" ); |
| 407 | WRITE_FOURCC(p_ctx, VC_FOURCC('s','t','r','l'), "Chunk ID" ); |
| 408 | |
| 409 | if ((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) return status; |
| 410 | |
| 411 | /* Write the stream header chunk ('strh') */ |
| 412 | status = avi_write_stream_header_chunk(p_ctx, track); |
| 413 | if (status != VC_CONTAINER_SUCCESS) return status; |
| 414 | |
| 415 | /* Write the stream format chunk ('strf') */ |
| 416 | if(!vc_container_writer_extraio_enable(p_ctx, &module->null_io)) |
| 417 | { |
| 418 | status = avi_write_stream_format_chunk(p_ctx, track, 0); |
| 419 | chunk_size = STREAM_POSITION(p_ctx) - 8; |
| 420 | } |
| 421 | vc_container_writer_extraio_disable(p_ctx, &module->null_io); |
| 422 | |
| 423 | status = avi_write_stream_format_chunk(p_ctx, track, chunk_size); |
| 424 | if (status != VC_CONTAINER_SUCCESS) return status; |
| 425 | |
| 426 | /* If the track has DRM data, write it into the 'strd' chunk (we don't write |
| 427 | write codec configuration data into 'strd') */ |
| 428 | if (track->priv->drmdata && track->priv->drmdata_size) |
| 429 | { |
| 430 | WRITE_FOURCC(p_ctx, VC_FOURCC('s','t','r','d'), "Chunk ID" ); |
| 431 | WRITE_U32(p_ctx, track->priv->drmdata_size, "Chunk Size" ); |
| 432 | WRITE_BYTES(p_ctx, track->priv->drmdata, track->priv->drmdata_size); |
| 433 | AVI_END_CHUNK(p_ctx); |
| 434 | if ((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) return status; |
| 435 | } |
| 436 | |
| 437 | /* Write the super index chunk ('indx') */ |
| 438 | if(!vc_container_writer_extraio_enable(p_ctx, &module->null_io)) |
| 439 | { |
| 440 | status = avi_write_super_index_chunk(p_ctx, track_num, 0); |
| 441 | chunk_size = STREAM_POSITION(p_ctx) - 8; |
| 442 | } |
| 443 | vc_container_writer_extraio_disable(p_ctx, &module->null_io); |
| 444 | |
| 445 | status = avi_write_super_index_chunk(p_ctx, track_num, chunk_size); |
| 446 | if (status != VC_CONTAINER_SUCCESS) return status; |
| 447 | |
| 448 | AVI_END_CHUNK(p_ctx); |
| 449 | |
| 450 | return STREAM_STATUS(p_ctx); |
| 451 | } |
| 452 | |
| 453 | /*****************************************************************************/ |
| 454 | static VC_CONTAINER_STATUS_T (VC_CONTAINER_T *p_ctx) |
| 455 | { |
| 456 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
| 457 | uint32_t bitrate = 0, width = 0, height = 0, frame_interval = 0; |
| 458 | uint32_t flags, num_chunks = 0, max_video_chunk_size = 0; |
| 459 | uint32_t num_streams = p_ctx->tracks_num; |
| 460 | unsigned int i; |
| 461 | |
| 462 | for (i = 0; i < p_ctx->tracks_num; i++) |
| 463 | { |
| 464 | VC_CONTAINER_TRACK_T *track = p_ctx->tracks[i]; |
| 465 | VC_CONTAINER_TRACK_MODULE_T *track_module = p_ctx->tracks[i]->priv->module; |
| 466 | if (track->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO) |
| 467 | { |
| 468 | width = track->format->type->video.width; |
| 469 | height = track->format->type->video.height; |
| 470 | if (track->format->type->video.frame_rate_num) |
| 471 | frame_interval = track->format->type->video.frame_rate_den * UINT64_C(1000000) / |
| 472 | track->format->type->video.frame_rate_num; |
| 473 | num_chunks = track_module->chunk_index; |
| 474 | max_video_chunk_size = track_module->max_chunk_size; |
| 475 | break; |
| 476 | } |
| 477 | } |
| 478 | |
| 479 | flags = (module->index_offset && module->index_status == VC_CONTAINER_SUCCESS) ? |
| 480 | (AVIF_HASINDEX | AVIF_TRUSTCKTYPE) : 0; |
| 481 | |
| 482 | WRITE_FOURCC(p_ctx, VC_FOURCC('a','v','i','h'), "Chunk ID" ); |
| 483 | WRITE_U32(p_ctx, 56, "Chunk Size" ); |
| 484 | WRITE_U32(p_ctx, frame_interval, "dwMicroSecPerFrame" ); |
| 485 | WRITE_U32(p_ctx, bitrate >> 3, "dwMaxBytesPerSec" ); |
| 486 | WRITE_U32(p_ctx, 0, "dwPaddingGranularity" ); |
| 487 | WRITE_U32(p_ctx, flags, "dwFlags" ); |
| 488 | WRITE_U32(p_ctx, num_chunks, "dwTotalFrames" ); |
| 489 | WRITE_U32(p_ctx, 0, "dwInitialFrames" ); |
| 490 | WRITE_U32(p_ctx, num_streams, "dwStreams" ); |
| 491 | WRITE_U32(p_ctx, max_video_chunk_size, "dwSuggestedBufferSize" ); |
| 492 | WRITE_U32(p_ctx, width, "dwWidth" ); |
| 493 | WRITE_U32(p_ctx, height, "dwHeight" ); |
| 494 | WRITE_U32(p_ctx, 0, "dwReserved0" ); |
| 495 | WRITE_U32(p_ctx, 0, "dwReserved1" ); |
| 496 | WRITE_U32(p_ctx, 0, "dwReserved2" ); |
| 497 | WRITE_U32(p_ctx, 0, "dwReserved3" ); |
| 498 | |
| 499 | return STREAM_STATUS(p_ctx); |
| 500 | } |
| 501 | |
| 502 | /*****************************************************************************/ |
| 503 | static VC_CONTAINER_STATUS_T ( VC_CONTAINER_T *p_ctx, uint32_t ) |
| 504 | { |
| 505 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
| 506 | VC_CONTAINER_STATUS_T status; |
| 507 | unsigned int i; |
| 508 | |
| 509 | WRITE_FOURCC(p_ctx, VC_FOURCC('L','I','S','T'), "Chunk ID" ); |
| 510 | WRITE_U32(p_ctx, header_list_size, "LIST Size" ); |
| 511 | WRITE_FOURCC(p_ctx, VC_FOURCC('h','d','r','l'), "Chunk ID" ); |
| 512 | if ((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) return status; |
| 513 | |
| 514 | /* Write the main AVI header chunk ('avih') */ |
| 515 | if ((status = avi_write_avi_header_chunk(p_ctx)) != VC_CONTAINER_SUCCESS) |
| 516 | return status; |
| 517 | |
| 518 | for (i = 0; i < p_ctx->tracks_num; i++) |
| 519 | { |
| 520 | uint32_t list_size = 0; |
| 521 | |
| 522 | /* Write a stream header list chunk ('strl') */ |
| 523 | if(!vc_container_writer_extraio_enable(p_ctx, &module->null_io)) |
| 524 | { |
| 525 | status = avi_write_stream_header_list(p_ctx, i, 0); |
| 526 | if (status != VC_CONTAINER_SUCCESS) return status; |
| 527 | list_size = STREAM_POSITION(p_ctx) - 8; |
| 528 | } |
| 529 | vc_container_writer_extraio_disable(p_ctx, &module->null_io); |
| 530 | |
| 531 | status = avi_write_stream_header_list(p_ctx, i, list_size); |
| 532 | if (status != VC_CONTAINER_SUCCESS) return status; |
| 533 | } |
| 534 | |
| 535 | return status; |
| 536 | } |
| 537 | |
| 538 | /*****************************************************************************/ |
| 539 | static VC_CONTAINER_STATUS_T ( VC_CONTAINER_T *p_ctx ) |
| 540 | { |
| 541 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
| 542 | VC_CONTAINER_STATUS_T status; |
| 543 | uint32_t , = 0; |
| 544 | |
| 545 | /* Write the header list chunk ('hdrl') */ |
| 546 | if(!vc_container_writer_extraio_enable(p_ctx, &module->null_io)) |
| 547 | { |
| 548 | status = avi_write_header_list(p_ctx, 0); |
| 549 | if (status != VC_CONTAINER_SUCCESS) return status; |
| 550 | header_list_size = STREAM_POSITION(p_ctx) - 8; |
| 551 | } |
| 552 | vc_container_writer_extraio_disable(p_ctx, &module->null_io); |
| 553 | |
| 554 | header_list_offset = STREAM_POSITION(p_ctx); |
| 555 | status = avi_write_header_list(p_ctx, header_list_size); |
| 556 | if (status == VC_CONTAINER_SUCCESS && !module->header_list_offset) |
| 557 | { |
| 558 | module->header_list_offset = header_list_offset; |
| 559 | module->header_list_size = header_list_size; |
| 560 | } |
| 561 | |
| 562 | return status; |
| 563 | } |
| 564 | |
| 565 | /*****************************************************************************/ |
| 566 | static VC_CONTAINER_STATUS_T avi_write_legacy_index_chunk( VC_CONTAINER_T *p_ctx, uint32_t index_size ) |
| 567 | { |
| 568 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
| 569 | VC_CONTAINER_STATUS_T status; |
| 570 | uint32_t chunk_offset = 4; |
| 571 | unsigned int track_num; |
| 572 | |
| 573 | vc_container_assert(8 + avi_num_chunks(p_ctx) * INT64_C(16) <= (int64_t)ULONG_MAX); |
| 574 | |
| 575 | if(module->null_io.refcount) |
| 576 | { |
| 577 | /* Assume that we're not actually writing the data, |
| 578 | just want know the index size */ |
| 579 | WRITE_BYTES(p_ctx, NULL, 8 + avi_num_chunks(p_ctx) * (int64_t)AVI_INDEX_ENTRY_SIZE); |
| 580 | return STREAM_STATUS(p_ctx); |
| 581 | } |
| 582 | |
| 583 | module->index_offset = STREAM_POSITION(p_ctx); |
| 584 | |
| 585 | WRITE_FOURCC(p_ctx, VC_FOURCC('i','d','x','1'), "Chunk ID" ); |
| 586 | WRITE_U32(p_ctx, index_size, "Chunk Size" ); |
| 587 | |
| 588 | /* Scan through all written entries, convert to appropriate index format */ |
| 589 | vc_container_io_seek(module->temp_io.io, INT64_C(0)); |
| 590 | |
| 591 | while((status = STREAM_STATUS(p_ctx)) == VC_CONTAINER_SUCCESS) |
| 592 | { |
| 593 | VC_CONTAINER_FOURCC_T chunk_id; |
| 594 | uint32_t chunk_size, flags; |
| 595 | |
| 596 | status = avi_read_index_entry(p_ctx, &track_num, &chunk_size); |
| 597 | if (status != VC_CONTAINER_SUCCESS) break; |
| 598 | |
| 599 | avi_chunk_id_from_track_num(p_ctx, &chunk_id, track_num); |
| 600 | flags = (chunk_size & AVI_INDEX_DELTAFRAME) ? 0 : AVIIF_KEYFRAME; |
| 601 | chunk_size &= ~AVI_INDEX_DELTAFRAME; |
| 602 | |
| 603 | WRITE_FOURCC(p_ctx, chunk_id, "Chunk ID" ); |
| 604 | WRITE_U32(p_ctx, flags, "dwFlags" ); |
| 605 | WRITE_U32(p_ctx, chunk_offset, "dwOffset" ); |
| 606 | WRITE_U32(p_ctx, chunk_size, "dwSize" ); |
| 607 | |
| 608 | chunk_offset += ((chunk_size + 1) & ~1) + 8; |
| 609 | } |
| 610 | |
| 611 | AVI_END_CHUNK(p_ctx); |
| 612 | |
| 613 | /* Note that currently, we might write a partial index but still set AVIF_HASINDEX */ |
| 614 | /* if ( STREAM_STATUS(p_ctx) != VC_CONTAINER_SUCCESS ) module->index_offset = 0 */ |
| 615 | |
| 616 | return STREAM_STATUS(p_ctx); |
| 617 | } |
| 618 | |
| 619 | /*****************************************************************************/ |
| 620 | static VC_CONTAINER_STATUS_T avi_write_standard_index_chunk( VC_CONTAINER_T *p_ctx, unsigned int index_track_num, |
| 621 | uint32_t index_size ) |
| 622 | { |
| 623 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
| 624 | VC_CONTAINER_TRACK_MODULE_T *track_module = p_ctx->tracks[index_track_num]->priv->module; |
| 625 | VC_CONTAINER_STATUS_T status; |
| 626 | VC_CONTAINER_FOURCC_T chunk_id; |
| 627 | int64_t base_offset = module->data_offset + 12; |
| 628 | uint32_t num_chunks = track_module->chunk_index; |
| 629 | uint32_t chunk_offset = 4; |
| 630 | |
| 631 | vc_container_assert(32 + num_chunks * (int64_t)AVI_STD_INDEX_ENTRY_SIZE <= (int64_t)ULONG_MAX); |
| 632 | |
| 633 | if(module->null_io.refcount) |
| 634 | { |
| 635 | /* Assume that we're not actually writing the data, just want know the index chunk size */ |
| 636 | WRITE_BYTES(p_ctx, NULL, 8 + 24 + num_chunks * INT64_C(8)); |
| 637 | return STREAM_STATUS(p_ctx); |
| 638 | } |
| 639 | |
| 640 | track_module->index_offset = STREAM_POSITION(p_ctx); |
| 641 | track_module->index_size = index_size ? (index_size - 8) : 0; |
| 642 | |
| 643 | avi_index_chunk_id_from_track_num(&chunk_id, index_track_num); |
| 644 | WRITE_FOURCC(p_ctx, chunk_id, "Chunk ID" ); |
| 645 | WRITE_U32(p_ctx, index_size, "Chunk Size" ); |
| 646 | |
| 647 | avi_chunk_id_from_track_num(p_ctx, &chunk_id, index_track_num); |
| 648 | WRITE_U16(p_ctx, 2, "wLongsPerEntry" ); |
| 649 | WRITE_U8(p_ctx, 0, "bIndexSubType" ); |
| 650 | WRITE_U8(p_ctx, AVI_INDEX_OF_CHUNKS, "bIndexType" ); |
| 651 | WRITE_U32(p_ctx, num_chunks, "nEntriesInUse" ); |
| 652 | WRITE_FOURCC(p_ctx, chunk_id, "dwChunkId" ); |
| 653 | WRITE_U64(p_ctx, base_offset, "qwBaseOffset" ); |
| 654 | WRITE_U32(p_ctx, 0, "dwReserved" ); |
| 655 | |
| 656 | /* Scan through all written entries, convert to appropriate index format */ |
| 657 | vc_container_io_seek(module->temp_io.io, INT64_C(0)); |
| 658 | |
| 659 | while(STREAM_STATUS(p_ctx) == VC_CONTAINER_SUCCESS) |
| 660 | { |
| 661 | uint32_t chunk_size; |
| 662 | unsigned int track_num; |
| 663 | |
| 664 | status = avi_read_index_entry(p_ctx, &track_num, &chunk_size); |
| 665 | if (status != VC_CONTAINER_SUCCESS) break; |
| 666 | |
| 667 | if(track_num != index_track_num) continue; |
| 668 | |
| 669 | WRITE_U32(p_ctx, chunk_offset, "dwOffset" ); |
| 670 | WRITE_U32(p_ctx, chunk_size, "dwSize" ); |
| 671 | |
| 672 | chunk_offset += ((chunk_size + 1) & ~(1 | AVI_INDEX_DELTAFRAME)) + 12; |
| 673 | } |
| 674 | |
| 675 | AVI_END_CHUNK(p_ctx); |
| 676 | |
| 677 | return STREAM_STATUS(p_ctx); |
| 678 | } |
| 679 | |
| 680 | /*****************************************************************************/ |
| 681 | static VC_CONTAINER_STATUS_T avi_write_legacy_index_data( VC_CONTAINER_T *p_ctx ) |
| 682 | { |
| 683 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
| 684 | VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; |
| 685 | uint32_t chunk_size = 0; |
| 686 | |
| 687 | /* Write the legacy index chunk ('idx1') */ |
| 688 | if(!vc_container_writer_extraio_enable(p_ctx, &module->null_io)) |
| 689 | { |
| 690 | status = avi_write_legacy_index_chunk(p_ctx, 0); |
| 691 | if (status != VC_CONTAINER_SUCCESS) return status; |
| 692 | chunk_size = STREAM_POSITION(p_ctx) - 8; |
| 693 | } |
| 694 | vc_container_writer_extraio_disable(p_ctx, &module->null_io); |
| 695 | |
| 696 | status = avi_write_legacy_index_chunk(p_ctx, chunk_size); |
| 697 | return status; |
| 698 | } |
| 699 | |
| 700 | /*****************************************************************************/ |
| 701 | static VC_CONTAINER_STATUS_T avi_write_standard_index_data( VC_CONTAINER_T *p_ctx ) |
| 702 | { |
| 703 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
| 704 | VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; |
| 705 | uint32_t chunk_size = 0; |
| 706 | unsigned int i; |
| 707 | |
| 708 | /* Write the standard index chunks ('ix00') */ |
| 709 | for (i = 0; i < p_ctx->tracks_num; i++) |
| 710 | { |
| 711 | if(!vc_container_writer_extraio_enable(p_ctx, &module->null_io)) |
| 712 | { |
| 713 | status = avi_write_standard_index_chunk(p_ctx, i, 0); |
| 714 | if (status != VC_CONTAINER_SUCCESS) return status; |
| 715 | chunk_size = STREAM_POSITION(p_ctx) - 8; |
| 716 | } |
| 717 | vc_container_writer_extraio_disable(p_ctx, &module->null_io); |
| 718 | |
| 719 | status = avi_write_standard_index_chunk(p_ctx, i, chunk_size); |
| 720 | if (status != VC_CONTAINER_SUCCESS) return status; |
| 721 | } |
| 722 | |
| 723 | return status; |
| 724 | } |
| 725 | |
| 726 | /*****************************************************************************/ |
| 727 | static int64_t avi_calculate_file_size( VC_CONTAINER_T *p_ctx, |
| 728 | VC_CONTAINER_PACKET_T *p_packet ) |
| 729 | { |
| 730 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
| 731 | VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; |
| 732 | int64_t filesize = 0; |
| 733 | int refcount; |
| 734 | |
| 735 | /* Start from current file position */ |
| 736 | filesize = STREAM_POSITION(p_ctx); |
| 737 | |
| 738 | refcount = vc_container_writer_extraio_enable(p_ctx, &module->null_io); |
| 739 | vc_container_assert(refcount == 0); /* Although perfectly harmless, we should |
| 740 | not be called with the null i/o enabled. */ |
| 741 | VC_CONTAINER_PARAM_UNUSED(refcount); |
| 742 | |
| 743 | do { |
| 744 | /* If we know what the final size of the chunk is going to be, |
| 745 | we can use that here to avoid writing a partial final packet */ |
| 746 | WRITE_BYTES(p_ctx, NULL, p_packet->frame_size ? p_packet->frame_size : p_packet->size); |
| 747 | AVI_END_CHUNK(p_ctx); |
| 748 | |
| 749 | /* Index entries for the chunk */ |
| 750 | WRITE_BYTES(p_ctx, NULL, AVI_INDEX_ENTRY_SIZE + AVI_STD_INDEX_ENTRY_SIZE); |
| 751 | |
| 752 | /* Current standard index data */ |
| 753 | if (avi_write_standard_index_data(p_ctx) != VC_CONTAINER_SUCCESS) break; |
| 754 | |
| 755 | /* Current legacy index data */ |
| 756 | status = avi_write_legacy_index_data(p_ctx); |
| 757 | if (status != VC_CONTAINER_SUCCESS) break; |
| 758 | } while(0); |
| 759 | |
| 760 | filesize += STREAM_POSITION(p_ctx); |
| 761 | |
| 762 | vc_container_writer_extraio_disable(p_ctx, &module->null_io); |
| 763 | |
| 764 | return filesize; |
| 765 | } |
| 766 | |
| 767 | /***************************************************************************** |
| 768 | Functions exported as part of the Container Module API |
| 769 | *****************************************************************************/ |
| 770 | |
| 771 | static VC_CONTAINER_STATUS_T avi_writer_write( VC_CONTAINER_T *p_ctx, |
| 772 | VC_CONTAINER_PACKET_T *p_packet ) |
| 773 | { |
| 774 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
| 775 | VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; |
| 776 | VC_CONTAINER_TRACK_T *track = NULL; |
| 777 | VC_CONTAINER_TRACK_MODULE_T *track_module = NULL; |
| 778 | |
| 779 | /* Check we have written headers before any data */ |
| 780 | if(!module->headers_written) |
| 781 | { |
| 782 | if ((status = avi_write_headers(p_ctx)) != VC_CONTAINER_SUCCESS) return status; |
| 783 | module->headers_written = 1; |
| 784 | } |
| 785 | |
| 786 | /* Check that we have started the 'movi' list */ |
| 787 | if (!module->data_offset) |
| 788 | { |
| 789 | module->data_offset = STREAM_POSITION(p_ctx); |
| 790 | vc_container_assert(module->data_offset != INT64_C(0)); |
| 791 | |
| 792 | WRITE_FOURCC(p_ctx, VC_FOURCC('L','I','S','T'), "Chunk ID" ); |
| 793 | WRITE_U32(p_ctx, 0, "LIST Size" ); |
| 794 | WRITE_FOURCC(p_ctx, VC_FOURCC('m','o','v','i'), "Chunk ID" ); |
| 795 | if ((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) return status; |
| 796 | } |
| 797 | |
| 798 | /* If the container is passing in a frame from a new track but we |
| 799 | arent't finished with a chunk from another track we need to finish |
| 800 | that chunk first */ |
| 801 | if (module->chunk_data_written && p_packet->track != module->current_track_num) |
| 802 | { |
| 803 | track_module = p_ctx->tracks[module->current_track_num]->priv->module; |
| 804 | status = avi_finish_data_chunk(p_ctx, module->chunk_data_written); |
| 805 | avi_write_index_entry(p_ctx, module->current_track_num, module->chunk_data_written, 0); |
| 806 | track_module->chunk_index++; |
| 807 | track_module->chunk_offs += module->chunk_data_written; |
| 808 | track_module->max_chunk_size = MAX(track_module->max_chunk_size, module->chunk_data_written); |
| 809 | module->chunk_data_written = 0; |
| 810 | if (status != VC_CONTAINER_SUCCESS) return status; |
| 811 | } |
| 812 | |
| 813 | /* Check we are not about to go over the limit of total number of chunks */ |
| 814 | if (avi_num_chunks(p_ctx) == (uint32_t)ULONG_MAX) return VC_CONTAINER_ERROR_OUT_OF_RESOURCES; |
| 815 | |
| 816 | if(STREAM_SEEKABLE(p_ctx)) |
| 817 | { |
| 818 | /* Check we are not about to go over the maximum file size */ |
| 819 | if (avi_calculate_file_size(p_ctx, p_packet) >= (int64_t)ULONG_MAX) return VC_CONTAINER_ERROR_OUT_OF_RESOURCES; |
| 820 | } |
| 821 | |
| 822 | /* FIXME: are we expected to handle this case or should it be picked up by the above layer? */ |
| 823 | vc_container_assert(!(module->chunk_data_written && (p_packet->flags & VC_CONTAINER_PACKET_FLAG_FRAME_START))); |
| 824 | |
| 825 | track = p_ctx->tracks[p_packet->track]; |
| 826 | track_module = p_ctx->tracks[p_packet->track]->priv->module; |
| 827 | module->current_track_num = p_packet->track; |
| 828 | |
| 829 | if (module->chunk_data_written == 0) |
| 830 | { |
| 831 | /* This is the first fragment of the chunk */ |
| 832 | VC_CONTAINER_FOURCC_T chunk_id; |
| 833 | uint32_t chunk_size; |
| 834 | |
| 835 | avi_chunk_id_from_track_num(p_ctx, &chunk_id, p_packet->track); |
| 836 | |
| 837 | if (p_packet->frame_size) |
| 838 | { |
| 839 | /* We know what the final size of the chunk is going to be */ |
| 840 | chunk_size = module->chunk_size = p_packet->frame_size; |
| 841 | } |
| 842 | else |
| 843 | { |
| 844 | chunk_size = p_packet->size; |
| 845 | module->chunk_size = 0; |
| 846 | } |
| 847 | |
| 848 | WRITE_FOURCC(p_ctx, chunk_id, "Chunk ID" ); |
| 849 | if(STREAM_SEEKABLE(p_ctx) || p_packet->flags & VC_CONTAINER_PACKET_FLAG_FRAME_END) |
| 850 | { |
| 851 | /* If the output stream can seek we can fix up the frame size later, and if the |
| 852 | * packet holds the whole frame we won't need to, so write data straight out. */ |
| 853 | WRITE_U32(p_ctx, chunk_size, "Chunk Size" ); |
| 854 | WRITE_BYTES(p_ctx, p_packet->data, p_packet->size); |
| 855 | } |
| 856 | else |
| 857 | { |
| 858 | vc_container_assert(module->avi_frame_buffer); |
| 859 | if(p_packet->size > AVI_FRAME_BUFFER_SIZE) |
| 860 | return VC_CONTAINER_ERROR_OUT_OF_RESOURCES; |
| 861 | module->frame_packet = *p_packet; |
| 862 | module->frame_packet.data = module->avi_frame_buffer; |
| 863 | memcpy(module->frame_packet.data, |
| 864 | p_packet->data, module->frame_packet.size); |
| 865 | } |
| 866 | |
| 867 | module->chunk_data_written = p_packet->size; |
| 868 | } |
| 869 | else |
| 870 | { |
| 871 | if(module->frame_packet.size > 0 && module->avi_frame_buffer) |
| 872 | { |
| 873 | if(module->frame_packet.size > 0) |
| 874 | { |
| 875 | if(module->frame_packet.size + p_packet->size > AVI_FRAME_BUFFER_SIZE) |
| 876 | return VC_CONTAINER_ERROR_OUT_OF_RESOURCES; |
| 877 | memcpy(module->frame_packet.data + module->frame_packet.size, |
| 878 | p_packet->data, p_packet->size); |
| 879 | module->frame_packet.size += p_packet->size; |
| 880 | } |
| 881 | } |
| 882 | else |
| 883 | { |
| 884 | WRITE_BYTES(p_ctx, p_packet->data, p_packet->size); |
| 885 | } |
| 886 | module->chunk_data_written += p_packet->size; |
| 887 | } |
| 888 | |
| 889 | if ((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) |
| 890 | return status; |
| 891 | |
| 892 | if ((p_packet->flags & VC_CONTAINER_PACKET_FLAG_FRAME_END) || |
| 893 | (track->format->es_type == VC_CONTAINER_ES_TYPE_AUDIO && |
| 894 | track->format->type->audio.block_align && |
| 895 | module->chunk_data_written > AVI_AUDIO_CHUNK_SIZE_LIMIT)) |
| 896 | { |
| 897 | if(module->frame_packet.size > 0) |
| 898 | { |
| 899 | WRITE_U32(p_ctx, module->frame_packet.size, "Chunk Size" ); |
| 900 | WRITE_BYTES(p_ctx, module->frame_packet.data, module->frame_packet.size); |
| 901 | p_packet->size = module->frame_packet.size; |
| 902 | module->frame_packet.size = 0; |
| 903 | } |
| 904 | |
| 905 | if (!module->chunk_size && module->chunk_data_written > p_packet->size) |
| 906 | { |
| 907 | /* The chunk size needs to be rewritten */ |
| 908 | status = avi_finish_data_chunk(p_ctx, module->chunk_data_written); |
| 909 | } |
| 910 | else |
| 911 | { |
| 912 | status = avi_finish_data_chunk(p_ctx, 0); |
| 913 | } |
| 914 | |
| 915 | if(!STREAM_SEEKABLE(p_ctx)) |
| 916 | { |
| 917 | /* If we are streaming then flush to avoid delaying data transport. */ |
| 918 | vc_container_control(p_ctx, VC_CONTAINER_CONTROL_IO_FLUSH); |
| 919 | } |
| 920 | |
| 921 | if(STREAM_SEEKABLE(p_ctx)) |
| 922 | { |
| 923 | /* Keep track of data written so we can check we don't exceed file size and also for doing |
| 924 | * index fix-ups, but only do this if we are writing to a seekable IO. */ |
| 925 | avi_write_index_entry(p_ctx, p_packet->track, module->chunk_data_written, AVI_PACKET_IS_KEYFRAME(p_packet->flags)); |
| 926 | } |
| 927 | track_module->chunk_index++; |
| 928 | track_module->chunk_offs += module->chunk_data_written; |
| 929 | track_module->max_chunk_size = MAX(track_module->max_chunk_size, module->chunk_data_written); |
| 930 | module->chunk_data_written = 0; |
| 931 | |
| 932 | if (status != VC_CONTAINER_SUCCESS) return status; |
| 933 | } |
| 934 | |
| 935 | |
| 936 | return STREAM_STATUS(p_ctx); |
| 937 | } |
| 938 | |
| 939 | /*****************************************************************************/ |
| 940 | static VC_CONTAINER_STATUS_T avi_writer_close( VC_CONTAINER_T *p_ctx ) |
| 941 | { |
| 942 | VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; |
| 943 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
| 944 | unsigned int i; |
| 945 | |
| 946 | /* If we arent't finished with a chunk we need to finish it first */ |
| 947 | if (module->chunk_data_written) |
| 948 | { |
| 949 | VC_CONTAINER_TRACK_MODULE_T *track_module = p_ctx->tracks[module->current_track_num]->priv->module; |
| 950 | status = avi_finish_data_chunk(p_ctx, module->chunk_data_written); |
| 951 | if (status != VC_CONTAINER_SUCCESS) |
| 952 | { |
| 953 | LOG_DEBUG(p_ctx, "warning, writing failed, last chunk truncated" ); |
| 954 | } |
| 955 | avi_write_index_entry(p_ctx, module->current_track_num, module->chunk_data_written, 0); |
| 956 | track_module->chunk_index++; |
| 957 | track_module->chunk_offs += module->chunk_data_written; |
| 958 | track_module->max_chunk_size = MAX(track_module->max_chunk_size, module->chunk_data_written); |
| 959 | module->chunk_data_written = 0; |
| 960 | } |
| 961 | |
| 962 | if(STREAM_SEEKABLE(p_ctx)) |
| 963 | { |
| 964 | uint32_t filesize; |
| 965 | |
| 966 | /* Write standard index data before finalising the size of the 'movi' list */ |
| 967 | status = avi_write_standard_index_data(p_ctx); |
| 968 | if (status != VC_CONTAINER_SUCCESS) |
| 969 | { |
| 970 | module->index_status = status; |
| 971 | LOG_DEBUG(p_ctx, "warning, writing standard index data failed, file will be malformed" ); |
| 972 | } |
| 973 | |
| 974 | /* FIXME: support for multiple RIFF chunks (AVIX) */ |
| 975 | module->data_size = STREAM_POSITION(p_ctx) - module->data_offset - 8; |
| 976 | |
| 977 | /* Now write the legacy index */ |
| 978 | status = avi_write_legacy_index_data(p_ctx); |
| 979 | if (status != VC_CONTAINER_SUCCESS) |
| 980 | { |
| 981 | module->index_status = status; |
| 982 | LOG_DEBUG(p_ctx, "warning, writing legacy index data failed, file will be malformed" ); |
| 983 | } |
| 984 | |
| 985 | /* If we can, do the necessary fixups for values not know at the |
| 986 | time of writing chunk headers */ |
| 987 | |
| 988 | /* Rewrite the AVI RIFF chunk size */ |
| 989 | filesize = (uint32_t)STREAM_POSITION(p_ctx); |
| 990 | SEEK(p_ctx, 4); |
| 991 | WRITE_U32(p_ctx, filesize, "fileSize" ); |
| 992 | if(STREAM_STATUS(p_ctx) != VC_CONTAINER_SUCCESS) |
| 993 | { |
| 994 | LOG_DEBUG(p_ctx, "warning, rewriting 'fileSize' failed, file will be malformed" ); |
| 995 | } |
| 996 | |
| 997 | /* Rewrite the header list chunk ('hdrl') */ |
| 998 | SEEK(p_ctx, module->header_list_offset); |
| 999 | status = avi_write_header_list(p_ctx, module->header_list_size); |
| 1000 | if (status != VC_CONTAINER_SUCCESS) |
| 1001 | { |
| 1002 | LOG_DEBUG(p_ctx, "warning, rewriting 'hdrl' failed, file will be malformed" ); |
| 1003 | } |
| 1004 | |
| 1005 | /* Rewrite the AVI RIFF 'movi' list size */ |
| 1006 | SEEK(p_ctx, module->data_offset + 4); |
| 1007 | WRITE_U32(p_ctx, module->data_size, "Chunk Size" ); |
| 1008 | if(STREAM_STATUS(p_ctx) != VC_CONTAINER_SUCCESS) |
| 1009 | { |
| 1010 | LOG_DEBUG(p_ctx, "warning, rewriting 'movi' list size failed, file will be malformed" ); |
| 1011 | } |
| 1012 | } |
| 1013 | |
| 1014 | vc_container_writer_extraio_delete(p_ctx, &module->null_io); |
| 1015 | if(module->temp_io.io) vc_container_writer_extraio_delete(p_ctx, &module->temp_io); |
| 1016 | |
| 1017 | for(i = 0; i < p_ctx->tracks_num; i++) |
| 1018 | vc_container_free_track(p_ctx, p_ctx->tracks[i]); |
| 1019 | p_ctx->tracks_num = 0; |
| 1020 | p_ctx->tracks = NULL; |
| 1021 | |
| 1022 | if(module->avi_frame_buffer) free(module->avi_frame_buffer); |
| 1023 | free(module); |
| 1024 | |
| 1025 | return status; |
| 1026 | } |
| 1027 | |
| 1028 | /*****************************************************************************/ |
| 1029 | static VC_CONTAINER_STATUS_T avi_writer_add_track( VC_CONTAINER_T *p_ctx, VC_CONTAINER_ES_FORMAT_T *format ) |
| 1030 | { |
| 1031 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
| 1032 | VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; |
| 1033 | VC_CONTAINER_TRACK_T *track = NULL; |
| 1034 | |
| 1035 | if (module->headers_written) return VC_CONTAINER_ERROR_FAILED; |
| 1036 | |
| 1037 | /* FIXME: should we check the format in more detail? */ |
| 1038 | if((format->es_type != VC_CONTAINER_ES_TYPE_VIDEO && format->es_type != VC_CONTAINER_ES_TYPE_AUDIO) || |
| 1039 | format->codec == VC_CONTAINER_CODEC_UNKNOWN) |
| 1040 | return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; |
| 1041 | |
| 1042 | if(!(format->flags & VC_CONTAINER_ES_FORMAT_FLAG_FRAMED)) |
| 1043 | return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; |
| 1044 | |
| 1045 | /* Allocate new track */ |
| 1046 | if(p_ctx->tracks_num >= AVI_TRACKS_MAX) return VC_CONTAINER_ERROR_OUT_OF_RESOURCES; |
| 1047 | p_ctx->tracks[p_ctx->tracks_num] = track = |
| 1048 | vc_container_allocate_track(p_ctx, sizeof(*p_ctx->tracks[0]->priv->module)); |
| 1049 | if(!track) return VC_CONTAINER_ERROR_OUT_OF_MEMORY; |
| 1050 | |
| 1051 | if(format->extradata_size) |
| 1052 | { |
| 1053 | status = vc_container_track_allocate_extradata( p_ctx, track, format->extradata_size ); |
| 1054 | if(status) goto error; |
| 1055 | } |
| 1056 | |
| 1057 | status = vc_container_format_copy(track->format, format, format->extradata_size); |
| 1058 | if(status) goto error; |
| 1059 | |
| 1060 | p_ctx->tracks_num++; |
| 1061 | return VC_CONTAINER_SUCCESS; |
| 1062 | |
| 1063 | error: |
| 1064 | vc_container_free_track(p_ctx, track); |
| 1065 | return status; |
| 1066 | } |
| 1067 | |
| 1068 | /*****************************************************************************/ |
| 1069 | static VC_CONTAINER_STATUS_T avi_writer_control( VC_CONTAINER_T *p_ctx, VC_CONTAINER_CONTROL_T operation, va_list args ) |
| 1070 | { |
| 1071 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
| 1072 | VC_CONTAINER_STATUS_T status; |
| 1073 | |
| 1074 | switch(operation) |
| 1075 | { |
| 1076 | case VC_CONTAINER_CONTROL_TRACK_ADD: |
| 1077 | { |
| 1078 | VC_CONTAINER_ES_FORMAT_T *format = |
| 1079 | (VC_CONTAINER_ES_FORMAT_T *)va_arg( args, VC_CONTAINER_ES_FORMAT_T * ); |
| 1080 | return avi_writer_add_track(p_ctx, format); |
| 1081 | } |
| 1082 | case VC_CONTAINER_CONTROL_TRACK_ADD_DONE: |
| 1083 | { |
| 1084 | if(!module->headers_written) |
| 1085 | { |
| 1086 | if ((status = avi_write_headers(p_ctx)) != VC_CONTAINER_SUCCESS) return status; |
| 1087 | module->headers_written = 1; |
| 1088 | return VC_CONTAINER_SUCCESS; |
| 1089 | } |
| 1090 | else |
| 1091 | return VC_CONTAINER_ERROR_FAILED; |
| 1092 | } |
| 1093 | default: return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; |
| 1094 | } |
| 1095 | } |
| 1096 | |
| 1097 | /****************************************************************************** |
| 1098 | Global function definitions. |
| 1099 | ******************************************************************************/ |
| 1100 | VC_CONTAINER_STATUS_T avi_writer_open( VC_CONTAINER_T *p_ctx ) |
| 1101 | { |
| 1102 | const char *extension = vc_uri_path_extension(p_ctx->priv->uri); |
| 1103 | VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; |
| 1104 | VC_CONTAINER_MODULE_T *module = 0; |
| 1105 | |
| 1106 | /* Check if the user has specified a container */ |
| 1107 | vc_uri_find_query(p_ctx->priv->uri, 0, "container" , &extension); |
| 1108 | |
| 1109 | /* Check we're the right writer for this */ |
| 1110 | if(!extension) |
| 1111 | return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; |
| 1112 | if(strcasecmp(extension, "avi" ) && strcasecmp(extension, "divx" )) |
| 1113 | return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; |
| 1114 | |
| 1115 | /* Allocate our context */ |
| 1116 | module = malloc(sizeof(*module)); |
| 1117 | if(!module) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } |
| 1118 | memset(module, 0, sizeof(*module)); |
| 1119 | p_ctx->priv->module = module; |
| 1120 | |
| 1121 | /* Create a null i/o writer to help us out in writing our data */ |
| 1122 | status = vc_container_writer_extraio_create_null(p_ctx, &module->null_io); |
| 1123 | if(status != VC_CONTAINER_SUCCESS) goto error; |
| 1124 | |
| 1125 | if(STREAM_SEEKABLE(p_ctx)) |
| 1126 | { |
| 1127 | /* Create a temporary i/o writer for storage of index data while we are writing */ |
| 1128 | status = vc_container_writer_extraio_create_temp(p_ctx, &module->temp_io); |
| 1129 | if(status != VC_CONTAINER_SUCCESS) goto error; |
| 1130 | } |
| 1131 | else |
| 1132 | { |
| 1133 | module->avi_frame_buffer = malloc(AVI_FRAME_BUFFER_SIZE); |
| 1134 | if(!module->avi_frame_buffer) |
| 1135 | { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } |
| 1136 | } |
| 1137 | module->frame_packet.size = 0; |
| 1138 | |
| 1139 | p_ctx->tracks = module->tracks; |
| 1140 | |
| 1141 | /* Write the RIFF chunk descriptor */ |
| 1142 | WRITE_FOURCC(p_ctx, VC_FOURCC('R','I','F','F'), "RIFF ID" ); |
| 1143 | WRITE_U32(p_ctx, 0, "fileSize" ); |
| 1144 | WRITE_FOURCC(p_ctx, VC_FOURCC('A','V','I',' '), "fileType" ); |
| 1145 | |
| 1146 | if((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) goto error; |
| 1147 | |
| 1148 | p_ctx->priv->pf_close = avi_writer_close; |
| 1149 | p_ctx->priv->pf_write = avi_writer_write; |
| 1150 | p_ctx->priv->pf_control = avi_writer_control; |
| 1151 | |
| 1152 | return VC_CONTAINER_SUCCESS; |
| 1153 | |
| 1154 | error: |
| 1155 | LOG_DEBUG(p_ctx, "error opening stream" ); |
| 1156 | p_ctx->tracks_num = 0; |
| 1157 | p_ctx->tracks = NULL; |
| 1158 | if(module) |
| 1159 | { |
| 1160 | if(module->avi_frame_buffer) free(module->avi_frame_buffer); |
| 1161 | free(module); |
| 1162 | } |
| 1163 | return status; |
| 1164 | } |
| 1165 | |
| 1166 | /******************************************************************************** |
| 1167 | Entrypoint function |
| 1168 | ********************************************************************************/ |
| 1169 | #if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__) |
| 1170 | # pragma weak writer_open avi_writer_open |
| 1171 | #endif |
| 1172 | |