最近在window是平臺下,做了一功能實現通過OBS採集音視頻,並通過RTMP協議將其編碼壓縮後的數據接入到自己的程序中來,因OBS軟件自帶有很強大的遊戲錄製和桌面錄製的功能,以及輸入、輸出音頻設備數據的採集並混音的功能,目前鬥魚遊戲直播也是使用的此軟件作爲錄製工具。
OBS軟件由於使用了window sdk進行音頻的採集,所以只支持window vista版本以上平臺,故XP系統用戶是使用不了此軟件的,下載地址:https://obsproject.com/download
OBS目前只支持RTMP協議進行廣播,所以需要在自己的程序中搭建一RTMP server,讓OBS接進來,然後通過研讀OBS源碼,對接收到的RTMP音視頻數據進行反封裝,最後組裝成原始的音視頻裸碼流,如H.264、MP3、AAC。
下面詳細介紹下主要流程:
1、如何搭建rtmp server
首先用戶可以下載我上傳的librtmp庫http://down.51cto.com/data/1904438;
2、接收到視頻的處理
由於OBS在編碼後在第一幀會把pps、sps、spi等解碼關鍵信息協議開始後單獨發送一次,所以在接收端必須保存該數據內容,並加在以後的每一個關鍵幀頭部,否則不能正常的解碼。以後每接受到一個關鍵幀(通過判斷其第一個字節是否是0x17),在其頭部加上該信息,然後在所有數據幀前面加上3個字節0x00 0x00 0x01 ,大致流程如上,具體請參考代碼。
3、接收到音頻的處理
目前OBS支持MP3和AAC兩種音頻編碼格式,針對MP3編碼,接收端只需要將數據包從第二個字節開始全部存儲即可通過MP3解碼器進行正常的解碼了。針對AAC編碼,目前還沒有處理。
相關代碼如下,已經去掉了一些敏感代碼,僅供參考。
//頭文件 #include <rtmp/srs_protocol_rtmp.hpp> #include <rtmp/srs_protocol_rtmp_stack.hpp> #include <libs/srs_librtmp.hpp> #include <process.h> #define InitSockets() {\ WORD version; \ WSADATA wsaData; \ \ version = MAKEWORD(1,1); \ WSAStartup(version, &wsaData); } #define CleanupSockets() WSACleanup() class RtmpServer : public CThread { public: RtmpServer( ); ~RtmpServer(); int StartRtmpServer( ); void StopRtmpServer( ); int fmle_publish(SrsRtmpServer* rtmp, SrsRequest* req); virtual int32_t terminate(); virtual void execute ( ); private: RtmpCaptureDataCallback* callback_; short m_port; SimpleSocketStream * m_pSocket; };
定義文件 #include "rtmpsrv.h" #include "rtmp_input_device.h" RtmpServer::RtmpServer( ) { callback_ = NULL; m_port = 1935; m_pSocket = NULL; } RtmpServer::~RtmpServer() { return; } int RtmpServer::fmle_publish(SrsRtmpServer* rtmp, SrsRequest* req ) { if (NULL == rtmp || NULL == req ) { return -1; } int ret = ERROR_SUCCESS; bool hasHead = false, bVFirst = true, bAFirst = true; char head[128] = {0}; char commonHead[4] = {0x00, 0x00, 0x00, 0x01}; string video_data, audio_data; int headLen = 0; uint64_t audio_last_recv_time(0), video_last_recv_time(0); while ( false == get_terminated() ) { Sleep(5); SrsMessage* msg = NULL; if ((ret = rtmp->recv_message(&msg)) != ERROR_SUCCESS) { break; } SrsAutoFree(SrsMessage, msg); if (msg->header.is_amf0_command() || msg->header.is_amf3_command()) { SrsPacket* pkt = NULL; if ((ret = rtmp->decode_message(msg, &pkt)) != ERROR_SUCCESS) { break; } SrsAutoFree(SrsPacket, pkt); if (dynamic_cast<SrsFMLEStartPacket*>(pkt)) { SrsFMLEStartPacket* unpublish = dynamic_cast<SrsFMLEStartPacket*>(pkt); if ((ret = rtmp->fmle_unpublish(10, unpublish->transaction_id)) != ERROR_SUCCESS) { break; } break; } continue; } if (msg->header.is_audio()) { audio_data.clear(); if (bAFirst) { audio_data.append((char *)msg->payload + 1, msg->size - 1); bAFirst = false; }else audio_data.append((char *)msg->payload, msg->size -1); }else if (msg->header.is_video()) { if (!hasHead) { memcpy(head, commonHead, 4); int8_t *skip = msg->payload; int8_t *skip2 = msg->payload; while(*(skip++) != 0x67); while(*(skip2++) != 0x68); int diff = skip2 - skip; if (diff <= 0) { continue; } memcpy(head + 4, skip - 1, diff - 4); //copy sps memcpy(head + 4 + diff - 4, commonHead, 4); memcpy(head + 4 + diff - 4 + 4, skip2 - 1, 4); //copy pps hasHead = true; headLen = 4 + diff - 4 + 4 + 4; }else { video_data.clear(); if (bVFirst) { video_last_recv_time = msg->header.timestamp; video_data.append(head, 128); video_data.append((const char *)msg->payload+9, msg->size - 9); bVFirst = false; } else { if (msg->header.timestamp > video_last_recv_time) { video_last_recv_time = msg->header.timestamp; } if (msg->payload[0] == 0x17) //I frame { video_data.append(head, headLen); } video_data.append(commonHead + 1, 3); video_data.append((const char *)msg->payload+9, msg->size - 9); } } } } return ret; } void RtmpServer::execute( ) { if (NULL == m_pSocket) { return ; } m_pSocket->listen("0.0.0.0", m_port, 10); SimpleSocketStream* server = new SimpleSocketStream; while ( false == get_terminated() ) { if (NULL == server) { SimpleSocketStream* server = new SimpleSocketStream; } if (NULL != m_pSocket && 0 == m_pSocket->accept(*server)) { AUDIO_INFO("RtmpServer thread accept obs connected!"); SrsRtmpServer* rtmp = new SrsRtmpServer(server); SrsRequest* req = new SrsRequest(); int ret = ERROR_SUCCESS; if ((ret = rtmp->handshake()) != ERROR_SUCCESS) { break; } SrsMessage* msg = NULL; SrsConnectAppPacket* pkt = NULL; if ((ret = srs_rtmp_expect_message<SrsConnectAppPacket>(rtmp->get_protocol(), &msg, &pkt)) != ERROR_SUCCESS) { break; } if ((ret = rtmp->response_connect_app(req, 0)) != ERROR_SUCCESS) { break; } while ( false == get_terminated() ) { SrsRtmpConnType type; if ((ret = rtmp->identify_client(10, type, req->stream, req->duration)) != ERROR_SUCCESS){ terminate(); // RtmpServer thread peer shutdown break; } assert(type == SrsRtmpConnFMLEPublish); req->strip(); rtmp->start_fmle_publish(10); int ret = fmle_publish( rtmp, req ); if ((ret != ERROR_CONTROL_REPUBLISH) && (ret != ERROR_CONTROL_RTMP_CLOSE)) //OBS主動停止串流 { break; } } if (NULL != rtmp) { delete rtmp; rtmp = NULL; } if(NULL != req) { delete req; req = NULL; } if (NULL != server) { server->close_socket(); } }else { Sleep(5); } } if (NULL != server) { delete server; server = NULL; } } int RtmpServer::StartRtmpServer( ) { InitSockets(); m_port = 1935; if (NULL != m_pSocket) { m_pSocket->close_socket(); delete m_pSocket; m_pSocket = NULL; } m_pSocket = new SimpleSocketStream; m_pSocket->create_socket(); return start(); } void RtmpServer::StopRtmpServer( ) { terminate(); if (NULL != m_pSocket) { m_pSocket->close_socket(); delete m_pSocket; m_pSocket = NULL; } CleanupSockets(); }; int32_t RtmpServer::terminate() { terminated_ = true; if(thr_handle_ != NULL) { if(WAIT_TIMEOUT == WaitForSingleObject(thr_handle_, 100)) { TerminateThread(thr_handle_, -1); } thr_handle_ = NULL; } return 0; } #endif
最後在OBS廣播設定 伺服器那裏填入rtmp://ip:port