FFmpeg:使用自定义上下文打开视频时未指定的像素格式

时间:2017-12-01 16:35:21

标签: c ffmpeg

我正在尝试使用自定义上下文解码视频。目的是我想直接从内存中解码视频。在下面的代码中,我正在传递给read的{​​{1}}函数中的文件中读取 - 但这仅用于测试目的。

我想我已经阅读过Stackoverflow或与此主题相关的任何其他网站上的任何帖子。至少我绝对尽力这样做。虽然有很多共同点,但细节有所不同:人们设置不同的标志,有些人说avio_alloc_context是必需的,有些人说不是,等等。由于某些原因,对我来说没有任何作用。

我的问题是像素格式未指定(请参阅下面的输出),这就是我在以后调用av_probe_input_format时遇到问题的原因。我检查了sws_getContext,它是-1。

请在代码中注明我的评论pFormatContext->streams[videoStreamIndex]->codec->pix_fmt// things I tried。我想,答案可能会隐藏在那里。到目前为止,我尝试了许多提示组合,但我想错了一个细节。

问题不在于视频文件,因为当我采用标准方式而只是在没有自定义上下文的情况下调用// seems not to help时,一切运行正常。

代码按原样编译和运行。

avformat_open_input(&pFormatContext, pFilePath, NULL, NULL)

这是我得到的输出:
readBytes = 32768
检索流信息...
[mov,mp4,m4a,3gp,3g2,mj2 @ 0xdf8d20]流0,偏移量0x30:部分文件 [mov,mp4,m4a,3gp,3g2,mj2 @ 0xdf8d20]无法找到流0的编解码器参数(视频:h264(avc1 / 0x31637661),无,640x360,351 kb / s):未指定的像素格式
考虑增加'analyzeuration'和'probesize'选项的值 nb_streams = 2

更新
感谢WLGfx,这里有解决方案:唯一缺少的是搜索功能。显然,实现它是解码的必要条件。重要的是返回新的偏移量 - 如果成功则不返回0(在Web中找到的某些解决方案只返回fseek的返回值,这是错误的)。以下是使其起作用的最小解决方案:

#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;
}

当然,对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; } 的调用需要相应调整:

avio_alloc_context

2 个答案:

答案 0 :(得分:2)

看到你的是基于文件的流然后它是可搜索的,所以你可以在创建AVIOContext时提供AVIO搜索:

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

使用此回调处理搜索:(您可以将ptr投射到您的班级或其他数据结构中)

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
}

将AVIOContext附加到AVFormatContext时,您还可以定义编解码器和探测器。这允许ffmpeg在流中寻找以更好地确定格式。

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

到目前为止,我还没有av_probe_input_format的需要,但我的溪流再次出现了。

希望这有帮助。

编辑:在avio_alloc_context函数中添加了评论,以提及回调中ptr的使用方式。

答案 1 :(得分:0)

虽然在您的情况下搜索是正确的答案,但事实是在我的情况下这是不可能的,因为我必须流式传输数据,在这种情况下搜索是不可能的。

所以我不得不调查:为什么需要搜索?

根据 ffmpeg 文档的说法,他们将缓存一些数据,以便在当前编码器/解码器需要时可以进行检索。但该缓冲区相对较小(您可能不想缓存 100 兆字节的数据)。

事实是 MP4 在文件末尾保存了一些元数据(一旦知道)。在读取该格式时,解码器想要寻找文件中非常远的位置(接近末尾)并读取所谓的 moov 原子。如果没有这些信息,系统就不想解压缩您的数据。

为了解决这个问题,我必须做的是使用以下命令移动 moov 原子:

ffmpeg -i far.mp4 -c copy -map 0 -movflags +faststart close.mp4

faststart 意味着您不必流式传输整个文件即可开始播放(解码)文件。