最近在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