[FFmpeg-trac] #10536(avformat:new): writing .avi files via custom IO context yields incomplete header

FFmpeg trac at avcodec.org
Fri Aug 25 15:33:43 EEST 2023


#10536: writing .avi files via custom IO context yields incomplete header
----------------------------------+--------------------------------------
             Reporter:  Gustav    |                     Type:  defect
               Status:  new       |                 Priority:  normal
            Component:  avformat  |                  Version:  git-master
             Keywords:  avi       |               Blocked By:
             Blocking:            |  Reproduced by developer:  0
Analyzed by developer:  0         |
----------------------------------+--------------------------------------
 **Summary of the bug:
 **I am trying to utilise AVIOContext to write an .avi file using a custom
 IO context. Doing so, however, yields .avi files that have a header
 without any information on the number of frames or file size, which in
 turn leads to an incorrect duration.

 **How to reproduce:**
 - compile the following code (taken from here taken from here:
 [here](https://ffmpeg.org/pipermail/ffmpeg-
 devel/2014-November/165014.html) using a C compiler (I used GCC 4.8.5 on
 CentOS 7) dynamically linking to the ffmpeg libraries
 {{{
 /* avio_writing.c */
 /*
  * Copyright (c) 2014 Stefano Sabatini
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
 copy
  * of this software and associated documentation files (the "Software"),
 to deal
  * in the Software without restriction, including without limitation the
 rights
  * to use, copy, modify, merge, publish, distribute, sublicense, and/or
 sell
  * copies of the Software, and to permit persons to whom the Software is
  * furnished to do so, subject to the following conditions:
  *
  * The above copyright notice and this permission notice shall be included
 in
  * all copies or substantial portions of the Software.
  *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 OTHER
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 FROM,
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 IN
  * THE SOFTWARE.
  */

 #include <libavcodec/avcodec.h>
 #include <libavformat/avformat.h>
 #include <libavformat/avio.h>
 #include <libavutil/file.h>
 #include <libavutil/timestamp.h>

 struct buffer_data
 {
     uint8_t* buf;
     size_t size;
     uint8_t* ptr;
     size_t room; ///< size left in the buffer
 };

 static void
 log_packet(const AVFormatContext* fmt_ctx, const AVPacket* pkt, const
 char* tag)
 {
     AVRational* time_base =
 &fmt_ctx->streams[pkt->stream_index]->time_base;

     printf("%s: pts:%s pts_time:%s dts:%s dts_time:%s duration:%s
 duration_time:%s stream_index:%d\n",
            tag,
            av_ts2str(pkt->pts),
            av_ts2timestr(pkt->pts, time_base),
            av_ts2str(pkt->dts),
            av_ts2timestr(pkt->dts, time_base),
            av_ts2str(pkt->duration),
            av_ts2timestr(pkt->duration, time_base),
            pkt->stream_index);
 }

 static int
 write_packet(void* opaque, uint8_t* buf, int buf_size)
 {
     struct buffer_data* bd = (struct buffer_data*)opaque;
     while (buf_size > bd->room) {
         int64_t offset = bd->ptr - bd->buf;
         bd->buf = av_realloc_f(bd->buf, 2, bd->size);
         if (!bd->buf) return AVERROR(ENOMEM);
         bd->size *= 2;
         bd->ptr = bd->buf + offset;
         bd->room = bd->size - offset;
     }
     printf("write packet pkt_size:%d used_buf_size:%zu buf_size:%zu
 buf_room:%zu\n", buf_size, bd->ptr - bd->buf, bd->size, bd->room);

     /* copy buffer data to buffer_data buffer */
     memcpy(bd->ptr, buf, buf_size);
     bd->ptr += buf_size;
     bd->room -= buf_size;

     return buf_size;
 }

 int
 main(int argc, char* argv[])
 {
     AVFormatContext* ifmt_ctx = NULL;
     AVFormatContext* ofmt_ctx = NULL;
     AVIOContext* avio_ctx = NULL;
     uint8_t* avio_ctx_buffer = NULL;
     size_t avio_ctx_buffer_size = 4096;
     char* in_filename = NULL;
     char* out_filename = NULL;
     int i, ret = 0;
     struct buffer_data bd = {0};
     const size_t bd_buf_size = 1024;
     AVPacket pkt;

     if (argc != 3) {
         fprintf(stderr,
                 "usage: %s input_file output_file\n"
                 "API example program to show how to write to a custom
 buffer "
                 "accessed through AVIOContext.\n"
                 "Remux the content of input_file to output_file, writing
 to a custom buffer.\n"
                 "The output file must support the same codec as the input
 file.\n",
                 argv[0]);
         return 1;
     }
     in_filename = argv[1];
     out_filename = argv[2];

     if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {
         fprintf(stderr, "Could not open input file '%s'", in_filename);
         goto end;
     }

     if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
         fprintf(stderr, "Failed to retrieve input stream information");
         goto end;
     }

     av_dump_format(ifmt_ctx, 0, in_filename, 0);

     /* fill opaque structure used by the AVIOContext write callback */
     bd.ptr = bd.buf = av_malloc(bd_buf_size);
     if (!bd.buf) {
         ret = AVERROR(ENOMEM);
         goto end;
     }
     bd.size = bd.room = bd_buf_size;

     avio_ctx_buffer = av_malloc(avio_ctx_buffer_size);
     if (!avio_ctx_buffer) {
         ret = AVERROR(ENOMEM);
         goto end;
     }
     avio_ctx = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size,
 1, &bd, NULL, &write_packet, NULL);
     if (!avio_ctx) {
         ret = AVERROR(ENOMEM);
         goto end;
     }

     ret = avformat_alloc_output_context2(&ofmt_ctx, NULL, "avi", NULL);
     if (ret < 0) {
         fprintf(stderr, "Could not create output context\n");
         goto end;
     }
     ofmt_ctx->pb = avio_ctx;

     for (i = 0; i < ifmt_ctx->nb_streams; i++) {
         AVStream* in_stream = ifmt_ctx->streams[i];
         AVStream* out_stream = avformat_new_stream(ofmt_ctx, NULL);
         if (!out_stream) {
             fprintf(stderr, "Failed creating output stream\n");
             ret = AVERROR_UNKNOWN;
             goto end;
         }

         AVCodecContext *codec_ctx =
 avcodec_alloc_context3(avcodec_find_encoder(in_stream->codecpar->codec_id));
         ret = avcodec_parameters_to_context(codec_ctx,
 in_stream->codecpar);
         if (ret < 0){
             printf("Failed to copy in_stream codecpar to codec
 context\n");
             goto end;
         }

         codec_ctx->codec_tag = 0;
         if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
             codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

         ret = avcodec_parameters_from_context(out_stream->codecpar,
 codec_ctx);
         if (ret < 0){
             printf("Failed to copy codec context to out_stream codecpar
 context\n");
             goto end;
         }

     }
     av_dump_format(ofmt_ctx, 0, out_filename, 1);

     ret = avformat_write_header(ofmt_ctx, NULL);
     if (ret < 0) {
         fprintf(stderr, "Error occurred when opening output file\n");
         goto end;
     }

     while (1) {
         AVStream *in_stream, *out_stream;

         ret = av_read_frame(ifmt_ctx, &pkt);
         if (ret < 0) break;

         in_stream = ifmt_ctx->streams[pkt.stream_index];
         out_stream = ofmt_ctx->streams[pkt.stream_index];
         log_packet(ifmt_ctx, &pkt, "in");

         /* copy packet */
         av_packet_rescale_ts(&pkt, in_stream->time_base,
 out_stream->time_base);
         pkt.pos = -1;
         log_packet(ofmt_ctx, &pkt, "out");

         ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
         if (ret < 0) {
             fprintf(stderr, "Error muxing packet\n");
             break;
         }
         av_packet_unref(&pkt);
     }

     av_write_trailer(ofmt_ctx);
 end:

     avformat_close_input(&ifmt_ctx);

     /* close output */
     avformat_free_context(ofmt_ctx);
     av_freep(&avio_ctx->buffer);
     av_free(avio_ctx);

     /* write buffer to file */
     {
         FILE* out_file = fopen(out_filename, "w");
         if (!out_file) {
             fprintf(stderr, "Could not open file '%s'\n", out_filename);
             ret = AVERROR(errno);
         } else {
             fwrite(bd.buf, bd.size, 1, out_file);
             fclose(out_file);
         }
     }

     av_free(bd.buf);

     if (ret < 0 && ret != AVERROR_EOF) {
         fprintf(stderr, "Error occurred: %s\n", av_err2str(ret));
         return 1;
     }

     return 0;
 }
 }}}
 - run compiled program as `./avio_writing ../data/forest_video.avi
 forest_video_rewritten.avi`

 While the input file has information on the file size (byte offset 4) and
 number of frames (byte offset of 48) this is FF FF FF FF and 00 00 00 00
 respectively in the output file. Which I think leads to a wrong bitrate
 and duration for the files. Both of these things can cause issues in tools
 consuming such files downstream.

 Bytes in the header of the input file (../data/forest_video.avi):
 {{{
 00000000  52 49 46 46 a6 c5 0d 00  41 56 49 20 4c 49 53 54  |RIFF....AVI
 LIST|
 00000010  ec 11 00 00 68 64 72 6c  61 76 69 68 38 00 00 00
 |....hdrlavih8...|
 00000020  56 82 00 00 98 3a 00 00  00 00 00 00 10 09 00 00
 |V....:..........|
 00000030  74 06 00 00 00 00 00 00  01 00 00 00 00 00 10 00
 |t...............|
 00000040  c0 03 00 00 1c 02 00 00  00 00 00 00 00 00 00 00
 |................|
 00000050  00 00 00 00 00 00 00 00                           |........|
 }}}
 and ffprobe output on that file:
 {{{
 Input #0, avi, from '../data/forest_video.avi':
   Metadata:
     software        : Lavf58.76.100
   Duration: 00:00:55.12, start: 0.000000, bitrate: 130 kb/s
   Stream #0:0: Video: h264 (High) (H264 / 0x34363248), yuv420p(tv,
 smpte170m, progressive), 960x540, 124 kb/s, 29.97 fps, 29.97 tbr, 29.97
 tbn
 }}}

 And hexdump of the file that went through AVIOContext
 (forest_video_rewritten.avi):
 {{{
 00000000  52 49 46 46 ff ff ff ff  41 56 49 20 4c 49 53 54  |RIFF....AVI
 LIST|
 00000010  e8 00 00 00 68 64 72 6c  61 76 69 68 38 00 00 00
 |....hdrlavih8...|
 00000020  0b 00 00 00 ba 3c 00 00  00 00 00 00 00 09 00 00
 |.....<..........|
 00000030  00 00 00 00 00 00 00 00  01 00 00 00 00 00 10 00
 |................|
 00000040  c0 03 00 00 1c 02 00 00  00 00 00 00 00 00 00 00
 |................|
 00000050  00 00 00 00 00 00 00 00                           |........|
 00000058
 }}}

 and ffprobe output:
 {{{
 Input #0, avi, from 'forest_video_rewritten.avi':
   Metadata:
     software        : Lavf60.3.100
   Duration: 00:14:33.81, start: 0.000000, bitrate: 19 kb/s
   Stream #0:0: Video: h264 (High) (H264 / 0x34363248), yuv420p(tv,
 smpte170m, progressive), 960x540, 600 fps, 29.97 tbr, 600 tbn
 }}}

 Maybe something is done incorrectly in this example file, but the
 documentation and examples on using AVIOContext for writing a file "in-
 memory" are quite scarce. I feel like this is the correct way of using
 AVIOContext to write the contents of an .avi file into a buffer (which is
 then written to a file to check the results), but it isn't working
 correctly, as the header that is written to this file does not correspond
 to the data the file contains.
-- 
Ticket URL: <https://trac.ffmpeg.org/ticket/10536>
FFmpeg <https://ffmpeg.org>
FFmpeg issue tracker


More information about the FFmpeg-trac mailing list