软件生成yuv420p视频并将其编码为H264格式

43 篇文章 0 订阅

通过前面对ffmpeg中常用的几个api的源码分析,从而对api有了更好的理解。之前已经做过视频的解码了, 今天来尝试视频的编码。ffmpeg已经给我们提供了相应的可供参考的程序:doc/examples/decoding_encoding.c文件就是 解码和编码的例程。仔细阅读它的代码后,我们可以按照自己的理解,写自己的视频编码程序。我们将会把一个yuv420p格式的文件,使用h264编码器进 行编码。

生成yuv视频

yuv图像的格式,可以参考这篇博客: 图文详解YUV420数据格式
如果你读yuv格式有所了解,或者认真阅读了上面的博客,那么阅读下面的代码就非常容易了。它的功能就是生成一个有200帧的yuv视频。

//created by Jinwei Liu
#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 <libavformat/avformat.h>
#include <libswscale/swscale.h>


int main(int argc, char **argv)
{
    AVFrame *frame;
    int i,x,y;
    /* register all the codecs */
    av_register_all();

    if(argc!=3)
        printf("usage:./encodec.bin widht height\n");

    frame  = av_frame_alloc();
    frame->width = atoi(argv[1]);
    frame->height = atoi(argv[2]);
    av_image_alloc(frame->data,frame->linesize,frame->width,frame->height,AV_PIX_FMT_YUV420P,32);
    FILE * file_fd =fopen("soft.yuv","wb+");
    for (i = 0; i < 200; i++) {
        /* prepare a dummy image */
        /* Y */
            for (y = 0; y < frame->height; y++) {
                for (x = 0; x < frame->width; x++) {
                    frame->data[0][y * frame->linesize[0] + x] = (x + y + i * 3)%256;
                }
            }

            /* Cb and Cr */
            for (y = 0; y < frame->height/2; y++) {
                for (x = 0; x < frame->width/2; x++) {
                    frame->data[1][y * frame->linesize[1] + x] = (128 + y + i * 2)%256;
                    frame->data[2][y * frame->linesize[2] + x] = (64 + x + i * 5)%256;
                }
            }

        fwrite(frame->data[0],1,frame->linesize[0]*frame->height,file_fd);
        fwrite(frame->data[1],1,frame->linesize[1]*frame->height/2,file_fd);
        fwrite(frame->data[2],1,frame->linesize[2]*frame->height/2,file_fd);

    }

    fclose(file_fd);
    av_freep(frame->data);
    av_free(frame);

    return 0;
}
  • 2

注意:这里标示一行图像宽度的时linesize,这个时候,播放会有问题。其生成的数据格式如下:

这里写图片描述
因此,我们要注意frame->width和frame->linesize的区别。使用linesize时,生成的数据有一些边界数据。不过这样的生成的yuv格式的数据也是可以播的,要注意使用的一帧图像的宽度位linesize,所以命令如下:
./encodec.bin 300 200
播放生成的yuv视频:
ffplay -f rawvideo -s 320x200 soft.yuv
将其转换为gif格式:
ffmpeg -f rawvideo -s 320x200 -i soft.yuv soft.gif
为什么时320而不是其他置呢?这个我也是通过打印frame->linesize[0]得知的,因为这个值时ffmpeg计算得出的,所以想知道这个值为什么是320的需要阅读相应的源码。

如果我们想得到没有无效数据的yuv视频,那就不要使用frame->linesize,而是直接使用frame->width。像下面这样:

    for (i = 0; i < 200; i++) {
        /* prepare a dummy image */
        /* Y */
        for (y = 0; y < frame->height; y++) {
            for (x = 0; x < frame->width; x++) {
                frame->data[0][y * frame->width + x] = (x + y + i * 3)%255;
            }
        }

        /* Cb and Cr */
        for (y = 0; y < frame->height/2; y++) {
            for (x = 0; x < frame->width/2; x++) {
                frame->data[1][y * frame->width/2 + x] = (y + y + i * 2)%255;
                frame->data[2][y * frame->width/2 + x] = (x + x + i * 5)%255;
            }
        }
        //以上就是软件生成一帧的yuv格式的图像。
        fwrite(frame->data[0],1,frame->width*frame->height,file_fd);
        fwrite(frame->data[1],1,frame->width*frame->height/4,file_fd);
        fwrite(frame->data[2],1,frame->width*frame->height/4,file_fd);
        //写入到文件,我们保存的格式为yuv420p而不是420sp。
    }
  • 1

以上程序,我们通篇使用的视频的宽度frame->width来表示,这样生成的yuv数据没有边界数据。
编译完成后执行可执行文件:
./encodec.bin 300 200
播放生成的yuv视频:
ffplay -f rawvideo -s 300x200 soft.yuv
将其转换为gif格式:
ffmpeg -f rawvideo -s 300x200 -i soft.yuv soft.gif
生成的gif如下:
这里写图片描述

接下来,我们把生成的这个Yuv视频编码成h.264格式(我们使用的yuv是使用frame->linesize来标示视频宽度的)。

yuv编码为h.264

程序如下,代码中已有详细注释。

//created by Jinwei Liu
#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>


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:./encodec.bin 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,"width=%d,height=%d\n",frame->width,frame->height);
    frame->format = AV_PIX_FMT_YUV420P;
    av_image_alloc(frame->data,frame->linesize,frame->width,frame->height,frame->format,32);
    fileIn =fopen(argv[1],"r+");

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


    //3.读出来的数据,我们需要编码,因此需要编码器
    //下面的函数找到h.264类型的编码器
    /* find the mpeg1 video encoder */
    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;
    /* resolution must be a multiple of two */
    codecContext->width = 300;
    codecContext->height = 200;
    /* 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;
    codecContext->max_b_frames = 1;
    codecContext->pix_fmt = AV_PIX_FMT_YUV420P;
    av_opt_set(codecContext->priv_data, "preset", "slow", 0);

    //准备好了编码器和编码器上下文环境,现在可以打开编码器了
    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);
        if(readSize == 0){
            fprintf(stdout,"end of file\n");
            frameCount++;
            break;
        }
        readSize = fread(frame->data[1],1,frame->linesize[1]*frame->height/2,fileIn);
        readSize = fread(frame->data[2],1,frame->linesize[2]*frame->height/2,fileIn);
        //初始化packet
        av_init_packet(&packet);
        /* encode the image */
        frame->pts = frameCount;
        ret = avcodec_encode_video2(codecContext, &packet, frame, &getPacket); //将AVFrame中的像素信息编码为AVPacket中的码流
        if (ret < 0) 
        {
            fprintf(stderr, "Error encoding frame\n");
            return -1;
        }

        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++) 
    {
        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);
        }
    } //for (got_output = 1; got_output; frameIdx++) 

    fclose(fileIn);
    fclose(fileOut);
    av_frame_free(&frame);
    avcodec_close(codecContext);
    av_free(codecContext);

    return 0;
}

 

 

  •  

编译代码后,执行发现,找不好解码器…什么情况呢?google了一下,原来ffmpeg支持h.264编码器需要相应库的支持,我的系统没有安装这个库,因此编译ffmpeg的时候,ffmpeg检测到没有这个库,所以就不会编译h.264相关的模块。
因此,我们需要安装libx264库,执行命令如下:
sudo apt-get install libx264-dev
安装好以后,需要重新编译ffmpeg库。
1.执行
./configure –enable-libx264 –enable-gpl –enable-decoder=h264 –enable-encoder=libx264 –enable-shared –enable-static –disable-yasm –enable-shared –prefix=tmp
2.make
3.make install

这样配置的ffmpeg就开始支持h264编解码器了。

然后重新编译我们代码,编译完成后使用案例如下:
首先进行编码:
./encodech264.bin soft.yuv 300 200
程序会生成test.h264文件,我们可以播放来看:
ffplay test.h264

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个Qt下使用OpenCV的Mat格式图片通过RK3399的硬件编码器RKMPP编码成H.264并通过RTSP推流的程序示例。 ```cpp #include <QCoreApplication> #include <opencv2/opencv.hpp> #include <rockchip/rk_mpi.h> #include <rockchip/rk_mpp.h> #include <stdio.h> int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); // 初始化RKMPP MPP_RET ret = mpp_env_init(); if (ret != MPP_OK) { printf("mpp_env_init failed!\n"); return -1; } // 创建编码器 MppCtx ctx; ret = mpp_create(&ctx, MPP_CTX_ENC); if (ret != MPP_OK) { printf("mpp_create failed!\n"); return -1; } // 配置编码器参数 MppApi *mpi = mpp_get_api(ctx); MppEncPrepCfg prep_cfg; prep_cfg.change = MPP_ENC_PREP_CFG_CHANGE_INPUT | MPP_ENC_PREP_CFG_CHANGE_FORMAT; prep_cfg.width = 640; prep_cfg.height = 480; prep_cfg.format = MPP_FMT_YUV420P; mpi->control(ctx, MPP_ENC_SET_PREP_CFG, &prep_cfg); MppEncCodecCfg codec_cfg; codec_cfg.coding = MPP_VIDEO_CodingAVC; codec_cfg.rc_mode = MPP_ENC_RC_MODE_CBR; codec_cfg.fps_in = 30; codec_cfg.fps_out = 30; codec_cfg.profile = MPP_PROFILE_AVC_MAIN; codec_cfg.level = MPP_LEVEL_UNKNOWN; codec_cfg.target_bitrate = 1000000; codec_cfg.qp_init = 26; codec_cfg.qp_max = 48; codec_cfg.qp_min = 20; mpi->control(ctx, MPP_ENC_SET_CODEC_CFG, &codec_cfg); // 初始化编码器 ret = mpi->init(ctx); if (ret != MPP_OK) { printf("mpi->init failed!\n"); return -1; } // 打开RTSP推流 cv::VideoWriter writer; writer.open("rtsp://localhost:8554/test.sdp", cv::CAP_FFMPEG, cv::VideoWriter::fourcc('H', '2', '6', '4'), 30, cv::Size(640, 480), true); // 加载测试图片 cv::Mat image = cv::imread("test.jpg"); if (image.empty()) { printf("Failed to load image!\n"); return -1; } // 创建输入YUV内存 MppFrame frame; ret = mpi->control(ctx, MPP_ENC_GET_EXTRA_INFO, &frame); if (ret != MPP_OK) { printf("mpi->control failed!\n"); return -1; } frame->width = 640; frame->height = 480; frame->hor_stride = 640; frame->ver_stride = 480; frame->fmt = MPP_FMT_YUV420P; mpp_frame_init(frame); // 创建输出H.264内存 MppBuffer packet_buf; mpp_buffer_get(ctx, &packet_buf, 4 * 1024 * 1024); MppPacket packet; mpp_packet_init(&packet, packet_buf); // 编码YUV图像 for (int i = 0; i < 100; i++) { // 将OpenCV的Mat格式换为YUV格式 cv::Mat yuv_image; cv::cvtColor(image, yuv_image, cv::COLOR_BGR2YUV_I420); // 将YUV图像写入输入YUV内存 uint8_t *buf = (uint8_t *)mpp_buffer_get_ptr(frame->buf); memcpy(buf, yuv_image.data, 640 * 480 * 3 / 2); // 编码YUV图像 ret = mpi->encode_put_frame(ctx, frame, NULL); if (ret != MPP_OK) { printf("mpi->encode_put_frame failed!\n"); return -1; } // 获取编码后的H.264数据 ret = mpi->encode_get_packet(ctx, &packet); if (ret != MPP_OK) { printf("mpi->encode_get_packet failed!\n"); return -1; } // 将H.264数据写入RTSP推流 writer.write(packet->data, packet->length); } // 释放资源 writer.release(); mpp_packet_deinit(&packet); mpp_buffer_put(packet_buf); mpp_frame_deinit(&frame); mpi->reset(ctx); mpi->control(ctx, MPP_ENC_SET_IDR_FRAME, NULL); mpi->destroy(ctx); mpp_env_deinit(); return 0; } ``` 需要注意的几点: 1. 该示例程序使用的是Rockchip的RKMPP编码器。如果使用的是其他厂商的编码器,代码可能会有所不同。 2. 在编码器初始化时,需要设置编码器的参数。例如,设置编码器的分辨率、码率、帧率等。 3. 将OpenCV的Mat格式图像换为YUV格式,并写入输入YUV内存。 4. 在编码器中,将输入YUV内存编码为H.264格式,并将编码后的数据写入输出H.264内存。 5. 将输出H.264内存的数据写入RTSP推流。在这个示例中,使用了OpenCV的VideoWriter类来实现RTSP推流。 6. 在程序结束前,需要释放所有资源。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值