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"\n", 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\n");
        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\n");
            return -1;
        }

        if(pkt->flags & AV_PKT_FLAG_KEY)
            printf("Write packet flags:%d pts:%3"PRId64" dts:%3"PRId64" (size:%5d)\n",
               pkt->flags, pkt->pts, pkt->dts, pkt->size);
        if(!pkt->flags)
            printf("Write packet flags:%d pts:%3"PRId64" dts:%3"PRId64" (size:%5d)\n",
               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\n", codec_name);
        exit(1);
    }

    // 2.分配內存
    AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
    if (!codec_ctx) {
        fprintf(stderr, "Could not allocate video codec context\n");
        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\n");
        }
        ret = av_opt_set(codec_ctx->priv_data, "profile", "main", 0); // 默認是high
        if(ret != 0) {
            printf("av_opt_set profile failed\n");
        }
        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\n");
        }
    }

    /* 設置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\n", av_err2str(ret));
        exit(1);
    }
    printf("thread_count: %d, thread_type:%d\n", codec_ctx->thread_count, codec_ctx->thread_type);

    // 5.打開輸入和輸出文件
    FILE *infile = fopen(in_yuv_file, "rb");
    if (!infile) {
        fprintf(stderr, "Could not open %s\n", in_yuv_file);
        exit(1);
    }
    FILE *outfile = fopen(out_h264_file, "wb");
    if (!outfile) {
        fprintf(stderr, "Could not open %s\n", out_h264_file);
        exit(1);
    }

    // 6.分配pkt和frame
    AVPacket *pkt = av_packet_alloc();
    if (!pkt) {
        fprintf(stderr, "Could not allocate video frame\n");
        exit(1);
    }
    AVFrame *frame = av_frame_alloc();
    if (!frame) {
        fprintf(stderr, "Could not allocate video frame\n");
        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\n");
        exit(1);
    }
    // 計算出每一幀的數據 像素格式 * 寬 * 高
    // 1382400
    int frame_bytes = av_image_get_buffer_size(frame->format, frame->width,
                                               frame->height, 1);
    printf("frame_bytes %d\n", frame_bytes);
    uint8_t *yuv_buf = (uint8_t *)malloc(frame_bytes);
    if(!yuv_buf) {
        printf("yuv_buf malloc failed\n");
        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\n");
    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\n");
            break;
        }
        /* 確保該frame可寫, 如果編碼器內部保持了內存參考計數,則需要重新拷貝一個備份
            目的是新寫入的數據和編碼器保存的數據不能產生沖突
        */
        int frame_is_writable = 1;
        if(av_frame_is_writable(frame) == 0) { // 這里只是用來測試
            printf("the frame can't write, buf:%p\n", frame->buf[0]);
            if(frame->buf && frame->buf[0])        // 打印referenc-counted,必須保證傳入的是有效指針
                printf("ref_count1(frame) = %d\n", 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\n", frame->buf[0]);
            if(frame->buf && frame->buf[0])        // 打印referenc-counted,必須保證傳入的是有效指針
                printf("ref_count2(frame) = %d\n", av_buffer_get_ref_count(frame->buf[0]));
        }
        if(ret != 0) {
            printf("av_frame_make_writable failed, ret = %d\n", 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\n",
                   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\n", end_time - begin_time);
        if(ret < 0) {
            printf("encode failed\n");
            break;
        }
    }

    // 9.沖刷編碼器
    encode(codec_ctx, NULL, pkt, outfile);
    all_end_time = get_time();
    printf("all encode time:%lldms\n", 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\n");
    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⽐較⾼

 

 

 

 

 

 

 




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM