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

1、FFmpeg解码流程(图解)

Android万能音频播放器01--多线程解码音频数据_第1张图片

2、FFmpeg解码流程(代码)

Android万能音频播放器01--多线程解码音频数据_第2张图片

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--多线程解码音频数据)