在cpp代码中。首先引入要用到的一些基本的ffmpeg头文件,以及一些宏定义。
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,"ffmpeg_for_tainan",__VA_ARGS__)
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
}
#include <iostream>
using namespace std;
从名字可以看出,libavformat用于格式。avcodec用于解码。
首先进入如下操作:
//初始化解封装,注册所有ffmepg组件 这里是avformat包里的方法
av_register_all();
// 初始化网络,用于解码一些网络协议
avformat_network_init();
AVFormatContext可以看作是一个视频的整体信息封装类。 当一个视频被打开后,所有的索引信息都会在AVFormatContext中。
//定义一个Avformatcontext ,以后的交互主要都是通过此类
AVFormatContext *ic =NULL;
char path[] ="/storage/emulated/0/tencent/QQfile_recv/WeChatSight26.mp4";
//打开一个视频文件,通过方法的返回值可能看出,0表示成功
int re = avformat_open_input(&ic, path,0,0);
if (re !=0) {
//av_err2str方法可以将错误码直接返回成信息
LOGW("avformat_open_input failed! :%s",av_err2str(re));
return;
}
LOGW("avformat_open_input %s success!",path);
从以上可以看出,ic是传的一个二级指针进去。为什么这样处理呢?是为了给一级指针赋值! 也就是说,avformat_open_input这个方法内部给了AVFormatContext的指针ic赋值。这是一种常用的方法。 当打开失败时,这里也输出了错误日志。
注意,在整个解码完成后,一定要释放!
//关闭上下文
avformat_close_input(&ic);
打开后,ic里就有了索引信息,但并不是真实的信息,只是索引。现在我们通过avformat_find_stream_info来访问流信息,成功后,ic里才会有流信息。 r2d是一个时间基单位换算。
//获取流信息
re = avformat_find_stream_info(ic,0);
if(re != 0)
{
LOGW("avformat_find_stream_info failed!");
return;
}
再遍历流通道,来得到视频流、音频流 以及其它如字幕的流。 既然是得到流,引出了AVStream这个结构体。下面将视频流与音频流在数组中的position记录下来。以便后面使用。并且输出了一些音频流的信息。
int fps = 0;
int videoStream = 0;
int audioStream = 1;
for(int i = 0; i < ic->nb_streams; i++)
{
AVStream *as = ic->streams[i];
if(as->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
LOGW("视频数据");
videoStream = i;
fps = r2d(as->avg_frame_rate);
LOGW("fps = %d,width=%d height=%d codeid=%d pixformat=%d",fps,
as->codecpar->width,
as->codecpar->height,
as->codecpar->codec_id,
as->codecpar->format
);
}
else if(as->codecpar->codec_type ==AVMEDIA_TYPE_AUDIO )
{
LOGW("音频数据");
audioStream = i;
LOGW("sample_rate=%d channels=%d sample_format=%d",
as->codecpar->sample_rate,
as->codecpar->channels,
as->codecpar->format
);
}
}
static double r2d(AVRational r)
{
return r.num==0||r.den == 0 ? 0 :(double)r.num/(double)r.den;
}
当然,除了遍历,还有另外一种方式获得流:
//获取音频流信息
audioStream = av_find_best_stream(ic,AVMEDIA_TYPE_AUDIO,-1,-1,NULL,0);
LOGW("av_find_best_stream audioStream = %d",audioStream);
获得了流,现在就要读取帧数据!引出AVPacket结构体。可以理解为,帧的实体类,视频与音频全是它。
关键方法为:av_read_frame,需要传入上下文与一个AVPacket指针,这里千万记住,用完了要释放前一帧,否则,这里会内存泄漏,释放方法为:av_packet_unref。
av_seek_frame(ic,videoStream,pos,AVSEEK_FLAG_BACKWARD|AVSEEK_FLAG_FRAME ); 这个方法指在作跳转,可以看到,这里是让视频帧跳转到pos的位置,因为跳转的时候,可能并不是刚好跳到关键帧,因此第三个参数就是表示,跳到向前的最近的一个关键帧。
//读取帧数据
AVPacket *pkt = av_packet_alloc();
for(;;)
{
int re = av_read_frame(ic,pkt);
if(re != 0)
{
LOGW("读取到结尾处!");
int pos = 20 * r2d(ic->streams[videoStream]->time_base);
av_seek_frame(ic,videoStream,pos,AVSEEK_FLAG_BACKWARD|AVSEEK_FLAG_FRAME );
continue;
}
LOGW("stream = %d size =%d pts=%lld flag=%d",
pkt->stream_index,pkt->size,pkt->pts,pkt->flags
);
//////////////////////
av_packet_unref(pkt);
}