ffmpeg学习八:软件生成yuv420p视频并将其编码为H264格式
通过前面对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格式相关推荐
- ffmpeg学习(11)音视频文件muxer(2)多输入混流
在 ffmpeg学习(3)编码.解码的流程介绍 和 ffmpeg学习(9)音视频文件demuxer中介绍了媒体文件的解封装.本文记录Ffmpeg封装格式另一种处理与与demuxer相反方式–视音频复用 ...
- ffmpeg学习(13)音视频转码(2)使用filter
ffmpeg学习(10)音视频文件muxer(1)封装格式转换 中介绍了媒体文件的封装格式转换,ffmpeg学习(11)音视频文件muxer(2)多输入混流 中介绍了音视频的混流,本文介绍基于ffmp ...
- FFmpeg学习之二 (yuv视频渲染)
FFmpeg学习之二 (yuv视频渲染) yuv简介 1.yuv是什么 2.yuv采集方式 3.yuv存储方式 4.yuv格式 yuv视频渲染 1. iOS YUV视频渲染 1.1 IOS利用open ...
- ffmpeg学习日记604-指令-将视频格式转为H264格式
ffmpeg学习日记604-指令-将视频格式转为H264格式 在第四篇中,想要解码视频,缺没有弄清楚怎样的一个数据流,现在又明晰了一点,所谓的h264编解码,并不是直接将视频格式,通过h264编解码为 ...
- ffmpeg学习日记612-指令-转换视频格式
ffmpeg学习日记612-指令-转换视频格式 mkv转mp4 ffmpeg -i LostInTranslation.mkv -codec copy LostInTranslation.mp4 Li ...
- ffmpeg学习日记603-指令-获取视频分辨率
ffmpeg学习日记603-指令-获取视频分辨率 ffmpeg:ffprobe获取视频分辨率,视频宽高,时长等元数据信息 ffprobe -select_streams v -show_entries ...
- ffmpeg学习日记605-指令-获取视频的总帧数
ffmpeg学习日记605-指令-获取视频的总帧数 获取视频的总帧数 ffprobe.exe -v error -count_frames -select_streams v:0 -show_entr ...
- ffmpeg学习日记602-指令-转换视频的分辨率
ffmpeg学习日记602-指令-转换视频的分辨率 指令如下 ffmpeg -i video_1920.mp4 -vf scale=640:360 video_640.mp4 -hide_banner
- linux mp4转h264工具,在Ubuntu下批量转换视频为H.264编码的mp4格式
是目前比较流行的视频编码方式,比起MPEG2在画质大致相同的情况下能再压缩2-4倍,即如果一个DVD视频大小是1GB,用H264编码后能缩小到250MB左右,同时H264视频还能用时髦的浏览器(比如F ...
最新文章
- php获取全部post_php post获取所有提交
- Win32 窗口篇(1)
- Hibernate 实体映射类的状态值自动转换
- OpenCV saturate_cast<uchar>函数用法(饱和剔除)(像素限制、溢出滤除、像素设限、防溢出)
- Linux串口转远程串口,linux虚拟串口及远程访问
- 前端学习(3279):循环 遍历 2
- USB连接TF卡 SD卡硬件电路
- Intel(R) 处理器产品型号/CPUID标识/签名对照表 (Family Model)
- 【转】windows下mongodb安装与使用整理
- Python 字符串 循环
- 凯恩斯归来,大堡礁畅游记
- 【转】我的算法学习之路
- android单机斗地主,单机斗地主最新安卓版
- mac framework
- eaysui datagrid 点击添加单元格
- .NET 5.0 正式版发布了!
- RS232 485 CAN端口浪涌、脉冲保护电路
- VS中调用工业相机Basler
- 磁盘空间单位GB与GiB是什么区别?
- 大数据清洗1(numpy之Ndarray对象)