国产亚洲精品福利在线无卡一,国产精久久一区二区三区,亚洲精品无码国模,精品久久久久久无码专区不卡

當(dāng)前位置: 首頁 > news >正文

徐州建設(shè)安全監(jiān)督網(wǎng)站搜索引擎seo

徐州建設(shè)安全監(jiān)督網(wǎng)站,搜索引擎seo,網(wǎng)站外鏈建設(shè)設(shè)計,化妝培訓(xùn)網(wǎng)站模板在之前完成的實(shí)戰(zhàn)項(xiàng)目【FFmpeg音視頻播放器】屬于拉流范疇,接下來將完成推流工作,通過RTMP實(shí)現(xiàn)推流,即直播客戶端。簡單的說,就是將手機(jī)采集的音頻數(shù)據(jù)和視頻數(shù)據(jù),推到服務(wù)器端。 接下來的RTMP直播客戶端系列&#xff…

在之前完成的實(shí)戰(zhàn)項(xiàng)目【FFmpeg音視頻播放器】屬于拉流范疇,接下來將完成推流工作,通過RTMP實(shí)現(xiàn)推流,即直播客戶端。簡單的說,就是將手機(jī)采集的音頻數(shù)據(jù)和視頻數(shù)據(jù),推到服務(wù)器端。

接下來的RTMP直播客戶端系列,主要實(shí)現(xiàn)紅框和紫色部分:

?本節(jié)主要內(nèi)容:

?1.Java層視頻編碼工作。

2.Native層視頻編碼器工作。

3.Native層視頻推流編碼工作。

源碼:

NdkPush: 通過RTMP實(shí)現(xiàn)推流,直播客戶端。

一、Java層視頻編碼

1)MainActivity:

MainActivity只與中轉(zhuǎn)站NdkPusher打交道,用戶操作頁面相關(guān)功能是調(diào)用NdkPusher分發(fā)下去;

初始化NdkPusher.java

mNdkPusher = new NdkPusher(this, Camera.CameraInfo.CAMERA_FACING_BACK, 640, 480, 25, 800000);

首次點(diǎn)擊【切換攝像頭】時,設(shè)置Camera與Surface綁定

/*** 切換攝像頭** @param view*/
public void switchCamera(View view) {if (initPermission()) {if (!isBind) {mNdkPusher.setPreviewDisplay(mSurfaceHolder);isBind = true;}mNdkPusher.switchCamera();}
}

點(diǎn)擊【開始直播】時,開始直播,并設(shè)置rtmp服務(wù)器地址

/*** 開始直播** @param view*/
public void startLive(View view) {mNdkPusher.startLive("rtmp://139.224.136.101/myapp");
}

點(diǎn)擊【停止直播】時,停止直播

/*** 停止直播** @param view*/
public void stopLive(View view) {mNdkPusher.stopLive();
}

頁面關(guān)閉,釋放資源

/*** 釋放工作*/
@Override
protected void onDestroy() {super.onDestroy();mNdkPusher.release();
}

2)NdkPusher:

中轉(zhuǎn)站,分發(fā)MainActivity事件和和Native層打交道;

NdkPusher初始化時,主要是的三件事,

①:初始化native層需要的加載,
②:實(shí)例化視頻通道并傳遞基本參數(shù)(寬高,fps,碼率等),
③:實(shí)例化音頻通道(下一節(jié)內(nèi)容)

public NdkPusher(Activity activity, int cameraId, int width, int height, int fps, int bitrate) {native_init();// 將this傳遞給VideoChannel,方便VideoChannel操控native層mVideoChannel = new VideoChannel(this, activity, cameraId, width, height, fps, bitrate);
}

分發(fā)給視頻通道VideoChannel-->SurfaceView與中轉(zhuǎn)站里面的Camera綁定

public void setPreviewDisplay(SurfaceHolder surfaceHolder) {mVideoChannel.setPreviewDisplay(surfaceHolder);
}

分發(fā)給視頻通道VideoChannel-->切換攝像頭

public void switchCamera() {mVideoChannel.switchCamera();
}

開始直播,調(diào)用native層開始直播工作,分發(fā)給視頻通道VideoChannel開始直播

public void startLive(String path) {native_start(path);mVideoChannel.startLive();
}

停止直播,調(diào)用native層停止直播工作,分發(fā)給視頻通道VideoChannel停止直播

public void stopLive() {mVideoChannel.stopLive();native_stop();
}

釋放工作,釋放native層數(shù)據(jù)和視頻通道VideoChannel

public void release() {mVideoChannel.release();native_release();
}

與native層通訊函數(shù)

// 音頻 視頻 公用的
private native void native_init(); // 初始化
private native void native_start(String path); // 開始直播start(音頻視頻通用一套代碼) path:rtmp推流地址
private native void native_stop(); // 停止直播
private native void native_release(); // onDestroy--->release釋放工作// 下面是視頻獨(dú)有
public native void native_initVideoEncoder(int width, int height, int mFps, int bitrate); // 初始化x264編碼器
public native void native_pushVideo(byte[] data); // 相機(jī)畫面的數(shù)據(jù) byte[] 推給 native層

3)VideoChannel:

視頻通道,處理NdkPusher分發(fā)下來的事件和將CameraHelper的Camera畫面數(shù)據(jù)推送到native層。

初始化CameraHelper,設(shè)置Camera相機(jī)預(yù)覽幫助類,onPreviewFrame(nv21)數(shù)據(jù)的回調(diào)監(jiān)聽和寬高發(fā)送改變的監(jiān)聽

public VideoChannel(NdkPusher ndkPusher, Activity activity, int cameraId, int width, int height, int fps, int bitrate) {this.mNdkPusher = ndkPusher; // 回調(diào)給中轉(zhuǎn)站this.mFps = fps; // fps 每秒鐘多少幀this.bitrate = bitrate; // 碼率mCameraHelper = new CameraHelper(activity, cameraId, width, height);mCameraHelper.setPreviewCallback(this); // 設(shè)置Camera相機(jī)預(yù)覽幫助類,onPreviewFrame(nv21)數(shù)據(jù)的回調(diào)監(jiān)聽mCameraHelper.setOnChangedSizeListener(this); // 寬高發(fā)送改變的監(jiān)聽回調(diào)設(shè)置
}

調(diào)用幫助類:與Surface綁定

public void setPreviewDisplay(SurfaceHolder surfaceHolder) {mCameraHelper.setPreviewDisplay(surfaceHolder);
}

調(diào)用幫助類-->切換攝像頭

public void switchCamera() {mCameraHelper.switchCamera();
}

開始直播,只修改標(biāo)記 讓其可以進(jìn)入if 完成圖像數(shù)據(jù)推送

public void startLive() {isLive = true;
}

停止直播,只修改標(biāo)記 讓其可以不要進(jìn)入if 就不會再數(shù)據(jù)推送了

public void stopLive() {isLive = false;
}

釋放,調(diào)用幫助類-->停止預(yù)覽

public void release() {mCameraHelper.stopPreview();
}

Camera預(yù)覽畫面的數(shù)據(jù),回調(diào)到這里,再通過mNdkPusher,將數(shù)據(jù)推送到native層

@Override
public void onPreviewFrame(byte[] data, Camera camera) {// data == nv21 數(shù)據(jù)if (isLive) {// 圖像數(shù)據(jù)推送mNdkPusher.native_pushVideo(data);}
}

Camera發(fā)送寬高改變,回調(diào)到這里,再通過mNdkPusher,將數(shù)據(jù)推送到native層

@Override
public void onChanged(int width, int height) {// 視頻編碼器的初始化有關(guān):width,height,fps,bitratemNdkPusher.native_initVideoEncoder(width, height, mFps, bitrate); // 初始化x264編碼器
}

4)CameraHelper第一節(jié)已完成。

二、Native層視頻編碼器

1)native-lib.cpp:

處理Java層NdkPusher調(diào)用的native函數(shù);

native層初始化工作:

NdkPusher構(gòu)造函數(shù)調(diào)用到這里,初始化native層VideoChannel,設(shè)置 Camera預(yù)覽畫面的數(shù)據(jù)推送到native層,videoChannel編碼后數(shù)據(jù),通過callback回調(diào)到native-lib.cpp,加入隊(duì)列。

extern "C"
JNIEXPORT void JNICALL
Java_com_ndk_push_NdkPusher_native_1init(JNIEnv *env, jobject thiz) {// 初始化 VideoChannelvideoChannel = new VideoChannel();// 設(shè)置 Camera預(yù)覽畫面的數(shù)據(jù)推送到native層,videoChannel編碼后數(shù)據(jù),通過callback回調(diào)到native-lib.cpp,加入隊(duì)列videoChannel->setVideoCallback(callback);// 設(shè)置 隊(duì)列的釋放工作 回調(diào)packets.setReleaseCallback(releasePackets);
}

videoCallback 函數(shù)指針的實(shí)現(xiàn)(將編碼后數(shù)據(jù)存放packet到隊(duì)列)

void callback(RTMPPacket *packet) {if (packet) {if (packet->m_nTimeStamp == -1) {packet->m_nTimeStamp = RTMP_GetTime() - start_time; // 如果是sps+pps 沒有時間搓,如果是I幀就需要有時間搓}packets.push(packet); // 存入隊(duì)列里面}
}

釋放RTMPPacket * 包的函數(shù)指針實(shí)現(xiàn),T無法釋放, 讓外界釋放

void releasePackets(RTMPPacket **packet) {if (packet) {RTMPPacket_Free(*packet);delete packet;packet = nullptr;}
}

?初始化x264編碼器,Camera寬高改變,回調(diào)到這里,首次設(shè)置預(yù)覽時觸發(fā);分發(fā)到VideoChannel視頻通道初始化編碼器。

extern "C"
JNIEXPORT void JNICALL
Java_com_ndk_push_NdkPusher_native_1initVideoEncoder(JNIEnv *env, jobject thiz, jint width,jint height, jint fps, jint bitrate) {if (videoChannel) {videoChannel->initVideoEncoder(width, height, fps, bitrate);}
}

2)VideoChannel.cpp:

?native層視頻通道,初始化x264編碼器和處理相機(jī)原始數(shù)據(jù)編碼,再回到給native-lib.cpp,加入隊(duì)列。

初始化 x264 編碼器

void VideoChannel::initVideoEncoder(int width, int height, int fps, int bitrate) {// 防止編碼器多次創(chuàng)建 互斥鎖pthread_mutex_lock(&mutex);mWidth = width;mHeight = height;mFps = fps;mBitrate = bitrate;y_len = width * height;uv_len = y_len / 4;// 防止重復(fù)初始化x264編碼器if (videoEncoder) {x264_encoder_close(videoEncoder);videoEncoder = nullptr;}// 防止重復(fù)初始化pic_inif (pic_in) {x264_picture_clean(pic_in);DELETE(pic_in);}// TODO 初始化x264編碼器x264_param_t param;// x264的參數(shù)集// 設(shè)置編碼器屬性// ultrafast 最快  (直播必須快)// zerolatency 零延遲(直播必須快)x264_param_default_preset(&param, "ultrafast", "zerolatency");// 編碼規(guī)格:https://wikipedia.tw.wjbk.site/wiki/H.264 看圖片param.i_level_idc = 32; // 3.2 中等偏上的規(guī)格  自動用 碼率,模糊程度,分辨率// 輸入數(shù)據(jù)格式是 YUV420P  平面模式VVVVVUUUU,如果沒有P,  就是交錯模式VUVUVUVUparam.i_csp = X264_CSP_I420;param.i_width = width;param.i_height = height;// 不能有B幀,如果有B幀會影響編碼、解碼效率(快)param.i_bframe = 0;// 碼率控制方式。CQP(恒定質(zhì)量),CRF(恒定碼率),ABR(平均碼率)param.rc.i_rc_method = X264_RC_CRF;// 設(shè)置碼率param.rc.i_bitrate = bitrate / 1000;// 瞬時最大碼率 網(wǎng)絡(luò)波動導(dǎo)致的param.rc.i_vbv_max_bitrate = bitrate / 1000 * 1.2;// 設(shè)置了i_vbv_max_bitrate就必須設(shè)置buffer大小,碼率控制區(qū)大小,單位Kb/sparam.rc.i_vbv_buffer_size = bitrate / 1000;// 碼率控制不是通過 timebase 和 timestamp,碼率的控制,完全不用時間搓   ,而是通過 fps 來控制 碼率(根據(jù)你的fps來自動控制)param.b_vfr_input = 0;// 分子 分母// 幀率分子param.i_fps_num = fps;// 幀率分母param.i_fps_den = 1;param.i_timebase_den = param.i_fps_num;param.i_timebase_num = param.i_fps_den;// 告訴人家,到底是什么時候,來一個I幀, 計算關(guān)鍵幀的距離// 幀距離(關(guān)鍵幀)  2s一個關(guān)鍵幀   (就是把兩秒鐘一個關(guān)鍵幀告訴人家)param.i_keyint_max = fps * 2;// sps序列參數(shù)   pps圖像參數(shù)集,所以需要設(shè)置header(sps pps)// 是否復(fù)制sps和pps放在每個關(guān)鍵幀的前面 該參數(shù)設(shè)置是讓每個關(guān)鍵幀(I幀)都附帶sps/pps。param.b_repeat_headers = 1;// 并行編碼線程數(shù)param.i_threads = 1;// profile級別,baseline級別 (把我們上面的參數(shù)進(jìn)行提交)x264_param_apply_profile(&param, "baseline");// 輸入圖像初始化pic_in = new x264_picture_t(); // 本身空間的初始化x264_picture_alloc(pic_in, param.i_csp, param.i_width, param.i_height); // pic_in內(nèi)部成員初始化等// 打開編碼器 一旦打開成功,我們的編碼器就拿到了videoEncoder = x264_encoder_open(&param);if (videoEncoder) {LOGE("x264編碼器打開成功");}pthread_mutex_unlock(&mutex);
}

三、Native層視頻推流編碼

1)native-lib.cpp:

開始直播 ---> 啟動工作

創(chuàng)建子線程實(shí)現(xiàn):
1.連接流媒體服務(wù)器;
2.發(fā)包;

extern "C"
JNIEXPORT void JNICALL
Java_com_ndk_push_NdkPusher_native_1start(JNIEnv *env, jobject thiz, jstring path_) {/*** 創(chuàng)建子線程:* 1.連接流媒體服務(wù)器;* 2.發(fā)包;*/if (isStart) {return;}isStart = true;const char *path = env->GetStringUTFChars(path_, nullptr);// 深拷貝char *url = new char(strlen(path) + 1); // C++的堆區(qū)開辟 new -- deletestrcpy(url, path);// 創(chuàng)建線程來進(jìn)行直播pthread_create(&pid_start, nullptr, task_start, url);env->ReleaseStringUTFChars(path_, path); // 你隨意釋放,我已經(jīng)深拷貝了
}

連接RTMP服務(wù)器,遍歷壓縮包隊(duì)列,將數(shù)據(jù)發(fā)送到RTMP服務(wù)器

void *task_start(void *args) {char *url = static_cast<char *>(args);// RTMPDump API 九部曲RTMP *rtmp = nullptr;int result; // 返回值判斷成功失敗do {// 1.1,rtmp 初始化rtmp = RTMP_Alloc();if (!rtmp) {LOGE("rtmp 初始化失敗");break;}// 1.2,rtmp 初始化RTMP_Init(rtmp);rtmp->Link.timeout = 5; // 設(shè)置連接的超時時間(以秒為單位的連接超時)// 2,rtmp 設(shè)置流媒體地址result = RTMP_SetupURL(rtmp, url);if (!result) { // result == 0 和 ffmpeg不同,0代表失敗LOGE("rtmp 設(shè)置流媒體地址失敗");break;}// 3,開啟輸出模式RTMP_EnableWrite(rtmp);// 4,建立連接result = RTMP_Connect(rtmp, nullptr);if (!result) { // result == 0 和 ffmpeg不同,0代表失敗LOGE("rtmp 建立連接失敗:%d, url: %s", result, url);break;}// 5,連接流result = RTMP_ConnectStream(rtmp, 0);if (!result) { // result == 0 和 ffmpeg不同,0代表失敗LOGE("rtmp 連接流失敗");break;}start_time = RTMP_GetTime();// 準(zhǔn)備好了,可以開始向服務(wù)器推流了readyPushing = true;// 隊(duì)列開始工作packets.setWork(1);RTMPPacket *packet = nullptr;// 從隊(duì)列里面獲取壓縮包,直接發(fā)給服務(wù)器while (readyPushing) {packets.pop(packet); // 阻塞式if (!readyPushing) {break;}// 取不到數(shù)據(jù),重新取,可能還沒生產(chǎn)出來if (!packet) {continue;}// 到這里就是成功的獲取隊(duì)列的ptk了,可以發(fā)送給流媒體服務(wù)器packet->m_nInfoField2 = rtmp->m_stream_id;// 給rtmp的流id// 成功取出數(shù)據(jù)包,發(fā)送result = RTMP_SendPacket(rtmp, packet, 1); // 1==true 開啟內(nèi)部緩沖// packet 你都發(fā)給服務(wù)器了,可以大膽釋放releasePackets(&packet);if (!result) { // result == 0 和 ffmpeg不同,0代表失敗LOGE("rtmp 失敗 自動斷開服務(wù)器");break;}}releasePackets(&packet); // 只要跳出循環(huán),就釋放} while (false);// 本次一系列釋放工作isStart = false;readyPushing = false;packets.setWork(0);packets.clear();if (rtmp) {RTMP_Close(rtmp);RTMP_Free(rtmp);}delete url;return nullptr;
}

Camera預(yù)覽畫面的數(shù)據(jù),回調(diào)到這里,將原始數(shù)據(jù)進(jìn)行x264編碼后,得到的RTMPPkt(壓縮數(shù)據(jù))加入隊(duì)列里面

extern "C"
JNIEXPORT void JNICALL
Java_com_ndk_push_NdkPusher_native_1pushVideo(JNIEnv *env, jobject thiz, jbyteArray data_) {if (!videoChannel || !readyPushing) { return; }// 把jni ---> C語言的jbyte *data = env->GetByteArrayElements(data_, nullptr);// data == nv21數(shù)據(jù),編碼,加入隊(duì)列videoChannel->encodeData(data);env->ReleaseByteArrayElements(data_, data, 0); // 釋放byte[]
}

2)VideoChannel.cpp:

視頻原始數(shù)據(jù)編碼工作

void VideoChannel::encodeData(signed char *data) {pthread_mutex_lock(&mutex);// 把nv21的y分量 Copy i420的y分量memcpy(pic_in->img.plane[0], data, y_len);// 把nv21的vuvuvuvu 轉(zhuǎn)化成 i420的 uuuuvvvvfor (int i = 0; i < uv_len; ++i) {// u 數(shù)據(jù)// data + y_len + i * 2 + 1 : 移動指針取 data(nv21) 中 u 的數(shù)據(jù)*(pic_in->img.plane[1] + i) = *(data + y_len + i * 2 + 1);// v 數(shù)據(jù)// data + y_len + i * 2 : 移動指針取 data(nv21) 中 v 的數(shù)據(jù)*(pic_in->img.plane[2] + i) = *(data + y_len + i * 2);}x264_nal_t *nal = nullptr; // 通過H.264編碼得到NAL數(shù)組(理解)int pi_nal; // pi_nal是nal中輸出的NAL單元的數(shù)量x264_picture_t pic_out; // 輸出編碼后圖片 (編碼后的圖片)// 1.視頻編碼器, 2.nal,  3.pi_nal是nal中輸出的NAL單元的數(shù)量, 4.輸入原始的圖片,  5.輸出編碼后圖片int ret = x264_encoder_encode(videoEncoder, &nal, &pi_nal, pic_in,&pic_out); // 進(jìn)行編碼(本質(zhì)的理解是:編碼一張圖片)if (ret < 0) { // 返回值:x264_encoder_encode函數(shù) 返回返回的 NAL 中的字節(jié)數(shù)。如果沒有返回 NAL 單元,則在錯誤時返回負(fù)數(shù)和零。LOGE("x264編碼失敗");pthread_mutex_unlock(&mutex); // 注意:一旦編碼失敗了,一定要解鎖,否則有概率性造成死鎖了return;}// 發(fā)送 Packets 入隊(duì)queue// sps(序列參數(shù)集) pps(圖像參數(shù)集) 說白了就是:告訴我們?nèi)绾谓獯a圖像數(shù)據(jù)int sps_len, pps_len; // sps 和 pps 的長度uint8_t sps[100]; // 用于接收 sps 的數(shù)組定義uint8_t pps[100]; // 用于接收 pps 的數(shù)組定義pic_in->i_pts += 1; // pts顯示的時間(+=1 目的是每次都累加下去), dts編碼的時間// 遍歷nal中輸出的NAL單元,組件壓縮包數(shù)據(jù),加入隊(duì)列for (int i = 0; i < pi_nal; ++i) {if (nal[i].i_type == NAL_SPS) {sps_len = nal[i].i_payload - 4; // 去掉起始碼(之前我們學(xué)過的內(nèi)容:00 00 00 01)memcpy(sps, nal[i].p_payload + 4, sps_len); // 由于上面減了4,所以+4挪動這里的位置開始} else if (nal[i].i_type == NAL_PPS) {pps_len = nal[i].i_payload - 4; // 去掉起始碼 之前我們學(xué)過的內(nèi)容:00 00 00 01)memcpy(pps, nal[i].p_payload + 4, pps_len); // 由于上面減了4,所以+4挪動這里的位置開始// sps + pps == 1個壓縮包數(shù)據(jù)sendSpsPps(sps, pps, sps_len, pps_len); // pps是跟在sps后面的,這里拿到的pps表示前面的sps肯定拿到了} else {// 發(fā)送 I幀 P幀sendFrame(nal[i].i_type, nal[i].i_payload, nal[i].p_payload);}}
}

組裝sps + pps == 1個壓縮包數(shù)據(jù),存入隊(duì)列

void VideoChannel::sendSpsPps(uint8_t *sps, uint8_t *pps, int sps_len, int pps_len) {// 根據(jù)協(xié)議設(shè)置壓縮包數(shù)據(jù)長度int body_size = 5 + 8 + sps_len + 3 + pps_len;RTMPPacket *packet = new RTMPPacket; // 開始封包RTMPPacketRTMPPacket_Alloc(packet, body_size); // 堆區(qū)實(shí)例化 RTMPPacketint i = 0;packet->m_body[i++] = 0x17; // 十六進(jìn)制轉(zhuǎn)換成二進(jìn)制,二進(jìn)制查表 就懂了packet->m_body[i++] = 0x00;   // 重點(diǎn)是此字節(jié) 如果是1 幀類型(關(guān)鍵幀 非關(guān)鍵幀), 如果是0一定是 sps ppspacket->m_body[i++] = 0x00;packet->m_body[i++] = 0x00;packet->m_body[i++] = 0x00;// 看圖說話packet->m_body[i++] = 0x01; // 版本packet->m_body[i++] = sps[1];packet->m_body[i++] = sps[2];packet->m_body[i++] = sps[3];packet->m_body[i++] = 0xFF;packet->m_body[i++] = 0xE1;// 兩個字節(jié)表達(dá)一個長度,需要位移// 用兩個字節(jié)來表達(dá) sps的長度,所以就需要位運(yùn)算,取出sps_len高8位 再取出sps_len低8位//(位運(yùn)算:https://blog.csdn.net/qq_31622345/article/details/98070787)// https://www.cnblogs.com/zhu520/p/8143688.htmlpacket->m_body[i++] = (sps_len >> 8) & 0xFF; // 取高8位packet->m_body[i++] = sps_len & 0xFF; // 去低8位memcpy(&packet->m_body[i], sps, sps_len); // sps拷貝進(jìn)去了i += sps_len; // 拷貝完sps數(shù)據(jù) ,i移位,(下面才能準(zhǔn)確移位)packet->m_body[i++] = 0x01; // pps個數(shù),用一個字節(jié)表示packet->m_body[i++] = (pps_len >> 8) & 0xFF; // 取高8位packet->m_body[i++] = pps_len & 0xFF; // 去低8位memcpy(&packet->m_body[i], pps, pps_len); // pps拷貝進(jìn)去了i += pps_len; // 拷貝完pps數(shù)據(jù) ,i移位,(下面才能準(zhǔn)確移位)// 封包處理packet->m_packetType = RTMP_PACKET_TYPE_VIDEO; // 包類型 視頻包packet->m_nBodySize = body_size; // 設(shè)置好 sps+pps的總大小packet->m_nChannel = 10; // 通道ID,隨便寫一個,注意:不要寫的和rtmp.c(里面的m_nChannel有沖突 4301行)packet->m_nTimeStamp = 0; // sps pps 包 沒有時間戳packet->m_hasAbsTimestamp = 0; // 時間戳絕對或相對 也沒有時間搓packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM; // 包的類型:數(shù)據(jù)量比較少,不像幀數(shù)據(jù)(那就很大了),所以設(shè)置中等大小的包// packet 存入隊(duì)列videoCallback(packet);
}

發(fā)送幀信息,把幀類型 RTMPPacket 存入隊(duì)列

void VideoChannel::sendFrame(int type, int payload, uint8_t *pPayload) {// 去掉起始碼 00 00 00 01 或者 00 00 01if (pPayload[2] == 0x00){ // 00 00 00 01pPayload += 4; // 例如:共10個,挪動4個后,還剩6個// 保證 我們的長度是和上的數(shù)據(jù)對應(yīng),也要是6個,所以-= 4payload -= 4;}else if(pPayload[2] == 0x01){ // 00 00 01pPayload +=3; // 例如:共10個,挪動3個后,還剩7個// 保證 我們的長度是和上的數(shù)據(jù)對應(yīng),也要是7個,所以-= 3payload -= 3;}// 根據(jù)協(xié)議設(shè)置壓縮包數(shù)據(jù)長度int body_size = 5 + 4 + payload;RTMPPacket *packet = new RTMPPacket; // 開始封包RTMPPacketRTMPPacket_Alloc(packet, body_size); // 堆區(qū)實(shí)例化 RTMPPacket// 區(qū)分關(guān)鍵幀 和 非關(guān)鍵幀packet->m_body[0] = 0x27; // 普通幀 非關(guān)鍵幀if(type == NAL_SLICE_IDR){packet->m_body[0] = 0x17; // 關(guān)鍵幀}packet->m_body[1] = 0x01; // 重點(diǎn)是此字節(jié) 如果是1 幀類型(關(guān)鍵幀或非關(guān)鍵幀), 如果是0一定是 sps ppspacket->m_body[2] = 0x00;packet->m_body[3] = 0x00;packet->m_body[4] = 0x00;// 四個字節(jié)表達(dá)一個長度,需要位移// 用四個字節(jié)來表達(dá) payload幀數(shù)據(jù)的長度,所以就需要位運(yùn)算//(位運(yùn)算:https://blog.csdn.net/qq_31622345/article/details/98070787)// https://www.cnblogs.com/zhu520/p/8143688.htmlpacket->m_body[5] = (payload >> 24) & 0xFF;packet->m_body[6] = (payload >> 16) & 0xFF;packet->m_body[7] = (payload >> 8) & 0xFF;packet->m_body[8] = payload & 0xFF;memcpy(&packet->m_body[9], pPayload, payload); // 拷貝H264的裸數(shù)據(jù)packet->m_packetType = RTMP_PACKET_TYPE_VIDEO; // 包類型,是視頻類型packet->m_nBodySize = body_size; // 設(shè)置好 關(guān)鍵幀 或 普通幀 的總大小packet->m_nChannel = 10; // 通道ID,隨便寫一個,注意:不要寫的和rtmp.c(里面的m_nChannel有沖突 4301行)packet->m_nTimeStamp = -1; // 幀數(shù)據(jù)有時間戳packet->m_hasAbsTimestamp = 0; // 時間戳絕對或相對 用不到,不需要packet->m_headerType = RTMP_PACKET_SIZE_LARGE ; // 包的類型:若是關(guān)鍵幀的話,數(shù)據(jù)量比較大,所以設(shè)置大包// 把最終的 幀類型 RTMPPacket 存入隊(duì)列videoCallback(packet);
}

當(dāng)壓縮數(shù)據(jù)加入隊(duì)列后,開啟直播創(chuàng)建的子線程將會獲取隊(duì)列的壓縮數(shù)據(jù),發(fā)送到RTMP服務(wù)器。

源碼:

NdkPush: 通過RTMP實(shí)現(xiàn)推流,直播客戶端。

視頻推流完成,下一節(jié)開始音頻推流工作。。。

http://m.aloenet.com.cn/news/33018.html

相關(guān)文章:

  • 做軟件的公司網(wǎng)站有哪些百度站長工具
  • 江蘇省工程建設(shè)信息網(wǎng)連云港seo優(yōu)化公司
  • 做網(wǎng)站改變圖片位置百度一下你就知道官網(wǎng)網(wǎng)址
  • frontpage做的網(wǎng)站好不好關(guān)鍵詞在線聽免費(fèi)
  • 空間租用網(wǎng)站模板情感營銷的十大案例
  • 個人交互式網(wǎng)站備案鄭州百度推廣公司電話
  • c語言 做網(wǎng)站瀏覽器網(wǎng)站大全
  • 深圳網(wǎng)站建設(shè)微信開發(fā)長沙做搜索引擎的公司
  • 廣州做網(wǎng)站技術(shù)seo公司培訓(xùn)課程
  • 科技創(chuàng)新網(wǎng)站建設(shè)策劃書溫州seo網(wǎng)站建設(shè)
  • 做網(wǎng)站用服務(wù)器軟文發(fā)布平臺媒體
  • 做網(wǎng)站到底需要什么it培訓(xùn)班出來現(xiàn)狀
  • 綠色環(huán)保企業(yè)網(wǎng)站模板鄭州seo公司
  • wordpress私人建站主題百度極速版客服人工在線咨詢
  • 網(wǎng)站策劃模版百度廣告銷售
  • 提高網(wǎng)站速度如何建立自己的網(wǎng)頁
  • 網(wǎng)站空間不支持php5.4關(guān)鍵詞查詢網(wǎng)站的工具
  • 網(wǎng)站建設(shè)seo運(yùn)營規(guī)劃優(yōu)就業(yè)seo課程學(xué)多久
  • 可以做網(wǎng)站的行業(yè)手機(jī)網(wǎng)站排名優(yōu)化
  • 做網(wǎng)站的企劃書谷歌關(guān)鍵詞推廣怎么做
  • 建立自己的平臺網(wǎng)站嗎哪家公司做推廣優(yōu)化好
  • 國內(nèi)做任務(wù)得數(shù)字貨幣的網(wǎng)站關(guān)鍵詞歌詞表達(dá)的意思
  • 網(wǎng)頁設(shè)計與網(wǎng)站建設(shè)實(shí)戰(zhàn)大全競價賬戶托管哪家好
  • 網(wǎng)站客服模板免費(fèi)二級域名注冊申請
  • 學(xué)校網(wǎng)站建設(shè)介紹騰訊朋友圈廣告怎么投放
  • 網(wǎng)站建設(shè) 開發(fā)的團(tuán)隊(duì)需要幾個人網(wǎng)絡(luò)營銷的方式有幾種
  • 南橋做網(wǎng)站百度問答首頁
  • 深圳龍崗做網(wǎng)站的廈門網(wǎng)
  • 淄博建網(wǎng)站哪家好百度搜索排名查詢
  • 做電影解析網(wǎng)站網(wǎng)站推廣100種方法