[FFmpeg萬能音訊播放器]解碼音訊資料(三)

2020-10-21 13:00:26

注意本文程式碼是在https://blog.csdn.net/we1less/article/details/109096144的基礎上寫的。

mainxml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    xmlns:android="http://schemas.android.com/apk/res/android" >
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="run"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="開始"
        android:onClick="begin"/>
</LinearLayout>

mainactivity

package com.example.godvmusic;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;

import com.example.myplayer.Demo;
import com.example.myplayer.listener.GonPreparedListener;
import com.example.myplayer.log.GLog;
import com.example.myplayer.player.GPlayer;

public class MainActivity extends AppCompatActivity {

    private Demo mDemo;
    private GPlayer mGPlayer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mDemo = new Demo();
        mGPlayer = new GPlayer();
        mGPlayer.setmGonPreparedListener(new GonPreparedListener() {
            @Override
            public void onPrepared() {
                GLog.d("準備成功");
                mGPlayer.play();
            }
        });
    }

    public void run(View view) {
        mDemo.showDetial();
    }

    public void begin(View view) {
        GLog.d("開始準備");
        mGPlayer.setSource("http://sc1.111ttt.cn/2017/1/05/09/298092035545.mp3");
        mGPlayer.prepared();
    }
}

開兩條許可權

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

interface GonPreparedListener

package com.example.myplayer.listener;

public interface GonPreparedListener {
    //回撥方法
    void onPrepared();
}

GLog.java    log類

package com.example.myplayer.log;

import android.util.Log;

public class GLog {
    public static void d(String msg) {
        Log.d("godv", msg);
    }
}

GPlayer.java   準備/播放類

package com.example.myplayer.player;

import android.text.TextUtils;

import com.example.myplayer.listener.GonPreparedListener;
import com.example.myplayer.log.GLog;

public class GPlayer {
    static {
        System.loadLibrary("native-lib");
        System.loadLibrary("avutil");
        System.loadLibrary("swresample");
        System.loadLibrary("avcodec");
        System.loadLibrary("avformat");
        System.loadLibrary("swscale");
        System.loadLibrary("postproc");
        System.loadLibrary("avfilter");
        System.loadLibrary("avdevice");
    }

    private String source;

    private GonPreparedListener mGonPreparedListener;

    public void setmGonPreparedListener(GonPreparedListener mGonPreparedListener) {
        this.mGonPreparedListener = mGonPreparedListener;
    }

    public void setSource(String source) {
        this.source = source;
    }

    public GPlayer() {
    }

    public void prepared() {
        if (TextUtils.isEmpty(source)) {
            GLog.d("source not be empty");
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
                GLog.d("Thread start");
                //呼叫執行方法
                n_prepared(source);

            }
        }).start();
    }

    //回撥方法 c->java
    public void onCallPrepared() {
        if (mGonPreparedListener != null) {
            mGonPreparedListener.onPrepared();
        }
    }

    public void play() {
        if (TextUtils.isEmpty(source)) {
            GLog.d("source not be empty");
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
                GLog.d("Thread start");
                //呼叫執行方法
                n_start();
            }
        }).start();
    }

    //呼叫執行方法java->c
    public native void n_prepared(String source);

    public native void n_start();
}

native-lib.cpp        *****之下的是本篇新加的

#include <jni.h>
#include <string>
#include "GLog.h"

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
}

extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_myplayer_Demo_stringFromJNI(JNIEnv *env, jobject thiz) {
    std::string hello = "Hello godv";

    return env->NewStringUTF(hello.c_str());
}

extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_myplayer_Demo_showDetial(JNIEnv *env, jobject thiz) {
    av_register_all();
    AVCodec *c_temp = av_codec_next(NULL);
    while (c_temp != NULL) {
        switch (c_temp->type) {
            case AVMEDIA_TYPE_VIDEO:
                LOGD("[Video]:%s", c_temp->name);
                break;
            case AVMEDIA_TYPE_AUDIO:
                LOGD("[Audio]:%s", c_temp->name);
                break;
            default:
                LOGD("[Other]:%s", c_temp->name);
                break;
        }
        c_temp = c_temp->next;
    }
    std::string hello = "Hello godv";
    return env->NewStringUTF(hello.c_str());
}

/******************************************************************************************/
#include "GCallJava.h"
#include "GFFmpeg.h"

//宣告指標
GCallJava *callJava = NULL;
GFFmpeg *gFFmpeg = NULL;
JavaVM *javaVm = NULL;

extern "C"
JNIEXPORT void JNICALL
Java_com_example_myplayer_player_GPlayer_n_1prepared(JNIEnv *env, jobject thiz, jstring source_) {
    if (LOG_DEBUG) {
        LOGD("c++ Java_com_example_myplayer_player_GPlayer_n_1prepared");
    }
    //將jstring 轉成const char *
    const char *source = env->GetStringUTFChars(source_, 0);
    if (callJava == NULL) {
        callJava = new GCallJava(javaVm, env, thiz);
    }
    if (gFFmpeg == NULL) {
        gFFmpeg = new GFFmpeg(callJava, source);
        if (LOG_DEBUG) {
            LOGD("c++ gFFmpeg->prepared()");
        }
    }
    gFFmpeg->prepared();
    //釋放記憶體
    //env->ReleaseStringUTFChars(source_, source);
}

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *unused) {
    JNIEnv *env;
    javaVm = vm;
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
        return -1;
    }
    return JNI_VERSION_1_4;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_myplayer_player_GPlayer_n_1start(JNIEnv *env, jobject thiz) {
    if (gFFmpeg != NULL) {
        gFFmpeg->start();
    }
}

GLog.h

#include "android/log.h"

#ifndef GODVMUSIC_GLOG_H
#define GODVMUSIC_GLOG_H

#endif //GODVMUSIC_GLOG_H

#define LOG_TAG "godv"

#define LOG_DEBUG true

#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)

#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

GCallJava.h

#ifndef GODVMUSIC_GCALLJAVA_H
#define GODVMUSIC_GCALLJAVA_H

#include <jni.h>
#include "GLog.h"

#define MAIN_THREAD 0
#define CHILD_THREAD 1

//全域性呼叫java類中的方法
class GCallJava {

public:
    _JavaVM *javaVm = NULL;
    JNIEnv *jniEnv = NULL;
    jobject jobj;

    //回撥方法 c->java
    jmethodID jmid_onCallPrepared;

public:
    //構造器
    GCallJava(_JavaVM *jvm, JNIEnv *env, jobject obj);

    ~GCallJava();

    void onCallPrepare(int type);

};


#endif //GODVMUSIC_GCALLJAVA_H

GCallJava.cpp

#include "GCallJava.h"

GCallJava::GCallJava(_JavaVM *jvm, JNIEnv *env, jobject obj) {
    this->javaVm = jvm;
    this->jniEnv = env;
    //獲取全域性的obj
    this->jobj = jniEnv->NewGlobalRef(obj);
    //根據全域性obj獲取jclass
    jclass clz = jniEnv->GetObjectClass(jobj);
    if (!clz) {
        if (LOG_DEBUG) {
            LOGD("get jclass error");
        }
        return;
    }
    //通過class java方法名 方法簽名返回jmethodID
    jmid_onCallPrepared = env->GetMethodID(clz, "onCallPrepared", "()V");
}

GCallJava::~GCallJava() {

}

void GCallJava::onCallPrepare(int type) {
    switch (type) {
        case MAIN_THREAD:
            //根據jobj 和methodid 呼叫java中方法
            jniEnv->CallVoidMethod(jobj, jmid_onCallPrepared);
            break;
        case CHILD_THREAD:
            //子執行緒需要獲取全域性的jnienv
            JNIEnv *jniEnv;
            if (javaVm->AttachCurrentThread(&jniEnv, 0) != JNI_OK) {
                if (LOG_DEBUG) {
                    LOGD("get child thread jnienv error");
                }
            }
            if (LOG_DEBUG) {
                LOGD("child thread CallVoidMethod jmid_onCallPrepared");
            }
            jniEnv->CallVoidMethod(jobj, jmid_onCallPrepared);
            //噹噹前執行緒關閉
            javaVm->DetachCurrentThread();
            break;
    }

};

GFFmpeg.h

#ifndef GODVMUSIC_GFFMPEG_H
#define GODVMUSIC_GFFMPEG_H

#include "GCallJava.h"
#include "pthread.h"
#include "GAudio.h"

extern "C" {
#include <libavformat/avformat.h>
};


class GFFmpeg {
public:
    GCallJava *callJava = NULL;

    const char *url = NULL;

    pthread_t decodeThread;

    AVFormatContext *gFormatctx = NULL;

    GAudio *gAudio = NULL;

public:
    GFFmpeg(GCallJava *callJava, const char *url);

    ~GFFmpeg();

    //準備方法
    void prepared();

    //解碼執行緒
    void decodeFFmpegThread();

    //播放方法
    void start();
};


#endif //GODVMUSIC_GFFMPEG_H

GFFmpeg.cpp

#include "GFFmpeg.h"

GFFmpeg::GFFmpeg(GCallJava *callJava, const char *url) {
    this->callJava = callJava;
    this->url = url;
}

//回撥方法
void *decodeCallBack(void *data) {
    GFFmpeg *gfFmpeg = (GFFmpeg *) data;
    if (LOG_DEBUG) {
        LOGD("c++ gFFmpeg->decodeCallBack()");
    }
    gfFmpeg->decodeFFmpegThread();
    pthread_exit(&gfFmpeg->decodeThread);
}

void GFFmpeg::prepared() {
    pthread_create(&decodeThread, NULL, decodeCallBack, this);
}

void GFFmpeg::decodeFFmpegThread() {
    av_register_all();
    avformat_network_init();
    gFormatctx = avformat_alloc_context();
    if (avformat_open_input(&gFormatctx, url, NULL, NULL) != 0) {
        if (LOG_DEBUG) {
            LOGD("can not open url %s", url);
        }
        return;
    }
    if (LOG_DEBUG) {
        LOGD("open url %s", url);
    }
    //獲取流
    if (avformat_find_stream_info(gFormatctx, NULL) < 0) {
        if (LOG_DEBUG) {
            LOGD("can not find stream");
        }
        return;
    }
    if (LOG_DEBUG) {
        LOGD("open url stream");
    }
    //找AVMEDIA_TYPE_AUDIO流
    for (int i = 0; i < gFormatctx->nb_streams; i++) {
        if (gFormatctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
            if (gAudio == NULL) {
                gAudio = new GAudio();
                gAudio->streamIndex = i;
                gAudio->codecpar = gFormatctx->streams[i]->codecpar;
            }
        }
    }
    if (LOG_DEBUG) {
        LOGD("already find stream");
    }
    //得到解碼器
    AVCodec *avCodec = avcodec_find_decoder(gAudio->codecpar->codec_id);
    if (!avCodec) {
        if (LOG_DEBUG) {
            LOGD("can not find codec");
        }
        return;
    }
    if (LOG_DEBUG) {
        LOGD("already find codec");
    }
    //獲得解碼器上下文
    gAudio->avCodecContext = avcodec_alloc_context3(avCodec);
    if (!gAudio->avCodecContext) {
        if (LOG_DEBUG) {
            LOGD("can not alloc avCodecContext");
        }
        return;
    }
    if (LOG_DEBUG) {
        LOGD("already alloc avCodecContext");
    }
    //把屬性賦值到解碼器上下文中
    if (avcodec_parameters_to_context(gAudio->avCodecContext, gAudio->codecpar) < 0) {
        if (LOG_DEBUG) {
            LOGD("can not parameters to avCodecContext");
        }
        return;
    }
    if (LOG_DEBUG) {
        LOGD("already parameters to avCodecContext");
    }
    //開啟解碼器
    if (avcodec_open2(gAudio->avCodecContext, avCodec, 0) != 0) {
        if (LOG_DEBUG) {
            LOGD("can not open audio");
        }
        return;
    }
    if (LOG_DEBUG) {
        LOGD("already onCallPrepare ...");
    }
    callJava->onCallPrepare(1);
}

void GFFmpeg::start() {
    if (gAudio == NULL) {
        if (LOG_DEBUG) {
            LOGE("gAudio is null");
        }
        return;
    }
    int count = 0;
    while (1) {
        AVPacket *avPacket = av_packet_alloc();
        if (av_read_frame(gFormatctx, avPacket) == 0) {
            if (avPacket->stream_index == gAudio->streamIndex) {
                count++;
                if (LOG_DEBUG) {
                    LOGE("解碼第%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;
            break;
        }
    }
}

GAudio.h

#ifndef GODVMUSIC_GAUDIO_H
#define GODVMUSIC_GAUDIO_H

extern "C" {
#include "include/libavcodec/avcodec.h"
};

class GAudio {
public:
    int streamIndex = -1;

    AVCodecParameters *codecpar;

    AVCodecContext *avCodecContext = NULL;
public:
    GAudio();

    ~GAudio();
};


#endif //GODVMUSIC_GAUDIO_H

GAudio.cpp

#include "GAudio.h"

GAudio::GAudio() {
}

GAudio::~GAudio() {
}

CMakeLists.txt          注意要把檔案加進來

add_library(
             native-lib
             SHARED
             ${CMAKE_SOURCE_DIR}/src/main/cpp/native-lib.cpp
             ${CMAKE_SOURCE_DIR}/src/main/cpp/GCallJava.cpp
             ${CMAKE_SOURCE_DIR}/src/main/cpp/GFFmpeg.cpp
             ${CMAKE_SOURCE_DIR}/src/main/cpp/GAudio.cpp)

下面附上工程結構圖