使用ffmpeg可以很容易把264文件数据解码为jpeg图片文件或者bmp图片。开发环境的搭建同样参考《Ffmpeg视频开发教程(一)——实现视频格式转换功能超详细版》(https://blog.csdn.net/zhangamxqun/article/details/80304494)。
环境搭建好,把主程序的cpp文件代码,换成下面的代码,即可进行测试。
lib库文件的路径,要根据自己的实际情况配置。
测试之前在exe同级目录下先放入test.264的测试264文件数据(csdn随便下载一个)。程序运行后可以在程序目录下看到大量图片被生成。
/**
实现FFMPEG把264数据保存为jpeg图像或者bmp图像,作者自己测试正确可用
作者:明天继续
使用的ffmpeg版本:ffmpeg-20180508-293a6e8-win32
开发工具:vs2012
**/
#include "stdafx.h"
#include <Windows.h>
#include <wingdi.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/opt.h>
#include <libavutil/imgutils.h>
}
#define INBUF_SIZE 4096
#pragma comment(lib,"../ffmpeg-20180508-293a6e8-win32/ffmpeg-20180508-293a6e8-win32-dev/lib/avcodec.lib")
#pragma comment(lib,"../ffmpeg-20180508-293a6e8-win32/ffmpeg-20180508-293a6e8-win32-dev/lib/avformat.lib")
#pragma comment(lib,"../ffmpeg-20180508-293a6e8-win32/ffmpeg-20180508-293a6e8-win32-dev/lib/avfilter.lib")
#pragma comment(lib,"../ffmpeg-20180508-293a6e8-win32/ffmpeg-20180508-293a6e8-win32-dev/lib/avutil.lib")
#pragma comment(lib,"../ffmpeg-20180508-293a6e8-win32/ffmpeg-20180508-293a6e8-win32-dev/lib/swscale.lib")
#if _MSC_VER
#define snprintf _snprintf_s
#define PRIx64 "I64x"
#define PRIX64 "I64X"
#endif
int save_jpg(const char* filename, AVFrame* pFrame)
{
// 分配AVFormatContext对象
AVFormatContext* pFormatCtx = avformat_alloc_context();
// 设置输出文件格式
pFormatCtx->oformat = av_guess_format("mjpeg", NULL, NULL); //mjpeg
// 创建并初始化一个和该路径相关的AVIOContext
if (avio_open(&pFormatCtx->pb, filename, AVIO_FLAG_READ_WRITE) < 0)
{
printf("Couldn't open output file.");
return -1;
}
// 构建一个新stream
AVStream* pAVStream = avformat_new_stream(pFormatCtx, 0);
if (pAVStream == NULL)
{
printf("avformat_new_stream error.");
return -1;
}
// 设置该stream的信息
AVCodecContext* pCodecCtx = pAVStream->codec;
pCodecCtx->codec_id = pFormatCtx->oformat->video_codec;
pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
pCodecCtx->pix_fmt = AV_PIX_FMT_YUVJ420P;
pCodecCtx->width = pFrame->width;
pCodecCtx->height = pFrame->height;
pCodecCtx->time_base.num = 1;
pCodecCtx->time_base.den = 25;
// 查找解码器
AVCodec* pCodec = avcodec_find_encoder(pCodecCtx->codec_id);
if (!pCodec)
{
printf("Could not find codec.");
return -1;
}
// 设置pCodecCtx的解码器为pCodec
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
{
printf("Could not open codec.");
return -1;
}
//写文件头
int ret = avformat_write_header(pFormatCtx, NULL);
if (ret < 0)
{
printf("avformat_write_header.\n");
return -1;
}
int y_size = pCodecCtx->width * pCodecCtx->height;
AVPacket pkt;
av_new_packet(&pkt, y_size * 3);
int got_picture = 0;
ret = avcodec_encode_video2(pCodecCtx, &pkt, pFrame, &got_picture);
if (ret < 0)
{
printf("Encode Error.\n");
return -1;
}
if (got_picture == 1)
{
ret = av_write_frame(pFormatCtx, &pkt);
}
av_free_packet(&pkt);
//写入文件尾
av_write_trailer(pFormatCtx);
if (pAVStream)
{
avcodec_close(pAVStream->codec);
}
avio_close(pFormatCtx->pb);
avformat_free_context(pFormatCtx);
return 0;
}
//保存8位的bmp图
static void bmp_8bit_save(unsigned char*buf, int wrap, int xsize, int ysize, char* filename)
{
FILE *f;
int bitCount = 8;
unsigned int linelength = (xsize * bitCount + 31)/32*4;
char filsename_new[1024];
snprintf(filsename_new, sizeof(filsename_new), "%s.%s", filename,"bmp");
f = fopen(filsename_new,"w");
BITMAPFILEHEADER targetfileheader;
BITMAPINFOHEADER targetinfoheader;
memset(&targetfileheader,0,sizeof(BITMAPFILEHEADER));
memset(&targetinfoheader,0,sizeof(BITMAPINFOHEADER));
//构造灰度图的文件头
targetfileheader.bfOffBits=(unsigned long)sizeof(BITMAPFILEHEADER)+(unsigned long)sizeof(BITMAPINFOHEADER)+sizeof(RGBQUAD)*256;
targetfileheader.bfSize=linelength*ysize+sizeof(RGBQUAD)*256 +sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER);
targetfileheader.bfReserved1=0;
targetfileheader.bfReserved2=0;
targetfileheader.bfType=0x4d42;
//构造灰度图的信息头
targetinfoheader.biBitCount=8;
targetinfoheader.biSize=sizeof(BITMAPINFOHEADER);
targetinfoheader.biHeight=ysize;
targetinfoheader.biWidth=xsize;
targetinfoheader.biPlanes=1;
targetinfoheader.biCompression=BI_RGB;
targetinfoheader.biSizeImage=0;
targetinfoheader.biXPelsPerMeter=0;
targetinfoheader.biYPelsPerMeter=0;
targetinfoheader.biClrImportant=0;
targetinfoheader.biClrUsed=0;
fwrite( &targetfileheader, 1, sizeof(BITMAPFILEHEADER), f);//写入位图文件头
fwrite( &targetinfoheader, 1, sizeof(BITMAPINFOHEADER), f);//写入位图信息头
RGBQUAD rgbquad[256];
for(int j=0;j<256;j++)
{
rgbquad[j].rgbBlue=j;
rgbquad[j].rgbGreen=j;
rgbquad[j].rgbRed=j;
rgbquad[j].rgbReserved=0;
}
fwrite(rgbquad , 1, sizeof(RGBQUAD)*256, f);
char* imagData = new char[linelength*ysize];
memset(imagData,0, linelength*ysize);
for (int i = 0; i < ysize; i++)
memcpy(imagData+i*linelength,buf+i*wrap, xsize);
fwrite(imagData , 1, linelength*ysize, f);
delete []imagData;
fclose(f);
}
void writ2bmp(AVFrame *pFrameRGB, int width, int height, int bpp, const char* filename)
{
BITMAPFILEHEADER bmpheader;
BITMAPINFOHEADER bmpinfo;
FILE *fp;
if( (fp = fopen(filename,"wb+")) == NULL ) {
printf ("open file failed!\n");
return;
}
int widthStep = (width*bpp+31)/32*4;
bmpheader.bfType = 0x4d42;
bmpheader.bfReserved1 = 0;
bmpheader.bfReserved2 = 0;
bmpheader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
bmpheader.bfSize = bmpheader.bfOffBits + width*height*bpp/8;
bmpinfo.biSize = sizeof(BITMAPINFOHEADER);
bmpinfo.biWidth = width;
bmpinfo.biHeight = height;
bmpinfo.biPlanes = 1;
bmpinfo.biBitCount = bpp;
bmpinfo.biCompression = BI_RGB;
bmpinfo.biSizeImage = widthStep*height;
bmpinfo.biXPelsPerMeter = 100;
bmpinfo.biYPelsPerMeter = 100;
bmpinfo.biClrUsed = 0;
bmpinfo.biClrImportant = 0;
fwrite(&bmpheader, sizeof(bmpheader), 1, fp);
fwrite(&bmpinfo, sizeof(bmpinfo), 1, fp);
//上下翻转
unsigned char* tempData = new unsigned char[widthStep];
for (int i=0;i<height/2;i++)
{
memcpy(tempData,pFrameRGB->data[0] + i*widthStep, widthStep);
memcpy(pFrameRGB->data[0] + i*widthStep, pFrameRGB->data[0] + (height-1-i)*widthStep, widthStep);
memcpy(pFrameRGB->data[0] + (height-1-i)*widthStep,tempData, widthStep);
}
delete[]tempData;
fwrite(pFrameRGB->data[0], width*height*bpp/8, 1, fp);
fclose(fp);
}
void save_24bit_bmp(AVPixelFormat inputFormat, AVFrame * inputFrame, const char* filename)
{
AVFrame * pFrameRGB = av_frame_alloc();
int pictureSize = avpicture_get_size(AV_PIX_FMT_BGR24, inputFrame->width, inputFrame->height);
uint8_t *outBuff = (uint8_t*)av_malloc(pictureSize);
avpicture_fill((AVPicture *)pFrameRGB, outBuff, AV_PIX_FMT_BGR24, inputFrame->width, inputFrame->height);
struct SwsContext *pSwsCtx = sws_getContext(inputFrame->width, inputFrame->height, inputFormat,
inputFrame->width, inputFrame->height, AV_PIX_FMT_BGR24,
SWS_BICUBIC, NULL, NULL, NULL);
sws_scale(pSwsCtx, inputFrame->data,
inputFrame->linesize, 0, inputFrame->height,
pFrameRGB->data, pFrameRGB->linesize);
writ2bmp(pFrameRGB, inputFrame->width, inputFrame->height, 24, filename);
sws_freeContext (pSwsCtx);
av_free (pFrameRGB);
}
static void decode(AVCodecContext *dec_ctx, AVFrame *frame, AVPacket *pkt,
const char *filename)
{
int ret;
ret = avcodec_send_packet(dec_ctx, pkt);
if (ret < 0) {
fprintf(stderr, "Error sending a packet for decoding\n");
exit(1);
}
while (ret >= 0) {
ret = avcodec_receive_frame(dec_ctx, frame);//解码一帧数据
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
return;
else if (ret < 0) {
fprintf(stderr, "Error during decoding\n");
exit(1);
}
printf("saving frame %3d\n", dec_ctx->frame_number);
fflush(stdout);
/* 图片是解码器分配的内存,不需要释放 */
//保存8位的bmp灰度图
//snprintf(buf, sizeof(buf), "%s-%d", filename, dec_ctx->frame_number);
//bmp_8bit_save(frame->data[0], frame->linesize[0],
// frame->width, frame->height, buf);
//保存24位bmp图片
char namebuf[1024]= {0};
snprintf(namebuf, sizeof(namebuf), "%s-%d.bmp", filename, dec_ctx->frame_number);
save_24bit_bmp(dec_ctx->pix_fmt, frame, namebuf);
//保存jpeg图片
char filsename_new[1024];
snprintf(filsename_new, sizeof(filsename_new), "%s%d.%s", filename,dec_ctx->frame_number,"jpg");
save_jpg(filsename_new,frame);
}
}
int _tmain(int argc, _TCHAR* argvec[])
{
const char *filename, *outfilename;
const AVCodec *codec_264;
AVCodecParserContext *parser;
AVCodecContext *codecContext_264= NULL;
FILE *f;
AVFrame *frame;
uint8_t inbuf[INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
uint8_t *data;
size_t data_size;
int ret;
AVPacket *pkt;
//输入的h264文件
filename = "test.264";
//输出的jpe图片文件的前缀
outfilename = "image";
av_register_all();
pkt = av_packet_alloc();
if (!pkt)
exit(1);
/* buffer尾部数据置为0 */
memset(inbuf + INBUF_SIZE, 0, AV_INPUT_BUFFER_PADDING_SIZE);
/* 找到h264解码器,用于解码h264 */
codec_264 = avcodec_find_decoder(AV_CODEC_ID_H264);
if (!codec_264) {
fprintf(stderr, "Codec not found\n");
exit(1);
}
parser = av_parser_init(codec_264->id);
if (!parser) {
fprintf(stderr, "parser not found\n");
exit(1);
}
codecContext_264 = avcodec_alloc_context3(codec_264);
if (!codecContext_264) {
fprintf(stderr, "Could not allocate video codec context\n");
exit(1);
}
/* 打开解码器 */
if (avcodec_open2(codecContext_264, codec_264, NULL) < 0) {
fprintf(stderr, "Could not open codec\n");
exit(1);
}
f = fopen(filename, "rb");
if (!f) {
fprintf(stderr, "Could not open %s\n", filename);
exit(1);
}
frame = av_frame_alloc();
if (!frame) {
fprintf(stderr, "Could not allocate video frame\n");
exit(1);
}
while (!feof(f)) {
/* 从264文件读取数据 */
data_size = fread(inbuf, 1, INBUF_SIZE, f);
if (!data_size)
break;
/* 使用解析器把数据分割为包 */
data = inbuf;
while (data_size > 0) {
ret = av_parser_parse2(parser, codecContext_264, &pkt->data, &pkt->size,
data, data_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
if (ret < 0) {
fprintf(stderr, "Error while parsing\n");
exit(1);
}
data += ret;
data_size -= ret;
if (pkt->size)
decode(codecContext_264, frame, pkt, outfilename);
}
}
/* 解码最后的数据 */
decode(codecContext_264, frame, NULL, outfilename);
fclose(f);
av_parser_close(parser);
avcodec_free_context(&codecContext_264);
av_frame_free(&frame);
av_packet_free(&pkt);
return 0;
}
效果图