使用ffmpeg进行视频解码以及图像转换

来源:互联网 发布:苏州市网络安全教育 编辑:程序博客网 时间:2024/05/21 06:45

看到一篇介绍ffmpeg基础的文章,十分清晰简洁,故转载一下,感谢原作者,转载出处:点击打开链接


1、Overview

ffmpeg里有几个重要的概念,熟悉它们以后,事情就变得简单多了。

avformat

AVFormatContext代表一个打开的文件或者别的媒体,总之可以说代表数据的来源。视频和音频是按照一定格式存储在文件中的。这种格式仅仅指定如何把视频和音频流区分开来,至于视频如何变成图像,那是解码。平常所说的AVI格式,也就是上面所说的格式,里面视频、音频的编码方式还是可以随意的。

ffmpeg中的AVFormat库可以帮助进行这一“分拆音视频流”的过程;而AVCodec则帮助解码视频。

2、解码视频

(1)打开文件

AVInputFormat   *inputFmt;AVFormatContext *fmtCtx = NULL; inputFmt = av_find_input_format("avi"); /* 打开“AVI”的输入格式 */if (inputFmt == NULL) {   /* Error processing */ }if (av_open_input_file(&fmtCtx,"/test/test.avi", inputFmt, 0, NULL) != 0) {  /* Error processing */ }

这里为了方便说明,先暂时假设输入的文件是AVI格式的。在很多情况下,我们都不知道文件是什么格式的,FFMPEG提供了探测的方法,这将在下文再提到。

(2)寻找解码器(视频)

int i, found_stream_index;AVCodecContext  *videoDecodeCtx = NULL;for (i=0;i<fmtCtx->nb_streams;i++){   if (fmtCtx->streams[i]->codec->codec_type == AVMEDIA_VIDEO){       videoDecodeCtx = fmtCtx->streams[i]->codec;       found_stream_index = i;       break;   }}if (decodeCtx == NULL) {   /* 找不到视频流,错误处理 */}

当一个AVFormatContext成功打开后,在其结构内就会存着该文件内包含的所有流(视频流、音频流等),每个流(AVStream)都会自动打开一个解码器上下文(AVCodecContext),即提供给解码器的参数,它并非真正的解码器,只是解码器参数!
以上代码中在文件所含的所有流中寻找视频流并得到一个解码器上下文。

(3)打开解码器

AVCodec   *videoDecoder;videoDecoder = avcodec_find_encoder(videoDecodeCtx->codec_id);if (videoDecoder == NULL){   /* 找不到解码器,错误处理 */}if (videoDecoder->capabilities & CODEC_CAP_TRUNCATED){   videoDecodeCtx->flags |= CODEC_FLAG_TRUNCATED;} if (avcodec_open(videoDecodeCtx,videoDecoder) < 0){  /* 打不开解码器,错误处理 */}

这一步真正地去寻找一个解码器,并使用之前获得的参数打开它。并非任何编码都会被支持,并非任何参数都会被解码器都会被支持,所以一定要进行错误处理哦。
CODEC_CAP_TRUNCATED指明解码器可以支持所谓“碎片输入”,先不管它,等会儿再说。
如果都成功了,那么解码器就成功打开了。接下来就可以开始解码了。

(4)解码视频

AVPacket pkt;AVFrame *frame = avcodec_alloc_frame();int got_picture = 0, rc = 0; while (1){  av_init_packet(&pkt);  rc = av_read_packet(fmtCtx,&pkt); /* 获取一个包的数据 */  if (rc != 0) break;  if (pkt.stream_index != found_stream_index)      goto free_pkt_continue; /* 不是所关心的视频流的数据,舍去 */  if ( avcodec_decode_video2 (videoDecodeCtx, frame, &got_picture, &pkt) < 0) {     /* 核心函数:解码。错误处理。 */  }   if (got_picture) {    {  /* 处理获得的图像(存储在AVFrame里) */ }    av_free(frame);    frame = avcodec_alloc_frame();  } free_pkt_continue:  av_free_packet(&pkt);}

其实这个过程很简单:
1. 读取数据;
2. 解码。
原始数据是以一个个AVPacket的形式存放的,而解出来的一帧图像存在AVFrame里。一帧图像可以由很多AVPacket组成,所以使用一个got_picture指针来指示是否获得一帧图像。
之前说的是关于CAP_TRUNCATED的问题,就是表明解码器是否支持AVFrame的边界和AVPacket的边界不重合。数据应该可以是零散的,解码器的这个能力很重要,它可以处理来自数据中的任一段(对于网络数据很有用)。

3、图像转换

这里说的图像转换,并非类似PNG到JPG的转换,而主要是色彩空间和大小伸缩的转换。例如MPEG4的解码器解出来的图像格式是YUV420P格式,而若让Qt来渲染图像,则需要RGB格式以及任意的大小。
ffmpeg中提供swscale库来提供图像转换支持。现在假设我们在上一步解码出来的数据存放于AVFrame *frame中,我们有:

SwsContext *swsCtx;int dst_width   = 320,  /* 目标宽度 */    dst_height  = 240, /* 目标高度 */    dst_pix_fmt = PIX_FMT_RGB24; /* 目标图像格式 */AVFrame *convertedFrame = avcodec_alloc_frame();swsCtx = sws_getContext (    videoDecodeCtx->width, videoDecodeCtx->height, videoDecodeCtx->pix_fmt,    dst_width, dst_height, dst_pix_fmt, SWS_FAST_BILINEAR,    NULL, NULL, NULL );if (swsCtx == NULL) {   /* 转换上下文初始化失败,错误处理 */} /* 分配转换需要的内存 */avpicture_fill ( (AVPicture *)convertedFrame,dst_pix_fmt, dst_width, dst_height); if (sws_scale(swsCtx,frame->data,frame->linesize,0,    videoDecodeCtx->height,convertedFrame->data,convertedFrame->linesize) <= 0) {   /* 转换失败,错误处理 */}

于是convertedFrame->data[0]中就存有转换好的图像了!


0 0