注意本文程式碼是在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)
下面附上工程結構圖