NDK開發彙總
文章目錄
- 一 RTMP
- 二 RTMPDump
-
- 1 交叉編譯
- 2 AS內建
- 二 RTMP視訊資料
-
- 1 FLV tags 結構
- 2 資料區(視訊資料)
-
- AVCVIDEOPACKET
- AVC 序列頭
- 其他
- NALU
- 三 內建x264
-
- 1 交叉編譯
- 2 AS內建
- IDR
- H.264資料
- 碼率和幀率
一 RTMP
與HTTP(超文本傳輸協定)同樣是一個基于TCP的Real Time Messaging Protocol(實時消息傳輸協定)。由Adobe Systems公司為Flash播放器和伺服器之間音頻、視訊和資料傳輸開發的一種開放協定 。在國内被廣泛的應用于直播領域。HTTP預設端口為80,RTMP則為1935。
本質上我們通過閱讀Adobe的協定規範,通過與伺服器建立TCP通信,根據協定格式生成與解析資料即可使用RTMP進行直播。當然我們也可以借助一些實作了RTMP協定的開源庫如RTMPDump來完成這一過程。
二 RTMPDump
RTMPDump 是一個用來處理RTMP流媒體的開源工具包。它能夠單獨使用進行RTMP的通信,也可以內建到FFmpeg中通過FFmpeg接口來使用RTMPDump。
注意:android包隻是一些指令工具,我們需要源碼
RTMPDump源碼下載下傳
1 交叉編譯
在Android中可以直接借助NDK在JNI層調用RTMPDump來完成RTMP通信。但是首先必須得進行交叉編譯。
RTMPDump源碼結構如下:
在根目錄下提供了一個
Makefile
與一些
.c
源檔案。這裡的源檔案将會編譯出一系列的可執行檔案。然後我們需要的并不是可執行檔案,真正的對RTMP的實作都在librtmp子目錄中。在這個子目錄中同樣包含了一個
Makefile
檔案。通過閱讀
Makefile
發現,它的源碼并不多:
OBJS=rtmp.o log.o amf.o hashswf.o parseurl.o
。是以我們不進行預編譯,即直接放入AS中借助
CMakeLists.txt
來進行編譯。這麼做可以讓我們友善的對庫本身進行調試或修改(實際上我們确實會稍微修改這個庫的源碼)。
2 AS內建
- 在AS中複制librtmp置于:
,并為其編寫CMakeLists.txtsrc/main/cpp/librtmp
#預編譯宏
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DNO_CRYPTO" )
#所有源檔案放入 rtmp_source 變量
file(GLOB rtmp_source *.c)
#編譯靜态庫
add_library(rtmp STATIC ${rtmp_source} )
- 在
中導入這個CMakeLists.txtapp/CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)
#導入其他目錄cmakelist
add_subdirectory(src/main/cpp/librtmp)
add_library(XXX SHARED ...)
#XXX需要連結rtmp庫
target_link_libraries(XXX rtmp ...)
一個CmakeList用另外的CmakeList
編譯報錯:
fatal error: ‘openssl/ssl.h’ file not found
解決:
二 RTMP視訊資料
RTMP視訊流格式與FLV很相似,通過檢視FLV的格式文檔,就能夠知道RTMP視訊資料應該怎麼拼接。RTMP中的資料就是由FLV的TAG中的資料區構成。
1 FLV tags 結構
flv分析器打開flv檔案
組裝rtmp包看的是資料區,從17開始
字段 | 位元組 | 描述 |
---|---|---|
類型 | 1 | 0x08: 音頻 0x09: 視訊 0x12: 腳本(描述資訊) |
資料大小 | 3 | 資料區的大小,不包括標頭。 |
時間戳 | 3 | 目前幀相對時間戳,機關是毫秒。相對于第一個TAG時戳。 |
時戳擴充 | 1 | 如果時戳大于0xFFFFFF,将會存在位元組。 |
流ID | 3 | 總是0 |
資料區 | n | 音、視訊包 |
2 資料區(視訊資料)
上圖從17開始是資料區,注意下表标題是占位不是位元組,17(1+7 :關鍵幀+進階編碼)
字段 | 占位 | 描述 |
---|---|---|
幀類型 | 4 | 1: 關鍵幀 2: 普通幀 … |
編碼ID | 4 | 7: 進階視訊編碼 AVC … |
視訊資料 | n | AVC則需要下面的AVCVIDEOPACKET |
AVCVIDEOPACKET
字段 | 位元組 | 描述 |
---|---|---|
類型 | 1 | 0:AVC 序列頭(指導播放器如何解碼) 1:其他單元(其他NALU) |
合成時間 | 3 | 對于AVC,全為0 |
資料 | n | 類型不同,資料不同 |
SPS:Sequence Paramater Set
PPS:Picture Paramater Set
AVC 序列頭
在AVCVIDEOPACKET 中如果類型為0,則後續資料為:
類型 | 位元組 | 說明 |
---|---|---|
版本 | 1 | 0x01 |
編碼規格 | 3 | sps[1]+sps[2]+sps[3] (後面說明) |
幾個位元組表示 NALU 的長度 | 1 | 0xFF,包長為 (0xFF& 3) + 1,也就是4位元組表示 |
SPS個數 | 1 | 0xE1,個數為0xE1 & 0x1F 也就是1 |
SPS長度 | 2 | 整個sps的長度 |
sps的内容 | n | 整個sps |
pps個數 | 1 | 0x01,不用計算就是1 |
pps長度 | 2 | 整個pps長度 |
pps内容 | n | 整個pps内容 |
其他
在AVCVIDEOPACKET 中如果類型為1,則後續資料為:
類型 | 位元組 | 說明 |
---|---|---|
包長 | 由AVC 序列頭中定義 | 後續長度 |
資料 | n | H.264資料 |
一般情況下,組裝的RTMPPacket(RTMPDump中的結構體)為:
NALU
NALU就是NAL UNIT,nal單元。NAL全稱Network Abstract Layer, 即網絡抽象層,H.264在網絡上傳輸的結構。一幀圖檔經過 H.264 編碼器之後,就被編碼為一個或多個片(slice),而裝載着這些片(slice)的載體,就是 NALU 了 。
我們通過x264編碼獲得一組或者多組
x264_nal_t
。結合RTMP,我們需要區分的是SPS、PPS、關鍵幀與普通幀:
enum nal_unit_type_e
{
NAL_UNKNOWN = 0,
NAL_SLICE = 1,
NAL_SLICE_DPA = 2,
NAL_SLICE_DPB = 3,
NAL_SLICE_DPC = 4,
NAL_SLICE_IDR = 5, /* ref_idc != 0 */ //關鍵幀片
NAL_SEI = 6, /* ref_idc == 0 */
NAL_SPS = 7, //sps片
NAL_PPS = 8, //pps片
NAL_AUD = 9,
NAL_FILLER = 12,
/* ref_idc == 0 for 6,9,10,11,12 */
};
三 內建x264
x264是一個C語言編寫的目前對H.264标準支援最完善的編解碼庫。與RTMPDump一樣同樣直接在Android中使用,也可以內建進入FFMpeg。
在linux下載下傳編譯:
wget https://code.videolan.org/videolan/x264/-/archive/master/x264-master.tar.bz2
1 交叉編譯
在Android中使用x264,首先需要預編譯出x264的靜/動态庫
#!/bin/bash
PREFIX=./android/armeabi-v7a
NDK_ROOT=/home/a/android-ndk-r17c
TOOLCHAIN=$NDK_ROOT/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64
FLAGS="-isysroot $NDK_ROOT/sysroot -isystem $NDK_ROOT/sysroot/usr/include/arm-linux-androideabi -D__ANDROID_API__=17 -g -DANDROID -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -mthumb -Wa,--noexecstack -Wformat -Werror=format-security -O0 -fPIC"
#--disable-cli 不需要指令行工具
#--enable-static 靜态庫
#和ffmpeg差不多
./configure \
--prefix=$PREFIX \
--disable-cli \
--enable-static \
--enable-pic \
--host=arm-linux \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--sysroot=$NDK_ROOT/platforms/android-17/arch-arm \
--extra-cflags="$FLAGS"
make clean
make install
但是編譯x264我們需要注意一點,x264在進行環境檢測的時候,使用的是比較寬松的方式,對于我們目前需要編譯的android-17為目标來說,編譯出的庫在使用上會出現問題(對于18以上不會)。
我們需要修改
configure
腳本,在腳本中搜尋
cc_check
vim如何搜尋:在vim裡底線指令模式(進入按/),輸入 /cc_check
cc_check() {
......
if [ $compiler_style = MS ]; then
cc_cmd="$CC conftest.c -Werror=implicit-function-declaration $(cc_cflags $CFLAGS $CHECK_CFLAGS $2) -link $(cl_ldflags $2 $LDFLAGSCLI $LDFLAGS)"
else
cc_cmd="$CC conftest.c -Werror=implicit-function-declaration $CFLAGS $CHECK_CFLAGS $2 $LDFLAGSCLI $LDFLAGS -o conftest"
fi
......
}
向
cc_cmd
内添加
-Werror=implicit-function-declaration
。
cc_cmd="$CC conftest.c -Werror=implicit-function-declaration $(cc_cflags
生産的頭檔案和.a庫檔案在./android/armeabi-v7a對應目錄下,內建到Android 項目中去
2 AS內建
指定頭檔案和.a庫檔案路徑
include_directories(src/main/cpp/include)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/src/main/cpp/libs/${ANDROID_ABI}")
target_link_libraries(
native-lib
rtmp
x264
log)
IDR
一段h264視訊由N組GOP(group of picture)組成,GOP指的就是畫面組,一個GOP是一組連續的畫面 。之前的學習中我們知道I幀能夠獨立解碼,而P、B需要參考其他幀。
屬于I幀的子集,有一種特殊的I幀,被稱之為IDR幀,IDR幀的作用為即時重新整理。
上面的這張圖檔描述的是2組GOP。其他I幀與IDR幀的差別就在于:重新整理。當解碼器解碼幀5的時候,可以跨過幀4參考到幀3,普通I幀不會導緻解碼器的解碼資訊資料重新整理。而IDR幀則會重新整了解碼需要的SPS、PPS資料,是以幀8不可能跨幀7參考解碼。
H.264資料
往RTMP包中填充的是H.264資料,但不是直接将x264編碼出來的資料填充進去。
一段包含了N個圖像的H.264裸資料,每個NAL之間由:
00 00 00 01 或者 00 00 01
進行分割。在分割符之後的第一個位元組,就是表示這個nal的類型。
0x67:sps 0x68:pps 0x65:IDR
即為上面的
NAL_SLICE_IDR 0x65& 0x1f = 5
NAL_SPS 0x67 & 0x1f = 7,
NAL_PPS 0x68 & 0x1f= 8,
在将資料加入RTMPPacket的時候是需要去除分割符的。
碼率和幀率
碼率越高,視訊越清晰,資料越大,但是不是越大越好,有上限值
幀率:1s切換畫面次數