FFmpeg解码流程简介

本文基于雷神的《基于 FFmpeg + SDL 的视频播放器的制作》课程的视频 ,本文就是基于该系列文章的学习后,总结出来的学习经验。如果想细致了解更多方法的使用,可以参考雷神的FFmpeg源代码结构图 - 编码

基于Android端的实现Dmeo:android_FFmpegDecoder

1、FFmpeg的解码流程

FFmpeg解码流程简介_第1张图片
流程分析如下:

  1. av_register_all():注册所有组件,FFmpeg所有的方法调用,第一步都是先注册组件;
  2. avformat_open_input():打开需要解码视频文件路径,注册视频流;
  3. avformat_find_stream_info():获取视频文件基本信息(宽、高和解码器类型等);
  4. avcodec_find_decoder():查找解码器(例如H.264或Mpeg2);
  5. avcodec_open2():打开解码器;
  6. av_read_frame():从输入文件读取一帧压缩数据,循环读取每一帧的数据,直至读不到数据,结束流程;
  7. avcodec_decode_video2():获取到AVPackaet后,解码出该帧的压缩数据(AVFrame),获取视频流的数据后,可根据需要针对该数据处理(显示、存储或重新编码);
  8. avcodec_close():关闭解码器;
  9. avformat_close_input():关闭输入视频文件。

FFmpeg关键的数据结构
FFmpeg解码流程简介_第2张图片

1.AVFormatContext:该视频文件的上下文结构体,在正确读取到视频文件后,就能获取该结构体信息,保存了视频文件的结构体信息:

 - iformat:输入视频的AVInputFormat
 - nb_streams :输入视频的AVStream 个数
 - streams :输入视频的AVStream []数组
 - duration :输入视频的时长(以微秒为单位)
 - bit_rate :输入视频的码率
  1. AVInputFormat:每种封装格式(例如FLV, MKV, MP4, AVI)对应一个该结构体

 - name:封装格式名称
 - long_name:封装格式的长名称
 - extensions:封装格式的扩展名
 - id:封装格式ID
 - 一些封装格式处理的接口函数
  1. AVStream:视频文件中每个视频(音频)流对应一个该结构体
 - id:序号
 - codec:该流对应的AVCodecContext
 - time_base:该流的时基
 - r_frame_rate: 该流的帧率
  1. AVCodecContext:编码器上下文结构体,保存了视频(音频)编解码相关信息

 - codec:编解码器的AVCodec
 - width, height:图像的宽高(只针对视频)
 - pix_fmt:像素格式(只针对视频)
 - sample_rate:采样率(只针对音频)
 - channels:声道数(只针对音频)
 - sample_fmt:采样格式(只针对音频)

  1. AVCodec:每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体
 - name:编解码器名称
 - long_name:编解码器长名称
 - type:编解码器类型
 - id:编解码器ID
 - 一些编解码的接口函数

  1. AVPacket:存储一帧压缩编码数据
 - pts:显示时间戳
 - dts :解码时间戳
 - data :压缩编码数据
 - size :压缩编码数据大小
 - stream_index :所属的AVStream
  1. AVFrame:存储一帧解码后像素(采样)数据
 - data:解码后的图像像素数据(音频采样数据)。
 - linesize:对视频来说是图像中一行像素的大小;对音频来说是整个音频帧的大小。
 - width, height:图像的宽高(只针对视频)。
 - key_frame:是否为关键帧(只针对视频) 。
 - pict_type:帧类型(只针对视频) 。例如I, P, B。

代码实现:

// ffmpegtest.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#define __STDC_CONSTANT_MACROS
//在C++中使用C程序
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
};

int _tmain(int argc, _TCHAR* argv[])
{
	/************************************************************************/
	/* 封装类上下文结构体,统领全局的结构体,保存了视频文件封装格式相关的信息                                                                     */
	/************************************************************************/
	AVFormatContext	*pFormatCtx;
	int				i, videoindex;
	/************************************************************************/
	/* 编码器上下文结构体,保存了视频(音频)对应的结构体							    */
	/************************************************************************/
	AVCodecContext	*pCodecCtx;
	/************************************************************************/
	/* 每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体                                                                     */
	/************************************************************************/
	AVCodec			*pCodec;
	/************************************************************************/
	/* 存储一帧压缩编码数据                                                    */
	/************************************************************************/
	AVPacket *packet;
	/************************************************************************/
	/* 存储一帧解码后像素(采样)数据。                                          */
	/************************************************************************/
	AVFrame	*pFrame,*pFrameYUV;
	uint8_t *out_buffer;
	int y_size;
	int ret, got_picture;
	struct SwsContext *img_convert_ctx;
	//输入文件路径
	char filepath[]="Titanic.ts";

	int frame_cnt;
	/************************************************************************/
	/* 1.注册所有组件                                                         */
	/************************************************************************/
	av_register_all();
	//网络相关
	avformat_network_init();
	pFormatCtx = avformat_alloc_context();
	/************************************************************************/
	/* 2.打开视频文件                                                         */
	/************************************************************************/
	if(avformat_open_input(&pFormatCtx,filepath,NULL,NULL)!=0){
		printf("Couldn't open input stream.\n");
		return -1;
	}
	/************************************************************************/
	/* 3.获取视频文件信息                                                      */
	/************************************************************************/
	if(avformat_find_stream_info(pFormatCtx,NULL)<0){
		printf("Couldn't find stream information.\n");
		return -1;
	}
	//获取video的角标,方便streams取出视频流
	videoindex=-1;
	for(i=0; inb_streams; i++) 
		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;
	}
	//编写 写入文件  fprintf:打印 写入文件中 记得关闭流
	//FILE *fp = fopen("info.txt","wb+");
	//fprintf(fp,"shichang %d\n",pFormatCtx->streams[videoindex]->codec->width);
	//fclose(fp);

	//streams[] //0:视频  1:音频
	pCodecCtx=pFormatCtx->streams[videoindex]->codec;
	/************************************************************************/
	/* 4.查找解码器                                                           */
	/************************************************************************/
	pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
	if(pCodec==NULL){
		printf("Codec not found.\n");
		return -1;
	}
	/************************************************************************/
	/* 5.打开解码器                                                           */
	/************************************************************************/
	if(avcodec_open2(pCodecCtx, pCodec,NULL)<0){
		printf("Could not open codec.\n");
		return -1;
	}
	/*
	 * 在此处添加输出视频信息的代码
	 * 取自于pFormatCtx,使用fprintf()
	 */
	pFrame=av_frame_alloc();
	
	pFrameYUV=av_frame_alloc();
	printf("wirdth %d",pFrameYUV->width);
	out_buffer=(uint8_t *)av_malloc(avpicture_get_size(PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));
	avpicture_fill((AVPicture *)pFrameYUV, out_buffer, PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
	packet=(AVPacket *)av_malloc(sizeof(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, 
		pCodecCtx->width, pCodecCtx->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL); 

	frame_cnt=0;
	FILE * fp_frame = fopen("test.yuv","wb+");
	/************************************************************************/
	/* 6.从文件逐帧读取数据                                                    */
	/************************************************************************/
	while(av_read_frame(pFormatCtx, packet)>=0){
		if(packet->stream_index==videoindex){
				/*
				 * 在此处添加输出H264码流的代码
				 * 取自于packet,使用fwrite()
				 */
		//	fwrite(packet->data,1,packet->size,fp_frame);
			/************************************************************************/
			/* 7、解码一帧压缩数据                                                     */
			/************************************************************************/
			ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
			if(ret < 0){
				printf("Decode Error.\n");
				return -1;
			}
			
			if(got_picture){
				sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, 
					pFrameYUV->data, pFrameYUV->linesize);
				printf("Decoded frame index: %d\n",frame_cnt);
				
				/*
				 * 在此处添加输出YUV的代码
				 * 取自于pFrameYUV,使用fwrite()
				 */
				fwrite(pFrameYUV->data[0],1,pCodecCtx->height*pCodecCtx->width,fp_frame);
				fwrite(pFrameYUV->data[1],1,pCodecCtx->height*pCodecCtx->width/4,fp_frame);
				fwrite(pFrameYUV->data[2],1,pCodecCtx->height*pCodecCtx->width/4,fp_frame);
				frame_cnt++;

			}
		}
		av_free_packet(packet);
	}
	fclose(fp_frame);
	sws_freeContext(img_convert_ctx);

	av_frame_free(&pFrameYUV);
	av_frame_free(&pFrame);
	/************************************************************************/
	/* 8、关闭解码器                                                          */
	/************************************************************************/
	avcodec_close(pCodecCtx);
	/************************************************************************/
	/* 9、关闭输入视频文件                                                     */
	/************************************************************************/
	avformat_close_input(&pFormatCtx);

	return 0;
}


你可能感兴趣的:(音视频)