Android萬(wàn)能音頻播放器01--多線(xiàn)程解碼音頻數(shù)據(jù)

1、FFmpeg解碼流程(圖解)

2、FFmpeg解碼流程(代碼)

3弟蚀、實(shí)現(xiàn)步驟

  1. 注冊(cè)解碼器并初始化網(wǎng)絡(luò)
av_register_all();
avformat_network_init();
  1. 打開(kāi)文件或網(wǎng)絡(luò)流
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. 利用解碼器創(chuàng)建解碼器上下文
AVCodecContext *avCodecContext = avcodec_alloc_context3(dec);
avcodec_parameters_to_context(audio->avCodecContext, audio->codecpar)
  1. 打開(kāi)解碼器
avcodec_open2(audio->avCodecContext, dec, 0)
  1. 讀取音頻幀
AVPacket *packet = av_packet_alloc();
av_read_frame(pFormatCtx, packet);

4、代碼結(jié)構(gòu)

4.1熙含、做解碼前的準(zhǔn)備

解碼前的準(zhǔn)備工作是在C++層做的,所以設(shè)置一個(gè)準(zhǔn)備工作完成以后的回調(diào)方法艇纺,其實(shí)是由C++層通知Java層:

public interface JfOnPreparedListener {
    void onPrepared();
}

所以要為C++層提供一個(gè)方法來(lái)回調(diào)上面的方法:

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(){

    }

    /**
     * 設(shè)置數(shù)據(jù)源
     * @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()完成后要調(diào)用JfOnPreparedListener
     */
    public void onCallPrepared(){
        if (jfOnPreparedListener != null)
        {
            jfOnPreparedListener.onPrepared();
        }
    }
    public native void n_prepared(String source);
}

因?yàn)镃++層要調(diào)用Java層代碼,所以先在C++層實(shí)現(xiàn)這個(gè)功能邮弹,新建一個(gè)C++類(lèi)-JfCallJava:
JfCallJava.h

#define MAIN_THREAD 0
#define CHILD_THREAD 1
/**
 * C++層調(diào)用Java層的類(lèi)
 */
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);//這里調(diào)用Java層的onCallPrepare方法黔衡,因?yàn)榭赡茉谥骶€(xiàn)程或者子線(xiàn)程中調(diào)用,所以加了這個(gè)方法
};

JfCallJava.cpp

#include "JfCallJava.h"

JfCallJava::JfCallJava(JavaVM *vm, JNIEnv *env, jobject *obj) {
    this->javaVM = vm;
    this->jniEnv = env;
    this->jobj = env->NewGlobalRef(*obj);//設(shè)為全局

    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();
    }
}

編碼的過(guò)程由FFmpeg在一個(gè)子線(xiàn)程中完成腌乡,創(chuàng)建一個(gè)C++類(lèi)-JfFFmpeg盟劫,將source的路徑傳進(jìn)去
JfFFmpeg.h

class JfFFmpeg {

public:
    JfCallJava *callJava = NULL;//初始化回調(diào)java方法封裝
    const char *url = NULL;//文件的url
    pthread_t decodeThread = NULL;//解碼的子線(xiàn)程


    /**
     * 解碼相關(guān)
     */
    AVFormatContext *pAFmtCtx = NULL; //封裝上下文
    JfAudio *audio = NULL;//封裝Audio信息
public:
    JfFFmpeg(JfCallJava *callJava,const char *url);//參數(shù)都是從外面?zhèn)鬟M(jìn)來(lái)的
    ~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);//退出線(xiàn)程
}
/**
 * 正式解碼的過(guò)程,開(kāi)一個(gè)子線(xiàn)程解碼
 */
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)){//將解碼器中信息復(fù)制到上下文當(dāng)中
        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);
}

創(chuàng)建一個(gè)C++類(lèi)-JfAudio与纽,保存音頻解碼過(guò)程中要用到的參數(shù):
JfAudio.h

class JfAudio {

public:
    int streamIndex = -1;//stream索引
    AVCodecParameters *codecpar = NULL;//包含音視頻參數(shù)的結(jié)構(gòu)體侣签。很重要,可以用來(lái)獲取音視頻參數(shù)中的寬度急迂、高度影所、采樣率、編碼格式等信息僚碎。
    AVCodecContext *pACodecCtx = NULL;
public:
    JfAudio();
    ~JfAudio();
};

JfAudio.cpp

#include "JfAudio.h"

JfAudio::JfAudio() {

}

JfAudio::~JfAudio() {

}

到這里猴娩,我們已經(jīng)完成了所有的準(zhǔn)備工作,接下來(lái)就要開(kāi)始讀取音頻幀

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++層去實(shí)現(xiàn)native方法:

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

實(shí)現(xiàn)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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末卷中,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子渊抽,更是在濱河造成了極大的恐慌蟆豫,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,589評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件懒闷,死亡現(xiàn)場(chǎng)離奇詭異十减,居然都是意外死亡栈幸,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門(mén)嫉称,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)侦镇,“玉大人,你說(shuō)我怎么就攤上這事织阅】欠保” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,933評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵荔棉,是天一觀的道長(zhǎng)闹炉。 經(jīng)常有香客問(wèn)我,道長(zhǎng)润樱,這世上最難降的妖魔是什么渣触? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,976評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮壹若,結(jié)果婚禮上嗅钻,老公的妹妹穿的比我還像新娘。我一直安慰自己店展,他們只是感情好养篓,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著赂蕴,像睡著了一般柳弄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上概说,一...
    開(kāi)封第一講書(shū)人閱讀 51,775評(píng)論 1 307
  • 那天碧注,我揣著相機(jī)與錄音,去河邊找鬼糖赔。 笑死萍丐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的挂捻。 我是一名探鬼主播碉纺,決...
    沈念sama閱讀 40,474評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼刻撒!你這毒婦竟也來(lái)了骨田?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,359評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤声怔,失蹤者是張志新(化名)和其女友劉穎态贤,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體醋火,經(jīng)...
    沈念sama閱讀 45,854評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡悠汽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評(píng)論 3 338
  • 正文 我和宋清朗相戀三年箱吕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片柿冲。...
    茶點(diǎn)故事閱讀 40,146評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡茬高,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出假抄,到底是詐尸還是另有隱情怎栽,我是刑警寧澤,帶...
    沈念sama閱讀 35,826評(píng)論 5 346
  • 正文 年R本政府宣布宿饱,位于F島的核電站熏瞄,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏谬以。R本人自食惡果不足惜强饮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望为黎。 院中可真熱鬧邮丰,春花似錦、人聲如沸铭乾。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,029評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)片橡。三九已至,卻和暖如春淮野,著一層夾襖步出監(jiān)牢的瞬間捧书,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,153評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工骤星, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留经瓷,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,420評(píng)論 3 373
  • 正文 我出身青樓洞难,卻偏偏與公主長(zhǎng)得像舆吮,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子队贱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評(píng)論 2 356

推薦閱讀更多精彩內(nèi)容

  • 一色冀、簡(jiǎn)歷準(zhǔn)備 1、個(gè)人技能 (1)自定義控件柱嫌、UI設(shè)計(jì)锋恬、常用動(dòng)畫(huà)特效 自定義控件 ①為什么要自定義控件? Andr...
    lucas777閱讀 5,213評(píng)論 2 54
  • Android FFmpeg音頻播放 本文介紹了使用opensl es和FFmpeg在Android平臺(tái)上實(shí)現(xiàn)音頻...
    JasonXiao閱讀 4,430評(píng)論 9 26
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)编丘、插件与学、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,109評(píng)論 4 62
  • 古來(lái)英雄,世易時(shí)移卵佛,功業(yè)何在杨赤?物議紛紜,取舍異道级遭,殊難定論望拖!然彼超拔之英姿,邈遠(yuǎn)之情懷挫鸽,如歌如詩(shī)说敏,美蘊(yùn)其中...
    東溪生閱讀 630評(píng)論 1 2
  • 像這荷塘里的花兒,看輕你時(shí)你重丢郊,看重你時(shí)你輕盔沫,看遠(yuǎn)時(shí)你近,看近時(shí)你遠(yuǎn)枫匾,看濃時(shí)你淡架诞,看淡時(shí)你濃……你很輕又很重,很淡...
    天堂里的魚(yú)閱讀 327評(píng)論 0 0