x264+mp4v2编码YUV420为mp4

视诀项目的目标是视频美颜,对视频的处理先要解码为YUV420P图片,然后将处理好的图片再编码为mp4文件。在使用ffmpeg编码mp4过程中发现过于复杂,所以换了下思路,首先使用x264将YUV420编码为h264,然后使用mp4v2将编码后的h264编为mp4文件。

x264将YUV编码为H264

x264编码相关的资源封装

typedef struct encoder_x264_t{
    x264_param_t    param;
    x264_picture_t  pic_in;
    x264_picture_t  pic_out;
    x264_t *        h;
    int             iframe;
    int             iframe_size;
    x264_nal_t *    nal;
    int             inal;
}encoder_x264_t;


初始化

主要为encoder_x264_t的配置,以及相关资源的申请。要注意帧率的设置及slice的设置。如果slice大于1,那么在i帧就会有多个0x65,slice的数量大于1,在mp4v2在转mp4过程中会有问题。

int encoder_x264_init(encoder_x264_t* x, int width, int height, int fps_num, int fps_den){
    //if(x264_param_default_preset( &x->param, "medium", NULL ) < 0){
    // 不保留缓冲buffer,这样后面不需要调用x264_encoder_delayed_frames
    if(x264_param_default_preset( &x->param, "veryfast", "zerolatency" ) < 0){
        return -1;
    }
	// YUV420    
    x->param.i_csp = X264_CSP_I420;
    x->param.i_width  = width;
    x->param.i_height = height;
    x->param.b_vfr_input = 0;
    x->param.b_repeat_headers = 1;
    x->param.b_annexb = 1;
    // 帧率
    x->param.i_fps_num = fps_num;
    x->param.i_fps_den = fps_den;
    // 指定处理线程,如果不为1,slice设置将失效
    x->param.i_threads = 1;
    // 依赖i_threads的设置,否则此处设置一个slice将失效
    x->param.i_slice_count = 1;
    x->param.i_slice_count_max = 1;
    x->param.rc.f_rf_constant = 24;
    x->param.rc.i_rc_method = X264_RC_CRF;
    // baseline通用性更好,很多播放器对highline支持不好
    if( x264_param_apply_profile( &x->param, "baseline" ) < 0 ){
        return -1;
    }
    // 申请图片空间
    if(x264_picture_alloc( &x->pic_in, x->param.i_csp, x->param.i_width, x->param.i_height ) < 0){
        return -1;
    }
    
    x->h = x264_encoder_open( &x->param );
    if( !x->h ){
        x264_picture_clean(&x->pic_in);
        return -1;
    }
    
    return 0;
}


YUV420编码为h264

编码的过程比较简单,代码如下:

int encoder_x264_encode(encoder_x264_t* x, const char* buffer){
    int luma_size = x->param.i_width * x->param.i_height;
    int chroma_size = luma_size / 4;
    
    memcpy(x->pic_in.img.plane[0], buffer, luma_size);
    memcpy(x->pic_in.img.plane[1], buffer+luma_size, chroma_size);
    memcpy(x->pic_in.img.plane[2], buffer+luma_size+chroma_size, chroma_size);
  
    x->pic_in.i_pts = x->iframe++;
    x->iframe_size = x264_encoder_encode( x->h, &x->nal, &x->inal, &x->pic_in, &x->pic_out );
    return x->iframe_size;
}


X264资源的释放

void encoder_x264_close(encoder_x264_t* x){
    /* init函数中已经设置不保留缓冲,所以此处无需调用
    while( x264_encoder_delayed_frames( x->h ) )
    {
        x->iframe_size = x264_encoder_encode( x->h, &x->nal, &x->inal, NULL, &x->pic_out );
        if(x->iframe_size){
            printf("rear!!!\n");
        }
    }
    */
    
    x264_encoder_close(x->h);
    x264_picture_clean(&x->pic_in);
}


MP4V2将H264转为MP4

调用方法

  • 建立一个MP4Encoder对象,参数分别为带路径的mp4文件名、宽、高、帧率
  • 根据场景选择以下两个方法将帧写入MP4文件。 -- writeYuv420Data:该方法先调用encoder_x264_encode编码为H264,然后调用writeH264Data写入MP4文件 -- writeH264Data:将H264帧写入MP4文件
  • 调用close flush文件及释放资源

源代码:

MP4Encoder.h

#ifndef	__MP4ENCODER_H_
#define __MP4ENCODER_H_


#include "mp4v2/mp4v2.h"
#include "video_encode.h"

// NALU单元
typedef struct _MP4ENC_NaluUnit
{
    int type;
    int size;
    unsigned char *data;
}MP4ENC_NaluUnit;

typedef struct _MP4ENC_Metadata
{
    // video, must be h264 type
    unsigned int    nSpsLen;
    unsigned char   Sps[1024];
    unsigned int    nPpsLen;
    unsigned char   Pps[1024];
    
} MP4ENC_Metadata,*LPMP4ENC_Metadata;

class MP4Encoder
{
public:
    MP4Encoder();
    MP4Encoder(const char *pFileName,int width,int height, int fps_num=25, int fps_den=1);

public:

    MP4FileHandle create(const char *fileName,int width,int height, int fps_num=25, int fps_den=1);
    bool writeH264Meta(MP4FileHandle hMp4File,LPMP4ENC_Metadata lpMetadata);
    /**
     * 写入YUV420P数据,data长度必须为width*height*1.5
     */
    int writeYuv420Data(const unsigned char* data);
    /**
     * 写入h264数据
     */
    int writeH264Data(const unsigned char* data,int size, bool first);
    void close();
    static bool praseMeta(const unsigned char* pData,int size,MP4ENC_Metadata &metadata);
private:

    static int readOneNaluFromBuf(const unsigned char *buffer,unsigned int nBufferSize,unsigned int offSet,MP4ENC_NaluUnit &nalu);
private:
    int             m_width;
    int             m_height;
    int             m_frame_rate;
    int             m_time_scale;
    MP4TrackId      m_video_id;
    unsigned char   m_sps[1024];
    unsigned char   m_pps[4096];
    int             m_sps_size;
    int             m_pps_size;
    MP4FileHandle   m_file;
    
    // x264编码
    encoder_x264_t  m_encode;
};

#endif

MP4Encoder.cpp

#include "MP4Encoder.h"
#include <string.h>

#define BUFFER_SIZE  (1024*1024)

MP4Encoder::MP4Encoder():m_video_id(NULL),
m_width(0),
m_height(0),
m_time_scale(0),
m_frame_rate(0),
m_sps_size(0),
m_pps_size(0){}

MP4Encoder::MP4Encoder(const char *pFileName,int width,int height, int fps_num, int fps_den){
    m_video_id = NULL;
    m_file = create(pFileName, width, height, fps_num, fps_den);
}

MP4FileHandle MP4Encoder::create(const char *pFileName,int width,int height, int fps_num, int fps_den)
{
    if(pFileName == NULL)
    {
        return NULL;
    }
    // create mp4 file
    m_file = MP4Create(pFileName);
    if (m_file == MP4_INVALID_FILE_HANDLE)
    {
        fprintf(stderr, "MP4Encoder ERROR: Open file[%s] fialed.\n", pFileName);
        return NULL;
    }
    m_width = width;
    m_height = height;
    m_time_scale = 90000;
    m_frame_rate = fps_num;
    MP4SetTimeScale(m_file, m_time_scale);
    
    if(encoder_x264_init(&m_encode, width, height, fps_num, fps_den)!=0){
        return NULL;
    }
    
    return m_file;
}

bool MP4Encoder::writeH264Meta(MP4FileHandle hMp4File,LPMP4ENC_Metadata lpMetadata)
{
    m_video_id = MP4AddH264VideoTrack
    (hMp4File,
     m_time_scale,
     m_time_scale / m_frame_rate,
     m_width, // width
     m_height,// height
     lpMetadata->Sps[1], // sps[1] AVCProfileIndication
     lpMetadata->Sps[2], // sps[2] profile_compat
     lpMetadata->Sps[3], // sps[3] AVCLevelIndication
     3);           // 4 bytes length before each NAL unit
    if (m_video_id == MP4_INVALID_TRACK_ID)
    {
        printf("add video track failed.\n");
        return false;
    }
    MP4SetVideoProfileLevel(hMp4File, 0x01); //  Simple Profile @ Level 3
    
    // write sps
    MP4AddH264SequenceParameterSet(hMp4File,m_video_id,lpMetadata->Sps,lpMetadata->nSpsLen);
    
    // write pps
    MP4AddH264PictureParameterSet(hMp4File,m_video_id,lpMetadata->Pps,lpMetadata->nPpsLen);
    
    return true;
}

int MP4Encoder::writeYuv420Data(const unsigned char* data){
    int size = encoder_x264_encode(&m_encode, (const char*)data);
    if (size>0) {
        return writeH264Data(encoder_x264_frame(&m_encode), m_encode.iframe_size, m_encode.iframe==1);
    }
    
    return size;
}

int MP4Encoder::writeH264Data(const unsigned char* pData,int size, bool first)
{
    if(m_file == NULL)
    {
        return -1;
    }
    if(pData == NULL)
    {
        return -1;
    }
    
    MP4ENC_NaluUnit nalu;
    int pos = 0, len = 0;
    printf("INPUT BUFFER SIZE: %d\n", size);
    while ((len = readOneNaluFromBuf(pData,size,pos,nalu))!=0){
        printf("pos[%d] len[%d] Nalu: type[%d] size[%d]\n", pos, len, nalu.type, nalu.size);
		if(first) {
			if(nalu.type != 0x67) {
                printf("sps not found!\n");
				return -1;
			}
            // 添加h264 track
            m_video_id = MP4AddH264VideoTrack
            (m_file,
             m_time_scale,
             m_time_scale / m_frame_rate,
             m_width,     // width
             m_height,    // height
             nalu.data[1], // sps[1] AVCProfileIndication
             nalu.data[2], // sps[2] profile_compat
             nalu.data[3], // sps[3] AVCLevelIndication
             3);           // 4 bytes length before each NAL unit
            if (m_video_id == MP4_INVALID_TRACK_ID)
            {
                printf("add video track failed.\n");
                return 0;
            }
            MP4SetVideoProfileLevel(m_file, 1); //  Simple Profile @ Level 3

			m_sps_size = m_pps_size = 0;
			first = false;
		}


        if(nalu.type == 0x67) // sps
        {
			if(!m_sps_size) {
				memcpy(m_sps, nalu.data,nalu.size);
				m_sps_size = nalu.size;
			}
        }
        else if(nalu.type == 0x68) // pps
        {
			if(!m_pps_size) {
				memcpy(m_pps, nalu.data,nalu.size);
				m_pps_size = nalu.size;
			}
        }
        else
        {
			int key = -1;
			if(nalu.type == 0x65) { // I frame
				if(!m_sps_size||!m_pps_size) {
					printf("sps/pps not found!\n");
					return 0;
				}
				MP4AddH264SequenceParameterSet(m_file,m_video_id,m_sps, m_sps_size);
				MP4AddH264PictureParameterSet (m_file,m_video_id,m_pps, m_pps_size);
				key = 1;
			}
			else if(nalu.type == 0x41) { // P frame
				key = 0;
			}
			
			if(key >= 0) {
				int datalen = nalu.size+4;
				unsigned char *data = new unsigned char[datalen];
				// MP4 Nalu前四个字节表示Nalu长度
				data[0] = nalu.size>>24;
				data[1] = nalu.size>>16;
				data[2] = nalu.size>>8;
				data[3] = nalu.size&0xff;
				memcpy(data+4,nalu.data,nalu.size);
				int success = MP4WriteSample(m_file, m_video_id, data, datalen,MP4_INVALID_DURATION, 0, key);
				delete[] data;
				if(!success){
					printf("MP4WriteSample() failed!\n");
					return 0;
				}
			}
        }
        
        pos += len;
    }
    return pos;
}

int MP4Encoder::readOneNaluFromBuf(const unsigned char *buffer,unsigned int nBufferSize,unsigned int offSet,MP4ENC_NaluUnit &nalu)
{
	int i = offSet+2;
	if(i < 2)
	return 0;
	while(++ i < nBufferSize)
	{
		if(	buffer[i-3] == 0x00 &&
			buffer[i-2] == 0x00 &&
			buffer[i-1] == 0x01
		)
		{
			int pos = i+3;
			while(++ pos < nBufferSize)
			{
				if(	buffer[pos-3] == 0x00 &&
					buffer[pos-2] == 0x00 &&
					((0xFE&buffer[pos-1]) == 0x00)
				)
				{
					break;
				}
			}
			if(pos >= nBufferSize)
			{
				nalu.size = nBufferSize-i;
			}
			else
			{
				nalu.size = pos-3-i;
			}

			nalu.type = buffer[i]&0xFF;
			nalu.data =(unsigned char*)&buffer[i];
			return (nalu.size+i-offSet);
		}
	}
	return 0;
}

void MP4Encoder::close()
{
    if(m_file)
    {
        MP4Close(m_file);
        m_file = NULL;
        encoder_x264_close(&m_encode);
    }
}

bool MP4Encoder:: praseMeta(const unsigned char* pData,int size,MP4ENC_Metadata &metadata)
{
    if(pData == NULL || size<4)
    {
        return false;
    }
    MP4ENC_NaluUnit nalu;
    int pos = 0;
    bool bRet1 = false,bRet2 = false;
    while (int len = readOneNaluFromBuf(pData,size,pos,nalu))
    {
        if(nalu.type == 0x07)
        {
            memcpy(metadata.Sps,nalu.data,nalu.size);
            metadata.nSpsLen = nalu.size;
            bRet1 = true;
        }
        else if((nalu.type == 0x08))
        {
            memcpy(metadata.Pps,nalu.data,nalu.size);
            metadata.nPpsLen = nalu.size;
            bRet2 = true;
        }
        pos += len;
    }
    if(bRet1 && bRet2)
    {
        return true;
    }
    return false;
}


评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值