- 打开输入流设备
- 打开编码器
- 采集并准备数据 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 memory
ret = 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
//Y
memcpy(frame->data[0], pkt->data, V_WIDTH * V_HEIGHT);
//V_WIDTH * V_HEIGHT之后是UV
for (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 -> yuv420p
sws_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 -> yuv420p
sws_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
//Y
memcpy(frame->data[0], pkt->data, V_WIDTH * V_HEIGHT);
//V_WIDTH * V_HEIGHT之后是UV
for (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 memory
ret = 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_EOF
if (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 file
const char* yuvout = "video.yuv"; //yuv420p
const char* h264out = "out.h264"; //h264
AVCodecContext* 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);
//创建avframe
frame_yuyv422 = create_frame(V_WIDTH, V_HEIGHT, in_pix_fmt);
frame_yuv420 = create_frame(V_WIDTH, V_HEIGHT, out_pix_fmt);
//设置转换context
if (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;
}
}
//创建编码输出的packet
AVPacket* 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 -> yuv420p
sws_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
//Y
memcpy(frame->data[0], pkt->data, V_WIDTH * V_HEIGHT);
//V_WIDTH * V_HEIGHT之后是UV
for (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,告诉编码器数据结束了,
//让编码器输出所有的数据,防止丢帧。
__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";
}