1. 程式人生 > >精簡的FFMPEG從UDP廣播接收視訊資料並播放的示例

精簡的FFMPEG從UDP廣播接收視訊資料並播放的示例

FIFO佇列緩衝區,用於接收從UDP獲得的視訊資料:

CLinkedQueue.h

#pragma once
#include "stdafx.h"
#include "afxsock.h"
class CLinkedQueue
{
public:
	CLinkedQueue();
	~CLinkedQueue();

public:
	typedef struct Node
	{
		struct Node *pNext;
		int size;
		unsigned * pData;		
	}Node;
	Node *m_pHead,*m_pTail;

	HANDLE m_hMutex;
	int m_numOfNode;
	void EnQueue(unsigned *pBuf, int size);
	int DeQueue(unsigned *pBuf, int size);
	void EmptyQueue();

};

CLinkedQueue.cpp

#include "stdafx.h"
#include "CLinkedQueue.h"

#define MAX_SiZE 1000	//佇列長度

CLinkedQueue::CLinkedQueue()
{
	m_pHead = NULL;
	m_pTail = NULL;
	m_numOfNode = 0;
	m_hMutex = CreateMutex(nullptr, FALSE, nullptr);

}

CLinkedQueue::~CLinkedQueue() {

	EmptyQueue();
	CloseHandle(m_hMutex);
}
/*
pBuf:緩衝區
size:緩衝區長度
*/
void CLinkedQueue::EnQueue(unsigned * pBuf, int size)
{
	WaitForSingleObject(m_hMutex, INFINITE);

	if (m_pHead == NULL) {
		Node *pNode = new Node;
		pNode->pNext = NULL;
		pNode->size = size;
		pNode->pData = new unsigned[size];
		memcpy(pNode->pData, pBuf, size);
		
		m_pHead=m_pTail = pNode;
	}
	else if (m_numOfNode > 1000) {//限制佇列長度,超過的丟棄!
		m_numOfNode--;
	}
	else {
		Node *pNode = new Node;
		pNode->pNext = NULL;
		pNode->size = size;
		pNode->pData = new unsigned[size];
		memcpy(pNode->pData, pBuf, size);

		m_pTail->pNext = pNode;
		m_pTail = pNode;
	}
	m_numOfNode++;
	printf("+numOfNode:%d\n", m_numOfNode);
	ReleaseMutex(m_hMutex);
	return;

}
/*
pBuf:緩衝區
size:緩衝區長度
return:出隊資料實際長度;如果佇列為空則返回0;如果出隊資料長度大於緩衝區長度,則返回-1。
*/
int CLinkedQueue::DeQueue(unsigned * pBuf, int size)
{
	WaitForSingleObject(m_hMutex, INFINITE);

	if (m_pHead == NULL) {
		ReleaseMutex(m_hMutex);
		return 0;
	}
	if (m_pHead->size > size) {
		ReleaseMutex(m_hMutex);
		return -1;
	}
	else{
		Node *pNode = m_pHead;
		m_pHead = m_pHead->pNext;
		memcpy(pBuf, pNode->pData, pNode->size);

		int ret = pNode->size;
		delete pNode->pData;
		delete pNode;

		m_numOfNode--;
		printf("-numOfNode:%d\n", m_numOfNode);	
		ReleaseMutex(m_hMutex);
		return ret;
	}	
}

void CLinkedQueue::EmptyQueue()
{
	WaitForSingleObject(m_hMutex, INFINITE);

	if (m_pHead == NULL) {
		ReleaseMutex(m_hMutex);
		return;
	}
	for (int i = 0; m_pHead != NULL; i++) {
		Node *pNode = m_pHead;
		m_pHead = m_pHead->pNext;
		delete pNode->pData;
		delete pNode;
	}

	ReleaseMutex(m_hMutex);
	return;
}


非同步Socket,用於接收視訊資料:

CLinkedQueue.h

#pragma once

#include "afxsock.h"
#include "CLinkedQueue.h"

// CMySock command target

class CUdpSocket : public CAsyncSocket
{
public:
	CLinkedQueue *m_pLinkedQueue;
public:
	CUdpSocket(CLinkedQueue *pLinkedQueue);
	virtual ~CUdpSocket();
public:
	virtual void OnReceive(int nErrorCode);
};



CUdpSocket.cpp
// MySock.cpp : implementation file
//

#include "stdafx.h"
#include "CUdpSocket.h"
 
// CMySock

CUdpSocket::CUdpSocket(CLinkedQueue *pLinkedQueue)
{
	m_pLinkedQueue = pLinkedQueue;
}

CUdpSocket::~CUdpSocket()
{
}


// CMySock member functions

void CUdpSocket::OnReceive(int nErrorCode)
{
	// TODO: Add your specialized code here and/or call the base class
	unsigned buf[10240];
	int nRead = Receive(buf, 10240);

	switch (nRead)
	{
	case 0:
		break;
	case SOCKET_ERROR:
		if (GetLastError() != WSAEWOULDBLOCK)
		{
			AfxMessageBox(_T("Error occurred"));
		}
		break;
	default:
		m_pLinkedQueue->EnQueue(buf, nRead);
	}

	CAsyncSocket::OnReceive(nErrorCode);
}

簡單的命令列下的視訊播放程式FFMPEGDemo:
// FFMPEGDemo03.cpp : main project file.
/*
功能:從記憶體獲取音視訊資料並播放,同時輸出影象檔案。

巨集GET_STREAM_FROM_UDP確定從upd獲取數還是從檔案獲取資料,不論那種方式,均轉換為從記憶體獲取資料。
實現方式是定義回撥函式read_buffer(),AVIOContext使用的回撥函式,當系統需要資料的時候,會自動呼叫該回調函式以獲取資料。
問題:從UPD讀取視音訊資料並播放,但是存在花屏的現象。

巨集OUTPUT_JPG確定輸出影象格式是bmp還是jpg

做個大的修改:
1、為了與獲取無人機視訊流的實際情況更加近似,便於整合到實際的程式中,採用CAsyncSocket派生類接收UDP資料,UDP資料接收後放入FIFO緩衝區佇列,ffmpeg從緩衝區佇列中讀取資料;
2、UDP緩衝區佇列採用連結串列的形式實現,這樣,每個UDP資料對應一個佇列節點;
3、由於CAsyncSocket派生類和ffmpeg均需要訪問緩衝區佇列,因此佇列中加入了互斥機制;
4、為了CAsyncSocket派生類的OnReceive()函式能夠響應訊息,main函式中增加了訊息迴圈,ffmpeg相關程式碼放在了一個執行緒中。
5、如果使用訊息迴圈,OnReceive()函式無法被呼叫,當然也可以在main函式中通過SetTimer函式設定定時器的方式週期性地獲取UDP包,但是前者的方式更好。
6、本以為這種方案會通過UDP解決播放h.264裸流時花屏的問題,但是花屏問題仍然存在,但是通過UDP播放ts檔案是正常的。
------
修改了程式,增加了延時,使得播放程式碼讀取佇列時佇列中有資料,播放效果非常理想,但是存在實時性的問題。
為了保證實時性:
1、可以設定延時讀取佇列,保證程式後,開始讀取佇列時,佇列中有資料。
2、可以設定佇列長度為1,使得佇列中的資料始終是新鮮的資料。
3、也可以同步快取GPS資料,當儲存影象時,同步讀取影象拍攝時刻的GPS資訊。

實時性的問題:
1、縮短解碼程式讀取佇列的時延,比如,設定延時為20ms,可以很快讀空佇列,從而提高實時性。如果這樣,實際上GPS資訊與影象的資訊的同步也就解決了。

*/

#include "stdafx.h"
#include <stdio.h>
#include "CLinkedQueue.h"
#include "CUdpSocket.h"


using namespace System;

#define __STDC_CONSTANT_MACROS

#ifdef _WIN32
	//Windows
	extern "C"
	{
		#include "libavcodec/avcodec.h"
		#include "libavformat/avformat.h"
		#include "libswscale/swscale.h"
		#include "libavutil/imgutils.h"
		#include "SDL.h"
	};
#else
	//Linux...
	#ifdef __cplusplus
		extern "C"
		{
	#endif
		#include <libavcodec/avcodec.h>
		#include <libavformat/avformat.h>
		#include <libswscale/swscale.h>
		#include <libavutil/imgutils.h>
		#include <SDL2/SDL.h>
	#ifdef __cplusplus
		};
	#endif
#endif


//視音訊資料來源是檔案(0)還是UDP(1)
#define GET_STREAM_FROM_UDP 1

FILE *fp_open = NULL;
//視音訊資料檔案
//char filepath[]="bigbuckbunny_480x272.h265";
//char filepath[] = "Titanic.ts";
char filepath[] = "test_raw.h264";

//輸出檔案格式是bmp(0)還是jpg(1)
#define OUTPUT_JPG 1
//輸出影象檔案位置
#define OUTPUT_DIR "D:/temp/"

//接收UDP的緩衝區佇列
CLinkedQueue linkedQueue;

//設定每幀的延時,實際上對應幀率,40ms對應每秒25幀
#define DELAY_PER_FRAME 10

//SDL Refresh Event
#define SFM_REFRESH_EVENT  (SDL_USEREVENT + 1)
#define SFM_BREAK_EVENT  (SDL_USEREVENT + 2)

//FILE _iob[] = { *stdin, *stdout, *stderr };
//extern "C" FILE * __cdecl __iob_func(void)
//{
//	return _iob;
//}

/*
FFMPEG中結構體很多。最關鍵的結構體可以分成以下幾類:
a)解協議(http,rtsp,rtmp,mms)
AVIOContext,URLProtocol,URLContext主要儲存視音訊使用的協議的型別以及狀態。
URLProtocol儲存輸入視音訊使用的封裝格式。每種協議都對應一個URLProtocol結構。
(注意:FFMPEG中檔案也被當做一種協議“file”)

b)解封裝(flv,avi,rmvb,mp4)
AVFormatContext主要儲存視音訊封裝格式中包含的資訊;
AVInputFormat儲存輸入視音訊使用的封裝格式。
每種視音訊封裝格式都對應一個AVInputFormat 結構。

c)解碼(h264,mpeg2,aac,mp3)
每個AVStream儲存一個視訊/音訊流的相關資料;
每個AVStream對應一個AVCodecContext,儲存該視訊/音訊流使用解碼方式的相關資料;
每個AVCodecContext中對應一個AVCodec,包含該視訊/音訊對應的解碼器。每種解碼器都對應一個AVCodec結構。

d)存資料
視訊的話,每個結構一般是存一幀;音訊可能有好幾幀
解碼前資料:AVPacket
解碼後資料:AVFrame
視訊流處理其實就是從檔案中讀取一包包的packet,將這一包包的packet組成frame幀
*/


int thread_exit = 0;
int thread_pause = 0;

int sfp_refresh_thread(void *opaque) {
	thread_exit = 0;	//表示執行緒結束
	thread_pause = 0;	//表示暫停

	while (!thread_exit) {
		if (!thread_pause) {//如果沒有暫停,則傳送事件,由於下面有延時,實際上如果沒有暫停,每40ms傳送一個SFM_REFRESH_EVENT事件
			SDL_Event event;
			event.type = SFM_REFRESH_EVENT;
			SDL_PushEvent(&event);
		}
		//這裡的值要合適,用1000/幀率,比如25fps,則設定為1000/25 = 40
		SDL_Delay(DELAY_PER_FRAME);//如果暫停,則只是迴圈延時40ms
	}
	thread_exit = 0;
	thread_pause = 0;
	//Break
	SDL_Event event;
	event.type = SFM_BREAK_EVENT;
	SDL_PushEvent(&event);

	return 0;
}

//AVIOContext使用的回撥函式,當系統需要資料的時候,會自動呼叫該回調函式以獲取資料。
int read_buffer(void *opaque, uint8_t *buf, int buf_size) {
#if GET_STREAM_FROM_UDP
	//從UDP接收資料
	int true_size;
	while (1) {		//沒有讀取到資料就不返回!
		true_size = linkedQueue.DeQueue((unsigned *)buf, buf_size);
		if (true_size == -1)
			return -1;
		if (true_size != 0)
			return true_size;	
	}
#else
	//從檔案獲取資料
	if (!feof(fp_open)) {	//每次讀取一部分
		int true_size = fread(buf, 1, buf_size, fp_open);
		return true_size;
	}
	else {
		return -1;
	}
#endif // GET_STREAM_FROM_UDP
}


void SaveAsBMP(AVFrame *pFrameRGB, int width, int height, int index,int bpp)
{
	char buf[5] = { 0 };
	BITMAPFILEHEADER bmpheader;
	BITMAPINFOHEADER bmpinfo;
	FILE *fp;

	char *filename = new char[255];	

	//檔案存放路徑,根據自己的修改  
	sprintf_s(filename, 255, "%s%d.bmp", OUTPUT_DIR, index);
	if ((fp = fopen(filename, "wb+")) == NULL) {
		printf("open file failed!\n");
		return;
	}

	bmpheader.bfType = 0x4d42;
	bmpheader.bfReserved1 = 0;
	bmpheader.bfReserved2 = 0;
	bmpheader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
	bmpheader.bfSize = bmpheader.bfOffBits + width*height*bpp / 8;

	bmpinfo.biSize = sizeof(BITMAPINFOHEADER);
	bmpinfo.biWidth = width;
	bmpinfo.biHeight = height;
	bmpinfo.biPlanes = 1;
	bmpinfo.biBitCount = bpp;
	bmpinfo.biCompression = BI_RGB;
	bmpinfo.biSizeImage = (width*bpp + 31) / 32 * 4 * height;
	bmpinfo.biXPelsPerMeter = 100;
	bmpinfo.biYPelsPerMeter = 100;
	bmpinfo.biClrUsed = 0;
	bmpinfo.biClrImportant = 0;

	fwrite(&bmpheader, sizeof(bmpheader), 1, fp);
	fwrite(&bmpinfo, sizeof(bmpinfo), 1, fp);
	fwrite(pFrameRGB->data[0], width*height*bpp / 8, 1, fp);

	fclose(fp);
}

/**
* 將AVFrame(YUV420格式)儲存為JPEG格式的圖片
*
* @param width YUV420的寬
* @param height YUV42的高
*
*/
int SaveAsJPEG(AVFrame* pFrame, int width, int height, int iIndex)
{
	// 輸出檔案路徑  
	char out_file[MAX_PATH] = { 0 };
	sprintf_s(out_file, sizeof(out_file), "%s%d.jpg", OUTPUT_DIR, iIndex);

	// 分配AVFormatContext物件  
	AVFormatContext* pFormatCtx = avformat_alloc_context();

	// 設定輸出檔案格式  
	pFormatCtx->oformat = av_guess_format("mjpeg", NULL, NULL);
	// 建立並初始化一個和該url相關的AVIOContext  
	if (avio_open(&pFormatCtx->pb, out_file, AVIO_FLAG_READ_WRITE) < 0) {
		printf("Couldn't open output file.");
		return -1;
	}

	// 構建一個新stream  
	AVStream* pAVStream = avformat_new_stream(pFormatCtx, 0);
	if (pAVStream == NULL) {
		return -1;
	}

	// 設定該stream的資訊  
	AVCodecContext* pCodecCtx = pAVStream->codec;

	pCodecCtx->codec_id = pFormatCtx->oformat->video_codec;
	pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
	pCodecCtx->pix_fmt = AV_PIX_FMT_YUVJ420P;
	pCodecCtx->width = width;
	pCodecCtx->height = height;
	pCodecCtx->time_base.num = 1;
	pCodecCtx->time_base.den = 25;



	// Begin Output some information  
	av_dump_format(pFormatCtx, 0, out_file, 1);
	// End Output some information  

	// 查詢解碼器  
	AVCodec* pCodec = avcodec_find_encoder(pCodecCtx->codec_id);
	if (!pCodec) {
		printf("Codec not found.");
		return -1;
	}
	// 設定pCodecCtx的解碼器為pCodec  
	if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
		printf("Could not open codec.");
		return -1;
	}

	//Write Header  
	avformat_write_header(pFormatCtx, NULL);

	int y_size = pCodecCtx->width * pCodecCtx->height;

	//Encode  
	// 給AVPacket分配足夠大的空間  
	AVPacket pkt;
	av_new_packet(&pkt, y_size * 3);

	//   
	int got_picture = 0;
	int ret = avcodec_encode_video2(pCodecCtx, &pkt, pFrame, &got_picture);
	if (ret < 0) {
		printf("Encode Error.\n");
		return -1;
	}
	if (got_picture == 1) {
		//pkt.stream_index = pAVStream->index;  
		ret = av_write_frame(pFormatCtx, &pkt);
	}

	av_free_packet(&pkt);

	//Write Trailer  
	av_write_trailer(pFormatCtx);

	printf("Encode Successful.\n");

	if (pAVStream) {
		avcodec_close(pAVStream->codec);
	}
	avio_close(pFormatCtx->pb);
	avformat_free_context(pFormatCtx);

	return 0;
}

DWORD WINAPI  MyThreadFunction(LPVOID lpParam)
{

	AVFormatContext	*pFormatCtx;
	int				i, videoindex;
	AVCodecContext	*pCodecCtx;
	AVCodec			*pCodec;
	AVFrame	*pFrame, *pFrameYUV, *pFrameRGB;
	unsigned char *out_buffer_yuv420p;
	unsigned char *out_buffer_rgb24;
	AVPacket *packet;
	int ret, got_picture;

	//------------SDL----------------
	int screen_w, screen_h;
	SDL_Window *screen;
	SDL_Renderer* sdlRenderer;
	SDL_Texture* sdlTexture;
	SDL_Rect sdlRect;
	SDL_Thread *video_tid;
	SDL_Event event;
	//------------SDL----------------

	struct SwsContext *img_convert_ctx_yuv420p;
	struct SwsContext *img_convert_ctx_rgb24;

#if GET_STREAM_FROM_UDP
	//UDP----------------
	//CMySock *pMySock = new CMySock(&linkedQueue);
	//pMySock->Create(8888, SOCK_DGRAM, NULL);//建立套接字
	//pMySock->Bind(8888,NULL);	//繫結本地套介面
	////設定接收廣播訊息
	//BOOL bBroadcast = TRUE;
	//pMySock->SetSockOpt(SO_BROADCAST, (const char*)&bBroadcast, sizeof(BOOL), SOL_SOCKET);
	//UDP-----------------
#else
	fp_open = fopen(filepath, "rb+");
#endif // GET_STREAM_FROM_UDP

	av_register_all();
	avformat_network_init();
	pFormatCtx = avformat_alloc_context();

	//初始化 AVIOContext
	/*
	不從檔案,而是從記憶體讀取視訊資料的關鍵是在avformat_open_input()之前初始化一個AVIOContext,
	而且將原本的AVFormatContext的指標pb(AVIOContext型別)指向這個自行初始化AVIOContext。
	當自行指定了AVIOContext之後,avformat_open_input()裡面的URL引數就不起作用了
	*/
	unsigned char *aviobuffer = (unsigned char *)av_malloc(32768);
	AVIOContext *avio = avio_alloc_context(aviobuffer, 32768, 0, NULL, read_buffer, NULL, NULL);
	pFormatCtx->pb = avio;
	
	/*
	有時需要延時一下,使得緩衝區佇列中有一定數量的UDP包,因為ffmpeg需要讀取一定數量的資料才能獲取到足夠的引數。
	如果avformat_open_input函式呼叫read_buffer函式多次沒有獲得資料,會返回-1!
	*/
	//SDL_Delay(DELAY_PER_FRAME);

	//pFormatCtx非常重要,裡面不僅包含了視訊的解析度,時間戳等資訊,而且包含了相應的解碼器的資訊
	if (avformat_open_input(&pFormatCtx, NULL, 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();//分配空間儲存介面後的YUV資料,該函式並沒有為AVFrame的畫素資料分配空間,需要使用av_image_fill_arrays分配 
	pFrameRGB = av_frame_alloc();

	out_buffer_yuv420p = (unsigned char *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P,
		pCodecCtx->width, pCodecCtx->height, 1));//分配一幀的影象儲存空間
	out_buffer_rgb24 = (unsigned char *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_BGR24/*設定成AV_PIX_FMT_RGB24則顏色不正,顏色位序的問題*/,
		pCodecCtx->width, pCodecCtx->height, 1));//分配一幀的影象儲存空間

	av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, out_buffer_yuv420p,
		AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);//配置空間
	av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize, out_buffer_rgb24,
		AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height, 1);//配置空間



	//Output Info-----------------------------
	printf("---------------- File Information ---------------\n");
	av_dump_format(pFormatCtx, 0, filepath, 0);
	printf("-------------------------------------------------\n");

	//建立用於縮放和轉換操作的上下文
	//sws_getContext是初始化函式,初始化你需要轉換的格式,目的是為了獲取返回的SwsContext指標變數,給後面的sws_scale使用  
	//sws_scale會根據你初始化的資訊來轉換視訊格式,可以改變視訊格式,也可以改變視訊的解析度,因為如果想要視窗縮小,需要將  
	//解析度改成相應大小  
	//1.這裡是將解碼後的視訊流轉換成YUV420P  
	img_convert_ctx_yuv420p = sws_getContext(pCodecCtx->width/*視訊寬度*/, pCodecCtx->height/*視訊高度*/,
		pCodecCtx->pix_fmt/*畫素格式*/,
		pCodecCtx->width/*目標寬度*/, pCodecCtx->height/*目標高度*/,
		AV_PIX_FMT_YUV420P/*目標格式*/,
		SWS_BICUBIC/*影象轉換的一些演算法*/, NULL, NULL, NULL);

	img_convert_ctx_rgb24 = sws_getContext(pCodecCtx->width/*視訊寬度*/, pCodecCtx->height/*視訊高度*/,
		pCodecCtx->pix_fmt/*畫素格式*/,
		pCodecCtx->width/*目標寬度*/, pCodecCtx->height/*目標高度*/,
		AV_PIX_FMT_BGR24/*目標格式*/,
		SWS_BICUBIC/*影象轉換的一些演算法*/, NULL, NULL, NULL);

	/*
	簡單解釋各個變數的作用:
	SDL_Window就是使用SDL的時候彈出的那個視窗。在SDL1.x版本中,只可以建立一個一個視窗。在SDL2.0版本中,可以建立多個視窗。
	SDL_Texture用於顯示YUV資料。一個SDL_Texture對應一幀YUV資料。
	SDL_Renderer用於渲染SDL_Texture至SDL_Window。
	SDL_Rect用於確定SDL_Texture顯示的位置。注意:一個SDL_Texture可以指定多個不同的SDL_Rect,這樣就可以在SDL_Window不同位置顯示相同的內容(使用SDL_RenderCopy()函式)。
	*/

	//初始化SDL------------------------------------------
	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
		printf("Could not initialize SDL - %s\n", SDL_GetError());
		return -1;
	}
	//設定視窗尺寸
	//SDL 2.0 Support for multiple windows
	screen_w = pCodecCtx->width;
	screen_h = pCodecCtx->height;
	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, pCodecCtx->width, pCodecCtx->height);

	sdlRect.x = 0;
	sdlRect.y = 0;
	sdlRect.w = screen_w;
	sdlRect.h = screen_h;

	packet = (AVPacket *)av_malloc(sizeof(AVPacket));//分配空間儲存解碼器的資料

	video_tid = SDL_CreateThread(sfp_refresh_thread, NULL, NULL);
	//------------SDL End------------
	//Event Loop

	//解碼並顯示
	int j = 0;
	for (;;) {
		//Wait
		SDL_WaitEvent(&event);
		if (event.type == SFM_REFRESH_EVENT) {
			while (1) {
				if (av_read_frame(pFormatCtx, packet) < 0)//讀取原始資料(此時還沒有解碼)放到packet中
					thread_exit = 1;//讀取完畢,退出執行緒

				if (packet->stream_index == videoindex) //如果這個是一個視訊流資料則解碼,否則繼續迴圈查詢視訊流 
					break;
			}
			ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);//從packet解碼一幀到pFrame
			if (ret < 0) {
				printf("Decode Error.\n");
				//return -1;				//不一定退出!
			}
			if (got_picture) {//這個標誌表示已經讀取了一個完整幀,因為讀取一個packet不一定就是一個完整幀,如果不完整需要繼續讀取packet  
				sws_scale(img_convert_ctx_yuv420p, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);//格式轉換函式,可以轉換視訊格式,也可以用來改變視訊的解析度  

				//SDL---------------------------
				SDL_UpdateTexture(sdlTexture, NULL, pFrameYUV->data[0], pFrameYUV->linesize[0]);
				SDL_RenderClear(sdlRenderer);
				//SDL_RenderCopy( sdlRenderer, sdlTexture, &sdlRect, &sdlRect );  
				SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, NULL);
				SDL_RenderPresent(sdlRenderer);
				//SDL End-----------------------
#if OUTPUT_JPG
				//SaveAsJPEG(pFrame, pCodecCtx->width, pCodecCtx->height, j++);
#else
				//儲存為BMP時需要反轉影象 ,否則生成的影象是上下調到的  
				pFrame->data[0] += pFrame->linesize[0] * (pCodecCtx->height - 1);
				pFrame->linesize[0] *= -1;
				pFrame->data[1] += pFrame->linesize[1] * (pCodecCtx->height / 2 - 1);
				pFrame->linesize[1] *= -1;
				pFrame->data[2] += pFrame->linesize[2] * (pCodecCtx->height / 2 - 1);
				pFrame->linesize[2] *= -1;

				//轉換影象格式,將解壓出來的YUV420P的影象轉換為BRG24的影象  
				sws_scale(img_convert_ctx_rgb24, pFrame->data,
					pFrame->linesize, 0, pCodecCtx->height,
					pFrameRGB->data, pFrameRGB->linesize);

				SaveAsBMP(pFrameRGB, pCodecCtx->width, pCodecCtx->height, j++, 24);
#endif // OUTPUT_JPG
			}
			av_free_packet(packet);//釋放的是packet->buf,而不是packet!packet->buf是av_read_frame分配的。
		}
		else if (event.type == SDL_KEYDOWN) {
			//Pause
			if (event.key.keysym.sym == SDLK_SPACE)
				thread_pause = !thread_pause;
		}
		else if (event.type == SDL_QUIT) {
			thread_exit = 1;
		}
		else if (event.type == SFM_BREAK_EVENT) {
			break;
		}

	}

	//當av_read_frame()迴圈退出的時候,實際上解碼器中可能還包含剩餘的幾幀資料。因此需要通過“flush_decoder”將這幾幀資料輸出。
	//flush decoder
	//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_yuv420p, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
			pFrameYUV->data, pFrameYUV->linesize);
		//SDL---------------------------
		SDL_UpdateTexture(sdlTexture, &sdlRect, pFrameYUV->data[0], pFrameYUV->linesize[0]);
		SDL_RenderClear(sdlRenderer);
		SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, &sdlRect);
		SDL_RenderPresent(sdlRenderer);
		//SDL End-----------------------

#ifdef OUTPUT_JPG
		//SaveAsJPEG(pFrame, pCodecCtx->width, pCodecCtx->height, j++);
#else
		//儲存為BMP時需要反轉影象 ,否則生成的影象是上下顛倒的  
		pFrame->data[0] += pFrame->linesize[0] * (pCodecCtx->height - 1);
		pFrame->linesize[0] *= -1;
		pFrame->data[1] += pFrame->linesize[1] * (pCodecCtx->height / 2 - 1);
		pFrame->linesize[1] *= -1;
		pFrame->data[2] += pFrame->linesize[2] * (pCodecCtx->height / 2 - 1);
		pFrame->linesize[2] *= -1;

		//轉換影象格式,將解壓出來的YUV420P的影象轉換為BRG24的影象  
		sws_scale(img_convert_ctx_rgb24, pFrame->data,
			pFrame->linesize, 0, pCodecCtx->height,
			pFrameRGB->data, pFrameRGB->linesize);

		SaveAsBMP(pFrameRGB, pCodecCtx->width, pCodecCtx->height, j++, 24);
#endif // OUTPUT_JPG

		//Delay 40ms
		SDL_Delay(DELAY_PER_FRAME);
	}

	sws_freeContext(img_convert_ctx_yuv420p);
	sws_freeContext(img_convert_ctx_rgb24);

	SDL_Quit();
	//--------------
	av_frame_free(&pFrameYUV);
	av_frame_free(&pFrameRGB);
	av_frame_free(&pFrame);
	avcodec_close(pCodecCtx);
	avformat_close_input(&pFormatCtx);

	return 0;
}

int main(int argc, char* argv[]) {
	AfxWinInit(::GetModuleHandle(NULL), NULL, NULL, 0);
	AfxSocketInit();
	CUdpSocket *pMySock=new CUdpSocket(&linkedQueue);
	pMySock->Create(8888, SOCK_DGRAM);
	pMySock->Bind(8888, NULL);	//繫結本地套介面
	
	//設定接收廣播訊息
	BOOL bBroadcast = TRUE;
	pMySock->SetSockOpt(SO_BROADCAST, (const char*)&bBroadcast, sizeof(BOOL), SOL_SOCKET);

	CreateThread(NULL, 0, MyThreadFunction, NULL,0, NULL);

	while (1)
	{
		MSG msg;
		while (PeekMessage(&msg, 0, 0, 0, PM_NOREMOVE))
		{
			if (GetMessage(&msg, 0, 0, 0))
				DispatchMessage(&msg);
		}
	}
}