php将视频流逐帧转图片,ffmpeg sdk解码视频帧后保存成BMP或JPG的方法

ffmpeg解码视频的例子可以看官方自带的encode_decode.c。

官方解码保存成ppm,这里接下来保存成BMP或JPG。

原理:

保存BMP是解码成功后,从YUV420转成RGB24,然后构造文件头,BITMAPINFOHEADER,再写入图片数据。

保存JPG是解码成功后,得到的是YUV420, 然后再重编码成JPG (这个重编码过程几乎不占CPU资源,可以接受)。

(以下代码只是演示怎么处理,不一定能直接运行。感觉渔比鱼更重要,学会解决思路比直接抄代码更有利于提高)

....

//之前略,从解码成功开始,之前的见encode_decode。

AVFrame *decode_picture;

decode_picture= avcodec_alloc_frame();//av_frame_alloc();

int len = avcodec_decode_video2(decode_c, decode_picture, &got_picture, &pkt);

if (got_picture)

{

//解码成功,开始保存,下面两个分支:

1  saveBMP()

2  saveJPG

}

1 save BMP:

首先定义两个结构体,从MSDN里抄过来就行。(当前运行环境是Linux,如果是VC开发,系统自带这两个结构体)

typedef struct tagBITMAPFILEHEADER {

WORD  bfType;

DWORD bfSize;

WORD  bfReserved1;

WORD  bfReserved2;

DWORD bfOffBits;

} BITMAPFILEHEADER, *PBITMAPFILEHEADER;

typedef struct tagBITMAPINFOHEADER {

DWORD biSize;

LONG  biWidth;

LONG  biHeight;

WORD  biPlanes;

WORD  biBitCount;

DWORD biCompression;

DWORD biSizeImage;

LONG  biXPelsPerMeter;

LONG  biYPelsPerMeter;

DWORD biClrUsed;

DWORD biClrImportant;

} BITMAPINFOHEADER, *PBITMAPINFOHEADER;

WORD 和DWORD就是uint16_t和uint32_t。

具体保存过程:

saveBMP()

{

//接着if (got_picture):

//1 先进行转换,  YUV420=>RGB24:

int w = decode_c->width;

int h = decode_c->height;

int numBytes=avpicture_get_size(PIX_FMT_RGB24, w,h);

uint8_t * buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));

AVFrame *pFrameRGB;

pFrameRGB = avcodec_alloc_frame();

avpicture_fill((AVPicture *)pFrameRGB, buffer,PIX_FMT_RGB24,  w, h);

img_convert_ctx = sws_getCachedContext(img_convert_ctx,

w, h, (PixelFormat)(decode_picture->format), w, h,PIX_FMT_BGR24, sws_flags, NULL, NULL, NULL);

if (img_convert_ctx == NULL)

{

fprintf(stderr, "Cannot initialize the conversion context\n");

exit(1);

}

sws_scale(img_convert_ctx, decode_picture->data, decode_picture->linesize,

0, h, pFrameRGB->data, pFrameRGB->linesize);

//2 构造 BITMAPINFOHEADER

BITMAPINFOHEADER header;

header.biSize = sizeof(BITMAPINFOHEADER);

header.biWidth = w;

header.biHeight = h*(-1);

header.biBitCount = 24;

header.biCompression = 0;

header.biSizeImage = 0;

header.biClrImportant = 0;

header.biClrUsed = 0;

header.biXPelsPerMeter = 0;

header.biYPelsPerMeter = 0;

header.biPlanes = 1;

//3 构造文件头

BITMAPFILEHEADER bmpFileHeader;

HANDLE hFile = NULL;

DWORD dwTotalWriten = 0;

DWORD dwWriten;

bmpFileHeader.bfType = 0x4d42; //'BM';

bmpFileHeader.bfOffBits=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER);

bmpFileHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)+ numBytes;

FILE* pf = fopen("test.bmp", "wb");

fwrite(&bmpFileHeader, sizeof(BITMAPFILEHEADER), 1, pf);

fwrite(&header, sizeof(BITMAPINFOHEADER), 1, pf);

fwrite(pFrameRGB->data[0], 1, numBytes, pf);

fclose(pf);

//释放资源

av_free(buffer);

av_free(pFrameRGB);

}

如果感觉与原图色彩不一样,分别试试PIX_FMT_BGR24和PIX_FMT_RGB24。

如果图像是倒置的,自己写个方法正过来就行了。就是矩形上下对调。

2 saveJPG

//接着if (got_picture)

int numBytes=avpicture_get_size(PIX_FMT_YUVJ420P, decode_c->width, decode_c->height);

uint8_t *buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));

bool bret = WriteJPEG(decode_c, decode_picture, "test.jpg", PIX_FMT_YUVJ420P, buffer, numBytes);

//以下内容是以前google出来的,不过这几天google被墙,等以后找到后再补上具体出处。

bool WriteJPEG (AVCodecContext *pCodecCtx, AVFrame *pFrame, char cFileName[], PixelFormat pix, uint8_t *buffer, int numBytes)

{

bool bRet = false;

AVCodec *pMJPEGCodec=NULL;

AVCodecContext *pMJPEGCtx = avcodec_alloc_context();

if( pMJPEGCtx )

{

pMJPEGCtx->bit_rate = pCodecCtx->bit_rate;

pMJPEGCtx->width = pCodecCtx->width;

pMJPEGCtx->height = pCodecCtx->height;

pMJPEGCtx->pix_fmt = pix;

pMJPEGCtx->codec_id = CODEC_ID_MJPEG;

pMJPEGCtx->codec_type = AVMEDIA_TYPE_VIDEO;

pMJPEGCtx->time_base.num = pCodecCtx->time_base.num;

pMJPEGCtx->time_base.den = pCodecCtx->time_base.den;

pMJPEGCodec = avcodec_find_encoder(pMJPEGCtx->codec_id );

if( pMJPEGCodec && (avcodec_open( pMJPEGCtx, pMJPEGCodec) >= 0) )

{

pMJPEGCtx->qmin = pMJPEGCtx->qmax = 3;

pMJPEGCtx->mb_lmin = pMJPEGCtx->lmin = pMJPEGCtx->qmin * FF_QP2LAMBDA;

pMJPEGCtx->mb_lmax = pMJPEGCtx->lmax = pMJPEGCtx->qmax * FF_QP2LAMBDA;

pMJPEGCtx->flags |= CODEC_FLAG_QSCALE;

pFrame->quality = 10;

pFrame->pts = 0;

int szBufferActual = avcodec_encode_video(pMJPEGCtx, buffer, numBytes, pFrame);

if( SaveFrame(szBufferActual, buffer, cFileName ) )

bRet = true;

avcodec_close(pMJPEGCtx);

}

}

return bRet;

}

bool SaveFrame(int nszBuffer, uint8_t *buffer, char cOutFileName[])

{

//printf("SaveFrame nszBuffer = %d, cOutFileName = %s\n", nszBuffer, cOutFileName);

bool bRet = false;

if( nszBuffer > 0 )

{

FILE *pFile = pFile = fopen(cOutFileName, "wb");

if(pFile)

{

fwrite(buffer, sizeof(uint8_t), nszBuffer, pFile);

bRet = true;

fclose(pFile);

}

}

return bRet;

}

阅读(18992) | 评论(4) | 转发(2) |

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值