Search code examples
c++ffmpeglibav

FFMPEG error when finding stream information with custom AVIOContext


I am writing software that takes in a file as a stream and decodes it. I have the following custom AVIO code for stream input:

/* Allocate a 4kb buffer for copying. */
std::uint32_t bufSize = 4096;
struct vidBuf
{
    std::byte* ptr;
    int size;
};

vidBuf tmpVidBuf = { const_cast<std::byte*>(videoBuffer.data()),
    static_cast<int>(videoBuffer.size()) };
AVIOContext *avioContext =
    avio_alloc_context(reinterpret_cast<std::uint8_t*>(av_malloc(bufSize)),
                       bufSize, 0,
                       reinterpret_cast<void*>(&tmpVidBuf),
                       [](void *opaque, std::uint8_t *buf, int bufSize) -> int
                       {
                           auto &me = *reinterpret_cast<vidBuf*>(opaque);
                           bufSize = std::min(bufSize, me.size);

                           std::copy_n(me.ptr, bufSize, reinterpret_cast<std::byte*>(buf));
                           me.ptr += bufSize;
                           me.size -= bufSize;
                           return bufSize;
                       }, nullptr, nullptr);

auto avFormatPtr = avformat_alloc_context();
avFormatPtr->pb = avioContext;
avFormatPtr->flags |= AVFMT_FLAG_CUSTOM_IO;
//avFormatPtr->probesize = tmpVidBuf.size;
//avFormatPtr->max_analyze_duration = 5000000;

avformat_open_input(&avFormatPtr, nullptr, nullptr, nullptr);

if(auto ret = avformat_find_stream_info(avFormatPtr, nullptr);
   ret < 0)
    logerror << "Could not open the video file: " << makeAVError(ret) << '\n';

However, when I run this code I get the error:

[mov,mp4,m4a,3gp,3g2,mj2 @ 0x55d10736d580] stream 0, offset 0x30: partial file
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x55d10736d580] Could not find codec parameters for stream 0 (Video: h264 (avc1 / 0x31637661), none(tv, bt709), 540x360, 649 kb/s): unspecified pixel format
Consider increasing the value for the 'analyzeduration' (0) and 'probesize' (5000000) options
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf58.76.100
  Duration: 00:04:08.41, start: 0.000000, bitrate: N/A
  Stream #0:0(und): Video: h264 (avc1 / 0x31637661), none(tv, bt709), 540x360, 649 kb/s, SAR 1:1 DAR 3:2, 29.97 fps, 29.97 tbr, 30k tbn, 60k tbc (default)
    Metadata:
      handler_name    : ISO Media file produced by Google Inc. Created on: 01/10/2021.
      vendor_id       : [0][0][0][0]
  Stream #0:1(und): Audio: aac (mp4a / 0x6134706D), 22050 Hz, mono, fltp, 69 kb/s (default)
    Metadata:
      handler_name    : ISO Media file produced by Google Inc. Created on: 01/10/2021.
      vendor_id       : [0][0][0][0]
Assertion desc failed at libswscale/swscale_internal.h:677

Note the absence of the YUV420p part in the video stream data.

This is strange since if I run my program with a different mp4 file it works perfectly fine, this error only occurs with a specific mp4 file. I know that the mp4 file is valid since mpv can play it, and ffprobe is able to get its metadata:

Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'heard.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf58.76.100
  Duration: 00:04:08.41, start: 0.000000, bitrate: 724 kb/s
  Stream #0:0(und): Video: h264 (Main) (avc1 / 0x31637661), yuv420p(tv, bt709), 540x360 [SAR 1:1 DAR 3:2], 649 kb/s, 29.97 fps, 29.97 tbr, 30k tbn, 59.94 tbc (default)
    Metadata:
      handler_name    : ISO Media file produced by Google Inc. Created on: 01/10/2021.
      vendor_id       : [0][0][0][0]
  Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 22050 Hz, mono, fltp, 69 kb/s (default)
    Metadata:
      handler_name    : ISO Media file produced by Google Inc. Created on: 01/10/2021.
      vendor_id       : [0][0][0][0]

As you can see by my code I also tried setting analyzeduration and probesize, but this did not fix the issue.

I also know that this error is because of my custom io because when I have avformat_open_input open the file directly, it is able to be decoded just fine. I am new to ffmpeg, so I might have missed something simple.


Solution

  • As SuRGeoNix pointed out, I had not implemented a seek function for the AVIO context; I think this messed up FFMPEG since it could not figure out the size of the buffer. This is my now working code:

    std::uint32_t bufSize = 4096;
    struct vidBuf
    {
        std::byte* ptr;
        std::byte* origPtr;
        int size;
        int fullSize;
    };
    
    vidBuf tmpVidBuf = { const_cast<std::byte*>(videoBuffer.data()),
    const_cast<std::byte*>(videoBuffer.data()),
    static_cast<int>(videoBuffer.size()),
    static_cast<int>(videoBuffer.size()), };
    
    AVIOContext *avioContext =
        avio_alloc_context(reinterpret_cast<std::uint8_t*>(av_malloc(bufSize)),
                           bufSize, 0,
                           reinterpret_cast<void*>(&tmpVidBuf),
                           [](void *opaque, std::uint8_t *buf, int bufSize) -> int
                           {
                               auto &me = *reinterpret_cast<vidBuf*>(opaque);
                               bufSize = std::min(bufSize, me.size);
    
                               std::copy_n(me.ptr, bufSize, reinterpret_cast<std::byte*>(buf));
                               me.ptr += bufSize;
                               me.size -= bufSize;
                               return bufSize;
                           },
                           nullptr,
                           [](void *opaque, std::int64_t where, int whence) -> std::int64_t
                           {
                               auto me = reinterpret_cast<vidBuf*>(opaque);
    
                               switch(whence)
                               {
                               case AVSEEK_SIZE:
                                   /* Maybe size left? */
                                   return me->fullSize;
                                   break;
                               case SEEK_SET:
                                   if(me->fullSize > where)
                                   {
                                       me->ptr = me->origPtr + where;
                                       me->size = me->fullSize - where;
                                   }
                                   else
                                       return EOF;
                                   break;
                               case SEEK_CUR:
                                   if(me->size > where)
                                   {
                                       me->ptr += where;
                                       me->size -= where;
                                   }
                                   else
                                       return EOF;
                                   break;
                               case SEEK_END:
                                   if(me->fullSize > where)
                                   {
                                       me->ptr = (me->origPtr + me->fullSize) - where;
                                       int curPos = me->ptr - me->origPtr;
                                       me->size = me->fullSize - curPos;
                                   }
                                   else
                                       return EOF;
                                   break;
                               default:
                               /* On error, do nothing, return current position of file. */
                                   logerror << "Could not process buffer seek: "
                                            << whence << ".\n";
                                   break;
                               }
                               return me->ptr - me->origPtr;
                           });