zoukankan      html  css  js  c++  java
  • 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⽐较⾼

     



  • 相关阅读:
    poj 2187 Beauty Contest(旋转卡壳)
    poj 2540 Hotter Colder(极角计算半平面交)
    poj 1279 Art Gallery(利用极角计算半平面交)
    poj 3384 Feng Shui(半平面交的联机算法)
    poj 1151 Atlantis(矩形面积并)
    zoj 1659 Mobile Phone Coverage(矩形面积并)
    uva 10213 How Many Pieces of Land (欧拉公式计算多面体)
    uva 190 Circle Through Three Points(三点求外心)
    zoj 1280 Intersecting Lines(两直线交点)
    poj 1041 John's trip(欧拉回路)
  • 原文地址:https://www.cnblogs.com/vczf/p/13605261.html
Copyright © 2011-2022 走看看