You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

FFmpeg自定义IO读取YUV420p时avformat_open_input调用失败求助

问题:自定义IO读取YUV420p原始文件时avformat_open_input失败

尝试对私有内存中的实时YUV420p原始数据编码失败,预研阶段用自定义IO读取本地YUV文件也失败。用FFmpeg命令生成的YUV文件可被ffplay正常播放,但C++代码中调用avformat_open_input失败,仅读取MP4文件正常。已尝试调整参数、增减seek回调,均无效。

测试步骤

  • 生成测试MP4文件:
    ffmpeg -f lavfi -i testsrc=duration=10:size=1920x1080:rate=30 1920x1080p30_10s.mp4
    
  • 转换为YUV420p格式MP4:
    ffmpeg -i 1920x1080p30_10s.mp4 -c:v libx264 -pix_fmt yuv420p 1920x1080p30_10s_420p.mp4
    
  • 生成YUV原始文件:
    ffmpeg -i 1920x1080p30_10s_420p.mp4 -c:v rawvideo -pixel_format yuv420p output_1920x1080_420p.yuv
    

编译命令

g++ ffmpeg_custom_io.cpp -lavformat -lavdevice -lavcodec -lavutil -lswscale -lswresample -lpthread -pthread -o ffmpeg_custom_io

完整代码

using namespace std;

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>

extern "C" {
    #include <libavformat/avformat.h>
    #include <libavcodec/avcodec.h>
    
    #include <libavutil/opt.h>
    #include <libavutil/imgutils.h>
    #include <libavutil/file.h>
}


struct buffer_data {
    uint8_t *   bd_ptr;
    size_t      bd_size; ///< size left in the buffer
};

int readFileBuffer(void *opaque, uint8_t *buf, int buf_size)
{

    printf("Start reading custom IO\n");
    struct buffer_data *bd = (struct buffer_data *)opaque;
    buf_size = FFMIN(buf_size, bd->bd_size);

    printf("min buf size:%d\n", buf_size);

    if (!buf_size) {
        printf("return -1, size = %d\n", buf_size);
        return -1;
    }
    printf("ptr:%p size:%zu\n", bd->bd_ptr, bd->bd_size);

    /* copy internal buffer data to buf */
    memcpy(buf, bd->bd_ptr, buf_size);
    bd->bd_ptr  += buf_size;
    bd->bd_size -= buf_size;
    printf("End reading custom IO\n");
    return buf_size;

}

int64_t seekFunction(void* opaque, int64_t offset, int whence)
{
    std::cout << "SEEK " << std::endl;
    if (whence == AVSEEK_SIZE)
        return -1; // I don't know "size of my handle in bytes"
}

static void encode(AVCodecContext *enc_ctx, AVFrame *frame, AVPacket *pkt,
                   FILE *outfile)
{
    int ret;

    /* send the frame to the encoder */
    if (frame)
        printf("Send frame %3" PRId64 "\n", frame->pts);

    ret = avcodec_send_frame(enc_ctx, frame);
    if (ret < 0) {
        fprintf(stderr, "Error sending a frame for encoding\n");
        exit(1);
    }

    while (ret >= 0) {
        ret = avcodec_receive_packet(enc_ctx, pkt);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            return;
        else if (ret < 0) {
            fprintf(stderr, "Error during encoding\n");
            exit(1);
        }

        printf("Write packet %3" PRId64 " (size=%5d)\n", pkt->pts, pkt->size);
        fwrite(pkt->data, 1, pkt->size, outfile);
        av_packet_unref(pkt);
    }
}

int main(int argc, char **argv)
{

    av_log_set_level(AV_LOG_MAX_OFFSET);

    AVFormatContext* pAVFormatContext;
    uint8_t *buffer = NULL, *avio_ctx_buffer = NULL;
    size_t buffer_size, avio_ctx_buffer_size = 32768;

    int ret = 0;
    char errbuf[100];

    // Alloc format context
    //--------------------
    if ( !(pAVFormatContext = avformat_alloc_context()) )
    {
        ret = AVERROR(ENOMEM);
        std::cout << "error=" << ret << " Fail to allocate avformat_alloc_context()\n";
        return false;
    }

    // Copy from ffmpeg example - bd buffer
    //--------------------
    struct buffer_data bd = { 0, 0 };

    // slurp file content into buffer
    ret = av_file_map("output_1920x1080_420p.yuv", &buffer, &buffer_size, 0, NULL);
    if (ret < 0)
    {
        std::cout << "error=" << ret << " Fail to allocate avformat_alloc_context()\n";
        return false;
    }

    // fill opaque structure used by the AVIOContext read callback
    bd.bd_ptr  = buffer;
    bd.bd_size = buffer_size;

    // Prepare AVIO context
    //--------------------
    avio_ctx_buffer = static_cast<uint8_t *>(av_malloc(avio_ctx_buffer_size + AV_INPUT_BUFFER_PADDING_SIZE));

    if (!avio_ctx_buffer) {
        ret = AVERROR(ENOMEM);
        std::cout << "error=" << ret << " Fail to allocate av_malloc()\n";
        return false;
    }

    AVIOContext* pAVIOContext = avio_alloc_context(avio_ctx_buffer,
                                                   avio_ctx_buffer_size,
                                                   0,
                                                   &bd,
                                                   readFileBuffer,
                                                   NULL,
                                                   seekFunction);

    if (!pAVIOContext) {
        ret = AVERROR(ENOMEM);
        std::cout << "error=" << ret << " Fail to allocate avio_alloc_context()\n";
        return false;
    }

    pAVFormatContext->pb = pAVIOContext;
    pAVFormatContext->flags = AVFMT_FLAG_CUSTOM_IO; // - NOT SURE ABOUT THIS FLAG

    // Handle ffmpeg input
    //--------------------
    AVDictionary* options_in = NULL;
    //av_dict_set(&options_in, "framerate", "30", 0);
    av_dict_set(&options_in, "video_size", "1920x1080", 0);
    av_dict_set(&options_in, "pixel_format", "yuv420p", 0);
    av_dict_set(&options_in, "vcodec", "rawvideo", 0);

    if ((ret = avformat_open_input(&pAVFormatContext, "IO", NULL, &options_in) < 0) )
    {
        printf ("err=%d\n", ret);
        av_strerror(ret, errbuf, sizeof(errbuf));
        fprintf(stderr, "Unable to open err=%s\n", errbuf);
        return false;
    }

    //Raw video doesn't contain any stream information.
    //if ((ret = avformat_find_stream_info(pAVFormatContext, 0)) < 0) {
    //    fprintf(stderr, "Failed to retrieve input stream information");
    //}

    AVCodec* pAVCodec = avcodec_find_decoder(AV_CODEC_ID_RAWVIDEO); //Get pointer to rawvideo codec.

    AVCodecContext* pAVCodecContext = avcodec_alloc_context3(pAVCodec); //Allocate codec context.

    //Fill the codec context based on the values from the codec parameters.
    AVStream *vid_stream = pAVFormatContext->streams[0];
    avcodec_parameters_to_context(pAVCodecContext, vid_stream->codecpar);

    avcodec_open2(pAVCodecContext, pAVCodec, NULL); //Open the codec

    //Allocate memory for packet and frame
    AVPacket* pAVPacketIn = av_packet_alloc();
    AVFrame* pAVFrameOut = av_frame_alloc();

    /* find the mpeg1video encoder */
    AVCodec* pAVCodecOut = avcodec_find_encoder_by_name("libx264");
    if (!pAVCodecOut) {
        fprintf(stderr, "Codec '%s' not found\n", "libx264");
        exit(1);
    }

    AVCodecContext* pAVCodecContextOut = avcodec_alloc_context3(pAVCodecOut);
    if (!pAVCodecContextOut) {
        fprintf(stderr, "Could not allocate video codec context\n");
        exit(1);
    }

    AVPacket* pAVPacketOut = av_packet_alloc();
    if (!pAVPacketOut)
        exit(1);

    /* put sample parameters */
    pAVCodecContextOut->bit_rate = 2000000;
    /* resolution must be a multiple of two */
    pAVCodecContextOut->width = 1280;
    pAVCodecContextOut->height = 720;
    /* frames per second */
    pAVCodecContextOut->time_base = (AVRational){1, 30};
    pAVCodecContextOut->framerate = (AVRational){30, 1};

    /* emit one intra frame every ten frames
     * check frame pict_type before passing frame
     * to encoder, if frame->pict_type is AV_PICTURE_TYPE_I
     * then gop_size is ignored and the output of encoder
     * will always be I frame irrespective to gop_size
     */
    pAVCodecContextOut->gop_size = 10;
    pAVCodecContextOut->max_b_frames = 0;
    pAVCodecContextOut->pix_fmt = AV_PIX_FMT_YUV420P;

    if (pAVCodecOut->id == AV_CODEC_ID_H264)
        av_opt_set(pAVCodecContextOut->priv_data, "preset", "slow", 0);

    /* open it */
    ret = avcodec_open2(pAVCodecContextOut, pAVCodecOut, NULL);
    if (ret < 0) {
        //fprintf(stderr, "Could not open codec: %s\n", av_err2str(ret));
        exit(1);
    }

    FILE* filename_out = fopen("out_vid_h264.mp4", "wb");
    if (!filename_out) {
        fprintf(stderr, "Could not open %s\n", "out_vid_h264.mp4");
        exit(1);
    }

    pAVFrameOut->format = pAVCodecContextOut->pix_fmt;
    pAVFrameOut->width  = pAVCodecContextOut->width;
    pAVFrameOut->height = pAVCodecContextOut->height;


    uint8_t endcode_suf[] = { 0, 0, 1, 0xb7 };
    int i = 0;
    //Read video frames and pass through the decoder.
    //Note: Since the video is rawvideo, we don't really have to pass it through the decoder.
    while (av_read_frame(pAVFormatContext, pAVPacketIn) >= 0)
    {
        //The video is not encoded - passing through the decoder is simply copy the data.
        avcodec_send_packet(pAVCodecContext, pAVPacketIn);    //Supply raw packet data as input to the "decoder".
        avcodec_receive_frame(pAVCodecContext, pAVFrameOut);   //Return decoded output data from the "decoder".

        fflush(stdout);

        pAVFrameOut->pts = i++;

        /* encode the image */
        encode(pAVCodecContextOut, pAVFrameOut, pAVPacketOut, filename_out);
    }

    /* flush the encoder */
    encode(pAVCodecContextOut, NULL, pAVPacketOut, filename_out);

    /* Add sequence end code to have a real MPEG file.
       It makes only sense because this tiny examples writes packets
       directly. This is called "elementary stream" and only works for some
       codecs. To create a valid file, you usually need to write packets
       into a proper file format or protocol; see mux.c.
     */
    if (pAVCodecOut->id == AV_CODEC_ID_MPEG1VIDEO || pAVCodecOut->id == AV_CODEC_ID_MPEG2VIDEO)
        fwrite(endcode_suf, 1, sizeof(endcode_suf), filename_out);
    fclose(filename_out);


    return 0;
}

错误输出

./ffmpeg_custom_io 
Start reading custom IO
min buf size:32768
ptr:0x7f236a200000 size:933120000
End reading custom IO
Probing adp score:25 size:2048
Probing adp score:25 size:4096
Probing adp score:25 size:8192
Probing adp score:25 size:16384
Start reading custom IO
min buf size:32768
ptr:0x7f236a208000 size:933087232
End reading custom IO
Start reading custom IO
min buf size:65536
ptr:0x7f236a210000 size:933054464
End reading custom IO
Start reading custom IO
min buf size:131072
ptr:0x7f236a220000 size:932988928
End reading custom IO
Start reading custom IO
min buf size:262144
ptr:0x7f236a240000 size:932857856
End reading custom IO
Start reading custom IO
min buf size:524288
ptr:0x7f236a280000 size:932595712
End reading custom IO
err=1
Unable to open err=Error number 1 occurred

解决方案

1. 修复avformat_open_input条件判断错误

代码中括号位置错误,导致ret被赋值为比较结果而非函数真实返回码。修正后:

ret = avformat_open_input(&pAVFormatContext, "IO", NULL, &options_in);
if (ret < 0)
{
    printf ("err=%d\n", ret);
    av_strerror(ret, errbuf, sizeof(errbuf));
    fprintf(stderr, "Unable to open err=%s\n", errbuf);
    return false;
}

2. 显式指定输入格式为rawvideo

原始YUV文件无格式标识,FFmpeg无法自动探测,需手动指定输入格式:

AVInputFormat* ifmt = av_find_input_format("rawvideo");
ret = avformat_open_input(&pAVFormatContext, "IO", ifmt, &options_in);

3. 补全seek函数逻辑

当前seek函数未处理完整的定位逻辑,会导致未定义行为,修正后:

// 先修改buffer_data结构体,加入完整文件大小和起始指针
struct buffer_data {
    uint8_t *bd_start;  // 文件起始指针
    uint8_t *bd_ptr;    // 当前读取指针
    size_t   bd_size;   // 剩余大小
    size_t   total_size;// 文件总大小
};

int64_t seekFunction(void* opaque, int64_t offset, int whence)
{
    struct buffer_data *bd = (struct buffer_data *)opaque;
    int64_t new_pos = -1;

    switch(whence) {
        case SEEK_SET:
            new_pos = offset;
            break;
        case SEEK_CUR:
            new_pos = (bd->bd_ptr - bd->bd_start) + offset;
            break;
        case SEEK_END:
            new_pos = bd->total_size + offset;
            break;
        case AVSEEK_SIZE:
            return bd->total_size;
        default:
            return -1;
    }

    // 检查位置是否越界
    if (new_pos < 0 || new_pos > (int64_t)bd->total_size) {
        return -1;
    }

    bd->bd_ptr = bd->bd_start + new_pos;
    bd->bd_size = bd->total_size - new_pos;
    return new_pos;
}

同时在main函数中初始化buffer_data时补充字段:

bd.bd_start = buffer;
bd.bd_ptr = buffer;
bd.
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 02:53:40