最簡單的視頻編碼器:基於libvpx(編碼YUV爲VP8)

=====================================================git

最簡單的視頻編碼器系列文章列表:github

最簡單的視頻編碼器:編譯

最簡單的視頻編碼器:基於libx264(編碼YUV爲H.264)
函數

最簡單的視頻編碼器:基於libx265(編碼YUV爲H.265)
測試

最簡單的視頻編碼器:libvpx(編碼YUV爲VP8)
this

=====================================================編碼

本文記錄一個最簡單的基於libvpx的VP8視頻編碼器。這個例子是從官方的示例代碼中精簡出來的例子。我發現與H.264不一樣,VP8的裸流(即不包含封裝格式的純視頻數據流)是不能播放的。換言之,VP8的裸流必須存放在容器中才能夠播放。官方示例代碼中存儲VP8視頻流的封裝格式是IVF。IVF這種封裝格式不是很常見,相關的資料能夠查詢有關的文檔。spa

此外,這個工程中的libvpx也能夠編碼VP9格式的視頻。可是封裝格式那裏有點問題目前尚未解決,因此暫時沒有包含編碼VP9的代碼。編碼VP9和編碼VP8的函數調用是如出一轍的。
 

流程圖

調用libvpx進行視頻編碼的流程圖以下所示。

流程圖中主要的函數以下所示。
vpx_img_alloc():爲圖像結構體vpx_image_t分配內存。
vpx_codec_enc_config_default():設置參數集結構體vpx_codec_enc_cfg_t的缺省值。
vpx_codec_enc_init():打開編碼器。
vpx_codec_encode():編碼一幀圖像。
vpx_codec_get_cx_data():獲取一幀壓縮編碼數據。
vpx_codec_destroy():關閉編碼器。
 
存儲數據的結構體以下所示。
vpx_image_t:存儲壓縮編碼前的像素數據。
vpx_codec_cx_pkt_t:存儲壓縮編碼後的碼流數據。
 
IVF封裝格式處理的函數以下所示。
write_ivf_file_header():寫IVF封裝格式的文件頭。
write_ivf_frame_header():寫IVF封裝格式中每幀數據的幀頭。
 
此外流程圖中還包括一個「flush_encoder」模塊,該模塊使用的函數和編碼模塊是同樣的。惟一的不一樣在於再也不輸入視頻像素數據。它的做用是輸出編碼器中剩餘的碼流數據。
 

源代碼

/**
 * 最簡單的基於VPX的視頻編碼器
 * Simplest VPX Encoder
 *
 * 雷霄驊 Lei Xiaohua
 * leixiaohua1020@126.com
 * 中國傳媒大學/數字電視技術
 * Communication University of China / Digital TV Technology
 * http://blog.csdn.net/leixiaohua1020
 *
 * 本程序精簡了libvpx中的一個示例代碼。
 * 能夠YUV格式的像素數據編碼爲VPx(VP8/VP9)碼流,是最簡單的
 * 基於libvpx的視頻編碼器
 * 須要注意的是,編碼輸出的封裝格式是IVF
 *
 * This example modified from an example from vpx project.
 * It encode YUV data to VPX(VP8/VP9) bitstream.
 * It's the simplest encoder example based on libvpx.
 */
#include <stdio.h>
#include <stdlib.h>
 
 
#define VPX_CODEC_DISABLE_COMPAT 1
 
#include "vpx/vpx_encoder.h"
#include "vpx/vp8cx.h"
 
#define interface (&vpx_codec_vp8_cx_algo)
 
#define fourcc    0x30385056
 
#define IVF_FILE_HDR_SZ  (32)
#define IVF_FRAME_HDR_SZ (12)
 
static void mem_put_le16(char *mem, unsigned int val) {
    mem[0] = val;
    mem[1] = val>>8;
}
 
static void mem_put_le32(char *mem, unsigned int val) {
    mem[0] = val;
    mem[1] = val>>8;
    mem[2] = val>>16;
    mem[3] = val>>24;
}
 
 
static void write_ivf_file_header(FILE *outfile,
                                  const vpx_codec_enc_cfg_t *cfg,
                                  int frame_cnt) {
    char header[32];
 
    if(cfg->g_pass != VPX_RC_ONE_PASS && cfg->g_pass != VPX_RC_LAST_PASS)
        return;
    header[0] = 'D';
    header[1] = 'K';
    header[2] = 'I';
    header[3] = 'F';
    mem_put_le16(header+4,  0);                   /* version */
    mem_put_le16(header+6,  32);                  /* headersize */
    mem_put_le32(header+8,  fourcc);              /* headersize */
    mem_put_le16(header+12, cfg->g_w);            /* width */
    mem_put_le16(header+14, cfg->g_h);            /* height */
    mem_put_le32(header+16, cfg->g_timebase.den); /* rate */
    mem_put_le32(header+20, cfg->g_timebase.num); /* scale */
    mem_put_le32(header+24, frame_cnt);           /* length */
    mem_put_le32(header+28, 0);                   /* unused */
 
    fwrite(header, 1, 32, outfile);
}
 
 
static void write_ivf_frame_header(FILE *outfile,
                                   const vpx_codec_cx_pkt_t *pkt)
{
    char             header[12];
    vpx_codec_pts_t  pts;
 
    if(pkt->kind != VPX_CODEC_CX_FRAME_PKT)
        return;
 
    pts = pkt->data.frame.pts;
    mem_put_le32(header, pkt->data.frame.sz);
    mem_put_le32(header+4, pts&0xFFFFFFFF);
    mem_put_le32(header+8, pts >> 32);
 
    fwrite(header, 1, 12, outfile);
}
 
int main(int argc, char **argv) {
 
    FILE *infile, *outfile;
    vpx_codec_ctx_t codec;
    vpx_codec_enc_cfg_t cfg;
    int frame_cnt = 0;
    unsigned char file_hdr[IVF_FILE_HDR_SZ];
    unsigned char frame_hdr[IVF_FRAME_HDR_SZ];
    vpx_image_t raw;
    vpx_codec_err_t ret;
    int width,height;
         int y_size;
    int frame_avail;
    int got_data;
    int flags = 0;
 
    width = 640;
    height = 360;
 
         /* Open input file for this encoding pass */
         infile = fopen("../cuc_ieschool_640x360_yuv420p.yuv", "rb");
         outfile = fopen("cuc_ieschool.ivf", "wb");
 
         if(infile==NULL||outfile==NULL){
                   printf("Error open files.\n");
                   return -1;
         }
 
         if(!vpx_img_alloc(&raw, VPX_IMG_FMT_I420, width, height, 1)){
        printf("Fail to allocate image\n");
                   return -1;
         }
 
    printf("Using %s\n",vpx_codec_iface_name(interface));
 
    /* Populate encoder configuration */
    ret = vpx_codec_enc_config_default(interface, &cfg, 0);
    if(ret) {
        printf("Failed to get config: %s\n", vpx_codec_err_to_string(ret));
        return -1;                                                 
    }
 
    /* Update the default configuration with our settings */
    cfg.rc_target_bitrate =800;
    cfg.g_w = width;                                                          
    cfg.g_h = height;                                                        
 
    write_ivf_file_header(outfile, &cfg, 0);
 
    /* Initialize codec */                                               
    if(vpx_codec_enc_init(&codec, interface, &cfg, 0)){
        printf("Failed to initialize encoder\n");
                   return -1;
         }
 
    frame_avail = 1;
    got_data = 0;
 
         y_size=cfg.g_w*cfg.g_h;
 
    while(frame_avail || got_data) {
        vpx_codec_iter_t iter = NULL;
        const vpx_codec_cx_pkt_t *pkt;
                  
                   if(fread(raw.planes[0], 1, y_size*3/2, infile)!=y_size*3/2){
                            frame_avail=0;
                   }
 
                   if(frame_avail){
                            ret=vpx_codec_encode(&codec,&raw,frame_cnt,1,flags,VPX_DL_REALTIME);
                   }else{
                            ret=vpx_codec_encode(&codec,NULL,frame_cnt,1,flags,VPX_DL_REALTIME);
                   }
 
                   if(ret){
            printf("Failed to encode frame\n");
                            return -1;
                   }
        got_data = 0;
        while( (pkt = vpx_codec_get_cx_data(&codec, &iter)) ) {
            got_data = 1;
            switch(pkt->kind) {
            case VPX_CODEC_CX_FRAME_PKT:
                write_ivf_frame_header(outfile, pkt);
                fwrite(pkt->data.frame.buf, 1, pkt->data.frame.sz,outfile);
                break;
            default:
                break;
            }
        }
                   printf("Succeed encode frame: %5d\n",frame_cnt);
        frame_cnt++;
    }
 
    fclose(infile);
 
    vpx_codec_destroy(&codec);
 
    /* Try to rewrite the file header with the actual frame count */
    if(!fseek(outfile, 0, SEEK_SET))
        write_ivf_file_header(outfile, &cfg, frame_cnt-1);
 
    fclose(outfile);
 
    return 0;
}


運行結果

程序的輸入爲一個YUV文件(已經測試過YUV420P格式)。.net


輸出爲IVF封裝格式的VP8碼流文件。code


VP8碼流文件的信息以下所示。視頻



下載


Simplest Encoder
 

項目主頁

SourceForge:https://sourceforge.net/projects/simplestencoder/

Github:https://github.com/leixiaohua1020/simplest_encoder

開源中國:http://git.oschina.net/leixiaohua1020/simplest_encoder


CDSN下載地址:http://download.csdn.net/detail/leixiaohua1020/8284105

 
該解決方案包含了幾個常見的編碼器的使用示例:
simplest_vpx_encoder:最簡單的基於libvpx的視頻編碼器 simplest_x264_encoder:最簡單的基於libx264的視頻編碼器 simplest_x265_encoder:最簡單的基於libx265的視頻編碼器