基于FFmpeg4.1的视频播放器的极简实现(音视频学习笔记四)

前言

这篇文章记录一个简单视频播放器的开发过程,代码极其为简洁,基于ffmpeg最新版本4.1实现的。视频渲染用的SDL2.0,SDL视频渲染部分代码直接copy的雷神的最简单的基于FFMPEG+SDL的视频播放器 ver2 (采用SDL2.0)但是他这篇的代码对于高版本的ffmpeg已经不适配了。

运行结果

测试视频是在Best Place on the Web to Download HD Trailers下载的电影宣传片,1080p的,下面是播放的画面。
在这里插入图片描述

  • log记录,打印log方便调试程序,另外这个简单的小程序没有内存泄漏,从播放开始到结束一直都是100M。
    在这里插入图片描述

结构体简单说明:

结构体:
AVFormatContext:封装格式上下文,媒体文件的处理句柄。
AVCodeContext:解码器上下文,用于存储解码器相关信息。
AVCodec:解码器结构体
AVCodecParameters:描述媒体流的详细信息,这个结构体在早期版本是没有的,加进来应该是为了解码和解封装直接的解耦合。
AVPacket: av_read_frame()的返回结果,是从AVFormatContext中读取到的信息,就叫pkt吧,一个pkt中可能只能包含一种媒体流,音频、视频、字幕等,如果是视频流可能包含一帧视频,如果是音频流可能包含多个音频帧。
AVFrame:这个结构体就是FFmpeg解码出来的实际数据了,经由av_send_packet(),和av_receive_frame()后得到的数据AVFrame,根据软硬解码的选择不同可能有不同的数据格式,常见的有yuv420p(多见于软解码),nv12,nv21等(常见于硬解码),一般的渲染播放需要rgb数据,这部分数据可以用FFmpeg的swscale来实现,但是效率较低。

API调用顺序说明

  1. avformat_open_input() //打开播放文件,可以是网络流或者本地文件
  2. avforamt_find_stream_info() //Read packets of a media file to get stream information
  3. av_find_best_stream() //找到媒体流对应的index
  4. avcodec_parameters_alloc //为AVCodecParameters分配空间
  5. avcodec_find_decoder //找到解码器
  6. avcodec_alloc_context3 //为avcodec_alloc_context3 分配空间
  7. avcodec_parameters_to_context() //Fill the codec context based on the values from the supplied codec
  8. avcodec_open2() .//打开解码器
  9. av_packet_alloc(); 为AVPacket分配空间
  10. av_frame_alloc()为AVFrame分配空间
    11 .av_read_frame()从AVFormatContext中读取AVPacket。
  11. avcodec_send_packet() //将读取的pkt发送到解码线程
  12. avcodec_receive_frame()从解码线程中取出解码后的数据AVFrame.

注意

av_packet_unref(pkt);
av_frame_unref(frame);

  • 注意一下这两个API,AVFram和AVPacket在alloc内存后呢会进入解码循环,循环中av_read_frame()会不断的读帧,每读取一个pkt,AVPacket的引用计数就会加1,同理在后面取frame的时候AVFrame的引用计数也会加1,这样就会导致内存泄漏,所以要在用完AVPacket和AVFrame后使用上面两个api使的引用计数减1,防止内存泄漏。在彻底用完之后要使用av_packet_free(&pkt);av_frame_free(&frame);来彻底释放内存。

源代码

这是第一版源码,以后有时间加入音频解码和音视频同步,再对解码解封装封装成生产者消费者模式,更符合实际生产需求。

/*
* @author wangyu
* @date 2020-08-08
* @[email protected]
*/
#include<stdio.h>
#include <windows.h>
extern "C"
{
#include<libavformat/avformat.h>
#include<libavcodec/avcodec.h>
#include<libavutil/avutil.h>
#include<libswresample/swresample.h>
#include<libswscale/swscale.h>
#include<SDL/SDL.h>
}
int main(int argc,char*argv[])
{
	/*
	* SDL变量
	*/
	//SDL---------------------------
	int screen_w = 0, screen_h = 0;
	SDL_Window* screen;
	SDL_Renderer* sdlRenderer;
	SDL_Texture* sdlTexture;
	SDL_Rect sdlRect;
	char url[] = "test.mov";
	AVFormatContext* ifmt=NULL;
	//打开文件
	int ret=avformat_open_input(&ifmt, url, NULL, NULL);
	if (ret < 0)
	{
		printf("can not open the input file %s\n", url);
		return -1;
	}
	printf("open the file successed!\n");
	ret =avformat_find_stream_info(ifmt, NULL);
	if (ret < 0)
	{
		printf("avformat_find_stream_info failed\n");
		return -1;
	}
	printf("avformat_find_stream_info successed!\n");
	int video_index = -1;
	int audio_index = -1;
	video_index=av_find_best_stream(ifmt, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
	audio_index = av_find_best_stream(ifmt, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
	if (video_index < 0)
	{
		printf("can not find the video stream!\n");
		return - 1;
	}
	printf("the videostream index is %d\n", video_index);
	if (audio_index < 0)
	{
		printf("can not find the audio stream!\n");
	}
	printf("the audio stream index is %d\n", audio_index);
	av_dump_format(ifmt, 1, url,0);
	//解码环节:
	/*
	* 找到解码器
	* 分配解码器上下文空间
	* 解码器参数复制
	*/
	AVCodecParameters* para = avcodec_parameters_alloc();
	AVCodec* vcodec = avcodec_find_decoder(ifmt->streams[video_index]->codecpar->codec_id);
	if (!vcodec)
	{
		printf(" can not find the  vidoe decoder!\n");
		return -1;
	}
	AVCodecContext* vctx = avcodec_alloc_context3(vcodec);
	avcodec_parameters_to_context(vctx, ifmt->streams[video_index]->codecpar);
	ret =avcodec_open2(vctx, vcodec, NULL);
	if (ret < 0)
	{
		printf("avcodec_open2 failed!\n");
		return -1;
	}
	printf("avcodec_open2() successed!\n");
	/*
	* SDL代码
	*/
	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 = vctx->width;
	screen_h = vctx->height;
	//SDL 2.0 Support for multiple windows
	screen = SDL_CreateWindow("Simplest ffmpeg player's Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
		screen_w, screen_h,
		SDL_WINDOW_OPENGL);
	if (!screen) {
		printf("SDL: could not create window - exiting:%s\n", SDL_GetError());
		return -1;
	}
	sdlRenderer = SDL_CreateRenderer(screen, -1, 0);
	//IYUV: Y + U + V  (3 planes)
	//YV12: Y + V + U  (3 planes)
	sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, vctx->width, vctx->height);
	sdlRect.x = 0;
	sdlRect.y = 0;
	sdlRect.w = screen_w;
	sdlRect.h = screen_h;
	/*
	* SDL End
	*/
	AVPacket* pkt=NULL;
	//av_packet_alloc中包含了av_pack_init()
	pkt = av_packet_alloc();
	AVFrame* frame = av_frame_alloc();
	while (1)
	{
		ret=av_read_frame(ifmt, pkt);
		if (ret < 0)
		{
			printf("read failed or the file is ended!\n");
			av_packet_unref(pkt);
			return -1;
		}
		if (pkt->stream_index != video_index)
		{
			av_packet_unref(pkt);
			continue;
		}
		printf("pkt's size=%d, pkt's pts =%ld\n", pkt->size, pkt->pts);
		//开始解码
		ret=avcodec_send_packet(vctx, pkt);
		if (ret < 0)
		{
			printf("avcodec_send_packet failed!\n");
			av_packet_unref(pkt);
			continue;
		}
		printf("avcodec_send_packet successed!\n");
		//用完packet第一时间释放内存
		av_packet_unref(pkt);
		ret = avcodec_receive_frame(vctx, frame);
		if (ret < 0)
		{
			printf("avcodec_receive_frame failed!\n");
			av_packet_unref(pkt);
			av_frame_unref(frame);
			continue;
		}
		printf("avcodec_receive_frame successed!\n");
		printf("frame.width= %d,frame.height= %d\n",frame->width, frame->height);
		/*
		*	SDL		
		*/
		SDL_UpdateYUVTexture(sdlTexture, &sdlRect,
			frame->data[0], frame->linesize[0],
			frame->data[1], frame->linesize[1],
			frame->data[2], frame->linesize[2]);
		SDL_RenderClear(sdlRenderer);
		SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, &sdlRect);
		SDL_RenderPresent(sdlRenderer);
		SDL_Delay(40);
		//释放内存
		av_frame_unref(frame);
		/*
		*	SDL End
		*/
		//Sleep(500);//40ms
	}
	av_packet_free(&pkt);
	av_frame_free(&frame);

	//释放内存
	avformat_free_context(ifmt);
	avcodec_parameters_free(&para);
	avcodec_free_context(&vctx);
	SDL_Quit();
	return 0;
}

==============================================================================

源代码加入了音频解码和播放,没区分线程也没做同步,等有时间再添加进去。

/*
* @author wangyu
* @date 2020-08-08
* @[email protected]
*/
#include<stdio.h>
#include <windows.h>
extern "C"
{
#include<libavformat/avformat.h>
#include<libavcodec/avcodec.h>
#include<libavutil/avutil.h>
#include<libswresample/swresample.h>
#include<libswscale/swscale.h>
#include<SDL/SDL.h>
}
#define MAX_AUDIO_FRAME_SIZE 192000 // 1 second of 48khz 32bit audio  
//48000 * (32/8)

unsigned int audioLen = 0;
unsigned char* audioChunk = NULL;
unsigned char* audioPos = NULL;

void fill_audio(void* udata, Uint8* stream, int len)
{
	SDL_memset(stream, 0, len);

	if (audioLen == 0)
		return;

	len = (len > audioLen ? audioLen : len);

	SDL_MixAudio(stream, audioPos, len, SDL_MIX_MAXVOLUME);

	audioPos += len;
	audioLen -= len;
}

int main(int argc, char* argv[])
{
	/*
	* SDL变量
	*/
	//SDL---------------------------
	uint64_t out_chn_layout = AV_CH_LAYOUT_STEREO;  //通道布局 输出双声道
	enum AVSampleFormat out_sample_fmt = (AVSampleFormat)AV_SAMPLE_FMT_S16; //声音格式
	int out_sample_rate = 44100;   //采样率
	int out_nb_samples = -1;
	int out_channels = -1;        //通道数
	int out_buffer_size = -1;   //输出buff
	unsigned char* outBuff = NULL;

	uint64_t in_chn_layout = -1;  //通道布局 

	
	SDL_AudioSpec wantSpec;
	int screen_w = 0, screen_h = 0;
	SDL_Window* screen;
	SDL_Renderer* sdlRenderer;
	SDL_Texture* sdlTexture;
	SDL_Rect sdlRect;
	char url[] = "test.mov";
	AVFormatContext* ifmt = NULL;
	//打开文件
	int ret = avformat_open_input(&ifmt, url, NULL, NULL);
	if (ret < 0)
	{
		printf("can not open the input file %s\n", url);
		return -1;
	}
	printf("open the file successed!\n");
	ret = avformat_find_stream_info(ifmt, NULL);
	if (ret < 0)
	{
		printf("avformat_find_stream_info failed\n");
		return -1;
	}
	printf("avformat_find_stream_info successed!\n");
	int video_index = -1;
	int audio_index = -1;
	video_index = av_find_best_stream(ifmt, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
	audio_index = av_find_best_stream(ifmt, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
	if (video_index < 0)
	{
		printf("can not find the video stream!\n");
		return -1;
	}
	printf("the videostream index is %d\n", video_index);
	if (audio_index < 0)
	{
		printf("can not find the audio stream!\n");
	}
	printf("the audio stream index is %d\n", audio_index);
	av_dump_format(ifmt, 1, url, 0);
	//解码环节:
	/*
	* 视频解码器初始化工作
	* 找到解码器
	* 分配解码器上下文空间
	* 解码器参数复制
	*/
	AVCodecParameters* vpara = avcodec_parameters_alloc();
	vpara = ifmt->streams[video_index]->codecpar;
	AVCodec* vcodec = avcodec_find_decoder(vpara->codec_id);
	//AVCodec* vcodec = avcodec_find_decoder_by_name("h264_mediacodec ");
	if (!vcodec)
	{
		printf(" can not find the  video decoder!\n");
		return -1;
	}
	AVCodecContext* vctx = avcodec_alloc_context3(vcodec);
	avcodec_parameters_to_context(vctx, vpara);
	ret = avcodec_open2(vctx, vcodec, NULL);
	if (ret < 0)
	{
		printf("avcodec_open2 failed!\n");
		return -1;
	}
	printf("avcodec_open2() successed!\n");
	/*
	*音频解码器初始化
	*/
	AVCodecParameters* apara = avcodec_parameters_alloc();
	apara = ifmt->streams[audio_index]->codecpar;
	AVCodec* acodec = avcodec_find_decoder(apara->codec_id);
	if (!acodec)
	{
		//这里在完善音视频同步播放后考虑只有视频的情况,即音频没有也可以正常播放
		printf("av_find_decoder failed!\n");
		return -1;
	}
	AVCodecContext* actx = avcodec_alloc_context3(acodec);
	avcodec_parameters_to_context(actx, apara);
	ret=avcodec_open2(actx, acodec, NULL);
	if (ret < 0)
	{
		printf("audio avcodec_open2() failed!\n");
		return -1;
	}
	printf("audio avcdecp_open2() successed!\n");
	/* audio codec init finished !*/





	/*
	* SDL代码
	*/
	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 = vctx->width;
	screen_h = vctx->height;
	//SDL 2.0 Support for multiple windows
	screen = SDL_CreateWindow("Simplest ffmpeg player's Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
		screen_w, screen_h,
		SDL_WINDOW_OPENGL);
	if (!screen) {
		printf("SDL: could not create window - exiting:%s\n", SDL_GetError());
		return -1;
	}
	sdlRenderer = SDL_CreateRenderer(screen, -1, 0);
	//IYUV: Y + U + V  (3 planes)
	//YV12: Y + V + U  (3 planes)
	sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, vctx->width, vctx->height);
	sdlRect.x = 0;
	sdlRect.y = 0;
	sdlRect.w = screen_w;
	sdlRect.h = screen_h;
	/*
	* SDL Video end
	* SDL Audio start
	*/
	//out parameter
	out_nb_samples = actx->frame_size;
	out_channels = av_get_channel_layout_nb_channels(out_chn_layout);
	out_buffer_size = av_samples_get_buffer_size(NULL, out_channels, out_nb_samples, out_sample_fmt, 1);
	outBuff = (unsigned char*)av_malloc(MAX_AUDIO_FRAME_SIZE * 2); //双声道
	printf("-------->out_buffer_size is %d\n", out_buffer_size);
	in_chn_layout = av_get_default_channel_layout(actx->channels);
	wantSpec.freq = out_sample_rate;
	wantSpec.format = AUDIO_S16SYS;
	wantSpec.channels = out_channels;
	wantSpec.silence = 0;
	wantSpec.samples = out_nb_samples;
	wantSpec.callback = fill_audio;
	wantSpec.userdata = actx;
	if (SDL_OpenAudio(&wantSpec, NULL) < 0)
	{
		printf("can not open SDL!\n");
		ret = -1;
		return -1;
	}
	//Swr
	struct SwrContext* au_convert_ctx = swr_alloc();
	au_convert_ctx = swr_alloc_set_opts(au_convert_ctx,
		out_chn_layout,                                /*out*/
		out_sample_fmt,                              /*out*/
		out_sample_rate,                             /*out*/
		in_chn_layout,                                  /*in*/
		actx->sample_fmt,               /*in*/
		actx->sample_rate,               /*in*/
		0,
		NULL);

	swr_init(au_convert_ctx);

	SDL_PauseAudio(0);

	/*
		SDL Audio end
	*/

	int pts, Synpts;
	AVPacket* pkt = NULL;
	//av_packet_alloc中包含了av_pack_init()
	pkt = av_packet_alloc();
	AVFrame* frame = av_frame_alloc();
	bool isAudio = false;//判断去读的pkt是音频还是视频。
	while (1)
	{
		ret = av_read_frame(ifmt, pkt);
		if (ret < 0)
		{
			printf("read failed or the file is ended!\n");
			av_packet_unref(pkt);
			return -1;
		}
		if (pkt->stream_index == audio_index)
		{
			isAudio = true;
			//开始解码
			ret = avcodec_send_packet(actx, pkt);
			if (ret < 0)
			{
				printf("audio avcodec_send_packet failed!\n");
				av_packet_unref(pkt);
				continue;
			}
		}
		else if(pkt->stream_index == video_index)
		{
			isAudio = false;
			ret = avcodec_send_packet(vctx, pkt);
			if (ret < 0)
			{
				printf("video  avcodec_send_packet failed!\n");
				av_packet_unref(pkt);
				continue;
			}
		}
		else
		{
			//既不是音频流也不是视频流选择丢弃,并且释放pkt
			continue;
			av_packet_unref(pkt);
		}
		av_packet_unref(pkt);//用完packet第一时间释放内存
		/*
		* frame的接收需要注意,一般一个pkt可以解码出一帧视频或者多帧音频,
		* 所以对于音频我们应该多次receive,保证读取完毕。
		*/
		/*音频帧*/
		if (isAudio)
		{
			while (avcodec_receive_frame(actx, frame)==0)
			{
				ret=swr_convert(au_convert_ctx, &outBuff, MAX_AUDIO_FRAME_SIZE, (const uint8_t**)frame->data, frame->nb_samples);
				if (ret < 0)
				{
					printf("swr_convert failed!\n");
				}
				while (audioLen > 0)
					SDL_Delay(1);
				//printf(" audio avcodec_receive_frame successed!\n");
				audioChunk = (unsigned char*)outBuff;
				audioPos = audioChunk;
				audioLen = out_buffer_size;
				Synpts = frame->pts;
				av_frame_unref(frame);
			}
		}
		/*视频帧*/
		else
		{
			ret = avcodec_receive_frame(vctx, frame);
			if (ret < 0)
			{
				printf(" video avcodec_receive_frame failed!\n");
				av_frame_unref(frame);
				continue;
			}

			/*
			*	SDL
			*/
			pts = frame->pts;
			SDL_UpdateYUVTexture(sdlTexture, &sdlRect,
				frame->data[0], frame->linesize[0],
				frame->data[1], frame->linesize[1],
				frame->data[2], frame->linesize[2]);
			SDL_RenderClear(sdlRenderer);
			SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, &sdlRect);
			SDL_RenderPresent(sdlRenderer);
			//SDL_Delay(40);
			//释放内存
			av_frame_unref(frame);
			/*
			*	SDL End
			*/
			//Sleep(500);//40ms
			continue;
		}	
	}
	av_packet_free(&pkt);
	av_frame_free(&frame);

	//释放内存
	avformat_free_context(ifmt);
	avcodec_parameters_free(&vpara);
	avcodec_free_context(&vctx);
	SDL_Quit();
	return 0;
}

【1】csdn 工程下载——vs打开直接即可运行

猜你喜欢

转载自blog.csdn.net/weixin_40840000/article/details/107885578