简单的基于FFmpeg的视频解码器

/**
 * 最简单的基于FFmpeg的视频解码器
 * Simplest FFmpeg Decoder
 *
 *
 * 本程序实现了视频文件解码为YUV数据。它使用了libavcodec和
 * libavformat。是最简单的FFmpeg视频解码方面的教程。
 * 通过学习本例子可以了解FFmpeg的解码流程。
 * This software is a simplest decoder based on FFmpeg.
 * It decodes video to YUV pixel data.
 * It uses libavcodec and libavformat.
 * Suitable for beginner of FFmpeg.
 *
 */

#include <stdio.h>
#include "iostream"

using namespace std;

#define __STDC_CONSTANT_MACROS

#ifdef _WIN32
//Windows
extern "C"
{
    
    
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
    
    
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#ifdef __cplusplus
};
#endif
#endif



int main(int argc, char* argv[])
{
    
    
	AVFormatContext* pFormatCtx;//需要avformat_alloc_context();初始化
	int				i, videoindex;
	AVCodecContext* pCodecCtx = NULL;
	AVCodec* pCodec;
	AVFrame* pFrame, * pFrameYUV;//需要av_frame_alloc();初始化
	unsigned char* out_buffer;
	AVPacket* packet;
	int y_size;
	int ret, got_picture;

	struct SwsContext* img_convert_ctx; //图像转换数据结构
	//sws_getContext() 负责初始化
	//sws_scale()主体,转换视频长宽高
	//sws_freeContext()释放

	char filepath[] = "test.mp4";

	FILE* fp_yuv = fopen("output2.yuv", "wb+");


	avformat_network_init();//加载socket库以及网络加密协议相关的库,为后续使用网络相关提供支持 
	pFormatCtx = avformat_alloc_context();

	if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0) {
    
    
		printf("Couldn't open input stream.\n");
		return -1;
	}

	if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
    
    
		//创建视频流,使用复用器解码得到码流,初始化AVStream
		printf("Couldn't find stream information.\n");
		return -1;
	}
	videoindex = -1;
	for (i = 0; i < pFormatCtx->nb_streams; i++)//nb_streams是输入视频的AVStream 个数
		if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
    
    //切换到视频流

			videoindex = i;
			break;
		}

	if (videoindex == -1) {
    
    
		printf("Didn't find a video stream.\n");
		return -1;//如果没有找到视频流就退出
	}
	/****************************************************/

	/****************************************************/
	pCodecCtx = pFormatCtx->streams[videoindex]->codec;
	//将AVFormatContext指定的解码器让avcodec_find_decoder()去查找
	pCodec = avcodec_find_decoder(pCodecCtx->codec_id);//查找解码器id
	if (pCodec == NULL) {
    
    
		printf("Codec not found.\n");
		return -1;
	}
	if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
    
    
		printf("Could not open codec.\n");
		return -1;
	}
	/****************************************************/
	pFrame = av_frame_alloc();//注册AvFrame
	pFrameYUV = av_frame_alloc();

	//重点说明一个参数align:此参数是设定内存对齐的对齐数,也就是按多大的字节进行内存对齐。比如设置为1,表示按1字节对齐,
	//那么得到的结果就是与实际的内存大小一样。再比如设置为4,表示按4字节对齐。也就是内存的起始地址必须是4的整倍数。
	out_buffer = (unsigned char*)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1));
	//申请解码堆内存,计算一帧需要的空间
	//有好几个参数, 第一第二个参数就是一个frame对象里的两个成员,dst_data[4]是一个指针数组
	//对申请的内存进行格式化瓜分
	av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, out_buffer,
		AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);

	packet = (AVPacket*)av_malloc(sizeof(AVPacket));//分配一个AVPacket包的内存
	//Output Info-----------------------------
	printf("--------------- File Information ----------------\n");
	av_dump_format(pFormatCtx, 0, filepath, 0);// 打印关于输入或输出格式的详细信息,例如持续时间,比特率,流,容器,程序,元数据,边数据,编解码器和时基。
	printf("-------------------------------------------------\n");

	img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
		1280, 720, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);

	while (av_read_frame(pFormatCtx, packet) >= 0) {
    
    //拆包

		if (packet->stream_index == videoindex) {
    
    

			ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);

			if (ret < 0) {
    
    
				printf("Decode Error.\n");
				return -1;
			}
			if (got_picture) {
    
    
				printf("  pts is %d \n", pFrame->pts);
				// 每帧格式类型转换
				sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
					pFrameYUV->data, pFrameYUV->linesize);
				y_size = pCodecCtx->width * pCodecCtx->height;

				int	y_size2 = pFrameYUV->linesize[2];
				// 根据YUV420格式,Y U V 比例 4 : 1 : 1
				fwrite(pFrameYUV->data[0], 1, y_size, fp_yuv);    //Y 
				fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_yuv);  //U
				fwrite(pFrameYUV->data[2], 1, y_size / 4, fp_yuv);  //V
				printf("Succeed to decode 1 frame!\n");
				printf("y_size2 = %d,y_size = %d\n", y_size2, y_size);
			}
		}

		av_free_packet(packet);
	}

	/*
		//flush decoder
		//FIX: Flush Frames remained in Codec
	while (1) {
		ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
		if (ret < 0)
			break;
		if (!got_picture)
			break;
		sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
			pFrameYUV->data, pFrameYUV->linesize);//转换长宽高,分辨率

		int y_size = pCodecCtx->width*pCodecCtx->height;
		fwrite(pFrame->data[0], 1, y_size, fp_yuv);    //Y
		fwrite(pFrame->data[1], 1, y_size / 4, fp_yuv);  //U
		fwrite(pFrame->data[2], 1, y_size / 4, fp_yuv);  //V

		printf("Flush Decoder: Succeed to decode 1 frame!\n");
	}*/
	printf("%dX%d", pCodecCtx->width, pCodecCtx->height);
	sws_freeContext(img_convert_ctx);

	fclose(fp_yuv);

	av_frame_free(&pFrameYUV);
	av_frame_free(&pFrame);
	avcodec_close(pCodecCtx);
	avformat_close_input(&pFormatCtx);

	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_51282224/article/details/131040793