视诀项目的目标是视频美颜,对视频的处理先要解码为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;
}