使用librtmp推送AVC数据——Android端

xiaoxiao2021-02-28  93

http://blog.csdn.net/a992036795/article/details/54572335

一、前言  本文要讲述的是将AVC(h264)数据推送到流媒体服务器。我的实现方法是:1先使用Android自带的API采集摄像头数据,然后进行h264编码。2、然后使用ndk开发将编码后的数据通过librtmp发送出去。关于如何得到librtmp的动态库和如何使用系统API编码视频,可以参考我以前的文章。

移植librtmp http://blog.csdn.net/a992036795/article/details/54377892使用系统api编码视频 http://blog.csdn.net/a992036795/article/details/54286654

二、思路  1、使用摄像头采集视频、编码、得到h264数据。(这些不是重点,之前文章有讲到)

2、定义jni方法,我定义了4个方法:

public static final native int init(String url, int timeOut); public static final native int sendSpsAndPps(byte[] sps, int sysLen, byte[] pps, int ppsLen, long time); public static final native int sendVideoFrame(byte[] frame, int len, long time); public static final native int stop(); 12345678 12345678

分别的作用:1、初始化,并连接url、握手。2、发送SPS帧、和PPS帧。3、发送视频数据。4、释放资源。

3、实现这些jni方法。

4、对得到的h264帧进行判断,(主要的帧分为 SPS帧、 PPS帧、 IDR帧、 非关键帧)调用。

三、h264数据的格式分析  1、对应h264数据而言,每帧的界定符都是 00 00 00 01 或者 00 00 01。但如果是SPS或PPS帧他们的界定符一定是00 00 00 01  2、 帧类型有:  NAL_SLICE = 1  NAL_SLICE_DPA = 2  NAL_SLICE_DPB = 3  NAL_SLICE_DPC = 4  NAL_SLICE_IDR = 5  NAL_SEI = 6  NAL_SPS = 7  NAL_PPS = 8  NAL_AUD = 9  NAL_FILLER = 12  我们发送 RTMP 数据时只需要知道四种帧类型,其它类型我都把它规类成非关键帧。分别是  NAL_SPS(7), sps 帧  NAL_PPS(8), pps 帧  NAL_SLICE_IDR(5), 关键帧  NAL_SLICE(1) 非关键帧

帧类型的方式判断为界面符后首字节的低四位。  第一帧的帧类型为: 0x67 & 0x1F = 7,这是一个 SPS 帧  第二帧的帧类型为: 0x68 & 0x1F = 8,这是一个 PPS 帧  第三帧的帧类型为: 0x06 & 0x1F = 6,这是一个 SEI 帧

四、代码  1、初始化并连接url

JNIEXPORT jint JNICALL Java_com_blueberry_hellortmp_Rtmp_init(JNIEnv *env, jclass type, jstring url_, jint timeOut) { const char *url = (*env)->GetStringUTFChars(env, url_, 0); int ret; RTMP_LogSetLevel(RTMP_LOGDEBUG); rtmp = RTMP_Alloc(); //申请rtmp空间 RTMP_Init(rtmp); rtmp->Link.timeout = timeOut;//单位秒 RTMP_SetupURL(rtmp, url); RTMP_EnableWrite(rtmp); //握手 if ((ret = RTMP_Connect(rtmp, NULL)) <= 0) { LOGD("rtmp connet error"); return (*env)->NewStringUTF(env, "error"); } if ((ret = RTMP_ConnectStream(rtmp, 0)) <= 0) { LOGD("rtmp connect stream error"); } (*env)->ReleaseStringUTFChars(env, url_, url); return ret; } 1234567891011121314151617181920212223242526 1234567891011121314151617181920212223242526

2、发送sps帧 和pps帧

/** * H.264 的编码信息帧是发送给 RTMP 服务器称为 AVC sequence header, * RTMP 服务器只有收到 AVC sequence header 中的 sps, pps 才能解析后续发送的 H264 帧。 */ int send_video_sps_pps(unsigned char *sps, int sps_len, unsigned char *pps, int pps_len) { int i; packet = (RTMPPacket *) malloc(RTMP_HEAD_SIZE + 1024); memset(packet, 0, RTMP_HEAD_SIZE); packet->m_body = (char *) packet + RTMP_HEAD_SIZE; body = (unsigned char *) packet->m_body; i = 0; body[i++] = 0x17; //1:keyframe 7:AVC body[i++] = 0x00; // AVC sequence header body[i++] = 0x00; body[i++] = 0x00; body[i++] = 0x00; //fill in 0 /*AVCDecoderConfigurationRecord*/ body[i++] = 0x01; body[i++] = sps[1]; //AVCProfileIndecation body[i++] = sps[2]; //profile_compatibilty body[i++] = sps[3]; //AVCLevelIndication body[i++] = 0xff;//lengthSizeMinusOne /*SPS*/ body[i++] = 0xe1; body[i++] = (sps_len >> 8) & 0xff; body[i++] = sps_len & 0xff; /*sps data*/ memcpy(&body[i], sps, sps_len); i += sps_len; /*PPS*/ body[i++] = 0x01; /*sps data length*/ body[i++] = (pps_len >> 8) & 0xff; body[i++] = pps_len & 0xff; memcpy(&body[i], pps, pps_len); i += pps_len; packet->m_packetType = RTMP_PACKET_TYPE_VIDEO; packet->m_nBodySize = i; packet->m_nChannel = 0x04; packet->m_nTimeStamp = 0; packet->m_hasAbsTimestamp = 0; packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM; packet->m_nInfoField2 = rtmp->m_stream_id; /*发送*/ if (RTMP_IsConnected(rtmp)) { RTMP_SendPacket(rtmp, packet, TRUE); } free(packet); return 0; } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263

3、发送视频数据

//sps 与 pps 的帧界定符都是 00 00 00 01,而普通帧可能是 00 00 00 01 也有可能 00 00 01 int send_rtmp_video(unsigned char *buf, int len, long time) { int type; long timeOffset; timeOffset = time - start_time;/*start_time为开始直播的时间戳*/ /*去掉帧界定符*/ if (buf[2] == 0x00) {/*00 00 00 01*/ buf += 4; len -= 4; } else if (buf[2] == 0x01) { buf += 3; len - 3; } type = buf[0] & 0x1f; packet = (RTMPPacket *) malloc(RTMP_HEAD_SIZE + len + 9); memset(packet, 0, RTMP_HEAD_SIZE); packet->m_body = (char *) packet + RTMP_HEAD_SIZE; packet->m_nBodySize = len + 9; /* send video packet*/ body = (unsigned char *) packet->m_body; memset(body, 0, len + 9); /*key frame*/ body[0] = 0x27; if (type == NAL_SLICE_IDR) { body[0] = 0x17; //关键帧 } body[1] = 0x01;/*nal unit*/ body[2] = 0x00; body[3] = 0x00; body[4] = 0x00; body[5] = (len >> 24) & 0xff; body[6] = (len >> 16) & 0xff; body[7] = (len >> 8) & 0xff; body[8] = (len) & 0xff; /*copy data*/ memcpy(&body[9], buf, len); packet->m_hasAbsTimestamp = 0; packet->m_packetType = RTMP_PACKET_TYPE_VIDEO; packet->m_nInfoField2 = rtmp->m_stream_id; packet->m_nChannel = 0x04; packet->m_headerType = RTMP_PACKET_SIZE_LARGE; packet->m_nTimeStamp = timeOffset; if (RTMP_IsConnected(rtmp)) { RTMP_SendPacket(rtmp, packet, TRUE); } free(packet); } 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758

4、释放

int stop() { RTMP_Close(rtmp); RTMP_Free(rtmp); } 12345 12345

完整代码:  MainActivity.Java

package com.blueberry.hellortmp; import android.app.Activity; import android.graphics.ImageFormat; import android.hardware.Camera; import android.media.MediaCodec; import android.media.MediaCodecInfo; import android.media.MediaCodecList; import android.media.MediaFormat; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.widget.Button; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Date; import java.util.List; import static android.hardware.Camera.Parameters.FOCUS_MODE_AUTO; import static android.hardware.Camera.Parameters.PREVIEW_FPS_MAX_INDEX; import static android.hardware.Camera.Parameters.PREVIEW_FPS_MIN_INDEX; import static android.media.MediaCodec.CONFIGURE_FLAG_ENCODE; import static android.media.MediaFormat.KEY_BIT_RATE; import static android.media.MediaFormat.KEY_COLOR_FORMAT; import static android.media.MediaFormat.KEY_FRAME_RATE; import static android.media.MediaFormat.KEY_I_FRAME_INTERVAL; public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback2 { static { System.loadLibrary("hellortmp"); } static final int NAL_SLICE = 1; static final int NAL_SLICE_DPA = 2; static final int NAL_SLICE_DPB = 3; static final int NAL_SLICE_DPC = 4; static final int NAL_SLICE_IDR = 5; static final int NAL_SEI = 6; static final int NAL_SPS = 7; static final int NAL_PPS = 8; static final int NAL_AUD = 9; static final int NAL_FILLER = 12; private static final String VCODEC_MIME = "video/avc"; private Button btnStart; private SurfaceView mSurfaceView; private SurfaceHolder mSurfaceHolder; private Camera mCamera; private boolean isStarted; private int colorFormat; private long presentationTimeUs; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btnStart = (Button) findViewById(R.id.btn_start); mSurfaceView = (SurfaceView) findViewById(R.id.surface_view); btnStart.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { togglePublish(); } }); mSurfaceHolder = mSurfaceView.getHolder(); mSurfaceHolder.addCallback(this); } private void togglePublish() { if (isStarted) { stop(); } else { start(); } btnStart.setText(isStarted ? "停止" : "开始"); } private void start() { isStarted = true; // initVideoEncoder(); presentationTimeUs = new Date().getTime() * 1000; Rtmp.init("rtmp://192.168.155.1:1935/live/test", 5); } private MediaCodec vencoder; private void initVideoEncoder() { MediaCodecInfo mediaCodecInfo = selectCodec(VCODEC_MIME); colorFormat = getColorFormat(mediaCodecInfo); try { vencoder = MediaCodec.createByCodecName(mediaCodecInfo.getName()); Log.d(TAG, "编码器:" + mediaCodecInfo.getName() + "创建完成!"); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException("vencodec初始化失败!", e); } MediaFormat mediaFormat = MediaFormat .createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, previewSize.width, previewSize.height); mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 0); mediaFormat.setInteger(KEY_BIT_RATE, 300 * 1000); //比特率 mediaFormat.setInteger(KEY_COLOR_FORMAT, colorFormat); mediaFormat.setInteger(KEY_FRAME_RATE, 20); mediaFormat.setInteger(KEY_I_FRAME_INTERVAL, 5); vencoder.configure(mediaFormat, null, null, CONFIGURE_FLAG_ENCODE); vencoder.start(); } private static MediaCodecInfo selectCodec(String mimeType) { int numCodecs = MediaCodecList.getCodecCount(); for (int i = 0; i < numCodecs; i++) { MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); if (!codecInfo.isEncoder()) { continue; } String[] types = codecInfo.getSupportedTypes(); for (int j = 0; j < types.length; j++) { if (types[j].equalsIgnoreCase(mimeType)) { return codecInfo; } } } return null; } private int getColorFormat(MediaCodecInfo mediaCodecInfo) { int matchedFormat = 0; MediaCodecInfo.CodecCapabilities codecCapabilities = mediaCodecInfo.getCapabilitiesForType(VCODEC_MIME); for (int i = 0; i < codecCapabilities.colorFormats.length; i++) { int format = codecCapabilities.colorFormats[i]; if (format >= codecCapabilities.COLOR_FormatYUV420Planar && format <= codecCapabilities.COLOR_FormatYUV420PackedSemiPlanar) { if (format >= matchedFormat) { matchedFormat = format; break; } } } return matchedFormat; } private void stop() { isStarted = false; vencoder.stop(); vencoder.release(); Rtmp.stop(); } @Override protected void onResume() { super.onResume(); initCamera(); } @Override protected void onPause() { super.onPause(); releaseCamera(); } private void releaseCamera() { if (mCamera != null) { mCamera.release(); } mCamera = null; } private void initCamera() { try { if (mCamera == null) { mCamera = Camera.open(); } } catch (Exception e) { throw new RuntimeException("open camera fail", e); } setParameters(); setCameraDisplayOrientation(this, Camera.CameraInfo.CAMERA_FACING_BACK, mCamera); try { mCamera.setPreviewDisplay(mSurfaceHolder); } catch (IOException e) { e.printStackTrace(); } mCamera.addCallbackBuffer(new byte[calculateFrameSize(ImageFormat.NV21)]); mCamera.setPreviewCallbackWithBuffer(getPreviewCallback()); mCamera.startPreview(); } public static void setCameraDisplayOrientation(Activity activity, int cameraId, android.hardware.Camera camera) { android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo(); android.hardware.Camera.getCameraInfo(cameraId, info); int rotation = activity.getWindowManager().getDefaultDisplay() .getRotation(); int degrees = 0; switch (rotation) { case Surface.ROTATION_0: degrees = 0; break; case Surface.ROTATION_90: degrees = 90; break; case Surface.ROTATION_180: degrees = 180; break; case Surface.ROTATION_270: degrees = 270; break; } int result; if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { result = (info.orientation + degrees) % 360; result = (360 - result) % 360; // compensate the mirror } else { // back-facing result = (info.orientation - degrees + 360) % 360; } camera.setDisplayOrientation(result); } private void setParameters() { Camera.Parameters parameters = mCamera.getParameters(); List<Camera.Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes(); for (Camera.Size size : supportedPreviewSizes ) { if (size.width <= 360 && size.width >= 180) { previewSize = size; Log.d(TAG, "select size width=" + size.width + ",height=" + size.height); break; } } List<int[]> supportedPreviewFpsRange = parameters.getSupportedPreviewFpsRange(); int[] destRange = {30 * 1000, 30 * 1000}; for (int[] range : supportedPreviewFpsRange ) { if (range[PREVIEW_FPS_MIN_INDEX] >= 30 * 1000 && range[PREVIEW_FPS_MAX_INDEX] <= 100 * 1000) { destRange = range; break; } } parameters.setPreviewSize(previewSize.width, previewSize.height); parameters.setPreviewFpsRange(destRange[PREVIEW_FPS_MIN_INDEX], destRange[PREVIEW_FPS_MAX_INDEX]); parameters.setPreviewFormat(ImageFormat.NV21); parameters.setFocusMode(FOCUS_MODE_AUTO); mCamera.setParameters(parameters); } private static final String TAG = "MainActivity"; private Camera.Size previewSize; @Override public void surfaceRedrawNeeded(SurfaceHolder holder) { } @Override public void surfaceCreated(SurfaceHolder holder) { } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { initCamera(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { } public Camera.PreviewCallback getPreviewCallback() { return new Camera.PreviewCallback() { byte[] dstByte = new byte[calculateFrameSize(ImageFormat.NV21)]; @Override public void onPreviewFrame(byte[] data, Camera camera) { if (data == null) { mCamera.addCallbackBuffer(new byte[calculateFrameSize(ImageFormat.NV21)]); } else { if (isStarted) { // data 是Nv21 if (colorFormat == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar) { Yuv420Util.Nv21ToYuv420SP(data, dstByte, previewSize.width, previewSize.height); } else if (colorFormat == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar) { Yuv420Util.Nv21ToI420(data, dstByte, previewSize.width, previewSize.height); } else if (colorFormat == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar) { // Yuv420packedPlannar 和 yuv420sp很像 // 区别在于 加入 width = 4的话 y1,y2,y3 ,y4公用 u1v1 // 而 yuv420dp 则是 y1y2y5y6 共用 u1v1 //http://blog.csdn.net/jumper511/article/details/21719313 //这样处理的话颜色核能会有些失真。 Yuv420Util.Nv21ToYuv420SP(data, dstByte, previewSize.width, previewSize.height); } else { System.arraycopy(data, 0, dstByte, 0, data.length); } onGetVideoFrame(dstByte); } mCamera.addCallbackBuffer(data); } } }; } private MediaCodec.BufferInfo vBufferInfo = new MediaCodec.BufferInfo(); private void onGetVideoFrame(byte[] i420) { // MediaCodec ByteBuffer[] inputBuffers = vencoder.getInputBuffers(); ByteBuffer[] outputBuffers = vencoder.getOutputBuffers(); int inputBufferId = vencoder.dequeueInputBuffer(-1); if (inputBufferId >= 0) { // fill inputBuffers[inputBufferId] with valid data ByteBuffer bb = inputBuffers[inputBufferId]; bb.clear(); bb.put(i420, 0, i420.length); long pts = new Date().getTime() * 1000 - presentationTimeUs; vencoder.queueInputBuffer(inputBufferId, 0, i420.length, pts, 0); } for (; ; ) { int outputBufferId = vencoder.dequeueOutputBuffer(vBufferInfo, 0); if (outputBufferId >= 0) { // outputBuffers[outputBufferId] is ready to be processed or rendered. ByteBuffer bb = outputBuffers[outputBufferId]; onEncodedh264Frame(bb, vBufferInfo); vencoder.releaseOutputBuffer(outputBufferId, false); } if (outputBufferId < 0) { break; } } } private void onEncodedh264Frame(ByteBuffer bb, MediaCodec.BufferInfo vBufferInfo) { int offset = 4; //判断帧的类型 if (bb.get(2) == 0x01) { offset = 3; } int type = bb.get(offset) & 0x1f; switch (type) { case NAL_SLICE: Log.d(TAG, "type=NAL_SLICE"); break; case NAL_SLICE_DPA: Log.d(TAG, "type=NAL_SLICE_DPA"); break; case NAL_SLICE_DPB: Log.d(TAG, "type=NAL_SLICE_DPB"); break; case NAL_SLICE_DPC: Log.d(TAG, "type=NAL_SLICE_DPC"); break; case NAL_SLICE_IDR: //关键帧 Log.d(TAG, "type=NAL_SLICE_IDR"); break; case NAL_SEI: Log.d(TAG, "type=NAL_SEI"); break; case NAL_SPS: // sps Log.d(TAG, "type=NAL_SPS"); //[0, 0, 0, 1, 103, 66, -64, 13, -38, 5, -126, 90, 1, -31, 16, -115, 64, 0, 0, 0, 1, 104, -50, 6, -30] //打印发现这里将 SPS帧和 PPS帧合在了一起发送 // SPS为 [4,len-8] // PPS为后4个字节 //so . byte[] pps = new byte[4]; byte[] sps = new byte[vBufferInfo.size - 12]; bb.getInt();// 抛弃 0,0,0,1 bb.get(sps, 0, sps.length); bb.getInt(); bb.get(pps, 0, pps.length); Log.d(TAG, "解析得到 sps:" + Arrays.toString(sps) + ",PPS=" + Arrays.toString(pps)); Rtmp.sendSpsAndPps(sps, sps.length, pps, pps.length, vBufferInfo.presentationTimeUs / 1000); return; case NAL_PPS: // pps Log.d(TAG, "type=NAL_PPS"); break; case NAL_AUD: Log.d(TAG, "type=NAL_AUD"); break; case NAL_FILLER: Log.d(TAG, "type=NAL_FILLER"); break; } byte[] bytes = new byte[vBufferInfo.size]; bb.get(bytes); Rtmp.sendVideoFrame(bytes, bytes.length, vBufferInfo.presentationTimeUs / 1000); } private int calculateFrameSize(int format) { return previewSize.width * previewSize.height * ImageFormat.getBitsPerPixel(format) / 8; } } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433

Yuv420Util.java

package com.blueberry.hellortmp; /** * Created by blueberry on 1/13/2017. */ public class Yuv420Util { public static void Nv21ToI420(byte[] data, byte[] dstData, int w, int h) { int size = w * h; // Y System.arraycopy(data, 0, dstData, 0, size); for (int i = 0; i < size / 4; i++) { dstData[size + i] = data[size + i * 2 + 1]; //U dstData[size + size / 4 + i] = data[size + i * 2]; //V } } public static void Nv21ToYuv420SP(byte[] data, byte[] dstData, int w, int h) { int size = w * h; // Y System.arraycopy(data, 0, dstData, 0, size); for (int i = 0; i < size / 4; i++) { dstData[size + i * 2] = data[size + i * 2 + 1]; //U dstData[size + i * 2 + 1] = data[size + i * 2]; //V } } } 12345678910111213141516171819202122232425262728293031 12345678910111213141516171819202122232425262728293031

public.c

#include <jni.h> #include "rtmp.h" #include "rtmp_sys.h" #include "log.h" #include "android/log.h" #include "time.h" #define TAG "RTMP" #define RTMP_HEAD_SIZE (sizeof(RTMPPacket)+RTMP_MAX_HEADER_SIZE) #define NAL_SLICE 1 #define NAL_SLICE_DPA 2 #define NAL_SLICE_DPB 3 #define NAL_SLICE_DPC 4 #define NAL_SLICE_IDR 5 #define NAL_SEI 6 #define NAL_SPS 7 #define NAL_PPS 8 #define NAL_AUD 9 #define NAL_FILLER 12 #define LOGD(fmt, ...) \ __android_log_print(ANDROID_LOG_DEBUG,TAG,fmt,##__VA_ARGS__); RTMP *rtmp; RTMPPacket *packet = NULL; unsigned char *body; long start_time; //int send(const char *buf, int buflen, int type, unsigned int timestamp); int send_video_sps_pps(unsigned char *sps, int sps_len, unsigned char *pps, int pps_len); int send_rtmp_video(unsigned char *buf, int len, long time); int stop(); JNIEXPORT jint JNICALL Java_com_blueberry_hellortmp_Rtmp_init(JNIEnv *env, jclass type, jstring url_, jint timeOut) { const char *url = (*env)->GetStringUTFChars(env, url_, 0); int ret; RTMP_LogSetLevel(RTMP_LOGDEBUG); rtmp = RTMP_Alloc(); //申请rtmp空间 RTMP_Init(rtmp); rtmp->Link.timeout = timeOut;//单位秒 RTMP_SetupURL(rtmp, url); RTMP_EnableWrite(rtmp); //握手 if ((ret = RTMP_Connect(rtmp, NULL)) <= 0) { LOGD("rtmp connet error"); return (*env)->NewStringUTF(env, "error"); } if ((ret = RTMP_ConnectStream(rtmp, 0)) <= 0) { LOGD("rtmp connect stream error"); } (*env)->ReleaseStringUTFChars(env, url_, url); return ret; } JNIEXPORT jint JNICALL Java_com_blueberry_hellortmp_Rtmp_sendSpsAndPps(JNIEnv *env, jclass type, jbyteArray sps_, jint spsLen, jbyteArray pps_, jint ppsLen, jlong time) { jbyte *sps = (*env)->GetByteArrayElements(env, sps_, NULL); jbyte *pps = (*env)->GetByteArrayElements(env, pps_, NULL); int ret = send_video_sps_pps((unsigned char *) sps, spsLen, (unsigned char *) pps, ppsLen); start_time = time; (*env)->ReleaseByteArrayElements(env, sps_, sps, 0); (*env)->ReleaseByteArrayElements(env, pps_, pps, 0); return ret; } JNIEXPORT jint JNICALL Java_com_blueberry_hellortmp_Rtmp_sendVideoFrame(JNIEnv *env, jclass type, jbyteArray frame_, jint len, jlong time) { jbyte *frame = (*env)->GetByteArrayElements(env, frame_, NULL); int ret = send_rtmp_video((unsigned char *) frame, len, time); (*env)->ReleaseByteArrayElements(env, frame_, frame, 0); return ret; } int stop() { RTMP_Close(rtmp); RTMP_Free(rtmp); } /** * H.264 的编码信息帧是发送给 RTMP 服务器称为 AVC sequence header, * RTMP 服务器只有收到 AVC sequence header 中的 sps, pps 才能解析后续发送的 H264 帧。 */ int send_video_sps_pps(unsigned char *sps, int sps_len, unsigned char *pps, int pps_len) { int i; packet = (RTMPPacket *) malloc(RTMP_HEAD_SIZE + 1024); memset(packet, 0, RTMP_HEAD_SIZE); packet->m_body = (char *) packet + RTMP_HEAD_SIZE; body = (unsigned char *) packet->m_body; i = 0; body[i++] = 0x17; //1:keyframe 7:AVC body[i++] = 0x00; // AVC sequence header body[i++] = 0x00; body[i++] = 0x00; body[i++] = 0x00; //fill in 0 /*AVCDecoderConfigurationRecord*/ body[i++] = 0x01; body[i++] = sps[1]; //AVCProfileIndecation body[i++] = sps[2]; //profile_compatibilty body[i++] = sps[3]; //AVCLevelIndication body[i++] = 0xff;//lengthSizeMinusOne /*SPS*/ body[i++] = 0xe1; body[i++] = (sps_len >> 8) & 0xff; body[i++] = sps_len & 0xff; /*sps data*/ memcpy(&body[i], sps, sps_len); i += sps_len; /*PPS*/ body[i++] = 0x01; /*sps data length*/ body[i++] = (pps_len >> 8) & 0xff; body[i++] = pps_len & 0xff; memcpy(&body[i], pps, pps_len); i += pps_len; packet->m_packetType = RTMP_PACKET_TYPE_VIDEO; packet->m_nBodySize = i; packet->m_nChannel = 0x04; packet->m_nTimeStamp = 0; packet->m_hasAbsTimestamp = 0; packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM; packet->m_nInfoField2 = rtmp->m_stream_id; /*发送*/ if (RTMP_IsConnected(rtmp)) { RTMP_SendPacket(rtmp, packet, TRUE); } free(packet); return 0; } //sps 与 pps 的帧界定符都是 00 00 00 01,而普通帧可能是 00 00 00 01 也有可能 00 00 01 int send_rtmp_video(unsigned char *buf, int len, long time) { int type; long timeOffset; timeOffset = time - start_time;/*start_time为开始直播的时间戳*/ /*去掉帧界定符*/ if (buf[2] == 0x00) {/*00 00 00 01*/ buf += 4; len -= 4; } else if (buf[2] == 0x01) { buf += 3; len - 3; } type = buf[0] & 0x1f; packet = (RTMPPacket *) malloc(RTMP_HEAD_SIZE + len + 9); memset(packet, 0, RTMP_HEAD_SIZE); packet->m_body = (char *) packet + RTMP_HEAD_SIZE; packet->m_nBodySize = len + 9; /* send video packet*/ body = (unsigned char *) packet->m_body; memset(body, 0, len + 9); /*key frame*/ body[0] = 0x27; if (type == NAL_SLICE_IDR) { body[0] = 0x17; //关键帧 } body[1] = 0x01;/*nal unit*/ body[2] = 0x00; body[3] = 0x00; body[4] = 0x00; body[5] = (len >> 24) & 0xff; body[6] = (len >> 16) & 0xff; body[7] = (len >> 8) & 0xff; body[8] = (len) & 0xff; /*copy data*/ memcpy(&body[9], buf, len); packet->m_hasAbsTimestamp = 0; packet->m_packetType = RTMP_PACKET_TYPE_VIDEO; packet->m_nInfoField2 = rtmp->m_stream_id; packet->m_nChannel = 0x04; packet->m_headerType = RTMP_PACKET_SIZE_LARGE; packet->m_nTimeStamp = timeOffset; if (RTMP_IsConnected(rtmp)) { RTMP_SendPacket(rtmp, packet, TRUE); } free(packet); } JNIEXPORT jint JNICALL Java_com_blueberry_hellortmp_Rtmp_stop(JNIEnv *env, jclass type) { stop(); return 0; } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231

可以安装Adobe Media Server进行观看

代码地址:https://github.com/blueberryCoder/Media/tree/master/HelloRtmp

本文参考:  https://my.oschina.net/jerikc/blog/501948

转载请注明原文地址: https://www.6miu.com/read-31281.html

最新回复(0)