FFmpeg4入门12:yuv编码为h264

1080p画质的视频帧有1920 * 1080=2073600个像素点,每个像素点为3通道,每个通道每个像素点为8位。那么,一张图片为1920 * 1080 * 3 * 8bit=4976400bit=6220800B=6075KB=6MB。那么一个普通三通道1080P的30帧的1秒的视频尺寸为:6 * 30 * 1=180M。

这个大小对于本地存储和网络传输都是一个考验,为了减少传输带宽、减小存储空间,就需要将视频压缩。这个压缩的过程叫编码。常用的是H264编码。h264编码器编码完成的文件格式为*.h264,与音频文件*.mp3和字幕文件*.ass打包后就是常见的MP4视频文件了。

yuv格式测试视频下载地址:yuv下载

根据网站提供的参数:宽为352,高为288,300帧,大小为44550kb。352* 288* 3* 8bit=2433024bit=304128B=297kB。300* 297kb=89100kb,yuv420p采样方式,大小为89100kb/2=44550kb。

编码流程为:

flow

编码函数调用流程:

yuv2h264

开发

打开文件、设置编码器参数、打开编码器

int main()
{
    
    
    AVFormatContext *fmtCtx = NULL;
    AVOutputFormat *outFmt = NULL;
    AVStream *vStream = NULL;
    AVCodecContext *codecCtx = NULL;
    AVCodec *codec = NULL;
    AVPacket *pkt=av_packet_alloc(); //创建已编码帧

    uint8_t *picture_buf = NULL;
    AVFrame *picFrame = NULL;
    size_t size;
    int ret = -1;

    //[1]!打开视频文件
    FILE *in_file = fopen("akiyo_cif.yuv", "rb");
    if (!in_file) {
    
    
        printf("can not open file!\n");
        return -1;
    }
    //[1]!

    do{
    
    
        //[2]!打开输出文件,并填充fmtCtx数据
        int in_w = 352,in_h=288,frameCnt=300;
        const char *outFile = "result.h264";
        if(avformat_alloc_output_context2(&fmtCtx,NULL,NULL,outFile)<0){
    
    
            printf("Cannot alloc output file context.\n");
            break;
        }
        outFmt=fmtCtx->oformat;
        //[2]!

        //[3]!打开输出文件
        if(avio_open(&fmtCtx->pb,outFile,AVIO_FLAG_READ_WRITE)<0){
    
    
            printf("output file open failed.\n");
            break;
        }
        //[3]!

        //[4]!创建h264视频流,并设置参数
        vStream = avformat_new_stream(fmtCtx,codec);
        if(vStream ==NULL){
    
    
            printf("failed create new video stream.\n");
            break;
        }
        vStream->time_base.den=25;
        vStream->time_base.num=1;
        //[4]!

        //[5]!编码参数相关
        AVCodecParameters *codecPara= fmtCtx->streams[vStream->index]->codecpar;
        codecPara->codec_type=AVMEDIA_TYPE_VIDEO;
        codecPara->width=in_w;
        codecPara->height=in_h;
        //[5]!

        //[6]!查找编码器
        codec = avcodec_find_encoder(outFmt->video_codec);
        if(codec == NULL){
    
    
            printf("Cannot find any endcoder.\n");
            break;
        }
        //[6]!

        //[7]!设置编码器内容
        codecCtx = avcodec_alloc_context3(codec);
        avcodec_parameters_to_context(codecCtx,codecPara);
        if(codecCtx==NULL){
    
    
            printf("Cannot alloc context.\n");
            break;
        }

        codecCtx->codec_id      = outFmt->video_codec;
        codecCtx->codec_type    = AVMEDIA_TYPE_VIDEO;
        codecCtx->pix_fmt       = AV_PIX_FMT_YUV420P;
        codecCtx->width         = in_w;
        codecCtx->height        = in_h;
        codecCtx->time_base.num = 1;
        codecCtx->time_base.den = 25;
        codecCtx->bit_rate      = 400000;
        codecCtx->gop_size      = 12;

        if (codecCtx->codec_id == AV_CODEC_ID_H264) {
    
    
            codecCtx->qmin      = 10;
            codecCtx->qmax      = 51;
            codecCtx->qcompress = (float)0.6;
        }
        if (codecCtx->codec_id == AV_CODEC_ID_MPEG2VIDEO)
            codecCtx->max_b_frames = 2;
        if (codecCtx->codec_id == AV_CODEC_ID_MPEG1VIDEO)
            codecCtx->mb_decision = 2;
        //[7]!

        //[8]!打开编码器
        if(avcodec_open2(codecCtx,codec,NULL)<0){
    
    
            printf("Open encoder failed.\n");
            break;
        }
        //[8]!

        av_dump_format(fmtCtx,0,outFile,1);//输出 输出文件流信息

        //初始化帧
        picFrame         = av_frame_alloc();
        picFrame->width  = codecCtx->width;
        picFrame->height = codecCtx->height;
        picFrame->format = codecCtx->pix_fmt;
        size            = (size_t)av_image_get_buffer_size(codecCtx->pix_fmt,codecCtx->width,codecCtx->height,1);
        picture_buf     = (uint8_t *)av_malloc(size);
        av_image_fill_arrays(picFrame->data,picFrame->linesize,
                             picture_buf,codecCtx->pix_fmt,
                             codecCtx->width,codecCtx->height,1);

        //[9] --写头文件
        ret = avformat_write_header(fmtCtx, NULL);
        //[9]

        int      y_size = codecCtx->width * codecCtx->height;
        av_new_packet(pkt, (int)(size * 3));

        //[10] --循环编码每一帧
        for (int i = 0; i < frameCnt; i++) {
    
    
            //读入YUV
            if (fread(picture_buf, 1, (unsigned long)(y_size * 3 / 2), in_file) <= 0) {
    
    
                printf("read file fail!\n");
                return -1;
            } else if (feof(in_file))
                break;

            picFrame->data[ 0 ] = picture_buf;                  //亮度Y
            picFrame->data[ 1 ] = picture_buf + y_size;         // U
            picFrame->data[ 2 ] = picture_buf + y_size * 5 / 4; // V
            // AVFrame PTS
            picFrame->pts    = i;

            //编码
            if(avcodec_send_frame(codecCtx,picFrame)>=0){
    
    
                while(avcodec_receive_packet(codecCtx,pkt)>=0){
    
    
                    printf("encoder success!\n");

                    // parpare packet for muxing
                    pkt->stream_index = vStream->index;
                    av_packet_rescale_ts(pkt, codecCtx->time_base, vStream->time_base);
                    pkt->pos = -1;
                    ret     = av_interleaved_write_frame(fmtCtx, pkt);
                    if(ret<0){
    
    
                        printf("error is: %s.\n",av_err2str(ret));
                    }
                    av_packet_unref(pkt);//刷新缓存
                }
            }
        }
        //[10]

        //[11] --Flush encoder
        ret = flush_encoder(fmtCtx,codecCtx, vStream->index);
        if (ret < 0) {
    
    
            printf("flushing encoder failed!\n");
            break;
        }
        //[11]

        //[12] --写文件尾
        av_write_trailer(fmtCtx);
        //[12]
    }while(0);

    //释放内存
    av_packet_free(&pkt);
    avcodec_close(codecCtx);
    av_free(picFrame);
    av_free(picture_buf);

    if(fmtCtx){
    
    
        avio_close(fmtCtx->pb);
        avformat_free_context(fmtCtx);
    }

    fclose(in_file);

    return 0;
}

flush_encode部分不要好像对编码没有影响,但是官方代码有这部分就保留。

结果

测试输出为:

[libx264 @ 0xfd8900] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX FMA3 BMI2 AVX2
[libx264 @ 0xfd8900] profile High, level 1.3
Output #0, h264, to 'result.h264':
    Stream #0:0: Video: h264, none, 352x288, q=2-31, 400 kb/s, 25 tbn
encoder success!
...
encoder success!
Flushing stream #0 encoder
success encoder 1 frame.
...
success encoder 1 frame.
[libx264 @ 0xfd8900] frame I:25    Avg QP:15.23  size: 19296
[libx264 @ 0xfd8900] frame P:76    Avg QP:21.48  size:  1428
[libx264 @ 0xfd8900] frame B:199   Avg QP:24.15  size:   208
[libx264 @ 0xfd8900] consecutive B-frames:  8.7%  0.7% 24.0% 66.7%
[libx264 @ 0xfd8900] mb I  I16..4:  5.9% 67.9% 26.2%
[libx264 @ 0xfd8900] mb P  I16..4:  0.0%  0.2%  0.0%  P16..4: 12.7% 10.8%  7.8%  0.0%  0.0%    skip:68.4%
[libx264 @ 0xfd8900] mb B  I16..4:  0.0%  0.0%  0.0%  B16..8: 14.4%  1.9%  0.5%  direct: 0.8%  skip:82.4%  L0:37.6% L1:46.7% BI:15.7%
[libx264 @ 0xfd8900] final ratefactor: 17.30
[libx264 @ 0xfd8900] 8x8 transform intra:68.0% inter:51.4%
[libx264 @ 0xfd8900] coded y,uvDC,uvAC intra: 95.7% 95.6% 89.7% inter: 4.7% 3.3% 0.6%
[libx264 @ 0xfd8900] i16 v,h,dc,p: 50% 12%  9% 29%
[libx264 @ 0xfd8900] i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 25% 27% 18%  4%  4%  6%  5%  6%  6%
[libx264 @ 0xfd8900] i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 28%  9% 10%  7%  7% 14%  6% 14%  6%
[libx264 @ 0xfd8900] i8c dc,h,v,p: 42% 23% 26% 10%
[libx264 @ 0xfd8900] Weighted P-Frames: Y:0.0% UV:0.0%
[libx264 @ 0xfd8900] ref P L0: 74.8% 10.2%  9.7%  5.2%
[libx264 @ 0xfd8900] ref B L0: 90.0%  8.2%  1.8%
[libx264 @ 0xfd8900] ref B L1: 96.8%  3.2%
[libx264 @ 0xfd8900] kb/s:421.54
按 <RETURN> 来关闭窗口...

编码后h264文件大小为617.5kb。

源视频为:

source

编码结果为:

result

如果需要使用x265编码,只需要调整部分参数就可以了。

完整代码在ffmpeg_Beginner中的12.video_encode_yuv2h264中。

猜你喜欢

转载自blog.csdn.net/qq_26056015/article/details/125478848
今日推荐