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.
相关产品推荐
相关产品推荐

