首页 > 代码库 > 基于ffmpeg网络播放器的教程与总结

基于ffmpeg网络播放器的教程与总结

一、         概述

为了解决在线无广告播放youku网上的视频。(youku把每个视频切换成若干个小视频)。

视频资源解析可以从www.flvcd.com获取,此网站根据你输入的优酷的播放网页地址解析成若干个真实的视频地址。

二、         实现

首先搜索关闭网络播放器(流媒体播放器的实现方法)

得出的结论,目前主流的播放器分三大阵营微软,苹果,基于FFmpeg内核的。所以我决定从ffmpeg开源的播放器入手。

最出名的ffmpeg播放器vcl播放器,开源免费。最后选择放弃。

原因

1 依赖于vcl的68M的plugins和libvlccore.dll,libvlc.dll项目生成文件过大。

2即使这样不能解决播放多段视频卡顿现象。

 

最后决定使用ffmpeg官方的ffpaly播放器只有1000多行 (很激动),使用ffmpeg编解码,使用sdl做显示。本想只修改下就行了。结果发现里面代码结构过于复杂,搞懂每行很是吃力。而且是用sdl做显示,sdl需要句柄。而我这个是为wpf项目量身定做的。Wpf只有顶层窗口有句柄。如果是使用wpf嵌入winform控件。导致此winform控件只能最上层显示(原因是wpf是directui思想实现的)。所以也放弃了。

 

决定使用ffmpeg库,自己开发

查看http://www.cnblogs.com/Alberl/p/3369187.html 关于ffmpeg开发的总结。对ffmpeg开发有个总体方向。

 

首先我们先把视频搞出来,参考

http://blog.csdn.net/leixiaohua1020/article/details/38868499  100行代码搞定视频。

然后100行搞定音频

http://blog.csdn.net/leixiaohua1020/article/details/38979615

 

这样视频音频都已经搞出来了。但是我们怎么把视频音频一起搞出来呢?

 

Csdn有一份文档

http://download.csdn.net/detail/u012832497/7340751

此文档介绍了用ffmpeg开发视频播放器的详细方法,有注解。但是已经过时了。最新的代码在https://github.com/chelyaev/ffmpeg-tutorial  

但是文档中的思想还是挺受用的。代码不同,思想是通的。

结论,视频包含视频流,音频流,字幕流(一般没有),

音视频同步跟进播放时间戳pts来做的。 视频和音频得出pts的方式有所不同。具体看文档。

 

如果按文档的注释,然后根据github的代码,编译我们发现视频可以显示,音频出现乌拉乌拉的杂音。 此时我参考100行搞定音频http://blog.csdn.net/leixiaohua1020/article/details/38979615

源码修改了github的音频部分。调试运行,可以播放了。

 

 

至此 我们的视频播放器可以播放了 ,使用sdl做显示。那现在我们还是没解决问题。网络播放器,多段无卡顿。

在此基础上我们分析,可以开辟一个线程从网络上下载视频,音频,放入到缓冲队列。音视频播放线程从缓冲区读取数据解析。

这就是网络播放器的原理,而且不会卡顿。其中音视频同步用音频驱动视频的方式实现。显示目前暂用sdl。

 

 

经过上面这些,我们的网络播放器终于可以工作了。那现在只剩下一个wpf句柄问题了。

好在我看到了http://www.cnblogs.com/viki117/archive/2013/05/29/3105417.html

文章里面介绍了vlc播放器c#开源代码,可以使用共享内存。但是说的不够详细

http://libvlcnet.codeplex.com

http://wpfcap.codeplex.com/SourceControl/latest

这两个开源项目都是用共享内存实现的。 参考此两篇文章。我的播放器终于可以播放网络的视频,音频,然后才wpf播放了。

中间有wpf调用c方法的一些细节。

至此我们的问题真的解决了吗?

 

 

NO,因为我们回调函数调用共享内存显示,里面有很多问题,比如当我们关闭程序时会出现访问锁定内存等问题。此问题肯定是可以解决的。但是我们东拼西凑把问题解决了。 当此方案不是最好的。

 

http://www.cnblogs.com/wdysunflower/archive/2011/05/27/2060035.html

http://www.cnblogs.com/scottwong/archive/2010/05/30/1747522.html

 

http://social.msdn.microsoft.com/Forums/zh-CN/a190e03d-f7f7-4ed5-b844-0a51d6eee434/silverlightmediaelementyv12?forum=silverlightzhchs

这3篇文章介绍了怎么使用mediaelement完美解决播放视频问题。

播放器源码可以用http://blog.csdn.net/leixiaohua1020/article/details/28685327

 

下面是我的播放器c部分的代码,

/*本播放器主要是解决 从优酷上播放视频。 是有多段网络视频组成一个完整视频。解决方案,开辟两个线程,一个线程从网络中读取数据包放入缓冲池(视频缓冲池和音频缓冲池)一个线程从音频缓冲池读取数据播放。一个从视频缓冲池中读取播放.难点1:av_read_frame是读取packet(包) 数据, 几包数据 组成avframe(帧)音频帧转换成byte[] 存储起来 放入缓冲池 吃音频byte[]可以直接放入音频流中播放视频帧也是byte[]  存储起来,此视频byte[]数组可以转换为图片 PIX_FMT_RGB24为了同步音视频,我们把没帧的最后一包的pts记录下来放入缓冲区*/#include "stdafx.h"#include "BonkerPlayer.h"#include <stdio.h>#include <stdlib.h>#include <string.h>extern "C"{#include "libavcodec/avcodec.h"#include "libavformat/avformat.h"#include "libswresample/swresample.h"#include "libswscale/swscale.h" #include <libavutil/avstring.h>	//SDL#include "sdl/SDL.h"#include "sdl/SDL_thread.h"};#define VideoBufferMaxSize 80//2048  //视频缓冲区最大值,大于此值 则不下载数据#define VideoBufferMinSize 20//1024  //视频缓冲区最小值,小于此值,则唤醒下载#define AudioBufferMaxSize 80//2048  //音频缓冲区最大值,大于此值 则不下载数据#define AudioBufferMinSize 20//1024  //音频缓冲区最小值,小于此值,则唤醒下载#define SDL_AUDIO_BUFFER_SIZE 1024 //音频流的缓冲区//#define VideoType PIX_FMT_YUV420P //视频转换的格式#define VideoType PIX_FMT_BGR24 //视频转换的格式#define MAX_AUDIO_FRAME_SIZE 192000 // 1 second of 48khz 32bit audio  //static char ErrorMsg[100]="";//错误的提示信息int FileDuration=0;//视频的长度  单位秒int flag=100;//标识 播放,暂停,退出 0退出,1标识,2暂停//声明了函数指针DispalyVideoDele Fn=NULL;Uint32  audio_len; Uint8  *audio_pos;double currentAudioClock=0;//当前音频播放时间double currentVideoClock=0;//当前视频播放时间double currentBufferClock=0;//当前以缓冲的时间,用于缓冲进度条//double currentPlayClock=0;//当前播放的时间,用于播放进度条double diffClock=0.2;//音视频相差的死区 int CurrentVolume=SDL_MIX_MAXVOLUME/2;//当前声音的大小SDL_Thread *decodeTid=NULL;//解码线程SDL_Thread *PlayVideoTid=NULL;//视频播放线程SDL_Thread *PlayAudioTid=NULL;//音频播放线程//快进的参数 bool isSeek=false;//是否在快进int global_seek_index=0;//文件索引 快进double globle_seek_pos=0;//快进的地方//存储音频的队列typedef struct AudioItem{	Uint8 *AudioData;//音频数据	int Length;//音频长度	double Pts;//时间戳	AudioItem *Next;//尾部	SDL_AudioSpec *wanted_spec;}AudioQueueItem;typedef struct{	AudioQueueItem *FirstItem;//队列头	AudioQueueItem *LastItem;//队列位	int Length;//队列长度	SDL_mutex *audioMutex;//用于同步两个线程同时操作队列的 互斥量	SDL_cond *audioCond;//唤醒线程}AudioQueue;//存储视频的队列typedef struct VideoItem{	Uint8 *VideoData;//音频数据	int Width;//视频图片的宽度	int Height;//视频图片的高度	int Length;//视频长度	double Pts;//时间戳	VideoItem *Next;//尾部}VideoQueueItem;typedef struct{	VideoQueueItem *FirstItem;//队列头	VideoQueueItem *LastItem;//队列位	int Length;//队列长度	double BufferPts;//缓冲的pts	SDL_mutex *videoMutex;//用于同步两个线程同时操作队列的 互斥量	SDL_cond *videoCond;//唤醒线程}VideoQueue;VideoQueue *videoQueue=NULL;//视频队列AudioQueue *audioQueue=NULL;//音频队列//清空视频队列void VideoQueueClear(VideoQueue *vq){	VideoItem *item,*temp;	SDL_LockMutex(vq->videoMutex);	for (item=vq->FirstItem; item!=NULL; item=temp)	{		temp=item->Next;//		av_free(item->VideoData);//释放video里面的数据		av_free(item);		vq->Length--;	}	vq->FirstItem=NULL;	vq->LastItem=NULL;	SDL_UnlockMutex(vq->videoMutex);}//清空音频队列void AudioQueueClear(AudioQueue *aq){	AudioItem *item,*temp;	SDL_LockMutex(aq->audioMutex);	for (item=aq->FirstItem; item!=NULL; item=temp)	{		temp=item->Next;//		av_free(item->AudioData);//释放video里面的数据		av_free(item->wanted_spec);		av_free(item);		aq->Length--;	}	aq->FirstItem=NULL;	aq->LastItem=NULL;	SDL_UnlockMutex(aq->audioMutex);}//初始化视频队列void VideoQueueInit(VideoQueue *vq){	memset(vq, 0, sizeof(VideoQueue));//初始化首地址为0	vq->videoMutex=SDL_CreateMutex();	vq->videoCond=SDL_CreateCond();}//初始化音频队列void AudioQueueInit(AudioQueue *aq){	memset(aq,0,sizeof(AudioQueue));	aq->audioMutex=SDL_CreateMutex();	aq->audioCond=SDL_CreateCond();}//向队列添加数据int VideoQueuePut(VideoQueue *vq,VideoQueueItem *item){	int result=0;	SDL_LockMutex(vq->videoMutex);//加锁	if(vq->Length<VideoBufferMaxSize)	{		if(!vq->FirstItem)//第一个item为null 则队列是空的		{			vq->FirstItem=item;			vq->LastItem=item;			vq->Length=1;			vq->BufferPts=item->Pts;		}		else		{			vq->LastItem->Next=item;//添加到队列后面			vq->Length++;			vq->LastItem=item;//此item变成队列尾部			vq->BufferPts=item->Pts;		}		if(vq->Length>=VideoBufferMinSize)		{			SDL_CondSignal(vq->videoCond);//唤醒其他线程  如果缓冲区里面有几个数据后再唤醒 较好		}		result=1;	}	else	{		SDL_CondWait(vq->videoCond,vq->videoMutex);//解锁  等待被唤醒	}	SDL_UnlockMutex(vq->videoMutex);//解锁	return result;}//向队列中取出数据,放入item中int  VideoQueueGet(VideoQueue *vq,VideoQueueItem *item){	int result=0;	SDL_LockMutex(vq->videoMutex);	if(vq->Length>0)	{		if(vq->FirstItem)//有数据		{			*item=*(vq->FirstItem);			if(!vq->FirstItem->Next)//只有一个			{				vq->FirstItem=NULL;				vq->LastItem=NULL;			}else			{				vq->FirstItem=vq->FirstItem->Next;			}			vq->Length--;			item->Next=NULL;			result= 1;		}		if(vq->Length<=VideoBufferMinSize)		{			SDL_CondSignal(vq->videoCond);//唤醒下载线程		}	}	else	{		SDL_CondWait(vq->videoCond,vq->videoMutex);//解锁  等待被唤醒	}	SDL_UnlockMutex(vq->videoMutex);	return result;}//向队列添加数据int AudioQueuePut(AudioQueue *aq,AudioQueueItem *item){	int result=0;	SDL_LockMutex(aq->audioMutex);//加锁	if(aq->Length<AudioBufferMaxSize)	{		if(!aq->FirstItem)//第一个item为null 则队列是空的		{			aq->FirstItem=item;			aq->LastItem=item;			aq->Length=1;		}		else		{			aq->LastItem->Next=item;//添加到队列后面			aq->Length++;			aq->LastItem=item;//此item变成队列尾部		}		if(aq->Length>=AudioBufferMinSize)		{			SDL_CondSignal(aq->audioCond);//唤醒其他线程  如果缓冲区里面有几个数据后再唤醒 较好		}		result=1;	}	else///音频缓冲区的大小 大于设定值 则让线程等待	{		SDL_CondWait(aq->audioCond,aq->audioMutex);//解锁  等待被唤醒	}	SDL_UnlockMutex(aq->audioMutex);//解锁	return result;}//向队列中取出数据,放入item中int AudioQueueGet(AudioQueue *aq,AudioQueueItem *item){	int result=0;	SDL_LockMutex(aq->audioMutex);	if(aq->Length>0)	{		if(aq->FirstItem)//有数据		{			*item=*(aq->FirstItem);			if(!aq->FirstItem->Next)//只有一个			{				aq->FirstItem=NULL;				aq->LastItem=NULL;			}else			{				aq->FirstItem=aq->FirstItem->Next;			}			aq->Length--;			item->Next=NULL;			result=1;		}		if(aq->Length<=AudioBufferMinSize)		{			SDL_CondSignal(aq->audioCond);//唤醒下载线程		}	}else	{		SDL_CondWait(aq->audioCond,aq->audioMutex);//解锁  等待被唤醒 	}	SDL_UnlockMutex(aq->audioMutex);	return result;}//输出声音的回调函数void AudioCallback(void *udata,Uint8 *stream,int len){   	//SDL 2.0	SDL_memset(stream, 0, len);	if(audio_len==0)		/*  Only  play  if  we  have  data  left  */ 		return; 	len=(len>audio_len?audio_len:len);	/*  Mix  as  much  data  as  possible  */ 	SDL_MixAudio(stream,audio_pos,len,CurrentVolume);	audio_pos += len; 	audio_len -= len; }   //下载视频和音频流并 解码  并放入相应的队列中int DecodePacket(void *arg){		VideoState *vs=(VideoState *)arg;	int length=vs->Length;	double currentAllFilePts=0;	av_register_all();  //注册所有解码器	avformat_network_init();  //初始化流媒体格式		for (int j = 0; j < length; j++)	{		double currentFilePts=0;		char* url=vs->Urls[j];		AVFormatContext *pFormatCtx;		pFormatCtx = avformat_alloc_context();  		//打卡文件		if(avformat_open_input(&pFormatCtx,url,NULL,NULL)!=0)		{  			//strcpy(ErrorMsg,"无法打开网络流");			return -1;		}		//avformat_close_input		if(av_find_stream_info(pFormatCtx)<0)  		{  			//strcpy(ErrorMsg,"无法获取流信息");  			return -1;  		} 		//获取此视频的总时间 微妙转化为妙		//FileDuration+= pFormatCtx->duration/1000000;		//把一个文件拆分为视频流和音频流		int videoIndex=-1,audioIndex=-1;  		int i=0;		//获取音频流和视频流的索引		for(i=0; i<pFormatCtx->nb_streams; i++)   		{			if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO)  			{  				videoIndex=i;  			}  			else if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO)			{				audioIndex=i;			}		}		AVCodecContext *pCodecCtx,*aCodecCtx;//视频,音频的解码器上下文		AVCodec *pCodec,*aCodec;//视频,音频解码器		if(videoIndex!=-1)		{			//视频解码器上下文,			pCodecCtx=pFormatCtx->streams[videoIndex]->codec;  			pCodec=avcodec_find_decoder(pCodecCtx->codec_id); 		}		else		{		}		if(audioIndex!=-1)		{			//音频解码器上下文			aCodecCtx=pFormatCtx->streams[audioIndex]->codec;  			aCodec=avcodec_find_decoder(aCodecCtx->codec_id);		}		else		{		}		if(videoIndex!=-1)		{			//打开解码器			if(avcodec_open2(pCodecCtx, pCodec,NULL)<0)  			{  				//strcpy(ErrorMsg,"无法打开视频解码器");  				return -1;  			}  		}		else		{		}		if(audioIndex!=-1)		{			if(avcodec_open2(aCodecCtx, aCodec,NULL)<0)  			{  				//strcpy(ErrorMsg,"无法打开音频解码器");  				return -1;  			}   		}		else		{		}		AVPacket *packet=(AVPacket *)av_mallocz(sizeof(AVPacket));  		AVFrame *pFrame=avcodec_alloc_frame();		AVFrame *pFrameRGB=avcodec_alloc_frame();		int frameFinished=0;//是否凑成一帧数据		int result=0;//标识一个视频是否解码完毕		int audioLength=0;//音频数组的长度		int videoLength=0;//视频数组的长度		//把视频帧转化为数组参数		struct SwsContext *img_convert_ctx; 		if(videoIndex!=-1)		{			videoLength=avpicture_get_size(VideoType, pCodecCtx->width, pCodecCtx->height);			img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, VideoType, SWS_BICUBIC, NULL, NULL, NULL);		}		//把音频帧转化为数组的参数		//uint64_t out_channel_layout=AV_CH_LAYOUT_STEREO;  		AVSampleFormat out_sample_fmt=AV_SAMPLE_FMT_S16;		int out_sample_rate=44100; 		int64_t in_channel_layout=av_get_channel_layout_nb_channels(aCodecCtx->channels);		int out_channels=av_get_channel_layout_nb_channels(aCodecCtx->channels);		int out_nb_samples=1024;		audioLength=av_samples_get_buffer_size(NULL,out_channels ,out_nb_samples,out_sample_fmt, 1);		struct SwrContext *au_convert_ctx;  		au_convert_ctx = swr_alloc();  		au_convert_ctx=swr_alloc_set_opts(au_convert_ctx,aCodecCtx->channels, out_sample_fmt, out_sample_rate,  			in_channel_layout,aCodecCtx->sample_fmt , aCodecCtx->sample_rate,0, NULL);  		swr_init(au_convert_ctx);  		int sample=SDL_AUDIO_BUFFER_SIZE;		//解码一包数据,一帧数据有多包		while(flag!=0&&av_read_frame(pFormatCtx, packet)>=0)  		{			if(isSeek)//要快进			{				//做快进				if(j==global_seek_index)				{					int seekFlag=avformat_seek_file(pFormatCtx, -1, (globle_seek_pos-10)* AV_TIME_BASE, globle_seek_pos * AV_TIME_BASE, (globle_seek_pos+10)* AV_TIME_BASE, AVSEEK_FLAG_ANY);					if(seekFlag>=0)					{						currentAllFilePts=0;						for (int k = 0; k < j; k++)						{							currentAllFilePts+=vs->times[k];						}					}					//av_seek_frame(pFormatCtx, -1 , globle_seek_pos * AV_TIME_BASE, AVSEEK_FLAG_ANY);					isSeek=false;				}else				{					j=global_seek_index-1;					break;				}			}			if(flag==0)//退出			{				break;			}else if(flag==1)//播放			{			}else if(flag==2)			{				SDL_Delay(1);				continue;			}			frameFinished=0;			//视频数据包 添加到视频队列中			if(packet->stream_index==videoIndex)  			{  				//把数据包转换为数据帧				result=avcodec_decode_video2(pCodecCtx,pFrame,&frameFinished,packet);				double pts=0;				if(packet->dts == AV_NOPTS_VALUE 					&& pFrame->opaque && *(uint64_t*)pFrame->opaque != AV_NOPTS_VALUE) {						pts = *(uint64_t *)pFrame->opaque;				} else if(packet->dts != AV_NOPTS_VALUE) {					pts = packet->dts;				} else {					pts = 0;				}				pts *= av_q2d(pFormatCtx->streams[videoIndex]->time_base);				//printf("+readVideo %d\n",videoQueue->Length);				if(result<0)//一个视频解码结束了				{					break;//跳出循环,继续解码下一个视频				}				if(frameFinished)//解析成了一帧数据,转化为字节数组存放队列中				{					uint8_t *bufferRGB=(uint8_t *)av_mallocz(videoLength);					avpicture_fill((AVPicture *)pFrameRGB, bufferRGB, VideoType, pCodecCtx->width, pCodecCtx->height);					sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);					//创建视频item					VideoQueueItem *videoItem;					videoItem=(VideoQueueItem *)av_mallocz(sizeof(VideoQueueItem));					videoItem->Height=pCodecCtx->height;					videoItem->Width=pCodecCtx->width;					videoItem->VideoData=http://www.mamicode.com/bufferRGB;"+readAudio %d\n",audioQueue->Length);				if(result<0)//一个视频解码结束了				{					break;//跳出循环,继续解码下一个视频				}				if(frameFinished)//解析成了一帧数据,转化为字节数组存放队列中				{					uint8_t *out_buffer=(uint8_t *)av_mallocz(MAX_AUDIO_FRAME_SIZE*2);					swr_convert(au_convert_ctx,&out_buffer, MAX_AUDIO_FRAME_SIZE,(const uint8_t **)pFrame->data , pFrame->nb_samples); 					//创建音频Item					AudioItem *audioItem;					audioItem=(AudioItem *)av_mallocz(sizeof(AudioItem));					audioItem->AudioData=http://www.mamicode.com/out_buffer;"Could not initialize SDL - %s\n", SDL_GetError());   		return -1;  	}   	bool isOpenAudio=false;	int samples=0;	SDL_AudioSpec  spec;	while (true&&flag!=0)	{		if(flag==2)// 暂停		{			SDL_Delay(1);			continue;		}else if(flag==0)//退出		{			return -1;		}else if(flag==1)//播放		{		}else if(flag==3)//解码结束了		{			//播放结束了 			if(audioQueue->Length<=0&&videoQueue->Length<=0)			{ 				break;			}		}		//音频快于视频 则加锁		if(currentAudioClock>=currentVideoClock+diffClock)		{			if(videoQueue->Length>0&&audioQueue->Length<AudioBufferMaxSize)			{				SDL_Delay(1);				continue;			}		}		AudioItem *audioItem=(AudioItem *)av_mallocz(sizeof(AudioItem));		//从队列中拿出音频数据		if(	AudioQueueGet( audioQueue,audioItem))		{			if(!isOpenAudio)			{				SDL_CloseAudio();				int re=SDL_OpenAudio(audioItem->wanted_spec, &spec); 				samples=audioItem->wanted_spec->samples;				isOpenAudio=true;			}			else			{				if(audioItem==NULL)				{					continue;				}				if(samples!=audioItem->wanted_spec->samples)				{					SDL_CloseAudio();					int re=SDL_OpenAudio(audioItem->wanted_spec, &spec); 					samples=audioItem->wanted_spec->samples;					isOpenAudio=true;				}			}			currentAudioClock=audioItem->Pts;//当前音频时间戳			audio_pos=audioItem->AudioData;			audio_len=audioItem->Length;			SDL_PauseAudio(0);			while(audio_len>0&&flag!=0)				SDL_Delay(5);			av_free(audioItem->AudioData);			av_free(audioItem->wanted_spec);		}		av_free(audioItem);	}	SDL_CondSignal(audioQueue->audioCond);//唤醒其他线程  如果缓冲区里面有几个数据后再唤醒 较好	SDL_CondSignal(videoQueue->videoCond);//唤醒其他线程  如果缓冲区里面有几个数据后再唤醒 较好	return 1;}int _tmain1(VideoState *vs){	currentAudioClock=0;	currentVideoClock=0;	currentBufferClock=0;	//currentPlayClock=0;	CurrentVolume=SDL_MIX_MAXVOLUME/2;	if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {		fprintf(stderr, "Unable to initialize SDL:  %s\n", SDL_GetError());		return 1;	}	atexit(SDL_Quit);	//atexit(SDL_Quit);// 注册SDL_Quit,当退出时调用,使得退出时程序自动清理	//flag=2;	//给音视频队列分配空间	videoQueue=(VideoQueue *)av_mallocz(sizeof(VideoQueue));	audioQueue=(AudioQueue *)av_mallocz(sizeof(AudioQueue));	//初始化音视频队列	VideoQueueInit(videoQueue);	AudioQueueInit(audioQueue);	decodeTid=SDL_CreateThread(DecodePacket,"DecodePacket",vs);	PlayVideoTid=SDL_CreateThread(PlayVideo,"PlayVideo",NULL);	PlayAudioTid=SDL_CreateThread(PlayAudio,"PlayAudioTid",NULL);	return 1;}//获取视频的总长度void InitAllTime(VideoState *vs){	FileDuration=0;	int length=vs->Length;	av_register_all();  //注册所有解码器	avformat_network_init();  //初始化流媒体格式		for (int j = 0; j < length; j++)	{		char* url=vs->Urls[j];		AVFormatContext *pFormatCtx = avformat_alloc_context();  		//打卡文件		if(avformat_open_input(&pFormatCtx,url,NULL,NULL)!=0)		{  			//strcpy(ErrorMsg,"无法打开网络流");			return;		}		if(av_find_stream_info(pFormatCtx)<0)  		{  			//strcpy(ErrorMsg,"无法获取流信息");  			return;  		} 		//保存每个文件的播放长度		vs->times[j]=pFormatCtx->duration/1000000;		//获取此视频的总时间 微妙转化为妙		FileDuration+= vs->times[j];		avformat_close_input(&pFormatCtx); 	}	avformat_network_deinit();}void bonker_pause(){	flag=2;}VideoState *_vs;//获取视频总长度double bonker_gettime(){	FileDuration=0;	if(_vs!=NULL)	{		InitAllTime(_vs);	}	return FileDuration;}void bonker_open(){	if(_vs!=NULL)	{				_tmain1(_vs);	}else	{		}}void bonker_play(){	flag=1;}void bonker_quit(){	bonker_close();}void bonker_init(DispalyVideoDele _fn){	FileDuration=0;//视频的长度  单位秒	flag=2;//标识 播放,暂停,退出 0退出,1标识,2暂停	audio_len=0; 	currentAudioClock=0;//当前音频播放时间	currentVideoClock=0;//当前视频播放时间	currentBufferClock=0;//当前以缓冲的时间,用于缓冲进度条	//currentPlayClock=0;//当前播放的时间,用于播放进度条	diffClock=0.2;//音视频相差的死区 	CurrentVolume=SDL_MIX_MAXVOLUME/2;//当前声音的大小	//快进的参数 	isSeek=false;//是否在快进	global_seek_index=0;//文件索引 快进	globle_seek_pos=0;//快进的地方	Fn=_fn;}void bonker_addurl(char *vs){	if(_vs==NULL)	{		_vs=(VideoState *)av_mallocz(sizeof(VideoState));		_vs->Length=0;	}	av_strlcpy(_vs->Urls[_vs->Length],vs,UrlLength);	_vs->Length++;}//释放内存资源void bonker_close(){	flag=0;//退出	SDL_CloseAudio();	if(videoQueue!=NULL)	{		SDL_CondSignal(videoQueue->videoCond);	}	if(audioQueue!=NULL)	{		SDL_CondSignal(audioQueue->audioCond);	}	SDL_Delay(10);	SDL_WaitThread(PlayVideoTid,NULL);	SDL_WaitThread(PlayAudioTid,NULL);	SDL_WaitThread(decodeTid,NULL);	if(videoQueue!=NULL)	{		VideoQueueClear(videoQueue);//释放视频队列		//videoQueue=NULL;	}	if(audioQueue!=NULL)	{		AudioQueueClear(audioQueue);		//audioQueue=NULL;	}	SDL_DestroyMutex(audioQueue->audioMutex);	SDL_DestroyCond(audioQueue->audioCond);	SDL_DestroyMutex(videoQueue->videoMutex);	SDL_DestroyCond(videoQueue->videoCond);	av_free(audioQueue);	av_free(videoQueue);	flag=2;	/*SDL_DetachThread(decodeTid);	SDL_DetachThread(PlayVideoTid);	SDL_DetachThread(PlayAudioTid);*/	if(_vs!=NULL)	{		av_free(_vs);	}	_vs=NULL;	SDL_Quit();	//关闭解码线程,视频播放线程,音频播放线程	/*if(decodeTid!=NULL)	{	decodeTid=NULL;	}	if(PlayVideoTid!=NULL)	{	PlayVideoTid=NULL;	}	if(PlayAudioTid!=NULL)	{	PlayAudioTid=NULL;	}*/}//设置声音void bonker_set_volumn(int volume){	if(volume>=0&&volume<=128)	{		CurrentVolume=volume;	}}//获取音量int bonker_get_volume(){	return CurrentVolume;}//快进 快退void bonker_seek(double seek_pos){	bool flagTemp=flag;	flag=2;	SDL_Delay(50);	//当快进的时间在缓冲区内	if(seek_pos>=currentVideoClock&&seek_pos<=videoQueue->BufferPts)	{		//清空之前的音频		AudioItem *item,*temp;		SDL_LockMutex(audioQueue->audioMutex);		for (item=audioQueue->FirstItem; item!=NULL; item=temp)		{			temp=item->Next;//			av_free(item->AudioData);//释放video里面的数据			av_free(item->wanted_spec);			av_free(item);			audioQueue->Length--;			if(temp!=NULL)			{				if(temp->Pts>=seek_pos)//目前缓冲区,大于此跳转位置				{					break;				}			}else			{				audioQueue->FirstItem=NULL;				audioQueue->LastItem=NULL;			}		}		SDL_UnlockMutex(audioQueue->audioMutex);		//清空之前的视频		VideoItem *item1,*temp1;		SDL_LockMutex(videoQueue->videoMutex);		for (item1=videoQueue->FirstItem; item1!=NULL; item1=temp1)		{			temp1=item1->Next;//			av_free(item1->VideoData);//释放video里面的数据			av_free(item1);			videoQueue->Length--;			if(temp1!=NULL)			{				if(temp1->Pts>=seek_pos)//目前缓冲区,大于此跳转位置				{					break;				}			}			else			{				videoQueue->FirstItem=NULL;				videoQueue->LastItem=NULL;			}		}		SDL_UnlockMutex(videoQueue->videoMutex);	}	else if(seek_pos>=0&&seek_pos<=FileDuration)//用av_seek_file,计算那个文件,	{		double pos=0;		for (int i = 0; i < _vs->Length; i++)		{			pos+=_vs->times[i];			if(pos>seek_pos)//就在这个文件内			{				isSeek=true;				global_seek_index=i;				globle_seek_pos=seek_pos- pos+_vs->times[i];//此文件快进到的位置				break;			}		}		//清空缓冲区		VideoQueueClear(videoQueue);		AudioQueueClear(audioQueue);	}	flag=flagTemp;	SDL_CondSignal(audioQueue->audioCond);//唤醒其他线程  如果缓冲区里面有几个数据后再唤醒 较好	SDL_CondSignal(videoQueue->videoCond);//唤醒其他线程  如果缓冲区里面有几个数据后再唤醒 较好}

  

 

基于ffmpeg网络播放器的教程与总结