解封装->软硬件解码->像素格式转换->重采样->pts/dts->同步策略
ffmpeg音视频解封装用到的函数和结构体
av_register_all() 注册所有的解封装格式和加封装格式(通用各种格式)打Open()之前必须先调用它
avformat_network_init() 直接解Rtsp(我们的摄像机或摄像机协议),通过网络打开文件,还支持Http
比如:把一个视频放到web服务器上面,只要把http地址传过来,它同样可以打开,需要把这个网络模块初始化好
int avformat_open_input(...) 解析出里面的视频流,音频流及参数还有视频帧的索引
(1)av_register_all avformat_network_init 已调用
(2)AVFormatContext **ps 指定格式(频繁使用)
(3)const char *url
(4)AVIContext *pb;char filename[1024]; 自定义格式读或从内存里读(文件IO上下文)断开重连
(5)unsigned int nb_streams;
(6)AVStream **streams; 视频:宽高,帧率 音频:采样率,样本大小,格式
(7)int64_t duration;//AV_TIME_BASE 这是宏,时间长度
(8)int64_t bit_rate; 1s占文件的大小,8bit一个字节,网络适应
(9)void avformat_close_input(AVFormatContext **s); 指针地址,关闭,把一块指针空间清理掉之后,要把它置零(而这个函数内部就帮我们置零了)
int avformat_find_stream_info(...) 查找文件的格式或索引 (探测)“获取文件(流)中的信息” 包含以下(1)(2)两个参数
(1)AVFormatContext *ic;
(2)AVDictionary **options
flv(没法获取时长)H262 mpeg2
av_find_best_stream(...)
(1)解封装之后,需要处理,去分开音频和视频,要知道它们对应的参数,独立处理,需要找到音频流和视频流
(2)另一种方式是直接遍历它的返回值的成员数组streams[],存了所有流(视频,音频,字幕,扩展信息
int av_find_best_stream(
AVFormatContext *ic,
enum AVMediaType type,
int wanted_stream_nb,
int related_stream,
AVCodec ** decoder_ret,
int flags )
结构体
AVFormatContest
AVStream 视频流和音频流都放在这里
AVPacket 针对封装完之后的具体的数据包 它包含了pts,dts,stream inst,它把HR_64的间隔符(0001或001)去除掉了,在做成自己格式发出去时,要添加,通过av_read_frame(...)读取,就能区分是音频还是视频了,而AVPacket并没有存这个内容是视频还是音频,只存了索引
AVPacket函数
extern "C" JNIEXPORT jstring JNICALL
Java_com_starpanda_activity_first_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
//__android_log_print(ANDROID_LOG_FATAL, "test log", "test log for android");
hello += avcodec_configuration(); //链接命令失败,这样是没有加载库
//初始化解封装
av_register_all();
//初始化网络
avformat_network_init();
//打开文件
av_log_set_level(AV_LOG_DEBUG);//调bug
AVFormatContext *ic = NULL;
char path[]="/sdcard/周杰伦 甜甜的.mid";
int re = avformat_open_input(&ic, path, 0, 0); //指向指针的指针
if (re == 0) {
LOGW("avformat open input %s success!", path);
avformat_close_input(&ic);
} else{
LOGW("avformat open input failed!:%s", av_err2str(re));
}
return env->NewStringUTF(hello.c_str());
}
报错
添加
#添加头文件路径(相对于本文件路径)include_directories()搜索文件
include_directories("include")
#设置ffmpeg库所在路径的变量,我的路径不是cmakelist同目录下的libs去找到abi,CMAKE_CURRENT_SOURCE_DIR指的就是CMakeLists.txt
set(FF ${CMAKE_CURRENT_SOURCE_DIR}/libs/${ANDROID_ABI})
#set(FF ${CMAKE_CURRENT_SOURCE_DIR}/libs/${ANDROID_ABI})
#添加库
add_library(avcodec SHARED IMPORTED)
#导入哪边的库(设置参数)
set_target_properties(avcodec PROPERTIES IMPORTED_LOCATION ${FF}/libavcodec.so)
add_library(avformat SHARED IMPORTED)
set_target_properties(avformat PROPERTIES IMPORTED_LOCATION ${FF}/libavformat.so)
add_library(avutil SHARED IMPORTED)
set_target_properties(avutil PROPERTIES IMPORTED_LOCATION ${FF}/libavutil.so)
修改代码
char path[]="/sdcard/zhoujielun_sweet.mp3";
int re = avformat_open_input(&ic, path, 0, 0); //指向指针的指针
if (re != 0) {
LOGW("avformat open input failed!:%s", av_err2str(re));
return env->NewStringUTF(hello.c_str());
}
LOGW("avformat open input %s success!", path);
LOGW("duration=%lld nb_stream =%d",ic->duration,ic->nb_streams);
//关闭上下文
avformat_close_input(&ic);
return env->NewStringUTF(hello.c_str());
无法读取.mp3格式
修改代码
//获取流信息
re = avformat_find_stream_info(ic, 0);
if (re!=0) {
LOGW("avformat find input failed!");
}
LOGW("duration=%lld nb_stream =%d",ic->duration,ic->nb_streams);
//关闭上下文
avformat_close_input(&ic);
查询
遍历代码修改
成功遍历
开辟的空间及时清理,否则容易当掉
av_read_frame
AVFormatContext *s
AVPacket *pkt 不能传null,这是输出参数,如果说传一个空指针进来,就得是指向指针的指针,这时候可以把指针指向新的空间,从设计原则来说,就不能给你去创建这个指针的空间
空间有两块:
所以这一块对象的空间需要你创建,对象中有个data,其压缩的信息,这部分空间不需要分配(我们也不知道),解码出来就知道帧有多大(宽=100,高=100,格式=rgb=3),一副画面就100*100*3=30000个字节就可以了,而压缩的画面是不固定的,变动大的时候,这个值就大一点“画面效果差”(变动小,值就小),这种压缩变动可以通过参数设置,静止时效果好
关键帧(带全部信息)
非关键帧(P,T)
C语言当中0表示成功
return 0 if OK,<0 on error or end of file
av_seek_frame
int av_seek_frame(AVFormatContext *s,int stream_index,//-1 default 考虑视频帧来移,返回当前帧,那部分音频就要丢弃掉
int64_t timestamp,//AVStream.time_base
int flags);//标识位 表示我们移的方法 假如:第7秒正好不是关键帧,一定要隔2秒钟才是关键帧,要是硬移,这画面就无法解码
(关键帧算法)
时间戳,界面当中的滑动条(通过比例×总时长=传递位置)
av_seek_frame_flag
或运算
代码:
#include
#include
#include
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,"testff",__VA_ARGS__)
//__VA_ARGS__相当于后面...(变参)的所有传参数都接在后面
/**
* 预处理->编译(汇编)->链接->执行
* 预处理:宏,include引用等先处理
* 编译:把所有的语法,函数调用编译成汇编指令(每个文件是单独编译的),注意!没有定义但是有声明编译是不会报错的
* 链接:多个编译用到的库链接到一个库或执行文件当中,它就会去找这个定义,声明是告诉你一个名字,然后去找它
* (1)如果在库中,就把库的地址拷过来 (2)如果在代码中,就从静态库复制过来
*/
extern "C"{
#include
#include
}
#include
using namespace std;
//分数转化成带小数这样的浮点数
static double r2d(AVRational r){
return r.num == 0 || r.den == 0 ? 0 : (double) r.num / (double) r.den;
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_starpanda_activity_first_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
//__android_log_print(ANDROID_LOG_FATAL, "test log", "test log for android");
hello += avcodec_configuration(); //链接命令失败,这样是没有加载库
//初始化解封装
av_register_all();
//初始化网络
avformat_network_init();
//打开文件
av_log_set_level(AV_LOG_DEBUG);//调bug
AVFormatContext *ic = NULL;
char path[]="/sdcard/gaoxiao.flv";
int re = avformat_open_input(&ic, path, 0, 0);//指向指针的指针
if (re != 0) {
LOGW("avformat open input failed!:%s", av_err2str(re));
return env->NewStringUTF(hello.c_str());
}
LOGW("avformat open input %s success!", path);
//获取流信息
re = avformat_find_stream_info(ic, 0);
if (re!=0) {
LOGW("avformat find input failed!");
}
LOGW("duration=%lld nb_stream =%d",ic->duration,ic->nb_streams);
int fps = 0;
int width = 0;
int height = 0;
//这两个需要存下了
int videoStream = 0;
int audioStream = 0;
//int codec_id = 0;//编码器的id
//有几个流(默认音频流和视频流
for (int i = 0; i nb_streams ; i++) {
AVStream *avStream = ic->streams[i];
//判断音频还是视频,根据codecpar内部的参数
if (avStream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
LOGW("视频数据");
videoStream = i;
fps = r2d(avStream->avg_frame_rate);
LOGW("fps=%d,width=%d height =%d codeid=%d pixformat=%d",fps,
avStream->codecpar->width,
avStream->codecpar->height,
avStream->codecpar->codec_id, //不用声明
avStream->codecpar->format //像素格式
);
} else if (avStream->codecpar->codec_type==AVMEDIA_TYPE_AUDIO) {
LOGW("音频数据");
audioStream = i;
LOGW("sample_rate=%d channels=%d sample_format=%d",
avStream->codecpar->sample_rate,
avStream->codecpar->channels,
avStream->codecpar->format
);
}
}
//ic->streams[videoStream]
//获取音视频流信息
audioStream = av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
LOGW("av_find_best_stream audioStream=%d",audioStream);
//读取帧数据
AVPacket *avPacket = av_packet_alloc();//包含创建一个对象空间并做初始化
for (;;) {//无限循环
int re = av_read_frame(ic, avPacket);//贯穿整个上下文
if (re != 0) {
LOGW("读取到结尾处!");
int pos = 20 * r2d(ic->streams[videoStream]->time_base);//在20s处
av_seek_frame(ic,videoStream,pos,AVSEEK_FLAG_BACKWARD|AVSEEK_FLAG_FRAME);//2.流索引,3.时间戳(跳到20s位置),4.往后找或者是关键帧
continue;
}
LOGW("stream=%d size=%d pts=%lld flag=%d",
avPacket->stream_index,//索引
avPacket->size,//关键帧与非关键帧大小的区别
avPacket->pts,//一帧的时间
avPacket->flags //音频,视频区别
);
}
//关闭上下文
avformat_close_input(&ic);
return env->NewStringUTF(hello.c_str());
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_starpanda_activity_first_MainActivity_Open(JNIEnv *env, jobject instance, jstring url_,
jobject handle) {
const char *url = env->GetStringUTFChars(url_, 0);
FILE *fp = fopen(url, "rb");
if (!fp) {
LOGW("%s open failed!", url);
} else{
LOGW("%s open success!", url);
fclose(fp);
}
env->ReleaseStringUTFChars(url_, url);
return true;
}
运行结果
通过解封装CPU内存一下子就耗了600M,按道理是用不了那么多的最终范围变化在(30%-50%),内存到达了1.8G
最后IDE还卡住了
修改代码
LOGW("stream=%d size=%d pts=%lld flag=%d",
avPacket->stream_index,//索引
avPacket->size,//关键帧与非关键帧大小的区别
avPacket->pts,//一帧的时间
avPacket->flags //音频,视频区别
);
//中间操作
//引用
av_packet_unref(avPacket);
效果:很明显,内存就下来了,最终停留在21.5M,CPU之所以占那么高,是因为现在没有做延时,以最快的速度来读的,取决于IO速度,IO速度越快,CPU指令耗费大
总结avPacket需要监听,这个数据泄露非常快10s-20s就把内存耗尽,创建空间对象很小倒没什么事