YUV编码为H264 H264封装为MP4

在项目中经常需要进行视频解码、编码和封装等操作,本文主要阐述”YUV编码为H264”和”H264封装为MP4”两个过程。

1 YUV编码为H264

YUV编码为H264有两种方式:
(1)基于FFMPEG调用libx264实现YUV420P的像素数据编码为H.264的压缩编码数据;
(2)直接调用libx264将输入的YUV数据编码为H.264码流文件;
1.1 基于FFmpeg YUV编码为H264
使用FFmpeg编码视频涉及的主要函数:
av_register_all():注册FFmpeg所有编解码器。
avformat_alloc_output_context2():初始化输出码流的AVFormatContext。
avio_open():打开输出文件。
av_new_stream():创建输出码流的AVStream。
avcodec_find_encoder():查找编码器。
avcodec_open2():打开编码器。
avformat_write_header():写文件头(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS)。
avcodec_encode_video2():编码一帧视频。即将AVFrame(存储YUV像素数据)编码为AVPacket(存储H.264等格式的码流数据)。
av_write_frame():将编码后的视频码流写入文件。
flush_encoder():输入的像素数据读取完成后调用此函数。用于输出编码器中剩余的AVPacket。
av_write_trailer():写文件尾(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS)。
代码:

#include 

#define __STDC_CONSTANT_MACROS

#ifdef _WIN32
//Windows
extern "C"
{
#include "libavutil/opt.h"
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include 
#include 
#include 
#ifdef __cplusplus
};
#endif
#endif


int flush_encoder(AVFormatContext *fmt_ctx,unsigned int stream_index){
    int ret;
    int got_frame;
    AVPacket enc_pkt;
    if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities &
        CODEC_CAP_DELAY))
        return 0;
    while (1) {
        enc_pkt.data = NULL;
        enc_pkt.size = 0;
        av_init_packet(&enc_pkt);
        ret = avcodec_encode_video2 (fmt_ctx->streams[stream_index]->codec, &enc_pkt,
            NULL, &got_frame);
        av_frame_free(NULL);
        if (ret < 0)
            break;
        if (!got_frame){
            ret=0;
            break;
        }
        printf("Flush Encoder: Succeed to encode 1 frame!\tsize:%5d\n",enc_pkt.size);
        /* mux encoded frame */
        ret = av_write_frame(fmt_ctx, &enc_pkt);
        if (ret < 0)
            break;
    }
    return ret;
}

int main(int argc, char* argv[])
{
    AVFormatContext* pFormatCtx;
    AVOutputFormat* fmt;
    AVStream* video_st;
    AVCodecContext* pCodecCtx;
    AVCodec* pCodec;
    AVPacket pkt;
    uint8_t* picture_buf;
    AVFrame* pFrame;
    int picture_size;
    int y_size;
    int framecnt=0;
    //FILE *in_file = fopen("src01_480x272.yuv", "rb"); //Input raw YUV data 
    FILE *in_file = fopen("../ds_480x272.yuv", "rb");   //Input raw YUV data
    int in_w=480,in_h=272;                              //Input data's width and height
    int framenum=100;                                   //Frames to encode
    //const char* out_file = "src01.h264";              //Output Filepath 
    //const char* out_file = "src01.ts";
    //const char* out_file = "src01.hevc";
    const char* out_file = "ds.h264";

    av_register_all();
    //Method1.
    pFormatCtx = avformat_alloc_context();
    //Guess Format
    fmt = av_guess_format(NULL, out_file, NULL);
    pFormatCtx->oformat = fmt;

    //Method 2.
    //avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, out_file);
    //fmt = pFormatCtx->oformat;


    //Open output URL
    if (avio_open(&pFormatCtx->pb,out_file, AVIO_FLAG_READ_WRITE) < 0){
        printf("Failed to open output file! \n");
        return -1;
    }

    video_st = avformat_new_stream(pFormatCtx, 0);
    video_st->time_base.num = 1; 
    video_st->time_base.den = 25;  

    if (video_st==NULL){
        return -1;
    }
    //Param that must set
    pCodecCtx = video_st->codec;
    //pCodecCtx->codec_id =AV_CODEC_ID_HEVC;
    pCodecCtx->codec_id = fmt->video_codec;
    pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
    pCodecCtx->pix_fmt = PIX_FMT_YUV420P;
    pCodecCtx->width = in_w;  
    pCodecCtx->height = in_h;
    pCodecCtx->time_base.num = 1;  
    pCodecCtx->time_base.den = 25;  
    pCodecCtx->bit_rate = 400000;  
    pCodecCtx->gop_size=250;
    //H264
    //pCodecCtx->me_range = 16;
    //pCodecCtx->max_qdiff = 4;
    //pCodecCtx->qcompress = 0.6;
    pCodecCtx->qmin = 10;
    pCodecCtx->qmax = 51;

    //Optional Param
    pCodecCtx->max_b_frames=3;

    // Set Option
    AVDictionary *param = 0;
    //H.264
    if(pCodecCtx->codec_id == AV_CODEC_ID_H264) {
        av_dict_set(¶m, "preset", "slow", 0);
        av_dict_set(¶m, "tune", "zerolatency", 0);
        //av_dict_set(¶m, "profile", "main", 0);
    }
    //H.265
    if(pCodecCtx->codec_id == AV_CODEC_ID_H265){
        av_dict_set(¶m, "preset", "ultrafast", 0);
        av_dict_set(¶m, "tune", "zero-latency", 0);
    }

    //Show some Information
    av_dump_format(pFormatCtx, 0, out_file, 1);

    pCodec = avcodec_find_encoder(pCodecCtx->codec_id);
    if (!pCodec){
        printf("Can not find encoder! \n");
        return -1;
    }
    if (avcodec_open2(pCodecCtx, pCodec,¶m) < 0){
        printf("Failed to open encoder! \n");
        return -1;
    }


    pFrame = av_frame_alloc();
    picture_size = avpicture_get_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);
    picture_buf = (uint8_t *)av_malloc(picture_size);
    avpicture_fill((AVPicture *)pFrame, picture_buf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);

    //Write File Header
    avformat_write_header(pFormatCtx,NULL);

    av_new_packet(&pkt,picture_size);

    y_size = pCodecCtx->width * pCodecCtx->height;

    for (int i=0; i//Read raw YUV data
        if (fread(picture_buf, 1, y_size*3/2, in_file) <= 0){
            printf("Failed to read raw data! \n");
            return -1;
        }else if(feof(in_file)){
            break;
        }
        pFrame->data[0] = picture_buf;              // Y
        pFrame->data[1] = picture_buf+ y_size;      // U 
        pFrame->data[2] = picture_buf+ y_size*5/4;  // V
        //PTS
        pFrame->pts=i;
        int got_picture=0;
        //Encode
        int ret = avcodec_encode_video2(pCodecCtx, &pkt,pFrame, &got_picture);
        if(ret < 0){
            printf("Failed to encode! \n");
            return -1;
        }
        if (got_picture==1){
            printf("Succeed to encode frame: %5d\tsize:%5d\n",framecnt,pkt.size);
            framecnt++;
            pkt.stream_index = video_st->index;
            ret = av_write_frame(pFormatCtx, &pkt);
            av_free_packet(&pkt);
        }
    }
    //Flush Encoder
    int ret = flush_encoder(pFormatCtx,0);
    if (ret < 0) {
        printf("Flushing encoder failed\n");
        return -1;
    }

    //Write file trailer
    av_write_trailer(pFormatCtx);

    //Clean
    if (video_st){
        avcodec_close(video_st->codec);
        av_free(pFrame);
        av_free(picture_buf);
    }
    avio_close(pFormatCtx->pb);
    avformat_free_context(pFormatCtx);

    fclose(in_file);

    return 0;
}

1.2 直接调用libx264 YUV编码为H264
调用libx264进行视频编码涉及的主要函数:
x264_param_default():设置参数集结构体x264_param_t的缺省值。
x264_picture_alloc():为图像结构体x264_picture_t分配内存。
x264_encoder_open():打开编码器。
x264_encoder_encode():编码一帧图像。
x264_encoder_close():关闭编码器。
x264_picture_clean():释放x264_picture_alloc()申请的资源。

存储数据的结构体如下所示。
x264_picture_t:存储压缩编码前的像素数据。
x264_nal_t:存储压缩编码后的码流数据。
代码:

#include   
#include   

#include "stdint.h"  

#if defined ( __cplusplus)  
extern "C"  
{  
#include "x264.h"  
};  
#else  
#include "x264.h"  
#endif  


int main(int argc, char** argv)  
{  

         int ret;  
         int y_size;  
         int i,j;  

         //FILE* fp_src  = fopen("../cuc_ieschool_640x360_yuv444p.yuv", "rb");  
         FILE* fp_src  = fopen("../cuc_ieschool_640x360_yuv420p.yuv", "rb");  

         FILE* fp_dst = fopen("cuc_ieschool.h264", "wb");  

         //Encode 50 frame  
         //if set 0, encode all frame  
         int frame_num=50;  
         int csp=X264_CSP_I420;  
         int width=640,height=360;  

         int iNal   = 0;  
         x264_nal_t* pNals = NULL;  
         x264_t* pHandle   = NULL;  
         x264_picture_t* pPic_in = (x264_picture_t*)malloc(sizeof(x264_picture_t));  
         x264_picture_t* pPic_out = (x264_picture_t*)malloc(sizeof(x264_picture_t));  
         x264_param_t* pParam = (x264_param_t*)malloc(sizeof(x264_param_t));  

         //Check  
         if(fp_src==NULL||fp_dst==NULL){  
                   printf("Error open files.\n");  
                   return -1;  
         }  

         x264_param_default(pParam);  
         pParam->i_width   = width;  
         pParam->i_height  = height;  
         /* 
         //Param 
         pParam->i_log_level  = X264_LOG_DEBUG; 
         pParam->i_threads  = X264_SYNC_LOOKAHEAD_AUTO; 
         pParam->i_frame_total = 0; 
         pParam->i_keyint_max = 10; 
         pParam->i_bframe  = 5; 
         pParam->b_open_gop  = 0; 
         pParam->i_bframe_pyramid = 0; 
         pParam->rc.i_qp_constant=0; 
         pParam->rc.i_qp_max=0; 
         pParam->rc.i_qp_min=0; 
         pParam->i_bframe_adaptive = X264_B_ADAPT_TRELLIS; 
         pParam->i_fps_den  = 1; 
         pParam->i_fps_num  = 25; 
         pParam->i_timebase_den = pParam->i_fps_num; 
         pParam->i_timebase_num = pParam->i_fps_den; 
         */  
         pParam->i_csp=csp;  
         x264_param_apply_profile(pParam, x264_profile_names[5]);  

         pHandle = x264_encoder_open(pParam);  

         x264_picture_init(pPic_out);  
         x264_picture_alloc(pPic_in, csp, pParam->i_width, pParam->i_height);  

         //ret = x264_encoder_headers(pHandle, &pNals, &iNal);  

         y_size = pParam->i_width * pParam->i_height;  
         //detect frame number  
         if(frame_num==0){  
                   fseek(fp_src,0,SEEK_END);  
                   switch(csp){  
                   case X264_CSP_I444:frame_num=ftell(fp_src)/(y_size*3);break;  
                   case X264_CSP_I420:frame_num=ftell(fp_src)/(y_size*3/2);break;  
                   default:printf("Colorspace Not Support.\n");return -1;  
                   }  
                   fseek(fp_src,0,SEEK_SET);  
         }  

         //Loop to Encode  
         for( i=0;iswitch(csp){  
                   case X264_CSP_I444:{  
                            fread(pPic_in->img.plane[0],y_size,1,fp_src);         //Y  
                            fread(pPic_in->img.plane[1],y_size,1,fp_src);         //U  
                            fread(pPic_in->img.plane[2],y_size,1,fp_src);         //V  
                            break;}  
                   case X264_CSP_I420:{  
                            fread(pPic_in->img.plane[0],y_size,1,fp_src);         //Y  
                            fread(pPic_in->img.plane[1],y_size/4,1,fp_src);     //U  
                            fread(pPic_in->img.plane[2],y_size/4,1,fp_src);     //V  
                            break;}  
                   default:{  
                            printf("Colorspace Not Support.\n");  
                            return -1;}  
                   }  
                   pPic_in->i_pts = i;  

                   ret = x264_encoder_encode(pHandle, &pNals, &iNal, pPic_in, pPic_out);  
                   if (ret< 0){  
                            printf("Error.\n");  
                            return -1;  
                   }  

                   printf("Succeed encode frame: %5d\n",i);  

                   for ( j = 0; j < iNal; ++j){  
                             fwrite(pNals[j].p_payload, 1, pNals[j].i_payload, fp_dst);  
                   }  
         }  
         i=0;  
         //flush encoder  
         while(1){  
                   ret = x264_encoder_encode(pHandle, &pNals, &iNal, NULL, pPic_out);  
                   if(ret==0){  
                            break;  
                   }  
                   printf("Flush 1 frame.\n");  
                   for (j = 0; j < iNal; ++j){  
                            fwrite(pNals[j].p_payload, 1, pNals[j].i_payload, fp_dst);  
                   }  
                   i++;  
         }  
         x264_picture_clean(pPic_in);  
         x264_encoder_close(pHandle);  
         pHandle = NULL;  

         free(pPic_in);  
         free(pPic_out);  
         free(pParam);  

         fclose(fp_src);  
         fclose(fp_dst);  

         return 0;  
}  

2 H264封装为MP4

H264封装为MP4有两种方式:
(1)基于FFmpeg使用mp4封装格式封装视频数据;(这种方式效率较低)
(2)根据MP4文件协议直接将H264包封装成MP4格式,通过Mp4v2可以很方便的将H264编码成MP4格式文件
2.1 基于FFmpeg H264封装为MP4
详细的解释参考:https://blog.csdn.net/cfqcfqcfqcfqcfq/article/details/68496213
代码:

#include 

extern "C"
{
#include   
#include   
#include   
#include   
}

using namespace std;

#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avutil.lib")
#pragma comment(lib, "avdevice.lib")
#pragma comment(lib, "avfilter.lib")
#pragma comment(lib, "postproc.lib")
#pragma comment(lib, "swresample.lib")
#pragma comment(lib, "swscale.lib")

int flush_encoder(AVFormatContext *fmt_ctx, unsigned int stream_index);

int main(int argc, char *argv[])
{
    AVFormatContext *pFormatCtx = nullptr;
    AVOutputFormat *fmt = nullptr;
    AVStream *video_st = nullptr;
    AVCodecContext *pCodecCtx = nullptr;
    AVCodec *pCodec = nullptr;

    uint8_t *picture_buf = nullptr;
    AVFrame *picture = nullptr;
    int size;

    //打开视频  
    FILE *in_file = fopen("test.yuv", "rb");
    if (!in_file)
    {
        cout << "can not open file!" << endl;
        return -1;
    }

    int in_w = 1280, in_h = 720;
    int framenum = 500;
    const char* out_file = "src01.mp4";

    //[1] --注册所有ffmpeg组件  
    avcodec_register_all();
    av_register_all();
    //[1]  

    //[2] --初始化AVFormatContext结构体,根据文件名获取到合适的封装格式  
    avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, out_file);
    fmt = pFormatCtx->oformat;
    //[2]  

    //[3] --打开文件  
    if (avio_open(&pFormatCtx->pb, out_file, AVIO_FLAG_READ_WRITE))
    {
        cout << "output file open fail!";
        goto end;
    }
    //[3]  

    //[4] --初始化视频码流  
    video_st = avformat_new_stream(pFormatCtx, 0);
    if (video_st == NULL)
    {
        printf("failed allocating output stram\n");
        goto end;
    }
    video_st->time_base.num = 1;
    video_st->time_base.den = 30;
    //[4]  

    //[5] --编码器Context设置参数  
    pCodecCtx = video_st->codec;
    pCodecCtx->codec_id = fmt->video_codec;
    pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
    pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
    pCodecCtx->width = in_w;
    pCodecCtx->height = in_h;
    pCodecCtx->time_base.num = 1;
    pCodecCtx->time_base.den = 30;
    pCodecCtx->bit_rate = 6126000;
    pCodecCtx->gop_size = 12;

    if (pCodecCtx->codec_id == AV_CODEC_ID_H264)
    {
        pCodecCtx->refs = 3;
        pCodecCtx->qmin = 10;
        pCodecCtx->qmax = 51;
        pCodecCtx->qcompress = 0.6;
    }
    if (pCodecCtx->codec_id == AV_CODEC_ID_MPEG2VIDEO)
        pCodecCtx->max_b_frames = 2;
    if (pCodecCtx->codec_id == AV_CODEC_ID_MPEG1VIDEO)
        pCodecCtx->mb_decision = 2;
    //[5]  

    //[6] --寻找编码器并打开编码器  
    pCodec = avcodec_find_encoder(pCodecCtx->codec_id);
    if (!pCodec)
    {
        cout << "no right encoder!" << endl;
        goto end;
    }
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
    {
        cout << "open encoder fail!" << endl;
        goto end;
    }
    //[6]  

    //输出格式信息  
    av_dump_format(pFormatCtx, 0, out_file, 1);

    //初始化帧  
    picture = av_frame_alloc();
    picture->width = pCodecCtx->width;
    picture->height = pCodecCtx->height;
    picture->format = pCodecCtx->pix_fmt;
    size = avpicture_get_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);
    picture_buf = (uint8_t*)av_malloc(size);
    avpicture_fill((AVPicture*)picture, picture_buf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);

    //[7] --写头文件  
    avformat_write_header(pFormatCtx, NULL);
    //[7]  

    AVPacket pkt; //创建已编码帧  
    int y_size = pCodecCtx->width*pCodecCtx->height;
    av_new_packet(&pkt, size * 3);

    //[8] --循环编码每一帧  
    for (int i = 0; i < framenum; i++)
    {
        //读入YUV  
        if (fread(picture_buf, 1, y_size * 3 / 2, in_file) < 0)
        {
            cout << "read file fail!" << endl;
            goto end;
        }
        else if (feof(in_file))
            break;

        picture->data[0] = picture_buf; //亮度Y  
        picture->data[1] = picture_buf + y_size; //U  
        picture->data[2] = picture_buf + y_size * 5 / 4; //V  
        //AVFrame PTS  
        picture->pts = i;
        int got_picture = 0;

        //编码  
        int ret = avcodec_encode_video2(pCodecCtx, &pkt, picture, &got_picture);
        if (ret < 0)
        {
            cout << "encoder fail!" << endl;
            goto end;
        }

        if (got_picture == 1)
        {
            cout << "encoder success!" << endl;

            // parpare packet for muxing  
            pkt.stream_index = video_st->index;
            av_packet_rescale_ts(&pkt, pCodecCtx->time_base, video_st->time_base);
            pkt.pos = -1;
            ret = av_interleaved_write_frame(pFormatCtx, &pkt);
            av_free_packet(&pkt);
        }
    }
    //[8]  

    //[9] --Flush encoder  
    int ret = flush_encoder(pFormatCtx, 0);
    if (ret < 0)
    {
        cout << "flushing encoder failed!" << endl;
        goto end;
    }
    //[9]  

    //[10] --写文件尾  
    av_write_trailer(pFormatCtx);
    //[10]  

end:
    //释放内存  
    if (video_st)
    {
        avcodec_close(video_st->codec);
        av_free(picture);
        av_free(picture_buf);
    }
    if (pFormatCtx)
    {
        avio_close(pFormatCtx->pb);
        avformat_free_context(pFormatCtx);
    }

    fclose(in_file);

    return 0;
}

int flush_encoder(AVFormatContext *fmt_ctx, unsigned int stream_index)
{
    int ret;
    int got_frame;
    AVPacket enc_pkt;
    if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities &
        CODEC_CAP_DELAY))
        return 0;
    while (1) {
        printf("Flushing stream #%u encoder\n", stream_index);
        enc_pkt.data = NULL;
        enc_pkt.size = 0;
        av_init_packet(&enc_pkt);
        ret = avcodec_encode_video2(fmt_ctx->streams[stream_index]->codec, &enc_pkt,
            NULL, &got_frame);
        av_frame_free(NULL);
        if (ret < 0)
            break;
        if (!got_frame)
        {
            ret = 0; break;
        }
        cout << "success encoder 1 frame" << endl;

        // parpare packet for muxing  
        enc_pkt.stream_index = stream_index;
        av_packet_rescale_ts(&enc_pkt,
            fmt_ctx->streams[stream_index]->codec->time_base,
            fmt_ctx->streams[stream_index]->time_base);
        ret = av_interleaved_write_frame(fmt_ctx, &enc_pkt);
        if (ret < 0)
            break;
    }
    return ret;
}

2.2通过Mp4v2 H264封装为MP4
说明及代码参考:https://blog.csdn.net/firehood_/article/details/8813587
源码下载地址:https://download.csdn.net/download/davebobo/10403571
如果没有特别说明,YUV编码为H264和H264封装为MP4两个过程均可以基于FFmpeg完成实现。
参考文献:
[1]https://blog.csdn.net/leixiaohua1020/article/details/25430425
[2]http://blog.csdn.net/leixiaohua1020/article/details/42078645
[3]https://blog.csdn.net/cfqcfqcfqcfqcfq/article/details/68496213
[4]http://blog.csdn.net/firehood_/article/details/8813587
[5]http://blog.csdn.net/qq_29350001/article/details/73742075

你可能感兴趣的:(FFmpeg)