上一節(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)配置
其中main碟婆,sdl不是ffmpeg編譯的so,大家不必在意蝙叛。然后把include放進(jìn)cpp文件夾借帘,這些是需要的頭文件,
然后在cpp目錄下創(chuàng)建play_audio.cpp
#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