视频编解码
#ffmpeg/解码#
一、解码
常用数据结构
- AVCodec :编码类型,h264?音频?相关信息
- AVCodecContext:编码器上下文
- AVFrame:解码后的帧
结构体的相关函数
av_frame_alloc/av_frame_free
avcodec_alloc_context3()/avcodec_free_context
:分配上下文
一般解码是按照
- 找到解码器
avcodec_find_decoder
- 打开解码器
avcodec_open2)
- 解码
avcodec_decode_video2
- 得到yuv这些原始像素数据。
代码:解码视频到bmp图片
代码分析:
#include "stdafx.h"
#define __STD_CONSTANT_MACROS
extern "C"
{
#include <libavcodec\avcodec.h>
#include <libavformat\avformat.h>
#include <libswscale\swscale.h>
}
#define INBUF_SIZE 4096
#define WORD uint16_t
#define DWORD uint32_t
#define LONG int32_t
#pragma pack(2)
/*--bmp图片文件头相关--*/
typedef struct tagBITMAPFILEHEADER {
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAP_FILEHEADER,*PBITMAP_FILEHEADER;
/*--bmp图片信息头--*/
typedef struct tagBITMAPINFOHEADER {
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAP_INFOHEADER, *PBITMAP_INFOHEADER;
void saveBMP(struct SwsContext *img_convert_ctx, AVFrame *frame, char *fileName);
static int decode_write_frame(const char *outfileName, AVCodecContext *codecCtx, struct SwsContext *img_convert_ctx, AVFrame *frame, int *frame_count, AVPacket *pkt, int last);
int main(int argc, char **argv) {
int ret;
const char *fileName, *outputFileName; //文件名
AVFormatContext *fmt_ctx = NULL;
const AVCodec *codec;
AVCodecContext *codec_ctx = NULL;
AVStream *stream = NULL;
int stream_index = 0;
int frame_cnt = 0;
AVFrame *frame;
struct SwsContext *img_convert_ctx;
AVPacket pkt; //包
fileName = "src/data/input.mp4";
av_register_all();
/*--打开文件 --*/
ret = avformat_open_input(&fmt_ctx, fileName, NULL, NULL);
if (ret < 0) {
fprintf(stderr, "cant open file %s\n", fileName);
return 1;
}
/*--找流信息--*/
ret = avformat_find_stream_info(fmt_ctx, NULL);
if (ret < 0) {
fprintf(stderr, "cant find stream info\n");
return 1;
}
av_dump_format(fmt_ctx, 0, fileName, 0); //打印视频信息
av_init_packet(&pkt);
/*--找到合适的流--*/
ret = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if (ret < 0) {
fprintf(stderr, "cant find %s stream in input file '%s'\n", av_get_media_type_string(AVMEDIA_TYPE_VIDEO), fileName);
return 1;
}
stream_index = ret;
stream = fmt_ctx->streams[stream_index]; //获取流
/*--找到解码器 --*/
codec = avcodec_find_decoder(stream->codecpar->codec_id);
if (!codec) {
fprintf(stderr, "Failed to find %s codec\n", av_get_media_type_string(AVMEDIA_TYPE_VIDEO));
return AVERROR(EINVAL);
}
/*--给编码器上下文分配空间--*/
codec_ctx = avcodec_alloc_context3(NULL);
if (!codec_ctx) {
fprintf(stderr, "could not allocate video codec context\n");
return 1;
}
/*--拷贝视频流参数到codec上下文里面*/
if ((ret = avcodec_parameters_to_context(codec_ctx, stream->codecpar))<0) {
fprintf(stderr, "Failed to copy %s codec parameters to decoder context\n", av_get_media_type_string(AVMEDIA_TYPE_VIDEO));
return 1;
}
/*--打开编码器 --*/
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
fprintf(stderr, "could not open codec\n");
return 1;
}
/*--获取格式转换上下文--*/
img_convert_ctx = sws_getContext(codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt, codec_ctx->width, codec_ctx->height, AV_PIX_FMT_RGB24, SWS_BICUBIC, NULL, NULL, NULL);
if (img_convert_ctx == NULL) {
fprintf(stderr, "could not get sws context\n");
return 1;
}
frame = av_frame_alloc();
if(!frame) {
fprintf(stderr, "could not allocate video frame\n");
return 1;
}
/*--循环读取包数据--*/
while (av_read_frame(fmt_ctx, &pkt) >= 0) {
if (pkt.stream_index == stream_index) {
if ((decode_write_frame(fileName, codec_ctx, img_convert_ctx, frame, &frame_cnt, &pkt, 0) < 0)) {
return 1;
}
}
av_packet_unref(&pkt); //释放下
}
/*--一些编码器,比如mpeg,利用隐藏的一帧来传输I P帧,为了获取视频最后的一帧,需要做如下步骤--*/
pkt.data = NULL;
pkt.size = 0;
decode_write_frame(outputFileName, codec_ctx, img_convert_ctx, frame, &frame_cnt, &pkt, 1);
avformat_close_input(&fmt_ctx);
sws_freeContext(img_convert_ctx);
avcodec_free_context(&codec_ctx);
av_frame_free(&frame);
printf("Press endter to quit\n");
getchar();
return 0;
}
/*--解析一包,读取帧,写入图片文件--*/
static int decode_write_frame(const char *outfileName, AVCodecContext *codecCtx, struct SwsContext *img_convert_ctx, AVFrame *frame, int *frame_count, AVPacket *pkt, int last) {
int len, got_frame;
char buf[1024];
len = avcodec_decode_video2(codecCtx, frame, &got_frame, pkt);//解码一包
if(len <0) {
fprintf(stderr, "deocde video err\n");
return -1;
}
if (got_frame) {
fprintf(stdout, "saving %s frame %3d\n", last ? "last" : "", *frame_count);
fflush(stdout); //刷新输出
snprintf(buf, sizeof(buf), "%s-%d.bmp", outfileName, *frame_count); //文件名
saveBMP(img_convert_ctx, frame, buf); //把一帧数据变成rbg数据来进行处理
(*frame_count)++;
}
if (pkt->data) {
//把数据包位移
pkt->size -= len;
pkt->data += len;
}
return 0;
}
void saveBMP(struct SwsContext *img_convert_ctx, AVFrame *frame, char *fileName) {
int w = frame->width;
int h = frame->height;
int numBytes = avpicture_get_size(AV_PIX_FMT_BGR24, w, h);//获取一张图的大小
uint8_t *buffer = (uint8_t*)av_malloc(numBytes * sizeof(uint8_t)); //获取数组大小
AVFrame *pFrameRGB = av_frame_alloc(); //f分配一个帧
avpicture_fill((AVPicture *)pFrameRGB, buffer, AV_PIX_FMT_BGR24, w, h); //把数据填充到帧里面
sws_scale(img_convert_ctx, frame->data, frame->linesize, 0, h, pFrameRGB->data, pFrameRGB->linesize); //把传进来的frame转换到新建的frame里面
/*--构建bmp图片文件信息头--*/
BITMAP_INFOHEADER header;
header.biSize = sizeof(BITMAP_INFOHEADER);
header.biWidth = w;
header.biHeight = h*(-1);
header.biBitCount = 24;
header.biCompression = 0;
header.biSizeImage = 0;
header.biClrImportant = 0;
header.biClrUsed = 0;
header.biXPelsPerMeter = 0;
header.biYPelsPerMeter = 0;
header.biPlanes = 1;
/*--构造文件头--*/
BITMAP_FILEHEADER bmpFileHeader = {
0, };
DWORD dwTotalWriten = 0;
DWORD dwWriten;
bmpFileHeader.bfType = 0x4d42; //ASC码为“BM"
bmpFileHeader.bfSize = sizeof(BITMAP_FILEHEADER) + sizeof(BITMAP_INFOHEADER) + numBytes;
bmpFileHeader.bfOffBits = sizeof(BITMAP_FILEHEADER) + sizeof(BITMAP_INFOHEADER);
FILE *fd = fopen(fileName, "wb");
fwrite(&bmpFileHeader, sizeof(BITMAP_FILEHEADER), 1, fd); //先写入文件头
fwrite(&header, sizeof(BITMAP_INFOHEADER) + sizeof(BITMAP_INFOHEADER),1,fd); //再写入信息头
fwrite(pFrameRGB->data[0], 1, numBytes, fd); //再写入图片数据
fclose(fd);
/*--释放资源--*/
av_freep(&pFrameRGB[0]);
av_free(pFrameRGB);
}
二、编码
- 查找编码器
avcodec_find_encoder_by_name
- 设置编码参数,打开编码器
avcodec_open2
- 编码
avcodec_encode_video2
代码:编码视频
代码的思路是:查找一个解码器,然后分配编码器上下文,设置编码器参数,打开编码器,然后自己创建帧,把这些帧编码到到包里,把这个包的数据写入到文件,再在文件尾部加上结束码。
#include "stdafx.h"
#define __STDC_CONSTANT_MACROS
extern "C"
{
#include <libavutil\log.h>
#include <libavcodec\avcodec.h>
#include <libavutil\opt.h>
#include <libavutil\imgutils.h>
}
int main(int argc, char *argv[]) {
const char *fileName, *codec_name;
AVCodecContext *codec_ctx = NULL;
AVCodec *codec = NULL;
AVFrame *frame;
AVPacket pkt;
uint8_t endcode[] = {
0,0,1,0xb7 };
FILE *fd;
int ret = 0;
int i = 0;
int gotpacket=0;
fileName = "src/data/output.mp4"; //获取文件名
codec_name = "libx264"; //获取编码器名
avcodec_register_all();
/*--查找编码器名--*/
codec = avcodec_find_encoder_by_name(codec_name);
if (!codec) {
fprintf(stderr, "Codec not found\n");
return 0;
}
/*--获取编码器上下文--*/
codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {
fprintf(stderr, "Codec_ctx not found\n");
return 0;
}
/*--设置编码器参数--*/
codec_ctx->bit_rate = 400000;
codec_ctx->width = 720;
codec_ctx->height= 360;
AVRational timebase_ration = {
1,25 };
AVRational framerate_ration = {
25,1 };
codec_ctx->time_base = timebase_ration; //时间戳
codec_ctx->framerate = framerate_ration; //帧率
codec_ctx->gop_size = 10; //多少帧产生一个关键帧/一组帧
codec_ctx->max_b_frames = 1; //b帧的最大数量
codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
if (codec->id == AV_CODEC_ID_H264) //设置压缩速度为慢
av_opt_set(codec_ctx->priv_data, "preset", "slow", 0);
/*--打开编码器--*/
if (avcodec_open2(codec_ctx, codec, NULL)) {
fprintf(stderr, "cant open codec\n");
return 0;
}
/*--打开文件--*/
fd = fopen(fileName, "wb");
if(!fd) {
fprintf(stderr, "cant open codec\n");
return 0;
}
frame = av_frame_alloc(); //给帧分配内存
if(!frame) {
fprintf(stderr, "cant alloc frame\n");
return 0;
}
frame->format = codec_ctx->pix_fmt; //给frame设置格式和大小
frame->width = codec_ctx->width;
frame->height= codec_ctx->height;
ret = av_frame_get_buffer(frame, 32); //给frame设置buffer,32位对齐
if(ret <0 ) {
fprintf(stderr, "cant allocate the video frame data\n");
return 0;
}
/*--实验:编1s的视频--*/
for (i = 0; i < 25; i++) {
av_init_packet(&pkt);
pkt.data = NULL;
pkt.size = 0;
fflush(stdout);
ret = av_frame_make_writable(frame);
if (ret < 0) {
return 0;
}
/*--这里的frame数据是自己造的数据,这里的数据可以替换成其他原始像素数据--*/
for (int y = 0; y < codec_ctx->height; y++) {
for (int x = 0; x < codec_ctx->width; x++) {
frame->data[0][y*frame->linesize[0] + x] = x + y + i * 3;
}
}
for (int y = 0; y < codec_ctx->height/2; y++) {
for (int x = 0; x < codec_ctx->width/2; x++) {
frame->data[1][y*frame->linesize[1] + x] = x + y + i * 3;
frame->data[2][y*frame->linesize[2] + x] = x + y + i * 3;
}
}
frame->pts = i; //设置pts
/*--开始编码视频--*/
ret = avcodec_encode_video2(codec_ctx, &pkt, frame, &gotpacket); //frame压缩到包里
if (ret < 0) {
fprintf(stderr, "cant encode video2\n");
return 0;
}
if (gotpacket) {
printf("packet size is %5d", pkt.size);
fwrite(pkt.data, 1, pkt.size, fd); //数据写入文件
av_packet_unref(&pkt);
}
}
//创建一个空白帧
for (gotpacket = 1; gotpacket; i++) {
fflush(stdout);
ret = avcodec_encode_video2(codec_ctx, &pkt, NULL, &gotpacket); //把空白数据压缩到包里
if (ret < 0) {
fprintf(stderr, "Error encoding frame\n");
exit(1);
}
if (gotpacket) {
printf("Write frame %3d (size=%5d)\n", i, pkt.size);
fwrite(pkt.data, 1, pkt.size, fd);
av_packet_unref(&pkt);
}
}
fwrite(endcode, 1, sizeof(endcode), fd); //把尾部数据写入文件
/*--内存释放--*/
fclose(fd);
avcodec_free_context(&codec_ctx);
av_frame_free(&frame);
return 0;
}