
- 打开输入流设备
- 打开编码器
- 采集并准备数据 AVFrame
- 转换YUYV422到YUV420P(NV12转YUV420P)
-
打开编码器
使用哪个编码器 libx264
GOP设置,码率设置,宽高设置
/*** @brief* @param[in]* @param[in]* @param[out]*/static int open_encoder(int width, int height, AVCodecContext** enc_ctx){int ret = 0;AVCodec* codec = NULL;codec = avcodec_find_encoder_by_name("libx264");if (!codec){printf("Codec libx264 not found\n");exit(1);}*enc_ctx = avcodec_alloc_context3(codec);if (!enc_ctx){printf("Counld out allocate video codec context\n\n");exit(1);}//SPS(*enc_ctx)->profile = FF_PROFILE_H264_HIGH_444;(*enc_ctx)->level = 50; //表示LEVELE是5.0//设置分辨率(*enc_ctx)->width = width;(*enc_ctx)->height = height;//GOP(*enc_ctx)->gop_size = 250;//图像有很多变化的时候可以自动插入一个I帧,即最小的gop size(*enc_ctx)->keyint_min = 25; //option 可选//设置B帧的数量(*enc_ctx)->max_b_frames = 3;//option 可选(*enc_ctx)->has_b_frames = 1;//option 可选//设置参考帧的数量(*enc_ctx)->refcounted_frames = 3; //option 可选//输入的YUV格式(*enc_ctx)->pix_fmt = AV_PIX_FMT_YUV420P;//设置码率(*enc_ctx)->bit_rate = 600000; //600kbps//设置帧率AVRational time_base_t = { 1, 25 };AVRational framerate_t = { 25, 1 };(*enc_ctx)->time_base = time_base_t; //帧与帧之间的间隔是time_base(*enc_ctx)->framerate = framerate_t; //帧率, 每秒25帧ret = avcodec_open2((*enc_ctx), codec, NULL);if (ret < 0){//printf("Counld not open codec: %s\n", av_err2str(ret));printf("Counld not open codec: %d\n", ret);exit(1);}}
采集并准备数据 AVFrame
获取到的av package是个输出,需要把av package数据导入av frame中
/*** @brief xxxx* @param[in] width* @param[in] height* @return AVFrame**/static AVFrame* create_frame(int width, int height, AVPixelFormat pix_fmt){int ret = 0;AVFrame* frame = NULL;frame = av_frame_alloc();if (!frame){printf("Error, No Memory!\n");goto __ERROR;}//设置参数frame->width = width;frame->height = height;frame->format = pix_fmt;//alloc inner memoryret = av_frame_get_buffer(frame, 32); //按32位对齐 【视频必须是32位对齐】if (ret < 0){printf("Error, Failed to alloc buffer for frame!\n");goto __ERROR;}return frame;__ERROR:if (frame){av_frame_free(&frame);}return NULL;}
转换YUYV422到YUV420P(NV12转YUV420P)
自己编写代码转换(NV12 —-> YUV420p)
//YYYYYYYYUVUV NV12//YYYYYYYYUUVV YUV420P//Ymemcpy(frame->data[0], pkt->data, V_WIDTH * V_HEIGHT);//V_WIDTH * V_HEIGHT之后是UVfor (i = 0; i < V_WIDTH * V_HEIGHT / 4; i++){frame->data[1][i] = pkt->data[V_WIDTH * V_HEIGHT + i*2];frame->data[2][i] = pkt->data[V_WIDTH * V_HEIGHT + i*2 + 1];}fwrite(frame->data[0], 1, V_WIDTH * V_HEIGHT, yuvoutfile);fwrite(frame->data[1], 1, V_WIDTH * V_HEIGHT/4, yuvoutfile);fwrite(frame->data[2], 1, V_WIDTH * V_HEIGHT/4, yuvoutfile);
使用sws_scale函数转换
//sws_scale函数转换 yuyv422 -> yuv420p//avpicture_fill((AVPicture*)frame_yuyv422, (unsigned char*)pkt->data, (AVPixelFormat)in_pix_fmt, V_WIDTH, V_HEIGHT); //该函数ffmpeg弃用了av_image_fill_arrays(frame_yuyv422->data, frame_yuyv422->linesize, pkt->data, in_pix_fmt, V_WIDTH, V_HEIGHT, 4);//yuyv422 -> yuv420psws_scale(img_convert_ctx, frame_yuyv422->data, frame_yuyv422->linesize,0, V_HEIGHT, frame_yuv420->data, frame_yuv420->linesize);fwrite(frame_yuv420->data[0], 1, V_WIDTH * V_HEIGHT, yuvoutfile);fwrite(frame_yuv420->data[1], 1, V_WIDTH * V_HEIGHT / 4, yuvoutfile);fwrite(frame_yuv420->data[2], 1, V_WIDTH * V_HEIGHT / 4, yuvoutfile);
H264编码
avcodec_send_frame(enc_ctx, frame_yuv420);
avcodec_receive_packet(enc_ctx, newpkt); ```cpp while ((ret = av_read_frame(fmt_ctx, pkt)) == 0) {
if 1 //sws_scale函数转换 yuyv422 -> yuv420p
//avpicture_fill((AVPicture*)frame_yuyv422, (unsigned char*)pkt->data, (AVPixelFormat)in_pix_fmt, V_WIDTH, V_HEIGHT);av_image_fill_arrays(frame_yuyv422->data, frame_yuyv422->linesize, pkt->data, in_pix_fmt, V_WIDTH, V_HEIGHT, 4);//yuyv422 -> yuv420psws_scale(img_convert_ctx, frame_yuyv422->data, frame_yuyv422->linesize,0, V_HEIGHT, frame_yuv420->data, frame_yuv420->linesize);fwrite(frame_yuv420->data[0], 1, V_WIDTH * V_HEIGHT, yuv_outfile);fwrite(frame_yuv420->data[1], 1, V_WIDTH * V_HEIGHT / 4, yuv_outfile);fwrite(frame_yuv420->data[2], 1, V_WIDTH * V_HEIGHT / 4, yuv_outfile);
else //代码处理将pkt->data里的数据按yuv420p的方式存储
//YYYYYYYYUVUV NV12//YYYYYYYYUUVV YUV420P//Ymemcpy(frame->data[0], pkt->data, V_WIDTH * V_HEIGHT);//V_WIDTH * V_HEIGHT之后是UVfor (i = 0; i < V_WIDTH * V_HEIGHT / 4; i++){frame->data[1][i] = pkt->data[V_WIDTH * V_HEIGHT + i*2];frame->data[2][i] = pkt->data[V_WIDTH * V_HEIGHT + i*2 + 1];}fwrite(frame->data[0], 1, V_WIDTH * V_HEIGHT, yuvoutfile);fwrite(frame->data[1], 1, V_WIDTH * V_HEIGHT/4, yuvoutfile);fwrite(frame->data[2], 1, V_WIDTH * V_HEIGHT/4, yuvoutfile);
endif
frame_yuv420->pts = base++; //告知编码器输入帧与帧之间的关系,否则编出来的数据会花屏encode(enc_ctx, frame_yuv420, newpkt, h264_outfile);}encode(enc_ctx, NULL, newpkt, h264_outfile);//传入NULL的frame,告诉编码器数据结束了,//让编码器输出所有的数据,防止丢帧。
<a name="JrrL5"></a># 完整代码```cpp#include <iostream>using namespace std;extern "C"{#include "libavcodec/avcodec.h"#include "libavutil/avutil.h"#include "libavutil/opt.h"#include "libavutil/channel_layout.h"#include "libavdevice/avdevice.h"#include "libswscale/swscale.h"#include "libswresample/swresample.h"#include "libavutil/samplefmt.h"#include "libavutil/error.h"#include "libavutil/imgutils.h"}#define V_WIDTH 640#define V_HEIGHT 480/*** @brief* @param[in]* @param[in]* @param[out]*/static int open_encoder(int width, int height, AVCodecContext** enc_ctx){int ret = 0;AVCodec* codec = NULL;codec = avcodec_find_encoder_by_name("libx264");if (!codec){printf("Codec libx264 not found\n");exit(1);}*enc_ctx = avcodec_alloc_context3(codec);if (!enc_ctx){printf("Counld out allocate video codec context\n\n");exit(1);}//SPS(*enc_ctx)->profile = FF_PROFILE_H264_HIGH_444;(*enc_ctx)->level = 50; //表示LEVELE是5.0//设置分辨率(*enc_ctx)->width = width;(*enc_ctx)->height = height;//GOP(*enc_ctx)->gop_size = 250;//图像有很多变化的时候可以自动插入一个I帧,即最小的gop size(*enc_ctx)->keyint_min = 25; //option 可选//设置B帧的数量(*enc_ctx)->max_b_frames = 3;//option 可选(*enc_ctx)->has_b_frames = 1;//option 可选//设置参考帧的数量(*enc_ctx)->refcounted_frames = 3; //option 可选//输入的YUV格式(*enc_ctx)->pix_fmt = AV_PIX_FMT_YUV420P;//设置码率(*enc_ctx)->bit_rate = 400000; //600kbps//设置帧率AVRational time_base_t = { 1, 15 };AVRational framerate_t = { 15, 1 };(*enc_ctx)->time_base = time_base_t; //帧与帧之间的间隔是time_base(*enc_ctx)->framerate = framerate_t; //帧率, 每秒30帧ret = avcodec_open2((*enc_ctx), codec, NULL);if (ret < 0){//printf("Counld not open codec: %s\n", av_err2str(ret));printf("Counld not open codec: %d\n", ret);exit(1);}}/*** @brief xxxx* @param[in] width* @param[in] height* @return AVFrame**/static AVFrame* create_frame(int width, int height, AVPixelFormat pix_fmt){int ret = 0;AVFrame* frame = NULL;frame = av_frame_alloc();if (!frame){printf("Error, No Memory!\n");goto __ERROR;}//设置参数frame->width = width;frame->height = height;frame->format = pix_fmt;//alloc inner memoryret = av_frame_get_buffer(frame, 32); //按32位对齐 【视频必须是32位对齐】if (ret < 0){printf("Error, Failed to alloc buffer for frame!\n");goto __ERROR;}return frame;__ERROR:if (frame){av_frame_free(&frame);}return NULL;}void encode(AVCodecContext* enc_ctx, AVFrame* frame, AVPacket* newpkt, FILE* outfile){if (frame)printf("send frame to encoder, pts = %lld\n", frame->pts);//把AVFrame传给编码器,送原始数据给编码器进行编码int ret = avcodec_send_frame(enc_ctx, frame);if (ret < 0){}//从编码器获取编码好的数据//从编码器获取编码是连续输出的,有可能喂frame数据给编码器的是编码器并不输出//有可能一次输出好几个编码好的数据。while (ret >= 0){ret = avcodec_receive_packet(enc_ctx, newpkt);//编码器数据不足时,返回EAGAIN,或者到数据结尾时返回AVERROR_EOFif (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)return;if (ret < 0) {printf("error, failed to encode.\n");exit(1);}//outfile输出h264的编码fwrite(newpkt->data, 1, newpkt->size, outfile);av_packet_unref(newpkt);//减少newpkt的引用计数}}int main(){int base = 0;int ret = 0;AVPacket* pkt = NULL;AVInputFormat* in_format = NULL;AVFormatContext* fmt_ctx = NULL;AVDictionary* options = NULL;AVFrame* frame_yuyv422 = NULL;AVFrame* frame_yuv420 = NULL;FILE* out_file = NULL;char errors[1024] = { 0 };char device_name[256] = "video=Integrated Webcam";//图像转换参数struct SwsContext* img_convert_ctx = NULL;AVPixelFormat in_pix_fmt = AV_PIX_FMT_YUYV422;AVPixelFormat out_pix_fmt = AV_PIX_FMT_YUV420P;static int sws_flags = SWS_BICUBIC; //差值算法,双三次//create fileconst char* yuvout = "video.yuv"; //yuv420pconst char* h264out = "out.h264"; //h264AVCodecContext* enc_ctx = NULL;FILE* yuv_outfile = fopen(yuvout, "wb+");FILE* h264_outfile = fopen(h264out, "wb+");av_register_all();avdevice_register_all();//设置采集in_format = av_find_input_format("dshow");if (in_format == NULL){printf("av_find_input_format error\n");}av_dict_set(&options, "video_size", "640*480", 0);av_dict_set(&options, "framerate", "30", 0);av_dict_set(&options, "pixel_format", "yuyv422", 0);if ((ret = avformat_open_input(&fmt_ctx, device_name, in_format, &options)) != 0){av_strerror(ret, errors, 1024);printf("Failed to open video device, [%s][%d]\n", errors, ret);return -1;}pkt = av_packet_alloc();av_init_packet(pkt);av_dump_format(fmt_ctx, 0, device_name, 0);//打开编码器open_encoder(V_WIDTH, V_HEIGHT, &enc_ctx);//创建avframeframe_yuyv422 = create_frame(V_WIDTH, V_HEIGHT, in_pix_fmt);frame_yuv420 = create_frame(V_WIDTH, V_HEIGHT, out_pix_fmt);//设置转换contextif (img_convert_ctx == NULL){img_convert_ctx = sws_getContext(V_WIDTH, V_HEIGHT,(AVPixelFormat)in_pix_fmt,V_WIDTH,V_HEIGHT,(AVPixelFormat)out_pix_fmt,sws_flags, NULL, NULL, NULL);if (img_convert_ctx == NULL){fprintf(stderr, "Cannot initialize the conversion context\n");return -1;}}//创建编码输出的packetAVPacket* newpkt = av_packet_alloc();if (!newpkt){printf("Error, Failed to alloc avpacket!\n");goto __END;}while ((ret = av_read_frame(fmt_ctx, pkt)) == 0){#if 1 //sws_scale函数转换 yuyv422 -> yuv420p//avpicture_fill((AVPicture*)frame_yuyv422, (unsigned char*)pkt->data, (AVPixelFormat)in_pix_fmt, V_WIDTH, V_HEIGHT);av_image_fill_arrays(frame_yuyv422->data, frame_yuyv422->linesize, pkt->data, in_pix_fmt, V_WIDTH, V_HEIGHT, 4);//yuyv422 -> yuv420psws_scale(img_convert_ctx, frame_yuyv422->data, frame_yuyv422->linesize,0, V_HEIGHT, frame_yuv420->data, frame_yuv420->linesize);fwrite(frame_yuv420->data[0], 1, V_WIDTH * V_HEIGHT, yuv_outfile);fwrite(frame_yuv420->data[1], 1, V_WIDTH * V_HEIGHT / 4, yuv_outfile);fwrite(frame_yuv420->data[2], 1, V_WIDTH * V_HEIGHT / 4, yuv_outfile);#else //代码处理将pkt->data里的数据按yuv420p的方式存储//YYYYYYYYUVUV NV12//YYYYYYYYUUVV YUV420P//Ymemcpy(frame->data[0], pkt->data, V_WIDTH * V_HEIGHT);//V_WIDTH * V_HEIGHT之后是UVfor (i = 0; i < V_WIDTH * V_HEIGHT / 4; i++){frame->data[1][i] = pkt->data[V_WIDTH * V_HEIGHT + i*2];frame->data[2][i] = pkt->data[V_WIDTH * V_HEIGHT + i*2 + 1];}fwrite(frame->data[0], 1, V_WIDTH * V_HEIGHT, yuvoutfile);fwrite(frame->data[1], 1, V_WIDTH * V_HEIGHT/4, yuvoutfile);fwrite(frame->data[2], 1, V_WIDTH * V_HEIGHT/4, yuvoutfile);#endifframe_yuv420->pts = base++; //告知编码器输入帧与帧之间的关系,否则编出来的数据会花屏encode(enc_ctx, frame_yuv420, newpkt, h264_outfile);}encode(enc_ctx, NULL, newpkt, h264_outfile);//传入NULL的frame,告诉编码器数据结束了,//让编码器输出所有的数据,防止丢帧。__END:if (frame_yuyv422){av_frame_free(&frame_yuyv422);frame_yuyv422 = NULL;}if (frame_yuv420){av_frame_free(&frame_yuv420);frame_yuv420 = NULL;}av_packet_free(&pkt);cout << "Hello World!\n";}
