cedarX_venc.c 12.7 KB
/*

    基于aodzip移植的cedarx写的编码文件
    拥有 初始化摄像头 释放摄像头 获取拍摄内容3个接口

*/
#include "cedarX_venc.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "../CedarX_include/vencoder.h"
#include "../CedarX_include/memoryAdapter.h"

#include "JZsdkLib.h"
#include "version_choose.h"

#if ALLWINNER_CEDAR == VERSION_SWITCH_ON


int g_venc_RecordWidth = 0;
int g_venc_RecordHeight = 0;
int g_venc_RecordCharm = 0;

#define RECORD_FRAME 25
#define RECORD_WIDTH (g_venc_RecordWidth)
#define RECORD_HEIGHT (g_venc_RecordHeight)

/*
    编码参数初始化
*/

//设置 H264 的参数
static VencH264Param h264Param;

//编码器设置
static VencBaseConfig VencodeBaseConfig;

//编码器
static VideoEncoder *CedarxVideoEncoder;

//编码的头数据
VencHeaderData sps_pps_data;

//编码器输入队列
VencInputBuffer inputBuffer;
//编码器输出序列
VencOutputBuffer outputBuffer;

//帧数计算
int g_charm_sps;
int g_Venc_charm;

/**********************************************************/  
/***********           H264参数设置 ***********************/
/**********************************************************/
static int cedarX_venc_H264param_init()
{

    //CABAC: 基于上下文自适应的二进制算术编码(Context-based Adaptive Binary Arithmetic Coding)
    //CAVLC: 基于上下文自适应变长编码(Context-based Adaptive Variable Length Coding)
    //CABAC 比特率小,但是 压缩时间长于CAVLC
    h264Param.bEntropyCodingCABAC = 1;

    // 4Mbps码流
    h264Param.nBitrate = 4 * 1024 * 1024;
    h264Param.nFramerate = RECORD_FRAME;     //帧速率

    //VENC_FRAME_CODING         = 0,   帧编码 适合  静止场景
    //VENC_FIELD_CODING         = 1,   场编码 适合  运动场景    
    //但是大疆psdk仅支持帧格式编码,不支持场编码
    h264Param.nCodingMode = VENC_FRAME_CODING;
    //h264Param.nCodingMode = VENC_FIELD_CODING;

    h264Param.nMaxKeyInterval = RECORD_FRAME;

    //Profile是用来描述视频压缩特性的,profile越高,就说明采用了越高级的压缩特性
    // https://blog.csdn.net/szfhy/article/details/53159869
    h264Param.sProfileLevel.nProfile = VENC_H264ProfileBaseline;  //baseline压缩  视频只有i p帧
    h264Param.sProfileLevel.nLevel = VENC_H264Level31;

    //QP:量化参数 quantization parameter。DCT时的量化参数大小。QP数值越大,失真越多,压缩越好,画质越差。
    //QP 区间。QP值对应量化步长的序号,对于亮度取值0-51, 对于色度取值0~39。
    //值越小,量化步长越小,量化的精度就越高,意味着同样画质的情况下,产生的数据量可能会更大。QP值每增加6,量化步长就增加一倍。
    h264Param.sQPRange.nMinqp = 10;   // 最小量化步长
    h264Param.sQPRange.nMaxqp = 40;   // 最大量化步长51
}

/**********************************************************/       
/***********           编码器参数设置 *********************/
/**********************************************************/
static int CedarX_venc_encode_config()
{
    //清空参数
    memset(&VencodeBaseConfig, 0, sizeof(VencBaseConfig));

    //注册编码器缓存
    if (VencodeBaseConfig.memops == NULL)
    {
        VencodeBaseConfig.memops = MemAdapterGetOpsS();
        if (VencodeBaseConfig.memops == NULL)
        {
            printf("MemAdapterGetOpsS failed\n");
            return -1;
        }
        CdcMemOpen(VencodeBaseConfig.memops);
    }

    //1. nInputWidth:输入图像帧的宽度,以像素为单位;
    //2. nInputHeight:输入图像帧的高度,以像素为单位;
    VencodeBaseConfig.nInputWidth = RECORD_WIDTH;
    VencodeBaseConfig.nInputHeight = RECORD_HEIGHT;

    //3. nDstWidth:编码前对输入图像做 scale 后的宽度,以像素为单位; 如果不需要做 scale,nDstWidth 的值保持和 nInputWidth 一致;
    //4. nDstHeight:编码前对输入图像做 scale 后的高度,以像素为单位; 如果不需要做 scale,nDstHeight 的值保持和 nInputHeight 一致;
    VencodeBaseConfig.nDstWidth = RECORD_WIDTH;
    VencodeBaseConfig.nDstHeight = RECORD_HEIGHT;

    //5. nStride: 输入图像帧在内存中的行宽,以像素为单位,编码器要求nStride 必须 16 对齐
    VencodeBaseConfig.nStride = RECORD_WIDTH;
    //VencodeBaseConfig.nStride  = ((RECORD_WIDTH + 15) & ~15);

    // eInputFormat:输入的颜色格式;
    // YUV420SP 即是 NV12  --ookk303
    VencodeBaseConfig.eInputFormat = VENC_PIXEL_YUV420SP;
} 


/**********************************************************/       
/***********           编码器初始化 *********************/
/**********************************************************/
static int CedarX_venc_encode_init()
{
    int EncodeParamValue;

    //如果编码器还没使用  注册一个编码器
    if (CedarxVideoEncoder == NULL)
    {
        //编码类型
        CedarxVideoEncoder = VideoEncCreate((VENC_CODEC_TYPE)VENC_CODEC_H264);

        //编码参数的写入
        VideoEncSetParameter(CedarxVideoEncoder, VENC_IndexParamH264Param, &h264Param);
        EncodeParamValue = 0;
        VideoEncSetParameter(CedarxVideoEncoder, VENC_IndexParamIfilter, &EncodeParamValue);
        EncodeParamValue = 0; //degree
        VideoEncSetParameter(CedarxVideoEncoder, VENC_IndexParamRotation, &EncodeParamValue);
        EncodeParamValue = 0;
        VideoEncSetParameter(CedarxVideoEncoder, VENC_IndexParamSetPSkip, &EncodeParamValue);

        //编码器初始化
        VideoEncInit(CedarxVideoEncoder, &VencodeBaseConfig);
    }

}

/**********************************************************/       
/***********     h264视频标准帧头创建 *********************/
/**********************************************************/
static int CedarX_venc_encode_H264head()
{

     //获取h264帧头部内容
    //pEncoder: 通过 VideoEncCreate 函数创建的视频编码器指针;
    //indexType:参数类型索引号;
    //paramData(输出):参数数据指针
    VideoEncGetParameter(CedarxVideoEncoder, VENC_IndexParamH264SPSPPS, &sps_pps_data);

    // 写入帧头信息 这里注释掉 将在其他地方写
    //fwrite(sps_pps_data.pBuffer, 1, sps_pps_data.nLength, fpH264);

}

/**********************************************************/       
/***********     编码器输入图像缓冲区创建 **********************/
/**********************************************************/
static int CedarX_venc_encode_InputBuffer_init()
{
    //创建 输入图像缓冲区,并配置参数
    VencAllocateBufferParam bufferParam;
    memset(&bufferParam, 0, sizeof(VencAllocateBufferParam));

    //图像缓冲区参数  yuv视频的yc分类
    bufferParam.nSizeY = VencodeBaseConfig.nInputWidth * VencodeBaseConfig.nInputHeight ;
    bufferParam.nSizeC = VencodeBaseConfig.nInputWidth * VencodeBaseConfig.nInputHeight / 2;  //yuv420
    //bufferParam.nSizeC = VencodeBaseConfig.nInputWidth * VencodeBaseConfig.nInputHeight ;  //yuv422
    bufferParam.nBufferNum = 1;

    //通过编码器   分配 输入图像缓冲区
    AllocInputBuffer(CedarxVideoEncoder, &bufferParam);
}




/**********************************************************/       
/***********    编码器的释放         **********************/
/**********************************************************/
int CedarX_encode_uninit()
{

    //编码器去初始化
    VideoEncUnInit(CedarxVideoEncoder);

    //销毁编码器
    VideoEncDestroy(CedarxVideoEncoder);
    CedarxVideoEncoder = NULL;

    //销毁输入输出队列
    //FreeOneBitStreamFrame(H3_VideoEncode, &inputBuffer);
    //FreeOneBitStreamFrame(H3_VideoEncode, &outputBuffer);
}


/**********************************************************/       
/***********    编码器的初始化       **********************/
/**********************************************************/
int CedarX_venc_init(int height, int width, int charm)
{
    g_venc_RecordHeight = height;
    g_venc_RecordWidth = width;
    g_venc_RecordCharm = charm;

    //配置H264流的参数
    cedarX_venc_H264param_init();

    //配置编码器
    CedarX_venc_encode_config();

    //创建编码器
    CedarX_venc_encode_init();

    //创建h264帧头
    CedarX_venc_encode_H264head();

    //创建编码器的 输入缓冲区
    CedarX_venc_encode_InputBuffer_init();

    printf("编码器初始化完成\n");

}

int CedarX_NV12_TO_H264_EncodeOneFrame(unsigned char *AddrVirY, unsigned char *AddrVirC, char **EncodeReturnBuffer, int *EncodeReturnLength)
{   
    //清空输入队列
    memset(&inputBuffer, 0, sizeof(VencInputBuffer));

    g_charm_sps++;
    if(g_charm_sps >g_venc_RecordCharm)
    {
        //第一帧 关键帧
        g_charm_sps = 1;
        //标记关键帧
        inputBuffer.nFlag = 1;
    }

    // AllocInputBuffer 申请输入获取队列
    GetOneAllocInputBuffer(CedarxVideoEncoder, &inputBuffer);

    //写入数据到数列
    memcpy(inputBuffer.pAddrVirY, AddrVirY, VencodeBaseConfig.nInputWidth * VencodeBaseConfig.nInputHeight);
    memcpy(inputBuffer.pAddrVirC, AddrVirC, VencodeBaseConfig.nInputWidth * VencodeBaseConfig.nInputHeight / 2); 

    //裁剪使能
    inputBuffer.bEnableCorp = 0;

    //矩形信息
    inputBuffer.sCropInfo.nLeft = 240;
    inputBuffer.sCropInfo.nTop = 240;
    inputBuffer.sCropInfo.nWidth = 240;
    inputBuffer.sCropInfo.nHeight = 240;

    //刷 cache 保存数据的一致性
    FlushCacheAllocInputBuffer(CedarxVideoEncoder, &inputBuffer);

    //添加输入图像帧到编码器
    AddOneInputBuffer(CedarxVideoEncoder, &inputBuffer);

    //编码一帧
    if (VENC_RESULT_OK != VideoEncodeOneFrame(CedarxVideoEncoder))
    {
        printf("VideoEncodeOneFrame failed.\n");
        return -1;
    }

    //获取 VideoEncodeOneFrame 已经使用过的输入图像帧;
    AlreadyUsedInputBuffer(CedarxVideoEncoder, &inputBuffer);

    // 还回由 AllocInputBuffer 申请的输入图像帧 buffe
    ReturnOneAllocInputBuffer(CedarxVideoEncoder, &inputBuffer);

    //功能 获取有效的的输出码流 buffer 的格式
    GetOneBitstreamFrame(CedarxVideoEncoder, &outputBuffer);

    //1.nID:用来识别不同的 buffer:
    //2.nPts:编码器不对时间戳信息做处理,输出 buffer 中的 pts 对应相应输入buffer 中的 pts;
    //3.nSize0:输出码流的第一部分的大小;
    //4.nSize1:输出码流的第二部分的大小;
    //5.pData0:输出码流的第一部分的地址;
    //6.pData1:输出码流的第二部分的地址;
    //输出的一笔码流可能由两部分组成:nSize0 表示第一部分的大小,nSize1表示第二部分的大小;
    //nSize0 一定大于 0,当 nSize1 = 0 的时候,输出码流只在地址 pData0 中;
    //当 nSize1 > 0 时,输出码流由两部分组成,第一部分在 pData0 中,第二部分
    //在 pData1 中,此时需要外部应用程序把这两部分数据组合成一帧;

    //普通帧的处理
    if(g_charm_sps != 1)
    {
        *EncodeReturnLength = outputBuffer.nSize0 + outputBuffer.nSize1;
        
        *EncodeReturnBuffer = (unsigned char*)malloc(*EncodeReturnLength);
        if(EncodeReturnBuffer == NULL)
        {
            JZSDK_LOG_ERROR("malloc uv_tmp_buffer fail\n");
        }
        //因为outputbuffer会释放掉,所以得memcpy出去
        //合成 data1 和data2
        //printf("write pData0\n");
        memcpy(*EncodeReturnBuffer, outputBuffer.pData0, outputBuffer.nSize0);

        //printf("write pData1\n");
        memcpy(*EncodeReturnBuffer + outputBuffer.nSize0 , outputBuffer.pData1, outputBuffer.nSize1);

    }
    //关键帧处理
    else if(g_charm_sps == 1)
    {
        //如果为关键帧,要加上头信息
        *EncodeReturnLength = sps_pps_data.nLength + outputBuffer.nSize0 + outputBuffer.nSize1;

        *EncodeReturnBuffer = (unsigned char*)malloc(*EncodeReturnLength);
        if(EncodeReturnBuffer == NULL)
        {
            JZSDK_LOG_ERROR("malloc uv_tmp_buffer fail\n");
        }

        memcpy(*EncodeReturnBuffer, sps_pps_data.pBuffer, sps_pps_data.nLength);
        memcpy(*EncodeReturnBuffer + sps_pps_data.nLength, outputBuffer.pData0, outputBuffer.nSize0);
        memcpy(*EncodeReturnBuffer + sps_pps_data.nLength + outputBuffer.nSize0 , outputBuffer.pData1, outputBuffer.nSize1);

        printf("关键帧处理\n");
    }

      //释放 输出队列的一帧
    FreeOneBitStreamFrame(CedarxVideoEncoder, &outputBuffer);

    return 0;
}

#endif