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

MySql 查询相关sql语句

-- 注释

-- 创建数据库
create database python_test_1 charset=utf8;

-- 查看数据库
show databases;

-- 使用数据库
use python_test_1;

-- 显示使用的数据库是哪个
select database();

-- student表
create table students(
id int unsigned primary key auto_increment not null,
name varchar(20) default '',
age tinyint unsigned default 0,
height decimal(5, 2),
gender enum('男', '女', '中性', '保密') default '保密',
cls_id int unsigned default 0,
is_delete bit default 0
);
-- bit 相当于 bit(1), 还可以bit(2)

-- 查看数据库创建语句
show create table students;

-- classes表
create table classes(
id int unsigned auto_increment primary key not null,
name varchar(30) not null
);

-- 查看数据表
show tables;

-- 向students表中插入数据
insert into students values
(0, '小明', 18, 180.00, 2, 1, 0),
(0, '小月月', 18, 180.00, 2, 2, 1),
(0, '彭于晏', 29, 185.00, 1, 1, 0),
(0, '刘德华', 59, 175.00, 1, 2, 1),
(0, '黄蓉', 38, 160.00, 2, 1, 0),
(0, '凤姐', 28, 150.00, 4, 2, 1),
(0, '王祖贤', 18, 172.00, 2, 1, 1),
(0, '周杰伦', 36, null, 1, 1, 0),
(0, '程坤', 27, 181.00, 1, 2, 0),
(0, '刘亦菲', 25, 166.00, 2, 2, 0),
(0, '金星', 33, 162.00, 3, 3, 1),
(0, '静香', 12, 180.00, 2, 4, 0),
(0, '郭靖', 12, 170.00, 1, 4, 0),
(0, '周杰', 34, 176.00, 2, 5, 0);

-- 向classes表中插入数据
insert into classes values(0, 'python_01期'), (0, 'python_02期');


-- 查询
-- 查询所有字段
-- select * from 表名;
select * from students;
select * from classes;

-- 查询指定字段
-- select 列, 列 ... from 表名;
select age, name from students;

-- 使用 as 给字段起别名
select name as 姓名, age as 年龄 from students;

-- select 表名.字段 ... from 表名;
select students.name, students.age from students;

-- 可以通过 as 给表起别名
select s.name, s.age from students as s;
-- 不可以 select students.name, s.age from students as s;

-- 消除重复行
-- distinct 字段
select distinct gender from students;


-- 条件查询
-- 比较运算符
-- select ... from 表名 where ...
-- > 查询大于18岁的信息
select * from students where age > 18;

-- < 查询小于18岁的信息
select * from students where age < 18;

-- >= <=
select * from students where age >= 18;

-- =
select * from students where age = 18;

-- != 或者 <>
select * from students where age != 18;

-- 逻辑运算符
-- and
select * from students where age > 18 and age < 28;
select * from students where age > 18 and gender='女';
select * from students where age > 18 and gender=2;

-- or
select * from students where age > 18 or height >= 180;

-- not
-- 不在 18岁以上的女性
-- 错误 select * from students where not age > 18 and gender=2;
select * from students where not (age > 18 and gender=2);

-- 年龄不是小于等18 并且是女性
select * from students where (not age <= 18) and gender=2;

-- 模糊查询
-- like 性能低
-- % 替换零个,一个或多个
-- _ 替换一个
-- 查询名字中 以"小"开始的名字
select name from students where name like "小%";

-- 查询姓名中有 "小"的名字
select name from students where name like "%小%";

-- 查询有2个字的名字
select name from students where name like "__";

-- 查询至少2个字的名字
select name from students where name like "__%";

-- rlike 正则
-- 查询 以"周"开始的姓名
select name from students where name rlike "^周.*";

-- 查询 以"周"开始,"伦"结尾的姓名
select name from students where name rlike "^周.*伦$";

-- 范围查询

-- in (1, 3, 8) 表示在一个非连续的范围内
select name, age from students where age = 18 or age = 34 or age = 12;
select name, age from students where age in(18, 34, 12);

-- not in ()
select name, age from students where age not in(18, 34, 12);

-- between .. and ..
-- [18, 34]
select name, age from students where age between 18 and 34;

-- not between .. and ..
select name, age from students where age not between 18 and 34;
select name, age from students where not age between 18 and 34;
-- 语法错误 select name, age from students where age not (begin 18 and 34);

-- 判断空
-- is null
select * from students where height is null;

-- is not null
select * from students where height is not null;


-- 排序
-- order by 字段
-- asc 从小到大 即升序
-- desc 从大到小 即降序
select * from students where age between 18 and 34 and gender=1 order by age;
select * from students where (age between 18 and 34) and gender=1 order by age; -- 默认asc
select * from students where (age between 18 and 34) and gender=1 order by age asc;

-- 如果有相同的情况下,默认按主键排,从小到大排
select * from students where (age between 18 and 34) and gender=2 order by height desc, id desc;
select * from students where (age between 18 and 34) and gender=2 order by height desc, age asc, id desc;
select * from students order by age asc, height desc;

-- 聚合函数
-- count 总数
select * from students where gender=1;
select count(*) from students where gender=1;
select count(*) as 男性人数 from students where gender=1;

-- max 最大值
select age from students;
select max(age) from students;
select max(height) from students where gender=2;

-- min 最小值

-- sum 求和
select sum(age) from students;

-- avg 平均值
select avg(age) from students;

-- 平均年龄
select sum(age)/count(*) from students;

-- 四舍五入 round(123.00, 1) 保留1位小数
select round(sum(age)/count(*), 2) from students;


-- 分组
-- group by
-- 按照性别分组,查询所有性别
-- 先分组,再从组里取数据
select gender from students group by gender;
-- 错误 select name from students group by gender;
-- 错误 select * from students group by gender;

select gender, count(*) from students group by gender;
select gender, max(age) from students group by gender;
select gender, group_concat(name) from students group by gender; -- 各性别,都有谁
select gender, group_concat(name, age) from students group by gender; -- 各性别,都有谁
select gender, group_concat(name, '_', age) from students group by gender; -- 各性别,都有谁

-- 计算男性的人数
select gender, count(*) from students where gender=1 group by gender;
select gender, group_concat(name, '_', age) from students where gender=1 group by gender; -- 各性别,都有谁

-- having
-- 查询平均年龄超过30的性别,以及姓名 having avg(age) > 30
select gender, group_concat(name), avg(age) from students group by gender having avg(age) > 30;

-- where 对源表过滤, having 对组过滤

-- 分页

-- limit start, count
-- 限制查询出来的个数
select * from students where gender=1 limit 2;
select * from students where gender=1 limit 0, 2; -- 0=0*2=(1-1)*2
select * from students where gender=1 limit 2, 2; -- 2=1*2=(2-1)*2 => (n-1)*count

-- 不能用表达式,需要计算结果出来 select * from students limit 2*(6-1), 2;
-- limit 放在最后
select * from students order by gender limit 10, 2;
-- 错误 select * from students limit 10, 2 order by gender
select * from students where gender=2 order by height desc limit 0,2;

-- 连接查询
-- 内连接,取交集
-- inner join .. on

-- select .. from 表A inner join 表B
select * from students inner join classes;
-- 新表数据总数: m * n

-- 查询 有能过对应班级的学生以及班级信息
select * from students inner join classes on students.cls_id = classes.id;
-- 新表数据总数 <= m * n

-- 按照要求显示姓名、班级
select students.*, classes.name from students inner join classes on students.cls_id=classes.id;

select students.name, classes.name from students inner join classes on students.cls_id=classes.id;
select s.name, c.name from students as s inner join classes as c on s.cls_id=c.id;

--
select c.name, s.* from students as s inner join classes as c on s.cls_id=c.id order by c.name;
select c.name, s.* from students as s inner join classes as c on s.cls_id=c.id order by c.name, s.id;

-- 左连接
-- left join
select s.*, c.name from students as s left join classes as c on s.cls_id=c.id;

select s.*, c.name from students as s left join classes as c on s.cls_id=c.id having c.id is null;
select s.*, c.name from students as s left join classes as c on s.cls_id=c.id where c.id is null;

-- 右连接
-- right join可以用left join实现

-- 自关联
-- id name id name c_id
-- 1 北京市 1 朝阳区 1
-- 2 上海 2 浦东 2

-- |
-- v

-- id name p_id
-- 1 北京市 null
-- 2 上海 null
-- 3 朝阳区 1
-- 4 浦东 2
-- 5 三林镇 4

create table areas(
aid int primary key,
atitle varchar(20),
pid int
);

-- mysql -uroot -p
show databases;
use python_test_1;
-- source areas.sql;

-- 省的地级市
select * from areas as province inner join areas as city on city.pid=province.aid having province.atitle="上海";
select province.atitle, city.atitle from areas as province inner join areas as city on city.pid=province.aid having province.atitle="上海";

-- 子查询 (慢点)
-- 标量子查询

-- 查询最高的男生信息
select * from students where height = (select max(height) from students);

select * from areas where pid = (select aid from areas where atitle="上海");

MySql 相关sql语句

-- 链接数据库
mysql -uroot -p
mysql -uroot -pxxx

-- 退出数据库
exit、quit、ctrl+d

-- 查看所有数据库
show databases;
大小写不分

-- 显示时间
select now();

-- 显示数据库版本
select version();

-- 创建数据库
-- create database 数据库名 charset=utf8;
create database python4;
create database python4new charset=utf8;

-- 查看创建数据库的语句
-- show create database 数据库名
show create database python4;
show create database python4new;

-- 删除数据库
-- drop database 数据库名
drop database `python-04`;

-- 使用数据库
use 数据库名

-- 查看当前数据库
select database();

-- 查看当前数据库中所有表
show tables;

-- 创建表
-- create table 数据表名(字段 类型 约束[, 字段 类型 约束]);
-- auto_increment
-- not null
-- primary key
-- default
create table xxx(id int, name varchar(30));
create table yyy(
id int primary key not null auto_increment,
name varchar(30)
);

create table student(
id int unsigned not null auto_increment primary key,
name varchar(30),
age tinyint unsigned default 0,
high decimal(5, 2),
gender enum("男", "女", "中性", "保密") default "保密",
cls_id int unsigned
);

inset into student values(0, "laowang", 18, 190, "男", 0);
select * from student;

create table classes(
id int unsigned not null auto_increment primary key,
name varchar(30)
);

-- 查看表结构
-- desc 表名
desc xxx;

-- 修改表结构 添加字段
-- alter table 表名 add 列名 类型;
alter table students add birthday datetime;

-- 修改表-修改字段,不重命名
-- alter table 表名 modify 列名 类型及约束
alter table students modify birthday date;

-- 修改表-修改字段:重命名
-- alter table 表名 change 原名 新名 类型及约束
alter table students change birthday birth date default "1990-01-01";


-- 修改表-删除字段
-- alter table 表名 drop 列名
alter table students drop high;


-- 删除表
-- drop table 表名;
drop database 数据库;
drop table xxxx;

-- 查看创建表的语句
show create table students;


----------- 存储引擎 ----------
MyISAM
Innodb


---------------CURD---------------------
create update retrieve delete
增删改查



-- ----增加----
-- 全列插入
-- insert [into] 表名 values(...)
-- 主键字段 可以用0 null default 来占位
insert into classes values(0, "菜鸟班");
insert into classes values(null, "菜鸟班");
insert into classes values(default, "菜鸟班");


inset into student values(0, "laowang", 18, 190, "男", 0);
-- 枚举 的 下标从1开始
insert into student values(0, "laowang", 18, 190, 1, 0);


-- 部分插入
-- insert into 表名(列名, ...) values(值, ...)
insert into students (name, gender) values("小乔", 2);


-- 多行插入
insert into students values(default "name1", 20, "女", 1, "1990-01-01"), (default "name2", 20, "女", 1, "1990-01-01")
insert into students (name, gender) values("name1", 1), ()

-- ---修改----
-- update 表名 set 列名=值, ... [where 条件]
update students set gender = 2;
update students set gender = 2 where id=3;
update students set gender = 2, age=22 where id=3;


-- 查询
select * from students;
select * from students where name="xxxx";

-- 查询指定列
-- select 列名,... from 表名
select name, gender from students where id>0;

-- 可以使用as为列或表指定别名
-- select 字段[as 别名], 字段[as 别名] from 表名 where 条件
select name as 姓名, gender as 性别 from students;

-- 调整字段顺序
select gender as 性别, name as 姓名 from students;


-- ---删除---
-- 物理删除
-- delete from 表名 where 条件
delete from students;
delete from students where name="xxx";

-- 逻辑删除
-- 用一个字段来表示,这条信息是否已经不能再使用了
alert table students add is_delete bit default 0;
update students set is_delete=1 where id=6;


Ubuntu的中文乱码问题 [完美解决]

首先,安装中文支持包language-pack-zh-hans:

sudo apt-get install language-pack-zh-hans

然后,修改/etc/environment(在文件的末尾追加):

LANG=”zh_CN.UTF-8″
LANGUAGE=”zh_CN:zh:en_US:en”

再修改/var/lib/locales/supported.d/local(没有这个文件就新建,同样在末尾追加):

en_US.UTF-8 UTF-8
zh_CN.UTF-8 UTF-8
zh_CN.GBK GBK
zh_CN GB2312

最后,执行命令:

sudo locale-gen

二叉树中序遍历打印节点信息

public static void main(String[] args){
BinaryTree tree = create();
MidOrderPrint(tree);
}


static class BinaryTree{
String data;
BinaryTree left;
BinaryTree right;

public BinaryTree(String data) {
this.data = data;
}
}

/**
* ........... a
* ........../.....\
* ........b........c
* ....../....\
* ....d........e
* .....\ ...../
* ......f....g

* dfbgeac
*/
private static BinaryTree create(){
BinaryTree a = new BinaryTree("a");

BinaryTree b = new BinaryTree("b");
BinaryTree c = new BinaryTree("c");
a.left = b;
a.right = c;

BinaryTree d = new BinaryTree("d");
BinaryTree e = new BinaryTree("e");

b.left = d;
b.right = e;

BinaryTree f = new BinaryTree("f");
BinaryTree g = new BinaryTree("g");

d.right = f;

e.left = g;

return a;
}

static void MidOrderPrint(BinaryTree tree){
if(tree == null) return ;

Stack<BinaryTree> stack = new Stack<>();
BinaryTree cur = tree;

while(!stack.isEmpty() || cur != null){
if(cur != null){
stack.push(cur);
cur = cur.left;
}else{
cur = stack.pop();
System.out.print(cur.data);
cur = cur.right;
}
}
}

有100个已排序数组,先需要合并为一个排序数组如何实现?数组长度在10w ~40w间

public static void main(String[] args){
int[][] arrs = new int[][]{
{1, 3, 4},
{1, 2, 5},
{0, 7, 5, 8}
};

int[] result = merge(arrs);

for(int num : result){
System.out.print(num+" ");
}
}


private static int getAllCount(int[][] arrs){
int size = 0;
for(int[] item : arrs){
if(item == null) continue;
size += item.length;
}
return size;
}

private static int[] getArrayIdxsAndSet_0(int size){
int[] idxs = new int[size];
// 初始化为零
for(int i = 0; i < idxs.length; i++){
idxs[i] = 0;
}
return idxs;
}

public static int[] merge(int[][] arrs) {
if(arrs == null) return null;

int allCount = getAllCount(arrs);

int[] result = new int[allCount];
int[] arrayIdxs = getArrayIdxsAndSet_0(arrs.length);

for(int resultIdx = 0; resultIdx < allCount; resultIdx++){

int minValue = 0;
int minArrayIdx = 0;
boolean tempNotSet = true;

for(int i = 0; i < arrs.length; i++){
int currentIdx = arrayIdxs[i];

if(currentIdx < arrs[i].length){
if(tempNotSet){
tempNotSet = false;
minValue = arrs[i][currentIdx];
minArrayIdx = i;
}else if(minValue > arrs[i][currentIdx]) {
minValue = arrs[i][currentIdx];
minArrayIdx = i;
}
}
}

arrayIdxs[minArrayIdx]++;
result[resultIdx] = minValue;
}

return result;
}