精簡的FFMPEG從UDP廣播接收視訊資料並播放的示例
阿新 • • 發佈:2019-02-20
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);
}
}
}