NDK 音视频的直播推流与流媒体播放
2022-06-12 本文已影响0人
carlwu_186
Java层的native方法和C/C++层的函数建立对应关系有两种方式:
- 静态注册
Java 层的 native 方法与 native 层的方法在名称上具有一一对应的关系 - 动态注册
使用动态注册时,我们需要准备好需要自己想要对应的 native 方法,然后构造 JNINativeMethod 数组,JNINativeMethod 是一种结构体,源码如下:
typedef struct {
// Java层native方法名称
const char* name;
// 方法签名
const char* signature;
// native层方法指针
void* fnPtr;
} JNINativeMethod;
动态注册的步骤如下:
- 通过 vm( Java 虚拟机)参数获取 JNIEnv 变量
- 通过 FindClass 方法找到对应的 Java 类
- 通过 RegisterNatives 方法,传入 JNINativeMethod 数组,注册 native 函数
视频流播放总结
- 使用ffmpeg引擎进行直播流的decode
- 使用OpenSLES进行音频的播放
- ffmpeg打开直播流地址后得到音、视频的解码器,分别实例化AudioChannel和VideoChannel
- 使用av_read_frame方法读取AVPacket,根据AVPacket的类型放入AudioChannel或VideoChannel的队列中缓存
- 音频的播放:从AudioChannel的AVPacket队列中循环读取AVPacket,使用AVCodecContext解码出AVFrame,读出AVFrame的内容使用OpenSLES进行播放音频。
- 视频的播放:从VideoChannel的AVPacket队列中循环读取AVPacket,使用AVCodecContext解码出AVFrame,AVFrame再放入一个队列中缓存。
循环读取AVFrame的缓存队列得到AVFrame,SDK层的Surface在native_window_jni中可以得到ANativeWindow对象,将AVFrame的数据拷贝到ANativeWindow对象中即可。 - 音视频如何同步:可以获取AVFrame的播放时间基点和延迟时长。当播放时间基点==0时,休眠延迟时长后渲染图像。当播放时间基点!=0时,需要比较音频和视频当前的时间基点误差,误差较大时如果视频比音频快就需要让视频播放额外延迟这个时间误差,如果视频比音频慢比较明显(>=0.1s)就要丢弃当前的AVFrame,并且将AVPacket缓存队列一直删除到最近的KeyFrame位置。
直播推流总结
服务端可以用:nginx-rtmp-win32-1.2.1 实现。
播放器填入推流地址就可以实现直播观看。
app核心流程介绍:
↓↓↓↓↓↓↓↓↓↓↓↓
摄像头采集的视频数据为NV21格式。
往RTMP包中填充的是H.264数据。
x264是一个C语言编写的目前对H.264标准支持最完善的编解码库。
而x264编码的输入数据却为I420格式。
因此,当我们采集到摄像头数据之后需要将NV21转为I420。
但不是直接将x264编码出来的数据填充进RTMP包中。
因此视频数据的数据流过程为:
摄像头采集回调(NV21) -> 数据旋转(I420) -> x264编码(H.264) -> 去除分隔符(可以填充进RTMP包)
===========================
麦克风采集的音频数据为PCM格式。
RTMP推流需要的是aac的裸数据。
可以使用FAAC编码PCM数据为AAC格式。
推送音频第一个包包含了接下来数据的格式的音频序列包。
因此音频数据的数据流过程为:
麦克风采集回调(PCM) -> FAAC转码(AAC) -> 添加固定包头(可以填充进RTMP包)
涉及到的lib:x264、RTMPDump 、faac。