FFMPEGはYUV420P形式のデータをH.264にエンコードします

FFMPEG は、YUV420P 形式のデータを H.264 にエンコードすることを学習します


序文

FFMPEGを学習する過程でH264ストリームを取得する必要がありますが、収集するデータは一般的にYUV形式です.ここでは、収集したYUVデータをFFMPEGでH.264ストリームにエンコードする学習プロセスの記録をまとめます.遭遇した問題と経験。


1. コーディングプロセス

H.264 プロセスにエンコードされた YUV420

2. コードの実装

/*---------------------------------------------------------
*	文件名:transfer.c 
*	文件说明:实现YUV数据编码成H.264文件
*	作者:hzg
*	修改记录:
*		1、修改了申请图像内存的对其数值,由原来的32位对齐修改为16位对齐,解决了编码成H.264文件之后播放绿屏乱码问题
*		2、编码第22帧的时候,才开始将包置为1,编码延迟较久。疑似编码选项av_opt_set问题
----------------------------------------------------------*/
#include <math.h>
#include <stdlib.h>
#include <libavutil/opt.h>
#include <libavcodec/avcodec.h>
#include <libavutil/channel_layout.h>
#include <libavutil/common.h>
#include <libavutil/imgutils.h>
#include <libavutil/mathematics.h>
#include <libavutil/samplefmt.h>
#include <libavutil/frame.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>

char * prename = "TestPre";
char * outname = "TestOut";
char * out_r = NULL;
int framenum = 0;

int main(int argc, char **argv)
{
    
    
    AVFrame *frame;
    AVCodec *codec = NULL;
    AVPacket packet;
    AVCodecContext *codecContext;
    int readSize=0;
    int ret=0,getPacket;
    FILE * fileIn,*fileOut;
    int frameCount=0;
    /* register all the codecs */
    av_register_all();

    if(argc!=4){
    
    
        fprintf(stdout,"usage:./test_ffmpeg xxx.yuv width height\n");
        return -1;
    }

    //1.我们需要读一帧一帧的数据,所以需要AVFrame结构
    //读出的一帧数据保存在AVFrame中。
    frame  = av_frame_alloc();
    frame->width = atoi(argv[2]);
    frame->height = atoi(argv[3]);
    fprintf(stdout,"transf para width=%d,height=%d\n",frame->width,frame->height);
    frame->format = AV_PIX_FMT_YUV420P;
	//根据指定的宽,高,和像素格式申请图像内存,最后一个参数表示内存对齐的值,32位对齐4字节,导致352*288时候绿屏,尝试修改成16位对齐
	//修改成16位对齐之后
    av_image_alloc(frame->data,frame->linesize,frame->width,frame->height,frame->format,16);
    fileIn =fopen(argv[1],"r+");

    //2.读出来的数据保存在AVPacket中,因此,我们还需要AVPacket结构体
    //初始化packet
    av_init_packet(&packet);

    //3.读出来的数据,我们需要编码,因此需要编码器
    //下面的函数找到h.264类型的编码器
    /* find the mpeg1 video encoder */
	fprintf(stdout, "find encoder AV_CODEC_ID_H264\n");
    codec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!codec) {
    
    
        fprintf(stderr, "Codec not found\n");
        exit(1);
    }

    //有了编码器,我们还需要编码器的上下文环境,用来控制编码的过程
    codecContext = avcodec_alloc_context3(codec);//分配AVCodecContext实例
    if (!codecContext)
    {
    
    
        fprintf(stderr, "Could not allocate video codec context\n");
        return -1;
    }
	//设置编码器参数控制编码
	/* put sample parameters */
    codecContext->bit_rate = 400000;		//参数为400000的时候,编码出来的busH264文件为254KB
    /* resolution must be a multiple of two */
    codecContext->width = 352;				//输入文件bus是CIF格式,YV12 352*288
    codecContext->height = 288;
    /* frames per second */
    codecContext->time_base = (AVRational){
    
    1,25};
    /* emit one intra frame every ten frames
     * check frame pict_type before passing frame
     * to encoder, if frame->pict_type is AV_PICTURE_TYPE_I
     * then gop_size is ignored and the output of encoder
     * will always be I frame irrespective to gop_size
     */
    codecContext->gop_size = 10;			//每10帧发送一帧I帧
    /*
	* vcodec_encode_video2函数输出的延时仅仅跟max_b_frames的设置有关,  
	* 想进行实时编码,将max_b_frames设置为0便没有编码延时了
	*/
    codecContext->max_b_frames = 1;			//每两个非B帧之间最多一个B帧
    codecContext->pix_fmt = AV_PIX_FMT_YUV420P;  
	/** 
 	* ultrafast,superfast, veryfast, faster, fast, medium 
	* slow, slower, veryslow, placebo. 这是x264编码速度的选项 
	*/  
    //av_opt_set(codecContext->priv_data, "preset", "slow", 0);
	av_opt_set(codecContext->priv_data, "preset", "ultrafast", 0);
    //准备好了编码器和编码器上下文环境,现在可以打开编码器了
    fprintf(stdout, "open encoder AV_CODEC_ID_H264\n");
    if (avcodec_open2(codecContext, codec, NULL) < 0)      //根据编码器上下文打开编码器
    {
    
    
        fprintf(stderr, "Could not open codec\n");
        return -1;
    }

    //4.准备输出文件
    fileOut= fopen("test.h264","w+");
    //下面开始编码
    while(1){
    
    
        //读一帧数据出来
        readSize = fread(frame->data[0],1,frame->linesize[0]*frame->height,fileIn);
		fprintf(stdout,"fread data[0] frame->linesize[0] %d,frame->height %d,readSize %d\n",frame->linesize[0],frame->height,readSize);
        if(readSize == 0){
    
    
            fprintf(stdout,"end of file\n");
            frameCount++;
            break;
        }
        readSize = fread(frame->data[1],1,frame->linesize[1]*frame->height/2,fileIn);//本应该是25344,实际是27648,帧数变成146,实际150
		fprintf(stdout,"fread data[1] frame->linesize[1] %d,readSize %d \n",frame->linesize[1],readSize);
        readSize = fread(frame->data[2],1,frame->linesize[2]*frame->height/2,fileIn);
		fprintf(stdout,"fread data[2] frame->linesize[2] %d readSize %d \n",frame->linesize[2],readSize);
        //初始化packet
        av_init_packet(&packet);
        /* encode the image */
        frame->pts = frameCount;
		//将AVFrame中的像素信息编码为AVPacket中的码流,成功编码一个packet,将getPacket置位1
        ret = avcodec_encode_video2(codecContext, &packet, frame, &getPacket); 
        if (ret < 0) 
        {
    
    
            fprintf(stderr, "Error encoding frame\n");
            return -1;
        }
		fprintf(stdout,"had encode %d frame,and getPacket is %d\n",framenum,getPacket);	//read并encode了22帧,才开始获得一个完整编码帧
        if (getPacket) 
        {
    
    
            frameCount++;
            //获得一个完整的编码帧
            printf("Write frame %3d (size=%5d)\n", frameCount, packet.size);
            fwrite(packet.data, 1,packet.size, fileOut);
            av_packet_unref(&packet);
        }

    }
    /* flush buffer */
    for ( getPacket= 1; getPacket; frameCount++) 
    {
    
    
		fprintf(stdout,"get flush buffer getPacket %d,frameCount %d\n",getPacket,frameCount);
		fflush(stdout);
        frame->pts = frameCount;
        ret = avcodec_encode_video2(codecContext, &packet, NULL, &getPacket);
        if (ret < 0)
        {
    
    
            fprintf(stderr, "Error encoding frame\n");
            return -1;
        }

        if (getPacket) 
        {
    
    
            printf("Write frame %3d (size=%5d)\n", frameCount, packet.size);	//
            fwrite(packet.data, 1, packet.size, fileOut);
            av_packet_unref(&packet);
        }
    } 
    fclose(fileIn);
    fclose(fileOut);
    av_frame_free(&frame);
    avcodec_close(codecContext);
    av_free(codecContext);

    return 0;
}

3.効果を実感する

ビデオを作るのは簡単ではありません。スクリーンショットの効果を見てください
H264 ストリーム再生効果

4.まとめ


Q1: 最初に編集したとき、使用した YUV データ ストリームがエンコード後に緑色であることがわかりました
グリーンスクリーンで遊ぶ
A1 に示すように: ここを確認してください, ソースファイル自体は 150fps ですが, エンコードされたフレームの実際の数は 146fps のみです. 追跡を続けて, UV データを読み取るときに, 実際のソースよりも 16 バイト多いことを見つけます. 続けてこれは、フレーム メモリの適用時に 32 ビット アライメントが使用されるためです.352*288 のソースを使用すると、UV データが 16 バイト多くなり、データの読み取りが異常になり、エンコードが異常になります。メモリ アプリケーション フレームのバイト アライメントを 16 ビット アライメントに変更すると、問題が解決します。
Q2: ログ出力を見ると、22 フレームをエンコードした後、エンコードが成功し、got_packet_ptr が 1 に設定されています。
A2: ここの問題は符号化遅延の問題として定義されています. プリセットパラメータを設定することにより, 圧縮効率と符号化時間のバランスをとることができます. ここでは許容できる最も遅い値を選択することをお勧めします. ここでは, 変更してみてください.低速から超高速までのプリセット 13 フレームをエンコードした後にのみ 1 に設定します。ここではあまり満足できず、後で勉強を続けます。

おすすめ

転載: blog.csdn.net/qq_38750519/article/details/121501688