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);
}
}
}