FFMPEG视音频解码【一】
多媒体的时代,得多了解点编解码的技术才行,而ffmpeg为我们提供了一系列多媒体编解码的接口,如何用好这些接口达到自己所需要的目的,这也是一门重要的学问。
要是了解得不够,总是会遇到一堆又一堆问题;网上关于ffmpeg的讲解,说少也不少,说多也不多,由于版本更新又更新,能找着的资料基本上都不大能对得上,需要进行一定量的修改才能正常工作;所以,我也借着这个机会,重新走一遍ffmpeg的入门,然后理清同步等问题。
本文主要讲的是ffmpeg解码最基本的步骤,以及其用到的接口,另附有完整的实例代码。
使用工具:FFMPEG(2.0.1)、VS2010
平台:WINDOWS
下面就开始吧……
步骤一:初始化ffmpeg(必须的)
av_register_all();
avcodec_register_all();
使用以上语句初始化ffmpeg的功能,简单,没什么好讲的。
步骤二:打开多媒体文件,检查头部信息
AVFormatContext *pFormatCtx = avformat_alloc_context();;
// Open video file
if(avformat_open_input(&pFormatCtx, filename, NULL, NULL)!=0)
return -1; // Couldn't open file
avformat_open_input()这个函数读取文件的头部并且把信息保存到我们给的AVFormatContext结构体中,注意要先调用avformat_alloc_context()为我们的结构体申请空间,否则读取文件的信息无法保存,avformat_open_input会失败。
步骤三:检查文件中的流的信息
// Retrieve stream information
if(av_find_stream_info(pFormatCtx)<0)
return -1; // Couldn't find stream information
// Dump information about file onto standard error
av_dump_format(pFormatCtx, 0, filename, 0);
av_find_stream_info()这个函数为pFormatCtx->streams填充上正确的信息。av_dump_format()是一个手工调试的函数,能使我们看到pFormatCtx->streams里面有什么内容。
步骤四:找出文件中的视频流
int i;
AVCodecContext *pCodecCtx;
// Find the first video stream
int videoStream=-1;
for(i=0; i<pFormatCtx->nb_streams; i++) {
if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) {
videoStream=i;
break;
}
}
if(videoStream==-1)
return -1; // Didn't find a video stream
// Get a pointer to the codec context for the video stream
pCodecCtx=pFormatCtx->streams[videoStream]->codec;
pFormatCtx->streams仅仅是一组大小为pFormatCtx->nb_streams的指针,所以让我们可以使用它找到一个视频流,并记录其在stream中的下标号(后面根据此标号可以与该视频匹配)。当然,视频流有可能不止一个,如果想要把所有视频流都找到,可以使用数组记录,就不需要break了。
AVCodecContext,流中关于编解码器的信息就是被我们叫做"codec context"(编解码器上下文)的东西。这里面包含了流中所使用的关于编解码器的所有信息,现在我们有了一个指向他的指针。
步骤五:找到该视频流真正的编解码器并且打开它
AVCodec *pCodec;
// Find the decoder for the video stream
pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
if(pCodec==NULL) {
fprintf(stderr, "Unsupported codec!\n");
return -1; // Codec not found
}
// Open codec
if(avcodec_open2(pCodecCtx, pCodec, NULL)<0)
return -1; // Could not open codec
avcodec_find_decoder()根据找到的视频流格式,找到想应的编解码器;avcodec_open2()可以开启我们的解码器使其处于随时可工作的状态,因为ffmpeg版本更新过许多次,所以有些Function名字都会带一些数字。
步骤六:申请数据空间(保存帧数据用)
AVFrame *pFrame, *pFrameRGB;
// Allocate video frame
pFrame=avcodec_alloc_frame();
// Allocate an AVFrame structure
pFrameRGB=avcodec_alloc_frame();
if(pFrameRGB==NULL)
return -1;
uint8_t *buffer;
int numBytes;
// Determine required buffer size and allocate buffer
numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,
pCodecCtx->height);
buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
// Assign appropriate parts of buffer to image planes in pFrameRGB
// Note that pFrameRGB is an AVFrame, but AVFrame is a superset
// of AVPicture
avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,
pCodecCtx->width, pCodecCtx->height);
AVFrame就是用来保存帧数据的,之所以定义了两个,是因为解码后帧数据一般是YUV格式,我们转换成RGB的帧数据,还需要一个存储的地方。我们使用avpicture_get_size()来获得我们需要的大小,然后用av_malloc手工申请内存空间,avpicture_fill来把帧和我们新申请的内存来结合。
步骤七:读取数据并解码
int frameFinished;
AVPacket packet;
i=0;
while(av_read_frame(pFormatCtx, &packet)>=0) {
// Is this a packet from the video stream?
if(packet.stream_index==videoStream) {
// Decode video frame
avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
// Did we get a video frame?
if(frameFinished) {
// Convert the image from its native format to RGB
static struct SwsContext *img_convert_ctx;
img_convert_ctx = sws_getCachedContext(img_convert_ctx,
pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height, PIX_FMT_RGB24,
SWS_FAST_BILINEAR, NULL, NULL, NULL);
if (!pCodecCtx)
{
printf("Cannot initialize sws conversion context\r\n");
return NULL;
}
sws_scale(img_convert_ctx,
(const uint8_t* const*)pFrame->data, pFrame->linesize, 0,
pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize); // Save the frame to disk
if(++i<=5)
SaveFrame(pFrameRGB, pCodecCtx->width,
pCodecCtx->height, i);
}
}
// Free the packet that was allocated by av_read_frame
av_free_packet(&packet);
}
这个循环过程是比较简单的:av_read_frame()读取一个包并且把它保存到AVPacket结构体中。注意我们仅仅申请了一个包的结构体 ――ffmpeg为我们申请了内部的数据的内存并通过packet.data指针来指向它。这些数据可以在后面通过av_free_packet()来释放。
函数avcodec_decode_video2()把包转换为帧。然而当解码一个包的时候,我们可能没有得到我们需要的关于帧的信息。因此,当我们得到下一帧的时候,avcodec_decode_video2()为我们设置了帧结束标志frameFinished。
然后,我们使用sws_getCachedContext()函数设置格式转换器,sws_scale()函数来把帧从原始格式(pCodecCtx->pix_fmt)转换成为RGB格式。最后,我们把帧和高度宽度信息传递给我们的SaveFrame()函数,保存成PPM的图片。
步骤八:清理一切,程序结束
// Free the RGB image
av_free(buffer);
av_free(pFrameRGB);
// Free the YUV frame
av_free(pFrame);
// Close the codec
avcodec_close(pCodecCtx);
// Close the video file
av_close_input_file(pFormatCtx);
return 0;
一旦我们开始读取完视频流,退出前需要释放所有申请的空间。
以上,就是ffmpeg解码的基本流程。最近也比较忙,时间也比较晚了,累了犯困,就不在此赘述环境的配置了,附上工程下载地址有兴趣的自行研究吧!
同时,欢迎广大志同道合的朋友讨论交流,要知道,You are not alone !
DownLoad_SourceCode_ClickHere (免分哦,亲!)
FFMPEG视音频解码【一】的更多相关文章
- [转]FFMPEG视音频编解码零基础学习方法
在CSDN上的这一段日子,接触到了很多同行业的人,尤其是使用FFMPEG进行视音频编解码的人,有的已经是有多年经验的“大神”,有的是刚开始学习的初学者.在和大家探讨的过程中,我忽然发现了一个问题:在“ ...
- [总结]FFMPEG视音频编解码零基础学习方法--转
ffmpeg编解码学习 目录(?)[-] ffmpeg程序的使用ffmpegexeffplayexeffprobeexe 1 ffmpegexe 2 ffplayexe 3 ffprobeexe ...
- FFMPEG视音频编解码零基础学习方法
在CSDN上的这一段日子,接触到了很多同行业的人,尤其是使用FFMPEG进行视音频编解码的人,有的已经是有多年经验的“大神”,有的是刚开始学习的初学者.在和大家探讨的过程中,我忽然发现了一个问题:在“ ...
- FFMPEG视音频编解码零基础学习方法-b
感谢大神分享,虽然现在还看不懂,留着大家一起看啦 PS:有不少人不清楚“FFmpeg”应该怎么读.它读作“ef ef em peg” 0. 背景知识 本章主要介绍一下FFMPEG都用在了哪里(在这里仅 ...
- [总结]FFMPEG视音频编解码零基础学习方法
在CSDN上的这一段日子,接触到了很多同行业的人,尤其是使用FFMPEG进行视音频编解码的人,有的已经是有多年经验的“大神”,有的是刚开始学习的初学者.在和大家探讨的过程中,我忽然发现了一个问题:在“ ...
- 【转】[总结]FFMPEG视音频编解码零基础学习方法
在CSDN上的这一段日子,接触到了很多同行业的人,尤其是使用FFMPEG进行视音频编解码的人,有的已经是有多年经验的“大神”,有的是刚开始学习的初学者.在和大家探讨的过程中,我忽然发现了一个问题:在“ ...
- FFMPEG视音频编解码零基础学习方法 【荐】
在CSDN上的这一段日子,接触到了很多同行业的人,尤其是使用FFMPEG进行视音频 编解码的人,有的已经是有多年经验的“大神”,有的是刚开始学习的初学者.在和大家探讨的过程中,我忽然发现了一个问题:在 ...
- [转载] FFMPEG视音频编解码零基础学习方法
在CSDN上的这一段日子,接触到了很多同行业的人,尤其是使用FFMPEG进行视音频编解码的人,有的已经是有多年经验的“大神”,有的是刚开始学习的初学者.在和大家探讨的过程中,我忽然发现了一个问题:在“ ...
- 转[总结]FFMPEG视音频编解码零基础学习方法 .
http://blog.csdn.net/leixiaohua1020/article/details/15811977 在CSDN上的这一段日子,接触到了很多同行业的人,尤其是使用FFMPEG进行视 ...
随机推荐
- sql 2000 分页
create PROCEDURE [dbo].[Proc_GetPageList] ( @Tables varchar(1000), --表名 @PK varchar(100 ...
- WebApi实现跨域功能
在配置文件的system.webServer节点中加入以下配置信息 <httpProtocol> <customHeaders> <add name="Acce ...
- 刷新 tableview
UITableView对于iOS开发者来说一定不会陌生,很有可能你的APP很多界面都用到它.关于UITableView的文章,想必已经不计其数,没事可以多看看.特别是UITableView优化的文章, ...
- 决策树简单介绍(二) Accord.Net中决策树的实现和使用
决策树介绍 决策树是一类机器学习算法,可以实现对数据集的分类.预测等.具体请阅读我另一篇博客(http://www.cnblogs.com/twocold/p/5424517.html). Accor ...
- Cogs 12 运输问题2 (有上下界网络流)
#include <cstdlib> #include <algorithm> #include <cstring> #include <iostream&g ...
- 清空文本框SetDlgItemText(IDC_TXTXT,NULL);
清空文本框 SetDlgItemText(IDC_TXTXT,NULL);
- C语言中对于结构的定义一般放在.h中还是.c中
单文件使用的结构体放.c文件中 本模块内部使用结构体,放模块内部头文件中 供外部使用的结构体,放模块对外头文件中.
- servlet 中字符集的处理
Servlet运行的步骤 Servlet作为Web服务器的补充功能在运行时需要受到Servlet容器的管理,其运行的流程如下: 浏览器依据IP建立与容器的连接 浏览器将请求数据打包 容器解析请求数据包 ...
- Bootstrap定制(一)less入门及编译
第一篇博,希望支持. 近期在开发一个项目,项目前端定位于bootstrap,遂花了少许时间研究了bootstrap,将其整理整理,与众人共享. bootstrap官方的定制,功能还算完善,但是基于we ...
- mongodb3 权限认证问题总结
mongodb3 权限认证问题总结 标签(空格分隔): mongodb 权限 数据库 认证 ubuntu用户安装最新版本mongodb 添加key sudo apt-key adv --keyserv ...