关于《最简单的基于FFMPEG+SDL的视频播放器》记录

一、概述

        熟悉了FFMPEG的命令行工具ffmpeg.exe之后,就开始了对这个库的函数的熟悉过程,按照零基础学习FFMPEG的学习轨迹,就应当实现一个“最简单的视频播放器”来熟悉FFMPEG的工作流程和一些常用的函数。实现播放器的文章请参考最简单的视频播放器,这里仅仅记录一下自己的心得。

二、播放器的解码流程

播放器的解码流程非常重要,有利于我们去读懂代码。流程图我就不自己画了,直接取自作者的文章里的图:


对这个流程图做一些解释:

1.av_register_all

注册所有的文件格式和编解码的库。具体的来说它初始化libavformat库,注册muxer,demuxer和一些协议。libavformat库提供了一个复用/解复用音视频、字幕的框架。它包括多媒体封装格式的多个复用器和解复用器,同样支持访问多媒体源的输入输出协议。

2.avformat_open_input

打开一个输入流并读取该流的头部信息。注意,这个时候解码器还没有打开;打开的流在后面解码工作完成后需要关闭。

3.av_find_stream_info

avformat_open_input只是检测了文件的头部,所以我们还需要av_find_stream_info来读取流的信息。这一步之后将为FormatContext->streams填充上正确的信息。此外在新版的FFMPEG中,该函数已经声明为要废弃的,使用avformat_find_stream_info来代替它。

4.avcodec_find_decoder

根据输入流的信息中的解码器ID,找到合适的解码器来进行解码工作。

5.avcodec_open

打开解码器。这个时候会对解码环境(AVCodecContext)做一些初始化的工作。

6.av_read_frame

读取下一帧数据。注意该数据帧还未解码。如果读取不到,说明已经读取完毕。当读取到后,还需要判断这一帧数据是否是视频类型的数据,如果是才进行解码。

7.avcodec_decode_video2

解码数据帧。

三、代码及注释

注意:这里仅仅对解码流程做了注释,而对于SDL部分并没有做注释。

#include <stdio.h>

//包含库
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "SDL/SDL.h"
};

int main(int argc, char* argv[])
{
	//FFmpeg相关变量
	AVFormatContext	*pFormatCtx;//AVFormatContext主要存储视音频封装格式中包含的信息
	int				i;
	int             videoindex;//视频流所在序号
	AVCodecContext	*pCodecCtx;//AVCodecContext,存储该视频/音频流使用解码方式的相关数据
	AVCodec			*pCodec;//解码器
	AVFrame	*pFrame,*pFrameYUV;//解码后数据
	AVPacket *packet;//解码前数据
	struct SwsContext *img_convert_ctx;
	//SDL
	int screen_w,screen_h;//屏幕宽、高
	SDL_Surface *screen; 
	SDL_VideoInfo *vi;
	SDL_Overlay *bmp; 
	SDL_Rect rect;


	FILE *fp_yuv;
	int ret, got_picture;
	char* filepath = "1.rmvb";

	av_register_all();//初始化libformat库和一些别的工作
	avformat_network_init();//初始化网络组件
	
	pFormatCtx = avformat_alloc_context();
	//打开视频文件然后读取头部信息到pFormatCtx
	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){
		printf("Couldn't find stream information.\n");
		return -1;
	}
	videoindex=-1;//视频流所处的流序号,因为媒体文件还可能包含音频流
	//找到视频流的序号
	for(i=0; i<pFormatCtx->nb_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;
	}
	pCodecCtx=pFormatCtx->streams[videoindex]->codec;//获取解码环境
	pCodec=avcodec_find_decoder(pCodecCtx->codec_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();
	pFrameYUV=av_frame_alloc();


	//SDL Begin----------------------------
	if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {  
		printf( "Could not initialize SDL - %s\n", SDL_GetError()); 
		return -1;
	} 

	screen_w = pCodecCtx->width;
	screen_h = pCodecCtx->height;
	screen = SDL_SetVideoMode(screen_w, screen_h, 0,0);


	if(!screen) {  
		printf("SDL: could not set video mode - exiting:%s\n",SDL_GetError());  
		return -1;
	}

	bmp = SDL_CreateYUVOverlay(pCodecCtx->width, pCodecCtx->height,SDL_YV12_OVERLAY, screen); 

	rect.x = 0;    
	rect.y = 0;    
	rect.w = screen_w;    
	rect.h = screen_h;  
	//SDL End------------------------

	packet=(AVPacket *)av_malloc(sizeof(AVPacket));

	img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL); 
	//------------------------------

	while(av_read_frame(pFormatCtx, packet)>=0){//读取下一帧数据

		if(packet->stream_index==videoindex){//必须是视频流帧
			//Decode
			ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);//解码数据帧到pFrame
			if(ret < 0){
				printf("Decode Error.\n");
				return -1;
			}
			if(got_picture){
				SDL_LockYUVOverlay(bmp);
				pFrameYUV->data[0]=bmp->pixels[0];
				pFrameYUV->data[1]=bmp->pixels[2];
				pFrameYUV->data[2]=bmp->pixels[1];     
				pFrameYUV->linesize[0]=bmp->pitches[0];
				pFrameYUV->linesize[1]=bmp->pitches[2];   
				pFrameYUV->linesize[2]=bmp->pitches[1];
				sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, 
					pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);


				SDL_UnlockYUVOverlay(bmp); 

				SDL_DisplayYUVOverlay(bmp, &rect); 
				//Delay 40ms
				SDL_Delay(40);
			}
		}
		av_free_packet(packet);
	}

	//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 uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
		
		SDL_LockYUVOverlay(bmp);
		pFrameYUV->data[0]=bmp->pixels[0];
		pFrameYUV->data[1]=bmp->pixels[2];
		pFrameYUV->data[2]=bmp->pixels[1];     
		pFrameYUV->linesize[0]=bmp->pitches[0];
		pFrameYUV->linesize[1]=bmp->pitches[2];   
		pFrameYUV->linesize[2]=bmp->pitches[1];


		SDL_UnlockYUVOverlay(bmp); 
		SDL_DisplayYUVOverlay(bmp, &rect); 
		//Delay 40ms
		SDL_Delay(40);
	}

	sws_freeContext(img_convert_ctx);

	SDL_Quit();

	av_free(pFrameYUV);//释放帧数据占用的内存
	avcodec_close(pCodecCtx);//释放解码器环境
	avformat_close_input(&pFormatCtx);//释放输入环境
	return 0;
}

在解码流程的第6步中,需要判断读取帧是否是视频帧。这个可以通过判断这一帧的数据的流序号是否是视频流的序号即可。

packet->stream_index==videoindex

而视频流的序号可以通过该流的codec_type==AVMEDIA_TYPE_VIDEO来判断:

//找到视频流的序号
	for(i=0; i<pFormatCtx->nb_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;
	}

下一步便是学习常用的数据结构:http://blog.csdn.net/gameloft9/article/details/45841935

如果想要了解SDL部分的内容,可以参考:关于《最简单的视频播放器》记录二

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值