利用FFmpeg编码器将JPG图片进行H.264编码原理
整体的编码流程
将JPG或BMP编码为YUV
为了将JPG和BMP编码为YUV,可以利用FFmpeg库里的Libswscale
库文件。具体的调用流程如下图:
该库文件主要有三个:
(1)sws_getContext()
:使用参数初始化SwsContext结构体。
/**
* Allocate and return an SwsContext. You need it to perform
* scaling/conversion operations using sws_scale().
*
* @param srcW the width of the source image
* @param srcH the height of the source image
* @param srcFormat the source image format
* @param dstW the width of the destination image
* @param dstH the height of the destination image
* @param dstFormat the destination image format
* @param flags specify which algorithm and options to use for rescaling
* @return a pointer to an allocated context, or NULL in case of error
* @note this function is to be removed after a saner alternative is
* written
*/
struct SwsContext *sws_getContext(int srcW, int srcH, enum AVPixelFormat srcFormat,
int dstW, int dstH, enum AVPixelFormat dstFormat,
int flags, SwsFilter *srcFilter,
SwsFilter *dstFilter, const double *param);
该函数包含以下参数:
srcW
:源图像的宽
srcH
:源图像的高
srcFormat
:源图像的像素格式
dstW
:目标图像的宽
dstH
:目标图像的高
dstFormat
:目标图像的像素格式
flags
:设定图像拉伸使用的算法
成功执行的话返回生成的SwsContext
,否则返回NULL。 (2)
sws_scale()`:转换一帧图像。
/**
* Scale the image slice in srcSlice and put the resulting scaled
* slice in the image in dst. A slice is a sequence of consecutive
* rows in an image.
*
* Slices have to be provided in sequential order, either in
* top-bottom or bottom-top order. If slices are provided in
* non-sequential order the behavior of the function is undefined.
*
* @param c the scaling context previously created with
* sws_getContext()
* @param srcSlice the array containing the pointers to the planes of
* the source slice
* @param srcStride the array containing the strides for each plane of
* the source image
* @param srcSliceY the position in the source image of the slice to
* process, that is the number (counted starting from
* zero) in the image of the first row of the slice
* @param srcSliceH the height of the source slice, that is the number
* of rows in the slice
* @param dst the array containing the pointers to the planes of
* the destination image
* @param dstStride the array containing the strides for each plane of
* the destination image
* @return the height of the output slice
*/
int sws_scale(struct SwsContext *c, const uint8_t *const srcSlice[],
const int srcStride[], int srcSliceY, int srcSliceH,
uint8_t *const dst[], const int dstStride[]);
该函数包含以下参数:
c
:sws_getContext
函数生成的scaling context
指针;
srcSlice[]
:源图像信息每个通道数据指针
srcStride[]
:源图像的每个通道行字节数
srcSliceY
:源图像上处理区域的起始位置
srcSliceH
:源图像上处理区域要处理的行数
如果srcSliceY=0,srcSliceH=height
,表示一次性处理完整个图像。
dst[]
:目标图像信息的每个通道数据指针
dstStride[]
:目标图像信息每个通道行字节数
(3)sws_freeContext()
:释放SwsContext结构体。
做完转换之后,就可以释放这样的结构体了。
利用FFmpeg将YUV格式的数据编码为H.264
以下的编码格式均是将图像格式为YUV的格式编码为H.264的格式
- 利用编码器的两个库编码的流程
调用了FFmpeg中的libavformat和libavcodec两个库完成了视频编码工作,主要流程如下:
av_register_all()
:注册FFmpeg所有编解码器。
avformat_alloc_output_context2()
:初始化输出码流的AVFormatContext。
avio_open()
:打开输出文件。
av_new_stream()
:创建输出码流的AVStream。
avcodec_find_encoder()
:查找编码器。
avcodec_open2()
:打开编码器。
avformat_write_header()
:写文件头(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS)。
avcodec_encode_video2()
:编码一帧视频。即将AVFrame(存储YUV像素数据)编码为AVPacket(存储H.264等格式的码流数据)。
av_write_frame()
:将编码后的视频码流写入文件。
flush_encoder()
:输入的像素数据读取完成后调用此函数。用于输出编码器中剩余的AVPacket。
av_write_trailer()
:写文件尾(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS)。
#include <stdio.h>
#define __STDC_CONSTANT_MACROS
#ifdef _WIN32
//Windows
extern "C"
{
#include "libavutil/opt.h"
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavutil/opt.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#ifdef __cplusplus
};
#endif
#endif
int flush_encoder(AVFormatContext *fmt_ctx,unsigned int stream_index){
int ret;
int got_frame;
AVPacket enc_pkt;
if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities &
AV_CODEC_CAP_DELAY))
return 0;
while (1) {
enc_pkt.data = NULL;
enc_pkt.size = 0;
av_init_packet(&enc_pkt);
ret = avcodec_encode_video2 (fmt_ctx->streams[stream_index]->codec, &enc_pkt,
NULL, &got_frame);
av_frame_free(NULL);
if (ret < 0)
break;
if (!got_frame){
ret=0;
break;
}
printf("Flush Encoder: Succeed to encode 1 frame!\tsize:%5d\n",enc_pkt.size);
/* mux encoded frame */
ret = av_write_frame(fmt_ctx, &enc_pkt);
if (ret < 0)
break;
}
return ret;
}
int main(int argc, char* argv[])
{
AVFormatContext* pFormatCtx;
AVOutputFormat* fmt;
AVStream* video_st;
AVCodecContext* pCodecCtx;
AVCodec* pCodec;
AVPacket pkt;
uint8_t* picture_buf;
AVFrame* pFrame;
int picture_size;
int y_size;
int framecnt=0;
//FILE *in_file = fopen("src01_480x272.yuv", "rb"); //Input raw YUV data
FILE *in_file = fopen("ds_480x272.yuv", "rb"); //Input raw YUV data
int in_w=480,in_h=272; //Input data's width and height
int framenum=100; //Frames to encode
//const char* out_file = "src01.h264"; //Output Filepath
//const char* out_file = "src01.ts";
//const char* out_file = "src01.hevc";
const char* out_file = "ds.h264";
av_register_all();
//Method1.
pFormatCtx = avformat_alloc_context();
//Guess Format
fmt = av_guess_format(NULL, out_file, NULL);
pFormatCtx->oformat = fmt;
//Method 2.
//avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, out_file);
//fmt = pFormatCtx->oformat;
//Open output URL
if (avio_open(&pFormatCtx->pb,out_file, AVIO_FLAG_READ_WRITE) < 0){
printf("Failed to open output file! \n");
return -1;
}
video_st = avformat_new_stream(pFormatCtx, 0);
video_st->time_base.num = 1;
video_st->time_base.den = 25;
if (video_st==NULL){
return -1;
}
//Param that must set
pCodecCtx = video_st->codec;
//pCodecCtx->codec_id =AV_CODEC_ID_HEVC;
pCodecCtx->codec_id = fmt->video_codec;
pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
pCodecCtx->width = in_w;
pCodecCtx->height = in_h;
pCodecCtx->time_base.num = 1;
pCodecCtx->time_base.den = 25;
pCodecCtx->bit_rate = 400000;
pCodecCtx->gop_size=250;
//H264
//pCodecCtx->me_range = 16;
//pCodecCtx->max_qdiff = 4;
//pCodecCtx->qcompress = 0.6;
pCodecCtx->qmin = 10;
pCodecCtx->qmax = 51;
//Optional Param
pCodecCtx->max_b_frames=3;
// Set Option
AVDictionary *param = 0;
//H.264
if(pCodecCtx->codec_id == AV_CODEC_ID_H264) {
av_dict_set(¶m, "preset", "slow", 0);
av_dict_set(¶m, "tune", "zerolatency", 0);
//av_dict_set(¶m, "profile", "main", 0);
}
//H.265
// if(pCodecCtx->codec_id == AV_CODEC_ID_H265){
// av_dict_set(¶m, "preset", "ultrafast", 0);
// av_dict_set(¶m, "tune", "zero-latency", 0);
// }
//Show some Information
av_dump_format(pFormatCtx, 0, out_file, 1);
pCodec = avcodec_find_encoder(pCodecCtx->codec_id);
if (!pCodec){
printf("Can not find encoder! \n");
return -1;
}
if (avcodec_open2(pCodecCtx, pCodec,¶m) < 0){
printf("Failed to open encoder! \n");
return -1;
}
pFrame = av_frame_alloc();
picture_size = avpicture_get_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);
picture_buf = (uint8_t *)av_malloc(picture_size);
avpicture_fill((AVPicture *)pFrame, picture_buf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);
//Write File Header
avformat_write_header(pFormatCtx,NULL);
av_new_packet(&pkt,picture_size);
y_size = pCodecCtx->width * pCodecCtx->height;
//设置相关参数
pFrame->format = pCodecCtx->pix_fmt;
pFrame->width = pCodecCtx->width;
pFrame->height = pCodecCtx->height;
for (int i=0; i<framenum; i++){
//Read raw YUV data
if (fread(picture_buf, 1, y_size*3/2, in_file) <= 0){
printf("Failed to read raw data! \n");
return -1;
}else if(feof(in_file)){
break;
}
pFrame->data[0] = picture_buf; // Y
pFrame->data[1] = picture_buf+ y_size; // U
pFrame->data[2] = picture_buf+ y_size*5/4; // V
//PTS
pFrame->pts=i;
int got_picture=0;
//Encode
int ret = avcodec_encode_video2(pCodecCtx, &pkt,pFrame, &got_picture);
if(ret < 0){
printf("Failed to encode! \n");
return -1;
}
if (got_picture==1){
printf("Succeed to encode frame: %5d\tsize:%5d\n",framecnt,pkt.size);
framecnt++;
pkt.stream_index = video_st->index;
ret = av_write_frame(pFormatCtx, &pkt);
av_free_packet(&pkt);
}
}
//Flush Encoder
int ret = flush_encoder(pFormatCtx,0);
if (ret < 0) {
printf("Flushing encoder failed\n");
return -1;
}
//Write file trailer
av_write_trailer(pFormatCtx);
//Clean
if (video_st){
avcodec_close(video_st->codec);
av_free(pFrame);
av_free(picture_buf);
}
avio_close(pFormatCtx->pb);
avformat_free_context(pFormatCtx);
fclose(in_file);
return 0;
}
- 只利用libavcodec库编码的流程
由于ffmpeg的两个库中libavformat完成封装格式处理,而libavcodec完成编码工作。一个“纯净”的编码器,理论上说只需要使用libavcodec就足够了,并不需要使用libavformat。
流程图中关键函数的作用如下所列:
avcodec_register_all()
:注册所有的编解码器。
avcodec_find_encoder()
:查找编码器。
avcodec_alloc_context3()
:为AVCodecContext分配内存。
avcodec_open2()
:打开编码器。
avcodec_encode_video2()
:编码一帧数据。
两个存储数据的结构体如下所列:
AVFrame
:存储一帧未编码的像素数据。
AVPacket
:存储一帧压缩编码数据。
#include <stdio.h>
#define __STDC_CONSTANT_MACROS
#ifdef _WIN32
//Windows
extern "C"
{
#include "libavutil/opt.h"
#include "libavcodec/avcodec.h"
#include "libavutil/imgutils.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavutil/opt.h>
#include <libavcodec/avcodec.h>
#include <libavutil/imgutils.h>
#ifdef __cplusplus
};
#endif
#endif
//test different codec
#define TEST_H264 1
#define TEST_HEVC 0
int main(int argc, char* argv[])
{
AVCodec *pCodec; //编码器
AVCodecContext *pCodecCtx= NULL;//编码器上下文
int i, ret, got_output;
FILE *fp_in;
FILE *fp_out;
AVFrame *pFrame;//原始数据包
AVPacket pkt;//压缩数据包
int y_size;
int framecnt=0;
char filename_in[]="ds_480x272.yuv";
// char filename_in[]="test_yuv422p.yuv";
#if TEST_HEVC
AVCodecID codec_id=AV_CODEC_ID_HEVC;
char filename_out[]="ds.hevc";
#else
AVCodecID codec_id=AV_CODEC_ID_H264;//编码器的编号
char filename_out[]="test1.h264";
#endif
int in_w=480,in_h=272;
// int in_w=640,in_h=480;
int framenum=100;
avcodec_register_all();//注册所有编码器
pCodec = avcodec_find_encoder(codec_id);//根据编码区ID找到编码器
if (!pCodec) {
printf("Codec not found\n");
return -1;
}
pCodecCtx = avcodec_alloc_context3(pCodec);//为编码器分配内存,创建编码器上下文
if (!pCodecCtx) {
printf("Could not allocate video codec context\n");
return -1;
}
///设置编码器的相关参数
pCodecCtx->bit_rate = 400000;
pCodecCtx->width = in_w;
pCodecCtx->height = in_h;
pCodecCtx->time_base.num=1;
pCodecCtx->time_base.den=25;
pCodecCtx->gop_size = 10;
pCodecCtx->max_b_frames = 1;
pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
if (codec_id == AV_CODEC_ID_H264)
av_opt_set(pCodecCtx->priv_data, "preset", "slow", 0);
/打开编码器///
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
printf("Could not open codec\n");
return -1;
}
///初始化原始数据包//
pFrame = av_frame_alloc();
if (!pFrame) {
printf("Could not allocate video frame\n");
return -1;
}
//设置相关参数
pFrame->format = pCodecCtx->pix_fmt;
pFrame->width = pCodecCtx->width;
pFrame->height = pCodecCtx->height;
//为图像分配缓冲区
ret = av_image_alloc(pFrame->data, pFrame->linesize, pCodecCtx->width, pCodecCtx->height,
pCodecCtx->pix_fmt, 16);
if (ret < 0) {
printf("Could not allocate raw picture buffer\n");
return -1;
}
//输入数据
//Input raw data
fp_in = fopen(filename_in, "rb");
if (!fp_in) {
printf("Could not open %s\n", filename_in);
return -1;
}
//Output bitstream
fp_out = fopen(filename_out, "wb");
if (!fp_out) {
printf("Could not open %s\n", filename_out);
return -1;
}
y_size = pCodecCtx->width * pCodecCtx->height;
//Encode
for (i = 0; i < framenum; i++) {
av_init_packet(&pkt);
pkt.data = NULL; // packet data will be allocated by the encoder
pkt.size = 0;
//Read raw YUV data
if (fread(pFrame->data[0],1,y_size,fp_in)<= 0|| // Y
fread(pFrame->data[1],1,y_size/4,fp_in)<= 0|| // U
fread(pFrame->data[2],1,y_size/4,fp_in)<= 0){ // V
return -1;
}else if(feof(fp_in)){
break;
}
pFrame->pts = i;//设置每帧的顺序标志位,知道每帧的顺序
/* encode the image */
//编码一帧视频。即将AVFrame(存储YUV像素数据)编码为AVPacket(存储H.264等格式的码流数据)
ret = avcodec_encode_video2(pCodecCtx, &pkt, pFrame, &got_output);
if (ret < 0) {
printf("Error encoding frame\n");
return -1;
}
if (got_output) {
printf("Succeed to encode frame: %5d\tsize:%5d\n",framecnt,pkt.size);
framecnt++;
fwrite(pkt.data, 1, pkt.size, fp_out);//把编码好的数据写到文件里
av_free_packet(&pkt);
}
}
//Flush Encoder
for (got_output = 1; got_output; i++) {
ret = avcodec_encode_video2(pCodecCtx, &pkt, NULL, &got_output);
if (ret < 0) {
printf("Error encoding frame\n");
return -1;
}
if (got_output) {
printf("Flush Encoder: Succeed to encode 1 frame!\tsize:%5d\n",pkt.size);
fwrite(pkt.data, 1, pkt.size, fp_out);
av_free_packet(&pkt);
}
}
fclose(fp_out);
avcodec_close(pCodecCtx);
av_free(pCodecCtx);
av_freep(&pFrame->data[0]);
av_frame_free(&pFrame);
return 0;
}
- 人工造数据
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
#include <libavutil/imgutils.h>
// 对每一帧进行编码
static void encode(AVCodecContext *enc_ctx, AVFrame *frame, AVPacket *pkt,
FILE *outfile)
{
int ret;
/* send the frame to the encoder */
if (frame)
printf("Send frame %3"PRId64"\n", frame->pts);
ret = avcodec_send_frame(enc_ctx, frame);
if (ret < 0) {
fprintf(stderr, "Error sending a frame for encoding\n");
exit(1);
}
while (ret >= 0) {
ret = avcodec_receive_packet(enc_ctx, pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
return;
else if (ret < 0) {
fprintf(stderr, "Error during encoding\n");
exit(1);
}
printf("Write packet %3"PRId64" (size=%5d)\n", pkt->pts, pkt->size);
fwrite(pkt->data, 1, pkt->size, outfile);
av_packet_unref(pkt);
}
}
int encode_video(const char *filename, const char *codec_name)
{
//编码器
const AVCodec *codec;
//编码器上下文
AVCodecContext *c= NULL;
//got_output 用于标记一帧是否压缩成功
int i, ret, x, y, got_output;
FILE *f;
//存放解码后的原始帧(未压缩的数据)
AVFrame *frame;
AVPacket pkt;
uint8_t endcode[] = { 0, 0, 1, 0xb7 };
//将所有需要的编解码,多媒体格式,以及网络,都注册要程序里
avcodec_register_all();
/* find the mpeg1video encoder */
//通过编解码名找到对应的编解码器
codec = avcodec_find_encoder_by_name(codec_name);
if (!codec) {
fprintf(stderr, "Codec not found\n");
exit(1);
}
// 根据编码器,创建相对应的编码器上下文
c = avcodec_alloc_context3(codec);
if (!c) {
fprintf(stderr, "Could not allocate video codec context\n");
exit(1);
}
//设置相关参数
/* put sample parameters */
//码率,400kb
c->bit_rate = 400000;
/* resolution must be a multiple of two */
c->width = 352;
c->height = 288;
/* frames per second */
//时间基,每一秒25帧,每一刻度25分之1(时间基根据帧率而变化)
c->time_base = (AVRational){1, 25};
//帧率
c->framerate = (AVRational){25, 1};
/* 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
*/
//多少帧产生一组关键帧
c->gop_size = 10;
//b帧,参考帧
c->max_b_frames = 1;
//编码的原始数据的YUV格式
c->pix_fmt = AV_PIX_FMT_YUV420P;
//如果编码器id 是 h264
if (codec->id == AV_CODEC_ID_H264)
// preset表示采用一个预先设定好的h264参数集,级别是slow,slow表示压缩速度是慢的,慢的可以保证视频质量,用快的会降低视频质量
av_opt_set(c->priv_data, "preset", "slow", 0);
/* open it */
//打开编码器
if (avcodec_open2(c, codec, NULL) < 0) {
fprintf(stderr, "Could not open codec\n");
exit(1);
}
//打开输入文件
f = fopen(filename, "wb");
if (!f) {
fprintf(stderr, "Could not open %s\n", filename);
exit(1);
}
//初始化帧并设置帧的YUV格式和分辨率
frame = av_frame_alloc();
if (!frame) {
fprintf(stderr, "Could not allocate video frame\n");
exit(1);
}
frame->format = c->pix_fmt;
frame->width = c->width;
frame->height = c->height;
//为音频或视频数据分配新的缓冲区
ret = av_frame_get_buffer(frame, 32);
if (ret < 0) {
fprintf(stderr, "Could not allocate the video frame data\n");
exit(1);
}
/* encode 1 second of video */
// 这里是人工添加数据模拟生成1秒钟(25帧)的视频(真实应用中是从摄像头获取的原始数据,摄像头拿到数据后会传给编码器,然后编码器进行编码形成一帧帧数据。)
for (i = 0; i < 25; i++) {
//初始化packet
av_init_packet(&pkt);
pkt.data = NULL; // packet data will be allocated by the encoder
pkt.size = 0;
// 强制输出写入文件
fflush(stdout);
/* make sure the frame data is writable */
//确保帧被写入
ret = av_frame_make_writable(frame);
if (ret < 0)
exit(1);
// 下面2个循环是人工往frame里面添的数据
/* prepare a dummy image */
/* Y */
for (y = 0; y < c->height; y++) {
for (x = 0; x < c->width; x++) {
frame->data[0][y * frame->linesize[0] + x] = x + y + i * 3;
}
}
/* Cb and Cr */
for (y = 0; y < c->height/2; y++) {
for (x = 0; x < c->width/2; x++) {
frame->data[1][y * frame->linesize[1] + x] = 128 + y + i * 2;
frame->data[2][y * frame->linesize[2] + x] = 64 + x + i * 5;
}
}
frame->pts = i;
/* encode the image */
//开始编码
//c : 编码器上下文
//&pkt : 输出压缩后的数据
//frame :输入未压缩数据
//&got_output :判断是否压缩成功
/* send the frame to the encoder */
// 进行编码压缩
encode(c,frame,&pkt,f);
}
// 进行编码压缩
encode(c,frame,&pkt,f);
/* add sequence end code to have a real MPEG file */
fwrite(endcode, 1, sizeof(endcode), f);
fclose(f);
avcodec_free_context(&c);
av_frame_free(&frame);
return 0;
}