ffmpeg视频编解码

视频编解码

#ffmpeg/解码#

一、解码

常用数据结构

  • AVCodec :编码类型,h264?音频?相关信息
  • AVCodecContext:编码器上下文
  • AVFrame:解码后的帧

结构体的相关函数

av_frame_alloc/av_frame_free

avcodec_alloc_context3()/avcodec_free_context:分配上下文

一般解码是按照

  1. 找到解码器avcodec_find_decoder
  2. 打开解码器 avcodec_open2)
  3. 解码avcodec_decode_video2
  4. 得到yuv这些原始像素数据。

代码:解码视频到bmp图片

代码分析:

打开输入文件,获取音视频格式上下文
获取流信息,打印流信息
找到视频流
利用视频流查找编码器
拷贝视频流参数到编码器上下文
打开编码器
获取格式转换上下文
循环读取流数据到包
利用decode_write_frame函数把包数据逐帧写入文件

在这里插入图片描述

#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);

}

二、编码

  1. 查找编码器avcodec_find_encoder_by_name
  2. 设置编码参数,打开编码器avcodec_open2
  3. 编码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;

}

猜你喜欢

转载自blog.csdn.net/qq_26144489/article/details/113829539