本文来自作者
Owen Chan
在
GitChat
上分享「关于视频的编解码与传输技术,你想知道的都在这里」,
「
阅读原文
」查看交流实录
「
文末高能
」
编辑 | 泰龙
一、如何编译 FFmpag
准备工作
下载后文件在 Mac 中的存放路径如下:
ChendeMacBook-Pro: compileFF
ChendeMacBook−Pro:compileFFchenzongwen pwd
/Users/chenzongwen/compileFF //文件的存放路径 NDK 与FFmpeg 源码
如何编译 ffmpeg
首先进入 ffmpeg-3.0文件夹 ,在文件夹中增加 编译脚本 ffmpegConfig 文件如下图:
ffmpegConfig 文件内容如下:
#!/bin/bash
NDK=/Users/chenzongwen/compileFF/android-ndk-r11b
export PATH=$PATH:$NDK
SYSROOT=$NDK/platforms/android-19/arch-arm/
TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64
function build_one
{
bash ./configure \
--prefix=$PREFIX \
--enable-shared \
--disable-static \
--disable-doc \
--disable-ffserver \
--enable-cross-compile \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--target-os=linux \
--arch=arm \
--extra-libs=-lgcc \
--sysroot=$SYSROOT \
--enable-asm \
--enable-neon \
--extra-cflags="-O3 -fpic $ADDI_CFLAGS" \
--extra-ldflags="$ADDI_LDFLAGS" \
$ADDITIONAL_CONFIGURE_FLAG
}
CPU=arm
PREFIX=$(pwd)/android/$CPU
ADDI_CFLAGS="-marm -mfpu=neon"
build_one
~
~
然后在当前文件夹下执行如下命令:
-
chenzongwen. / ffmpegConfig
-
chenzongwen make -j4 //4 为 用4个 cup 进行编译
-
chenzongwen$ make install
命令执行完之后会在当前文件夹下生成一个 android 文件夹 android/arm 文件夹中的内容如下几个文件夹:
bin
include
lib
share
include 中存放的是头文件, lib 中存放的是 so 文件 整个编译过程结束 。
如何使用 FFmpeg
-
在跟 anroid-ndk-r11b ffmpeg-3.0 同级的目录下 创建 jni 文件夹 执行如下命令 :
mkdir jni
-
将之前编译的头文件(在 include 文件夹下)拷贝到 jni 文件夹下 并且在 jni 文件夹下创建 prbuilt 文件夹 并将之前生成的 so(在 lib 文件夹下)拷贝到 prebuilt 文件夹下, 拷贝完成后如下图:
ChendeMacBook-Pro:jni chenzongwen$ ls
libavcodec libavdevice libavfilter libavformat libavutil libswresample libswscale prebuild
ChendeMacBook-Pro:jni chenzongwen$ ls prebuild/
libavcodec-57.so libavfilter-6.so libavutil-55.so libswscale-4.so
libavdevice-57.so libavformat-57.so libswresample-2.so libswscale.so
-
调用 so 方法 在当前文件夹下添加两个文件 Android.mk 和 Application.mk 内容分别如下
Android.mk 内容
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := avcodec-56-prebuilt
LOCAL_SRC_FILES := prebuilt/libavcodec-57.so
include $(PREBUILT_SHARED_LIBRARY)
#include $(CLEAR_VARS)
#LOCAL_MODULE := avdevice-56-prebuilt
#LOCAL_SRC_FILES := prebuilt/libavdevice-57.so
#include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := avfilter-5-prebuilt
LOCAL_SRC_FILES := prebuilt/libavfilter-6.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := avformat-56-prebuilt
LOCAL_SRC_FILES := prebuilt/libavformat-57.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := avutil-54-prebuilt
LOCAL_SRC_FILES := prebuilt/libavutil-55.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := avswresample-1-prebuilt
LOCAL_SRC_FILES := prebuilt/libswresample-2.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := swscale-3-prebuilt
LOCAL_SRC_FILES := prebuilt/libswscale-4.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
Application.mk 的内容如下 :
ChendeMacBook-Pro:jni chenzongwen$ vim Application.mk
APP_ABI := armeabi
#APP_ABI := armeabi-v7a
APP_PLATFORM := android-10
编写调用文件 在当前文件夹下创建 **.c 文件 内容如下(文章结束会给出全部源码):
JNIEXPORT jint JNICALL Java_tan_h264_FFmpegNative_decode_1file
(JNIEnv *env, jobject obj, jstring filePath) { // jni 方法的定义
//todu
}
JNIEXPORT jint JNICALL Java_tan_h264_FFmpegNative_decodeFrame
(JNIEnv *env, jobject obj, jbyteArray in, jint inSize)
{
//todo
}
开始编译 在 jni 目录下执行:
ChendeMacBook-Pro:jni chenzongwen$ ../android-ndk-r11b/ndk-build
最后将 如下目录下的 so 拷贝到工程中就可以使用了。
ChendeMacBook-Pro:armeabi chenzongwen$ pwd
/Users/chenzongwen/compileFF/libs/armeabi
ChendeMacBook-Pro:armeabi chenzongwen$ ls
libavcodec-57.so libavutil-55.so libswscale-4.so
libavfilter-6.so libowenchan_Test.so
libavformat-57.so libswresample-2.so
ChendeMacBook-Pro:armeabi chenzongwen$
二、Android App 中如何调用 FFmpag so, jni 技术的讲解
java 层代码调用如下:
import android.graphics.Bitmap;
import java.nio.ByteBuffer;
public class FFmpegNative {
static {
System.loadLibrary("avutil-54");
System.loadLibrary("swresample-1");
System.loadLibrary("avcodec-56");
System.loadLibrary("avformat-56");
System.loadLibrary("swscale-3");
System.loadLibrary("avfilter-5");
System.loadLibrary("avdevice-56");
System.loadLibrary("ffmpeg_codec");
}
public native int decode_init();
public native int decode_file(String filePath);
public native int decodeFrame(ByteBuffer in, int inSzie);
public native int decodeFrame2(ByteBuffer in, int inSzie);
public native int copyFrameRGB(ByteBuffer out);
public native int copyFrameYUV420p(ByteBuffer out);
public native int copyFrame2(ByteBuffer outY, ByteBuffer outU, ByteBuffer outV);
}
三、H264 格式
H.264分为两层
(一) H264 分为两层
-
视频编码层 (VCL: Video Coding Layer): 进行视频编解码,包括运动补偿预 测,变换编码和熵编码等功能;
-
网络 取层 (NAL: Network Abstraction Layer): 用于采用适当的格式对VCL 视频数据进行封装打包;VCL需要打包成NAL,才能用于传输或存储.
(二)分层的目的
-
可以定义 VCL 视频压缩处理与 NAL 网络传输机制的接口,这样允许视频 编码层 VCL 的设计可以在不同的处理器平台进行移植,而与NAL层的数据封装格 式无关;
-
VCL 和 NAL 都被设计成工作于不同的传输环境,异构的网络环境并不需要 对 VCL 比特流进行重构和重编码。
(三)NALU 单元(NAL Unit)
H264 基本码流由一系列的 NALU 组成,组成结构如下
-
NALU: Coded H.264 data is stored or transmitted as a series of packets known as Network Abstraction LayerUnits. (NALU单元)
-
RBSP : A NALU contains a Raw Byte Sequence Payload, a sequence of bytes containing syntax elements.(原始数据字节流)
-
SODB:String Of Data Bits (原始数据比特流, 长度不一定是8的倍数,需要补齐)
Start code
一共有两种起始码:3字节的 0x000001 和4字节的 0x00000001;
如果 NALU 对应的 Slice 为一帧的开始,则用4字节表示,即 0x00000001;
否则用3字节 0x000001 表示,就是一个完整的帧被编为多个slice的时候,包含这些 slice 的 nalu 使用3字节起始码。
由于NAL的语法中没有给出长度信息,实际的传输、存储系统需要增加额外 的起始头实现各个 NAL 单元的定界。
先识别 H264 起始码 0x00000001;
接着读取NALU的header 字节,判断后 RBSP类型,相应的 六进制类 型定义如下:
0x67: SPS
0x68: PPS
0x65: IDR
0x61: non-IDR Slice 0x01: B Slice
0x06: SEI
0x09: AU Delimiter
从 读出的 个 H.264 视频帧以下 的形式存在: 0000000167… SPS
0000000168... PPS
00 00 00 01 65 ... IDR Slice
剩下的几个部分是视频的传输压缩与解压,我做 Chat 交流的时候对着代码来分析。
代码下载地址:
https://github.com/chenzongwen/SimpleFFmpeg
界面如下:
近期热文
《
手把手教你如何向 Linux 内核提交代码
》
《
Java 实现 Web 应用中的定时任务
》