未编码的视频数据放在AVFrame中, 编码的视频数据放在AVPacket
本文不介绍源码级的代码, 如何使用ffmpeg的API将AVFrame转为AVPacket并保存在文件中. 以H264编码格式为例子
假定有一个400*300尺寸的YUV420P格式的input.yuv文件,需要编码成h264
AVFrame跟AVPacket的数据都是需要资源释放的, 不然会内存泄露, 特别是AVPacket数据空间小, 不容易发现
废话不多说直接上代码
#include <iostream> #include <fstream> #include <list> using namespace std; extern "C"{ #include <libavcodec/avcodec.h> #include <libavutil/opt.h> } #define PIX_WIDTH 400 #define PIX_HEIGHT 300 #define BITRATE 400000 void printErr(int errID)//ffmpeg的错误信息如何打印 { char buf[1024] = { 0 }; av_strerror(errID, buf, sizeof(buf) - 1); cerr << buf << endl; } int main(){ ifstream ifs; ifs.open("input.yuv", ios::binary); if(!ifs) { cout << "input.yuv open failed! " << endl; return 0; } ofstream ofs; ofs.open("output.h264", ios::binary); if(!ofs) { cout << "output.h264 open failed! " << endl; return 0; } /**********AVFrame的初始化*********/ //1,申请AVFrame空间 AVFrame *frame = av_frame_alloc(); if(!frame){ cout << "frame_alloc failed!" << endl; return -1; } //2,设定视频帧的信息 frame->width = PIX_WIDTH ; frame->height = PIX_HEIGHT ; frame->format = AV_PIX_FMT_YUV420P; //3,设置好linesize, 编码格式不一样linesize也会不同 frame->linesize[0] = PIX_WIDTH ; //Y frame->linesize[1] = PIX_WIDTH / 2; //U frame->linesize[2] = PIX_WIDTH / 2; //V //4,生成AVFrame的数据空间, 使用默认对齐, 默认对齐是32字节对齐 int ret = av_frame_get_buffer(frame, 0); if(ret != 0){//生成失败 printErr(ret); av_frame_free(&frame); //记得释放空间 return -2; } /**********以上便完成了AVFrame的初始化*********/ /**********编码器的初始化*****************/ //1,找到所需要编码器 AVCodec *avcodec = avcodec_find_encoder(AV_CODEC_ID_H264); if(!avcodec){ cerr << "avcodec_find_encoder failed!" << endl; return -3; } //2,申请编码器上下文 需要手动释放的 AVCodecContext *c = avcodec_alloc_context3(avcodec); if(!c){ cerr << "avcodec_alloc_context3 failed!" << endl; avcodec_free_context(&c); } //3,设定上下文的参数 c->width = PIX_WIDTH ; c->height = PIX_HEIGHT ; //帧时间戳的时间单位 c->time_base = { 1, 25 }; //时间基数 1/25 很重要,跟帧率有关, 每一帧在什么时间播放都有关系 //这里的意思就是将一个单位时间设置为 1/25秒 //在编码时 frame->pts会被赋值, frame->pts * time_base = 播放时间点(秒) c->pix_fmt = AV_PIX_FMT_YUV420P; c->thread_count = 16; //设置编码的线程数, 可以通过调用系统接口获取CPU核心数 //4,设置所需要的编码编码模式, 这里先记录几种编码模式,用的话是用 恒定速率因子 (CRF)编码 /***ABR平均速率编码***/ //c->bit_rate = BITRATE ; /***CQP恒定质量编码***/ //av_opt_set_int(c->priv_data, "qp", 18, 0);//函数内部实现的是赋值 /***恒定比特率***/ //c->rc_min_rate = BITRATE ; //c->rc_max_rate = BITRATE ; //c->rc_buffer_size = BITRATE * 2; //c->bit_rate = BITRATE ; //av_opt_set(c->priv_data, "nal-hrd", "cbr", 0); /***CRF恒定速率因子 最常用的模式***/ ret = av_opt_set_int(c->priv_data, "crf", 23, 0); if(ret != 0){ cout << "crf failed!" << endl; } c->rc_max_rate = BITRATE ; //约束编码 VBV c->rc_buffer_size = BITRATE *2; /****预设编码器参数***可选设置*/ //c->max_b_frames = 0; //设置每个GOP中b帧的最大数量 //ret = av_opt_set(c->priv_data, "preset", "ultrafast", 0);//设置编码速率为最快 //if(ret !=0){ // cout << "preset failed" << endl; //} //ret = av_opt_set(c->priv_data, "tune", "zerolatency", 0);//设置为0延迟, 也就是没有B帧 //if(ret != 0){ // cout << "tune failed" << endl; //} //5, 打开编码上下文 ret = avcodec_open2(c, avcodec, nullptr); if(ret != 0){ printErr(ret); return -4; } //6, 申请AVPacket 也需要手动释放的 每一帧的编码出来的AVPacket都不一样大 AVPacket* avpacket = av_packet_alloc(); if(!avpacket){ cout << "av_packet_alloc failed!" << endl; av_packet_free(&avpacket); return -5; } /**********以上便完成了编码器的初始化*****************/ //循环读取input.yuv的数据 进行编码 for(int i=0; ; ++i){ //1, 读取视频原始帧 ifs.read((char*)frame->data[0], frame->linesize[0]*PIX_HEIGHT );//Y值 ifs.read((char*)frame->data[1], frame->linesize[1]*PIX_HEIGHT /2);//U值 ifs.read((char*)frame->data[2], frame->linesize[2]*PIX_HEIGHT /2);//V值 //2, 设置frame的pts frame->pts = i; //3, 将frame发送到编码线程中进行编码 ret = avcodec_send_frame(c, frame); if(ret !=0){//出错 break;//退出循环,记得释放空间 } //4, 接收压缩帧数据, 一半前几次调用是返回空, 因为编码还未完成 while(ret >= 0){ //每次调用都会重新分配avpacket中的空间 ret = avcodec_receive_packet(c, avpacket); if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF){ //这是正常的,在还没编码完成时会进入AVERROR(EAGAIN) //在编码结束时会进入AVERROR_EOF break;//直接进行下一帧发送 }else if(ret < 0){//失败 printErr(ret); return -6; } //接受编码成功 ofs.write((char*)avpacket->data, avpacket->size); //引用计数减一 不能少否则内存泄露 av_packet_unref(avpacket); } if(ifs.eof()){//读到文件的末尾了 break; } } //5,在文件读完,仍然有一些数据还在编码中, 此时要取出这些数据避免丢帧 list<AVPacket*> list; ret = avcodec_send_frame(c, nullptr);//发送nullptr获取缓冲 while(ret >=0){ AVPacket *pkt = av_packet_alloc(); ret = avcodec_receive_packet(c, pkt); if(ret != 0){//出错了 printErr(ret); av_packet_free(&pkt); break; } list.push_back(pkt); } for(auto i : list){ ofs.write((char*)i->data, i->size); av_packet_free(&i); } ifs.close(); ofs.close(); av_frame_free(&frame); //记得释放空间 avcodec_free_context(&c); //记得释放空间 av_packet_free(&avpacket);//记得释放空间 }