1 | /*
|
---|
2 | * Libavformat API example: Output a media file in any supported
|
---|
3 | * libavformat format. The default codecs are used.
|
---|
4 | *
|
---|
5 | * Copyright (c) 2003 Fabrice Bellard
|
---|
6 | *
|
---|
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
|
---|
8 | * of this software and associated documentation files (the "Software"), to deal
|
---|
9 | * in the Software without restriction, including without limitation the rights
|
---|
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
---|
11 | * copies of the Software, and to permit persons to whom the Software is
|
---|
12 | * furnished to do so, subject to the following conditions:
|
---|
13 | *
|
---|
14 | * The above copyright notice and this permission notice shall be included in
|
---|
15 | * all copies or substantial portions of the Software.
|
---|
16 | *
|
---|
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
---|
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
---|
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
---|
20 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
---|
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
---|
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
---|
23 | * THE SOFTWARE.
|
---|
24 | */
|
---|
25 | #include <stdlib.h>
|
---|
26 | #include <stdio.h>
|
---|
27 | #include <string.h>
|
---|
28 | #include <math.h>
|
---|
29 |
|
---|
30 | #ifndef M_PI
|
---|
31 | #define M_PI 3.1415926535897931
|
---|
32 | #endif
|
---|
33 |
|
---|
34 | #include "avformat.h"
|
---|
35 |
|
---|
36 | /* 5 seconds stream duration */
|
---|
37 | #define STREAM_DURATION 5.0
|
---|
38 | #define STREAM_FRAME_RATE 25 /* 25 images/s */
|
---|
39 | #define STREAM_NB_FRAMES ((int)(STREAM_DURATION * STREAM_FRAME_RATE))
|
---|
40 | #define STREAM_PIX_FMT PIX_FMT_YUV420P /* default pix_fmt */
|
---|
41 |
|
---|
42 | /**************************************************************/
|
---|
43 | /* audio output */
|
---|
44 |
|
---|
45 | float t, tincr, tincr2;
|
---|
46 | int16_t *samples;
|
---|
47 | uint8_t *audio_outbuf;
|
---|
48 | int audio_outbuf_size;
|
---|
49 | int audio_input_frame_size;
|
---|
50 |
|
---|
51 | /*
|
---|
52 | * add an audio output stream
|
---|
53 | */
|
---|
54 | static AVStream *add_audio_stream(AVFormatContext *oc, int codec_id)
|
---|
55 | {
|
---|
56 | AVCodecContext *c;
|
---|
57 | AVStream *st;
|
---|
58 |
|
---|
59 | st = av_new_stream(oc, 1);
|
---|
60 | if (!st) {
|
---|
61 | fprintf(stderr, "Could not alloc stream\n");
|
---|
62 | exit(1);
|
---|
63 | }
|
---|
64 |
|
---|
65 | c = st->codec;
|
---|
66 | c->codec_id = codec_id;
|
---|
67 | c->codec_type = CODEC_TYPE_AUDIO;
|
---|
68 |
|
---|
69 | /* put sample parameters */
|
---|
70 | c->bit_rate = 64000;
|
---|
71 | c->sample_rate = 44100;
|
---|
72 | c->channels = 2;
|
---|
73 | return st;
|
---|
74 | }
|
---|
75 |
|
---|
76 | static void open_audio(AVFormatContext *oc, AVStream *st)
|
---|
77 | {
|
---|
78 | AVCodecContext *c;
|
---|
79 | AVCodec *codec;
|
---|
80 |
|
---|
81 | c = st->codec;
|
---|
82 |
|
---|
83 | /* find the audio encoder */
|
---|
84 | codec = avcodec_find_encoder(c->codec_id);
|
---|
85 | if (!codec) {
|
---|
86 | fprintf(stderr, "codec not found\n");
|
---|
87 | exit(1);
|
---|
88 | }
|
---|
89 |
|
---|
90 | /* open it */
|
---|
91 | if (avcodec_open(c, codec) < 0) {
|
---|
92 | fprintf(stderr, "could not open codec\n");
|
---|
93 | exit(1);
|
---|
94 | }
|
---|
95 |
|
---|
96 | /* init signal generator */
|
---|
97 | t = 0;
|
---|
98 | tincr = 2 * M_PI * 110.0 / c->sample_rate;
|
---|
99 | /* increment frequency by 110 Hz per second */
|
---|
100 | tincr2 = 2 * M_PI * 110.0 / c->sample_rate / c->sample_rate;
|
---|
101 |
|
---|
102 | audio_outbuf_size = 10000;
|
---|
103 | audio_outbuf = malloc(audio_outbuf_size);
|
---|
104 |
|
---|
105 | /* ugly hack for PCM codecs (will be removed ASAP with new PCM
|
---|
106 | support to compute the input frame size in samples */
|
---|
107 | if (c->frame_size <= 1) {
|
---|
108 | audio_input_frame_size = audio_outbuf_size / c->channels;
|
---|
109 | switch(st->codec->codec_id) {
|
---|
110 | case CODEC_ID_PCM_S16LE:
|
---|
111 | case CODEC_ID_PCM_S16BE:
|
---|
112 | case CODEC_ID_PCM_U16LE:
|
---|
113 | case CODEC_ID_PCM_U16BE:
|
---|
114 | audio_input_frame_size >>= 1;
|
---|
115 | break;
|
---|
116 | default:
|
---|
117 | break;
|
---|
118 | }
|
---|
119 | } else {
|
---|
120 | audio_input_frame_size = c->frame_size;
|
---|
121 | }
|
---|
122 | samples = malloc(audio_input_frame_size * 2 * c->channels);
|
---|
123 | }
|
---|
124 |
|
---|
125 | /* prepare a 16 bit dummy audio frame of 'frame_size' samples and
|
---|
126 | 'nb_channels' channels */
|
---|
127 | static void get_audio_frame(int16_t *samples, int frame_size, int nb_channels)
|
---|
128 | {
|
---|
129 | int j, i, v;
|
---|
130 | int16_t *q;
|
---|
131 |
|
---|
132 | q = samples;
|
---|
133 | for(j=0;j<frame_size;j++) {
|
---|
134 | v = (int)(sin(t) * 10000);
|
---|
135 | for(i = 0; i < nb_channels; i++)
|
---|
136 | *q++ = v;
|
---|
137 | t += tincr;
|
---|
138 | tincr += tincr2;
|
---|
139 | }
|
---|
140 | }
|
---|
141 |
|
---|
142 | static void write_audio_frame(AVFormatContext *oc, AVStream *st)
|
---|
143 | {
|
---|
144 | AVCodecContext *c;
|
---|
145 | AVPacket pkt;
|
---|
146 | av_init_packet(&pkt);
|
---|
147 |
|
---|
148 | c = st->codec;
|
---|
149 |
|
---|
150 | get_audio_frame(samples, audio_input_frame_size, c->channels);
|
---|
151 |
|
---|
152 | pkt.size= avcodec_encode_audio(c, audio_outbuf, audio_outbuf_size, samples);
|
---|
153 |
|
---|
154 | pkt.pts= av_rescale_q(c->coded_frame->pts, c->time_base, st->time_base);
|
---|
155 | pkt.flags |= PKT_FLAG_KEY;
|
---|
156 | pkt.stream_index= st->index;
|
---|
157 | pkt.data= audio_outbuf;
|
---|
158 |
|
---|
159 | /* write the compressed frame in the media file */
|
---|
160 | if (av_write_frame(oc, &pkt) != 0) {
|
---|
161 | fprintf(stderr, "Error while writing audio frame\n");
|
---|
162 | exit(1);
|
---|
163 | }
|
---|
164 | }
|
---|
165 |
|
---|
166 | static void close_audio(AVFormatContext *oc, AVStream *st)
|
---|
167 | {
|
---|
168 | avcodec_close(st->codec);
|
---|
169 |
|
---|
170 | av_free(samples);
|
---|
171 | av_free(audio_outbuf);
|
---|
172 | }
|
---|
173 |
|
---|
174 | /**************************************************************/
|
---|
175 | /* video output */
|
---|
176 |
|
---|
177 | AVFrame *picture, *tmp_picture;
|
---|
178 | uint8_t *video_outbuf;
|
---|
179 | int frame_count, video_outbuf_size;
|
---|
180 |
|
---|
181 | /* add a video output stream */
|
---|
182 | static AVStream *add_video_stream(AVFormatContext *oc, int codec_id)
|
---|
183 | {
|
---|
184 | AVCodecContext *c;
|
---|
185 | AVStream *st;
|
---|
186 |
|
---|
187 | st = av_new_stream(oc, 0);
|
---|
188 | if (!st) {
|
---|
189 | fprintf(stderr, "Could not alloc stream\n");
|
---|
190 | exit(1);
|
---|
191 | }
|
---|
192 |
|
---|
193 | c = st->codec;
|
---|
194 | c->codec_id = codec_id;
|
---|
195 | c->codec_type = CODEC_TYPE_VIDEO;
|
---|
196 |
|
---|
197 | /* put sample parameters */
|
---|
198 | c->bit_rate = 400000;
|
---|
199 | /* resolution must be a multiple of two */
|
---|
200 | c->width = 352;
|
---|
201 | c->height = 288;
|
---|
202 | /* time base: this is the fundamental unit of time (in seconds) in terms
|
---|
203 | of which frame timestamps are represented. for fixed-fps content,
|
---|
204 | timebase should be 1/framerate and timestamp increments should be
|
---|
205 | identically 1. */
|
---|
206 | c->time_base.den = STREAM_FRAME_RATE;
|
---|
207 | c->time_base.num = 1;
|
---|
208 | c->gop_size = 12; /* emit one intra frame every twelve frames at most */
|
---|
209 | c->pix_fmt = STREAM_PIX_FMT;
|
---|
210 | if (c->codec_id == CODEC_ID_MPEG2VIDEO) {
|
---|
211 | /* just for testing, we also add B frames */
|
---|
212 | c->max_b_frames = 2;
|
---|
213 | }
|
---|
214 | if (c->codec_id == CODEC_ID_MPEG1VIDEO){
|
---|
215 | /* needed to avoid using macroblocks in which some coeffs overflow
|
---|
216 | this doesnt happen with normal video, it just happens here as the
|
---|
217 | motion of the chroma plane doesnt match the luma plane */
|
---|
218 | c->mb_decision=2;
|
---|
219 | }
|
---|
220 | // some formats want stream headers to be seperate
|
---|
221 | if(!strcmp(oc->oformat->name, "mp4") || !strcmp(oc->oformat->name, "mov") || !strcmp(oc->oformat->name, "3gp"))
|
---|
222 | c->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
---|
223 |
|
---|
224 | return st;
|
---|
225 | }
|
---|
226 |
|
---|
227 | static AVFrame *alloc_picture(int pix_fmt, int width, int height)
|
---|
228 | {
|
---|
229 | AVFrame *picture;
|
---|
230 | uint8_t *picture_buf;
|
---|
231 | int size;
|
---|
232 |
|
---|
233 | picture = avcodec_alloc_frame();
|
---|
234 | if (!picture)
|
---|
235 | return NULL;
|
---|
236 | size = avpicture_get_size(pix_fmt, width, height);
|
---|
237 | picture_buf = malloc(size);
|
---|
238 | if (!picture_buf) {
|
---|
239 | av_free(picture);
|
---|
240 | return NULL;
|
---|
241 | }
|
---|
242 | avpicture_fill((AVPicture *)picture, picture_buf,
|
---|
243 | pix_fmt, width, height);
|
---|
244 | return picture;
|
---|
245 | }
|
---|
246 |
|
---|
247 | static void open_video(AVFormatContext *oc, AVStream *st)
|
---|
248 | {
|
---|
249 | AVCodec *codec;
|
---|
250 | AVCodecContext *c;
|
---|
251 |
|
---|
252 | c = st->codec;
|
---|
253 |
|
---|
254 | /* find the video encoder */
|
---|
255 | codec = avcodec_find_encoder(c->codec_id);
|
---|
256 | if (!codec) {
|
---|
257 | fprintf(stderr, "codec not found\n");
|
---|
258 | exit(1);
|
---|
259 | }
|
---|
260 |
|
---|
261 | /* open the codec */
|
---|
262 | if (avcodec_open(c, codec) < 0) {
|
---|
263 | fprintf(stderr, "could not open codec\n");
|
---|
264 | exit(1);
|
---|
265 | }
|
---|
266 |
|
---|
267 | video_outbuf = NULL;
|
---|
268 | if (!(oc->oformat->flags & AVFMT_RAWPICTURE)) {
|
---|
269 | /* allocate output buffer */
|
---|
270 | /* XXX: API change will be done */
|
---|
271 | video_outbuf_size = 200000;
|
---|
272 | video_outbuf = malloc(video_outbuf_size);
|
---|
273 | }
|
---|
274 |
|
---|
275 | /* allocate the encoded raw picture */
|
---|
276 | picture = alloc_picture(c->pix_fmt, c->width, c->height);
|
---|
277 | if (!picture) {
|
---|
278 | fprintf(stderr, "Could not allocate picture\n");
|
---|
279 | exit(1);
|
---|
280 | }
|
---|
281 |
|
---|
282 | /* if the output format is not YUV420P, then a temporary YUV420P
|
---|
283 | picture is needed too. It is then converted to the required
|
---|
284 | output format */
|
---|
285 | tmp_picture = NULL;
|
---|
286 | if (c->pix_fmt != PIX_FMT_YUV420P) {
|
---|
287 | tmp_picture = alloc_picture(PIX_FMT_YUV420P, c->width, c->height);
|
---|
288 | if (!tmp_picture) {
|
---|
289 | fprintf(stderr, "Could not allocate temporary picture\n");
|
---|
290 | exit(1);
|
---|
291 | }
|
---|
292 | }
|
---|
293 | }
|
---|
294 |
|
---|
295 | /* prepare a dummy image */
|
---|
296 | static void fill_yuv_image(AVFrame *pict, int frame_index, int width, int height)
|
---|
297 | {
|
---|
298 | int x, y, i;
|
---|
299 |
|
---|
300 | i = frame_index;
|
---|
301 |
|
---|
302 | /* Y */
|
---|
303 | for(y=0;y<height;y++) {
|
---|
304 | for(x=0;x<width;x++) {
|
---|
305 | pict->data[0][y * pict->linesize[0] + x] = x + y + i * 3;
|
---|
306 | }
|
---|
307 | }
|
---|
308 |
|
---|
309 | /* Cb and Cr */
|
---|
310 | for(y=0;y<height/2;y++) {
|
---|
311 | for(x=0;x<width/2;x++) {
|
---|
312 | pict->data[1][y * pict->linesize[1] + x] = 128 + y + i * 2;
|
---|
313 | pict->data[2][y * pict->linesize[2] + x] = 64 + x + i * 5;
|
---|
314 | }
|
---|
315 | }
|
---|
316 | }
|
---|
317 |
|
---|
318 | static void write_video_frame(AVFormatContext *oc, AVStream *st)
|
---|
319 | {
|
---|
320 | int out_size, ret;
|
---|
321 | AVCodecContext *c;
|
---|
322 |
|
---|
323 | c = st->codec;
|
---|
324 |
|
---|
325 | if (frame_count >= STREAM_NB_FRAMES) {
|
---|
326 | /* no more frame to compress. The codec has a latency of a few
|
---|
327 | frames if using B frames, so we get the last frames by
|
---|
328 | passing the same picture again */
|
---|
329 | } else {
|
---|
330 | if (c->pix_fmt != PIX_FMT_YUV420P) {
|
---|
331 | /* as we only generate a YUV420P picture, we must convert it
|
---|
332 | to the codec pixel format if needed */
|
---|
333 | fill_yuv_image(tmp_picture, frame_count, c->width, c->height);
|
---|
334 | img_convert((AVPicture *)picture, c->pix_fmt,
|
---|
335 | (AVPicture *)tmp_picture, PIX_FMT_YUV420P,
|
---|
336 | c->width, c->height);
|
---|
337 | } else {
|
---|
338 | fill_yuv_image(picture, frame_count, c->width, c->height);
|
---|
339 | }
|
---|
340 | }
|
---|
341 |
|
---|
342 |
|
---|
343 | if (oc->oformat->flags & AVFMT_RAWPICTURE) {
|
---|
344 | /* raw video case. The API will change slightly in the near
|
---|
345 | futur for that */
|
---|
346 | AVPacket pkt;
|
---|
347 | av_init_packet(&pkt);
|
---|
348 |
|
---|
349 | pkt.flags |= PKT_FLAG_KEY;
|
---|
350 | pkt.stream_index= st->index;
|
---|
351 | pkt.data= (uint8_t *)picture;
|
---|
352 | pkt.size= sizeof(AVPicture);
|
---|
353 |
|
---|
354 | ret = av_write_frame(oc, &pkt);
|
---|
355 | } else {
|
---|
356 | /* encode the image */
|
---|
357 | out_size = avcodec_encode_video(c, video_outbuf, video_outbuf_size, picture);
|
---|
358 | /* if zero size, it means the image was buffered */
|
---|
359 | if (out_size > 0) {
|
---|
360 | AVPacket pkt;
|
---|
361 | av_init_packet(&pkt);
|
---|
362 |
|
---|
363 | pkt.pts= av_rescale_q(c->coded_frame->pts, c->time_base, st->time_base);
|
---|
364 | if(c->coded_frame->key_frame)
|
---|
365 | pkt.flags |= PKT_FLAG_KEY;
|
---|
366 | pkt.stream_index= st->index;
|
---|
367 | pkt.data= video_outbuf;
|
---|
368 | pkt.size= out_size;
|
---|
369 |
|
---|
370 | /* write the compressed frame in the media file */
|
---|
371 | ret = av_write_frame(oc, &pkt);
|
---|
372 | } else {
|
---|
373 | ret = 0;
|
---|
374 | }
|
---|
375 | }
|
---|
376 | if (ret != 0) {
|
---|
377 | fprintf(stderr, "Error while writing video frame\n");
|
---|
378 | exit(1);
|
---|
379 | }
|
---|
380 | frame_count++;
|
---|
381 | }
|
---|
382 |
|
---|
383 | static void close_video(AVFormatContext *oc, AVStream *st)
|
---|
384 | {
|
---|
385 | avcodec_close(st->codec);
|
---|
386 | av_free(picture->data[0]);
|
---|
387 | av_free(picture);
|
---|
388 | if (tmp_picture) {
|
---|
389 | av_free(tmp_picture->data[0]);
|
---|
390 | av_free(tmp_picture);
|
---|
391 | }
|
---|
392 | av_free(video_outbuf);
|
---|
393 | }
|
---|
394 |
|
---|
395 | /**************************************************************/
|
---|
396 | /* media file output */
|
---|
397 |
|
---|
398 | int main(int argc, char **argv)
|
---|
399 | {
|
---|
400 | const char *filename;
|
---|
401 | AVOutputFormat *fmt;
|
---|
402 | AVFormatContext *oc;
|
---|
403 | AVStream *audio_st, *video_st;
|
---|
404 | double audio_pts, video_pts;
|
---|
405 | int i;
|
---|
406 |
|
---|
407 | /* initialize libavcodec, and register all codecs and formats */
|
---|
408 | av_register_all();
|
---|
409 |
|
---|
410 | if (argc != 2) {
|
---|
411 | printf("usage: %s output_file\n"
|
---|
412 | "API example program to output a media file with libavformat.\n"
|
---|
413 | "The output format is automatically guessed according to the file extension.\n"
|
---|
414 | "Raw images can also be output by using '%%d' in the filename\n"
|
---|
415 | "\n", argv[0]);
|
---|
416 | exit(1);
|
---|
417 | }
|
---|
418 |
|
---|
419 | filename = argv[1];
|
---|
420 |
|
---|
421 | /* auto detect the output format from the name. default is
|
---|
422 | mpeg. */
|
---|
423 | fmt = guess_format(NULL, filename, NULL);
|
---|
424 | if (!fmt) {
|
---|
425 | printf("Could not deduce output format from file extension: using MPEG.\n");
|
---|
426 | fmt = guess_format("mpeg", NULL, NULL);
|
---|
427 | }
|
---|
428 | if (!fmt) {
|
---|
429 | fprintf(stderr, "Could not find suitable output format\n");
|
---|
430 | exit(1);
|
---|
431 | }
|
---|
432 |
|
---|
433 | /* allocate the output media context */
|
---|
434 | oc = av_alloc_format_context();
|
---|
435 | if (!oc) {
|
---|
436 | fprintf(stderr, "Memory error\n");
|
---|
437 | exit(1);
|
---|
438 | }
|
---|
439 | oc->oformat = fmt;
|
---|
440 | snprintf(oc->filename, sizeof(oc->filename), "%s", filename);
|
---|
441 |
|
---|
442 | /* add the audio and video streams using the default format codecs
|
---|
443 | and initialize the codecs */
|
---|
444 | video_st = NULL;
|
---|
445 | audio_st = NULL;
|
---|
446 | if (fmt->video_codec != CODEC_ID_NONE) {
|
---|
447 | video_st = add_video_stream(oc, fmt->video_codec);
|
---|
448 | }
|
---|
449 | if (fmt->audio_codec != CODEC_ID_NONE) {
|
---|
450 | audio_st = add_audio_stream(oc, fmt->audio_codec);
|
---|
451 | }
|
---|
452 |
|
---|
453 | /* set the output parameters (must be done even if no
|
---|
454 | parameters). */
|
---|
455 | if (av_set_parameters(oc, NULL) < 0) {
|
---|
456 | fprintf(stderr, "Invalid output format parameters\n");
|
---|
457 | exit(1);
|
---|
458 | }
|
---|
459 |
|
---|
460 | dump_format(oc, 0, filename, 1);
|
---|
461 |
|
---|
462 | /* now that all the parameters are set, we can open the audio and
|
---|
463 | video codecs and allocate the necessary encode buffers */
|
---|
464 | if (video_st)
|
---|
465 | open_video(oc, video_st);
|
---|
466 | if (audio_st)
|
---|
467 | open_audio(oc, audio_st);
|
---|
468 |
|
---|
469 | /* open the output file, if needed */
|
---|
470 | if (!(fmt->flags & AVFMT_NOFILE)) {
|
---|
471 | if (url_fopen(&oc->pb, filename, URL_WRONLY) < 0) {
|
---|
472 | fprintf(stderr, "Could not open '%s'\n", filename);
|
---|
473 | exit(1);
|
---|
474 | }
|
---|
475 | }
|
---|
476 |
|
---|
477 | /* write the stream header, if any */
|
---|
478 | av_write_header(oc);
|
---|
479 |
|
---|
480 | for(;;) {
|
---|
481 | /* compute current audio and video time */
|
---|
482 | if (audio_st)
|
---|
483 | audio_pts = (double)audio_st->pts.val * audio_st->time_base.num / audio_st->time_base.den;
|
---|
484 | else
|
---|
485 | audio_pts = 0.0;
|
---|
486 |
|
---|
487 | if (video_st)
|
---|
488 | video_pts = (double)video_st->pts.val * video_st->time_base.num / video_st->time_base.den;
|
---|
489 | else
|
---|
490 | video_pts = 0.0;
|
---|
491 |
|
---|
492 | if ((!audio_st || audio_pts >= STREAM_DURATION) &&
|
---|
493 | (!video_st || video_pts >= STREAM_DURATION))
|
---|
494 | break;
|
---|
495 |
|
---|
496 | /* write interleaved audio and video frames */
|
---|
497 | if (!video_st || (video_st && audio_st && audio_pts < video_pts)) {
|
---|
498 | write_audio_frame(oc, audio_st);
|
---|
499 | } else {
|
---|
500 | write_video_frame(oc, video_st);
|
---|
501 | }
|
---|
502 | }
|
---|
503 |
|
---|
504 | /* close each codec */
|
---|
505 | if (video_st)
|
---|
506 | close_video(oc, video_st);
|
---|
507 | if (audio_st)
|
---|
508 | close_audio(oc, audio_st);
|
---|
509 |
|
---|
510 | /* write the trailer, if any */
|
---|
511 | av_write_trailer(oc);
|
---|
512 |
|
---|
513 | /* free the streams */
|
---|
514 | for(i = 0; i < oc->nb_streams; i++) {
|
---|
515 | av_freep(&oc->streams[i]->codec);
|
---|
516 | av_freep(&oc->streams[i]);
|
---|
517 | }
|
---|
518 |
|
---|
519 | if (!(fmt->flags & AVFMT_NOFILE)) {
|
---|
520 | /* close the output file */
|
---|
521 | url_fclose(&oc->pb);
|
---|
522 | }
|
---|
523 |
|
---|
524 | /* free the stream */
|
---|
525 | av_free(oc);
|
---|
526 |
|
---|
527 | return 0;
|
---|
528 | }
|
---|