ffmpeg解封装
需要调用ffmpeg的API首先需要引入对应的头文件:
extern "C"{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
}
1.初始化解封装
//初始化解封装
av_register_all();
//初始化网络,可以直接从服务器拉流
avformat_network_init();
av_register_all()用于注册所有复用器,编码器和协议处理器。如果要指定注册某种编码器可以使用:av_register_input_format() ,av_register_output_format(),ffurl_register_protocol()。av_register_all()调用了avcodec_register_all()。avcodec_register_all()注册了和编解码器有关的组件:硬件加速器,解码器,编码器,Parser,Bitstream Filter。av_register_all()除了调用avcodec_register_all()之外,还注册了复用器,解复用器,协议处理器。
avformat_network_init()用于网络组件的全局初始化。这是可选的,但建议使用,因为它避免了隐式地为每个会话进行安装的开销。如果在某些主要版本中使用网络协议,调用此函数将成为强制性要求。加载socket库以及网络加密协议相关的库,为后续使用网络相关提供支持。2.打开媒体文件
//打开文件
AVFormatContext *ic = NULL;
char path[] = "/sdcard/1080.mp4";
int re = avformat_open_input(&ic,path,0,0);
if(re == 0)
{
LOGW("avformat_open_input %s success!",path);
}
else
{
LOGW("avformat_open_input failed!:%s",av_err2str(re));
}
ffmpeg打开媒体的的过程开始于avformat_open_input()。在该方法调用之前确保av_register_all(),avformat_network_init()已经被调用。该函数用于打开多媒体数据(输入流)并且获得一些相关的信息(头数据)。对应的关闭流的函数为avformat_close_input()。
该方法中主要完成了:
-
输入输出结构体AVIOContext的初始化;
-
输入数据的协议(例如RTMP,或者file)的识别(通过一套评分机制):1.判断文件名的后缀 2.读取文件头的数据进行比对;
-
使用获得最高分的文件协议对应的URLProtocol,通过函数指针的方式,与FFMPEG连接(非专业用词);
-
剩下的就是调用该URLProtocol的函数进行open,read等操作了。
URLProtocol结构如下,是一大堆函数指针的集合(avio.h文件)
typedef struct URLProtocol {
const char *name;
int (*url_open)(URLContext *h, const char *url, int flags);
int (*url_read)(URLContext *h, unsigned char *buf, int size);
int (*url_write)(URLContext *h, const unsigned char *buf, int size);
int64_t (*url_seek)(URLContext *h, int64_t pos, int whence);
int (*url_close)(URLContext *h);
struct URLProtocol *next;
int (*url_read_pause)(URLContext *h, int pause);
int64_t (*url_read_seek)(URLContext *h, int stream_index,
int64_t timestamp, int flags);
int (*url_get_file_handle)(URLContext *h);
int priv_data_size;
const AVClass *priv_data_class;
int flags;
int (*url_check)(URLContext *h, int mask);
} URLProtocol;
URLProtocol功能就是完成各种输入协议的读写等操作。
该方法的签名为:
int avformat_open_input(AVFormatContext **ps, const char *filename, AVInputFormat *fmt, AVDictionary **options);
- ps:函数调用成功之后处理过的AVFormatContext结构体。
- file:打开的视音频流的URL。
- fmt:强制指定AVFormatContext中AVInputFormat的。这个参数一般情况下可以设置为NULL,这样ffmpeg可以自动检测AVInputFormat。
- dictionay:附加的一些选项,一般情况下可以设置为NULL。
函数执行成功的话,其返回值大于等于0。
AVFormatContext:输入数据的封装格式
-
AVIOContext *pb:输入数据的缓存
-
unsigned int nb_streams:视音频流的个数
-
AVStream **streams:视音频流
-
char filename[1024]:文件名
-
int64_t duration:时长(单位:微秒us,转换为秒需要除以1000000)
-
int bit_rate:比特率(单位bps,转换为kbps需要除以1000)
-
AVDictionary *metadata:元数据
-
char filename[1024]:输入或输出文件名
-
void avformat_close_input(AVFormatContext **s);:该函数用于关闭一个AVFormatContext,一般情况下是和avformat_open_input()成对使用的。
3.获取流信息
avformat_find_stream_info()。该函数可以读取一部分视音频数据并且获得一些相关的信息(适用于没有头部信息的文件):
int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
- ic:输入的AVFormatContext。
- options:额外的选项。
函数正常执行后返回值大于等于0。
//获取流信息
re = avformat_find_stream_info(ic,0);
if(re != 0)
{
LOGW("avformat_find_stream_info failed!");
}
LOGW("duration = %lld nb_streams = %d",ic->duration,ic->nb_streams);
获取音视频信息:
static double r2d(AVRational r)
{
return r.num==0||r.den == 0 ? 0 :(double)r.num/(double)r.den;
}
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
);
}
}
AVStream:存储每一个视频/音频流信息的结构体。
int index:标识该视频/音频流
AVCodecContext *codec:指向该视频/音频流的AVCodecContext(它们是一一对应的关系)。codec参数在58版本及之后就不会支持了,需要由codecpar参数所替代。
AVRational time_base:时基。通过该值可以把PTS,DTS转化为真正的时间。FFMPEG其他结构体中也有这个字段,但是根据我的经验,只有AVStream中的time_base是可用的。PTS*time_base=真正的时间。
int64_t duration:该视频/音频流长度。
AVDictionary *metadata:元数据信息。
AVRational avg_frame_rate:帧率(注:对视频来说,这个挺重要的)。
AVPacket attached_pic:附带的图片。比如说一些MP3,AAC音频文件附带的专辑封面。
AVCodecParameters *codecpar:codec参数在58版本及之后就不会支持了,需要由codecpar参数所替代。
获取音频流索引:
//获取音频流信息
audioStream = av_find_best_stream(ic,AVMEDIA_TYPE_AUDIO,-1,-1,NULL,0);
int av_find_best_stream (
AVFormatContext * ic,
enum AVMediaType type,
int wanted_stream_nb,
int related_stream,
AVCodec ** decoder_ret,
int flags
)
在文件中找到“最佳”流。
ic:媒体文件句柄。
type:流类型:视频,音频,字幕等。
wanted_stream_nb:用户请求的流号码,或-1用于自动选择。
related_stream:尝试查找与此相关的流(例如,在相同的程序中),如果没有,则返回-1。
decoder_ret:如果非NULL,则返回所选流的解码器。
flags:目前没有定义。
4.读取音视频帧数据
//读取帧数据
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);
}
AVPacket *av_packet_alloc(void):分配一个AVPacket结构体大小的内存。
void av_packet_unref(AVPacket *pkt):释放对应的AVPacket结构体。
AVPacket是存储压缩编码数据相关信息的结构体。
uint8_t *data:压缩编码的数据。
例如对于H.264来说。1个AVPacket的data通常对应一个NAL。
注意:在这里只是对应,而不是一模一样。他们之间有微小的差别:使用FFMPEG类库分离出多媒体文件中的H.264码流
因此在使用FFMPEG进行视音频处理的时候,常常可以将得到的AVPacket的data数据直接写成文件,从而得到视音频的码流文件。
int size:data的大小
int64_t pts:显示时间戳(num/den)
int64_t dts:解码时间戳
int stream_index:标识该AVPacket所属的视频/音频流。
读取帧数据:
int av_read_frame(AVFormatContext * s,AVPacket * pkt)
返回流的下一帧。
此函数返回存储在文件中的内容,并且不验证解码器的有效帧是什么。它会将存储在文件中的内容拆分为帧,并为每个调用返回一个。它不会忽略有效帧之间的无效数据,从而为解码器提供解码所需的最大信息。
如果pkt-> buf为NULL,那么数据包在下一个av_read_frame()或avformat_close_input()之前是有效的。否则数据包无限期地有效。在这两种情况下,数据包必须在不再需要时使用av_free_packet释放。对于视频,数据包恰好包含一帧。对于音频,如果每个帧具有已知的固定大小(例如PCM或ADPCM数据),则它包含整数个帧。如果音频帧具有可变大小(例如MPEG音频),则它包含一帧。
pkt-> pts,pkt-> dts和pkt->duration始终设置为以AVStream.time_base单位的正确值。如果视频格式具有B帧,则pkt-> pts可以是AV_NOPTS_VALUE,所以如果不解压缩有效载荷,则最好依赖pkt-> dts。
返回
如果成功返回为0,错误或文件结束时为 < 0。
设置ffmpeg将流偏移到正确的起始位置:
int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp, int flags);
s:为容器内容;
stream_index:流索引
timestamp:将要定位处的时间戳
flags:功能flag
#define AVSEEK_FLAG_BACKWARD 1 ///< seek backward
#define AVSEEK_FLAG_BYTE 2 ///< seeking based on position in bytes
#define AVSEEK_FLAG_ANY 4 ///< seek to any frame, even non-keyframes
#define AVSEEK_FLAG_FRAME 8 ///< seeking based on frame number