JoJo的个人博客

记录精彩的程序人生

目录
音视频系统入门9之实战视频编码
/    

音视频系统入门9之实战视频编码

主要内容

  • SPS/PPS/Slice Header
  • 常见分析工具
  • ffmpeg视频编码

H264中SPS的profile和level

SPS中的参数Profile和Level

  • H264 Profile

    对视频压缩特性的描述,Profile越高,就说明采用了越高级的压缩特性

  • H264 Level

    Level是对视频的描述,Level越高,视频的码率、分辨率、fps越高

SPS其它重要参数

帧相关

  • 帧数 log2_max_frame_num_minus4
  • 参考帧数 max_num_ref_frames
  • 显示帧序号 pic_order_cnt_type

帧率相关

Framerate = (float)(sps->vui.vui_time_scale)/(float)(sps->vui.vui_num_units_in_tick)/2;

H264 PPS与Slice-Header

Slice Header

  • 帧类型
  • GOP中解码帧序号
  • 预测权重
  • 滤波

H264分析工具

代码

基本步骤

  • 打开编码器
  • 转换NV12到YUV420P
  • 准备编码数据AVFrame
  • H264编码

实战打开视频编码器

static void open_encoder(int width, int height, AVCodecContext **enc_ctx) {
    int ret = 0;
    AVCodec *codec = NULL;
    codec = avcodec_find_encoder_by_name("libx264");
    if (!codec) {
        printf("Codec libx264 not found\n");
        exit(1);
    }
    *enc_ctx = avcodec_alloc_context3(codec);
    if (!enc_ctx) {
        printf("Could not allocate video context!\n");
        exit(1);
    }
    //SPS/PPS
    (*enc_ctx)->profile = FF_PROFILE_H264_HIGH_444;
    (*enc_ctx)->level = 50//表示LEVEL是5.0
   
    //设置分辨率
    (*enc_ctx)->width = width;    //640
    (*enc_ctx)->height = height;  // 480
    
    //GOP
    (*enc_ctx)->gop_size = 250;    //最大的GOP的值
    (*enc_ctx)->keyint_min = 25;   //option  最小的I帧的间隔
    
    //设置B帧数量  为了减少码流可以设置b帧
    (*enc_ctx)->max_b_frames = 3;  //option
    (*enc_ctx)->has_b_frames = 1;  //option
    
    //参考帧的数量 参考帧越大,处理的越慢,还原性会越好
    (*enc_ctx)->refs = 3;   //option
    
    //设置输入的YUV格式
    (*enc_ctx)->pix_fmt = AV_PIX_FMT_YUV420P;
    
    //设置码率
    (*enc_ctx)->bit_rate = 600000;    //600kbps
    
    //设置帧率
    (*enc_ctx)->time_base = (AVRational){125};   //帧与帧之间的间隔是time_base
    (*enc_ctx)->framerate = (AVRational){251};   //帧率,每秒25帧
    
    ret = avcodec_open2((*enc_ctx), codec, NULL);
    if (ret < 0) {
        printf("Could not open codec: %s!\n", av_err2str(ret));
        exit(1);
    }
}

AVCodecContext *enc_ctx = NULL;
//打开编码器
open_encoder(V_WIDTH, V_HEIGHT, &enc_ctx);

准备编码器输入输出数据

static AVFrame* create_frame(int width, int height) {
    int ret = 0;
    AVFrame* frame = NULL;
    frame = av_frame_alloc();
    if (frame) {
        printf("Error, No memory!\n");
        goto __ERROR;
    }

    //设置参数
    frame->width = width;
    frame->height = height;
    frame->format = AV_PIX_FMT_YUV420P;
    
    //alloc inner memory
    ret = av_frame_get_buffer(frame, 32);  //按32位对齐
    if(ret < 0) {
        printf("Error, Failed to alloc buffer for frame!\n");
        goto __ERROR;
    }
    return frame;
    
__ERROR:
    if (frame) {
        av_frame_free(&frame);
    }
    return NULL;
}
 //创建AVFrame
AVFrame* frame = create_frame(V_WIDTH, V_HEIGHT);

//创建编码后输出的Packet
AVPacket * newpkt = av_packet_alloc();
if (!newpkt) {
    printf("Error, Failed to alloc avpacket!\n");
    goto __ERROR;
}

NV12转YUV420P

memcpy(frame->data[0], pkt.data, 307200);  //copy Y data  640*480 = 307200
//307200之后,是UV
for (int i=0; i < 307200/4; i++) {
    frame->data[1][i] = pkt.data[307200+i*2];
    frame->data[2][i] = pkt.data[307201+i*2];
}

//输出yuv文件
char *youvout = "/Users/tianyang/Desktop/av_base/video.yuv";
FILE *yuvoutFile = fopen(youvout, "wb+");
fwrite(frame->data[0], 1307200, yuvoutFile);
fwrite(frame->data[1], 1307200/4, yuvoutFile);
fwrite(frame->data[2], 1307200/4, yuvoutFile);

编码

static void encode(AVCodecContext *enc_ctx,
                   AVFrame *frame,
                   AVPacket *newpkt,
                   FILE *outfile)
 
{
    int ret = 0;
    
    if (frame) {
        printf("send frame to encoder, pts=%lld", frame->pts);
    }
    
    //送原始数据给编码器进行编码
    ret = avcodec_send_frame(enc_ctx, frame);
    if (ret < 0) {
        printf("Error, Failed to send a frame for encoding\n");
        exit(1);
    }
    
    //从编码器获取编码好的数据
    while (ret >= 0) {
        ret = avcodec_receive_packet(enc_ctx, newpkt);
        
        //如果编码器数据不足时会返回 EAGAIN,或者到数据尾时会返回 AVERROR_EOF
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            return;
        } else if(ret < 0) {
            printf("Error, Failed to encode!\n");
            exit(1);
        }
        
        fwrite(newpkt->data, 1, newpkt->size, outfile);
        av_packet_unref(newpkt);
    }
}

//设置连续pts
frame->pts = base++;
encode(enc_ctx, frame, newpkt, outFile);
encode(enc_ctx, NULL, newpkt, outFile);
播放录制文件
ffplay -s 640x480 video.yuv
播放编码后文件
ffplay video.h264

如果报错没有找到libx264库

  1. 安装fdk-aacbrew install x264

  2. 重新在ffmpeg源码目录下执行./configure 并增加--enable-libx264参数

    ./configure --prefix=/usr/local/ffmpeg --enable-debug=3 --enable-shared --disable-static --enable-sdl2 --enable-libfdk-aac --enable-libopus --enable-libx264 --enable-gpl --enable-nonfree
  3. make && make install

x264参数详解

x264参数分类

  • 预设值
  • 帧相关参数
  • 码流的控制
  • 编码分析
  • 输出

预设值

  • preset fast/slow 编码速度
  • tune 编码质量

帧相关

  • keyint(gop_size)/min-keyint
  • scenecut
  • Frames
  • ref
  • no-deblock/deblock
  • no-cabac

流控

  • Qp 关注量化器,比crf码流大且与bitrate/crf互斥
  • Bitrate,关注码流,无法控制质量
  • Crf,关注质量,默认是23,数越低越好
  • Qmin 默认为10
  • Qmax 默认为51
  • Qpstep 两帧之间量化器的最大变化,默认为4

编码分析

  • Partitions p8x8, b8x8, i8x8, i4x4
  • Me 运动评估算法,钻石、六边型....

输出

  • SAR 设置输出的宽高比
  • fps 帧率
  • level

示例

ffmpeg -vsync 1 - async 1 -i xxx.flv          //音频视频同步
       -b:v 200k -maxrate 250k -bufsize 400k  //视频码流200k,最大250k,bufsize 400k
       -pix_fmt                               //原始数据个数
       -vcodec libx264 -coder 1               //-vcodec编码器,-coder 1 代表使用cabac
       -refs 3 -bf 5                          //参考帧数量3,B帧最大数量5
       -flags +loop -deblock -1:-1            //使用去块化的滤波器
       -partitions i4x4+i8x8+p8x8+b8x8        //宏块分析
       -me_method umh                         //运动估算算法 umh 不均衡的六边形算法
       -g 60 -keyint_min 30                   //-g gopsize -keyint_min 最小I帧的间隔
       -qmin 0 -qmax 69 -qdiff 2              //量化器 最小最大量化值 两帧之间量化差别
       out.mp4

参考资料

http://www.chaneru.com/Roku/HLS/X264_Settings.htm

Https://sites.google.com/site/linuxencoding/x264-ffmpeg-mapping

代码仓

FFmpegProject


标题:音视频系统入门9之实战视频编码
作者:SunnySky
地址:https://www.tianyang.pub/articles/2020/11/17/1605611404797.html

评论