FFmpeg: unspecified pixel format when opening vide

2019-07-19 10:52发布

问题:

I am trying to decode a video with a custom context. The purpose is that I want to decode the video directly from memory. In the following code, I am reading from file in the read function passed to avio_alloc_context - but this is just for testing purposes.

I think I've read any post there is on Stackoverflow or on any other website related to this topic. At least I definitely tried my best to do so. While there is much in common, the details differ: people set different flags, some say av_probe_input_format is required, some say it isn't, etc. And for some reason nothing works for me.

My problem is that the pixel format is unspecified (see output below), which is why I run into problems later when calling sws_getContext. I checked pFormatContext->streams[videoStreamIndex]->codec->pix_fmt, and it is -1.

Please note my comments // things I tried and // seems not to help in the code. I think, the answer might be hidden somehwere there. I tried many combinations of hints that I've read so far, but I am missing a detail I guess.

The problem is not the video file, because when I go the standard way and just call avformat_open_input(&pFormatContext, pFilePath, NULL, NULL) without a custom context, everything runs fine.

The code compiles and runs as is.

#include <libavformat/avformat.h>
#include <string.h>
#include <stdio.h>

FILE *f;

static int read(void *opaque, uint8_t *buf, int buf_size) {
    if (feof(f)) return -1;
    return fread(buf, 1, buf_size, f);
}

int openVideo(const char *pFilePath) {
    const int bufferSize = 32768;
    int ret;

    av_register_all();

    f = fopen(pFilePath, "rb");
    uint8_t *pBuffer = (uint8_t *) av_malloc(bufferSize + AVPROBE_PADDING_SIZE);
    AVIOContext *pAVIOContext = avio_alloc_context(pBuffer, bufferSize, 0, NULL,
                      &read, NULL, NULL);

    if (!f || !pBuffer || !pAVIOContext) {
        printf("error: open / alloc failed\n");
        // cleanup...
        return 1;
    }

    AVFormatContext *pFormatContext = avformat_alloc_context();
    pFormatContext->pb = pAVIOContext;

    const int readBytes = read(NULL, pBuffer, bufferSize);

    printf("readBytes = %i\n", readBytes);

    if (readBytes <= 0) {
        printf("error: read failed\n");
        // cleanup...
        return 2;
    }

    if (fseek(f, 0, SEEK_SET) != 0) {
        printf("error: fseek failed\n");
        // cleanup...
        return 3;
    }

    // required for av_probe_input_format
    memset(pBuffer + readBytes, 0, AVPROBE_PADDING_SIZE);

    AVProbeData probeData;
    probeData.buf = pBuffer;
    probeData.buf_size = readBytes;
    probeData.filename = "";
    probeData.mime_type = NULL;

    pFormatContext->iformat = av_probe_input_format(&probeData, 1);

    // things I tried:
    //pFormatContext->flags = AVFMT_FLAG_CUSTOM_IO;
    //pFormatContext->iformat->flags |= AVFMT_NOFILE;
    //pFormatContext->iformat->read_header = NULL;

    // seems not to help (therefore commented out here):
    AVDictionary *pDictionary = NULL;
    //av_dict_set(&pDictionary, "analyzeduration", "8000000", 0);
    //av_dict_set(&pDictionary, "probesize", "8000000", 0);

    if ((ret = avformat_open_input(&pFormatContext, "", NULL, &pDictionary)) < 0) {
        char buffer[4096];
        av_strerror(ret, buffer, sizeof(buffer));
        printf("error: avformat_open_input failed: %s\n", buffer);
        // cleanup...
        return 4;
    }

    printf("retrieving stream information...\n");

    if ((ret = avformat_find_stream_info(pFormatContext, NULL)) < 0) {
        char buffer[4096];
        av_strerror(ret, buffer, sizeof(buffer));
        printf("error: avformat_find_stream_info failed: %s\n", buffer);
        // cleanup...
        return 5;
    }

    printf("nb_streams = %i\n", pFormatContext->nb_streams);

    // further code...

    // cleanup...
    return 0;
}

int main() {
    openVideo("video.mp4");
    return 0;
}

This is the output that I get:
readBytes = 32768
retrieving stream information...
[mov,mp4,m4a,3gp,3g2,mj2 @ 0xdf8d20] stream 0, offset 0x30: partial file [mov,mp4,m4a,3gp,3g2,mj2 @ 0xdf8d20] Could not find codec parameters for stream 0 (Video: h264 (avc1 / 0x31637661), none, 640x360, 351 kb/s): unspecified pixel format
Consider increasing the value for the 'analyzeduration' and 'probesize' options
nb_streams = 2

UPDATE:
Thanks to WLGfx, here is the solution: The only thing that was missing was the seek function. Apparently, implementing it is mandatory for decoding. It is important to return the new offset - and not 0 in case of success (some solutions found in the web just return the return value of fseek, and that is wrong). Here is the minimal solution that made it work:

static int64_t seek(void *opaque, int64_t offset, int whence) {
    if (whence == SEEK_SET && fseek(f, offset, SEEK_SET) == 0) {
        return offset;
    }
    // handling AVSEEK_SIZE doesn't seem mandatory
    return -1;
}

Of course, the call to avio_alloc_context needs to be adapted accordingly:

AVIOContext *pAVIOContext = avio_alloc_context(pBuffer, bufferSize, 0, NULL,
                      &read, NULL, &seek);

回答1:

Seeing as yours is a file based stream then it is seekable so you can provide the AVIO seek when creating the AVIOContext:

avioContext = avio_alloc_context((uint8_t *)avio_buffer, AVIO_QUEUE_SIZE * PKT_SIZE7,
    0,
    this, // *** This is your data pointer to a class or other data passed to the callbacks
    avio_ReadFunc,
    NULL,
    avio_SeekFunc);

Handle the seeking with this callback: (You can cast ptr to your class or other data structure)

int64_t FFIOBufferManager::avio_SeekFunc(void *ptr, int64_t pos64, int whence) {
    // SEEK_SET(0), SEEK_CUR(1), SEEK_END(2), AVSEEK_SIZE
    // ptr is cast to your data or class

    switch (whence) {
    case 0 : // SEEK_SET
    ... etc
    case (AVSEEK_SIZE) : // get size
        return -1; // if you're unable to get the size
        break;
    }

    // set new position in the file

    return (int64_t)new_pos; // new position
}

You can also define the codec and the probesize when attaching the AVIOContext to the AVFormatContext. This allows ffmpeg to seek in the stream to better determine the format.

context->pb = ffio->avioContext;
context->flags = AVFMT_FLAG_CUSTOM_IO;
context->iformat = av_find_input_format("mpegts"); // not necessary
context->probesize = 1200000;

So far I haven't had the need for av_probe_input_format, but then again my streams are mpegts.

Hope this helps.

EDIT: Added a comment to the avio_alloc_context function to mention how the ptr is used in the callbacks.



标签: c ffmpeg