• FFmpeg简单使用:视频编码 ---- YUV转H264


     =====================================================

    FFmpeg简单使用:解封装 ---- 基本流程

    FFmpeg简单使用:解封装 ---- 提取aac

    FFmpeg简单使用:音频解码 ---- 提取pcm

    FFmpeg简单使用:视频解码 ---- 提取yuv

    FFmpeg简单使用:音频编码 ---- pcm转aac

    FFmpeg简单使用:视频编码 ---- YUV转H264

    FFmpeg简单使用:过滤器 ---- 视频过滤

    FFmpeg简单使用:过滤器 ---- 视频过滤2

    FFmpeg简单使用:过滤器 ---- h264_mp4toannexb

    FFmpeg简单使用:解封装h264 ---- 提取SPS PPS

    =====================================================

    基本流程

    从本地读取YUV数据编码为h264格式的数据,然后再存⼊到本地,编码后的数据有带startcode。

    与FFmpeg 示例⾳频编码的流程基本⼀致。

    函数说明:
    avcodec_find_encoder_by_name:根据指定的编码器名称查找注册的编码器。

    avcodec_alloc_context3:为AVCodecContext分配内存。

    avcodec_open2:打开编解码器。

    avcodec_send_frame:将AVFrame⾮压缩数据给编码器。

    avcodec_receive_packet:获取到编码后的AVPacket数据。

    av_frame_get_buffer: 为⾳频或视频数据分配新的buffer。在调⽤这个函数之前,必须在AVFame上设置好以下属性:format(视频为像素格式,⾳频为样本格式)、nb_samples(样本个数,针对⾳频)、channel_layout(通道类型,针对⾳频)、width/height(宽⾼,针对视频)。

    av_frame_make_writable:确保AVFrame是可写的,尽可能避免数据的复制。如果AVFrame不是是可写的,将分配新的buffer和复制数据。

    av_image_fill_arrays: 存储⼀帧像素数据存储到AVFrame对应的data buffer。

    编码:

    /**
    * @projectName   08-02-encode_video
    * @brief         视频编码,从本地读取YUV数据进行H264编码
    * @author        Liao Qingfu
    * @date          2020-04-16
    */
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #include <libavcodec/avcodec.h>
    #include <libavutil/time.h>
    #include <libavutil/opt.h>
    #include <libavutil/imgutils.h>
    
    int64_t get_time()
    {
        return av_gettime_relative() / 1000;  // 换算成毫秒
    }
    static int 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"
    ", frame->pts);
        /* 通过查阅代码,使用x264进行编码时,具体缓存帧是在x264源码进行,
         * 不会增加avframe对应buffer的reference*/
        ret = avcodec_send_frame(enc_ctx, frame);
        if (ret < 0)
        {
            fprintf(stderr, "Error sending a frame for encoding
    ");
            return -1;
        }
    
        while (ret >= 0)
        {
            ret = avcodec_receive_packet(enc_ctx, pkt);
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                return 0;
            } else if (ret < 0) {
                fprintf(stderr, "Error encoding audio frame
    ");
                return -1;
            }
    
            if(pkt->flags & AV_PKT_FLAG_KEY)
                printf("Write packet flags:%d pts:%3"PRId64" dts:%3"PRId64" (size:%5d)
    ",
                   pkt->flags, pkt->pts, pkt->dts, pkt->size);
            if(!pkt->flags)
                printf("Write packet flags:%d pts:%3"PRId64" dts:%3"PRId64" (size:%5d)
    ",
                   pkt->flags, pkt->pts, pkt->dts, pkt->size);
            fwrite(pkt->data, 1, pkt->size, outfile);
        }
        return 0;
    }
    /**
     * @brief 提取测试文件:ffmpeg -i test_1280x720.flv -t 5 -r 25 -pix_fmt yuv420p yuv420p_1280x720.yuv
     *           参数输入: yuv420p_1280x720.yuv yuv420p_1280x720.h264 libx264
     * @param argc
     * @param argv
     * @return
     */
    int main(int argc, char **argv)
    {
        int ret = 0;
        const char *in_yuv_file = "yuv420p_1280x720.yuv";      // 输入YUV文件
        const char *out_h264_file = "yuv420p_1280x720.h264";
        const char *codec_name = "libx264";
    
        // 1.查找编码器
        const AVCodec *codec = avcodec_find_encoder_by_name(codec_name);
        if (!codec) {
            fprintf(stderr, "Codec '%s' not found
    ", codec_name);
            exit(1);
        }
    
        // 2.分配内存
        AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
        if (!codec_ctx) {
            fprintf(stderr, "Could not allocate video codec context
    ");
            exit(1);
        }
    
        // 3.设置编码参数
        /* 设置分辨率*/
        codec_ctx->width = 1280;
        codec_ctx->height = 720;
        /* 设置time base */
        codec_ctx->time_base = (AVRational){1, 25};
        codec_ctx->framerate = (AVRational){25, 1};
        /* 设置I帧间隔
         * 如果frame->pict_type设置为AV_PICTURE_TYPE_I, 则忽略gop_size的设置,一直当做I帧进行编码
         */
        codec_ctx->gop_size = 25;   // I帧间隔
        codec_ctx->max_b_frames = 2; // 如果不想包含B帧则设置为0
        codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
        //
        if (codec->id == AV_CODEC_ID_H264) {
            // 相关的参数可以参考libx264.c的 AVOption options
            ret = av_opt_set(codec_ctx->priv_data, "preset", "medium", 0);
            if(ret != 0) {
                printf("av_opt_set preset failed
    ");
            }
            ret = av_opt_set(codec_ctx->priv_data, "profile", "main", 0); // 默认是high
            if(ret != 0) {
                printf("av_opt_set profile failed
    ");
            }
            ret = av_opt_set(codec_ctx->priv_data, "tune","zerolatency",0); // 直播是才使用该设置
    //        ret = av_opt_set(codec_ctx->priv_data, "tune","film",0); //  画质film
            if(ret != 0) {
                printf("av_opt_set tune failed
    ");
            }
        }
    
        /* 设置bitrate */
        codec_ctx->bit_rate = 3000000;
    //    codec_ctx->rc_max_rate = 3000000;
    //    codec_ctx->rc_min_rate = 3000000;
    //    codec_ctx->rc_buffer_size = 2000000;
    //    codec_ctx->thread_count = 4;  // 开了多线程后也会导致帧输出延迟, 需要缓存thread_count帧后再编程。
    //    codec_ctx->thread_type = FF_THREAD_FRAME; // 并 设置为FF_THREAD_FRAME
    
        //对于H264 AV_CODEC_FLAG_GLOBAL_HEADER  设置则只包含I帧,此时sps pps需要从codec_ctx->extradata读取 不设置则每个I帧都带 sps pps sei
        //codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; // 存本地文件时不要去设置
    
        // 4.将codec_ctx和codec进行绑定
        ret = avcodec_open2(codec_ctx, codec, NULL);
        if (ret < 0) {
            fprintf(stderr, "Could not open codec: %s
    ", av_err2str(ret));
            exit(1);
        }
        printf("thread_count: %d, thread_type:%d
    ", codec_ctx->thread_count, codec_ctx->thread_type);
    
        // 5.打开输入和输出文件
        FILE *infile = fopen(in_yuv_file, "rb");
        if (!infile) {
            fprintf(stderr, "Could not open %s
    ", in_yuv_file);
            exit(1);
        }
        FILE *outfile = fopen(out_h264_file, "wb");
        if (!outfile) {
            fprintf(stderr, "Could not open %s
    ", out_h264_file);
            exit(1);
        }
    
        // 6.分配pkt和frame
        AVPacket *pkt = av_packet_alloc();
        if (!pkt) {
            fprintf(stderr, "Could not allocate video frame
    ");
            exit(1);
        }
        AVFrame *frame = av_frame_alloc();
        if (!frame) {
            fprintf(stderr, "Could not allocate video frame
    ");
            exit(1);
        }
    
        // 7.为frame分配buffer
        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) {
            fprintf(stderr, "Could not allocate the video frame data
    ");
            exit(1);
        }
        // 计算出每一帧的数据 像素格式 * 宽 * 高
        // 1382400
        int frame_bytes = av_image_get_buffer_size(frame->format, frame->width,
                                                   frame->height, 1);
        printf("frame_bytes %d
    ", frame_bytes);
        uint8_t *yuv_buf = (uint8_t *)malloc(frame_bytes);
        if(!yuv_buf) {
            printf("yuv_buf malloc failed
    ");
            return 1;
        }
        int64_t begin_time = get_time();
        int64_t end_time = begin_time;
        int64_t all_begin_time = get_time();
        int64_t all_end_time = all_begin_time;
        int64_t pts = 0;
    
        // 8.循环读取数据
        printf("start enode
    ");
        for (;;) {
            memset(yuv_buf, 0, frame_bytes);
            size_t read_bytes = fread(yuv_buf, 1, frame_bytes, infile);
            if(read_bytes <= 0) {
                printf("read file finish
    ");
                break;
            }
            /* 确保该frame可写, 如果编码器内部保持了内存参考计数,则需要重新拷贝一个备份
                目的是新写入的数据和编码器保存的数据不能产生冲突
            */
            int frame_is_writable = 1;
            if(av_frame_is_writable(frame) == 0) { // 这里只是用来测试
                printf("the frame can't write, buf:%p
    ", frame->buf[0]);
                if(frame->buf && frame->buf[0])        // 打印referenc-counted,必须保证传入的是有效指针
                    printf("ref_count1(frame) = %d
    ", av_buffer_get_ref_count(frame->buf[0]));
                frame_is_writable = 0;
            }
            ret = av_frame_make_writable(frame);
            if(frame_is_writable == 0) {  // 这里只是用来测试
                printf("av_frame_make_writable, buf:%p
    ", frame->buf[0]);
                if(frame->buf && frame->buf[0])        // 打印referenc-counted,必须保证传入的是有效指针
                    printf("ref_count2(frame) = %d
    ", av_buffer_get_ref_count(frame->buf[0]));
            }
            if(ret != 0) {
                printf("av_frame_make_writable failed, ret = %d
    ", ret);
                break;
            }
            int need_size = av_image_fill_arrays(frame->data, frame->linesize, yuv_buf,
                                                 frame->format,
                                                 frame->width, frame->height, 1);
            if(need_size != frame_bytes) {
                printf("av_image_fill_arrays failed, need_size:%d, frame_bytes:%d
    ",
                       need_size, frame_bytes);
                break;
            }
             pts += 40;
            // 设置pts
            frame->pts = pts;       // 使用采样率作为pts的单位,具体换算成秒 pts*1/采样率
            begin_time = get_time();
            ret = encode(codec_ctx, frame, pkt, outfile);
            end_time = get_time();
            printf("encode time:%lldms
    ", end_time - begin_time);
            if(ret < 0) {
                printf("encode failed
    ");
                break;
            }
        }
    
        // 9.冲刷编码器
        encode(codec_ctx, NULL, pkt, outfile);
        all_end_time = get_time();
        printf("all encode time:%lldms
    ", all_end_time - all_begin_time);
    
        // 10.结束
        fclose(infile);
        fclose(outfile);
    
        // 释放内存
        if(yuv_buf) {
            free(yuv_buf);
        }
    
        av_frame_free(&frame);
        av_packet_free(&pkt);
        avcodec_free_context(&codec_ctx);
    
        printf("main finish, please enter Enter and exit
    ");
        getchar();
        return 0;
    }
    preset设置
      预设是⼀系列参数的集合,这个集合能够在编码速度和压缩率之间做出⼀个权衡。⼀个编码速度稍慢的预设会提供更⾼的压缩效率(压缩效率是以⽂件⼤⼩来衡量的)。
    这就是说,假如你想得到⼀个指定⼤⼩的⽂件或者采⽤恒定⽐特率编码模式,你可以采⽤⼀个较慢的预设来获得更好的质量。同样的,对于恒定质量编码模式,你可以
    通过选择⼀个较慢的预设轻松地节省⽐特率。如果你很有耐⼼,通常的建议是使⽤最慢的预设。⽬前所有的预设按照编码速度降序排列为:

    常用设置:
    • ultrafast
    • superfast
    • veryfast
    • faster
    • fast
    • medium – default preset
    • slow
    • slower
    • veryslow

    设置为ultrafa

    设置为slower,明显编码时间慢了很多,码率会低一点

     tune设置

    tune是x264中重要性仅次于preset的选项,它是视觉优化的参数,tune可以理解为视频偏好(或者视频类型),tune不是⼀个单⼀的参数,⽽是由⼀组参数构成-tune来改变参数设置。当前的 tune包括:
    film:电影类型,对视频的质量⾮常严格时使⽤该选项
    animation:动画⽚,压缩的视频是动画⽚时使⽤该选项
    grain:颗粒物很重,该选项适⽤于颗粒感很重的视频
    stillimage:静态图像,该选项主要⽤于静⽌画⾯⽐较多的视频
    psnr:提⾼psnr,该选项编码出来的视频psnr⽐较⾼

     



  • 相关阅读:
    Ubuntu中安装mysql(一)
    磁盘格式化分区挂载
    mysql主从&主主部署记录
    python基础-编码环境安装与基本语法
    测试报告应包含的内容信息
    HTTP协议数据包简单总结
    支付功能测试点
    Android--monkey测试命令
    Fiddler 抓包学习_下载安装配置
    chrome F12_Network 开发者工具详解
  • 原文地址:https://www.cnblogs.com/vczf/p/13605261.html
Copyright © 2020-2023  润新知