使用ffmpeg3.x进行YUV420P->H.264的简单转码

本文代码是基于雷霄骅的《最简单的基于FFMPEG的视频编码器(YUV编码为H.264)》一文修改得来的。

由于雷霄骅原文中采用的API在新版本ffmpeg中,关健的编码API发生了变化,我在此基础上,参考了ffmpeg3.x的sample和header修改后得到。

主要的变化是在编码API上,原来是通过avcodec_encode_video2()来完成编码的,现在编码API变为avcodec_send_frame()/avcodec_receive_packet()组合。

avcodec_send_frame()顾名词义就是发送一帧去编码。avcodec_receive_packet()是吐出来的编码包,写到文件中就是H.264数据了。

另外我也是初学者,对个别编码参数的含义还没有完全搞懂,还有就是在将所有帧编码写入文件后,最后为什么要flush一下,也还没有搞明白,相信以后我会知道答案的^_^

以下代码在vs 2013上跑通,ffmpeg来自:ffmpeg-20170525-b946bd8-win32-dev

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

//encode one frame
static int encode(AVCodecContext *pCodecCtx, AVFrame *pFrame, AVPacket *pPkt, FILE *out_file) {

    int got_packet = 0;
    int ret = avcodec_send_frame(pCodecCtx, pFrame);
    if (ret < 0) {
        //failed to send frame for encoding
        return -1;
    }
    while (!ret) {
        ret = avcodec_receive_packet(pCodecCtx, pPkt);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            return 0;
        }else if (ret < 0) {
            //error during encoding
            return -1;
        }

        printf("Write frame %d, size=%d\n", pPkt->pts, pPkt->size);
        fwrite(pPkt->data, 1, pPkt->size, out_file);
        av_packet_unref(pPkt);
    }
    return 0;
}

int _tmain(int argc, _TCHAR* argv[])
{
    AVFormatContext *pFormatCtx;
    AVOutputFormat *pOutputFmt;
    AVStream *pStream;
    AVCodecContext *pCodecCtx;
    AVCodec *pCodec;
    AVPacket *pkt;
    AVFrame *pFrame;

    FILE *in_file = NULL, *out_file = NULL;
    in_file = fopen("ds_480x272.yuv", "rb");
    if (in_file == NULL){
        printf("cannot open in file\n");
        goto OUT;
    }
    int in_w = 480, in_h = 272;
    int nFrameNum = 100;

    out_file = fopen("out.h264", "wb");
    if (out_file == NULL) {
        printf("cannot create out file\n");
        goto OUT;
    }

    uint8_t* pFrameBuf = NULL;
    int frame_buf_size = 0;
    int y_size = 0;
    int nEncodedFrameCount = 0;
    uint8_t endcode[] = { 0, 0, 1, 0xb7 };

    av_register_all();
    pFormatCtx = avformat_alloc_context();
    pOutputFmt = av_guess_format(NULL, "test.h264", NULL);
    pFormatCtx->oformat = pOutputFmt;

    //除了以下方法,另外还可以使用avcodec_find_encoder_by_name()来获取AVCodec
    pCodec = avcodec_find_encoder(pOutputFmt->video_codec);
    if (!pCodec) {
        //cannot find encoder
        return -1;
    }
    pCodecCtx = avcodec_alloc_context3(pCodec);
    if (!pCodecCtx) {
        //failed get AVCodecContext
        return -1;
    }
    pkt = av_packet_alloc();
    if (!pkt){
        return -1;
    }

    //pCodecCtx = pStream->codec;
    pCodecCtx->codec_id = pOutputFmt->video_codec;
    pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
    pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
    pCodecCtx->width = in_w;
    pCodecCtx->height = in_h;
    pCodecCtx->time_base.num = 1;
    pCodecCtx->time_base.den = 25;
    //pCodecCtx->time_base = (AVRational){ 1, 25 };
    pCodecCtx->bit_rate = 400000;   //???
    pCodecCtx->gop_size = 250;      //???
    //pCodecCtx->framerate = (AVRational){ 25, 1 };
    //H264
    //pCodecCtx->me_range = 16;     //???
    //pCodecCtx->max_qdiff = 4;     //???
    //pCodecCtx->qcompress = 0.6;       //???
    pCodecCtx->qmin = 10;           //???
    pCodecCtx->qmax = 51;           //???
    //Optional Param
    pCodecCtx->max_b_frames = 3;

    //Set Option
    AVDictionary *param = NULL;
    //H.264
    if (pCodecCtx->codec_id == AV_CODEC_ID_H264) {
        av_dict_set(&param, "preset", "slow", 0);
        av_dict_set(&param, "tune", "zerolatency", 0);
        //av_dict_set(&param, "profile", "main", 0);
    }
    //H.265
    if (pCodecCtx->codec_id == AV_CODEC_ID_H265) {
        av_dict_set(&param, "preset", "ultrafast", 0);
        av_dict_set(&param, "tune", "zero-latency", 0);
    }

    if (avcodec_open2(pCodecCtx, pCodec, &param) < 0) {
        //failed to open codec
        return -1;
    }

    pFrame = av_frame_alloc();
    if (!pFrame) {
        fprintf(stderr, "Could not allocate the video frame data\n");
        goto OUT;
    }
    pFrame->format = pCodecCtx->pix_fmt;
    pFrame->width = pCodecCtx->width;
    pFrame->height = pCodecCtx->height;

    int ret = av_frame_get_buffer(pFrame, 32);
    if (ret < 0) {
        fprintf(stderr, "Could not allocate the video frame data\n");
        goto OUT;
    }

    frame_buf_size = av_image_get_buffer_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, 1);
    pFrameBuf = (uint8_t*)av_malloc(frame_buf_size);
    av_image_fill_arrays(pFrame->data, pFrame->linesize, 
        pFrameBuf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, 1);

    y_size = pCodecCtx->width * pCodecCtx->height;

    for (int i = 0; i < nFrameNum; i++) {
        //Read raw YUV data
        if (fread(pFrameBuf, 1, y_size * 3 / 2, in_file) <= 0) {
            //failed to read raw YUV data
            return -1;
        }
        else if (feof(in_file)) {
            break;
        }

        ret = av_frame_make_writable(pFrame);
        if (ret < 0) {
            goto OUT;
        }

        pFrame->data[0] = pFrameBuf;                //Y
        pFrame->data[1] = pFrameBuf + y_size;       //U
        pFrame->data[2] = pFrameBuf + y_size*5/4;   //V
        //PTS
        pFrame->pts = i;
        //encode
        encode(pCodecCtx, pFrame, pkt, out_file);
    }

    //flush the encoder
    encode(pCodecCtx, NULL, pkt, out_file);

    //add sequence end code to have a real MPEG file
    fwrite(endcode, 1, sizeof(endcode), out_file);

    avcodec_free_context(&pCodecCtx);
    avformat_free_context(pFormatCtx);
    av_frame_free(&pFrame);
    av_packet_free(&pkt);
    av_free(pFrameBuf);

OUT:
    if (out_file)
        fclose(out_file);
    if (in_file)
        fclose(in_file);
    system("pause");

    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值