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

20 篇文章 0 订阅

ffmpeg作为一个支持非常多视频、音频格式的开源项目,其应用灰常广泛。今儿在这我们就探讨一下读者对其的理解,其中不泛错误谬误,望各位大大批评指教。这样做的原因主要是官方的文档比较匮乏。经过无数摸索,有一些经验分享。

1、Overview

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

avformat

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

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

2、解码视频

(1)打开文件

1
2
3
4
5
6
7
8
9
10
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)寻找解码器(视频)

1
2
3
4
5
6
7
8
9
10
11
12
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)打开解码器

1
2
3
4
5
6
7
8
9
10
11
12
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)解码视频

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
AVPacket pkt;
AVFrame *frame = avcodec_alloc_frame();
int got_picture = 0, rc = 0;
 
while (rc == 0){
  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的转换,而主要是色彩空间和大小伸缩的转换。例如

Rating: 9.2/ 10 (5 votes cast)
Rating: +2 (from 2 votes)

Related posts:

  1. 用boost::shared_ptr实现COW 在boost中,shared_ptr作为智能指针的模块类,可以为程序提供基于引用计数的内存管理功能。用智能指针的优点在于:当托管的对象不再被需要时,可以自动回收内存。以上的话太晦涩了,我想还是一个例子比较好。 例如在某函数内用malloc或者new申请了一片内存区用于存储网络接收到的数据,然后返回将该指针给函数的调用者。调用者可能又将这个指针交给了另一个线程进行数据处理,甚至交给第二进线程进行并发处理。这样一来,你就不知道什么时候该收回这块内存了(i.e. 不知道何时该调用free或者delete)。如果不小心在这个线程删除了而另一个线程还在使用,那你完蛋了!老板该扣你工资啦! shared_ptr的作用就是每复制一次shared_ptr,就会增加内部的“引用计数”——即它保护的内存区域指针被引用了多少次,当一个shared_ptr被删除(经常叫做析构,局部变量会在函数调用返回时析构/销毁)时,“引用计数”就会减一,当引用计数减为0时,说明这个指针不再被谁需要,它指向的内存区域就会被真正地释放掉。 1、实现一个ByteArray 顾名思义,ByteArray就是管理一片内存区域。有人问为什么不直接使用 char *buf,请返回看以上章节。有人还可以说,我在每个函数内(Stack内),都声明固定长度的数据区域,每次调用都复制一次即可。暂且不说固定长度的不灵活性和每次复制的时间损耗,若你存储的是几MB、几十MB甚至上G的数据,你也敢在每次复制吗? 1 2...

以上关联文章由 Yet Another Related Posts Plugin 提供支持。

已经发布的版本:
变化:
2011 年 1 月 7 日 23:41:55Current Revision
Content
 ffmpeg作为一个支持非常多视频、音频格式的开源项目,其应用灰常广泛。今儿在这我们就探讨一下读者对其的理解,其中不泛错误谬误,望各位大大批评指教。这样做的原因主要是官方的文档比较匮乏。经过无数摸索,有一些经验分享。 ffmpeg作为一个支持非常多视频、音频格式的开源项目,其应用灰常广泛。今儿在这我们就探讨一下读者对其的理解,其中不泛错误谬误,望各位大大批评指教。这样做的原因主要是官方的文档比较匮乏。经过无数摸索,有一些经验分享。
 <h1>1、Overview</h1> <h1>1、Overview</h1>
 ffmpeg里有几个重要的概念,熟悉它们以后,事情就变得简单多了。 ffmpeg里有几个重要的概念,熟悉它们以后,事情就变得简单多了。
 <a href="http:// blog.simophin.net/wp-content/ uploads/2011/ 01/avformat.png"><img style="background-image: none; padding-left: 0px; padding-right: 0px; display: block; float: none; padding-top: 0px; border-width: 0px;" title="avformat" src="http://blog.simophin.net/ wp-content/uploads/2011/01/ avformat_thumb.png" border="0" alt="avformat" width="516" height="124" /></a> <a href="http:// blog.simophin.net/wp-content/ uploads/2011/ 01/avformat.png"><img style="background-image: none; padding-left: 0px; padding-right: 0px; display: block; float: none; padding-top: 0px; border-width: 0px;" title="avformat" src="http://blog.simophin.net/ wp-content/uploads/2011/01/ avformat_thumb.png" border="0" alt="avformat" width="516" height="124" /></a>
 AVFormatContext代表一个打开的文件或者别的媒体,总之可以说代表数据的来源。视频和音频是按照一定格式存储在文件中的。这种< strong>格式< /strong>仅仅指定如何把视频和音频流区分开来,至于视频如何变成图像,那是< strong>解码< /strong>。平常所说的AVI格式,也就是上面所说的格式,里面视频、音频的编码方式还是可以随意的。 AVFormatContext代表一个打开的文件或者别的媒体,总之可以说代表数据的来源。视频和音频是按照一定格式存储在文件中的。这种< strong>格式< /strong>仅仅指定如何把视频和音频流区分开来,至于视频如何变成图像,那是< strong>解码< /strong>。平常所说的AVI格式,也就是上面所说的格式,里面视频、音频的编码方式还是可以随意的。
 +<!--more-->
 ffmpeg中的AVFormat库可以帮助进行这一“分拆音视频流”的过程;而AVCodec则帮助解码视频。 ffmpeg中的AVFormat库可以帮助进行这一“分拆音视频流”的过程;而AVCodec则帮助解码视频。
 <h1>2、解码视频</h1> <h1>2、解码视频</h1>
 <h2>(1)打开文件</h2> <h2>(1)打开文件</h2>
 <pre lang="C" line="1"> <pre lang="C" line="1">
 AVInputFormat *inputFmt; AVInputFormat *inputFmt;
 AVFormatContext *fmtCtx = NULL; AVFormatContext *fmtCtx = NULL;
 inputFmt = av_find_input_ format("avi"); /* 打开“AVI”的输入格式 */ inputFmt = av_find_input_ format("avi"); /* 打开“AVI”的输入格式 */
 if (inputFmt == NULL) { if (inputFmt == NULL) {
 /* Error processing */ /* Error processing */
 } }
 if (av_open_input_ file(&fmtCtx," /test/test.avi", inputFmt, 0, NULL) != 0) { if (av_open_input_ file(&fmtCtx," /test/test.avi", inputFmt, 0, NULL) != 0) {
 /* Error processing */ /* Error processing */
 } }
 </pre> </pre>
 这里为了方便说明,先暂时假设输入的文件是AVI格式的。在很多情况下,我们都不知道文件是什么格式的,FFMPEG提供了探测的方法,这将在下文再提到。 这里为了方便说明,先暂时假设输入的文件是AVI格式的。在很多情况下,我们都不知道文件是什么格式的,FFMPEG提供了探测的方法,这将在下文再提到。
 <h2>(2)寻找解码器(视频)</h2> <h2>(2)寻找解码器(视频)</h2>
 <pre lang="C" line="1"> <pre lang="C" line="1">
 int i, found_stream_index; int i, found_stream_index;
 AVCodecContext *videoDecodeCtx = NULL; AVCodecContext *videoDecodeCtx = NULL;
 for (i=0;i<fmtCtx- >nb_streams;i++){ for (i=0;i<fmtCtx- >nb_streams;i++){
 if (fmtCtx->streams[i]- >codec->codec_type == AVMEDIA_VIDEO){ if (fmtCtx->streams[i]- >codec->codec_type == AVMEDIA_VIDEO){
 videoDecodeCtx = fmtCtx->streams[i]->codec; videoDecodeCtx = fmtCtx->streams[i]->codec;
 found_stream_index = i; found_stream_index = i;
 break; break;
 } }
 } }
 if (decodeCtx == NULL) { if (decodeCtx == NULL) {
 /* 找不到视频流,错误处理 */ /* 找不到视频流,错误处理 */
 } }
 </pre> </pre>
 当一个AVFormatContext成功打开后,在其结构内就会存着该文件内包含的所有流(视频流、音频流等),每个流(AVStream)都会自动打开一个解码器上下文(AVCodecContext),即提供给解码器的参数,< b>它并非真正的解码器,只是解码器参数!</b> 当一个AVFormatContext成功打开后,在其结构内就会存着该文件内包含的所有流(视频流、音频流等),每个流(AVStream)都会自动打开一个解码器上下文(AVCodecContext),即提供给解码器的参数,< b>它并非真正的解码器,只是解码器参数!</b>
 以上代码中在文件所含的所有流中寻找视频流并得到一个解码器上下文。 以上代码中在文件所含的所有流中寻找视频流并得到一个解码器上下文。
 <h2>(3)打开解码器</h2> <h2>(3)打开解码器</h2>
 <pre lang="C" line="1"> <pre lang="C" line="1">
 AVCodec *videoDecoder; AVCodec *videoDecoder;
 videoDecoder = avcodec_find_ encoder(videoDecodeCtx->codec_id); videoDecoder = avcodec_find_ encoder(videoDecodeCtx->codec_id);
 if (videoDecoder == NULL){ if (videoDecoder == NULL){
 /* 找不到解码器,错误处理 */ /* 找不到解码器,错误处理 */
 } }
 if (videoDecoder- >capabilities & CODEC_CAP_TRUNCATED){ if (videoDecoder- >capabilities & CODEC_CAP_TRUNCATED){
 videoDecodeCtx->flags |= CODEC_FLAG_TRUNCATED; videoDecodeCtx->flags |= CODEC_FLAG_TRUNCATED;
 } }
 if (avcodec_open( videoDecodeCtx,videoDecoder) < 0){ if (avcodec_open( videoDecodeCtx,videoDecoder) < 0){
 /* 打不开解码器,错误处理 */ /* 打不开解码器,错误处理 */
 } }
 </pre> </pre>
 这一步真正地去寻找一个解码器,并使用之前获得的参数打开它。并非任何编码都会被支持,并非任何参数都会被解码器都会被支持,所以一定要进行错误处理哦。 这一步真正地去寻找一个解码器,并使用之前获得的参数打开它。并非任何编码都会被支持,并非任何参数都会被解码器都会被支持,所以一定要进行错误处理哦。
 CODEC_CAP_TRUNCATED指明解码器可以支持所谓“碎片输入”,先不管它,等会儿再说。 CODEC_CAP_TRUNCATED指明解码器可以支持所谓“碎片输入”,先不管它,等会儿再说。
 如果都成功了,那么解码器就成功打开了。接下来就可以开始解码了。 如果都成功了,那么解码器就成功打开了。接下来就可以开始解码了。
 <h2>(4)解码视频</h2> <h2>(4)解码视频</h2>
 <pre lang="C" line="1"> <pre lang="C" line="1">
 AVPacket pkt; AVPacket pkt;
 AVFrame *frame = avcodec_alloc_frame(); AVFrame *frame = avcodec_alloc_frame();
 int got_picture = 0, rc = 0; int got_picture = 0, rc = 0;
-while (rc == 0){+while (1){
 av_init_packet(&pkt); av_init_packet(&pkt);
 rc = av_read_packet( fmtCtx,&pkt); /* 获取一个包的数据 */ rc = av_read_packet( fmtCtx,&pkt); /* 获取一个包的数据 */
 if (rc != 0) break; if (rc != 0) break;
 if (pkt.stream_index != found_stream_index) if (pkt.stream_index != found_stream_index)
 goto free_pkt_continue; /* 不是所关心的视频流的数据,舍去 */ goto free_pkt_continue; /* 不是所关心的视频流的数据,舍去 */
 if ( avcodec_decode_video2 (videoDecodeCtx, frame, &got_picture, &pkt) < 0) { if ( avcodec_decode_video2 (videoDecodeCtx, frame, &got_picture, &pkt) < 0) {
 /* 核心函数:解码。错误处理。 */ /* 核心函数:解码。错误处理。 */
 } }
    
 if (got_picture) { if (got_picture) {
 { /* 处理获得的图像( 存储在AVFrame里) */ } { /* 处理获得的图像( 存储在AVFrame里) */ }
 av_free(frame); av_free(frame);
 frame = avcodec_alloc_frame(); frame = avcodec_alloc_frame();
 } }
    
 free_pkt_continue: free_pkt_continue:
 av_free_packet(&pkt); av_free_packet(&pkt);
 } }
 </pre> </pre>
 其实这个过程很简单: 其实这个过程很简单:
 1. 读取数据; 1. 读取数据;
 2. 解码。 2. 解码。
 原始数据是以一个个AVPacket的形式存放的,而解出来的一帧图像存在AVFrame里。一帧图像可以由很多AVPacket组成,所以使用一个got_ picture指针来指示是否获得一帧图像。 原始数据是以一个个AVPacket的形式存放的,而解出来的一帧图像存在AVFrame里。一帧图像可以由很多AVPacket组成,所以使用一个got_ picture指针来指示是否获得一帧图像。
 之前说的是关于CAP_ TRUNCATED的问题,就是表明解码器是否支持AVFrame的边界和AVPacket的边界不重合。数据应该可以是零散的,解码器的这个能力很重要,它可以处理来自数据中的任一段(对于网络数据很有用)。 之前说的是关于CAP_ TRUNCATED的问题,就是表明解码器是否支持AVFrame的边界和AVPacket的边界不重合。数据应该可以是零散的,解码器的这个能力很重要,它可以处理来自数据中的任一段(对于网络数据很有用)。
 <h1>3、图像转换</h1> <h1>3、图像转换</h1>
 +这里说的图像转换,并非类似PNG到JPG的转换,而主要是色彩空间和大小伸缩的转换。例如MPEG4的解码器解出来的图像格式是YUV420P格式,而若让Qt来渲染图像,则需要RGB格式以及任意的大小。
 +ffmpeg中提供swscale库来提供图像转换支持。现在假设我们在上一步解码出来的数据存放于AVFrame *frame中,我们有:
 +<pre lang="C" line="1">
 +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) {
-这里说的图像转换,并非类似PNG到JPG的转换,而主要是色彩空间和大小伸缩的转换。例如+/* 转换上下文初始化失败,错误处理 */
 +}
 +/* 分配转换需要的内存 */
 +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) {
 +/* 转换失败,错误处理 */
 +}
 +</pre>
 +于是convertedFrame- >data[0]中就存有转换好的图像了!

Note: Spaces may be added to comparison text to allow better line wrapping.


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值