美文网首页
Android万能音频播放器01--多线程解码音频数据

Android万能音频播放器01--多线程解码音频数据

作者: 张俊峰0613 | 来源:发表于2019-01-04 15:08 被阅读0次

    1、FFmpeg解码流程(图解)

    2、FFmpeg解码流程(代码)

    3、实现步骤

    1. 注册解码器并初始化网络
    av_register_all();
    avformat_network_init();
    
    1. 打开文件或网络流
    AVFormatContext *pFormatCtx = avformat_alloc_context();
    avformat_open_input(&pFormatCtx, url, NULL, NULL)
    
    1. 获取流信息
    avformat_find_stream_info(pFormatCtx, NULL)
    
    1. 获取音频流
    pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO
    
    1. 获取解码器
    AVCodec *dec = avcodec_find_decoder(audio->codecpar->codec_id);
    
    1. 利用解码器创建解码器上下文
    AVCodecContext *avCodecContext = avcodec_alloc_context3(dec);
    avcodec_parameters_to_context(audio->avCodecContext, audio->codecpar)
    
    1. 打开解码器
    avcodec_open2(audio->avCodecContext, dec, 0)
    
    1. 读取音频帧
    AVPacket *packet = av_packet_alloc();
    av_read_frame(pFormatCtx, packet);
    

    4、代码结构

    4.1、做解码前的准备

    解码前的准备工作是在C++层做的,所以设置一个准备工作完成以后的回调方法,其实是由C++层通知Java层:

    public interface JfOnPreparedListener {
        void onPrepared();
    }
    

    所以要为C++层提供一个方法来回调上面的方法:

    public void onCallPrepared(){
        if (jfOnPreparedListener != null)
        {
            jfOnPreparedListener.onPrepared();
        }
    }
    

    Java层代码:

    public class JfPlayer {
        static {
            System.loadLibrary("avutil-55");
            System.loadLibrary("avcodec-57");
            System.loadLibrary("avformat-57");
            System.loadLibrary("avdevice-57");
            System.loadLibrary("swresample-2");
            System.loadLibrary("swscale-4");
            System.loadLibrary("postproc-54");
            System.loadLibrary("avfilter-6");
            System.loadLibrary("native-lib");
        }
    
        private String source;
        private JfOnPreparedListener jfOnPreparedListener;
        public JfPlayer(){
    
        }
    
        /**
         * 设置数据源
         * @param source
         */
        public void setSource(String source) {
            this.source = source;
        }
    
        public void setJfOnPreparedListener(JfOnPreparedListener jfOnPreparedListener) {
            this.jfOnPreparedListener = jfOnPreparedListener;
        }
    
        public void prepared(){
            if (TextUtils.isEmpty(source)){
                JfLog.w("SOURCE IS EMPTY");
                return;
            }
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    n_prepared(source);
                }
            }).start();
        }
    
        /**
         * C++层n_prepare()完成后要调用JfOnPreparedListener
         */
        public void onCallPrepared(){
            if (jfOnPreparedListener != null)
            {
                jfOnPreparedListener.onPrepared();
            }
        }
        public native void n_prepared(String source);
    }
    

    因为C++层要调用Java层代码,所以先在C++层实现这个功能,新建一个C++类-JfCallJava:
    JfCallJava.h

    #define MAIN_THREAD 0
    #define CHILD_THREAD 1
    /**
     * C++层调用Java层的类
     */
    class JfCallJava {
    
    public:
        JavaVM *javaVM = NULL;
        JNIEnv *jniEnv = NULL;
        jobject jobj;
    
        jmethodID jmid_prepared;
    public:
        JfCallJava(JavaVM *vm,JNIEnv *env,jobject *obj);
        ~JfCallJava();
    
        void onCallPrepared(int threadType);//这里调用Java层的onCallPrepare方法,因为可能在主线程或者子线程中调用,所以加了这个方法
    };
    

    JfCallJava.cpp

    #include "JfCallJava.h"
    
    JfCallJava::JfCallJava(JavaVM *vm, JNIEnv *env, jobject *obj) {
        this->javaVM = vm;
        this->jniEnv = env;
        this->jobj = env->NewGlobalRef(*obj);//设为全局
    
        jclass jclz = env->GetObjectClass(jobj);
        if (!jclz) {
            LOGE("get jclass error");
            return ;
        }
    
        jmid_prepared = env->GetMethodID(jclz,"onCallPrepared","()V");
    }
    
    JfCallJava::~JfCallJava() {
    
    }
    
    void JfCallJava::onCallPrepared(int threadType) {
        if (threadType == MAIN_THREAD){
            jniEnv->CallVoidMethod(jobj,jmid_prepared);
        } else if (threadType == CHILD_THREAD){
            JNIEnv *jniEnv;
            if (javaVM->AttachCurrentThread(&jniEnv,0) != JNI_OK){
                if (LOG_DEBUG) {
                    LOGE("GET CHILD THREAD JNIENV ERROR");
                    return;
                }
            }
    
            jniEnv->CallVoidMethod(jobj,jmid_prepared);
            javaVM->DetachCurrentThread();
        }
    }
    

    编码的过程由FFmpeg在一个子线程中完成,创建一个C++类-JfFFmpeg,将source的路径传进去
    JfFFmpeg.h

    class JfFFmpeg {
    
    public:
        JfCallJava *callJava = NULL;//初始化回调java方法封装
        const char *url = NULL;//文件的url
        pthread_t decodeThread = NULL;//解码的子线程
    
    
        /**
         * 解码相关
         */
        AVFormatContext *pAFmtCtx = NULL; //封装上下文
        JfAudio *audio = NULL;//封装Audio信息
    public:
        JfFFmpeg(JfCallJava *callJava,const char *url);//参数都是从外面传进来的
        ~JfFFmpeg();
    
        void prepare();
        void decodeAudioThread();
    };
    

    JfFFmpeg.cpp

    JfFFmpeg::JfFFmpeg(JfCallJava *callJava, const char *url) {
        this->callJava = callJava;
        this->url = url;
    }
    
    
    void *decodeFFmpeg(void *data){
        JfFFmpeg *jfFFmpeg = (JfFFmpeg *)(data);
        jfFFmpeg->decodeAudioThread();
        pthread_exit(&jfFFmpeg->decodeThread);//退出线程
    }
    /**
     * 正式解码的过程,开一个子线程解码
     */
    void JfFFmpeg::prepare() {
        pthread_create(&decodeThread,NULL,decodeFFmpeg,this);
    }
    
    void JfFFmpeg::decodeAudioThread() {
        av_register_all();
        avformat_network_init();
    
        pAFmtCtx = avformat_alloc_context();
    
        if (avformat_open_input(&pAFmtCtx,url,NULL,NULL) != 0){
            if (LOG_DEBUG){
                LOGE("open url file error url === %s",url);
            }
            return;
        }
    
        if (avformat_find_stream_info(pAFmtCtx,NULL) < 0){
            if (LOG_DEBUG){
                LOGE("find stream info error url === %s",url);
            }
            return;
        }
    
        for (int i = 0; i < pAFmtCtx->nb_streams; i++) {
            if (pAFmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
                if (audio == NULL) {
                    audio = new JfAudio;
                    audio->streamIndex = i;
                    audio->codecpar = pAFmtCtx->streams[i]->codecpar;
                }
            }
        }
    
        AVCodec *dec = avcodec_find_decoder(audio->codecpar->codec_id);
        if (!dec){
            if (LOG_DEBUG){
                LOGE("FIND DECODER ERROR");
            }
            return;
        }
    
        audio->pACodecCtx = avcodec_alloc_context3(dec);
        if (!audio->pACodecCtx){
            if (LOG_DEBUG){
                LOGE("avcodec_alloc_context3 ERROR");
            }
            return;
        }
    
        if (avcodec_parameters_to_context(audio->pACodecCtx,audio->codecpar)){//将解码器中信息复制到上下文当中
            if (LOG_DEBUG){
                LOGE("avcodec_parameters_to_context ERROR");
            }
            return;
        }
    
        if (avcodec_open2(audio->pACodecCtx,dec,NULL) < 0){
            if (LOG_DEBUG){
                LOGE("avcodec_open2 ERROR");
            }
            return;
        }
    
        callJava->onCallPrepared(CHILD_THREAD);
    }
    

    创建一个C++类-JfAudio,保存音频解码过程中要用到的参数:
    JfAudio.h

    class JfAudio {
    
    public:
        int streamIndex = -1;//stream索引
        AVCodecParameters *codecpar = NULL;//包含音视频参数的结构体。很重要,可以用来获取音视频参数中的宽度、高度、采样率、编码格式等信息。
        AVCodecContext *pACodecCtx = NULL;
    public:
        JfAudio();
        ~JfAudio();
    };
    

    JfAudio.cpp

    #include "JfAudio.h"
    
    JfAudio::JfAudio() {
    
    }
    
    JfAudio::~JfAudio() {
    
    }
    

    到这里,我们已经完成了所有的准备工作,接下来就要开始读取音频帧

    Java层添加native方法:

    public void start(){
        if (TextUtils.isEmpty(source)){
            JfLog.w("SOURCE IS EMPTY");
            return;
        }
    
        new Thread(new Runnable() {
            @Override
            public void run() {
                n_start();
            }
        }).start();
    }
    
    public native void n_start();
    

    C++层去实现native方法:

    extern "C"
    JNIEXPORT void JNICALL
    Java_com_example_myplayer_player_JfPlayer_n_1start(JNIEnv *env, jobject instance) {
        // TODO
        if (ffmpeg != NULL){
            ffmpeg->start();
        }
    }
    

    实现start方法:

    void JfFFmpeg::start() {
        if (audio == NULL) {
            if (LOG_DEBUG){
                LOGE("AUDIO == NULL");
            }
        }
    
        int count;
        while (1) {
            AVPacket *avPacket = av_packet_alloc();
            if (av_read_frame(pAFmtCtx,avPacket) == 0) {
                if (avPacket->stream_index == audio->streamIndex){
                    count++;
                    if (LOG_DEBUG) {
                        LOGD("解码第%d帧",count);
                    }
                    av_packet_free(&avPacket);
                    av_free(avPacket);
                    avPacket = NULL;
                } else {
                    av_packet_free(&avPacket);
                    av_free(avPacket);
                    avPacket = NULL;
                }
            } else {
                av_packet_free(&avPacket);
                av_free(avPacket);
                avPacket = NULL;
            }
        }
    }
    

    这里每次取出一帧都要释放缓存。

    源码地址:https://github.com/Xiaoben336/SuperAudioPlayer

    相关文章

      网友评论

          本文标题:Android万能音频播放器01--多线程解码音频数据

          本文链接:https://www.haomeiwen.com/subject/rhwalqtx.html