Android音頻開發(fā)(五)——使用ffmpeg播放音頻

沒有妹子你們就不會出現(xiàn).jpg

上一節(jié)储狭,我們做了很多準(zhǔn)備工作捣郊,把ffmpeg源碼編譯成so供我們使用,這下我們終于可以來真正的使用它了稻艰。請大家使用最新版本你的AS侈净,使用cmake畜侦,拋棄以前的mk。

首先旋膳,創(chuàng)建一個新的project,記得勾上Include C++ support尸变,然后一路next就ok了减俏。

我們發(fā)現(xiàn)main下面多了一個cpp文件夾,這就是你放c/c++源代碼的地方奏夫,為了用到我們的ffmpeg so文件酗昼,我們在main下面創(chuàng)建jniLibs,把編好的lib里面的so放進(jìn)去梳猪。

PS:之前我們編譯的平臺是armeabi,如果你想要x86可修改腳本文件中相關(guān)配置
111.jpg

其中main碟婆,sdl不是ffmpeg編譯的so,大家不必在意蝙叛。然后把include放進(jìn)cpp文件夾借帘,這些是需要的頭文件,

然后在cpp目錄下創(chuàng)建play_audio.cpp
a3.png

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

extern "C" {
#include "AudioDevice.h"
}

//注意加extern "C"蔫缸,否則將按照C++的編譯方法吧方法名改了拾碌,則java層找不到相應(yīng)的方法
extern "C"
JNIEXPORT jint JNICALL
Java_com_dy_ffmpeg_PlayMusicActivity_play(JNIEnv *env, jobject instance, jstring url_) {
    const char *url = env->GetStringUTFChars(url_, 0);

    LOGD("play");
    int code = play(url);
    return code;
}

extern "C"
JNIEXPORT jint JNICALL
Java_com_dy_ffmpeg_PlayMusicActivity_stop(JNIEnv *env, jobject instance) {

    // TODO
    LOGD("stop");
    int code = shutdown();
    return code;
}

下面貼出關(guān)鍵代碼

/**
 * 初始化操作校翔,創(chuàng)建解碼器
 * @param file_name  文件名
 * @param rate 采樣率
 * @param channel 通道數(shù)
 * @return
 */
int init(const char *file_name, int *rate, int *channel) {

    //初始化
    av_register_all();
    aFormatCtx = avformat_alloc_context();


    //讀取輸入的音頻文件地址
    if (avformat_open_input(&aFormatCtx, file_name, NULL, NULL) != 0) {
        LOGE("文件%s不存在灾前!\n", file_name);
        return -1; // Couldn't open file
    }

    //查找文件的流信息
    if (avformat_find_stream_info(aFormatCtx, NULL) < 0) {
        LOGE("文件流信息錯誤\n");
        return -1;
    }

    //找到第一個音頻幀
    int i;
    audioStream = -1;
    for (i = 0; i < aFormatCtx->nb_streams; i++) {
        if (aFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
            audioStream = i;
            break;
        }
    }
    if (audioStream == -1) {
        LOGE("音頻流未找到!");
        return -1;
    }


    aCodecCtx = aFormatCtx->streams[audioStream]->codec;

    //獲取相應(yīng)音頻流的解碼器
    AVCodec *aCodec = avcodec_find_decoder(aCodecCtx->codec_id);
    if (!aCodec) {
        fprintf(stderr, "不支持的音頻格式!\n");
        return -1;
    }

    if (avcodec_open2(aCodecCtx, aCodec, NULL) < 0) {
        LOGE("無法打開解碼器!\n");
        return -1; // Could not open codec
    }
    //分配一個幀指針饲嗽,指向解碼后的原始幀
    aFrame = av_frame_alloc();

    // 設(shè)置格式轉(zhuǎn)換
    swr = swr_alloc();
    //輸入通道數(shù)
    av_opt_set_int(swr, "in_channel_layout", aCodecCtx->channel_layout, 0);
    //輸出通道數(shù)
    av_opt_set_int(swr, "out_channel_layout", aCodecCtx->channel_layout, 0);
    //輸入采樣率
    av_opt_set_int(swr, "in_sample_rate", aCodecCtx->sample_rate, 0);
    //輸出采樣率
    av_opt_set_int(swr, "out_sample_rate", aCodecCtx->sample_rate, 0);
    //輸入采樣位寬
    av_opt_set_sample_fmt(swr, "in_sample_fmt", aCodecCtx->sample_fmt, 0);
    //輸出采樣位寬喝噪,16bit
    av_opt_set_sample_fmt(swr, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);
    swr_init(swr);

    // 分配PCM數(shù)據(jù)緩沖區(qū)大小
    outputBufferSize = 8196;
    outputBuffer = (uint8_t *) malloc(sizeof(uint8_t) * outputBufferSize);

    // 返回采樣率和通道數(shù)
    *rate = aCodecCtx->sample_rate;
    *channel = aCodecCtx->channels;
    return 0;
}
// 獲取PCM數(shù)據(jù), 自動回調(diào)獲取
int getPCM(void **pcm, size_t *pcmSize) {
    LOGD("getPcm");
    while (av_read_frame(aFormatCtx, &packet) >= 0) {

        int frameFinished = 0;
        // Is this a packet from the audio stream?
        if (packet.stream_index == audioStream) {
            avcodec_decode_audio4(aCodecCtx, aFrame, &frameFinished, &packet);

            if (frameFinished) {
                // data_size為音頻數(shù)據(jù)所占的字節(jié)數(shù)
                int data_size = av_samples_get_buffer_size(
                        aFrame->linesize, aCodecCtx->channels,
                        aFrame->nb_samples, aCodecCtx->sample_fmt, 1);

                // 這里內(nèi)存再分配可能存在問題
                if (data_size > outputBufferSize) {
                    outputBufferSize = data_size;
                    outputBuffer = (uint8_t *) realloc(outputBuffer,
                                                       sizeof(uint8_t) * outputBufferSize);
                }

                // 音頻格式轉(zhuǎn)換
                swr_convert(swr, &outputBuffer, aFrame->nb_samples,
                            (uint8_t const **) (aFrame->extended_data),
                            aFrame->nb_samples);

                // 返回pcm數(shù)據(jù)
                *pcm = outputBuffer;
                *pcmSize = data_size;
                return 0;
            }
        }
    }
    return -1;
}

然后是activity

package com.dy.ffmpeg;

import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;

public class PlayMusicActivity extends AppCompatActivity implements View.OnClickListener {
    static {
        System.loadLibrary("play_audio");
    }

    private Button play;
    private Button stop;
    private String url;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_play_music);
        initView();
    }

    private void initView() {
        play = (Button) findViewById(R.id.play);
        stop = (Button) findViewById(R.id.stop);

        play.setOnClickListener(this);
        stop.setOnClickListener(this);
        //獲取文件地址,注意把音頻文件放在該目錄下,或者修改成你自己需要的路徑
        String folderurl = Environment.getExternalStorageDirectory().getPath();
        url = folderurl + "/Valentine.mp3";
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.play:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        int code = play(url);
                        System.out.println("開始播放");
                    }
                }).start();


                break;
            case R.id.stop:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        int code = stop();
                        if (code == 0) {
                            System.out.println("停止成功");
                        }
                    }
                }).start();


                break;
        }

    }

    private native int play(String url);

    private native int stop();
}

接下來就是最重要的編寫cmake

接下來就是最重要的編寫cmake

接下來就是最重要的編寫cmake

上面我們做了這么多工作晚唇,gradle編譯的時候怎么知道去哪找這些東西呢哩陕,就是重要的app目錄下得CMakeLists.txt文件

cmake_minimum_required(VERSION 3.4.1)
#設(shè)置so目錄
set(lib_src_DIR ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI})
#添加頭文件查找路徑赫舒,包括引入庫的和自己寫的
include_directories(
     ${CMAKE_SOURCE_DIR}/src/main/cpp/include
)
#添加動態(tài)庫或靜態(tài)庫接癌,其中本地的動態(tài)庫名稱,位置可以由set_target_properties設(shè)置
add_library(avcodec-57_lib SHARED IMPORTED)
set_target_properties(avcodec-57_lib PROPERTIES IMPORTED_LOCATION
                          ${lib_src_DIR}/libavcodec-57.so)

add_library(avformat-57_lib SHARED IMPORTED)
set_target_properties(avformat-57_lib PROPERTIES IMPORTED_LOCATION
                     ${lib_src_DIR}/libavformat-57.so)

add_library(avutil-55_lib SHARED IMPORTED)
set_target_properties(avutil-55_lib PROPERTIES IMPORTED_LOCATION
                     ${lib_src_DIR}/libavutil-55.so)

add_library(swresample-2_lib SHARED IMPORTED)
set_target_properties(swresample-2_lib PROPERTIES IMPORTED_LOCATION
                     ${lib_src_DIR}/libswresample-2.so)

add_library(swscale-4_lib SHARED IMPORTED)
set_target_properties(swscale-4_lib PROPERTIES IMPORTED_LOCATION
                     ${lib_src_DIR}/libswscale-4.so)


# build application's shared lib
#這里是你自己編寫的c/c++,因為用到ffmpeg的so缨叫,所以下面要鏈接它的庫
add_library(play_video SHARED
         ${CMAKE_SOURCE_DIR}/src/main/cpp/play_video.cpp)
add_library(decode_video SHARED
         ${CMAKE_SOURCE_DIR}/src/main/cpp/decode_video.cpp)
add_library(play_audio SHARED
         ${CMAKE_SOURCE_DIR}/src/main/cpp/play_audio.cpp
         ${CMAKE_SOURCE_DIR}/src/main/cpp/AudioDevice.c
         ${CMAKE_SOURCE_DIR}/src/main/cpp/FFmpegAudioPlay.c)
#通過名稱查找并引入庫耻姥,可以引入 NDK 中的庫有咨,比如日志模塊
find_library( # Sets the name of the path variable.
              log-lib

              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )
#添加參加編譯的庫名稱座享,也可以是絕對路徑,注意被依賴的模塊寫在后面
#特別注意据某,你想生成幾個so诗箍,就要寫幾個link挽唉,寫在一個link里面是錯誤的
target_link_libraries(play_video
log
android
avcodec-57_lib
avformat-57_lib
avutil-55_lib
swresample-2_lib
swscale-4_lib
)
target_link_libraries(decode_video
log
android
avcodec-57_lib
avformat-57_lib
avutil-55_lib
swresample-2_lib
swscale-4_lib
)

#我們需要opensl es來輔助播放筷狼,注意加入opensl es
target_link_libraries(play_audio
log
OpenSLES
android
avcodec-57_lib
avformat-57_lib
avutil-55_lib
swresample-2_lib
swscale-4_lib
)

然后編譯執(zhí)行埂材,done,完整demo請移步我的github

參考http://blog.csdn.net/leixiaohua1020/article/details/47008825

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末严拒,一起剝皮案震驚了整個濱河市裤唠,隨后出現(xiàn)的幾起案子莹痢,更是在濱河造成了極大的恐慌,老刑警劉巖航瞭,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刊侯,死亡現(xiàn)場離奇詭異长窄,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)挠日,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來翰舌,“玉大人嚣潜,你說我怎么就攤上這事∫渭” “怎么了懂算?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長庇麦。 經(jīng)常有香客問我计技,道長,這世上最難降的妖魔是什么山橄? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上睡雇,老公的妹妹穿的比我還像新娘萌衬。我一直安慰自己,他們只是感情好它抱,可當(dāng)我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布秕豫。 她就那樣靜靜地躺著,像睡著了一般观蓄。 火紅的嫁衣襯著肌膚如雪混移。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天侮穿,我揣著相機(jī)與錄音沫屡,去河邊找鬼。 笑死撮珠,一個胖子當(dāng)著我的面吹牛沮脖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播芯急,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼勺届,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了娶耍?” 一聲冷哼從身側(cè)響起免姿,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎榕酒,沒想到半個月后胚膊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡想鹰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年紊婉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辑舷。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡喻犁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出何缓,到底是詐尸還是另有隱情肢础,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布碌廓,位于F島的核電站传轰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏谷婆。R本人自食惡果不足惜慨蛙,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一辽聊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧股淡,春花似錦身隐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至埠帕,卻和暖如春垢揩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背敛瓷。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工叁巨, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人呐籽。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓锋勺,卻偏偏與公主長得像,于是被迫代替她去往敵國和親狡蝶。 傳聞我的和親對象是個殘疾皇子庶橱,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,452評論 2 348

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