基於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=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);//喚醒其他線程  如果緩沖區里面有幾個數據后再喚醒 較好
}

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM