利用ffmpeg压缩屏幕图像为avi(录屏、压制)

看了雷神关于ffmpeg方面的文章,结合自己最近做的一个基与cdc抓屏、vfw压制的录屏工具,就想改为采用ffmpeg来压制avi。

关于如何用ffmpeg压制视频相信大家都有一些见解,这里写这篇文章最核心的东西是如何把采集的来的LPBITMAPINFOHEADER图像数据转换为ffmpeg所需要的AVFrame数据。

核心思想是利用sws_scale把图像从PIX_FMT_RGB32格式转换为PIX_FMT_YUV420P格式,下面是详细代码。

LPBITMAPINFOHEADER bmp;
memset(buffer, 0, numBytes);
uint8_t* tmpBuf = (uint8_t*)(bmp + 1);
for (int i = 0; i < pCodecCtx->height;i++)
memcpy(buffer + i * (pCodecCtx->width * 4), (tmpBuf + (pCodecCtx->width*4)*(pCodecCtx->height - i - 1)), (pCodecCtx->width * 4));
int ret1 = avpicture_fill((AVPicture *)pFrame, buffer, PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height);
SwsContext *img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, PIX_FMT_RGB32, pCodecCtx->width, 
pCodecCtx->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, picture->data, picture->linesize);

这里LPBITMAPINFOHEADER bmp;是通过采集得到的bmp图像数据。

for (int i = 0; i < pCodecCtx->height;i++)
memcpy(buffer + i * (pCodecCtx->width * 4), (tmpBuf + (pCodecCtx->width*4)*(pCodecCtx->height - i - 1)), (pCodecCtx->width * 4));

这句代码主要是把图像数据上下颠倒过来,因为bmp里面数据是上下颠倒的。

int ret1 = avpicture_fill((AVPicture *)pFrame, buffer, PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height);
SwsContext *img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, PIX_FMT_RGB32, pCodecCtx->width, 
pCodecCtx->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, picture->data, picture->linesize);

这三句就是整个代码的核心了,

先用avpicture_fill把buffer里面的图像数据填充到AVFrame *pFrame;里面去。

然后利用sws_getContext得到SwsContext *img_convert_ctx;

然后用得到的img_convert_ctx参数利用sws_sacle把pFrame数据转到AVFrame* picture;里面去,此时picture里面的数据就已经是满足编码的PIX_FMT_YUV420P格式了。

用avcodec_encode_video2(pCodecCtx, &pkt,picture, &got_picture);得到编码后的数据。

通过av_write_frame(pFormatCtx, &pkt);把编码后的数据写到文件中去。

至此一帧的采集、编码、写入就完成了。


下面把几个核心函数贴上已做备忘。

屏幕采集函数:

LPBITMAPINFOHEADER CScreenCaptureDlg::captureScreenFrame( int left,int top,int width, int height,int tempDisableRect )
{
HDC hScreenDC = CreateDC("DISPLAY", NULL, NULL, NULL);


HDC hMemDC = ::CreateCompatibleDC(hScreenDC);     
HBITMAP hbm;
hbm = CreateCompatibleBitmap(hScreenDC, width, height);
HBITMAP oldbm = (HBITMAP) SelectObject(hMemDC, hbm);
//BitBlt(hMemDC, 0, 0, width, height, hScreenDC, left, top, SRCCOPY);
//ver 1.6
DWORD bltFlags = SRCCOPY;
//bltFlags |= CAPTUREBLT;
BitBlt(hMemDC, 0, 0, width, height, hScreenDC, left, top, SRCCOPY|CAPTUREBLT);


SelectObject(hMemDC,oldbm);
LPBITMAPINFOHEADER pBM_HEADER = (LPBITMAPINFOHEADER)GlobalLock(Bitmap2Dib(hbm, 32));
//LPBITMAPINFOHEADER pBM_HEADER = (LPBITMAPINFOHEADER)GlobalLock(Bitmap2Dib(hbm, 24));
if (pBM_HEADER == NULL) 
{ 
return NULL;
}    


DeleteObject(hbm);
DeleteDC(hMemDC);
::ReleaseDC(NULL,hScreenDC);
return pBM_HEADER;
}
HANDLE CScreenCaptureDlg::Bitmap2Dib( HBITMAP hbitmap, UINT bits )
{
HANDLE              hdib = NULL ;
HDC                 hdc ;
BITMAP              bitmap ;
UINT                wLineLen ;
DWORD               dwSize ;
DWORD               wColSize ;
LPBITMAPINFOHEADER  lpbi ;
LPBYTE              lpBits ;


GetObject(hbitmap,sizeof(BITMAP),&bitmap) ;


//
// DWORD align the width of the DIB
// Figure out the size of the colour table
// Calculate the size of the DIB
//
wLineLen = (bitmap.bmWidth*bits+(bits - 1))/(bits) * (bits/8);
wColSize = sizeof(RGBQUAD)*((bits <= 8) ? 1<<bits : 0);
dwSize = sizeof(BITMAPINFOHEADER) + wColSize +
(DWORD)(UINT)wLineLen*(DWORD)(UINT)bitmap.bmHeight;


//
// Allocate room for a DIB and set the LPBI fields
//
hdib = GlobalAlloc(GHND,dwSize);
if (!hdib)
return hdib ;


lpbi = (LPBITMAPINFOHEADER)GlobalLock(hdib) ;


lpbi->biSize = sizeof(BITMAPINFOHEADER) ;
lpbi->biWidth = bitmap.bmWidth ;
lpbi->biHeight = bitmap.bmHeight ;
lpbi->biPlanes = 1 ;
lpbi->biBitCount = (WORD) bits ;
lpbi->biCompression = BI_RGB ;
lpbi->biSizeImage = dwSize - sizeof(BITMAPINFOHEADER) - wColSize ;
lpbi->biXPelsPerMeter = 0 ;
lpbi->biYPelsPerMeter = 0 ;
lpbi->biClrUsed = (bits <= 8) ? 1<<bits : 0;
lpbi->biClrImportant = 0 ;


//
// Get the bits from the bitmap and stuff them after the LPBI
//
lpBits = (LPBYTE)(lpbi+1)+wColSize ;


hdc = CreateCompatibleDC(NULL) ;


GetDIBits(hdc,hbitmap,0,bitmap.bmHeight,lpBits,(LPBITMAPINFO)lpbi, DIB_RGB_COLORS);


lpbi->biClrUsed = (bits <= 8) ? 1<<bits : 0;


DeleteDC(hdc) ;
GlobalUnlock(hdib);


return hdib ;
}
初始化
BOOL CScreenCaptureDlg::InitOutFile()
{
av_register_all();
AVOutputFormat* fmt;



AVCodec* pCodec;

const char* out_file = "src01.avi";
pFormatCtx = avformat_alloc_context();
fmt = av_guess_format(NULL, out_file, NULL);
pFormatCtx->oformat = fmt;
//注意输出路径
if (avio_open(&pFormatCtx->pb,out_file, AVIO_FLAG_READ_WRITE) < 0)
{
MessageBox("输出文件打开失败");
return FALSE;
}


video_st = av_new_stream(pFormatCtx, 0);
if (video_st==NULL)
{
return FALSE;
}
pCodecCtx = video_st->codec;
pCodecCtx->codec_id = CODEC_ID_MPEG4;
//pCodecCtx->vcodec = ;
pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
pCodecCtx->pix_fmt = PIX_FMT_YUV420P;//AV_PIX_FMT_RGB24;
pCodecCtx->width = m_width;  
pCodecCtx->height = m_height;
pCodecCtx->time_base.num = 1;  
pCodecCtx->time_base.den = 8;  
//   pCodecCtx->bit_rate = 3000000;
// pCodecCtx->global_quality = 300;
// pCodecCtx->gop_size=80;
// pCodecCtx->qmin = 10;
// pCodecCtx->qmax = 51;
//输出格式信息
av_dump_format(pFormatCtx, 0, out_file, 1);


pCodec = avcodec_find_encoder(CODEC_ID_MPEG4);
if (!pCodec)
{
MessageBox("没有找到合适的编码器!\n");
return FALSE;
}
if (avcodec_open2(pCodecCtx, pCodec,NULL) < 0)
{
MessageBox("编码器打开失败!\n");
return FALSE;
}
//写文件头
avformat_write_header(pFormatCtx,NULL);



int size;

picture = avcodec_alloc_frame();
size = avpicture_get_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);
picture_buf = new uint8_t[size];
avpicture_fill((AVPicture *)picture, picture_buf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);



pFrame=avcodec_alloc_frame();
numBytes=avpicture_get_size(PIX_FMT_RGB32, pCodecCtx->width,pCodecCtx->height);   
buffer = new uint8_t[numBytes];


int y_size = pCodecCtx->width * pCodecCtx->height;
av_new_packet(&pkt,y_size*3);
//video_outbuf= (unsigned char *) malloc(y_size*3);
}
写入一帧:
BOOL CScreenCaptureDlg::WriteFrame(LPBITMAPINFOHEADER bmp, int pts)
{
memset(buffer, 0, numBytes);
uint8_t* tmpBuf = (uint8_t*)(bmp + 1);
for (int i = 0; i < pCodecCtx->height;i++)
memcpy(buffer + i * (pCodecCtx->width * 4), (tmpBuf + (pCodecCtx->width*4)*(pCodecCtx->height - i - 1)), (pCodecCtx->width * 4));
int ret1 = avpicture_fill((AVPicture *)pFrame, buffer, PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height);
SwsContext *img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, PIX_FMT_RGB32, pCodecCtx->width, 
pCodecCtx->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, picture->data, picture->linesize);
//PTS
picture->pts=pts;
int got_picture=0;
int ret = avcodec_encode_video2(pCodecCtx, &pkt,picture, &got_picture);
if(ret < 0)
{
MessageBox("编码错误!\n");
return -1;
}
if (got_picture==1)
{
pkt.stream_index = video_st->index;
ret = av_write_frame(pFormatCtx, &pkt);
av_free_packet(&pkt);
}
return TRUE;
}
收尾工作:
BOOL CScreenCaptureDlg::CloseCaptureFile()
{
// free(video_outbuf);
//Flush Encoder
int ret = flush_encoder(pFormatCtx,0);
if (ret < 0) {
printf("Flushing encoder failed\n");
return -1;
}


//写文件尾
av_write_trailer(pFormatCtx);


//清理
if (video_st)
{
avcodec_close(video_st->codec);
}
avio_close(pFormatCtx->pb);
avformat_free_context(pFormatCtx);


av_free(picture);
delete[] picture_buf;
av_free(pFrame);
delete[] buffer;
}
//如果不加这个函数极有可能少帧
int flush_encoder(AVFormatContext *fmt_ctx,unsigned int stream_index)
{
int ret;
int got_frame;
AVPacket enc_pkt;
if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities &
CODEC_CAP_DELAY))
return 0;
while (1) {
printf("Flushing stream #%u encoder\n", stream_index);
//ret = encode_write_frame(NULL, stream_index, &got_frame);
enc_pkt.data = NULL;
enc_pkt.size = 0;
av_init_packet(&enc_pkt);
ret = avcodec_encode_video2 (fmt_ctx->streams[stream_index]->codec, &enc_pkt,
NULL, &got_frame);
av_frame_free(NULL);
if (ret < 0)
break;
if (!got_frame)
{ret=0;break;}
printf("编码成功1帧!\n");
/* mux encoded frame */
ret = av_write_frame(fmt_ctx, &enc_pkt);
if (ret < 0)
break;
}
return ret;
}


差点把这些搞忘了:
extern "C"
{
#include "libavcodec\avcodec.h"
#include "libavformat\avformat.h"
#include "libswscale\swscale.h"
}

extern "C"
{
#pragma comment (lib, "avcodec.lib")
#pragma comment (lib, "avformat.lib")
#pragma comment (lib, "avutil.lib")
#pragma comment (lib, "avdevice.lib")
#pragma comment (lib, "avfilter.lib")
#pragma comment (lib, "postproc.lib")
#pragma comment (lib, "swresample.lib")
#pragma comment (lib, "swscale.lib")
};



工程下载地址:http://download.csdn.net/detail/dancing_night/8671911

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dancing_night

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值