1. 程式人生 > >YUV編碼為H264 H264封裝為MP4

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 <stdio.h>

#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 <libavutil/opt.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h> #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(&param, "preset", "slow", 0); av_dict_set(&param, "tune", "zerolatency", 0); //av_dict_set(&param, "profile", "main", 0); } //H.265 if(pCodecCtx->codec_id == AV_CODEC_ID_H265){ av_dict_set(&param, "preset", "ultrafast", 0); av_dict_set(&param, "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,&param) < 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<framenum; 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 <stdio.h>  
#include <stdlib.h>  

#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;i<frame_num;i++){  
                   switch(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 <iostream>

extern "C"
{
#include <libavcodec/avcodec.h>  
#include <libavformat/avformat.h>  
#include <libavutil/avutil.h>  
#include <libswscale/swscale.h>  
}

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;
}