h.264 視頻解碼的一點小經驗(ffmpeg)

最近做視頻文件264解碼,由于對這個領域不是很熟悉,感覺困難重重。不過經過不懈的努力,已經取得一些進展,心里感覺特別慶幸。 剛開始做這個的時候,由于不熟悉,就在網上搜尋資料,網絡上的資料雖然多,但是卻很雜亂,因此一開始走了不少彎路,現在把我的一點小小心得寫出來,后來的兄弟們可以參考一下,沒準能夠少走些彎路。當然啦,我在視頻處理方面仍然是個非常菜的菜鳥,如果是高手路過,看到我這所謂的“心得”,也請不要見笑,看到不對的地方請批評指正,呵呵。

剛開始做的時候,先是在網絡上查找資料,我覺得有一篇文章非常的有用,因為當時我最需要了解的就是世界上現存的各種編解碼器,每種都有什么特性,比如說解碼速度是否能夠滿足實時播放的需求、對h.264標準的支持程度等等。這篇文章就是《H.264開源解碼器評測》,這篇文章詳細的評測了當今流行的幾種h.264解碼器,包括JM Decoder,T264 Decoder,X264 Decoder,ffmpeg libavcodec和Intel的IPP庫,經過作者的評測,發現速度最快的就是intel IPP了,但是intel IPP屬于商品化軟件,而其他的各種解碼器都屬于開源項目,所以最適合選擇的就是解碼速度第二的ffmpeg了,而且其速度完全可以滿足實時播放的要求;

選擇好了解碼器,第一步算是完成了,第二步就是研究ffmpeg的用法了。經過摸索,我的選擇是:到中華視頻網下在ffmpeg SDK 2.0,這恐怕是目前最適合在VC++6下使用的基于ffmpeg的SDK了,其易用性比較好。

第三步就是編寫播放器外殼了,外殼代碼采用VC++6編寫,我會在文張末尾給出外殼的所有代碼;注意:外科代碼獲取的lpdata是windows內存位圖,具有dword對齊的特性,另外,解碼出的圖像是倒立的,因此我專門寫了一個把圖像倒轉的函數,運行速度還是挺快的,完全不妨礙實時播放;

上一階段的工作完成得還算滿意,下一階段的工作就是h.264 的 RTP payload協議了。

附錄:

h.264播放的外殼代碼-------------------------------------------------------------------------------------------------

// Decode264.cpp : Defines the initialization routines for the DLL.

#include "stdafx.h"
#include "Decode264.h"

//以下代碼為自己添加
#include <stdlib.h>
#include <time.h>
#include "avformat.h"
#include "avcodec.h"
#include <windows.h>

//定義目標格式
#define DEST_FORMAT PIX_FMT_BGR24
//PIX_FMT_YUV420P

//定義全局變量
AVFormatContext *pFormatCtx; //
int i, videoStream;
AVCodecContext *pCodecCtx;
AVCodec *pCodec; //編解碼器
AVFrame *pFrame; //幀
AVFrame *pFrameYUV; //YUV幀

clock_t t;
double fps;
int y_size, i_frame=0;
int numBytes;
uint8_t *buffer;

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

//
// Note!
//
// If this DLL is dynamically linked against the MFC
// DLLs, any functions exported from this DLL which
// call into MFC must have the AFX_MANAGE_STATE macro
// added at the very beginning of the function.
//
// For example:
//
// extern "C" BOOL PASCAL EXPORT ExportedFunction()
// {
// AFX_MANAGE_STATE(AfxGetStaticModuleState());
// // normal function body here
// }
//
// It is very important that this macro appear in each
// function, prior to any calls into MFC. This means that
// it must appear as the first statement within the
// function, even before any object variable declarations
// as their constructors may generate calls into the MFC
// DLL.
//
// Please see MFC Technical Notes 33 and 58 for additional
// details.
//

/
// CDecode264App

BEGIN_MESSAGE_MAP(CDecode264App, CWinApp)
//{{AFX_MSG_MAP(CDecode264App)
// NOTE - the ClassWizard will add and remove mapping macros here.
// DO NOT EDIT what you see in these blocks of generated code!
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/
// CDecode264App construction

CDecode264App::CDecode264App()
{
// TODO: add construction code here,
// Place all significant initialization in InitInstance
}

/
// The one and only CDecode264App object

CDecode264App theApp;

//以下代碼為自己添加/

//把圖像倒立過來;
long UpendBmp(unsigned char *lpdata,long width ,long height)
{

long lBPL;//每行的字節數,因為要考慮dword對齊
long x,y,idx_src,idx_dest;
unsigned char *tmpdata;

if (0==((width*3)%4)) //nWidth * 3 是存儲每行像素需要的字節數,如果是4的整數倍。
lBPL = (width*3); //那么返回 nWidth * 3 ,就是每行的字節數
else //如果不是4的整數倍,那么就一定要加上一個數,達到4的整數倍,才是每行的字節數。
lBPL = (width*3+(4-((width*3)%4)));

tmpdata= new unsigned char[lBPL * height];

x =0;
for (y=0 ; y<height ; y++)
{
idx_src =(height-1-y)*lBPL;//idx_src =(height-1-y)*lBPL+x*3;優化前
idx_dest=y*lBPL;//idx_dest=y*lBPL+x*3;優化前
memcpy(&tmpdata[idx_dest],&lpdata[idx_src],lBPL);//復制一行
}

memcpy(lpdata,tmpdata,lBPL * height);
delete[] tmpdata;

return 0;
}

//創建一個bmp文件。用于調試
static int av_create_bmp(char* filename,uint8_t *pRGBBuffer,
int width,int height,int bpp)
{
BITMAPFILEHEADER bmpheader;
BITMAPINFO bmpinfo;
FILE *fp;

fp = fopen(filename,"wb");
if(!fp)return -1;

bmpheader.bfType = (''M''<<8)|''B'';
bmpheader.bfReserved1 = 0;
bmpheader.bfReserved2 = 0;
bmpheader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
bmpheader.bfSize = bmpheader.bfOffBits + width*height*bpp/8;

bmpinfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmpinfo.bmiHeader.biWidth = width;
bmpinfo.bmiHeader.biHeight = height;
bmpinfo.bmiHeader.biPlanes = 1;
bmpinfo.bmiHeader.biBitCount = bpp;
bmpinfo.bmiHeader.biCompression = BI_RGB;
bmpinfo.bmiHeader.biSizeImage = 0;
bmpinfo.bmiHeader.biXPelsPerMeter = 100;
bmpinfo.bmiHeader.biYPelsPerMeter = 100;
bmpinfo.bmiHeader.biClrUsed = 0;
bmpinfo.bmiHeader.biClrImportant = 0;

fwrite(&bmpheader,sizeof(BITMAPFILEHEADER),1,fp);
fwrite(&bmpinfo.bmiHeader,sizeof(BITMAPINFOHEADER),1,fp);
fwrite(pRGBBuffer,width*height*bpp/8,1,fp);
fclose(fp);

return 0;
}

//獲取下一幀
static bool GetNextFrame(AVFormatContext *pFormatCtx,
AVCodecContext *pCodecCtx,
int videoStream,
AVFrame *pFrame)
{
static AVPacket packet; //AV包。靜態變量。
static int bytesRemaining=0; //字節剩余。靜態變量。
static uint8_t *rawData; //原始數據字節數。靜態變量。
static bool fFirstTime=true; //標志,第一次;。靜態變量。
int bytesDecoded; //解碼后獲得的字節;
int frameFinished; //幀解碼完畢標志;

// First time we''re called, set packet.data to NULL to indicate it
// doesn''t have to be freed 當第一次被調用的時候,把packet.data設置為NULL,以表示
//它沒有必要被釋放;
if (fFirstTime){
fFirstTime = false;
packet.data = NULL;
}

//解碼那些包,直到我們解碼出一個完整的幀;
// Decode packets until we have decoded a complete frame
while (true)
{
//在當前包上工作,直到我們解碼出所有的。
//Work on the current packet until we have decoded all of it
while (bytesRemaining > 0)
{
// Decode the next chunk of data 解碼出下一個數據塊
bytesDecoded = avcodec_decode_video(pCodecCtx, pFrame,
&frameFinished, rawData, bytesRemaining);

// Was there an error?
if (bytesDecoded < 0){
fprintf(stderr, "Error while decoding frame\\n");
return false;
}

bytesRemaining -= bytesDecoded;
rawData += bytesDecoded;

// Did we finish the current frame? Then we can return
if (frameFinished) //如果我們完成了當前幀的解碼,就可以返回了
return true;
}

//讀取下一個包,跳過所有的不是屬于這個流的包;
// Read the next packet, skipping all packets that aren''t for this
// stream
do{
// Free old packet 釋放舊包
if(packet.data != NULL)
av_free_packet(&packet);

// Read new packet 讀取新包
if(av_read_frame(pFormatCtx, &packet) < 0)
goto loop_exit;
} while(packet.stream_index != videoStream); //當不是要找的視頻流的時候,繼續循環,就是重新讀了;
//直到找到要找的視頻流,退出循環;

bytesRemaining = packet.size; //紀錄包的字節數;
rawData = packet.data; //
}

loop_exit:

// Decode the rest of the last frame
bytesDecoded = avcodec_decode_video(pCodecCtx, pFrame, &frameFinished,
rawData, bytesRemaining);

// Free last packet
if(packet.data != NULL)
av_free_packet(&packet);

return frameFinished != 0;
}

//對外的API接口。打開264文件,并且獲取必要的信息,比如寬度高度幀數等等
long __stdcall open264file(char *filename,long *out_width ,
long *out_height,long *out_framenum,
long *out_bufsize)
{


// Register all formats and codecs 注冊所有的格式和編解碼器
av_regi[FS:PAGE]ster_all();

// Open video file//打開視頻文件
if (av_open_input_file(&pFormatCtx, filename, NULL, 0, NULL) != 0)
return -1; // Couldn''t open file如果不能打開,那么返回-1

// Retrieve stream information 取流信息
if (av_find_stream_info(pFormatCtx) < 0)
return -1; // Couldn''t find stream information

// Dump information about file onto standard error
dump_format(pFormatCtx, 0, filename, false);

t = clock();

// Find the first video stream 尋找第一個視頻流
videoStream = -1;
for (i=0; i<pFormatCtx->nb_streams; i++)
if(pFormatCtx->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO){
videoStream=i;
break;
}
if (videoStream == -1)
return -1; // Didn''t find a video stream

//獲取該視頻流的一個編解碼器上下文的指針;
// Get a pointer to the codec context for the video stream
pCodecCtx = pFormatCtx->streams[videoStream]->codec;

// Find the decoder for the video stream 獲取解碼器
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);

if (pCodec == NULL)
return -1; // Codec not found解碼器沒有找到;

//告知解碼器,我們能處理被刪節的位流
// 也就是說,幀的分界處的位流可以落到包的中間;
// Inform the codec that we can handle truncated bitstreams -- i.e.,
// bitstreams where frame boundaries can fall in the middle of packets
if ( pCodec->capabilities & CODEC_CAP_TRUNCATED )
pCodecCtx->flags|=CODEC_FLAG_TRUNCATED;

// Open codec //打開解碼器
if ( avcodec_open(pCodecCtx, pCodec) < 0 )
return -1; // Could not open codec 不能打開解碼器,返回-1;

// Allocate video frame 分配視頻幀
pFrame = avcodec_alloc_frame();

// Allocate an AVFrame structure 分配一個AVFrame結構
pFrameYUV=avcodec_alloc_frame(); //解碼后的幀
if(pFrameYUV == NULL)
return -1;

//決定需要多大的緩沖空間,并且分配空間;
// Determine required buffer size and allocate buffer
numBytes=avpicture_get_size(DEST_FORMAT, pCodecCtx->width,
pCodecCtx->height);
buffer = (uint8_t*)malloc(numBytes);

//向外界輸出寬高、幀數;
*out_width = pCodecCtx->width;
*out_height = pCodecCtx->height;
*out_framenum = pCodecCtx->frame_number;
*out_bufsize = numBytes;

// Assign appropriate parts of buffer to image planes in pFrameRGB
//把緩沖區中合適的部分指派到pFrameRGB中的圖像面板
avpicture_fill((AVPicture *)pFrameYUV, buffer, DEST_FORMAT,
pCodecCtx->width, pCodecCtx->height);

return 0;
}

//對外的API接口。關閉264文件,釋放相關資源
long __stdcall close264file()
{
//calculate decode rate 計算解碼速率
t = clock() - t;
fps = (double)(t) / CLOCKS_PER_SEC;
fps = i_frame / fps;
printf("\\n==>Decode rate %.4f fps!\\n", fps);

// Free the YUV image 釋放yuv圖像
free(buffer);
av_free(pFrameYUV);

// Free the YUV frame 釋放yuv幀
av_free(pFrame);
// Close the codec 關閉解碼器
avcodec_close(pCodecCtx);
// Close the video file 關閉視頻文件
av_close_input_file(pFormatCtx);

return 0;
}

//對外的API接口。獲取一幀解碼后的數據
long __stdcall GetNextFrame(unsigned char *lpdata)
{

// Read frames 讀取個個幀
if (GetNextFrame(pFormatCtx, pCodecCtx, videoStream, pFrame))
{
img_convert((AVPicture *)pFrameYUV, DEST_FORMAT, (AVPicture*)pFrame,
pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);


//調試用,向C盤寫入一個bmp文件;
//av_create_bmp("c:\\\\1.bmp",(unsigned char *)pFrameYUV->data[0],pCodecCtx->width,pCodecCtx->height,24);

i_frame++;
y_size = pCodecCtx->width * pCodecCtx->height;

//寫入文件
/*fwrite(pFrameYUV->data[0], 1, y_size, fp);
fwrite(pFrameYUV->data[1], 1, (y_size/4), fp);
fwrite(pFrameYUV->data[2], 1, (y_size/4), fp);*/
memcpy(lpdata,pFrameYUV->data[0],y_size*3);
UpendBmp(lpdata,pCodecCtx->width,pCodecCtx->height);
return 0;
}
else
{return -1;}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/254945.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/254945.shtml
英文地址,請注明出處:http://en.pswp.cn/news/254945.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

HALCON示例程序novelty_detection_dyn_threshold.hdev紗網缺陷檢測

HALCON示例程序novelty_detection_dyn_threshold.hdev紗網缺陷檢測 示例程序源碼&#xff08;加注釋&#xff09; 關于顯示類函數解釋 dev_update_window (‘off’) read_image (Image, ‘plastic_mesh/plastic_mesh_01’) dev_close_window () get_image_size (Image, Width…

配置云服務器 FTP 服務

自己配置的環境: OS: 阿里云 CentOS 6.5 >>Begin: 1. 登錄到阿里云服務器(如何登錄阿里云服務器), 在root權限下, 通過如下命令安裝 vsftp [rootVM_250_202_tlinux ~]# yum install vsftpd 2. 在啟動vsftpd服務之前&#xff0c;需要登錄云服務器修改配置文件&#xff0c;…

【躍遷之路】【428天】程序員高效學習方法論探索系列(實驗階段185-2018.04.09)...

(躍遷之路)專欄 實驗說明 從2017.10.6起&#xff0c;開啟這個系列&#xff0c;目標只有一個&#xff1a;探索新的學習方法&#xff0c;實現躍遷式成長實驗期2年&#xff08;2017.10.06 - 2019.10.06&#xff09;我將以自己為實驗對象。我將開源我的學習方法&#xff0c;方法不斷…

opencv中的一些陷阱 坑死我了~~~~(_)~~~~

1.這幾天被opencv給坑的夠慘&#xff0c;好好的程序&#xff0c;先是因為imread&#xff08;&#xff09;不能讀文件&#xff0c;整了很久沒整出來&#xff0c;然后改了下path路徑&#xff0c;沒想到后面徹底奔潰了&#xff0c;&#xff0c;&#xff0c;&#xff0c;前后大概2天…

一篇需要膜拜的文篇--Javascript異步編程模型進化(轉)

要我能用得這么熟&#xff0c; 那前端出師了哈。 http://foio.github.io/javascript-asyn-pattern/ 改天一個一個親測一下。 Javascript語言是單線程的&#xff0c;沒有復雜的同步互斥&#xff1b;但是&#xff0c;這并沒有限制它的使用范圍&#xff1b;相反&#xff0c;借助于…

很強大的FFMPEG API Documentation

http://wiki.aasimon.org/doku.php?idffmpeg:ffmpeg 點擊打開鏈接

HALCON示例程序obj_diff.hdev算子obj_diff 的使用

HALCON示例程序obj_diff.hdev算子obj_diff 的使用 示例程序源碼&#xff08;加注釋&#xff09; 關于顯示類函數解釋 read_image (Image, ‘particle’)二值化 threshold (Image, Region, 57, 255)分割連通域 connection (Region, ConnectedRegions) dev_close_window () get…

JS函數方法Call Apply Bind運用

JS 函數非繼承的call和apply方法 同&#xff1a;call & apply 主要是用于擴展this指向&#xff0c;降低this作用域與函數之間的耦合度&#xff1b; 區別&#xff1a;傳參差異 function.call(this/object,params1,params2,...) 第一個參數為作用域指向參數&#xff0c;后邊參…

IplImage, CvMat, Mat 的關系和相互轉換 再次理解 /(ㄒoㄒ)/~~

opencv中常見的與圖像操作有關的數據容器有Mat&#xff0c;cvMat和IplImage&#xff0c;這三種類型都可以代表和顯示圖像&#xff0c;但是&#xff0c;Mat類型側重于計算&#xff0c;數學性較高&#xff0c;openCV對Mat類型的計算也進行了優化。而CvMat和IplImage類型更側重于“…

HALCON示例程序optical_flow.hdev如何使用optical_flow_mg計算圖像序列中的光流以及如何分割光流。

HALCON示例程序optical_flow.hdev如何使用optical_flow_mg計算圖像序列中的光流以及如何分割光流。 示例程序源碼&#xff08;加注釋&#xff09; 關于顯示類函數解釋 dev_update_off () dev_close_window () read_image (Image1, ‘xing/xing000’) dev_open_window_fit_ima…

數字信號處理原理

關于傅里葉變換的解釋&#xff0c;在下面的鏈接&#xff1a;http://blog.jobbole.com/70549/ 。講的挺詳細的&#xff1a; 注意點&#xff1a; 1、信號處理基于這么一個概念&#xff0c;待處理的信號&#xff08;&#xff1f;&#xff09;都可以分解為正弦波&#xff0c;不同…

webpack的一些常用配置 (轉)

webpack 的配置文件就是 Node 的一個模塊&#xff0c;它導出的將是一個對象 module.exports {entry: ./entry,output: {path: path.resolve(__dirname, dist),filename: bundle.js} }如果直接使用 webpack 來執行編譯&#xff0c;webpack 默認讀取的是當前目錄下的 webpack.co…

CvMat,Mat和IplImage之間的轉化和拷貝

1、CvMat之間的復制 //注意&#xff1a;深拷貝 - 單獨分配空間&#xff0c;兩者相互獨立 CvMat* a; CvMat* b cvCloneMat(a); //copy a to b 2、Mat之間的復制 //注意&#xff1a;淺拷貝 - 不復制數據只創建矩陣頭&#xff0c;數據共享&#xff08;更改a,b,c的任意一…

HALCON示例程序particle.hdev測量小圓部分

HALCON示例程序particle.hdev測量小圓部分 示例程序源碼&#xff08;加注釋&#xff09; 關于顯示類函數解釋 dev_update_off () dev_close_window () dev_open_window (0, 0, 512, 512, ‘black’, WindowID) set_display_font (WindowID, 14, ‘mono’, ‘true’, ‘false’…

Java List 分頁

//分頁&#xff0c;根據country或者site分br/>Overridepublic List<Integer> getSitesPage(Integer parentLevel, Integer currentPage) {List<Integer> subFrames getSites(parentLevel) ;int currentNum ( currentPage - 1 ) * CardViewUtil.PREPAGE_NUM ;D…

跟多導出數據庫的方法

鏈接&#xff1a;http://www.2cto.com/database/201207/139330.html轉載于:https://www.cnblogs.com/nycj/p/5661151.html

rtp協議詳解/rtcp協議詳解

、簡介 目前&#xff0c;在IP網絡中實現實時語音、視頻通信和應用已經成為網絡應用的一個主流技術和發展方向&#xff0c;本文詳細介紹IP協議族中用于實時語音、視頻數據傳輸的標準協議RTP&#xff08; Real-time Transport Protocol&#xff09;和RTCP&#xff08;RTP Control…

MVC開發中的常見錯誤-04-“System.NullReferenceException”類型的異常在 BBFJ.OA.WebApp.dll 中發生,但未在用戶代碼中進行處理...

未將對象引用設置到對象實例,又名空指針異常,伴隨程序員開發的一生. 查看詳細信息得知: SetUserRoleInfo() 首先想到的是 IBLL.IRoleInfoService RoleInfoService { set; get; }應該是config文件中反射出現了問題 <?xml version"1.0" encoding"utf-8"…

HALCON示例程序pcb_inspection.hdev檢測pcb印刷缺陷

HALCON示例程序pcb_inspection.hdev檢測pcb印刷缺陷 示例程序源碼&#xff08;加注釋&#xff09; 關于顯示類函數解釋 read_image (Image, ‘pcb’) dev_close_window () get_image_size (Image, Width, Height) dev_open_window (0, 0, Width, Height, ‘black’, WindowHa…

profibus GSD文件詳解

profibus GSD文件詳解 2015-6-19 通過PROFIBUS DP用功能塊在主、從站之間實現雙向數據傳送&#xff1a;在主站PLC可以通過調用SFC14“DPRD_DAT”和SFC15“DPWR_DAT”來完成和從站的數據交換&#xff0c;而對于從站來說可以調用FC1“DP_SEND”和FC2“DP_RECV”完成數據的交換。 …