看了雷神关于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