FFmpeg开发实战(六):使用 FFmpeg 将YUV数据编码为视频文件

时间:2019-03-10 12:44:28   收藏:0   阅读:1094

本文中实现的一个小功能是把一个YUV原始视频数据(时间序列图像)经过h264编码为视频码流,然后在使用mp4封装格式封装。

编码&封装的流程图如下:

 

技术分享图片

使用ffmpeg编码流程:

1、首先使用av_register_all()函数注册所有的编码器和复用器(理解为格式封装器)。该步骤必须放在所有ffmpeg代码前第一个执行

2、avformat_alloc_output_context2():初始化包含有输出码流(AVStream)和解复用器(AVInputFormat)的AVFormatContext

3、avio_open( )打开输出文件

4、av_new_stream() 创建视频码流 该函数生成一个空AVstream 该结构存放编码后的视频码流 。视频码流被拆分为AVPacket新式保存在AVStream中。

5、设置编码器信息,该步骤主要是为AVCodecContext(从AVStream->codec 获取指针)结构体设置一些参数,包括codec_id、codec_type、width、height、pix_fmt .....  根据编码器的不同,还要额外设置一些参数(如 h264 要设置qmax、qmin、qcompress参数才能正常使用h264编码)

6、查找并打开编码器,根据前一步设置的编码器参数信息,来查找初始化一个编码其,并将其打开。用到函数为av_fine_encoder()和av_open2()。

7、写头文件  avformat_write_header()。这一步主要是将封装格式的信息写入文件头部位置。

8、编码帧。用到的函数 avcodec_encode_video2() 将AVFrame编码为AVPacket

9、在写入文件之前 还需要做一件事情就是设置AVPacket一些信息。这些信息关乎最后封装格式能否被正确读取。后面回详细讲述该部分内容

10、编码帧写入文件 av_write_frame()

11、flush_encoder():输入的像素数据读取完成后调用此函数。用于输出编码器中剩余的AVPacket。

12、av_write_trailer():写文件尾(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS)。

源码:

#include "pch.h"
#include <iostream>

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

using namespace std;

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("111.yuv", "rb");
    if (!in_file)
    {
        cout << "can not open file!" << endl;
        return -1;
    }

    //352x288
    int in_w = 352, in_h = 288;
    int framenum = 50;
    const char* out_file = "111.mp4";

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

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

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

    //[4] --初始化视频码流
    video_st = avformat_new_stream(pFormatCtx, 0);
    if (video_st == NULL)
    {
        printf("failed allocating output stram\n");
        return -1;
    }
    video_st->time_base.num = 1;
    video_st->time_base.den = 25;
    //[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 = 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 = 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;
        return -1;
    }
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
    {
        cout << "open encoder fail!" << endl;
        return -1;
    }
    //[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;
            return -1;
        }
        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;
            return -1;
        }

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

其实就是一个编码+封装的流程。

目前此源码编码封装后的mp4只能在VLC及部分播放器下进行播放,Window Media Player是无法播放的。Mp4info也无法打开,应该是还存在一些问题,后续会进行优化。

 

本文参考文章:https://blog.csdn.net/cfqcfqcfqcfqcfq/article/details/68496213

 

原文:https://www.cnblogs.com/renhui/p/10504851.html

评论(0
© 2014 bubuko.com 版权所有 - 联系我们:wmxa8@hotmail.com
打开技术之扣,分享程序人生!