精简的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);
		}
	}
}







评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值