ffmpeg 格式转换过程

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

1、Overview

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

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

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

2、解码视频

(1)打开文件

  1. AVInputFormat   *inputFmt;  
  2. AVFormatContext *fmtCtx = NULL;  
  3.    
  4. inputFmt = av_find_input_format("avi"); /* 打开“AVI”的输入格式 */  
  5. if (inputFmt == NULL) {   
  6.   /* Error processing */   
  7. }  
  8. if (av_open_input_file(&fmtCtx,"/test/test.avi", inputFmt, 0, NULL) != 0) {  
  9.   /* Error processing */   
  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. int i, found_stream_index;  
  2. AVCodecContext  *videoDecodeCtx = NULL;  
  3. for (i=0;i<fmtCtx->nb_streams;i++){  
  4.    if (fmtCtx->streams[i]->codec->codec_type == AVMEDIA_VIDEO){  
  5.        videoDecodeCtx = fmtCtx->streams[i]->codec;  
  6.        found_stream_index = i;  
  7.        break;  
  8.    }  
  9. }  
  10. if (decodeCtx == NULL) {  
  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. AVCodec   *videoDecoder;  
  2. videoDecoder = avcodec_find_encoder(videoDecodeCtx->codec_id);  
  3. if (videoDecoder == NULL){  
  4.    /* 找不到解码器,错误处理 */  
  5. }  
  6. if (videoDecoder->capabilities & CODEC_CAP_TRUNCATED){  
  7.    videoDecodeCtx->flags |= CODEC_FLAG_TRUNCATED;  
  8. }  
  9.    
  10. if (avcodec_open(videoDecodeCtx,videoDecoder) < 0){  
  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. AVPacket pkt;  
  2. AVFrame *frame = avcodec_alloc_frame();  
  3. int got_picture = 0, rc = 0;  
  4.    
  5. while (1){  
  6.   av_init_packet(&pkt);  
  7.   rc = av_read_packet(fmtCtx,&pkt); /* 获取一个包的数据 */  
  8.   if (rc != 0) break;  
  9.   if (pkt.stream_index != found_stream_index)   
  10.      goto free_pkt_continue; /* 不是所关心的视频流的数据,舍去 */  
  11.   if ( avcodec_decode_video2 (videoDecodeCtx, frame, &got_picture, &pkt) < 0) {  
  12.      /* 核心函数:解码。错误处理。 */  
  13.   }  
  14.    
  15.   if (got_picture) {  
  16.     {  /* 处理获得的图像(存储在AVFrame里) */ }  
  17.     av_free(frame);  
  18.     frame = avcodec_alloc_frame();  
  19.   }  
  20.    
  21. free_pkt_continue:  
  22.   av_free_packet(&pkt);  
  23. }  
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中,我们有:

  1. SwsContext *swsCtx;  
  2. int dst_width   = 320,  /* 目标宽度 */  
  3.     dst_height  = 240, /* 目标高度 */  
  4.     dst_pix_fmt = PIX_FMT_RGB24; /* 目标图像格式 */  
  5. AVFrame *convertedFrame = avcodec_alloc_frame();  
  6. swsCtx = sws_getContext (  
  7.     videoDecodeCtx->width, videoDecodeCtx->height, videoDecodeCtx->pix_fmt,  
  8.     dst_width, dst_height, dst_pix_fmt, SWS_FAST_BILINEAR,  
  9.     NULL, NULL, NULL );  
  10. if (swsCtx == NULL) {  
  11.    /* 转换上下文初始化失败,错误处理 */  
  12. }  
  13.    
  14. /* 分配转换需要的内存 */  
  15. avpicture_fill ( (AVPicture *)convertedFrame,dst_pix_fmt, dst_width, dst_height);   
  16. if (sws_scale(swsCtx,frame->data,frame->linesize,0,  
  17.     videoDecodeCtx->height,convertedFrame->data,convertedFrame->linesize) <= 0) {  
  18.    /* 转换失败,错误处理 */  
  19. }  
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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FFmpeg是一个开源的跨平台多媒体处理工具,它可以用来换、编辑和播放各种音视频格式。根据引用中的源码示例,可以看出可以使用FFmpeg将RTSP流封装成mp4、avi、flv等格式的文件。 在引用中的源码示例中,通过创建一个MyFFmpeg对象,设置RTSP播放路径,然后进行初始化。在初始化成功后,可以使用循环读取帧的方式将RTSP流换为特定格式的文件。最后,通过释放资源完成过程。 需要注意的是,这只是一个简单的示例,实际过程可能涉及更多的参数和细节。如果需要更详细的操作和参数设置,可以参考引用中的FFmpeg操作类封装来进行扩展和优化。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [实现ffmpeg将RTSP流封装成mp4、avi、flv文件的源码](https://download.csdn.net/download/u012125696/9589163)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Qt+FFmpeg播放RTSP H264视频流(2)- FFmpeg自定义类封装](https://blog.csdn.net/eastcnme/article/details/94607856)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值