天天看點

NDK32_RTMPDump與x264的交叉編譯一 RTMP二 RTMPDump二 RTMP視訊資料三 內建x264

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源碼結構如下:

NDK32_RTMPDump與x264的交叉編譯一 RTMP二 RTMPDump二 RTMP視訊資料三 內建x264

​ 在根目錄下提供了一個

Makefile

與一些

.c

源檔案。這裡的源檔案将會編譯出一系列的可執行檔案。然後我們需要的并不是可執行檔案,真正的對RTMP的實作都在librtmp子目錄中。在這個子目錄中同樣包含了一個

Makefile

檔案。通過閱讀

Makefile

發現,它的源碼并不多:

OBJS=rtmp.o log.o amf.o hashswf.o parseurl.o

。是以我們不進行預編譯,即直接放入AS中借助

CMakeLists.txt

來進行編譯。這麼做可以讓我們友善的對庫本身進行調試或修改(實際上我們确實會稍微修改這個庫的源碼)。

2 AS內建

  • 在AS中複制librtmp置于:

    src/main/cpp/librtmp

    ,并為其編寫CMakeLists.txt
#預編譯宏
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DNO_CRYPTO"  )
#所有源檔案放入 rtmp_source 變量
file(GLOB rtmp_source *.c)
#編譯靜态庫
add_library(rtmp STATIC ${rtmp_source} )
           
  • app/CMakeLists.txt

    中導入這個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中的資料區構成。
NDK32_RTMPDump與x264的交叉編譯一 RTMP二 RTMPDump二 RTMP視訊資料三 內建x264

1 FLV tags 結構

flv分析器打開flv檔案

組裝rtmp包看的是資料區,從17開始

字段 位元組 描述
類型 1

0x08: 音頻

0x09: 視訊

0x12: 腳本(描述資訊)

資料大小 3 資料區的大小,不包括標頭。
時間戳 3 目前幀相對時間戳,機關是毫秒。相對于第一個TAG時戳。
時戳擴充 1 如果時戳大于0xFFFFFF,将會存在位元組。
流ID 3 總是0
資料區 n 音、視訊包
NDK32_RTMPDump與x264的交叉編譯一 RTMP二 RTMPDump二 RTMP視訊資料三 內建x264

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 類型不同,資料不同
NDK32_RTMPDump與x264的交叉編譯一 RTMP二 RTMPDump二 RTMP視訊資料三 內建x264

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内容
NDK32_RTMPDump與x264的交叉編譯一 RTMP二 RTMPDump二 RTMP視訊資料三 內建x264

其他

​ 在AVCVIDEOPACKET 中如果類型為1,則後續資料為:

類型 位元組 說明
包長 由AVC 序列頭中定義 後續長度
資料 n H.264資料

一般情況下,組裝的RTMPPacket(RTMPDump中的結構體)為:

NDK32_RTMPDump與x264的交叉編譯一 RTMP二 RTMPDump二 RTMP視訊資料三 內建x264
NDK32_RTMPDump與x264的交叉編譯一 RTMP二 RTMPDump二 RTMP視訊資料三 內建x264

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幀的作用為即時重新整理。

NDK32_RTMPDump與x264的交叉編譯一 RTMP二 RTMPDump二 RTMP視訊資料三 內建x264
上面的這張圖檔描述的是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的時候是需要去除分割符的。

NDK32_RTMPDump與x264的交叉編譯一 RTMP二 RTMPDump二 RTMP視訊資料三 內建x264

碼率和幀率

碼率越高,視訊越清晰,資料越大,但是不是越大越好,有上限值

幀率:1s切換畫面次數

繼續閱讀