经过前面的几个步骤,我们基本把视频直播的所有环境配置完成,接下来我们进行真正的开发阶段。
1.把前面编译好的库以及文件复制到androidstudio中cpp文件夹下,如图
2.修改cmakelist文件中、
cmake_minimum_required(VERSION 3.4.1)
add_subdirectory(src/main/cpp/include/librtmp)
#添加动态库
file(GLOB my_source src/main/cpp/*.cpp src/main/cpp/*.c)
add_library(pusher SHARED ${my_source})
#设置变量 指向ffmpeg静态库目录
set(my_lib_path ${CMAKE_SOURCE_DIR}/src/main/cpp/lib/${ANDROID_ABI})
#添加静态库 并设置库路径
add_library(faac STATIC IMPORTED)
set_target_properties(faac PROPERTIES IMPORTED_LOCATION ${my_lib_path}/libfaac.a)
add_library(x264 STATIC IMPORTED)
set_target_properties(x264 PROPERTIES IMPORTED_LOCATION ${my_lib_path}/libx264.a)
#设置头文件查找目录
include_directories(src/main/cpp/include)
#编译链接
target_link_libraries(pusher log rtmp faac x264 )
在build.gradle中defaultConfig的添加如下代码:
sourceSets {
main {
jniLibs.srcDirs = ['lib']
}
}
3..直播流程图
视频编码格式
视频编码格式:
代表软编码器——openh264、x264
Profile : baseline main high
Level : 限制了码率上限
Resolution : 分辨率
Bitrate : 码率,与数据大小成正比
Frame Rate : 帧率,每秒多少帧图像,影响流畅度
Frame Interval : 关键帧间隔
4.音频编码格式
aac
Profile : lc main le
Sample Rate : 采样率
Channel : 声道数
Bitrate : 码率,与数据大小成正比
RTMP
Real Time Message Protocol(实时信息传输协议)
基于TCP的应用层协议
视频包
1、解码信息包(sps与pps)
2、数据包
音频包
1、解码信息包
2、数据包
5.视频包数据
6.音频包数据
7.RTMPDump
用于实现视频的录制,从rtmp的中转服务器接受到视频流,并把视频流保存成flv文件
交叉编译,过程如下:
8.在进行相机录制视频摄像时候,在手机屏幕上看到图像不是很正确,这就需要相机的旋转
特别再用camera时候注意:
mCamera.addCallbackBuffer(buffer);
不然后期手机内存会很快的增加,调用camera处理相机的代码:
private final static String TAG = "VideoPusher";
private boolean mPreviewRunning;
private Camera mCamera;
private SurfaceHolder mHolder;
private VideoParam mParam;
private byte[] buffer;
private byte[] raw;
private Activity mActivity;
private int screen;
private final static int SCREEN_PORTRAIT = 0;
private final static int SCREEN_LANDSCAPE_LEFT = 90;
private final static int SCREEN_LANDSCAPE_RIGHT = 270;
public VideoPusher(Activity activity, SurfaceHolder surfaceHolder, VideoParam param, PusherNative pusherNative) {
super(pusherNative);
mActivity = activity;
mParam = param;
mHolder = surfaceHolder;
surfaceHolder.addCallback(this);
}
@Override
public void startPusher() {
startPreview();
mPusherRuning = true;
}
@Override
public void stopPusher() {
mPusherRuning = false;
}
@Override
public void release() {
mPusherRuning = false;
mActivity = null;
stopPreview();
}
public void switchCamera() {
if (mParam.getCameraId() == CameraInfo.CAMERA_FACING_BACK) {
mParam.setCameraId(CameraInfo.CAMERA_FACING_FRONT);
} else {
mParam.setCameraId(CameraInfo.CAMERA_FACING_BACK);
}
stopPreview();
startPreview();
}
private void stopPreview() {
if (mPreviewRunning && mCamera != null) {
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
mCamera.release();
mCamera = null;
mPreviewRunning = false;
}
}
@SuppressWarnings("deprecation")
private void startPreview() {
if (mPreviewRunning) {
return;
}
try {
mCamera = Camera.open(mParam.getCameraId());
Camera.Parameters parameters = mCamera.getParameters();
parameters.setPreviewFormat(ImageFormat.NV21);
setPreviewSize(parameters);
setPreviewFpsRange(parameters);
setPreviewOrientation(parameters);
mCamera.setParameters(parameters);
int bitsPerPixel = ImageFormat.getBitsPerPixel(ImageFormat.NV21);
buffer = new byte[mParam.getWidth() * mParam.getHeight() * bitsPerPixel / 8];
raw = new byte[mParam.getWidth() * mParam.getHeight() * bitsPerPixel / 8];
mCamera.addCallbackBuffer(buffer);
System.out.println("bitsPerPixel:" + bitsPerPixel);
mCamera.setPreviewCallbackWithBuffer(this);
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
mPreviewRunning = true;
} catch (Exception ex) {
ex.printStackTrace();
if (null != mListener) {
mListener.onErrorPusher(-100);
}
}
}
private void setPreviewSize(Camera.Parameters parameters) {
List<Integer> supportedPreviewFormats = parameters.getSupportedPreviewFormats();
for (Integer integer : supportedPreviewFormats) {
System.out.println("支持:" + integer);
}
List<Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes();
Size size = supportedPreviewSizes.get(0);
Log.d(TAG, "支持 " + size.width + "x" + size.height);
int m = Math.abs(size.height * size.width - mParam.getHeight() * mParam.getWidth());
supportedPreviewSizes.remove(0);
Iterator<Size> iterator = supportedPreviewSizes.iterator();
while (iterator.hasNext()) {
Size next = iterator.next();
Log.d(TAG, "支持 " + next.width + "x" + next.height);
int n = Math.abs(next.height * next.width - mParam.getHeight() * mParam.getWidth());
if (n < m) {
m = n;
size = next;
}
}
mParam.setHeight(size.height);
mParam.setWidth(size.width);
parameters.setPreviewSize(mParam.getWidth(), mParam.getHeight());
Log.d(TAG, "预览分辨率 width:" + size.width + " height:" + size.height);
}
private void setPreviewFpsRange(Camera.Parameters parameters) {
int range[] = new int[2];
parameters.getPreviewFpsRange(range);
Log.d(TAG, "预览帧率 fps:" + range[0] + " - " + range[1]);
// int fps = mParam.getFps()*1000;
// if (fps >= range[0] && fps <= range[1]) {
// parameters.setPreviewFpsRange(fps, fps);
// }
}
private void setPreviewOrientation(Camera.Parameters parameters) {
CameraInfo info = new CameraInfo();
Camera.getCameraInfo(mParam.getCameraId(), info);
int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
screen = 0;
switch (rotation) {
case Surface.ROTATION_0:
screen = SCREEN_PORTRAIT;
mNative.setVideoOptions(mParam.getHeight(), mParam.getWidth(), mParam.getBitrate(), mParam.getFps());
break;
case Surface.ROTATION_90: // 横屏 左边是头部(home键在右边)
screen = SCREEN_LANDSCAPE_LEFT;
mNative.setVideoOptions(mParam.getWidth(), mParam.getHeight(), mParam.getBitrate(), mParam.getFps());
break;
case Surface.ROTATION_180:
screen = 180;
break;
case Surface.ROTATION_270:// 横屏 头部在右边
screen = SCREEN_LANDSCAPE_RIGHT;
mNative.setVideoOptions(mParam.getWidth(), mParam.getHeight(), mParam.getBitrate(), mParam.getFps());
break;
}
int result;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation + screen) % 360;
result = (360 - result) % 360; // compensate the mirror
} else { // back-facing
result = (info.orientation - screen + 360) % 360;
}
mCamera.setDisplayOrientation(result);
// // 如果是竖屏 设置预览旋转90度,并且由于回调帧数据也需要旋转所以宽高需要交换
// if (mContext.getResources().getConfiguration().orientation ==
// Configuration.ORIENTATION_PORTRAIT) {
// mNative.setVideoOptions(mParam.getHeight(), mParam.getWidth(),
// mParam.getBitrate(), mParam.getFps());
// parameters.set("orientation", "portrait");
// mCamera.setDisplayOrientation(90);
// } else {
// mNative.setVideoOptions(mParam.getWidth(), mParam.getHeight(),
// mParam.getBitrate(), mParam.getFps());
// parameters.set("orientation", "landscape");
// mCamera.setDisplayOrientation(0);
// }
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
mHolder = holder;
stopPreview();
startPreview();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
stopPreview();
}
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
if (mPusherRuning) {
switch (screen) {
case SCREEN_PORTRAIT:
portraitData2Raw(data);
break;
case SCREEN_LANDSCAPE_LEFT:
raw = data;
break;
case SCREEN_LANDSCAPE_RIGHT:
landscapeData2Raw(data);
break;
}
mNative.fireVideo(raw);
}
camera.addCallbackBuffer(buffer);
}
private void landscapeData2Raw(byte[] data) {
int width = mParam.getWidth(), height = mParam.getHeight();
int y_len = width * height;
int k = 0;
// y数据倒叙插入raw中
for (int i = y_len - 1; i > -1; i--) {
raw[k] = data[i];
k++;
}
// System.arraycopy(data, y_len, raw, y_len, uv_len);
// v1 u1 v2 u2
// v3 u3 v4 u4
// 需要转换为:
// v4 u4 v3 u3
// v2 u2 v1 u1
int maxpos = data.length - 1;
int uv_len = y_len >> 2; // 4:1:1
for (int i = 0; i < uv_len; i++) {
int pos = i << 1;
raw[y_len + i * 2] = data[maxpos - pos - 1];
raw[y_len + i * 2 + 1] = data[maxpos - pos];
}
}
private void portraitData2Raw(byte[] data) {
// if (mContext.getResources().getConfiguration().orientation !=
// Configuration.ORIENTATION_PORTRAIT) {
// raw = data;
// return;
// }
int width = mParam.getWidth(), height = mParam.getHeight();
int y_len = width * height;
int uvHeight = height >> 1; // uv数据高为y数据高的一半
int k = 0;
if (mParam.getCameraId() == CameraInfo.CAMERA_FACING_BACK) {
for (int j = 0; j < width; j++) {
for (int i = height - 1; i >= 0; i--) {
raw[k++] = data[width * i + j];
}
}
for (int j = 0; j < width; j += 2) {
for (int i = uvHeight - 1; i >= 0; i--) {
raw[k++] = data[y_len + width * i + j];
raw[k++] = data[y_len + width * i + j + 1];
}
}
} else {
for (int i = 0; i < width; i++) {
int nPos = width - 1;
for (int j = 0; j < height; j++) {
raw[k] = data[nPos - i];
k++;
nPos += width;
}
}
for (int i = 0; i < width; i += 2) {
int nPos = y_len + width - 1;
for (int j = 0; j < uvHeight; j++) {
raw[k] = data[nPos - i - 1];
raw[k + 1] = data[nPos - i];
k += 2;
nPos += width;
}
}
}
}
在cpp中编写具体的逻辑代码
我已经在github编写好了源码,需要的自行下载
livevideo