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