`
mmdev
  • 浏览: 13337926 次
  • 性别: Icon_minigender_1
  • 来自: 大连
文章分类
社区版块
存档分类
最新评论

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

分享到:
评论

相关推荐

    利用ffmpeg x264 编解码h264

    FFmpeg和x264是两个在...总的来说,FFmpeg结合x264库为我们提供了强大的H.264编解码能力,可以用于视频转码、分析、提取等多样化任务。在实际应用中,还需要根据具体需求调整编解码参数,以达到最佳的性能和质量平衡。

    h264Decoder

    H264Decoder是一个专注于处理H.264编码视频的动态链接库,它依赖于著名的FFmpeg多媒体处理框架。H.264,也称为AVC(Advanced Video Coding),是一种高效、高压缩率的视频编码标准,广泛应用于高清视频流媒体、蓝光...

    c++使用ffmpeg把h264/h265和mp3写入mp4文件

    本主题聚焦于如何使用C++与FFmpeg库来将H264、H265编码的视频流和MP3音频流合并并写入到MP4文件中。H264和H265是两种高效的视频编码标准,而MP3是一种广泛接受的音频编码格式。MP4文件格式则允许同时存储视频和音频...

    基于ffmpeg内核的H.264实时视频解码器开发 (2014年)

    为了解决这个问题,作者提出了一种基于ffmpeg内核的H.264实时视频解码方法。这里需要了解的两个关键技术点是H.264编码技术和ffmpeg库。 H.264编码技术是目前广泛应用于视频编解码的标准之一,它能够以较低的数据率...

    264文件播放器

    相比于之前的MPEG-2等编码标准,H.264通过更复杂的编码算法,如运动估计、熵编码和多参考帧等,实现了在相同带宽下更高的视频质量,或者在同等画质下更小的文件大小。这使得H.264成为网络流媒体、DVD、蓝光光盘以及...

    ffmpeg_ffmpeg_QTqtffmpeg_qt+ffmpeg_QT_DEMO_

    描述中的“在qt中测试ffmpeg”进一步确认了这一点,意味着我们将探讨如何在QT应用中使用FFmpeg库来实现音视频的编解码、播放、录制等功能。 在提供的文件列表中,我们可以看到几个关键的文件: 1. `ffmpeg.pro....

    [免费PDF]FFMPEG教程完美排版.rar

    4. **视频和音频编解码**:介绍FFmpeg支持的各种编解码器,包括常见的H.264视频编码和AAC音频编码,以及如何通过命令行选择合适的编解码器进行转码。 5. **视频和音频转换**:如何使用FFmpeg将不同格式的媒体文件...

    ffmpeg help full_Help!_ffmpeg_

    3. **视频编码与解码**:FFmpeg支持多种编码器,如H.264、H.265(HEVC)、VP9等。可以使用`-c:v codec_name`选项选择编码器。 4. **音频编码与解码**:对于音频,有AAC、Vorbis、Opus等编码器。使用`-c:a codec_...

    opencv 3.4.12 缺少的ffmpeg库文件

    2. **视频编码和解码**:FFmpeg提供了大量的编解码器,使得OpenCV能够处理不同编码标准的视频流,如H.264、MPEG-4等。 3. **流处理**:FFmpeg库还支持网络流媒体,使得OpenCV可以处理来自网络摄像头或其他在线流的...

    test-ffmpeg.rar

    FFmpeg 支持多种编解码器,如 H.264、HEVC(H.265)、VP9 等。例如,将一个 MP4 视频转码为 WebM 格式,可以使用命令 `ffmpeg -i input.mp4 -c:v libvpx-vp9 -crf 30 -b:v 0 -c:a libvorbis output.webm`。 **添加...

    Android上的ffmpeg

    对于“支持 X264”这一点,X264 是一个高效的 H.264 视频编码库。H.264 是一种广泛使用的视频编码标准,以其高质量和高压缩率著称。X264 提供了非常出色的编码性能,特别是在低比特率下。在 Android 上使用 FFmpeg ...

    ffmpeg decoder

    9. **兼容性**:一个好的解码器应具备广泛的格式支持,能够处理不同的编码标准,如 H.264、AV1、AAC、Opus 等,以确保能够播放各种来源的媒体内容。 总的来说,"ffmpeg decoder" 提供了一个高度定制的、适合实时...

    video_player-源码.rar

    视频解码涉及到各种编码标准,如H.264、AV1、VP9等。源码中可能包含对这些标准的实现,解码器需要理解并解析视频帧的结构,如NAL单元(网络抽象层单元)在H.264中的使用,以及如何从中提取解码所需的宏块信息。 3....

    视频渲染yuanma.7z

    常见的视频编码标准有H.264、H.265(HEVC)、VP9以及AV1等。源码可能包含了这些编码算法的实现。 2. **视频解码**:解码是编码的逆过程,将编码后的数据还原成可观看的视频帧。源码中可能包括了解码器的设计,用于...

    CGFileCut.rar

    FFmpeg是一款强大的、开源的多媒体处理工具,广泛用于视频、音频的编码、解码、转换、流媒体处理等任务。在“CGFileCut.rar”这个压缩包中,我们看到它与视频裁剪有关,特别是针对指定帧数的视频截取。下面我们将...

    屏幕录制源码

    3. 视频编码:FFMPEG支持多种视频编码格式,如H.264、VP9等。这些编码器能够将连续的帧压缩成更小的文件,同时保持足够的视觉质量。编码过程通常涉及帧类型选择(I帧、P帧、B帧)、熵编码和运动补偿等步骤。 4. ...

    视频处理工具-视频编辑

    这个过程涉及视频编码标准(如H.264、AV1)、容器格式(如MP4、MKV)以及解码器的使用。播放器软件如VLC、PotPlayer等,它们内置了多种解码器,能够支持各种格式的视频文件。 **视频裁剪**: 视频裁剪是编辑过程中...

    视频传输 C#

    常见的编码标准有H.264、AV1、VP9等。编码后的视频数据以流的形式传输,到达接收端后,再进行解码恢复成原始视频。 2. **C#编程基础**:C#是一种面向对象的编程语言,由微软开发,广泛用于Windows平台的软件开发。...

    家庭视频监控系统完整源代码

    常见的视频编码标准有H.264和HEVC,这些编码器可以在C#中通过第三方库如FFmpeg实现。 3. **流媒体服务器**:系统需要一个服务器来接收和分发视频流。这可能涉及到RTSP(实时流协议)或HTTP Live Streaming (HLS)等...

    c语言做的播放器源码毕业设计—(包含完整源码可运行).rar

    2. 数据解码:不同格式的媒体文件使用不同的编码标准,如MP3使用MPEG音频编码,视频可能使用H.264或VP9等。C语言库如FFmpeg可以提供这些解码功能。 3. 缓冲管理:为了确保流畅的播放体验,播放器需要管理内部缓冲区...

Global site tag (gtag.js) - Google Analytics