Android播放器-顶点坐标和材质坐标

顶点坐标信息

                     ^
|
4 | 3
(1, -1, 0) <----------------- (1, 1, 0)
| | |
| | |
---------------------|------------------------->
| | |
| | |
V | V
(-1, -1, 0) <-----——----------- (1, -1, 0)
2 | 1
|

上三角形:3->4->2->3
下三角形:1->2->3->1
共向量:2->3

float *vertexData = new float[12]{
1f, -1f, 0f,
-1f, -1f, 0f,
1f, 1f, 0f,
-1f, 1f, 0f
}

材质坐标信息

         ^
|
4 | 3
(0, 1) ----------- (1, 1)
| |
| |
| |
-------(0, 0) ----------- (1, 0)------>
2 | 1
|

glTexParameteri

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

GL_TEXTURE_2D: 操作2D纹理
GL_TEXTURE_MIN_FILTER: 缩小过滤
GL_TEXTURE_MAG_FILTER: 放大过滤

GL_LINEAR: 线性过滤,使用距离当前渲染像素中心最近的4个纹理加权平均值

Android播放器-着色器语言GLSL

着色器语言又叫Shader,有两个模块一个是顶点着色器,一个是片元着色器

  • 顶点着色器是针对每个顶点执行一次,用于确定顶点的位置;
  • 片元着色器是针对每个片元(可以理解为每个像素)执行一次,用于确定每个片元(像素)的颜色;
  • GLSL的基本语法与C基本相同
  • 它完美的支持向量和矩阵操作
  • GLSL提供了大量的内置函数来提供丰富的扩展功能
  • 它是通过限定符操作来管理输入输出类型的

ffmpeg -i 720.mp4 -pix_fmt yuv420p -s 424×240 out.yuv

Android播放器-EGL

EGL是OpenGL与窗口系统对应的适配层

https://www.khronos.org/registry/EGL/sdk/docs/man/

---RenderAPI(OpenGL ES)---
-----------EGL------------
---Native Window System---
Display与原生窗口链接
EGLDisplay eglGetDisplay
EGLBoolean eglInitialize

Surface配置和创建surface(窗口和屏幕上的渲染区域)
EGLBoolean eglChooseConfig
EGLSurface eglCreateWindowSurface

Context创建渲染环境(Context上下文)
渲染环境指OpengGL ES的所有项目运行需要的数据结构。如顶点、片段着色器、顶点数据矩阵
eglCreateContext
eglMakeCurrent

Android播放器-OpenSLES使用流程

  1. 创建并设置SL引擎
  2. 创建并设置混音器
  3. 创建并设置播放器(有数据队列),设置回调并写入缓存队列

enqueue()->Buffer Queue->slBufferQueueCallback

初始化引擎

SLObjectltf engineObject = NULL; // 相当于引擎上下文
SLEngineltf engineEngine = NULL; // 引擎对象接口

slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);

(*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); // 阻塞方法,也可以用其他的回调方法
(*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);

具体函数参数

// 创建对象
SL_API SLresult SLAPIENTRY slCreateEngine(
SLObjectltf *pEngine,
SLuint32 numOptions,
const SLEngineOption *pEngineOptions, // 选择项目 默认参数
SLuint32 numInterfaces,
const SLInterfaceID *pInterfaceIds, // 支持的接口
const SLboolean *pInterceRequired // 接口标识数值,是否支持
)

// 实例化
SLresult (*Realize)(
SLObjectltf self,
SLboolean async
)
对象已实现状态(false阻塞)

// 获取接口
SLresult (*GetInterface)(
SLObjectltf self,
const SLInterfaceID id,
void *pInterface
)

创建输出设备

SLEngineltf engineEngine = NULL;
SLObjectltf outputMixObject = NULL;

(*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, 0, 0);
// 实例化
(*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE); // 阻塞

// 输出对象
SLDataLocator_OutputMix outputMix = {
SL_DATALOCATOR_OUTPUT_MIX,
outputMixObject
};

SLDataSink audioSnk = {&outputMix, NULL}

配置PCM格式信息

// 缓存队列配置信息
SLDataLocator_AndroidSimpleBufferQueue android_queue = {
SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
2
};
// 播放的音频的格式信息
SLDataFormat_PCM pcm = {
SL_DATAFORMAT_PCM, // 播放pcm格式的数据
2, // 2个声道
SL_SAMPLINGRATE_44_1, // 44100hz的频率
SL_PCMSAMPLEFORMAT_FIXED_16, // 位数 16位
SL_PCMSAMPLEFORMAT_FIXED_16, // 和位数一致就行
SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_PRONT_RIGHT, // 立体声(前左前右)
SL_BYTEORDER_LITTLEENDIAN // 字节序 小端模式
}

SLDataSource slDataSource = {
&android_queue,
&pcm
};

初始化播放器

const SLInterfaceID ids[1] = {SL_IID_BUFFERQUEUE};
const SLboolean req[1] = {SL_BOOLEAN_TRUE};

// 创建
(*engineEngine)->CreateAudioPlayer(
engineEngine,
&pcmPlayerObject,
&slDataSource,
&audioSnk,
1,
ids,
req);

// 初始化
(*pcmPlayerObject)->Realize(pcmPlayerObject, SL_BOOLEAN_FALSE);

// 得到接口后调用 获取Player接口
(*pcmPlayerObject)->GetInterface(
pcmPlayerObject,
SL_IID_PLAY,
&pcmPlayerPlay);

播放和缓存队列

注册回调缓冲区

// 获取缓冲区队列接口
(*pcmPlayerObject)->GetInterface(
pcmPlayerObject,
SL_IID_BUFFERQUEUE,
&pcmBufferQueue);

// 在缓冲区读完之后,回调函数就会调用
(*pcmBufferQueue)->RegisterCallback(
pcmBufferQueue,
pcmBufferCallBack,
NULL);
// 回调函数被调用后,需要立刻往缓冲队列里加数据,否则会出现断音

(*pcmPlayerPlay)->SetPlayState(pcmPlayerPlay, SL_PLAYSTATE_PLAYING);

// 发一个空数据""={\0} (0是不会发声的),激活回调
(*pcmBufferQueue)->Enqueue(pcmBufferQueue, "", 1);


Android播放器-对解码后数据处理

视频像素和尺寸转换

用ffmpeg处理的话,大概要20ms,性能消耗很大,但其代码处理简单

// 多线程中最好用这个
sws_getContext

// 单线程可以使用这个
// 如果传入的context的格式和新的格式一样,它就会直接返回当前context
struct SwsContext *sws_getCachedContext(
struct SwsContext *context,
int srcW,
int srcH,
enum AVPixelFormat srcFormat,
int dstW, // 尺寸的转换
int dstH,
enum AVPixelFormat dstFormat, // 像素格式的转换
int flags,
SwsFilter *srcFilter, // 过滤器
SwsFilter *dstFilter,
const double *params); // 缩放算法,相关参数

flags,提供了一系列的算法,差值的算法,矩阵的算法,使得缩放图片
#define SWS_FAST_BILNEAR 1
#define SWS_BILINEAR 2
#define SWS_BICUBIC 4
#define SWS_X 8
#define SWS_POINT 0x10
#define SWS_AREA 0x20
#define SWS_BICUBLIN 0x40

int sws_scale(
struct SwsContext *c,
const uint8_t *const srcSlice[], // 指针数组,二维数组 YUV(数组大小3) RGBA(数组大小1)
const int srcStride[], // 一行的大小
int srcSliceY, // 深度
int srcSliceH,
uint8_t *const dst[],
const int dstStride[]);

void sws_freeContext(struct SwsContext *swsContext);

Android播放器-解码

  • 解封装
  • 软硬解码
  • 像素格式转换
  • 重采样
  • pts/dts
  • 同步策略

avcodec_find_decoder

avcodec_register_all(); // 注册所有的解码器
AVCodec *avcodec_find_decoder(enum AVCodecID id)
AVCodec 只是存放了解码器格式的配置信息
AVCodec *avcodec_find_decoder_by_name(const char *name)
avcodec_find_decoder_by_name("h264_mediacodec") // 用Android里面自带的解码模块,ARM CPU是一个集合,里面自带一个解码模块,其实就是调用了Android里面的java接口

AVCodecContext

AVCodecContext *avcodec_alloc_context3(const AVCodec *codec) // 可以不传解码器,只是创建的解码器上下文里面会没有解码器
void avcodec_free_context(AVCodecContext **avctx)

// 打开
int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options); // 如果AVCodecContext里面已经有解码器,就不需要传codec,options:参考/libavcodec/options_table.h

int thread_count; // 多线程
time_base // 时间的基数

avcodec_parameters_to_context

将codec的参数拷贝到context
avcodec_parameters_to_context(codec, p);

AVFrame

AVFrame *frame = av_frame_alloc();
void av_frame_free(AVFrame **frame);// 引用计数减一,并释放本身
int av_frame_ref(AVFrame *dst, const AVFrame *src); // 引用计数加一
AVFrame *av_frame_clone(const AVFrame *src); // 复制,并引用计数加一
void av_frame_unref(AVFrame *frame); // 直接引用计数减一

成员:
uint8_t *data[AV_NUM_DATA_POINTERS] // 可能是交叉模式或平面模式
int linesize[AV_NUM_DATA_POINTERS] // 视频表示一行数据大小,音频表示一个通道数据大小
int width;
int height;
int nb_samples; // 音频,表示单通道样本数量,一般1024
int64_t pts;
int64_t pkt_dts;
int sample_rate;
uint64_t channel_layout;
int channels;
int format; // AVPixelFormat AVSampleFormat

linesize

平面模式(因为有对齐的原因):

0 Y大小
1 U大小
2 V大小
3 NULL

0 左声道(planner)大小
1 右声道大小
2 NULL

0 RGB RGB RGB大小
1 NULL

avcodec_send_packet

int avcodec_send_packet(
AVCodecContext *avctx,
const AVPacket *avpkt);
// 调用完这个函数后,可以安全的释放avpkt,因为内部已经缓存了一个AVPacket空间

avcodec_receive_frame

int avcodec_receive_frame(
AVCodecContext *avctx,
AVFrame *frame);

可能前面几帧解不出来,获取的这一帧可能是之前的,怎么取出结尾中的几帧,也可能取到多个

Android播放器-解封装

  • 解封装
  • 软硬解码
  • 像素格式转换
  • 重采样
  • pts/dts
  • 同步策略

解封装

av_register_all()  // 注册所有的格式(解封装和加封装格式)
avformat_network_init() // 想支持rtsp等网络协议,初始化网络模块

avformat_open_input(...) // 打开文件并解析
avformat_find_stream_info(...) // 查找文件格式和索引

av_find_best_stream(...) // 查找对应的流

AVFormatContext 解加封装上下文,有AVStream数组
AVStream 音视频流信息
AVPacket 解封装完的数据包(视频的话,h264的间隔符00 00 01去掉了)

av_read_frame(...)

int avformat_open_input函数

确保av_register_all avformat_network_init已调用
AVFormatContext **ps 可以传入指向空指针的指针,也可以传入指向自己申请空间地址的指针
const char *url 可以是本地的文件,也可以是网络地址(http,rtsp)
AVInputFormat *fmt 一般不用,直接传null,指定封装格式(如果是频繁打开,最好指定)
AVDictionary **options 一般可以给null,可参考libavformat\options_table.h

AVFormatContext

AVIOContext *pb;  // io context 如果要自定义一些读写格式就要用到这个
char filename[1024]; // 文件名,断开重连需要用到

unsigned int nb_streams; // 数组大小
AVStream **streams; // 一般0是视频,1是音频,但不能保证

int64_t duration; // 以AV_TIME_BASE为单位,获取的总长度,不一定能获取到
int64_t bit_rate; // 比特率

void avformat_close_input(AVFormatContext **s);

关闭完之后,内部帮我们已经置零了

avformat_find_stream_info

获取视频流的信息
在一些文件中,可能没有文件信息(如flv格式、h264格式的),这时我们前面调用的avformat_open_input打开文件后,就没有参数,获取不到文件信息
我们就可以用avformat_find_stream_info,来探测一下流信息,

815,281 261,869.51 554,018.67

AVStream

AVCodecContext *codec; // 过时了
AVRational time_base; // 时间基数 一般为一百万 其实就是分数,这是了减少精度的损失,表示一秒钟有多少个,就是多少分之一秒
int64_t duration; // 转换成毫秒:
// duration*((double)time_base.num/(double)time_base.den)*1000
AVRational avg_frame_rate: // 帧率
AVCodecParameter *codecpar; // 音视频参数

enum AVMediaType codec_type; // 编码类型,表示是音频还是视频
enum AVCodecID codec_id; // 编码格式
unit32 codec_tag; // 用四个字节表示各种编码器
int format; // 指的是像素格式或音频的采样格式
int width;
int height;
uint64_t channel_layout; // 2.1声道,还是3声道
int channels; // 声道数
int sample_rate;
int frame_size; // 一帧音频的大小

写一段代码就测试一下

av_find_best_stream

获取音视频的索引
int av_find_best_stream(
AVFormatContext *ic,
enum AVMediaType type,
int wanted_stream_nb, // 默认设置-1
int related_stream, // 相关的流信息,一个文件里可能有多个节目
AVCodec **decoder_ret, // 解码器
int flags // 暂未使用的参数
)

av_read_frame

参数:
AVFormatContext *s
AVPacket *pkt // 不能传NULL,输出参数,内部会分配帧空间
返回值:
return 0 if OK
< 0 on error or end of file

AVPacket

AVBufferRef *buf; // 可能多个AVPacket共用 buf
int64_t pts; // pts * (num/den)
int64_t dts; // 根据dts顺序发给解码器
uint8_t *data;
int size;

相关函数:
AVPacket *av_packet_alloc(void) // 创建并初始化
AVPacket *av_packet_clone(const AVPacket *src) // 创建并引用计数
int av_packet_ref(AVPacket *dst, const AVPacket *src); // 增加引用
av_packet_unref(AVPacket *pkt) // 减引用
void av_packet_free(AVPacket **pkt) // 清空对象并减引用计数
void av_init_packet(AVPacket *pkt) // 设置默认值
int av_packet_from_data(AVPacket *pkt, uint8_t *data, int size) // 自己构造AVPacket包
int av_copy_packet(AVPacket *dst, const AVPacket *src); // 不建议使用

av_seek_frame

int av_seek_frame(
AVFormatContext *s,
int stream_index, // 流索引 默认-1,即视频
int64_t timestamp, // 和pts一致,AVStream.time_base
int flags
)
stream_index:
音频不存在IBP帧,而视频有IBP帧,所以最好用视频移动到关键帧位置

flags:
#define AVSEEK_FLAG_BACKWARD 1 ///< seek backward 往后找,就找过去的时间点
#define AVSEEK_FLAG_BYTE 2 ///< seeking based on position in bytes 根据文件大小位置来跳,这时时间戳就是文件位置
#define AVSEEK_FLAG_ANY 4 ///< seek to any frame, even non-keyframes 针对frame来说,就跳到这个frame,可能会花屏
#define AVSEEK_FLAG_FRAME 8 ///< seeking based on frame number 找关键帧

Android播放器-Jni

JNI原始数据类型

JNI 原始数据类型
Java Type Native Type Description
boolean jboolean unsigned 8 bits
byte jbyte signed 8 bits
char jchar unsigned 16 bits
short jshort signed 16 bits
int jint signed 32 bits
long jlong signed 64 bits
float jfloat 32 bits
double jdouble 64 bits
void void N/A

JNI引用类型

jobject  (all java objects)
|--jclass (java.lang.Class.objects)
|--jstring (java.lang.String.objects)
|--jarray (array)
| |--jobjectArray (object arrays)
| |--jbooleanArray (boolean arrays)
| |--jbyteArray (byte arrays)
| |--jcharArray (char arrays)
| |--jshortArray (short arrays)
| |--jintArray (int arrays)

Android播放器-创建支持ffmpeg的项目

  1. 引入头文件
  2. 引入so文件

将Ubuntu下编译的include文件拷贝到app目录下

在native-lib.cpp中引用头文件

将ffmpeg的库,拷贝到app\libs\armeabi-v7a目录下

将库文件通过cmake文件配置,引入到项目中

将avcodec链接到native-lib中

编译错误:
error: '../../../../libs/arm64-v8a/libavcodec.so', needed by '../../../../build/intermediates/cmake/debug/obj/arm64-v8a/libnative-lib.so', missing and no known rule to make it

这是因为Anroid默认编译多个版本的库
所以我们要过滤下版本

过滤库版本

还需要将库的路径

调用库中的函数

最终效果

Android播放器-支持ffmpeg的项目设置

  • 权限(版本)
  • jni库路径
  • cmake参数
  • cmake项目配置
app\src\main\AndroidManifest.xml
<!-- 读写权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

app\build.gradle(设置版本)

/* android {defaultConfig { 下面 */
compileSdkVersion 26
defaultConfig{
application "ff.aplay"
minSdkVersion 14
targetSdkVersion 12

添加jni用到的库所在的路径

/* android {defaultConfig { 下面 */
sourceSets{
main{
jniLibs.srcDirs = ['libs] // 相对路径下的libs,就是配置文件当前路径
}

设置cmake和NDK的参数

/* android { 下面 */
externalNativeBuild {
cmake {
cppFlags "-std=c++11"
}
ndk{
abiFilters "armeabi-v7a" // 过滤,支持armeabi-v7a
}
}

cmake配置文件路径

/* android { 下面 */
externalNativeBuild {
cmake{
path "CMakeLists.txt"
}
}

app\CMakeLists.txt

include_directories(include)  // 头文件路径,相对CMakeLists.txt文件的路径

设置ffmpeg的库的路径

# 设置ffmpeg的库的路径
-DANDROID_ABI=armeabi-v7a
set(FF_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libs/${ANDROID_ABI})

cmake添加动态库add_library

add_library(<name> [STATIC|SHARED|MODULE]
[EXCLUDE_FROM_ALL]
source1 [source2 ...]
)

add_library(native-lib SHARED
src/main/cpp/native-lib.cpp
)

# 定义avformat库名称,声明为需要导入的(imported)
add_library(avformat SHARED
IMPORTED
)

# 设置属性
set_target_properties(target1 target2 ...
PROPERTIES prop1 value1 prop2 value2 ...
)

# 设置avformat的导入的路径属性
set_target_properties(avformat
PROPERTIES
IMPORTED_LOCATION ${FF_DIR}/libavformat.so
)

Cmake链接动态库

# 将所有的动态库都链接到native-lib一个库中
target_link_libraries(# Specifies the target library.
native-lib # 链接的目标库
# 下面都是被依赖库
android # android系统 依赖库
avformat avcodec swscale avutil
${log-lib} # android log库
)

Android播放器-Ubuntu设置

安装完系统后

sudo passwd // 配置root的密码
su - // 切换为root用户
apt-get update // 更新数据源
apt-get install openssh-server // 安装ssh服务端
ifconfig // 查看主机IP地址
apt-get install vim // 安装一个编辑工具

ssh parallels@10.211.55.4 // 不能用root登录Ubuntu
wget https://dl.google.com/android/repository/android-ndk-r14b-linux-x86_64.zip
apt-get install unzip
unzip android-ndk-r14b-linux-x86_64.zip

// samba共享
apt-get install samba
vim /etc/samba/smb.conf
/*******文件尾********/
[root]
comment=root
path=/root
browseable=yes
readonly=on
/***************/
smbpasswd -a root // 添加root用户
/etc/init.d/samba restart // 重启samba

git clone https://git.ffmpeg.org/ffmpeg.git ffmpeg
wget http://ffmpeg.org/releases/ffmpeg-3.4.tar.bz2
tar -xvf ffmpeg-3.4.tar.bz2
apt-get install make

// 编译ffmpeg
./configure
// ./configure help
make -j16
make install
------configure------
--prefix 输出目录
--enable 开启模块,硬解码,neon
--disable 禁止模块,禁止ffmpeg工具
交叉编译参数 cross-prefix arch target cpu sysroot extra-cflags
cd ffmpeg-3.4

// NDK路径
export NDK=/home/parallels/ff/android-ndk-r14b
// 指定平台android5.0 架构下的so库和头文件
export PLATFORM=$NDK/platforms/android-21/arch-arm
// 工具链 交叉编译工具 谁来用(linux-x86),用在那(arm)
export TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64

// cpu输出类型
export CPU=armv7-a
// 输出路径
export PREFIX=./android/$CPU

// 用env看下环境变量
// 生成makefile文件
./configure \
--prefix=$PREFIX \
--target-os=android \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--arch=arm \
--cpu=armv7-a \
--sysroot=$PLATFORM \
--extra-cflags="-I$PLATFORM/usr/include -fPIC -DANDROID -mfpu=neon -mfloat-abi=softfp " \
--cc=$TOOLCHAIN/bin/arm-linux-androideabi-gcc \
--nm=$TOOLCHAIN/bin/arm-linux-androideabi-nm \
--enable-shared \
--enable-runtime-cpudetect \
--enable-gpl \
--enable-small \
--enable-cross-compile \
--enable-asm \
--enable-neon \
--enable-jni \
--enable-mediacodec \
--enable-decoder=h264_mediacodec \
--enable-hwaccel=h264_mediacodec \
--disable-debug \
--disable-static \
--disable-doc \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-ffserver \
--disable-postproc \
--disable-avdevice \
--disable-symver \
--disable-stripping

make -j16
vim zzh_android_build.sh
-----------
#!/bin/bash

echo "进入编译ffmpeg脚本"
-----------

chmod +x zzh_android_build.sh
./zzh_android_build.sh
make clean
./zzh_android_build.sh
bash -x zzh_android_build.sh // 看打印输出
软解码
开neon单线程 60帧 CPU占用一样
不开neon单线程 30帧 CPU占用一样
开neon 8线程 250帧
不开neon 8线程 140帧

硬解码 120帧

Android播放器-应用二进制界面

应用二进制界面 ABI

不同Android手机使用不同的CPU,因此支持不同的指令集
CPU与指令集的每种组合都有其自己的应用ABI

ABI包含以下信息

CPU指令集
内存字节顺序
可执行二进制文件的格式
解析的各种约定,对齐限制,堆栈使用和调用函数

支持的ABI

armebi
armebi-v7a
arm64-v8a
x86
x86_64
mips
mips64

NEON

NEON提供一组标量/矢量指令和寄出去(与FPU共享) armeabi(默认)
注:FPU Float Point Unit,浮点运算单元
armeabi-v7a(NEON)
-mfpu=vfp 浮点协处理器
-mfpu=neon
-march=armv7-a

Android播放器-音视频基础知识

MPEG-4

是一套用于音频、视频信息的压缩编码标准

MPEG-4 Part 14:MPEG-4文件格式
MPEG-4 Part 15:AVC文件格式
MPEG-4 Part 10:H264(AVC)

注:用来Mp4文件不等同于使用了MPEG-4整套标准

常用封装格式

AVI 压缩标准可任意选择
FLV ts 流媒体格式
ASF
MP4

常用编码格式

视频:
H264(AVC Part10)
wmv
XviD(Part 2)
mjpeg (相对应帧内编码)
音频:
aac(有损压缩)
MP3(有损压缩)
ape(无损压缩)
flac()

封装格式和编码格式

1、封装格式(格式头)
mp4(Part 14) box音视频信息 (编码和格式关键帧索引)
flv
mov
avi
2、视频编码帧
h264 mpeg-4-10
nal vcl sps pps I B P
解码出:YUV => 转换为RGB显示(转换开销很大)
软解码主要耗电严重,不过性能好兼容性好
3、音频编码帧
AAC
APE、FLAC无损压缩
PCM原始音频
解码为PCM FLT(float 一个浮点)=> 转为声卡支持的S16播放(short)

像素格式

BGRA RGBA ARGB32 RGB32 YUV420
10bit的颜色,但大多数显示器不支持,还是要转换为8bit的来显示
算法都是基于YUV,感光芯片和显示器都是基于RGB

R=Y+1.4075*(V-128)
G=Y-0.3455*(U-128)-0.7169*(v-128)
B=Y+1.779*(U-128)

3x3RGB 会补齐

Y 表示明亮度,也就是灰度值
而U和V表示的则是色度

P平面存储方式,先全是Y再全是UV

PCM音频参数

采样率 sample_rate 44100(CD)
通道channels(左右声道)
样本大小(格式)sample_size
format:FMT
AV_SAMPLE_FMT_S16
AV_SAMPLE_FMT_FLTP(float 32位)

样本类型planar

AV_SAMPLE_FMT_S16在内存的格式就为:
c1, c2, c1, c2, c1, c2....
AV_SAMPLE_FMT_S16P(平面格式)在内存的格式就为:
c1,c1,c1... c2,c2,c2...

MP4格式

ftyp moov mdat
ftyp:file type 表明文件类型
moov:metadata container 存放媒体信息的地方
mdat:media data container 具体的媒体数据
moov{
mvhd:movie header 文件总体信息,如时长,创建时间
trak:track or stream container存放视频/音频流的容器
}
trak{
tkhd:track header track的总体信息,如时长,宽高等
mdia:track media information container
}
mdia{
mdhd:media header 定义TimeScale,track需要通过TimeScale换算真实时间
hdlr:handler 表明本track类型,指明是video/audio/还是hint
minf:media information container 数据在子box中
}
minf{
stbl:sample table box 存放时间/偏移的映射关系表,数据在子box中
}
stbl{
stsd:sample descriptions
stts:(decoding) time-to-sample “时戳-sample序号”的映射表
stcs:sample-to-chunk sample和chunk的映射表,这里的算比较巧妙
stsz:sample size 每个sample的大小
stz2:sample size 另一个sample size的存储算法,更节省空间
stss:sync sample table 可随机访问的sample列表(关键帧列表)
stco:chunk offset 每个chunk的偏移,sample的偏移可根据其他box推算出来
co64:64-bit chunk offset
}

H264/AVC视频编码标准

网络抽象层面NAL 格式化数据并提供头信息
视频编码层面VCL 视频数据的内容

NAL单元

因此我们平时的每帧数据就是一个NAL单元(sps和pps除外)
在实际的h264数据帧中,往往帧前面带有00 00 00 01或00 00 01分隔符
一般来说编码器编出的首帧数据未pps与sps,接着为I帧

GOP

I B B P B B P B B P B B P B P
编码顺序 1 3 4 2 6 7 5 9 10 8 12 13 11 15 14
显示顺序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
DTS 解码时间 1 3 4 2 6 7 5 9 10 8 12 13 11 15 14
PTS 显示时间 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

NEON

协处理器NEON提供一组标量/矢量指令和寄存器(与FPU共享)

armeabi 默认 方式
armeabi-v7a (neon)
-mfpu=vfp 浮点协处理器
-mfpu=neon
-march=armv7-a

音视频优秀博客

  • 雷霄骅http://blog.csdn.net/leixiaohua1020/
    主要从事与广播电视有关的视音频技术的研究。包括视音频质量评价,视音频编解码,流媒体,媒资检索等。


  • 逆流的鱼yuiop: http://blog.csdn.net/hejjunlin/
    爱奇艺智能设备RD,关注Codec,多媒体,FFmpeg领域,OpenGL ES,希望结识更多同道中人。


  • lcyw: http://blog.csdn.net/machh/
    马成海,近十年一直从事网络视频监控系统的开发,从客户端开发,音视频编解码开发,到后台服务器的开发,以及视频监控系统的架构设计