ffmpeg SDK软硬解码基础(解封装C++ NDK)

解封装->软硬件解码->像素格式转换->重采样->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      指定格式(频繁使用)

  1. 传一个指针地址,这个指针地址指向空的空间,那么它内部就会创建这个空间
  2. 直接创建AVFormatContext在传进来,它会把这些解封装的内容全写到我们创建好的结构体里面。注意!清理出问题,动态链接库当中创建的链接只能在动态链接库里清理,外部的链接去动态链接库清理会出问题,所以要内存分离开来清理
  3. **ps指向的是空,那内存空间就有close()来清理(指针的指针:指针的地址 AVFormatContext (*p)传参就传&,把指针的地址传进来就好了)

(3)const char *url

(4)AVIContext *pb;char filename[1024];  自定义格式读或从内存里读(文件IO上下文)断开重连

(5)unsigned int nb_streams;

(6)AVStream **streams; 视频:宽高,帧率  音频:采样率,样本大小,格式

  •  AVCodecContent *codec;//过时了
  • AVRational time_base;//分数,1s占多少分之一
  • int64_t duration; //毫秒
  1. duration *((double) time_base.num/(double) time_base.den)*1000
  • int64_t nb_frames;
  • AVRational avg_frame_rate;  //帧率
  • AVCodeParameters *codecpar;(音视频参数)
  1. enum AVMediaType codec_type;
  2. enum AVCodecID codec_id;
  3. uint32_t codec_tag;
  4. int format; //样本格式
  5. int width;int height;
  6. uint64_t channel_layout;int  channel;int sample_rate;int frame_size;

(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并没有存这个内容是视频还是音频,只存了索引

  • AVBufferRef *buf;
  • int64_t pts;//pts *(num/den) 显示时间
  • int64_t dts;解码时间
  • uint8_t *data;int size;

AVPacket函数

  • AVPacket *av_packet_alloc(void);//创建并初始化 (生成一个对象也是创建了一个空间)
  • AVPacket  *av_packet_clone(const AVPacket *src);//创建并并用计数
  1. 这个复制创建一个AVPacket对象,里面指向的空间直接做一个引用计数,读完之后,会把AVPacket塞到一个缓冲队列里面,做音视频同步的部分,会生成一个音频的队列一个视频的队列,读完的那部分就清理掉
  • int av_packet_ref(AVPacket *dst,const AVPacket *src);av_packet_unref(AVPaket *pkt)
  1. 手动的引用加1,把引用从原始空间加到目标空间
  2. 减少引用,减到0,就会删掉
  • void av_packet_free(AVPacket **pkt);//清空对象并减引用计数(置零)
  • void av_init_packet(AVPacket *pkt);//默认值
  • int av_packet_from_data(AVPaket *pkt,uint8_t*data,int size);
  • int av_copy_packet(AVPacket *dst,const AVPaket *src);attribute_deprecated
  1. 解封装之后就马上解码,不做拷贝(也就是没有引用计数这回事),代码的复杂度耦合度就非常大,解码模块和封装模块共有一块空间,就容易出问题
  2. 所以用引用计数 加1之后就清理了,这数据就是另外的了,分工清楚
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)

ffmpeg SDK软硬解码基础(解封装C++ NDK)_第1张图片

修改代码

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格式

ffmpeg SDK软硬解码基础(解封装C++ NDK)_第2张图片

ffmpeg SDK软硬解码基础(解封装C++ NDK)_第3张图片

修改代码

//获取流信息
    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);

查询

遍历代码修改

 

成功遍历

ffmpeg SDK软硬解码基础(解封装C++ NDK)_第4张图片

ffmpeg SDK软硬解码基础(解封装C++ NDK)_第5张图片

开辟的空间及时清理,否则容易当掉

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

  1. #define AVSEEK_FLAG_BACKWARD 1 ///
  2. #define AVSEEK_FLAG_BYTE 2///
  3. #define AVSEEK_FLAG_ANY  4///
  4. #define AVSEEK_FLAG_FRAME 8///

或运算

代码:

#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;
}

运行结果

ffmpeg SDK软硬解码基础(解封装C++ NDK)_第6张图片

通过解封装CPU内存一下子就耗了600M,按道理是用不了那么多的最终范围变化在(30%-50%),内存到达了1.8G

最后IDE还卡住了

ffmpeg SDK软硬解码基础(解封装C++ NDK)_第7张图片

修改代码

 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指令耗费大

ffmpeg SDK软硬解码基础(解封装C++ NDK)_第8张图片

总结avPacket需要监听,这个数据泄露非常快10s-20s就把内存耗尽,创建空间对象很小倒没什么事

你可能感兴趣的:(音视频)