[FFmpeg万能音频播放器]解码音频数据(三)

徘徊边缘 提交于 2020-10-22 08:55:04

注意本文代码是在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)

下面附上工程结构图

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!