ffmpeg将多个yuv420p图片编码成h264文件

9 阅读3分钟

将yuv420p编码成h264最小示例

#include <stdio.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>

 
int main(int argc, char *argv[])
{
    int ret = -1;
    int frame_index = 0;

    AVFormatContext *fmt_ctx = NULL;
    const AVOutputFormat *ofmt = NULL;
    AVStream *stream = NULL;

    const AVCodec *codec = NULL;
    AVCodecContext *codec_ctx = NULL;
    
    AVFrame *frame = NULL;
    AVPacket *packet = NULL;

    int width = 1280;                                 // 输入YUV文件的宽度
    int height = 536;                                 // 输入YUV文件的高度
    enum AVPixelFormat pix_fmt = AV_PIX_FMT_YUV420P;  // 输入YUV文件的像素格式
    int frame_rate = 60;                              // 输出视频帧率

    char in_file[256];
    char *in_filename  = "frame_out/%dx%d_%d.yuv420p";
    char *out_filename = "xxx.h264";
 


    // 分配输出格式上下文
    avformat_alloc_output_context2(&fmt_ctx, NULL, NULL, out_filename);
    if (!fmt_ctx)
    {
        printf("avformat_alloc_output_context2 failed\n");
        goto end;
    }
    ofmt = fmt_ctx->oformat;

    // 创建新的视频流
    stream = avformat_new_stream(fmt_ctx, NULL);
    if (!stream)
    {
        printf("avformat_new_stream failed\n");
        goto end;
    }
 
    // 查找编码器
    codec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!codec)
    {
        printf("Codec not found\n");
        goto end;
    }
    printf("codec name: %s\n", codec->name);
 
    // 分配编码器上下文
    codec_ctx = avcodec_alloc_context3(codec);
    if (!codec_ctx)
    {
        printf("avcodec_alloc_context3 failed\n");
        goto end;
    }

    /* 设置编码器参数 */
    codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
    codec_ctx->width = width;
    codec_ctx->height = height;
    codec_ctx->time_base = (AVRational){1, frame_rate};     // 设置时间基
 
    // 打开编码器
    if (avcodec_open2(codec_ctx, NULL, NULL) < 0)
    {
        printf("avcodec_open2 failed\n");
        goto end;
    }
 
    // 将编码器参数复制到流
    ret = avcodec_parameters_from_context(stream->codecpar, codec_ctx);
    if (ret < 0)
    {
        printf("avcodec_parameters_from_context failed\n");
        goto end;
    }
 
    frame = av_frame_alloc();
    packet = av_packet_alloc();
    if (!frame || !packet)
    {
        printf("allocate frame or packet failed\n");
        goto end;
    }
 
    // 设置帧参数
    frame->format = codec_ctx->pix_fmt;
    frame->width = codec_ctx->width;
    frame->height = codec_ctx->height;
 
    // 分配帧数据缓冲区
    ret = av_frame_get_buffer(frame, 0);
    if (ret < 0)
    {
        printf("av_frame_get_buffer failed\n");
        goto end;
    }
 
    // 打开输出文件
    if (!(ofmt->flags & AVFMT_NOFILE))
    {
        ret = avio_open(&fmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
        if (ret < 0)
        {
            printf("open output file failed\n");
            goto end;
        }
    }
 
    // 写文件头
    ret = avformat_write_header(fmt_ctx, NULL);
    if (ret < 0)
    {
        printf("avformat_write_header failed\n");
        goto end;
    }
 
    // 编码循环
    while (1)
    {
        // 从文件中读取一帧yuv420p图片
        snprintf(in_file, sizeof(in_file), in_filename, width, height, frame_index++);
        printf("in_file: %s\n", in_file);

        FILE * f = fopen(in_file, "rb");
        if (f == NULL) {
          perror("fopen failed");
          break;
        }
        fread(frame->data[0], 1, width*height, f);
        fread(frame->data[1], 1, width*height/4, f);
        fread(frame->data[2], 1, width*height/4, f);
        fclose(f);

        frame->pts = frame_index;
        frame_index++;
 
        // 发送帧到编码器
        ret = avcodec_send_frame(codec_ctx, frame);
        if (ret < 0)
        {
            printf("avcodec_send_frame error (errmsg '%s')\n", av_err2str(ret));
            goto end;
        }
 
        // 接收编码后的数据包
        while (ret >= 0)
        {
            ret = avcodec_receive_packet(codec_ctx, packet);
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            {
                break;
            }
            else if (ret < 0)
            {
                printf("avcodec_receive_packet error (errmsg '%s')\n", av_err2str(ret));
                goto end;
            }

            // 写数据包到输出文件
            ret = av_interleaved_write_frame(fmt_ctx, packet);
            if (ret < 0)
            {
                printf("av_interleaved_write_frame failed\n");
                av_packet_unref(packet);
                goto end;
            }
 
            av_packet_unref(packet);
        }
    }
 
    // 发送NULL到编码器,刷新编码器内部缓冲区
    ret = avcodec_send_frame(codec_ctx, NULL);
    while (ret >= 0)
    {
        ret = avcodec_receive_packet(codec_ctx, packet);
        if (ret == AVERROR_EOF)
        {
            break;
        }
        else if (ret < 0)
        {
            printf("avcodec_receive_packet error (errmsg '%s')\n", av_err2str(ret));
            goto end;
        }
 
        ret = av_interleaved_write_frame(fmt_ctx, packet);
        if (ret < 0)
        {
            printf("av_interleaved_write_frame failed\n");
            av_packet_unref(packet);
            goto end;
        }
 
        av_packet_unref(packet);
    }
 
    // 写文件尾
    av_write_trailer(fmt_ctx);
 
end:
    av_frame_free(&frame);
    av_packet_free(&packet);
    avcodec_free_context(&codec_ctx);
    avformat_close_input(&fmt_ctx);

    return 0;
}