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

标签 c ffmpeg

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

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

我的问题是未指定像素格式(请参阅下面的输出),这就是我后来在调用 sws_getContext 时遇到问题的原因。我查看了pFormatContext->streams[videoStreamIndex]->codec->pix_fmt,是-1。

请注意我在代码中的评论//我尝试过的事情//似乎没有帮助。我认为,答案可能隐藏在那里。我尝试了迄今为止已阅读的许多提示组合,但我想我遗漏了一个细节。

问题不在于视频文件,因为当我采用标准方式并在没有自定义上下文的情况下调用 avformat_open_input(&pFormatContext, pFilePath, NULL, NULL) 时,一切运行正常。

代码按原样编译和运行。

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

这是我得到的输出:
读取字节数 = 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):未指定的像素格式
考虑增加“analyzeduration”和“probesize”选项的值
nb_streams = 2

更新:
感谢 WLGfx,这是解决方案:唯一缺少的是搜索功能。显然,实现它对于解码是强制性的。返回新的偏移量很重要——如果成功则返回新的偏移量而不是 0(网上找到的一些解决方案只返回 fseek 的返回值,这是错误的)。这是使其工作的最小解决方案:

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的调用需要做相应的调整:

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

最佳答案

因为你的是一个基于文件的流,所以它是可搜索的,所以你可以在创建 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,但我的流还是 mpegts。

希望这对您有所帮助。

编辑:向 avio_alloc_context 函数添加注释以提及如何在回调中使用 ptr

关于c - FFmpeg:使用自定义上下文打开视频时未指定像素格式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47597638/

相关文章:

c - 使用 `int x = x;` 表达式(C 语言)可能是什么情况?

c++ - 如何在 C++ 中使用 ffmpeg 流式传输 opencv Mat

c# - 如何从 JPEG 文件列表创建视频?

C高效读取20000000行文件的方法

c++ - 如何在 C 中创建固定持续时间的计时器

c - time_t 的转换如何在 C 中工作?

C 重定位 0 具有无效的符号索引

encryption - 如何为我的加密子播放列表(使用 ffmpeg 创建)创建主 m3u8 播放列表?

ffmpeg - 源视频和减去的音频的持续时间不同

python - 流式传输帧序列的有效方法