博客專欄

        EEPW首頁 > 博客 > 征程6E/M快速上手實戰Sample-Codec

        征程6E/M快速上手實戰Sample-Codec

        發布人:地平線開發者 時間:2024-09-19 來源:工程師 發布文章

        01 Codec 模塊簡述


        Codec(Coder-Decoder)是指編****,用于壓縮或解壓縮視頻、圖像、音頻等媒體數據;J6 Soc 中存在兩種硬件編解碼單元,分別是 VPU(Video process unit)和 JPU(Jpeg process unit),可提供 4K@90fps 的視頻編解碼能力和 4K@90fps 的圖像編解碼能力。


        1.1 硬件特性1.1.1 JPU 硬件特性:

        image


        1.1.2 VPU 硬件特性:

        image

        image



        1.2 軟件功能


        MediaCodec 子系統會提供音視頻和圖像的編解碼組件,原始流封裝和視頻錄像等功能。該系統主要會封裝底層 codec 硬件資源和軟件編解碼庫,為上層提供編解碼能力。開發者可以基于提供的編解碼接口實現 H265 和 H264 視頻的編解碼功能,也可以使用 JPEG 編碼功能將攝像頭數據存成 JPEG 圖片,還可以使用視頻錄像功能實現攝像頭數據的錄制。


        1.2.1 整體框架:

        image


        1.2.2 控制接口:

        hb_s32 hb_mm_mc_initialize(media_codec_context_t *context):初始化編碼或****,調用成功后 MediaCodec 進入 MEDIA_CODEC_STATE_INITIALIZED 狀態。


        hb_s32 hb_mm_mc_configure(media_codec_context_t *context):配置編碼或****,調用成功后 MediaCodec 進入 MEDIA_CODEC_STATE_CONFIGURED 狀態。


        hb_s32 hb_mm_mc_start(media_codec_context_t *context, const mc_av_codec_startup_params_t *info):啟動編碼/解碼流程,MediaCodec 將創建編解碼實例、設置序列或解析數據流、注冊 Framebuffer、編碼頭信息等,調用成功后 MediaCodec 進入 MEDIA_CODEC_STATE_STARTED 狀態。


        hb_s32 hb_mm_mc_stop(media_codec_context_t *context):停止編碼/解碼流程,退出所有子線程并釋放相關資源,調用成功后 MediaCodec 回到 MEDIA_CODEC_STATE_INITIALIZED 狀態。


        hb_s32 hb_mm_mc_release(media_codec_context_t *context):釋放 MediaCodec 內部所有資源,用戶需要在調用該函數前調用 hb_mm_mc_stop 來停止編解碼,操作成功后 MediaCodec 進入 MEDIA_CODEC_STATE_UNINITIALIZED 狀態。


        hb_s32 hb_mm_mc_queue_input_buffer(media_codec_context_t *context, media_codec_buffer_t *buffer, hb_s32 timeout):填充需要處理的 buffer 到 MediaCodec 中。


        hb_s32 hb_mm_mc_dequeue_input_buffer(media_codec_context_t *context, media_codec_buffer_t *buffer, hb_s32 timeout):獲取輸入的 buffer。


        hb_s32 hb_mm_mc_queue_output_buffer(media_codec_context_t *context, media_codec_buffer_t *buffer, hb_s32 timeout):返還處理完的 output buffer 到 MediaCodec 中。


        hb_s32 hb_mm_mc_dequeue_output_buffer(media_codec_context_t *context, media_codec_buffer_t *buffer, media_codec_output_buffer_info_t *info, hb_s32 timeout):獲取輸出的 buffer。

        1.2.3 碼率控制模式:

        MediaCodec 支持對 H264/H265 和 MJPEG 協議的碼率控制,分別支持 H264/H265 編碼通道的 CBR、VBR、AVBR、FixQp 和 QpMap 五種碼率控制方式,以及支持 MJPGE 編碼通道的 FixQp 碼率控制方式。


        *1.2.3.1 CBR 說明:*

        CBR 表示恒定碼率,能夠保證整體的編碼碼率穩定。下面是 CBR 模式下各個參數含義:

        image


        *1.2.3.2 VBR 說明:*

        VBR 表示可變碼率,簡單場景分配比較大的 qp,壓縮率小,質量高。復雜場景分配較小 qp,可以保證編碼圖像的質量穩定。下面是 VBR 模式下各個參數含義:

        image


        1.2.3.3 AVBR 說明:

        ABR 表示恒定平均目標碼率,簡單場景分配較低碼率,復雜場景分配足夠碼率,使得有限的碼率能夠在不同場景下合理分配,這類似 VBR。同時一定時間內,平均碼率又接近設置的目標碼率,這樣可以控制輸出文件的大小,這又類似 CBR。可以認為是 CBR 和 VBR 的折中方案,產生碼率和圖像質量相對穩定的碼流。下面是 AVBR 模式下各個參數含義:

        image


        1.2.3.4 FixQp 說明:

        FixQp 表示固定每一個 I 幀、P 幀的 QP 值,對于 I/P 幀可以分別設值。下面是 FixQp 模式下各個參數含義:

        image


        1.2.3.5 QPMAP 說明:

        image


        1.2.4 編碼效果:

        根據當前客戶使用 codec 進行視頻編碼的場景,多將碼率模式設置為 CBR,當編碼的場景較為復雜時,為了保證視頻質量,硬件會自動提高碼率值,導致輸出的視頻較預期更大。因此為了兼顧視頻質量和實際碼率,需要統籌 bit_rate 和 max_qp_I/P 值的設置。下面給出了全 I 幀模式下,不同復雜場景下,碼率設置為 15000kbps 時,不同 max_qp_I 下實際碼率和 qp 的情況(不同場景復雜程度不同,下列數據僅供參考):

        image


        H264/H265 編碼支持 GOP 結構的設置,用戶可從預置的 3 種 GOP 結構種選擇,也可自定義 GOP 結構。

        GOP 結構表可定義一組周期性的 GOP 結構,該 GOP 結構將用于整個編碼過程。單個結構表中的元素如下表所示,其中可以指定該圖像的參考幀,如果 IDR 幀后的其他幀指定的參考幀為 IDR 幀前的數據幀,編碼器內部會自動處理這種情況使其不參考其他幀,用戶無需關心這種情況。用戶在自定義 GOP 結構時需要指明結構表的數量,最多可定義 3 個結構表,結構表的順序需要按照解碼順序排列。下面表示了結構表中各個元素的含義:

        img


        征程6 中一共支持設置九種 GOP 預置結構:

        img



        02 Codec-Sample 使用


        編碼 yuv 圖像, 生成 h264/h265 視頻或 jpg 圖片。


        2.1 encoder** **2.1.1 調用流程

        采用 MediaCodec 的 poll 模式來解耦輸入和輸出,可使編碼幀率性能達到最優。在主線程中灌 YUV 數據:取出一個空的 input buffer,配置 YUV 數據的地址信息(如 phys addr),再 queue input buffer 并通知編碼器處理該幀數據;另一個線程取輸出碼流:通過 select 接收硬件編碼完成通知,取出一個硬件填滿輸出碼流的 output buffer,將編碼結果寫到文件中后歸還 output buffer。

        img

        check_and_init_test:打開輸入文件(yuv),并打開內存管理模塊申請內存緩沖;


        hb_mm_mc_initialize:初始化編碼或****,調用成功后 MediaCodec 進入 MEDIA_CODEC_STATE_INITIALIZED 狀態;


        hb_mm_mc_configure:配置編碼或****,調用成功后 MediaCodec 進入 MEDIA_CODEC_STATE_CONFIGURED 狀態;


        hb_mm_mc_start:啟動編碼/解碼流程,MediaCodec 將創建編解碼實例、設置序列或解析數據流、注冊 Framebuffer、編碼頭信息等,調用成功后 MediaCodec 進入 MEDIA_CODEC_STATE_STARTED 狀態;


        hb_mm_mc_dequeue_input_buffer:獲取輸入的 buffer;


        read_input_frames:從輸入文件(yuv)中讀取視頻幀數據,并將其刷新到 buffer 中;


        hb_mm_mc_queue_input_buffer:填充需要處理的 buffer 到 MediaCodec 中;


        hb_mm_mc_dequeue_output_buffer:獲取輸出的 buffer;


        write_output_streams:outputbuffer 寫入 outFile 中;


        hb_mm_mc_queue_output_buffer:返還處理完的 output buffer 到 MediaCodec 中;


        hb_mm_mc_stop:停止編碼/解碼流程,退出所有子線程并釋放相關資源,調用成功后 MediaCodec 回到 MEDIA_CODEC_STATE_INITIALIZED 狀態;


        hb_mm_mc_release:釋放 MediaCodec 內部所有資源,操作成功后 MediaCodec 進入 MEDIA_CODEC_STATE_UNINITIALIZED 狀態。



        2.1.2 源碼主干:

        #Sample源碼路徑
        /test/samples/platform_samples/source/S83_Sample/S83E04_Module/codec_sample


        Encoder:

        void do_sync_encoding(void *arg)
        {
        hb_s32 ret = 0;
        MediaCodecTestContext *ctx = (MediaCodecTestContext *)arg;
        media_codec_context_t *context = NULL;
        media_codec_buffer_t inputBuffer;
        media_codec_buffer_t outputBuffer;
        media_codec_output_buffer_info_t info;

        ret = check_and_init_test(ctx);
        if (ret != 0) {
            printf("check_and_init_test failed(%d).\n", ret);
            return;
        }

        context = ctx->context;
        ret = hb_mm_mc_initialize(context);
        if (ret != 0) {
            printf("hb_mm_mc_initialize failed(%d).\n", ret);
            return;
        }

        // set_message(ctx);

        ret = hb_mm_mc_configure(context);
        if (ret != 0) {
            printf("hb_mm_mc_configure failed(%d).\n", ret);
            hb_mm_mc_release(context);
            return;
        }

        mc_av_codec_startup_params_t startup_params;
        startup_params.video_enc_startup_params.receive_frame_number = 0;
        ret = hb_mm_mc_start(context, &startup_params);
        if (ret != 0) {
            printf("hb_mm_mc_start failed(%d).\n", ret);
            hb_mm_mc_release(context);
            return;
        }

        do {
            // process input buffers
            memset(&inputBuffer, 0x00, sizeof(media_codec_buffer_t));
            ret = hb_mm_mc_dequeue_input_buffer(context, &inputBuffer, 3000);
            if (ret == 0) {
                ret = read_input_frames(ctx, &inputBuffer);
                if (ret <= 0) {
                    printf("There is no more input data(ret=%d)!\n", ret);
                    inputBuffer.vframe_buf.size = 0;
                    inputBuffer.vframe_buf.frame_end = TRUE;
                }

                ctx->input_num++;
                // ASSERT_EQ(do_encode_params_setting(ctx, &inputBuffer), 0);

                ret = hb_mm_mc_queue_input_buffer(context, &inputBuffer, 100);
                if (ret != 0) {
                    break;
                }
            } else {
                printf("dequeue input buffer fail(%d).\n", ret);
                if (ret != (int32_t)HB_MEDIA_ERR_WAIT_TIMEOUT) {
                    break;
                }
            }

            // process output buffers
            memset(&outputBuffer, 0x00, sizeof(media_codec_buffer_t));
            memset(&info, 0x00, sizeof(media_codec_output_buffer_info_t));
            ret = hb_mm_mc_dequeue_output_buffer(context, &outputBuffer, &info, 3000);
            if (ret == 0) {
                write_output_streams(ctx, &outputBuffer);
                ret = hb_mm_mc_queue_output_buffer(context, &outputBuffer, 100);
                if (outputBuffer.vstream_buf.stream_end) {
                    printf("There is no more output data!\n");
                    break;
                }
                if (ret) {
                    break;
                }
            } else {
                printf("dequeue output buffer fail(ret=0x%x).\n", ret);
                if (ret != (int32_t)HB_MEDIA_ERR_WAIT_TIMEOUT) {
                    break;
                }
            }

        } while(TRUE);

        ret = hb_mm_mc_stop(context);
        if (ret != 0) {
            printf("hb_mm_mc_stop failed(%d).\n", ret);
        }

        ret = hb_mm_mc_release(context);
        if (ret != 0) {
            printf("hb_mm_mc_release failed(%d).\n", ret);
        }

        check_and_release_test(ctx);

        }


        2.2 decoder


        VPU/JPU 從 DDR 中獲得 H265/H264/JPG 輸入源,經過硬件解碼后生成 yuv 圖像

        2.2.1 調用流程

        img


        check_and_init_test:打開輸入文件(h265),并打開內存管理模塊申請內存緩沖;


        hb_mm_mc_initialize:初始化編碼或****,調用成功后 MediaCodec 進入 MEDIA_CODEC_STATE_INITIALIZED 狀態;


        hb_mm_mc_configure:配置編碼或****,調用成功后 MediaCodec 進入 MEDIA_CODEC_STATE_CONFIGURED 狀態;


        hb_mm_mc_start:啟動編碼/解碼流程,MediaCodec 將創建編解碼實例、設置序列或解析數據流、注冊 Framebuffer、編碼頭信息等,調用成功后 MediaCodec 進入 MEDIA_CODEC_STATE_STARTED 狀態;


        hb_mm_mc_dequeue_input_buffer:獲取輸入的 buffer;


        hb_mm_mc_dequeue_output_buffer:獲取輸出的 buffer;


        write_output_streams:outputbuffer 寫入 outFile 中;


        hb_mm_mc_queue_output_buffer:返還處理完的 output buffer 到 MediaCodec 中;


        hb_mm_mc_stop:停止編碼/解碼流程,退出所有子線程并釋放相關資源,調用成功后 MediaCodec 回到 MEDIA_CODEC_STATE_INITIALIZED 狀態;


        hb_mm_mc_release:釋放 MediaCodec 內部所有資源,操作成功后 MediaCodec 進入 MEDIA_CODEC_STATE_UNINITIALIZED 狀態。

        ** **2.2.2 源碼主干:

        void do_sync_decoding(void *arg)
        {
        int ret = 0;
        MediaCodecTestContext *ctx = (MediaCodecTestContext *)arg;
        media_codec_context_t *context = NULL;
        media_codec_buffer_t inputBuffer;
        media_codec_buffer_t outputBuffer;
        media_codec_output_buffer_info_t info;

        ret = check_and_init_test(ctx);
        if (ret != 0) {
            printf("check_and_init_test failed(%d).\n", ret);
            return;
        }   context = ctx->context;

        context = ctx->context;
        ret = hb_mm_mc_initialize(context);
        if (ret != 0) {
            printf("hb_mm_mc_initialize failed(%d).\n", ret);
            return;
        }

        ret = hb_mm_mc_configure(context);
        if (ret != 0) {
            printf("hb_mm_mc_configure failed(%d).\n", ret);
            hb_mm_mc_release(context);
            return;
        }

        mc_av_codec_startup_params_t startup_params;
        startup_params.video_enc_startup_params.receive_frame_number = 0;
        ret = hb_mm_mc_start(context, &startup_params);
        if (ret != 0) {
            printf("hb_mm_mc_start failed(%d).\n", ret);
            hb_mm_mc_release(context);
            return;
        }

        do {
            // process input buffers
            ret = hb_mm_mc_dequeue_input_buffer(context, &inputBuffer, 3000);
            if (ret == 0) {
                ret = read_input_streams(ctx, &inputBuffer);
                if (ret <= 0) {
                    printf("There is no more input data(ret=%d)!\n", ret);
                    inputBuffer.vstream_buf.stream_end = TRUE;
                    inputBuffer.vstream_buf.size = 0;
                    ctx->lastStream = 1;
                }

                ret = hb_mm_mc_queue_input_buffer(context, &inputBuffer, 100);
                if (ret != 0) {
                    break;
                }
            } else {
                if (ret != (int32_t)HB_MEDIA_ERR_WAIT_TIMEOUT) {
                    char info[256];
                    hb_mm_strerror(ret, info, 256);
                    printf("dequeue input buffer fail.(%s)\n", info);
                    break;
                }
            }

            // process output buffers
            memset(&outputBuffer, 0x00, sizeof(media_codec_buffer_t));
            memset(&info, 0x00, sizeof(media_codec_output_buffer_info_t));
            ret = hb_mm_mc_dequeue_output_buffer(context, &outputBuffer, &info, 100);
            if (ret == 0) {
                printf("info.video_frame_info.nalu_type %d\n", info.video_frame_info.nalu_type);
                write_output_frames(ctx, &outputBuffer);

                if (ctx->enable_get_userdata) {
                    mc_user_data_buffer_t userdata = {0};
                    ret = hb_mm_mc_get_user_data(context, &userdata, 0);
                    if (!ret) {
                        printf("Get userdata %d:\n", userdata.size);
                        for (uint32_t i = 0; i < userdata.size; i++) {
                            if (i < 16) {
                                printf("userdata[i]:%x\n", userdata.virt_addr[i]);
                            } else {
                                printf("userdata[i]:%c\n", userdata.virt_addr[i]);
                            }
                        }
                        ret = hb_mm_mc_release_user_data(context, &userdata);
                    } else {
                        ret = 0;
                    }
                }

                ret = hb_mm_mc_queue_output_buffer(context, &outputBuffer, 100);
                if (outputBuffer.vframe_buf.frame_end) {
                    printf("There is no more output data!\n");
                    break;
                }
                if (ret) {
                    break;
                }
            } else {
                char info[256];
                hb_mm_strerror(ret, info, 256);
                printf("dequeue output buffer fail.(%s)\n", info);
                if (ret != (int32_t)HB_MEDIA_ERR_WAIT_TIMEOUT) {
                    break;
                }
            }
        } while (TRUE);

        ret = hb_mm_mc_stop(context);
        if (ret != 0) {
            printf("hb_mm_mc_stop failed(%d).\n", ret);
        }

        ret = hb_mm_mc_release(context);
        if (ret != 0) {
            printf("hb_mm_mc_release failed(%d).\n", ret);
        }

        check_and_release_test(ctx);
        }


        2.3 編譯&運行

        獲取 AppSDK 包后,進入 appuser 執行:

        *其中 hbrootfs-sdk_0.0.1.XXX_all.deb 是地平線自己的庫和頭文件,rootfs-sdk-focal_0.0.1.XXX_all.deb 是系統庫,aarch64-linux-hb-gcc_12.2.0_amd64.deb 是 gcc 12.2.0 工具鏈,目前在 ubuntu22.04 非 docker 環境下運行正常。其它環境不能保證。

        dpkg-deb -x rootfs-sdk*.deb ./sdk
        dpkg-deb -x hbrootfs-sdk*.deb ./sdk
        ##移動sdk庫路徑,本文檔放入/usr/lib中
        sudo mv sdk/ /usr/lib


        進入 toolchain 執行:

        dpkg -x aarch64-linux-hb-gcc_12.2.0_amd64.deb ./arm-gnu-toolchain
        ##移動toolchain庫路徑,本文檔放入/usr/lib中
        sudo mv arm-gnu-toolchain/ /usr/lib
        nano ~/.bashrc
        ##添加系統路徑
        export PATH="/usr/lib/arm-gnu-toolchain/bin:$PATH"
        export LD_LIBRARY_PATH="/usr/lib/arm-gnu-toolchain/lib:$LD_LIBRARY_PATH"
        ##
        source ~/.bashrc


        Sample 代碼路徑:

        #Sample源碼路徑
        /test/samples/platform_samples/source/S83_Sample/S83E04_Module/codec_sample


        運行參數說明:

        img


        復制/src 源碼到新建文件夾 codec 并構建新 Makefile:

        codec
        ├── Makefile
        └── src
        ├── sample.c
        ├── sample_common.c
        ├── sample.h
        ├── sample_vdec.c
        └── sample_venc.c

        Makefile:

        CROSS_COMPILE = aarch64-none-linux-gnu-
        OUTPUT_HBROOTFS_DIR = /usr/lib/sdk

        CXX := ${CROSS_COMPILE}gcc

        INC_DIR := ${OUTPUT_HBROOTFS_DIR}/usr/hobot/include
        INC_DIR += ${OUTPUT_HBROOTFS_DIR}/include
        LIB_DIR := ${OUTPUT_HBROOTFS_DIR}/usr/hobot/lib
        LIB_DIR += ${OUTPUT_HBROOTFS_DIR}/usr/lib/aarch64-linux-gnu
        LIBS += -lpthread -ldl -lhbmem -lalog  -lmultimedia
        LIBS += -lavformat -lavcodec -lavutil -lswresample
        CXXFLAGS := -Wall -O2 $(foreach dir,$(INC_DIR),-I$(dir))
        LDFLAGS := $(addprefix -L, $(LIB_DIR)) $(LIBS)

        SRC_DIR := src
        TARGET := program
        SRCS := $(wildcard $(SRC_DIR)/*.c)

        OBJS := $(SRCS:.c=.o)

        $(TARGET): $(OBJS)
        $(CXX) $(CXXFLAGS) $(LDFLAGS) $^ -o $@
        %.o: %.c
        $(CXX) $(CXXFLAGS) -c $< -o $@
        clean:
        rm -f $(OBJS) $(TARGET)


        執行 make 完成編譯,生成的文件為。/program

        img

        使用 WinScp 將 program 傳輸到單板上。*需要 encode 的 yuv 文件請用戶自行準備,本文檔使用 PYM 生成的 yuv 文件。

        *WinScp 使用方法請參考征程 6E/M 底軟開發 Sample-IPC 2.1.2

        通過 ssh 或串口進入/home/hobot/執行:*w 和 h 需要進行 16 字節對齊,如原始 yuv 不支持則會出現數據丟失

        chmod +x program
        #encode
        #代碼可參考源碼目錄中的codec_sample.sh
        ./program -m 0 -c 3 -w 3840 -h 2160 -p 1 -i 3840x2160_pipe0_pym_ds0_f0_roi_1.yuv -o ./3840x2160.jpg
        ./program -m 0 -c 1 -w 3840 -h 2160 -p 1 -i 3840x2160_pipe0_pym_ds0_f0_roi_1.yuv -o ./3840x2160.h265

        Sample 運行時日志:

        g_samplemode = 0
        g_codecid = 3
        g_width = 3840
        g_height = 2160
        g_pixfmt = 1
        g_pixfmt = 1
        g_inputfilename = 3840x2160_pipe0_pym_ds0_f0_roi_1.yuv
        g_outputfilename = ./3840x2160.jpg
        InputFileName = 3840x2160_pipe0_pym_ds0_f0_roi_1.yuv
        OutputFileName = ./3840x2160.jpg
        Thread use internal buffer mode, 0 rc mode
        Failed to read input file (size=12441600)
        There is no more input data(ret=0)!
        There is no more output data!


        生成的縮放 jpg 文件存放在指定-o 目錄下。

        生成的 Jpeg 效果如下:

        img

        *有關 jpg 工具查看 1080p 圖片時出現綠邊問題:

        img


        問題說明:這是因為當前 ip 進行編碼時按照 16 位對齊進行,假如到最后如果是 8 位對齊而不是 16 位對齊,那么編碼器就會在后面補齊,這部分補齊的數據是隨機產生的,不屬于有效數據;


        #decode
        #使用上述生成的3840x2160.h265為例
        ./program -m 1 -c 1 -w 3840 -h 2160 -p 1 -i 3840x2160.h265 -o ./3840x2160.yuv

        img

        使用 YUView 打開生成的 3840x2160.yuv:

        img

        *注意配置 offset:

        img

        img



        附件:

        1、codec.7z


        *博客內容為網友個人發布,僅代表博主個人觀點,如有侵權請聯系工作人員刪除。



        關鍵詞: 算法 自動駕駛

        相關推薦

        技術專區

        關閉
        主站蜘蛛池模板: 秀山| 贡嘎县| 桐柏县| 九龙城区| 吕梁市| 文昌市| 出国| 丹阳市| 远安县| 布尔津县| 始兴县| 囊谦县| 军事| 丹阳市| 呼玛县| 定陶县| 三明市| 鄱阳县| 邳州市| 张家川| 孟村| 普格县| 德庆县| 大石桥市| 绥中县| 和静县| 大英县| 沅江市| 抚顺县| 永寿县| 莆田市| 新津县| 肇州县| 独山县| 泰和县| 乡城县| 且末县| 军事| 昌江| 突泉县| 望奎县|