Android NDK開發之旅29 雲服務器Ubuntu下搭建NDK環境,並編譯FFmpeg

###前言html

由於在Linux環境下編譯FFmpeg生成庫和頭文件下比較方便,因此接下來主要操做在Linux環境下進行。可是對於Android NDK 開發新手來講,本身電腦配置Ubuntu Linux環境過程比較繁瑣。而採用雲服務器極大的方便了此過程,服務器對客戶端遠程的支持,讓我的開發更加有拓展性和創意性,並且也便利於接下來課程學習。 如今雲服務器發展迅速,有阿里雲、騰訊雲、百度雲、京東雲、美團雲、網易雲等等。開發者能夠根據本身的需求選擇合適的雲服務器。學習與使用雲服務器的過程,有利於提高本身我的能力。 這裏我購買了京東雲服務器Ubuntu 14.04 64位進行開發。linux

###一、瞭解Linux基本操做指令與經常使用快捷鍵 熟悉Linux 基本操做指令與經常使用快捷鍵能加快咱們的編程速度,在這裏我只列舉本篇文章中遇到的指令與快捷鍵,其餘的你們能夠本身查找資料去學習。android

基本指令與快捷鍵 解釋
ls 輸出當前文件夾下包含的子文件
mkdir xx 建立文件夾xx
cd ../ 進入到上一級目錄
cd /xx/ 進入到xx目錄
reset 讓終端回到預設狀態
clear 清屏
Tab鍵 內容聯想;好比,該目錄下有一個android-ndk-r14b文件夾,先輸入一個a,
再按Tab鍵就會自動把android-ndk-r14b 文件名輸入到命令行中,便於操做。
touch xxx.xx 新建xxx.xx的文件
apt-get install xx 在linux系統下安裝某個程序
rm -rf xx 刪除文件夾xx和及其目錄下全部文件

###二、雲服務器Ubuntu基本配置 瞭解完Linux基本操做指令與經常使用快捷鍵,如今咱們進行雲服務器Ubuntu基本配置。 ####2.一、下載Xshell和Xftp,用來遠程控制雲服務器。

Xshell是一個強大的安全終端模擬軟件,方便操做命令行。 Xftp是一個基於 MS windows 平臺的功能強大的SFTP、FTP 文件傳輸軟件,方便傳輸文件。git

####2.二、Xshell鏈接雲服務器 #####2.2.一、新建會話 ######(1)文件->新建
輸入雲服務器對應的公網IP地址github

填寫雲服務器公網Ip地址

######(2)屬性->用戶身份驗證
輸入雲服務器設置的用戶名和密碼 shell

設置用戶名和密碼
#####2.2.二、鏈接
鏈接成功

####3.一、xftp鏈接雲服務器 #####3.1.一、新建會話編程

######注意:這裏必定要選擇SFTP

#####3.1.二、鏈接 vim

鏈接成功
#####注意:有文件新增和刪除和修改時,記得點擊刷新按鈕刷新目錄。


###四、在雲主機Ubuntu 中安裝所需的程序 ####4.一、安裝vim並對其配置 #####4.1.一、安裝 輸入命令 ``` apt-get install vim /etc/vim/vimrc-gtk ``` ![安裝vim](http://upload-images.jianshu.io/upload_images/1824809-fadfeb39da76ede7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) #####4.1.二、查看vim安裝成功 輸入命令 ``` vim /etc/vim/vimrc ``` ![vim安裝成功頁面](http://upload-images.jianshu.io/upload_images/1824809-472c5f168553d779.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) #####4.1.三、配置vimrc ######(1)點擊 i 鍵,進入到進入編輯模式 ######(2)設定光標和行數參數 ``` set nu "顯示行號 set tabstop set ruler "顯示光標位置 set cursorline "光標高亮顯示 ``` ![](http://upload-images.jianshu.io/upload_images/1824809-f973da4e643a991a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ######注意:`-- INSERT --`表示如今是編輯模式 ######(3)保存退出 ``` 按Esc鍵 再次進入命令模式 shift + : 再輸入 x 保存退出 或shift + z z 保存退出 ``` ######其餘快捷鍵的使用 ``` shift + : 再輸入 q! 強制退出

命令模式下,x 表示刪除,dd 表示刪除行windows

####4.二、安裝 dos2unix 將DOS格式的文本文件轉換成UNIX格式
複製代碼

apt-get install dos2unix安全

####4.三、安裝make用來完成編譯工做
複製代碼

apt-get install make

####4.四、安裝unzip用來解壓zip文件包
複製代碼

apt-get install unzip

<br>
###五、搭建NDK環境
####5.一、下載Linux版本的[NDK](https://developer.android.google.cn/ndk/downloads/index.html)
我下載的是android-ndk-r14b-linux-x86_64.zip這個文件,你們則根據本身項目須要下載。
####5.二、在\目錄下建立一個usr文件夾
![](http://upload-images.jianshu.io/upload_images/1824809-b5b9ac474a9dcc5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
複製代碼

cd / mkdir usr

![建立了usr文件夾](http://upload-images.jianshu.io/upload_images/1824809-474008f5c97daaa9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
######說明:有些開發者問我,usr以外的文件是怎麼來的?這些文件是你買雲服務的時候,已經配置好的。你們能夠先不用管。
####5.三、在usr目錄中建立NDK目錄,經過Xftp上傳已下載好壓縮文件
複製代碼

mkdir NDK

####5.四、 賦予ndk文件夾下全部文件的drwx權限,使其可執行解壓操做。
複製代碼

chmod 777 -R ndk

![權限操做](http://upload-images.jianshu.io/upload_images/1824809-b908d1a40dc246dd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
####5.五、解壓上傳的的`zip`文件
 解壓zip文件到ndk文件下
複製代碼

unzip android-ndk-r14b-linux-x86_64.zip

######注意:解壓過程比較長屬於正常狀況
![解壓結果](http://upload-images.jianshu.io/upload_images/1824809-3437dd84a503c72c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
####5.六、配置NDK環境變量

使用命令`vim ~/.bashrc`  進入到環境變量配置文件進行編輯  (~表明用戶),添加
複製代碼

export NDKROOT=/usr/ndk/android-ndk-r14b export PATH=$NDKROOT:$PATH

![配置環境變量](http://upload-images.jianshu.io/upload_images/1824809-9a2a15b2118d659e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

####5.七、更新環境變量
使用命令`source ~/.bashrc`更新環境變量,`ndk-build -v` 查看是否配置成功
![環境變量配置成功](http://upload-images.jianshu.io/upload_images/1824809-d17328c94a133eeb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
至此,NDK環境已經搭建好了。

<br>
###六、編譯FFmpeg
####6.一、到[FFmpeg官網](http://ffmpeg.org/download.html)下載`FFmpge. zip`
我這裏使用`FFmpeg 2.6.9`版本,建議你們用2.8如下版本,出現問題便於解決。
####6.二、上傳FFmpeg文件並解壓
使用`xftp`上傳`ffmpeg`壓縮包,使用命令`unzip ffmpeg-2.6.9.zip`解壓文件
![](http://upload-images.jianshu.io/upload_images/1824809-caa99e0c279ca41a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
####6.三、修改 ffmpeg-2.6.9 目錄下的configure文件
修改輸出的動態庫的命名規則:
複製代碼

註釋或刪除如下語句 SLIBNAME_WITH_MAJOR='$(SLIBNAME).$(LIBMAJOR)' LIB_INSTALL_EXTRA_CMD='$$(RANLIB) "$(LIBDIR)/$(LIBNAME)"' SLIB_INSTALL_NAME='$(SLIBNAME_WITH_VERSION)' SLIB_INSTALL_LINKS='$(SLIBNAME_WITH_MAJOR) $(SLIBNAME)'

同時修改爲如下語句 SLIBNAME_WITH_MAJOR='$(SLIBPREF)$(FULLNAME)-$(LIBMAJOR)$(SLIBSUF)' LIB_INSTALL_EXTRA_CMD='$$(RANLIB)"$(LIBDIR)/$(LIBNAME)"' SLIB_INSTALL_NAME='$(SLIBNAME_WITH_MAJOR)' SLIB_INSTALL_LINKS='$(SLIBNAME)'

######注意:若是不進行這一步操做,將生成以.5六、.五、.3等結尾的庫,這種庫Android很難加載到,咱們須要的是後綴.so結尾的庫。
####6.四、編寫shell腳本文件並將其放在ffmpeg-2.6.9目錄下
```build_android.sh```文件:
複製代碼

#!/bin/bash make clean export NDK=/usr/ndk/android-ndk-r14b
export SYSROOT=$NDK/platforms/android-9/arch-arm/ export TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64 export CPU=arm export PREFIX=$(pwd)/android/$CPU export ADDI_CFLAGS="-marm"

function build_one { ./configure --target-os=linux
--prefix=$PREFIX --arch=arm
--disable-doc
--enable-shared
--disable-static
--disable-yasm
--disable-symver
--enable-gpl
--disable-ffmpeg
--disable-ffplay
--disable-ffprobe
--disable-ffserver
--disable-doc
--disable-symver
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi-
--enable-cross-compile
--sysroot=$SYSROOT
--extra-cflags="-Os -fpic $ADDI_CFLAGS"
--extra-ldflags="$ADDI_LDFLAGS"
$ADDITIONAL_CONFIGURE_FLAG make clean make -j4 make install }

>指定NDK路徑:export NDK=/usr/ndk/android-ndk-r14b ;
配置CPU架構類型:export CPU=arm,PREFIX是指定動態庫輸出的路徑,而後disable一些不須要的庫(可減少輸出的動態庫的大小);
enable-shared:生成共享庫。

#####注意:

* 換行的時候須要有\,注意不要有額外的空格,不然編譯出錯
* 腳本文件統一轉爲UTF-8無BOM格式。可經過note pad++進行轉碼,或者先由Linux建立文件再由Windows編輯。
* NDK儘可能不要使用太新的版本,通常使用Android-9便可,不然會出如今Android編譯不兼容老版本而沒法使用的問題。
* 將編寫好的shell腳本放在解壓後的ffmpeg-2.6.9文件夾中。
####6.五、build_android.sh給予執行權限。
複製代碼

chmod 777 -R build_android.sh

####6.六、執行文件build_android
複製代碼

./build_android.sh

![執行結束頁面](http://upload-images.jianshu.io/upload_images/1824809-e8c0b38c94fb21db.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

######說明:
若是出現問題`:bad interpreter : No such file or directory`,緣由:沒有將文件轉成Linux編碼格式。
轉換Linux編碼格式有兩種方式:
複製代碼

一、在Linux下建立這個文件touch build_android.sh,從Linux傳出到桌面,而後把腳本命令拷入這個文件中,再從新上傳到Linux; 二、使用 dos2unix build_android.sh 轉成Linux編碼格式

####6.七、用xftp查看生成文件
咱們發如今ffmpeg-2.6.9文件夾生成android文件夾,在android文件夾下生成arm文件夾
![生成的include和lib](http://upload-images.jianshu.io/upload_images/1824809-a8e0c18e1f083305.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![include目錄](http://upload-images.jianshu.io/upload_images/1824809-47b0310748554500.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![lib目錄](http://upload-images.jianshu.io/upload_images/1824809-e9b3131668d4738e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
至此,FFmpeg庫已編譯完成。

<br>
###七、利用編譯好的FFmpeg庫寫一個視頻解碼Demo
####7.一、jni Java聲明
複製代碼

public class VideoUtils {

public native static void decode(String input,String output);

static{
	System.loadLibrary("avutil-54");
	System.loadLibrary("swresample-1");
	System.loadLibrary("avcodec-56");
	System.loadLibrary("avformat-56");
	System.loadLibrary("swscale-3");
	System.loadLibrary("postproc-53");
	System.loadLibrary("avfilter-5");
	System.loadLibrary("avdevice-56");
	System.loadLibrary("myffmpeg");
}
複製代碼

}

####7.二、編寫C主程序
![jni目錄](http://upload-images.jianshu.io/upload_images/1824809-a2ba5b80afca7b65.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

#####ffmpeg_player.c
複製代碼

#include <com_haocai_ffmpegtest_VideoUtils.h>

#include <android/log.h>

//解碼 #include "include/libavcodec/avcodec.h" //封裝格式處理 #include "include/libavformat/avformat.h" //像素處理 #include "include/libswscale/swscale.h"

#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"FFmpeg",FORMAT,##VA_ARGS); #define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"FFmpeg",FORMAT,##VA_ARGS);

JNIEXPORT void JNICALL Java_com_haocai_ffmpegtest_VideoUtils_decode (JNIEnv env, jclass jcls, jstring input_jstr, jstring output_jstr){ //須要轉碼的視頻文件(輸入的視頻文件) const char input_cstr = (env)->GetStringUTFChars(env,input_jstr,NULL); const char output_cstr = (*env)->GetStringUTFChars(env,output_jstr,NULL);

//1.註冊全部組件
av_register_all();

//封裝格式上下文,統領全局的結構體,保存了視頻文件封裝格式的相關信息
AVFormatContext *pFormatCtx = avformat_alloc_context();

//2.打開輸入視頻文件
if (avformat_open_input(&pFormatCtx, input_cstr, NULL, NULL) != 0)
{
	LOGE("%s","沒法打開輸入視頻文件");
	return;
}

//3.獲取視頻文件信息
if (avformat_find_stream_info(pFormatCtx,NULL) < 0)
{
	LOGE("%s","沒法獲取視頻文件信息");
	return;
}

//獲取視頻流的索引位置
//遍歷全部類型的流(音頻流、視頻流、字幕流),找到視頻流
int v_stream_idx = -1;
int i = 0;
//number of streams
for (; i < pFormatCtx->nb_streams; i++)
{
	//流的類型
	if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
	{
		v_stream_idx = i;
		break;
	}
}

if (v_stream_idx == -1)
{
	LOGE("%s","找不到視頻流\n");
	return;
}

//只有知道視頻的編碼方式,纔可以根據編碼方式去找到解碼器
//獲取視頻流中的編解碼上下文
AVCodecContext *pCodecCtx = pFormatCtx->streams[v_stream_idx]->codec;
//4.根據編解碼上下文中的編碼id查找對應的解碼
AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
//(迅雷看看,找不到解碼器,臨時下載一個解碼器)
if (pCodec == NULL)
{
	LOGE("%s","找不到解碼器\n");
	return;
}

//5.打開解碼器
if (avcodec_open2(pCodecCtx,pCodec,NULL)<0)
{
	LOGE("%s","解碼器沒法打開\n");
	return;
}

//輸出視頻信息
LOGI("視頻的文件格式:%s",pFormatCtx->iformat->name);
LOGI("視頻時長:%lld", (pFormatCtx->duration)/1000000);
LOGI("視頻的寬高:%d,%d",pCodecCtx->width,pCodecCtx->height);
LOGI("解碼器的名稱:%s",pCodec->name);

//準備讀取
//AVPacket用於存儲一幀一幀的壓縮數據(H264)
//緩衝區,開闢空間
AVPacket *packet = (AVPacket*)av_malloc(sizeof(AVPacket));

//AVFrame用於存儲解碼後的像素數據(YUV)
//內存分配
AVFrame *pFrame = av_frame_alloc();
//YUV420
AVFrame *pFrameYUV = av_frame_alloc();
//只有指定了AVFrame的像素格式、畫面大小才能真正分配內存
//緩衝區分配內存
uint8_t *out_buffer = (uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));
//初始化緩衝區
avpicture_fill((AVPicture *)pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);

//用於轉碼(縮放)的參數,轉以前的寬高,轉以後的寬高,格式等
struct SwsContext *sws_ctx = sws_getContext(pCodecCtx->width,pCodecCtx->height,pCodecCtx->pix_fmt,
	pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P,
	SWS_BICUBIC, NULL, NULL, NULL);


int got_picture, ret;

FILE *fp_yuv = fopen(output_cstr, "wb+");

int frame_count = 0;

//6.一幀一幀的讀取壓縮數據
while (av_read_frame(pFormatCtx, packet) >= 0)
{
	//只要視頻壓縮數據(根據流的索引位置判斷)
	if (packet->stream_index == v_stream_idx)
	{
		//7.解碼一幀視頻壓縮數據,獲得視頻像素數據
		ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
		if (ret < 0)
		{
			LOGE("%s","解碼錯誤");
			return;
		}

		//爲0說明解碼完成,非0正在解碼
		if (got_picture)
		{
			//AVFrame轉爲像素格式YUV420,寬高
			//2 6輸入、輸出數據
			//3 7輸入、輸出畫面一行的數據的大小 AVFrame 轉換是一行一行轉換的
			//4 輸入數據第一列要轉碼的位置 從0開始
			//5 輸入畫面的高度
			sws_scale(sws_ctx, pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
				pFrameYUV->data, pFrameYUV->linesize);

			//輸出到YUV文件
			//AVFrame像素幀寫入文件
			//data解碼後的圖像像素數據(音頻採樣數據)
			//Y 亮度 UV 色度(壓縮了) 人對亮度更加敏感
			//U V 個數是Y的1/4
			int y_size = pCodecCtx->width * pCodecCtx->height;
			fwrite(pFrameYUV->data[0], 1, y_size, fp_yuv);
			fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_yuv);
			fwrite(pFrameYUV->data[2], 1, y_size / 4, fp_yuv);

			frame_count++;
			LOGI("解碼第%d幀",frame_count);
		}
	}

	//釋放資源
	av_free_packet(packet);
}

fclose(fp_yuv);

(*env)->ReleaseStringUTFChars(env,input_jstr,input_cstr);
(*env)->ReleaseStringUTFChars(env,output_jstr,output_cstr);

av_frame_free(&pFrame);

avcodec_close(pCodecCtx);

avformat_free_context(pFormatCtx);
複製代碼

}

####7.三、寫mk文件
#####Android.mk
複製代碼

LOCAL_PATH := $(call my-dir)

#ffmpeg lib include $(CLEAR_VARS) LOCAL_MODULE := avcodec LOCAL_SRC_FILES := libavcodec-56.so include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS) LOCAL_MODULE := avdevice LOCAL_SRC_FILES := libavdevice-56.so include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS) LOCAL_MODULE := avfilter LOCAL_SRC_FILES := libavfilter-5.so include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS) LOCAL_MODULE := avformat LOCAL_SRC_FILES := libavformat-56.so include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS) LOCAL_MODULE := avutil LOCAL_SRC_FILES := libavutil-54.so include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS) LOCAL_MODULE := postproc LOCAL_SRC_FILES := libpostproc-53.so include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS) LOCAL_MODULE := swresample LOCAL_SRC_FILES := libswresample-1.so include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS) LOCAL_MODULE := swscale LOCAL_SRC_FILES := libswscale-3.so include $(PREBUILT_SHARED_LIBRARY)

#myapp include $(CLEAR_VARS) LOCAL_MODULE := myffmpeg LOCAL_SRC_FILES := ffmpeg_player.c LOCAL_C_INCLUDES += $(LOCAL_PATH)/include LOCAL_LDLIBS := -llog LOCAL_SHARED_LIBRARIES := avcodec avdevice avfilter avformat avutil postproc swresample swscale include $(BUILD_SHARED_LIBRARY)

#####Applicatoin.mk
複製代碼

APP_MODULES := myffmpeg APP_ABI := armeabi APP_PLATFORM := android-9

####7.四、Android調用主函數
複製代碼

public class MainActivity extends Activity {

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

public void mDecode(View btn){
    String input = new File(Environment.getExternalStorageDirectory(),"小蘋果.mp4").getAbsolutePath();
    String output = new File(Environment.getExternalStorageDirectory(),"小蘋果_out.yuv").getAbsolutePath();
    VideoUtils.decode(input, output);
}
複製代碼

}

####7.五、其它文件配置
#####build.gradle中
複製代碼
ndk{
        moduleName "myffmpeg"
    }
    sourceSets.main{
        jni.srcDirs = []
        jniLibs.srcDir "src/main/libs"
    }
複製代碼
#####AndroidManifest.xml中
複製代碼
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
複製代碼
####7.6 結果運行
#####7.6.一、Log輸出
複製代碼

I/FFmpeg: 視頻的文件格式:mov,mp4,m4a,3gp,3g2,mj2 I/FFmpeg: 視頻時長:211 I/FFmpeg: 視頻的寬高:720,480 I/FFmpeg: 解碼器的名稱:mpeg4 I/FFmpeg: 解碼第1幀 I/FFmpeg: 解碼第2幀 I/FFmpeg: 解碼第3幀 I/FFmpeg: 解碼第4幀 I/FFmpeg: 解碼第5幀 I/FFmpeg: 解碼第6幀 I/FFmpeg: 解碼第7幀 I/FFmpeg: 解碼第8幀 I/FFmpeg: 解碼第9幀 太多省略......

#####7.6.二、mp4轉換格式生成的文件

![](http://upload-images.jianshu.io/upload_images/1824809-1fcf1a62c2b94c7b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
至此,Android調用FFmpeg庫完成。
<br>
###結語
#####雖然過程漫長,但我相信你們會漲很多知識。
<br>
###源碼下載
#####Github:[https://github.com/kpioneer123/FFmpegTest](https://github.com/kpioneer123/FFmpegTest)


複製代碼
相關文章
相關標籤/搜索