通过前面对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;
}

注意:这里标示一行图像宽度的时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。}

以上程序,我们通篇使用的视频的宽度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结构体//初始化packetav_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);//初始化packetav_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

ffmpeg学习八:软件生成yuv420p视频并将其编码为H264格式相关推荐

  1. ffmpeg学习(11)音视频文件muxer(2)多输入混流

    在 ffmpeg学习(3)编码.解码的流程介绍 和 ffmpeg学习(9)音视频文件demuxer中介绍了媒体文件的解封装.本文记录Ffmpeg封装格式另一种处理与与demuxer相反方式–视音频复用 ...

  2. ffmpeg学习(13)音视频转码(2)使用filter

    ffmpeg学习(10)音视频文件muxer(1)封装格式转换 中介绍了媒体文件的封装格式转换,ffmpeg学习(11)音视频文件muxer(2)多输入混流 中介绍了音视频的混流,本文介绍基于ffmp ...

  3. FFmpeg学习之二 (yuv视频渲染)

    FFmpeg学习之二 (yuv视频渲染) yuv简介 1.yuv是什么 2.yuv采集方式 3.yuv存储方式 4.yuv格式 yuv视频渲染 1. iOS YUV视频渲染 1.1 IOS利用open ...

  4. ffmpeg学习日记604-指令-将视频格式转为H264格式

    ffmpeg学习日记604-指令-将视频格式转为H264格式 在第四篇中,想要解码视频,缺没有弄清楚怎样的一个数据流,现在又明晰了一点,所谓的h264编解码,并不是直接将视频格式,通过h264编解码为 ...

  5. ffmpeg学习日记612-指令-转换视频格式

    ffmpeg学习日记612-指令-转换视频格式 mkv转mp4 ffmpeg -i LostInTranslation.mkv -codec copy LostInTranslation.mp4 Li ...

  6. ffmpeg学习日记603-指令-获取视频分辨率

    ffmpeg学习日记603-指令-获取视频分辨率 ffmpeg:ffprobe获取视频分辨率,视频宽高,时长等元数据信息 ffprobe -select_streams v -show_entries ...

  7. ffmpeg学习日记605-指令-获取视频的总帧数

    ffmpeg学习日记605-指令-获取视频的总帧数 获取视频的总帧数 ffprobe.exe -v error -count_frames -select_streams v:0 -show_entr ...

  8. ffmpeg学习日记602-指令-转换视频的分辨率

    ffmpeg学习日记602-指令-转换视频的分辨率 指令如下 ffmpeg -i video_1920.mp4 -vf scale=640:360 video_640.mp4 -hide_banner

  9. linux mp4转h264工具,在Ubuntu下批量转换视频为H.264编码的mp4格式

    是目前比较流行的视频编码方式,比起MPEG2在画质大致相同的情况下能再压缩2-4倍,即如果一个DVD视频大小是1GB,用H264编码后能缩小到250MB左右,同时H264视频还能用时髦的浏览器(比如F ...

最新文章

  1. php获取全部post_php post获取所有提交
  2. Win32 窗口篇(1)
  3. Hibernate 实体映射类的状态值自动转换
  4. OpenCV saturate_cast<uchar>函数用法(饱和剔除)(像素限制、溢出滤除、像素设限、防溢出)
  5. Linux串口转远程串口,linux虚拟串口及远程访问
  6. 前端学习(3279):循环 遍历 2
  7. USB连接TF卡 SD卡硬件电路
  8. Intel(R) 处理器产品型号/CPUID标识/签名对照表 (Family Model)
  9. 【转】windows下mongodb安装与使用整理
  10. Python 字符串 循环
  11. 凯恩斯归来,大堡礁畅游记
  12. 【转】我的算法学习之路
  13. android单机斗地主,单机斗地主最新安卓版
  14. mac framework
  15. eaysui datagrid 点击添加单元格
  16. .NET 5.0 正式版发布了!
  17. RS232 485 CAN端口浪涌、脉冲保护电路
  18. VS中调用工业相机Basler
  19. 磁盘空间单位GB与GiB是什么区别?
  20. 大数据清洗1(numpy之Ndarray对象)

热门文章

  1. 都说MES能提高企业生产效率,具体表现在哪些方面呢?
  2. html iframe禁止横向滑动_iframe横向滚动条显示
  3. 「秘籍」JAVA求职面试宝典,轻松搞定面试官!拿下大厂offer
  4. 男人帮经典台词_男人帮经典语录大全
  5. 【English】二月英语总结
  6. 查询图书信息接口 查询图书详情
  7. 数据库表与表的关系(超详细讲解)
  8. 使用Stargate访问K8ssandra,Springboot整合Cassandra
  9. LoadWebOffice实现在线编辑Word
  10. R实战| PCA、tSNE、UMAP三种降维方法在R中的实现