SPS、PPS 的头部信息 增加了额外的16字节的长度 sps/pps 按照关键帧处理
frame type : 关键帧 / 非关键帧 (4 bit)
CodecID : 7表示AVC CodecID 和frametype组合成一个字节 (4 bit) 0x17 / 0x27
fixed : 0x00(AVCDecoderConfigurationRecord) 0x00 0x00 0x00 (4 byte)
sps + pps数据
configurationVersion (1 byte) 0x01 版本
AVCProfileIndication (1 byte) sps[1] Profile
profile_compatibility (1 byte) sps[2] 兼容性
AVCLevelIndication (1 byte) sps[3] Profile level
lengthSizeMinusOne (1 byte) 0xff 包长数据所使用的字节数
sps number (1 byte) 0xe1 sps个数
sps data length (2 byte) sps长度
sps data sps实际内容
pps number (1 byte) 0x01 pps的个数
pps data length (2 byte) pps长度
pps data pps内容
char *body = packet->m_body;
int i = 0;
//0x17关键帧 frame type (4 bit)
// CodecID (4 bit)
body[i++] = 0x17;
//fixed (4 byte)
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
//版本 Profile 兼容性 ProfileLevel
body[i++] = 0x01;
body[i++] = sps[1];
body[i++] = sps[2];
body[i++] = sps[3];
//包长数据所使用的字节数
body[i++] = 0xff;
//sps个数
body[i++] = 0xe1;
//sps长度 (2 byte)
body[i++] = (sps_len >> 8) & 0xff;
body[i++] = sps_len & 0xff;
//sps实际内容
memcpy(&body[i], sps, sps_len);
i += sps_len;
//pps的个数
body[i++] = 0x01;
//pps长度 (2 byte)
body[i++] = (pps_len >> 8) & 0xff;
body[i++] = pps_len & 0xff;
//pps实际内容
memcpy(&body[i], pps, pps_len);
H264 增加了额外的9字节的长度
frame type : 1关键帧、2非关键帧 (4 bit)
CodecID : 7表示AVC (4 bit) 0x17/0x27 和frametype组合成一个字节
fixed : 0x01(NALU) 0x00 0x00 0x00 (4 byte)
data length : 长度信息(4 byte)
data : h264裸数据
char *body = packet->m_body;
int i = 0;
//0x17关键帧 frame type (4 bit)
//0x27非关键帧 CodecID (4 bit)
if (keyFrame) {
body[i++] = 0x17;
} else {
body[i++] = 0x27;
}
//fixed (4 byte) NALU
body[i++] = 0x01;
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
//dataLength : 长度信息(4 byte)
body[i++] = (data_len >> 24) & 0xff;
body[i++] = (data_len >> 16) & 0xff;
body[i++] = (data_len >> 8) & 0xff;
body[i++] = data_len & 0xff;
//h264 裸数据
memcpy(&body[i], data, data_len);
LivePushActivity
package com.example.glivepush;
import android.os.Bundle;
import android.os.Environment;
import android.se.omapi.SEService;
import android.util.Log;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.example.glivepush.camera.GCameraView;
import com.example.glivepush.push.BasePushEncoder;
import com.example.glivepush.push.GConnectListener;
import com.example.glivepush.push.PushEncodec;
import com.example.glivepush.push.PushVideo;
import com.example.glivepush.util.DisplayUtil;
public class LivePushActivity extends AppCompatActivity {
private PushVideo pushVideo;
private GCameraView gCameraView;
private boolean start = false;
private PushEncodec pushEncodec;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_live_push);
pushVideo = new PushVideo();
gCameraView = findViewById(R.id.cameraView);
pushVideo.setgConnectListener(new GConnectListener() {
@Override
public void onConnecting() {
Log.d("godv", "链接服务器中");
}
@Override
public void onConnectSuccess() {
Log.d("godv", "链接服务器成功");
pushEncodec = new PushEncodec(LivePushActivity.this, gCameraView.getTextureId());
pushEncodec.initEncodec(
gCameraView.getEglContext(),
DisplayUtil.getScreenWidth(LivePushActivity.this),
DisplayUtil.getScreenHeight(LivePushActivity.this),
44100,
2
);
pushEncodec.startRecord();
/*************************************直播推流-video-start***********************************/
pushEncodec.setOnMediaInfoListener(new BasePushEncoder.OnMediaInfoListener() {
@Override
public void onMediaTime(int times) {
}
@Override
public void onSPSPPSInfo(byte[] sps, byte[] pps) {
pushVideo.pushSPSPPS(sps, pps);
}
@Override
public void videoInfo(byte[] data, boolean keyFrame) {
pushVideo.pushVideoData(data, keyFrame);
}
});
/*************************************直播推流-video-end***********************************/
}
@Override
public void onConnectFail(String msg) {
Log.d("godv", msg);
}
});
}
public void startPush(View view) {
start = !start;
if (start) {
pushVideo.initLivePush("rtmp://192.168.0.14/myapp/mystream");
} else {
if (pushEncodec != null) {
pushEncodec.stopRecord();
pushEncodec = null;
}
}
}
}
BasePushEncoder
package com.example.glivepush.push;
import android.content.Context;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.util.Log;
import android.view.Surface;
import com.example.glivepush.egl.EglHelper;
import com.example.glivepush.egl.GEGLSurfaceView;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import javax.microedition.khronos.egl.EGLContext;
public abstract class BasePushEncoder {
private Surface surface;
private EGLContext eglContext;
private int width;
private int height;
//视频的编码器
private MediaCodec videoEncodec;
private MediaFormat videoFormat;
private MediaCodec.BufferInfo videoBufferinfo;
//音频的编码器
private MediaCodec audioEncodec;
private MediaFormat audioFormat;
private MediaCodec.BufferInfo audioBufferinfo;
private long audioPts = 0;
private int sampleRate = 0;
//渲染视频的线程
private GEGLMediaThread geglMediaThread;
//编码视频的线程
private VideoEncodecThread videoEncodecThread;
//编码音频的线程
private AudioEncodecThread audioEncodecThread;
private GEGLSurfaceView.GGLRender gGLRender;
public final static int RENDERMODE_WHEN_DIRTY = 0;
public final static int RENDERMODE_CONTINUOUSLY = 1;
private int mRenderMode = RENDERMODE_CONTINUOUSLY;
private OnMediaInfoListener onMediaInfoListener;
public void setOnMediaInfoListener(OnMediaInfoListener onMediaInfoListener) {
this.onMediaInfoListener = onMediaInfoListener;
}
public BasePushEncoder(Context context) {
}
public void setRender(GEGLSurfaceView.GGLRender gGLRender) {
this.gGLRender = gGLRender;
}
public void setRenderMode(int mRenderMode) {
if (gGLRender == null) {
throw new RuntimeException("must set render before");
}
this.mRenderMode = mRenderMode;
}
//初始化方法
public void initEncodec(EGLContext eglContext, int width, int height, int sampleRate, int channelCount) {
this.width = width;
this.height = height;
this.eglContext = eglContext;
initMediaEncodc(width, height, sampleRate, channelCount);
}
//开始编码
public void startRecord() {
if (surface != null && eglContext != null) {
audioPts = 0;
geglMediaThread = new GEGLMediaThread(new WeakReference<BasePushEncoder>(this));
videoEncodecThread = new VideoEncodecThread(new WeakReference<BasePushEncoder>(this));
audioEncodecThread = new AudioEncodecThread(new WeakReference<BasePushEncoder>(this));
geglMediaThread.isCreate = true;
geglMediaThread.isChange = true;
geglMediaThread.start();
videoEncodecThread.start();
//audioEncodecThread.start();
}
}
//结束编码
public void stopRecord() {
if (geglMediaThread != null && videoEncodecThread != null && audioEncodecThread != null) {
videoEncodecThread.exit();
audioEncodecThread.exit();
geglMediaThread.onDestory();
videoEncodecThread = null;
geglMediaThread = null;
audioEncodecThread = null;
}
}
private void initMediaEncodc(int width, int height, int sampleRate, int channelCount) {
//参数二录制类型
initVideoEncodec(MediaFormat.MIMETYPE_VIDEO_AVC, width, height);
initAudioEncodec(MediaFormat.MIMETYPE_AUDIO_AAC, sampleRate, channelCount);
}
//初始化video的编码器
private void initVideoEncodec(String mimeType, int width, int height) {
try {
videoBufferinfo = new MediaCodec.BufferInfo();
videoFormat = MediaFormat.createVideoFormat(mimeType, width, height);
videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
//码率
videoFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height * 4);
//帧率
videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
//关键帧间隔
videoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
videoEncodec = MediaCodec.createEncoderByType(mimeType);
//录制没有Surface
//最后一个参数传的是编码
videoEncodec.configure(videoFormat, null, null,
MediaCodec.CONFIGURE_FLAG_ENCODE);
//得到Surface
surface = videoEncodec.createInputSurface();
} catch (IOException e) {
e.printStackTrace();
videoEncodec = null;
videoFormat = null;
videoBufferinfo = null;
}
}
//初始化音频编码器
private void initAudioEncodec(String mimeType, int simpleRate, int channelCount) {
try {
this.sampleRate = simpleRate;
audioBufferinfo = new MediaCodec.BufferInfo();
audioFormat = MediaFormat.createAudioFormat(mimeType, simpleRate, channelCount);
//设置比特率
audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, 96000);
//设置aac格式等级
audioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
//设置最大输入缓存
audioFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 4096);
//生成encodec
audioEncodec = MediaCodec.createEncoderByType(mimeType);
audioEncodec.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
} catch (IOException e) {
e.printStackTrace();
audioEncodec = null;
audioFormat = null;
audioBufferinfo = null;
}
}
//传递PCM的方法
public void putPCMDate(byte[] buffer, int size) {
if (audioEncodecThread != null && !audioEncodecThread.isExit && buffer != null && size > 0) {
int inputBufferindex = audioEncodec.dequeueInputBuffer(0);
if (inputBufferindex >= 0) {
ByteBuffer byteBuffer = audioEncodec.getInputBuffers()[inputBufferindex];
byteBuffer.clear();
byteBuffer.put(buffer);
long pts = getAudioPts(size, sampleRate);
audioEncodec.queueInputBuffer(inputBufferindex, 0, size, pts, 0);
}
}
}
private long getAudioPts(int size, int sampleRate) {
audioPts += (long) (1.0 * size / (sampleRate * 2 * 2) * 1000000.0);
return audioPts;
}
//渲染视频
static class GEGLMediaThread extends Thread {
private WeakReference<BasePushEncoder> encoder;
private EglHelper eglHelper;
private Object object;
private boolean isExit = false;
private boolean isCreate = false;
private boolean isChange = false;
private boolean isStart = false;
public GEGLMediaThread(WeakReference<BasePushEncoder> encoder) {
this.encoder = encoder;
}
@Override
public void run() {
super.run();
isExit = false;
isStart = false;
object = new Object();
eglHelper = new EglHelper();
eglHelper.initEgl(encoder.get().surface, encoder.get().eglContext);
while (true) {
if (isExit) {
release();
break;
}
//刷新模式
if (isStart) {
if (encoder.get().mRenderMode == RENDERMODE_WHEN_DIRTY) {
synchronized (object) {
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} else if (encoder.get().mRenderMode == RENDERMODE_CONTINUOUSLY) {
try {
Thread.sleep(1000 / 60);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
throw new RuntimeException("mRenderMode is wrong value");
}
}
onCreate();
onChange(encoder.get().width, encoder.get().height);
onDraw();
isStart = true;
}
}
public void release() {
if (eglHelper != null) {
eglHelper.destoryEgl();
eglHelper = null;
object = null;
encoder = null;
}
}
private void onCreate() {
if (isCreate && encoder.get().gGLRender != null) {
isCreate = false;
encoder.get().gGLRender.onSurfaceCreated();
}
}
private void onChange(int width, int height) {
if (isChange && encoder.get().gGLRender != null) {
isChange = false;
encoder.get().gGLRender.onSurfaceChanged(width, height);
}
}
private void onDraw() {
if (encoder.get().gGLRender != null && eglHelper != null) {
encoder.get().gGLRender.onDrawFrame();
if (!isStart) {
encoder.get().gGLRender.onDrawFrame();
}
eglHelper.swapBuffers();
}
}
private void requestRender() {
if (object != null) {
synchronized (object) {
object.notifyAll();
}
}
}
public void onDestory() {
isExit = true;
requestRender();
}
}
//编码视频
static class VideoEncodecThread extends Thread {
private WeakReference<BasePushEncoder> encoder;
private boolean isExit;
private MediaCodec videoEncodec;
private MediaFormat videoFormat;
private MediaCodec.BufferInfo videoBufferinfo;
//pts
private long pts;
//sps
private byte[] sps;
private byte[] pps;
/*************************************直播推流-video-start***********************************/
private boolean keyFrame = false;
/*************************************直播推流-video-end***********************************/
public VideoEncodecThread(WeakReference<BasePushEncoder> encoder) {
this.encoder = encoder;
videoEncodec = encoder.get().videoEncodec;
videoFormat = encoder.get().videoFormat;
videoBufferinfo = encoder.get().videoBufferinfo;
}
@Override
public void run() {
super.run();
pts = 0;
isExit = false;
videoEncodec.start();
while (true) {
if (isExit) {
videoEncodec.stop();
videoEncodec.release();
videoEncodec = null;
Log.d("godv", "录制完成");
break;
}
//视频编码开始
//输出队列索引
int outputBufferIndex = videoEncodec.dequeueOutputBuffer(videoBufferinfo, 0);
/*************************************直播推流-video-start***********************************/
keyFrame = false;
/*************************************直播推流-video-end***********************************/
if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
Log.d("godv", "INFO_OUTPUT_FORMAT_CHANGED");
ByteBuffer spsb = videoEncodec.getOutputFormat().getByteBuffer("csd-0");
sps = new byte[spsb.remaining()];
spsb.get(sps, 0, sps.length);
ByteBuffer ppsb = videoEncodec.getOutputFormat().getByteBuffer("csd-1");
pps = new byte[ppsb.remaining()];
ppsb.get(pps, 0, pps.length);
Log.d("godv", "sps : " + byteToHex(sps));
Log.d("godv", "pps : " + byteToHex(pps));
} else {
while (outputBufferIndex >= 0) {
ByteBuffer outputBuffer = videoEncodec.getOutputBuffers()[outputBufferIndex];
outputBuffer.position(videoBufferinfo.offset);
outputBuffer.limit(videoBufferinfo.offset + videoBufferinfo.size);
if (pts == 0) {
pts = videoBufferinfo.presentationTimeUs;
}
videoBufferinfo.presentationTimeUs = videoBufferinfo.presentationTimeUs - pts;
byte[] data = new byte[outputBuffer.remaining()];
outputBuffer.get(data, 0, data.length);
Log.d("godv", "data : " + byteToHex(data));
/*************************************直播推流-video-start***********************************/
//判断是否为关键帧
if (videoBufferinfo.flags == MediaCodec.BUFFER_FLAG_KEY_FRAME) {
keyFrame = true;
if (encoder.get().onMediaInfoListener != null) {
encoder.get().onMediaInfoListener.onSPSPPSInfo(sps, pps);
}
}
//返回数据
if (encoder.get().onMediaInfoListener != null) {
encoder.get().onMediaInfoListener.videoInfo(data, keyFrame);
encoder.get().onMediaInfoListener.onMediaTime(
(int) videoBufferinfo.presentationTimeUs / 1000000);
}
/*************************************直播推流-video-end***********************************/
videoEncodec.releaseOutputBuffer(outputBufferIndex, false);
outputBufferIndex = videoEncodec.dequeueOutputBuffer(videoBufferinfo, 0);
}
}
}
}
public void exit() {
isExit = true;
}
}
//编码音频
static class AudioEncodecThread extends Thread {
//外层类的引用
private WeakReference<BasePushEncoder> encoder;
//是否退出
private boolean isExit;
//编码
private MediaCodec audioEncodec;
private MediaCodec.BufferInfo bufferInfo;
//pts
private long pts;
public AudioEncodecThread(WeakReference<BasePushEncoder> encoder) {
this.encoder = encoder;
audioEncodec = encoder.get().audioEncodec;
bufferInfo = encoder.get().audioBufferinfo;
}
@Override
public void run() {
super.run();
//初始化
pts = 0;
isExit = false;
//编码器开始编码
audioEncodec.start();
while (true) {
if (isExit) {
//回收资源
audioEncodec.stop();
audioEncodec.release();
audioEncodec = null;
break;
}
int outputBufferIndex = audioEncodec.dequeueOutputBuffer(bufferInfo, 0);
//格式改变
if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
} else {
while (outputBufferIndex >= 0) {
ByteBuffer outputBuffer = audioEncodec.getOutputBuffers()[outputBufferIndex];
outputBuffer.position(bufferInfo.offset);
outputBuffer.limit(bufferInfo.offset + bufferInfo.size);
if (pts == 0) {
pts = bufferInfo.presentationTimeUs;
}
bufferInfo.presentationTimeUs = bufferInfo.presentationTimeUs - pts;
audioEncodec.releaseOutputBuffer(outputBufferIndex, false);
outputBufferIndex = audioEncodec.dequeueOutputBuffer(bufferInfo, 0);
}
}
}
}
public void exit() {
isExit = true;
}
}
public interface OnMediaInfoListener {
void onMediaTime(int times);
/*************************************直播推流-video-start***********************************/
void onSPSPPSInfo(byte[] sps, byte[] pps);
//参数2 是否是关键帧 0x17 0x27
void videoInfo(byte[] data, boolean keyFrame);
/*************************************直播推流-video-end*************************************/
}
//byte 转 16进制
public static String byteToHex(byte[] bytes) {
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i]);
if (hex.length() == 1) {
stringBuffer.append("0" + hex);
} else {
stringBuffer.append(hex);
}
if (i > 20) {
break;
}
}
return stringBuffer.toString();
}
}
PushVideo
package com.example.glivepush.push;
import android.text.TextUtils;
public class PushVideo {
static {
System.loadLibrary("gpush");
}
private GConnectListener gConnectListener;
public void setgConnectListener(GConnectListener gConnectListener) {
this.gConnectListener = gConnectListener;
}
private void onConnecting() {
if (gConnectListener != null) {
gConnectListener.onConnecting();
}
}
private void onConnectSuccess() {
if (gConnectListener != null) {
gConnectListener.onConnectSuccess();
}
}
private void onConnectFail(String msg) {
if (gConnectListener != null) {
gConnectListener.onConnectFail(msg);
}
}
public void initLivePush(String url) {
if (!TextUtils.isEmpty(url)) {
initPush(url);
}
}
/*************************************直播推流-video-start***********************************/
public void pushSPSPPS(byte[] sps, byte[] pps) {
if (sps != null && pps != null) {
pushSPSPPS(sps, sps.length, pps, pps.length);
}
}
public void pushVideoData(byte[] data, boolean ketFrame) {
if (data != null) {
pushVideoData(data, data.length, ketFrame);
}
}
/*************************************直播推流-video-end***********************************/
private native void initPush(String pushUrl);
/*************************************直播推流-video-start***********************************/
//推流信息
//发送sps pps
private native void pushSPSPPS(byte[] sps, int sps_len, byte[] pps, int pps_len);
//发送帧数据
private native void pushVideoData(byte[] data, int data_len, boolean keyFrame);
/*************************************直播推流-video-end***********************************/
}
gpush.cpp
#include <jni.h>
#include <string>
#include "RtmpPush.h"
#include "GCallJava.h"
GCallJava *gCallJava = NULL;
JavaVM *javaVm = NULL;
RtmpPush *rtmpPush = NULL;
extern "C"
JNIEXPORT void JNICALL
Java_com_example_glivepush_push_PushVideo_initPush(JNIEnv *env, jobject thiz, jstring pushUrl_) {
// TODO: implement initPush()
const char *pushUrl = env->GetStringUTFChars(pushUrl_, 0);
gCallJava = new GCallJava(javaVm, env, &thiz);
rtmpPush = new RtmpPush(pushUrl, gCallJava);
rtmpPush->init();
env->ReleaseStringUTFChars(pushUrl_, pushUrl);
}
extern "C"
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env;
javaVm = vm;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return -1;
}
return JNI_VERSION_1_6;
}
extern "C"
JNIEXPORT void JNICALL
JNI_OnUnload(JavaVM *vm, void *reserved) {
javaVm = NULL;
}
/*************************************直播推流-video-start***********************************/
extern "C"
JNIEXPORT void JNICALL
Java_com_example_glivepush_push_PushVideo_pushSPSPPS(JNIEnv *env, jobject thiz, jbyteArray sps_,
jint sps_len, jbyteArray pps_, jint pps_len) {
// TODO: implement pushSPSPPS()
jbyte *sps = env->GetByteArrayElements(sps_, NULL);
jbyte *pps = env->GetByteArrayElements(pps_, NULL);
if (rtmpPush != NULL) {
rtmpPush->pushSPSPPS(reinterpret_cast<char *>(sps), sps_len, reinterpret_cast<char *>(pps),
pps_len);
}
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_glivepush_push_PushVideo_pushVideoData(JNIEnv *env, jobject thiz, jbyteArray data_,
jint data_len, jboolean keyFrame) {
// TODO: implement pushVideoData()
jbyte *data = env->GetByteArrayElements(data_, NULL);
if (rtmpPush != NULL) {
rtmpPush->pushVideoData(reinterpret_cast<char *>(data), data_len, keyFrame);
}
}
/*************************************直播推流-video-end***********************************/
RtmpPush.h
#ifndef RTMPSUC_RTMPPUSH_H
#define RTMPSUC_RTMPPUSH_H
#include <malloc.h>
#include <cstring>
#include "GQueue.h"
#include "pthread.h"
#include "GCallJava.h"
extern "C" {
#include "librtmp/rtmp.h"
};
class RtmpPush {
public:
RTMP *rtmp = NULL;
char *url = NULL;
GQueue *queue = NULL;
pthread_t push_thread;
GCallJava *gCallJava = NULL;
/*************************************直播推流-video-start***********************************/
bool startPushing = false;
long startTime = 0;
/*************************************直播推流-video-end***********************************/
public:
RtmpPush(const char *url, GCallJava *gCallJava);
~RtmpPush();
void init();
/*************************************直播推流-video-start***********************************/
//发送sps pps
void pushSPSPPS(char *sps, int sps_len, char *pps, int pps_len);
//发送帧数据
void pushVideoData(char *data, int data_len, bool keyFrame);
/*************************************直播推流-video-end***********************************/
};
#endif //RTMPSUC_RTMPPUSH_H
RtmpPush.cpp
#include "RtmpPush.h"
RtmpPush::RtmpPush(const char *url, GCallJava *gCallJava) {
this->url = static_cast<char *>(malloc(512));
strcpy(this->url, url);
this->queue = new GQueue();
this->gCallJava = gCallJava;
}
RtmpPush::~RtmpPush() {
queue->notifyQueue();
queue->clearQueue();
free(url);
}
void *callBackPush(void *data) {
RtmpPush *rtmpPush = static_cast<RtmpPush *>(data);
/*************************************直播推流-video-start***********************************/
rtmpPush->startPushing = false;
/*************************************直播推流-video-end***********************************/
rtmpPush->gCallJava->onConnecting(G_THREAD_CHILD);
rtmpPush->rtmp = RTMP_Alloc(); //分配空间
RTMP_Init(rtmpPush->rtmp); //初始化
rtmpPush->rtmp->Link.timeout = 10; //设置超时时间
rtmpPush->rtmp->Link.lFlags |= RTMP_LF_LIVE; //追加直播
RTMP_SetupURL(rtmpPush->rtmp, rtmpPush->url); //设置推流URL
RTMP_EnableWrite(rtmpPush->rtmp); //设置可写状态
if (!RTMP_Connect(rtmpPush->rtmp, NULL)) { //链接服务器 0失败
// LOGE("can not connect the url %s", rtmpPush->url);
rtmpPush->gCallJava->onConnectFail("can not connect the url");
goto end;
}
if (!RTMP_ConnectStream(rtmpPush->rtmp, 0)) { //链接流 0失败
rtmpPush->gCallJava->onConnectFail("can not connect the stream of the service");
goto end;
}
rtmpPush->gCallJava->onConnectSuccess();
/*************************************直播推流-video-start***********************************/
//推流
rtmpPush->startPushing = true;
rtmpPush->startTime = RTMP_GetTime();
while (true) {
if (!rtmpPush->startPushing) {
break;
}
RTMPPacket *packet = NULL;
packet = rtmpPush->queue->getRtmpPacket();
if (packet != NULL) {
int result = RTMP_SendPacket(rtmpPush->rtmp, packet, 1);
LOGD("RTMP_SendPacket result is %d", result);
RTMPPacket_Free(packet);
packet = NULL;
}
}
/*************************************直播推流-video-end***********************************/
rtmpPush->gCallJava->onConnectSuccess();
end:
RTMP_Close(rtmpPush->rtmp);
RTMP_Free(rtmpPush->rtmp);
rtmpPush->rtmp = NULL;
pthread_exit(&rtmpPush->push_thread);
}
void RtmpPush::init() {
//gCallJava->onConnecting(G_THREAD_MAIN);
pthread_create(&push_thread, NULL, callBackPush, this);
}
/*************************************直播推流-video-start***********************************/
//发送sps pps
void RtmpPush::pushSPSPPS(char *sps, int sps_len, char *pps, int pps_len) {
//增加了额外的16字节的长度 sps/pps 按照关键帧处理
int bodySize = sps_len + pps_len + 16;
//初始化rtmpPacket
RTMPPacket *packet = static_cast<RTMPPacket *>(malloc(sizeof(RTMPPacket)));
RTMPPacket_Alloc(packet, bodySize);
RTMPPacket_Reset(packet);
char *body = packet->m_body;
int i = 0;
//0x17关键帧 frame type (4 bit)
// CodecID (4 bit)
body[i++] = 0x17;
//fixed (4 byte)
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
//版本 Profile 兼容性 ProfileLevel
body[i++] = 0x01;
body[i++] = sps[1];
body[i++] = sps[2];
body[i++] = sps[3];
//包长数据所使用的字节数
body[i++] = 0xff;
//sps个数
body[i++] = 0xe1;
//sps长度 (2 byte)
body[i++] = (sps_len >> 8) & 0xff;
body[i++] = sps_len & 0xff;
//sps实际内容
memcpy(&body[i], sps, sps_len);
i += sps_len;
//pps的个数
body[i++] = 0x01;
//pps长度 (2 byte)
body[i++] = (pps_len >> 8) & 0xff;
body[i++] = pps_len & 0xff;
//pps实际内容
memcpy(&body[i], pps, pps_len);
packet->m_packetType = RTMP_PACKET_TYPE_VIDEO;
packet->m_nBodySize = bodySize;
//时间戳
packet->m_nTimeStamp = 0;
packet->m_hasAbsTimestamp = 0;
packet->m_nChannel = 0x04; //音频或者视频
packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
packet->m_nInfoField2 = rtmp->m_stream_id;
//进队列
queue->putRtmpPacket(packet);
}
void RtmpPush::pushVideoData(char *data, int data_len, bool keyFrame) {
//增加了额外的9字节的长度
int bodySize = data_len + 9;
//初始化rtmpPacket
RTMPPacket *packet = static_cast<RTMPPacket *>(malloc(sizeof(RTMPPacket)));
RTMPPacket_Alloc(packet, bodySize);
RTMPPacket_Reset(packet);
char *body = packet->m_body;
int i = 0;
//0x17关键帧 frame type (4 bit)
//0x27非关键帧 CodecID (4 bit)
if (keyFrame) {
body[i++] = 0x17;
} else {
body[i++] = 0x27;
}
//fixed (4 byte) NALU
body[i++] = 0x01;
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
//dataLength : 长度信息(4 byte)
body[i++] = (data_len >> 24) & 0xff;
body[i++] = (data_len >> 16) & 0xff;
body[i++] = (data_len >> 8) & 0xff;
body[i++] = data_len & 0xff;
//h264 裸数据
memcpy(&body[i], data, data_len);
packet->m_packetType = RTMP_PACKET_TYPE_VIDEO;
packet->m_nBodySize = bodySize;
//时间戳
packet->m_nTimeStamp = RTMP_GetTime() - startTime;
packet->m_hasAbsTimestamp = 0;
packet->m_nChannel = 0x04; //音频或者视频
packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
packet->m_nInfoField2 = rtmp->m_stream_id;
//进队列
queue->putRtmpPacket(packet);
}
/*************************************直播推流-video-end***********************************/