专栏名称: GitChat技术杂谈
GitChat是新时代的学习工具。
目录
相关文章推荐
OSC开源社区  ·  谷歌安卓系统“假开源、真垄断”? ·  4 天前  
OSC开源社区  ·  开源模型未必更先进,但会更长久 ·  4 天前  
程序猿  ·  松下电器突然官宣解散!曾风靡全球 ·  2 天前  
程序猿  ·  本地部署 DeepSeek ... ·  3 天前  
程序员的那些事  ·  趣图:“微软穷疯了?上架的 ... ·  4 天前  
51好读  ›  专栏  ›  GitChat技术杂谈

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

GitChat技术杂谈  · 公众号  · 程序员  · 2017-11-24 07:41

正文

本文来自作者 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 应用中的定时任务







请到「今天看啥」查看全文