视频的「编解码」与「传输」的那些事儿

本文介绍FFmpeg在Android上的编译过程及H264格式的解析方法,涵盖FFmpeg的编译配置、JNI调用示例与H264的分层结构。

本文来自作者 Owen Chan 在 GitChat 上分享「关于视频的编解码与传输技术,你想知道的都在这里」,阅读原文」查看交流实录

文末高能

编辑 | 泰龙

一、如何编译 FFmpag

准备工作
  • 下载 FFmpeg 源码 :https://www.ffmpeg.org

  • 下载 NDK  :http://developer.android.com/ndk/downloads/index.html

下载后文件在 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 ~                                                                                                                                                             ~        

然后在当前文件夹下执行如下命令:

  1. chenzongwen. / ffmpegConfig

  2. chenzongwen make -j4  //4 为 用4个 cup 进行编译

  3. chenzongwen$ make install

命令执行完之后会在当前文件夹下生成一个 android 文件夹 android/arm 文件夹中的内容如下几个文件夹:

bin include lib share

include 中存放的是头文件, lib 中存放的是 so 文件 整个编译过程结束 。

如何使用 FFmpeg
  1. 在跟 anroid-ndk-r11b ffmpeg-3.0 同级的目录下 创建 jni 文件夹 执行如下命令 :mkdir  jni

  2. 将之前编译的头文件(在 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
  3. 调用 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 分为两层

  1. 视频编码层 (VCL: Video Coding Layer): 进行视频编解码,包括运动补偿预 测,变换编码和熵编码等功能;

  2. 网络 取层 (NAL: Network Abstraction Layer): 用于采用适当的格式对VCL 视频数据进行封装打包;VCL需要打包成NAL,才能用于传输或存储.

(二)分层的目的

  1. 可以定义 VCL 视频压缩处理与 NAL 网络传输机制的接口,这样允许视频 编码层 VCL 的设计可以在不同的处理器平台进行移植,而与NAL层的数据封装格 式无关;

  2. 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 应用中的定时任务

沉迷前端,无法自拔的人,如何规划职业生涯?

TensorFlow 计算与智能基础

突破技术发展瓶颈、成功转型的重要因素

Selenium 爬取评论数据,就是这么简单!


你和普通的程序员最大区别

在于

「阅读原文」看交流实录,你想知道的都在这里

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值