最简单的视频编码器:libvpx(编码YUV为VP8)

本文记录一个最简单的基于libvpx的VP8视频编码器。这个例子是从官方的示例代码中精简出来的例子。我发现与H.264不同,VP8的裸流(即不包含封装格式的纯视频数据流)是不能播放的。换言之,VP8的裸流必须存放在容器中才可以播放。官方示例代码中存储VP8视频流的封装格式是IVF。IVF这种封装格式不是很常见,相关的资料可以查询有关的文档。
此外,这个工程中的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
 * [email protected]
 * 中国传媒大学/数字电视技术
 * 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格式)。

输出为IVF封装格式的VP8码流文件。

VP8码流文件的信息如下所示。

下载

Simplest Encoder
 
SourceForge项目主页:https://sourceforge.net/projects/simplestencoder/
CDSN下载地址:http://download.csdn.net/detail/leixiaohua1020/8284105
 
该解决方案包含了几个常见的编码器的使用示例:
simplest_vpx_encoder:最简单的基于libvpx的视频编码器
simplest_x264_encoder:最简单的基于libx264的视频编码器
simplest_x265_encoder:最简单的基于libx265的视频编码器

时间: 2024-10-16 06:37:51

最简单的视频编码器:libvpx(编码YUV为VP8)的相关文章

最简单的视频编码器:编译(libx264,libx265,libvpx)

===================================================== 最简单的视频编码器系列文章列表: 最简单的视频编码器:编译 最简单的视频编码器:基于libx264(编码YUV为H.264) 最简单的视频编码器:基于libx265(编码YUV为H.265) 最简单的视频编码器:libvpx(编码YUV为VP8) ===================================================== 近期研究了一下几种常见的视频编码器:

最简单的视频编码器:基于libx264(编码YUV为H.264)

本文记录一个最简单的基于libx264的H.264视频编码器.此前记录的H.264编码器都是基于FFmpeg调用libx264完成编码的,例如:<最简单的基于FFMPEG的视频编码器(YUV编码为H.264)>相比与上文中的编码器,本文记录的编码器属于"轻量级"的编码器.因为它不再包含FFmpeg的代码,直接调用libx264完成编码.因此项目的体积非常小巧.该编码器可以将输入的YUV数据编码为H.264码流文件. 流程图 调用libx264进行视频编码的流程图如下所示.

最简单的视频编码器:基于libx265(编码YUV为H.265)

本文记录一个最简单的基于libx265的H.265(HEVC)视频编码器.此前记录的编码器是通过FFmpeg调用libx265完成编码的,例如: <最简单的基于FFmpeg的视频编码器-更新版(YUV编码为HEVC(H.265))> 相比与上文中的编码器,本文记录的编码器属于"轻量级"的编码器.因为它不再包含FFmpeg的代码,直接调用libx265完成编码.因此项目的体积非常小巧.该编码器可以将输入的YUV数据编码为H.265码流文件. 流程图 调用libx265进行视频

最简单的视频编码器:编译

最近研究了一下几种常见的视频编码器:x264,x265,vpx.本文简单记录一下它们的编译方法. x264,x265,vpx这三个开源的视频编码器可以说是当今"最火"的视频编码器.x264现在占据着H.264视频编码器的半壁江山:x265则是目前实现H.265标准最好的开源视频编码器,并且在未来可能接替x264:而vpx则是Google推出的开源视频编码器,它提出的VP9编码标准的性能也不错.在记录完这篇文章后,计划分别针对x264,x265,vpx写三篇文章记录它们的使用方法. X2

x264源代码简单分析:宏块编码(Encode)部分

本文记录x264的 x264_slice_write()函数中调用的x264_macroblock_encode()的源代码.x264_macroblock_encode()对应着x264中的宏块编码模块.宏块编码模块主要完成了DCT变换和量化两个步骤. 函数调用关系图 宏块编码(Encode)部分的源代码在整个x264中的位置如下图所示. 单击查看更清晰的图片 宏块编码(Encode)部分的函数调用关系如下图所示. 单击查看更清晰的图片 从源代码可以看出,宏块编码模块的x264_macrobl

后端开发实践系列之四——简单可用的CQRS编码实践

本文只讲了一件事情:软件模型中存在读模型和写模型之分,CQRS便为此而生. 20多年前,Bertrand Meyer在他的<Object-Oriented Software Construction>一书中提出了CQS(Command Query Seperation,命令查询分离)的概念,指出: Every method should either be a command that performs an action, or a query that returns data to th

我的开源视音频项目汇总

本文汇总一下自己视音频编解码学习方面的开源项目.这些开源项目大体上可以分成专业领域程序,FFmpeg示例程序,FFmpeg移植程序,多媒体项目示例程序,视音频编解码原理学习工程几个类别.这些类别的详细含义如下所列. 专业领域程序:一些有专业性质的程序. FFmpeg示例程序:FFmpeg的类库的各种示例程序. FFmpeg移植程序:FFmpeg项目中移植到Windows的VC平台下的程序. 多媒体项目示例程序:除了FFmpeg之外其他多媒体项目的示例程序. 视音频编解码学习工程:用于学习视音频编

最简单的基于FFMPEG的视频编码器(YUV编码为H.264)

本文介绍一个最简单的基于FFMPEG的视频编码器.该编码器实现了YUV420P的像素数据编码为H.264的压缩编码数据.编码器代码十分简单,但是每一行代码都很重要,适合好好研究一下.弄清楚了本代码也就基本弄清楚了FFMPEG的编码流程.目前我虽然已经调通了程序,但是还是有些地方没有完全搞明白,需要下一步继续探究然后补充内容. 本程序使用最新版的类库(编译时间为2014.5.6),开发平台为VC2010.所有的配置都已经做好,只需要运行就可以了. 下面直接上代码: /* *最简单的基于FFmpeg

最简单的基于FFmpeg的视频编码器-更新版(YUV编码为HEVC(H.265))

前一阵子做过一个基于FFmpeg的视频编码器的例子:最简单的基于FFMPEG的视频编码器(YUV编码为H.264)在该例子中,可以将YUV像素数据(YUV420P)编码为H.264码流.因为如今FFmpeg已经实现了对libx265的支持,因此对上述编码H.264的例子进行了升级,使之变成编码H.265(HEVC)的例子.比较早的FFmpeg的类库(大约几个月以前的版本,我这里编译时间是2014.05.06)对H.265的编码支持有问题.开始调试的时候,以为是自己的代码有问题,几经修改也没有找到