31.FFmpeg+OpenGLES+OpenSLES播放器实现(五.FFmpeg解封装)

项目源码
FFmpeg开发文档
Android Studio的开发环境已经准备好,接下来开始正式的写ndk代码,首先创建一个FFmpeg工具类,添加native方法

import android.view.Surface;
public class FFmpegPlayer {
    static {
        System.loadLibrary("ffmpeg");
    }
    /**
     * 播放视频
     */
    public native void playVideo(String url,Surface surface);

}

传入的Surface对象是用于显示播放界面的,这里先传入,后边再说

然后生成对应的头文件,之前在eclipse上开发的时候都是通过javah命令生成头文件后将对应的方法名拷贝到我们的c文件中,这部分的生成方法可以查看之前的博客。现在我们使用的Android Studio3.x可以一键生成对应的native方法,将光标选中方法名然后alt+enter选择create function xxx即可

extern "C"
JNIEXPORT void JNICALL
Java_com_rzm_ffmpegplayer_FFmpegPlayer_playVideo(JNIEnv *env, jobject instance, jstring url_,
                                                 jobject surface) {
    const char *url = env->GetStringUTFChars(url_, 0);

    env->ReleaseStringUTFChars(url_, url);
}

今天来进行的是FFmpeg的第一部分,解封装

解封装

一个mp4文件可能是视频流音频流字幕流等等多个流的一个结合体,而我们要实现视频和音频的播放,就需要将这个结合体拆分开来,单独的进行视频播放,音频播放,实现同步,这是实现播放的一个必要的条件。所以播放的第一步就要进行源文件的解封装,解封装涉及到的一些接口如下

av_register_all()

初始化libavformat 并注册所有的muxers和demuxers以及各种协议,当然,如果你只需要初始化特定的支持组件,那么单独调用特定的方法即可,一般直接这样做一劳永逸

avformat_open_input

打开输入流(可以是本地流也可以是网络流,如果是网络的,那么需要avformat_network_init()方法支持)并读取视频头信息,视频头通常包含一些视频基本信息,比如说视频格式,streams的数量等等。注意此时编解码器未打开,最后必须使用avformat_close_input(&avFormatContext)进行关闭,避免造成泄漏

avformat_network_init()

初始化全局的网络组件,支持rtfp协议的时候需要打开这个开关,ffmpeg推荐打开这个全局的网络开关,因为可以减少单独对每个单独回话做设置的开销

avformat_find_stream_info()

打开流文件之后执行这个方法,读取媒体文件获取到流信息,这个方法对于没有视频头的视频尤其有效(MPEG-2),,此方法执行之后会将读取到的流信息填充到AVFormatContext中的各个流的轨道上,这样一来,AVFormatContext->streams[i]中就有信息了.这个方法可以打开部分解码器并保存在第二个参数AVDictionary中,但是他无法保证打开全部的解码器,如果存在null那么也是正常现象,这里第二个参数我们直接置为NULL

av_find_best_stream

媒体信息已经获取到了,接下来需要将视频流和音频流区分开来,可以通过av_find_best_stream来获取流轨道的index,在老版本的ffmpeg上我们是通过遍历所有的流通过对比codec_type来判断的,代码如下

    for(int i = 0; i < avFormatContext->nb_streams; i++){
        AVStream *avStream = avFormatContext->streams[i];
        if (avStream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
            //找到视频index
            videoIndex = i;
        } else if (avStream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO){
            //找到音频index
            audioIndex = i;            
        }
    }

通过这个方法获取的方式为下,验证一下,你会发现二者结果相同

videoIndex = av_find_best_stream(avFormatContext,AVMEDIA_TYPE_VIDEO,-1,-1,NULL,0);
audioIndex = av_find_best_stream(avFormatContext,AVMEDIA_TYPE_AUDIO,-1,-1,NULL,0);
av_read_frame

返回流的下一帧,这个函数返回文件中存储的内容。每调用一次,它就将返回一帧数据,这是从文件存储的内容中切割出来的

av_packet_unref

这个方法会擦除packet空间,不再指向这个packet的缓存空间,另外也会将packet重置为默认状态。一帧数据也就是一个ACPacket使用完之后需要回收,否则会造成内存急剧增长,下边分别是调用这个方法和不调用这个方法的内存变化状态


调用.png

不调用.png
完整的代码如下
/**
 * 播放视频,支持本地和网络两种
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_rzm_ffmpegplayer_FFmpegPlayer_playVideo(JNIEnv *env, jobject instance, jstring url_,
                                                 jobject surface) {
    const char *url = env->GetStringUTFChars(url_, 0);

    //初始化解封装
    av_register_all();
    //初始化全局网络组件,可选推荐使用,在使用网络协议的场景中这是必选的(rtfp http)
    avformat_network_init();

    AVFormatContext *avFormatContext = NULL;
    //指定输入的格式,如果为NULL,将自动检测输入格式,所以可置为NULL
    //AVInputFormat *fmt = NULL;
    //打开输入文件,可以是本地视频或者网络视频
    int result = avformat_open_input(&avFormatContext,url,NULL,NULL);

    //打开输入内容失败
    if(result != 0){
        LOGE("avformat_open_input failed!:%s",av_err2str(result));
        return;
    }

    //打开输入成功
    LOGI("avformat_open_input success!:%s",av_err2str(result));

    //读取媒体文件的分组以获得流信息。这个对于没有标题的文件格式(如MPEG)很有用。这个函数还计算实际的帧率在
    //MPEG-2重复的情况下帧模式。
    result = avformat_find_stream_info(avFormatContext,NULL);
    if (result < 0){
        LOGE("avformat_find_stream_info failed: %s",av_err2str(result));
    }

    //获取到了输入文件信息,打印一下视频时长和nb_streams
    LOGI("duration = %lld nb_streams=%d",avFormatContext->duration,avFormatContext->nb_streams);

    //分离音视频,获取音视频在源文件中的streams index
    int videoIndex = 0;
    int audioIndex = 1;
    int fps = 0;
    for(int i = 0; i < avFormatContext->nb_streams; i++){
        AVStream *avStream = avFormatContext->streams[i];
        if (avStream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
            //找到视频index
            videoIndex = i;
            LOGI("video index = %d",videoIndex);
            //FPS是图像领域中的定义,是指画面每秒传输帧数
            fps = r2d(avStream->avg_frame_rate);

            LOGI("video info ---- fps = %d fps den= %d fps num= %d width=%d height=%d code id=%d format=%d",
                 fps,
                 avStream->avg_frame_rate.den,
                 avStream->avg_frame_rate.num,
                 avStream->codecpar->width,
                 avStream->codecpar->height,
                 avStream->codecpar->codec_id,
                 avStream->codecpar->format
            );

        } else if (avStream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO){
            //找到音频index
            audioIndex = i;
            LOGI("audio index = %d sampe_rate=%d channels=%d sample_format=%d",
                 audioIndex,
                 avStream->codecpar->sample_rate,
                 avStream->codecpar->channels,
                 avStream->codecpar->format
            );
        }
    }

    //上边通过遍历streams音视频的index,还可以通过提供的接口获取
    videoIndex = av_find_best_stream(avFormatContext,AVMEDIA_TYPE_VIDEO,-1,-1,NULL,0);
    audioIndex = av_find_best_stream(avFormatContext,AVMEDIA_TYPE_AUDIO,-1,-1,NULL,0);
    LOGI("av_find_best_stream videoIndex=%d audioIndex=%d",videoIndex,audioIndex);

    //读取帧数据

    //Allocate an AVPacket and set its fields to default values
    //存储压缩数据,对于视频,它通常应该包含一个压缩帧。对于音频它可能包含几个压缩帧
    AVPacket *avPacket = av_packet_alloc();
    for (;;) {

        //Return the next frame of a stream.
        int read_result = av_read_frame(avFormatContext,avPacket);
        if(read_result != 0){
            //读取到结尾处,从20秒位置继续开始播放
            LOGI("读取到结尾处 %s",av_err2str(read_result));
            //跳转到指定的position播放,最后一个参数表示
            //int pos = 200000 * r2d(avFormatContext->streams[videoIndex]->time_base);
            //av_seek_frame(avFormatContext,videoIndex,pos,AVSEEK_FLAG_BACKWARD|AVSEEK_FLAG_FRAME );
            //LOGI("avFormatContext->streams[videoIndex]->time_base= %d",avFormatContext->streams[videoIndex]->time_base);
            //continue;
            break;
        }
        LOGW("stream = %d size =%d pts=%lld flag=%d pos = %d",
             avPacket->stream_index,avPacket->size,avPacket->pts,avPacket->flags,avPacket->pos
        );

        //packet使用完成之后执行,否则内存会急剧增长
        //不再引用这个packet指向的空间,并且将packet置为default状态
        av_packet_unref(avPacket);
    }

    //关闭上下文
    avformat_close_input(&avFormatContext);
    env->ReleaseStringUTFChars(url_, url);
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容