RTMP、X264与交叉编译
RTMP、X264与交叉编译
与HTTP(超文本传输协议)同样是一个基于TCP的Real Time Messaging Protocol(实时消息传输协议)。由Adobe Systems公司为Flash播放器和服务器之间音频、视频和数据传输开发的一种开放协议 。在国内被广泛的应用于直播领域。HTTP默认端口为80,RTMP则为1935。
本质上我们通过阅读Adobe的协议规范,通过与服务器建立TCP通信,根据协议格式生成与解析数据即可使用RTMP进行直播。当然我们也可以借助一些实现了RTMP协议的开源库来完成这一过程。
RTMPDump
RTMPDump 是一个用来处理RTMP流媒体的开源工具包。它能够单独使用进行RTMP的通信,也可以集成到FFmpeg中通过FFmpeg接口来使用RTMPDump。
RTMPDump源码下载:http://rtmpdump.mplayerhq.hu/download/rtmpdump-2.3.tgz
交叉编译
在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
来进行编译。这么做可以让我们方便的对库本身进行调试或修改(实际上我们确实会稍微修改这个库的源码)。
在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 ...)
如果在librtmp/CMakeLists.txt中没有写set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DNO_CRYPTO")
这句,编译会报错,因为librtmp\hashswf.c
中没有openssl
,所以使用这句屏蔽预编译
RTMP视频数据
RTMP视频流格式与FLV很相似,通过查看FLV的格式文档,就能够知道RTMP视频数据应该怎么拼接。
RTMP中的数据就是由FLV的TAG中的数据区构成。
FLV tags 结构
字段 | 字节 | 描述 |
---|---|---|
类型 | 1 | 0x08: 音频 0x09: 视频 0x12: 脚本(描述信息) |
数据大小 | 3 | 数据区的大小,不包括包头。 |
时间戳 | 3 | 当前帧相对时间戳,单位是毫秒。相对于第一个TAG时戳。 |
时戳扩展 | 1 | 如果时戳大于0xFFFFFF,将会存在字节。 |
流ID | 3 | 总是0 |
数据区 | n | 音、视频包 |
如上图,第一个字节0x09表示此段数据为视频,数据大小为0x00,0x00,0x2F即47,时间戳为0x00,0x00,0x00,时间戳扩展也为0x00。(第二行)流ID:0x00,0x00,0x00。接下来就是视频数据,通过此处的数据大小字段得知,数据长为47字节。则从0x17开始,一直到最后一行的0xC0,就是数据区域,而最后的0x00,0x00,0x00,0x3A 即58,表示的是这个数据块除最后4个字节的总大小。本处为视频数据,那么从0x17开始,数据内容则为下面的部分。
视频数据
字段 | 占位 | 描述 |
---|---|---|
帧类型 | 4 | 1: 关键帧 2: 普通帧 ...... |
编码ID | 4 | 7: 高级视频编码 AVC ...... |
视频数据 | n | AVC则需要下面的AVCVIDEOPACKET |
AVCVIDEOPACKET
字段 | 字节 | 描述 |
---|---|---|
类型 | 1 | 0:AVC 序列头(指导播放器如何解码) 1:其他单元(其他NALU) |
合成时间 | 3 | 对于AVC,全为0 |
数据 | n | 类型不同,数据不同 |
视频数据中 0x17 则表示了1: 关键帧与7: 高级视频编码 AVC,如果是普通帧,则此数据为0x27。而类型为:0x00表示这段数据为AVC序列头(avc sequence header)。最后三个字节为合成时间。而如果类型为AVC序列头接下来的数据就是下面的内容:
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内容 |
0x01为版本,后续数据按照上表记录,最后四字节上面说过:为这个数据块除最后4个字节的总大小。其中
SPS与PPS是编码器在编码H.264视频时,在关键帧前会编码出的关于这个关键帧与需要参考该关键帧的B/P
帧如何解码的内容,如:宽、高等信息。
其他
在AVCVIDEOPACKET 中如果类型为1,则后续数据为:
类型 | 字节 | 说明 |
---|---|---|
包长 | 由AVC 序列头中定义 | 后续长度 |
数据 | n | H.264数据 |
一般情况下,组装的RTMPPacket(RTMPDump中的结构体)为:
这里的sps与pps表示 AVC序列头
所以对于视频的数据封装,AVC序列头为:
int i = 0;
//AVC sequence header 与IDR一样
packet->m_body[i++] = 0x17;
//AVC sequence header 设置为0x00
packet->m_body[i++] = 0x00;
//CompositionTime
packet->m_body[i++] = 0x00;
packet->m_body[i++] = 0x00;
packet->m_body[i++] = 0x00;
//AVC sequence header
packet->m_body[i++] = 0x01; //configurationVersion 版本号 1
packet->m_body[i++] = sps[1]; //profile 如baseline、main、 high
packet->m_body[i++] = sps[2]; //profile_compatibility 兼容性
packet->m_body[i++] = sps[3]; //profile level
packet->m_body[i++] = 0xFF; // reserved(111111) + lengthSizeMinusOne(2位 nal 长度) 总是 0xff
//sps
packet->m_body[i++] = 0xE1; //reserved(111) + lengthSizeMinusOne(5位 sps 个数) 总是0xe1
//sps length 2字节
packet->m_body[i++] = (sps_len >> 8) & 0xff; //第0个字节
packet->m_body[i++] = sps_len & 0xff; //第1个字节
memcpy(&packet->m_body[i], sps,sps_len);
i += sps_len;
/*pps*/
packet->m_body[i++] = 0x01; //pps number
//pps length
packet->m_body[i++] = (pps_len >> 8) & 0xff;
packet->m_body[i++] = pps_len & 0xff;
memcpy(&packet->m_body[i],pps, pps_len);
而对于非AVC序列头,关键字与非关键字,只有第一个字节0x17与0x27的区别:
packet->m_body[0] = 0x27;
if (buf[0] == 0x65) { //关键帧
packet->m_body[0] = 0x17;
LOGI("发送关键帧 data");
}
packet->m_body[1] = 0x01;
packet->m_body[2] = 0x00;
packet->m_body[3] = 0x00;
packet->m_body[4] = 0x00;
//长度
packet->m_body[5] = (len >> 24) & 0xff;
packet->m_body[6] = (len >> 16) & 0xff;
packet->m_body[7] = (len >> 8) & 0xff;
packet->m_body[8] = (len) & 0xff;
//buf为编码出的一帧h.264数据
memcpy(&packet->m_body[9], buf, len);
H.264数据
H.264码流在网络中传输时实际是以NALU的形式进行传输的。NALU就是NAL UNIT,NAL单元。NAL全称Network Abstract Layer,即网络抽象层。在H.264/AVC视频编码标准中,整个系统框架被分为了两个层面:视频编码层面(VCL)和网络抽象层面(NAL)。其中,前者负责有效表示视频数据的内容,而后者则负责格式化数据并提供头信息,以保证数据适合各种信道和存储介质上的传输。我们平时的每帧数据就是一个NAL单元。
往RTMP包中填充的就是NAL数据,但不是直接将编码出来的数据填充进去
一段包含了N个图像的H.264裸数据,每个NAL之间由00 00 00 01
或者00 00 01
进行分割。在分割符之后的第一个字节,就是表示这个nal的类型。
- 0x67: sps
- 0x68: pps
- 0x65: IDR
在将数据加入RTMPPacket的时候是需要去除分割符的。
所以完整的封包代码为:
buf += 4; //跳过 0x00 0x00 0x00 0x01
len -= 4;
int body_size = len + 9;
RTMPPacket *packet = (RTMPPacket *) malloc(sizeof(RTMPPacket));
RTMPPacket_Alloc(packet, len + 9);
packet->m_body[0] = 0x27;
if (buf[0] == 0x65) { //关键帧
packet->m_body[0] = 0x17;
LOGI("发送关键帧 data");
}
packet->m_body[1] = 0x01;
packet->m_body[2] = 0x00;
packet->m_body[3] = 0x00;
packet->m_body[4] = 0x00;
//长度
packet->m_body[5] = (len >> 24) & 0xff;
packet->m_body[6] = (len >> 16) & 0xff;
packet->m_body[7] = (len >> 8) & 0xff;
packet->m_body[8] = (len) & 0xff;
//数据
memcpy(&packet->m_body[9], buf, len);
NALU
NALU就是NAL UNIT,nal单元。NAL全称Network Abstract Layer, 即网络抽象层,H.264在网络上传输的结构。
一帧图片经过 H.264 编码器之后,就被编码为一个或多个片(slice),而装载着这些片(slice)的载体,就是
NALU 了 。
音频数据
RTMP的音频数据相对视频比较简单,只需要根据是否为音频audio specific config(记录音频的格式)。如果为
audio specific config拼接0xAF,0x00,否则就只需要添加0xAF,0x01。
int body_size = len + 2;
RTMPPacket *packet = (RTMPPacket *) malloc(sizeof(RTMPPacket));
RTMPPacket_Alloc(packet, body_size);
packet->m_body[0] = 0xAF;
packet->m_body[1] = 0x01;
//audio specific config
if (type == 1) {
packet->m_body[1] = 0x00;
}
memcpy(&packet->m_body[2], buf, len);
0xAF的由来:
我们的编码为,10:AAC,3:44100采样率,1:采样长度,1:声道。按照位数表示数据就为:0xAF
而audio specific config只需要在发起推流时,发送音频数据之前发起一次即可。其数据为两字节:
第一个数据:AAC-LC值为2,占用5位,则数据为: 0001 0
第二个数据:采样率44100值为4,占用4位,则数据为:0100
第三个数据:声道,双声道为2,单声道为1,则数据为:0010(双声道),0001(单声道)
最后三位为0,这样组成的数据5+4+4+3=16位,两字节。
双声道:0x12 , 0x10
单声道:0x12 , 0x08
x264
x264是一个C语言编写的目前对H.264标准支持最完善的编解码库。与RTMPDump一样同样直接在Android中使用,也可以集成进入FFMpeg。
https://www.videolan.org/developers/x264.html
在linux下载编译:
wget ftp://ftp.videolan.org/pub/x264/snapshots/last_x264.tar.bz2
下载完成后将在当前目录下存在x264目录,目录中为x264的所有源码。接下来我们开始使用NDK对其进行交叉编译。
进入x264目录之后执行ls,可以看到x264源码根目录下的所有文件与文件夹。在x264目录下存在一个 configure文件。
并不是所有的库都已经存在
configure
文件,可能只存在configure.ac
和makefile.am
。这种情况需要借助 autoconf来生成configure。
如果存在configure,这时候我们第一反应一定是执行help指令:
如果存在configure,这时候我们第一反应一定是执行help指令:
./configure --help
#输出
Usage: ./configure [options]
Help:
-h, --help print this message
Standard options:
--prefix=PREFIX install architecture-independent files in PREFIX
[/usr/local]
--exec-prefix=EPREFIX install architecture-dependent files in EPREFIX
[PREFIX]
--bindir=DIR install binaries in DIR [EPREFIX/bin]
--libdir=DIR install libs in DIR [EPREFIX/lib]
--includedir=DIR install includes in DIR [PREFIX/include]
--extra-asflags=EASFLAGS add EASFLAGS to ASFLAGS
--extra-cflags=ECFLAGS add ECFLAGS to CFLAGS
--extra-ldflags=ELDFLAGS add ELDFLAGS to LDFLAGS
--extra-rcflags=ERCFLAGS add ERCFLAGS to RCFLAGS
Configuration options:
--disable-cli disable cli
--system-libx264 use system libx264 instead of internal
--enable-shared build shared library
--enable-static build static library
--disable-opencl disable OpenCL features
--disable-gpl disable GPL-only features
--disable-thread disable multithreaded encoding
--disable-win32thread disable win32threads (windows only)
--disable-interlaced disable interlaced encoding support
--bit-depth=BIT_DEPTH set output bit depth (8, 10, all) [all]
--chroma-format=FORMAT output chroma format (400, 420, 422, 444, all) [all]
Advanced options:
--disable-asm disable platform-specific assembly optimizations
--enable-lto enable link-time optimization
--enable-debug add -g
--enable-gprof add -pg
--enable-strip add -s
--enable-pic build position-independent code
Cross-compilation:
--host=HOST build programs to run on HOST
--cross-prefix=PREFIX use PREFIX for compilation tools
--sysroot=SYSROOT root of cross-build tree
External library support:
--disable-avs disable avisynth support
--disable-swscale disable swscale support
--disable-lavf disable libavformat support
--disable-ffms disable ffmpegsource support
--disable-gpac disable gpac support
--disable-lsmash disable lsmash support
x264的编译配置不算多,在编写我们的交叉编译脚本之前。首先来简单分析下应该如何编译这个库。
首先我们看到Standard options
中存在--prefix
(设置编译结果输出目录),一般规范的配置脚本都会存在这个参数,相信大家也能理解它是干嘛的(有些坑爹的可能没有)。同时还存在--extra-cflags
参数,这表示我们可以借助这个参数来对编译器(gcc/clang)设置选项,类似给javac
设置-classpath
等参数,就可以传递给它。
然后在Configuration options
中有一个--disable-cli
,cli是命令行工具的意思,所以这个配置是关闭编译命令
行工具,我们在Android中使用,是自己编写代码,而不是借助命令行工具,所以可以加上这个配置。(不加也没事,他编译出来就编译咯,我们不用就行了)。这个分组中可以看到主要是enable/disable一些功能,在这里我更喜欢使用静态库,所以我会开启--enable-static
。
第三组为Advanced options
,在这个组里的--enable-debug/gprof/strip
可以看到说明就是add -g/-pg/-s
,这其
实就是给编译器传递参数:gcc -g/ clang -g
。-enable-pic
这个配置其实也是给编译器附加-fPIC
参数,这里我们记住一点:如果编译Android使用的动态库使用PIC,编译Android可用的命令行工具使用PIE。
第四组为Cross-compilation
,非常爽快的提供了交叉编译的配置,但是实际上更多的是可能不会提供这些东西。这里的配置是干嘛的,可以去看看 CMake配置 这节课。
最后一组External library support
, 一些扩展内容的关闭,如果你不了解你是否会用到这里的内容,那就先别管
它。
我们这里是以目标平台版本17为例,如果是最新版本的,则因为Android的NDK在19版本以上去掉了gcc,使用clang替代,所以下面给处理最新版本的编译脚本:
#!/bin/bash
#NDK目录
NDK_ROOT=/home/zxj/android-ndk-r20
#编译后安装位置 pwd表示当前目录
PREFIX=`pwd`/android/armeabi-v7a
#目标平台版本,我们将兼容到android-21
API=21
#编译工具链目录
TOOLCHAIN=$NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64
#小技巧,创建一个AS的NDK工程,执行编译,
#然后在 app/.cxx/cmake/debug(release)/自己要编译的平台/目录下自己观察 build.ninja与rules.ninja
FLAGS="--target=armv7-none-linux-androideabi21 --gcc-toolchain=$TOOLCHAIN -g -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -fno-addrsin -march=armv7-a -mthumb -Wa,--noexecstack -Wformat -Werror=format-security -Oz -DNDEBUG -fPIC"
#虽然x264提供了交叉编译配置:--cross-prefix,如--corss-prefix=/NDK/arm-linux-androideabi-
#那么则会使用 /NDK/arm-linux-androideabi-gcc 来编译
#然而ndk19开始gcc已经被移除,由clang替代。
# 小常识:一般的库都会使用$CC 变量来保存编译器,我们自己设置CC变量的值为clang。
export CC=$TOOLCHAIN/bin/armv7a-linux-androideabi$API-clang
export CXX=$TOOLCHAIN/bin/armv7a-linux-androideabi$API-clang++
#--extra-cflag会附加到CFLAGS 变量之后,作为传递给编译器的参数,所以就算有些库没有--extra-cflags配置, 我们也可以自己创建变量CFLAGS传参。
./configure --prefix=$PREFIX \
--disable-cli \
--enable-static \
--enable-pic \
--host=arm-linux \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--sysroot=$TOOLCHAIN/sysroot \
--extra-cflags="$FLAGS"
make install
交叉编译
在Android中使用x264,首先需要预编译出x264的静/动态库
#!/bin/bash
PREFIX=./android/armeabi-v7a
NDK_ROOT=/home/zxj/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以上不会)。
注意:我当前使用的是x264的版本是
x264-shapshot-20180916-2245
,现在最新版本的好像不会出现这个问题了。
为什么有问题
当我们使用android-17版本执行上面的x264编译脚本后生成libx264.a,不会出现错误日志,然后我们在把这个生成的x264预编译库集成到ffmpag
#!/bin/bash
PREFIX=./android/armeabi-v7a
NDK_ROOT=/home/zxj/android-ndk-r17c
CPU=arm-linux-androideabi
TOOLCHAIN=$NDK_ROOT/toolchains/$CPU-4.9/prebuilt/linux-x86_64
FLAGS="-U_FILE_OFFSET_BITS -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"
PREFIX=./android/armeabi-v7a/live
X264=/home/zxj/x264-master/android/armeabi-v7a
# --enable-gpl 集成libx264的时候必须要加上gpl,不加的话会报错
# --extra-cflags 指定X264头文件查找路径
# --extra-ldflags库文件的查找路径
# \ 换行连接符
./configure --prefix=$PREFIX \
--enable-small \
--disable-programs \
--disable-avdevice \
--disable-postproc \
--disable-encoders \
--disable-muxers \
--disable-filters \
--enable-libx264 \
--enable-gpl \
--enable-cross-compile \
--cross-prefix=$TOOLCHAIN/bin/$CPU- \
--disable-shared \
--enable-static \
--sysroot=$NDK_ROOT/platforms/android-17/arch-arm \
--extra-cflags="$FLAGS -I$X264/include" \
--extra-cflags="-isysroot $NDK_ROOT/sysroot/" \
--extra-ldflags="-L$X264/lib" \
--arch=arm \
--target-os=android
# 清理一下
make clean
#执行makefile
make install
这里的ffmpag预编译脚本里的所有的目标版本都改为17,如果是17的版本则必须在FLAGS
开头需要加上-U_FILE_OFFSET_BITS
。
然后执行ffmpag的编译脚本,会发现打印日志里有报错信息
我们可以通过ffbuild/config.log
文件查看具体的错误日志,打开这个文件后,直接定位到最后,可以看到错误信息
可以看到没有log2f
和log2
函数,这是由于x264使用了这两个函数,却没有这两个函数的实现导致的,那么编译x264的时候没有出现问题,x264集成到ffmpag里就出现问题了。
这两个函数其实是在NDK里面是有实现的,打开NDK目录
这个libm.so
是一个数学的函数库,我们可以通过nm
命令来查看一下里面有那些函数
sdk\ndk-bundle\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\bin\arm-linux-androideabi-nm.exe libm.so
可以看到android-17
里的libm.so
库中是没有上面那两个函数的,在看看android-18
的
可以看到android-18
里面是有上面那两个函数的log2f
和log2
函数,所以就会出现android-17
会报错,而android-18
以上不回报错
具体的出错原因是在x264的common/osdep.h
里面定义了一个HAVE_LOG2F
的宏变量,如下图:如果HAVE_LOG2F
为1那么就不会定义log2f
和log2
这两个函数,那HAVE_LOG2F
在哪里定义的为1呢?
是在x264的config.h
头文件里
注意:在最新的x264版本中这个
HAVE_LOG2F
的宏变量定义的值是0,所以不会出现上面的问题。
这个config.h
头文件是在执行x264的configure
的这个配置脚本的时候生成的。那么它在执行这个配置脚本的时候会调用cc_check()
函数来检查有没有log2f
和log2
这两个函数的时候,它会执行一个命令cc_cmd="$CC conftest.c $CFLAGS $CHECK_CFLAGS $2 $LDFLAGSCLI $LDFLAGS -o conftest"
,这里有个conftest.c
是一个临时文件,我们可以输出一下这条命令及查看这个临时文件
再次执行一下x264的编译脚本build.sh
,看一下输出日志
conftest.c
文件的代码
#include <math.h>
int main(void){log2f(2); return 0;}
解决方法
我们需要修改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
。
都弄好后,开始执行./build.sh
脚本,会生成libx264.a
脚本执行完后,会在当前目录下生成android目录,将其打包:
导入项目中
将上面编译好的资源导入到项目中
编写app/CMakeLists.txt
最后不要忘记了在build.gradle中加入abiFilters "armeabi-v7a"
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 */
};
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的时候是需要去除分割符的。
RTMP、X264与交叉编译的更多相关文章
- FFmpeg和X264的交叉编译环境
在下载好了FFmpeg和X264的源码包之后,在Linux下进行安装的基本流程就是切换到其源码的根目录,然后以此执行以下命令.基本上所有的开源源码包的默认编译安装都是这三步. ./configure ...
- Android NDK/JIN 从入门到精通
1.1 JNI(Java Native Interface) 提供一种Java字节码调用C/C++的解决方案,JNI描述的是一种技术 1.2 NDK(Native Development Kit) A ...
- Android NDK 直播推流与引流
本篇介绍一下直播技术中推流与引流的简单实现. 1.流媒体服务器测试 首先利用快直播 app (其他支持 RTMP 推流与引流的 app 亦可)和 ffplay.exe 对流媒体服务器进行测试. 快直播 ...
- 爱了,字节跳动大神最佳整理:582页Android NDK七大模块学习宝典,理论与实践
前言 时至今日,短视频App可谓是如日中天,一片兴兴向荣.随着短视频的兴起,音视频开发也越来越受到重视,而且薪资水涨船高,以一线城市为例,音视频工程开发的薪资比Android应用层开发高出40%. 但 ...
- CentOS-6.4 编译安装ffmpeg加x264以及rtmp
CentOS 6.4-64位下编译ffmpeg几个简单步骤: 1.编译前环境准备: 2.下载源码: 3.编译,安装: ----------------------------------------- ...
- ubuntu下使用脚本交叉编译windows下使用的ffmpeg + X264
这里主要是补充一些遇到的问题和解决方法. 2013-06 下旬 由于项目需要,重新编译ffmpeg+264+其他. 这里使用的环境Ubuntu 13.04,脚本依然是cross_compile_ffm ...
- 交叉编译x264和ffmpeg
1.x264 ./configure --host=arm-hisiv300-linux CC=arm-hisiv300-linux-gcc --enable-pic --prefix=/usr/lo ...
- vmware虚拟机下ubuntu 13.04使用zeranoe脚本交叉编译ffmpeg
2013-07-01今天是建党节,习总书记指出,党的建设要以“照镜子.正衣冠.洗洗澡.治治病”为总要求.希望我们的党越来越纯洁,为人民谋福利.言归正传,每次项目中需要编译相应的ffmpeg,都很费时费 ...
- Android流媒体开发之路二:NDK开发Android端RTMP直播推流程序
NDK开发Android端RTMP直播推流程序 经过一番折腾,成功把RTMP直播推流代码,通过NDK交叉编译的方式,移植到了Android下,从而实现了Android端采集摄像头和麦克缝数据,然后进行 ...
- 开源代码Window下搭建rtmp流媒体服务器
合肥程序员群:49313181. 合肥实名程序员群:128131462 (不愿透露姓名和信息者勿加入) Q Q:408365330 E-Mail:egojit@qq.com 综合:有这样需求,将摄像头 ...
随机推荐
- 树莓派4B改造成云桌面客户端,连接DoraCloud免费版
Raspberry Pi(树莓派) 是为学习计算机编程教育而设计的只有信用卡大小的微型电脑,自问世以来受众多计算机发烧友和创客的追捧,曾经一"派"难求. DoraCloud是一款多 ...
- 音频处理实用AI工具
最近在做音频处理相关的工作,主要有以下几个好用的工具. 1. 语音转文字--whisper 这是一款由OpenAI开发的语音转文字工具,项目地址位于:openai/whisper. 这个工具是用来生成 ...
- navicat破解(15以前的版本)
navicat破解各种不成功,很耽误事.所以,再次整理一个相对省事有效的办法.内容如下: 一:下载此激活工具 二:按下图生成激活码 1. 2. 点击手动激活 3. 将请求码按图粘贴,按图点击操作便可激 ...
- 小知识:MAC上添加小米喷墨打印机
最近新购一个小米喷墨打印机,价格不贵,可彩打资料,也能打印照片,非常提升家庭幸福感的一件物品: 如果使用手机打印,下载米家打印就非常方便了. 但是有时候需要电脑打印,使用自己电脑添加打印机时遇到一些小 ...
- 《ASP.NET Core 微服务实战》-- 读书笔记(第8章)
第 8 章 服务发现 面对大量服务,为了简化配置和管理工作,我们需要了解"服务发现"概念 回顾云原生特性 配置外置 将 URL 和登录凭证移到配置文件和 C# 代码之外,放到环境变 ...
- HBase-通过外部表将Hive数据写入到HBase
a) 准备测试数据 这里准备的csv文件data_test.csv,内容没用''包裹,逗号作为列分隔符 171301,燕青,男,27,发展部 171207,武松,男,39,开发部 171307,李逵, ...
- Python-pymysql查询MySQL的表
一.安装pymysql py -m pip install pymysql; 二.创建表并插入数据 CREATE TABLE `course` ( `course_id` varchar(10) DE ...
- react 快速接入 sentry,性能监控与错误上报踩坑日记
壹 ❀ 引 本文是我入职第一个月所写,在主导基建组的这段时间也难免会与错误监控和性能监控打交道,因为公司主要考虑接入sentry,所以对于接入sentry的基建任务也提了一些需求,主要分为: 支持查看 ...
- 从零开始的react入门教程(四),了解常用的条件渲染、列表渲染与独一无二的key
壹 ❀ 引 在从零开始的react入门教程(三),了解react事件与使用注意项一文中,我们了解了react中事件命名规则,绑定事件时对于this的处理,以及事件中可使用的e对象.那么这篇文章中我们来 ...
- 从零开始手写 redis(三)内存数据重启后如何不丢失?
前言 我们在 从零手写 cache 框架(一)实现固定大小的缓存 中已经初步实现了我们的 cache. 我们在 从零手写 cache 框架(一)实现过期特性 中实现了 key 的过期特性. 本节,让我 ...