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

简介: 一、         概述 为了解决在线无广告播放youku网上的视频。(youku把每个视频切换成若干个小视频)。 视频资源解析可以从www.flvcd.com获取,此网站根据你输入的优酷的播放网页地址解析成若干个真实的视频地址。

一、         概述

为了解决在线无广告播放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=bufferRGB;
					//videoItem->Length=videoLength;
					//videoItem->VideoData=pFrameRGB->data[0];
					videoItem->Length=pFrameRGB->linesize[0];
					//获取显示时间戳pts


					currentFilePts=pts;
					videoItem->Pts = currentAllFilePts+currentFilePts;//音频绝对pts;
					videoItem->Next=NULL;
					//添加到队列中
					while(flag!=0&&!VideoQueuePut(videoQueue,videoItem));
					//av_free(bufferRGB);//释放
				}
			}//音频数据包 ,添加到音频队列中
			else if(packet->stream_index==audioIndex)
			{
				result=	avcodec_decode_audio4( aCodecCtx, 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("+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=out_buffer;
					audioItem->Length=audioLength;
					//获取显示时间戳pts

					currentFilePts=pts;
					audioItem->Pts =currentAllFilePts+currentFilePts;//音频绝对pts


					SDL_AudioSpec *wanted_spec=(SDL_AudioSpec *)av_mallocz(sizeof(SDL_AudioSpec));;  //音频设置
					//初始化音频设置
					wanted_spec->silence = 0;   
					wanted_spec->samples = sample;    
					wanted_spec->format = AUDIO_S16SYS;    

					wanted_spec->freq =aCodecCtx->sample_rate; 
					wanted_spec->channels = out_channels;   
					wanted_spec->userdata = aCodecCtx; 

					wanted_spec->callback = AudioCallback;  
					if(wanted_spec->samples!=pFrame->nb_samples){  
						//SDL_CloseAudio();  
						out_nb_samples=pFrame->nb_samples;  
						audioLength=av_samples_get_buffer_size(NULL,out_channels ,out_nb_samples,out_sample_fmt, 1);  

						wanted_spec->samples=out_nb_samples;  
						wanted_spec->freq=aCodecCtx->sample_rate;
						//SDL_OpenAudio(&wanted_spec, NULL);  
					}  
					audioItem->wanted_spec=wanted_spec;
					//添加到队列中

					audioItem->Next=NULL;
					while(flag!=0&&!AudioQueuePut(audioQueue,audioItem));
				}
			}

			av_free_packet(packet);  //释放内存
		}
		av_free(img_convert_ctx);
		av_free(au_convert_ctx);
		av_free(pFrame);
		av_free(pFrameRGB);

		avcodec_close(pCodecCtx); 
		avcodec_close(aCodecCtx); 
		avformat_close_input(&pFormatCtx); 
		currentAllFilePts+=currentFilePts;//把这一段视频地址 累加
		if(flag==0)
		{
			return 1;
		}

	}


	avformat_network_deinit();
	flag=3;//解码结束了
	return 1;
}


//播放视频线程
int PlayVideo(void *arg)
{
	while (flag!=0&&true)
	{
		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(currentVideoClock>=currentAudioClock+diffClock)
		{
			//音频队中有数据,这样判断是因为,当只有视频时,视频满了,就可以播放了
			if(audioQueue->Length>0&&videoQueue->Length<VideoBufferMaxSize)
			{
				SDL_Delay(1);
				continue;
			}
		}
		VideoItem *videoItem=(VideoItem *)av_mallocz(sizeof(VideoItem));
		//从队列中拿出视频数据
		if(VideoQueueGet( videoQueue,videoItem))
		{
			currentVideoClock=videoItem->Pts;//当前视频时间戳
			if(Fn)
			{
				Fn((unsigned char *)videoItem->VideoData,videoItem->Width,videoItem->Height,videoItem->Pts,videoQueue->BufferPts);
			}

			av_free(videoItem->VideoData);
		}
		av_free(videoItem);

	}
	return 1;
}
//播放音频线程
int PlayAudio(void *arg)
{
	if(SDL_Init( SDL_INIT_AUDIO | SDL_INIT_TIMER)) {    
		printf( "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);//唤醒其他线程  如果缓冲区里面有几个数据后再唤醒 较好
}

  

 

作者:Bonker
出处:http://www.cnblogs.com/Bonker
QQ:519841366
       
本页版权归作者和博客园所有,欢迎转载,但未经作者同意必须保留此段声明, 且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利
目录
相关文章
|
1月前
|
存储 网络协议 Ubuntu
【C++网络编程】Socket基础:网络通讯程序入门级教程
【C++网络编程】Socket基础:网络通讯程序入门级教程
61 7
|
24天前
|
机器学习/深度学习 编解码 算法
YOLOv5改进 | 主干网络 | 用EfficientNet卷积替换backbone【教程+代码 】
在YOLOv5的GFLOPs计算量中,卷积占了其中大多数的比列,为了减少计算量,研究人员提出了用EfficientNet代替backbone。本文给大家带来的教程是**将原来的主干网络替换为EfficientNet。文章在介绍主要的原理后,将手把手教学如何进行模块的代码添加和修改,并将修改后的完整代码放在文章的最后,方便大家一键运行,小白也可轻松上手实践。以帮助您更好地学习深度学习目标检测YOLO系列的挑战。
|
1月前
|
JavaScript 前端开发 网络安全
【网络安全 | 信息收集】JS文件信息收集工具LinkFinder安装使用教程
【网络安全 | 信息收集】JS文件信息收集工具LinkFinder安装使用教程
43 4
|
1月前
|
机器学习/深度学习 自然语言处理 PyTorch
使用Python实现循环神经网络(RNN)的博客教程
使用Python实现循环神经网络(RNN)的博客教程
53 1
|
1天前
|
机器学习/深度学习 异构计算
【保姆级教程|YOLOv8改进】【5】精度与速度双提升,使用FasterNet替换主干网络
【保姆级教程|YOLOv8改进】【5】精度与速度双提升,使用FasterNet替换主干网络
|
1天前
|
机器学习/深度学习
【保姆级教程】【YOLOv8替换主干网络】【1】使用efficientViT替换YOLOV8主干网络结构(4)
【保姆级教程】【YOLOv8替换主干网络】【1】使用efficientViT替换YOLOV8主干网络结构
|
1天前
|
机器学习/深度学习
【保姆级教程】【YOLOv8替换主干网络】【1】使用efficientViT替换YOLOV8主干网络结构(3)
【保姆级教程】【YOLOv8替换主干网络】【1】使用efficientViT替换YOLOV8主干网络结构
|
1天前
|
机器学习/深度学习
【保姆级教程】【YOLOv8替换主干网络】【1】使用efficientViT替换YOLOV8主干网络结构(2)
【保姆级教程】【YOLOv8替换主干网络】【1】使用efficientViT替换YOLOV8主干网络结构
|
1天前
|
编解码 自动驾驶 计算机视觉
【保姆级教程】【YOLOv8替换主干网络】【1】使用efficientViT替换YOLOV8主干网络结构(1)
【保姆级教程】【YOLOv8替换主干网络】【1】使用efficientViT替换YOLOV8主干网络结构
|
11天前
|
XML 网络协议 Java
53. 【Android教程】Socket 网络接口
53. 【Android教程】Socket 网络接口
15 0