ffmpeg将yuv数据编码为H264格式数据

1、yuv->H264经过编码后可以明显缩小视频文件的体积,例如我们经常看到的MP4文件其实就是由H264格式的视频文件和aac音频格式文件打包而成。

2、整个编码流程:

网上的一个关于AVFormatContext结构体的一张图,比较直观:

3、编码实现:

extern "C" {
 
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavfilter/avfilter.h"
#include "libavutil/avutil.h"
#include "libavutil/ffversion.h"
#include <libavutil/imgutils.h>
#include "libswresample/swresample.h"
#include "libswscale/swscale.h"
#include "libavdevice/avdevice.h"
#include "libpostproc/postprocess.h"
}

void MainWindow::yuvToh264()
{
    AVFormatContext* pFmtCtx = nullptr;
    AVCodec* pCodec = nullptr;
    AVStream* pVStream = nullptr;
    AVCodecContext* pCodecCtx = nullptr;
    AVFrame* pSrcFrame = nullptr;
    uint8_t* pSrcBuffer = nullptr;
    AVPacket* pkt = av_packet_alloc();
 
    do
    {
        QString strOutFile = "out.h264";
 
        //创建输出码流的上下文并初始化
        if(avformat_alloc_output_context2(&pFmtCtx, nullptr, nullptr, strOutFile.toStdString().c_str()))
        {
            qDebug() << "cannot alloc output file context." << endl;
            break;
        }
 
        //打开输出文件
        if(avio_open(&pFmtCtx->pb, strOutFile.toStdString().c_str(), AVIO_FLAG_READ_WRITE))
        {
            qDebug() << "cannot open output file." << endl;
            break;
        }
 
        //为AVFormatContext上下文添加视频流
        if(nullptr == (pVStream = avformat_new_stream(pFmtCtx, nullptr)))
        {
            qDebug() << "cannot create AVStream." << endl;
            break;
        }
        pVStream->time_base.den = 25;
        pVStream->time_base.num = 1;
 
        //查找编码器
        if(nullptr == (pCodec = const_cast<AVCodec*>(avcodec_find_encoder(pFmtCtx->oformat->video_codec))))
        {
            qDebug() << "connot find AVCodec." << endl;
            break;
        }
 
        //设置视频流的编码器参数
        AVCodecParameters* pCodecParam = pFmtCtx->streams[pVStream->index]->codecpar;
        if(nullptr == pCodecParam)
        {
            qDebug() << "AVCodecParameters is nullptr." << endl;
            break;
        }
        pCodecParam->codec_type = AVMEDIA_TYPE_VIDEO;
        pCodecParam->width = 352;
        pCodecParam->height = 288;
 
        //根据编码器参数设置编码器内容
        if(nullptr == (pCodecCtx = avcodec_alloc_context3(pCodec)))
        {
            qDebug() << "connot create CodecContext." << endl;
            break;
        }
        if(avcodec_parameters_to_context(pCodecCtx, pCodecParam) < 0)
        {
            qDebug() << "set CodecContext failed." << endl;
            break;
        }
 
        //编码器的ID
        pCodecCtx->codec_id      = pFmtCtx->oformat->video_codec;
        //编码器的类型
        pCodecCtx->codec_type    = AVMEDIA_TYPE_VIDEO;
        //像素格式
        pCodecCtx->pix_fmt       = AV_PIX_FMT_YUV420P;
        //编码视频每一帧的宽、高
        pCodecCtx->width         = 352;
        pCodecCtx->height        = 288;
        //根据下面两个参数可以将PTS转化为时间
        pCodecCtx->time_base.num = 1;
        pCodecCtx->time_base.den = 25;
        //平均比特率
        pCodecCtx->bit_rate      = 400000;
        //一组图片的数量
        pCodecCtx->gop_size      = 12;
 
        if (pCodecCtx->codec_id == AV_CODEC_ID_H264) {
            pCodecCtx->qmin      = 10;
            pCodecCtx->qmax      = 51;
            pCodecCtx->qcompress = (float)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;
 
        //打开编码器
        if(avcodec_open2(pCodecCtx, pCodec, nullptr) < 0)
        {
            qDebug() << "open encoder failed." << endl;
            break;
        }
 
        av_dump_format(pFmtCtx,0,strOutFile.toStdString().c_str(),1);
 
        //创建需要编码的一帧(并没有申请内存),并绑定内存,
        if(nullptr == (pSrcFrame = av_frame_alloc()))
        {
            qDebug() << "alloc frame failed." << endl;
            break;
        }
        pSrcFrame->width = 352;
        pSrcFrame->height = 288;
        pSrcFrame->format = pCodecCtx->pix_fmt;
 
        int nSize = av_image_get_buffer_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, 1);
        pSrcBuffer = static_cast<uint8_t*>(av_malloc(static_cast<size_t>(nSize)));
        av_image_fill_arrays(pSrcFrame->data, pSrcFrame->linesize,
                             pSrcBuffer, pCodecCtx->pix_fmt,
                             pCodecCtx->width, pCodecCtx->height, 1);
 
        //写头文件
        avformat_write_header(pFmtCtx, nullptr);
 
        //循环编码每一帧
        int y_Size = pCodecCtx->width * pCodecCtx->height;
        av_new_packet(pkt, (int)(nSize * 3));
 
        FILE *in_file = fopen("D:\\QT\\Project\\ffmpeg01\\Debug\\debug\\akiyo_cif(352_288).yuv", "rb");
        if (!in_file) {
            qDebug() << "cannot open yuv file." << endl;
            break;
        }
 
        for (int i = 0; i < 300; i++) {
 
            if(fread(pSrcBuffer, 1, (unsigned long)(y_Size * 3 / 2), in_file) <= 0)
            {
                qDebug() << "cannot read yuv file." << endl;
            }
            else if(feof(in_file))
            {
                break;
            }
 
            pSrcFrame->data[0] = pSrcBuffer;                  // Y
            pSrcFrame->data[1] = pSrcBuffer + y_Size;          // U
            pSrcFrame->data[2] = pSrcBuffer + y_Size * 5 / 4;  // V
 
            pSrcFrame->pts = i;
 
            if(avcodec_send_frame(pCodecCtx, pSrcFrame) >= 0)
            {
                while(avcodec_receive_packet(pCodecCtx, pkt) >= 0)
                {
                    pkt->stream_index = pVStream->index;
                    av_packet_rescale_ts(pkt, pCodecCtx->time_base, pVStream->time_base);
                    pkt->pos = -1;
 
                    int ret = -1;
                    if((ret = av_interleaved_write_frame(pFmtCtx, pkt)) < 0)
                    {
                        qDebug() << "encode error." << endl;
                    }
                    av_packet_unref(pkt);
                }
            }
        }
 
        //写文件尾
        av_write_trailer(pFmtCtx);
 
        fclose(in_file);
 
    }while(false);
 
    av_packet_free(&pkt);
    av_free(pSrcFrame);
    av_free(pSrcBuffer);
 
    if(pCodecCtx)
    {
        avcodec_close(pCodecCtx);
        avcodec_free_context(&pCodecCtx);
    }
 
    //销毁输出码流上下文
    if(pFmtCtx)
    {
        if(pFmtCtx->pb)
        {
            avio_close(pFmtCtx->pb);
        }
        avformat_free_context(pFmtCtx);
    }
}

4、效果:

(1)、使用专用播放器播放转码前的yuv格式文件:

(2)、使用ffplayer播放转码后的H264文件:

下面附上YUV格式文件下载地址:http://trace.eas.asu.edu/yuv/

网站下载速度太慢,急需的话可以去csdn搜索下载。

原文 ffmpeg将yuv数据编码为H264格式数据_ffmpeg yuv转h264_小米的修行之路的博客-CSDN博客

★文末名片可以免费领取音视频开发学习资料,内容包括(FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音视频学习路线图等等。

见下方!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

猜你喜欢

转载自blog.csdn.net/yinshipin007/article/details/131937684