视频编码
今天的目的在于将视频文件中的帧取出来,然后保存。
可以在上一次的基础上进行。最终代码如下。
void __fastcall TFFMPEGTestForm::Button1Click(TObject *Sender) { // 取得文件信息
const char *fileName = "d:\test.mp4";
AVFormatContext * pFormatCtx = NULL;
// 读取文件的头部并且把信息保存到AVFormatContext结构体中
int err = avformat_open_input(&pFormatCtx, fileName, NULL, 0);
if (err != 0)
return;
err = av_find_stream_info(pFormatCtx); // Retrieve stream information
if (err < 0)
return;
// FormatCtx->streams是一组大小为pFormatCtx->nb_streams的指针
for (uint32_t i = 0; i < pFormatCtx->nb_streams; i++) {
// stream 结构数据
AVStream *pStream = pFormatCtx->streams[i];
// 帧率信息
AVRational frameRate = pStream->r_frame_rate;
// 时间单位比率
AVRational timeBase = pStream->time_base;
// stream duration
int64_t duration = pStream->duration;
// 获取Codec数据结构
AVCodecContext *pCodecCtx = pStream->codec;
AVMediaType codecType = pCodecCtx->codec_type;
AVCodecID codecId = pCodecCtx->codec_id;
// enum AVCodecID codec_id; /* see AV_CODEC_ID_xxx */
if (codecType == AVMEDIA_TYPE_VIDEO) {
// 获取Video基本信息
int width = pCodecCtx->width;
int height = pCodecCtx->height;
PixelFormat pixelFormat = pCodecCtx->pix_fmt;
}
else if (codecType == AVMEDIA_TYPE_AUDIO) {
// 获取Audio基本信息
int channels = pCodecCtx->channels;
int sample_rate = pCodecCtx->sample_rate;
AVSampleFormat sampleFmt = pCodecCtx->sample_fmt;
}
// Find the decoder for the video stream
AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == NULL)
av_log(NULL, AV_LOG_ERROR, "Unsupported codec! ");
else if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
av_log(NULL, AV_LOG_ERROR, "Could not open codec! ");
else {
AVFrame * pFrame = avcodec_alloc_frame();
AVFrame * pFrameRGB = avcodec_alloc_frame();
if (pFrameRGB == NULL)
av_log(NULL, AV_LOG_ERROR, "alloc frame failure! ");
else {
int numBytes = avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height);
uint8_t * buffer = (uint8_t*)av_malloc(numBytes*sizeof(uint8_t));
avpicture_fill((AVPicture*)pFrameRGB, buffer, PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height);
int frameFinished;
AVPacket packet;
i = 0;
while (1) {
if (av_read_frame(pFormatCtx, &packet) < 0) // 创建AVPacket,填充其data数据缓冲区
break;
// while (av_read_frame(pFormatCtx, &packet) >= 0) {
// Is this a packet from the video stream?
if (packet.stream_index == video_stream_index) {
// Decode video frame
frameFinished = 0;
avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
// avcodec_decode_video(pCodecCtx, pFrame, &frameFinished, packet.data, packet.size);
// Did we get a video frame?
if (frameFinished) {
// Convert the image from its native format to RGB
img_convert((AVPicture*)pFrameRGB, PIX_FMT_RGB24, (AVPicture*)pFrame, pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height);
if (++i % 100 == 1)
SaveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height, i);
}
}
}
// Free the packet that was allocated by av_read_frame
av_free_packet(&packet);
av_free(buffer);
}
av_free(pFrameRGB);
av_free(pFrame);
}
avcodec_close(pCodecCtx);
}
// 释放
if (pFormatCtx != NULL) {
av_close_input_file(pFormatCtx);
pFormatCtx = NULL;
}
}
上面代码编译时,会提示img_convert找不到。
原因在于早期的ffmpeg库中,采用img_convert图像格式转换,而较新的ffmpeg库中去掉了该函数,加入了swscale库,用该库中的sws_scale函数可以替代img_convert,两个函数原型如下:
int img_convert(AVPicture *dst, int dst_pix_fmt,
const AVPicture *src, int src_pix_fmt,
int src_width, int src_height)
int sws_scale(struct SwsContext *ctx, uint8_t* src[], int srcStride[],
int srcSliceY, int srcSliceH, uint8_t* dst[], int dstStride[])
为了方便,可以自己写一个img_convert函数,然后函数内部用sws_scale来实现,只是对于一些错误的处理及返回值处理不太严格,但基本能用,代码如下:
int img_convert(AVPicture *dst, int dst_pix_fmt,
const AVPicture *src, int src_pix_fmt,
int src_width, int src_height)
{
int w;
int h;
SwsContext *pSwsCtx;
w = src_width;
h = src_height;
pSwsCtx = sws_getContext(w, h, src_pix_fmt,
w, h, dst_pix_fmt,
SWS_BICUBIC, NULL, NULL, NULL);
sws_scale(pSwsCtx, src->data, src->linesize,
0, h, dst->data, dst->linesize);
//这里释放掉pSwsCtx的内存
sws_freeContext(pSwsCtx);
return 0;
}
其实,稍微多用一下代码就知道,这个ffmpeg感觉与GDI一样,需要完成一些准备工作,然后按相应的流程进行处理,最后释放。
应该可以设计成为一个类库,这样只管用。
这项工作可以留待以后,把这个东东搞熟了再说。
运行结果,生成了一堆PPM文件
这些PPM文件可以用XnView程序查看。
这种结果,确实很爽的。现在的成果,可以取得视频文件的任意一帧,并保存为文件。