音視頻技術應用(16)- 調節x264編碼器, 控制編碼速度和質量


有時候需要根據實際情況對x264編碼器進行實際的調節,以達到編碼速度和視頻質量的平衡,FFmpeg提供了一系列參數方便我們直接去設定:

1. preset 參數

主要用於調節編碼速度和質量的平衡,其可選參數如下:

ultrafast、superfast、veryfast、faster、fast、medium、slow、slower、veryslow、placebo

從左到右代表編碼速度的快慢,越慢代表視頻質量越好,如果要追求好的質量可以選擇靠右的參數,如果要追求較快的速度可以選擇靠左的參數;

2. tune

主要用於調節視頻類型、對視覺效果進行優化,設定此參數,方便在編碼過程中, 根據不同的視頻類型選擇對應的特征算法,做不同的優化處理,以達到最好的效果。其可選參數如下:

  • film: 電影類型。電影中畫面的人物或自然場景顏色比較豐富,光影變化大,根據這樣的場景提供的一套優化方法;
  • animation: 動畫。特指普通動畫,非三維動畫,普通的動畫中會存在顏色塊,或者整片顏色,它的光影變化不大;
  • grain: 需要保留大量的顆粒效果的場景,比如像“我的世界”那種包含大量顆粒效果的視頻類型;
  • stillimage: 靜態圖像編碼時使用;比如視頻相冊這種,只需要一個切換過程,其它圖片都是靜止的;
  • fastdecode: 可以快速解碼的參數;
  • zerolatency: 零延時,用在需要非常低的延遲的情況下,比如電視電話會議的編碼。注意不能設置B幀。

還有一些具體的參數,詳見: libavcodec\libx264.c , 里面定義了具體支持的所有參數類型,以及各種參數的說明和默認值等:

下面使用代碼實際演示一下設置以上兩種參數達到的最終效果:

1. 使用默認的x264編碼器參數編碼視頻:

#include <iostream>
#include <fstream>

using namespace std;

extern "C" { // 指定函數是C語言函數,以C語言的方式去編譯
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
}

// 以預處理指令的方式導入庫
#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avutil.lib")

int main(int argc, char *argv[])
{

    string filename = "400_300_25";
    
    AVCodecID codec_id = AV_CODEC_ID_H264;

    if (argc > 1)
    {
        string codec = argv[1];
        if (codec == "h265" || codec == "hevc")
        {
            codec_id = AV_CODEC_ID_HEVC;
        }
    }

    if (codec_id == AV_CODEC_ID_H264)
    {
        filename += ".h264";
    }
    else if (codec_id == AV_CODEC_ID_HEVC)
    {
        filename += ".h265";
    }

    ofstream ofs;
    ofs.open(filename, ios::binary);

    // 1. 查找編碼器        AV_CODEC_ID_H264(h264) AV_CODEC_ID_HEVC(h265)
    auto codec = avcodec_find_encoder(codec_id);
    if (!codec)
    {
        cerr << "codec not found" << endl;
        return -1;
    }

    // 2. 創建上下文
    auto c = avcodec_alloc_context3(codec);
    if (!c)
    {
        cerr << "avcodec_alloc_context3 failed" << endl;
        return -1;
    }

    // 3. 設定編碼器的上下文參數
    c->width = 400;                                            // 視頻幀的寬度
    c->height = 300;                                        // 視頻幀的高度
    c->time_base = { 1, 25 };                                // 時間基數,即時間戳的顯示單位,可以認為是1秒內顯示25幀
    
    c->pix_fmt = AV_PIX_FMT_YUV420P;                        // 指定源數據的像素格式,跟編碼格式有關,如果編碼格式是h264, 這里就不能指定為RGBA
    c->thread_count = 16;                                    // 編碼線程數,可以通過調用系統接口獲取CPU的核心數量

    // 4. 打開編碼上下文
    int re = avcodec_open2(c, codec, nullptr);
    if (re != 0)
    {
        char buf[1024] = { 0 };
        av_strerror(re, buf, sizeof(buf) - 1);
        cerr << "avcodec_open2 failed" << buf << endl;
        return -1;
    }

    cout << "avcodec_open2 success" << endl;

    // 創建AVFrame空間,用於表示一幀數據
    auto frame = av_frame_alloc();
    frame->width = c->width;
    frame->height = c->height;
    frame->format = c->pix_fmt;

    re = av_frame_get_buffer(frame, 0);
    if (re != 0)
    {
        char buf[1024] = {0};
        av_strerror(re, buf, sizeof(buf) - 1);
        cerr << "av_frame_get_buffer() failed" << endl;
        return -1;
    }

    auto pkt = av_packet_alloc();

    // 創建一個10s的視頻,共250幀
    for (int i = 0; i < 250; i++)
    {
        // 生成一幀圖像 每幀數據都不同
        // Y
        for (int y = 0; y < frame->height; y++)
        {
            for (int x = 0; x < frame->width; x++)
            {
                frame->data[0][y * frame->linesize[0] + x] = x + y + i * 3;
            }
        }

        // UV
        for (int y = 0; y < frame->height / 2; y++)
        {
            for (int x = 0; x < frame->width / 2; x++)
            {
                frame->data[1][y * frame->linesize[1] + x] = 128 + y + i * 2;
                frame->data[2][y * frame->linesize[2] + x] = 64 + x + i * 5;
            }
        }

        // 顯示的時間
        frame->pts = i;

        re = avcodec_send_frame(c, frame);
        if (re != 0)
        {
            cerr << "avcodec_send_frame() failed" << endl;
            break;
        }

        // >=0 表示有數據返回,編碼一個AVFrame 可能會返回多個AVPacket
        while (re >= 0)
        {
            // 接收壓縮幀 一般前幾次調用會返回空的 (緩沖,立即返回,編碼未完成)
            // 編碼是在獨立的線程中完成的
            // 每次調用會重新分配pkt的數據空間,並改變內部的指針指向
            re = avcodec_receive_packet(c, pkt);
            if (re == AVERROR(EAGAIN) || re == AVERROR_EOF)
                break;

            if (re < 0)
            {
                char buf[1024] = { 0 };
                av_strerror(re, buf, sizeof(buf) - 1);
                cerr << "avcodec_receive_packet() failed" << endl;
                return -1;
            }

            cout << pkt->size << " " << flush;
            
            // 寫入編碼后的壓縮數據
            ofs.write((char *)pkt->data, pkt->size);

            // 注意 一定要調用 av_packet_unref() 刪除已申請的數據空間,若不刪除,下次還會重新申請,這樣會造成內存泄漏
            av_packet_unref(pkt);
        }
    }

    // 釋放AVPacket
    av_packet_free(&pkt);

    // 釋放AVFrame
    av_frame_free(&frame);

    // 釋放上下文
    avcodec_free_context(&c);
}

使用Bitrate Viewer 打開生成的h264文件:

可以看到,碼率基本維持在 163kbps, 下面指定編碼器參數再來測試下,

修改上述code,設定preset 和 tune, 添加紅色部分的代碼:

#include <iostream>
#include <fstream>

using namespace std;

extern "C" { // 指定函數是C語言函數,以C語言的方式去編譯
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
}

// 以預處理指令的方式導入庫
#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avutil.lib")

int main(int argc, char* argv[])
{

    string filename = "400_300_25_preset";

    AVCodecID codec_id = AV_CODEC_ID_H264;

    if (argc > 1)
    {
        string codec = argv[1];
        if (codec == "h265" || codec == "hevc")
        {
            codec_id = AV_CODEC_ID_HEVC;
        }
    }

    if (codec_id == AV_CODEC_ID_H264)
    {
        filename += ".h264";
    }
    else if (codec_id == AV_CODEC_ID_HEVC)
    {
        filename += ".h265";
    }

    ofstream ofs;
    ofs.open(filename, ios::binary);

    // 1. 查找編碼器        AV_CODEC_ID_H264(h264) AV_CODEC_ID_HEVC(h265)
    auto codec = avcodec_find_encoder(codec_id);
    if (!codec)
    {
        cerr << "codec not found" << endl;
        return -1;
    }

    // 2. 創建上下文
    auto c = avcodec_alloc_context3(codec);
    if (!c)
    {
        cerr << "avcodec_alloc_context3 failed" << endl;
        return -1;
    }

    // 3. 設定編碼器的上下文參數
    c->width = 400;                                            // 視頻幀的寬度
    c->height = 300;                                        // 視頻幀的高度
    c->time_base = { 1, 25 };                                // 時間基數,即時間戳的顯示單位,可以認為是1秒內顯示25幀

    c->pix_fmt = AV_PIX_FMT_YUV420P;                        // 指定源數據的像素格式,跟編碼格式有關,如果編碼格式是h264, 這里就不能指定為RGBA
    c->thread_count = 16;                                    // 編碼線程數,可以通過調用系統接口獲取CPU的核心數量

    // 設置編碼器參數
    c->max_b_frames = 0;                                    // 將B幀個數設置為0

    // 調節編碼速度(這里選擇的是最快)
    int re = av_opt_set(c->priv_data, "preset", "ultrafast", 0);
    if (re != 0)
    {
        cout << "preset failed" << endl;
        return -1;
    }

    // 調整視頻的類型 這里選擇零延時
    re = av_opt_set(c->priv_data, "tune", "zerolatency", 0);
    if (re != 0)
    {
        cout << "tune failed" << endl;
        return -1;
    }

    // 4. 打開編碼上下文
    re = avcodec_open2(c, codec, nullptr);
    if (re != 0)
    {
        char buf[1024] = { 0 };
        av_strerror(re, buf, sizeof(buf) - 1);
        cerr << "avcodec_open2 failed" << buf << endl;
        return -1;
    }

    cout << "avcodec_open2 success" << endl;

    // 創建AVFrame空間,用於表示一幀數據
    auto frame = av_frame_alloc();
    frame->width = c->width;
    frame->height = c->height;
    frame->format = c->pix_fmt;

    re = av_frame_get_buffer(frame, 0);
    if (re != 0)
    {
        char buf[1024] = { 0 };
        av_strerror(re, buf, sizeof(buf) - 1);
        cerr << "av_frame_get_buffer() failed" << endl;
        return -1;
    }

    auto pkt = av_packet_alloc();

    // 創建一個10s的視頻,共250幀
    for (int i = 0; i < 250; i++)
    {
        // 生成一幀圖像 每幀數據都不同
        // Y
        for (int y = 0; y < frame->height; y++)
        {
            for (int x = 0; x < frame->width; x++)
            {
                frame->data[0][y * frame->linesize[0] + x] = x + y + i * 3;
            }
        }

        // UV
        for (int y = 0; y < frame->height / 2; y++)
        {
            for (int x = 0; x < frame->width / 2; x++)
            {
                frame->data[1][y * frame->linesize[1] + x] = 128 + y + i * 2;
                frame->data[2][y * frame->linesize[2] + x] = 64 + x + i * 5;
            }
        }

        // 顯示的時間
        frame->pts = i;

        re = avcodec_send_frame(c, frame);
        if (re != 0)
        {
            cerr << "avcodec_send_frame() failed" << endl;
            break;
        }

        // >=0 表示有數據返回,編碼一個AVFrame 可能會返回多個AVPacket
        while (re >= 0)
        {
            // 接收壓縮幀 一般前幾次調用會返回空的 (緩沖,立即返回,編碼未完成)
            // 編碼是在獨立的線程中完成的
            // 每次調用會重新分配pkt的數據空間,並改變內部的指針指向
            re = avcodec_receive_packet(c, pkt);
            if (re == AVERROR(EAGAIN) || re == AVERROR_EOF)
                break;

            if (re < 0)
            {
                char buf[1024] = { 0 };
                av_strerror(re, buf, sizeof(buf) - 1);
                cerr << "avcodec_receive_packet() failed" << endl;
                return -1;
            }

            cout << pkt->size << " " << flush;

            // 寫入編碼后的壓縮數據
            ofs.write((char*)pkt->data, pkt->size);

            // 注意 一定要調用 av_packet_unref() 刪除已申請的數據空間,若不刪除,下次還會重新申請,這樣會造成內存泄漏
            av_packet_unref(pkt);
        }
    }

    // 釋放AVPacket
    av_packet_free(&pkt);

    // 釋放AVFrame
    av_frame_free(&frame);

    // 釋放上下文
    avcodec_free_context(&c);
}

再次查看新生成的h264文件:

可以看到,碼率還是有很明顯的提升的。

 


免責聲明!

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



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