流媒体之RTMP——librtmp拉流测试
生活随笔
收集整理的這篇文章主要介紹了
流媒体之RTMP——librtmp拉流测试
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
文章目錄
- 一:LibRTMP拉流
- 1.1 拉流保存成FLV
- 1.2 拉流解析出H264和AAC
作者:一步(Reser)
日期:2019.10.11
一:LibRTMP拉流
1.1 拉流保存成FLV
常見的使用方式是直接拉流成 FLV 文件:
/** * @brief: * Test librtmp of pulling streams * * Frames from server --> puller --> local file(.flv or .h264 or .aac) */ class CTestLibRTMPPuller { public:CTestLibRTMPPuller();virtual ~CTestLibRTMPPuller();bool create(const std::string &file);void destroy();bool connect(const std::string &url, uint32_t timeout_secs);void disconnect();static void thread_proc(void *param){CTestLibRTMPPuller *this_ptr = (CTestLibRTMPPuller *)param;if (NULL != this_ptr)this_ptr->thread_proc_internal();}void thread_proc_internal();protected:bool _init_sockets();void _cleanup_sockets();protected:RTMP *_rtmp_ptr;bool _running;std::thread *_thread_ptr;uint32_t _buffer_size;uint8_t *_buffer_ptr;FILE *_file_ptr; };實現(xiàn):
CTestLibRTMPPuller::CTestLibRTMPPuller() {_rtmp_ptr = NULL;_running = false;_thread_ptr = NULL;_buffer_size = 0;_buffer_ptr = NULL;_file_ptr = NULL; }CTestLibRTMPPuller::~CTestLibRTMPPuller() { }bool CTestLibRTMPPuller::create(const std::string &file) {bool success = false;do {if (file.empty())break;// Init socketif (!_init_sockets())break;// Librtmp init_rtmp_ptr = RTMP_Alloc();if (NULL == _rtmp_ptr)break;RTMP_Init(_rtmp_ptr);// Recv buffers_buffer_size = 2 * 1024 * 1024; // 2MB_buffer_ptr = new uint8_t[_buffer_size];if (NULL == _buffer_ptr)break;_file_ptr = fopen(file.c_str(), "wb+");if (NULL == _file_ptr)break;success = true;} while (false);if (!success) {destroy();}return success; }void CTestLibRTMPPuller::destroy() {if (NULL != _rtmp_ptr) {RTMP_Free(_rtmp_ptr);_rtmp_ptr = NULL;}if (NULL != _buffer_ptr) {delete[] _buffer_ptr;_buffer_ptr = NULL;}_buffer_size = 0;if (NULL != _file_ptr) {fclose(_file_ptr);_file_ptr = NULL;}_cleanup_sockets(); }bool CTestLibRTMPPuller::connect(const std::string &url, uint32_t timeout_secs) {bool success = false;do {// Parse rtmp url_rtmp_ptr->Link.timeout = timeout_secs;_rtmp_ptr->Link.lFlags |= RTMP_LF_LIVE;if (RTMP_SetupURL(_rtmp_ptr, (char *)url.c_str()) < 0)break;// Set recv buffersRTMP_SetBufferMS(_rtmp_ptr, 2 * 1024 * 1024); // 2MB// Socket connection// Handshakes and connect commandif (RTMP_Connect(_rtmp_ptr, NULL) < 0)break;// Setup stream and stream settingsif (RTMP_ConnectStream(_rtmp_ptr, 0) < 0)break;// Recving thread_running = true;_thread_ptr = new std::thread(thread_proc, this);if (NULL == _thread_ptr)break;success = true;} while (false);if (!success) {disconnect();}return success; }void CTestLibRTMPPuller::disconnect() {_running = false;if (NULL != _thread_ptr) {_thread_ptr->join();delete _thread_ptr;_thread_ptr = NULL;}if (NULL != _rtmp_ptr) {RTMP_Close(_rtmp_ptr);} }void CTestLibRTMPPuller::thread_proc_internal() {uint64_t frame_count = 0;RTMPPacket packet = { 0 };while (_running){// FLV fileint ret = RTMP_Read(_rtmp_ptr, (char *)_buffer_ptr, _buffer_size);if (ret < 0)break;if (ret == 0) // Timeout for recvcontinue;fwrite(_buffer_ptr, sizeof(uint8_t), ret, _file_ptr);frame_count++;if (frame_count % 100 == 0) {printf("read frames=%lld\n", frame_count);}} }bool CTestLibRTMPPuller::_init_sockets() {WORD version;WSADATA wsaData;version = MAKEWORD(2, 2);return (0 == WSAStartup(version, &wsaData)); }void CTestLibRTMPPuller::_cleanup_sockets() {WSACleanup(); }- RTMP_Read 直接將拉下來的流打包成FLV文件,可以使用播放器直接播放;
- 但如果想獲得音視頻流做后續(xù)處理則不適合此種方式。
1.2 拉流解析出H264和AAC
對于需要H264和AAC流做后續(xù)處理的情況不適合使用 RTMP_Read ,可以使用 RTMP_ReadPacket 方式:
// Parse rtmp stream to h264 and aac uint8_t nalu_header[4] = { 0x00, 0x00, 0x00, 0x01 };int ret = RTMP_ReadPacket(_rtmp_ptr, &packet); if (ret < 0)break; if (0 == ret)continue; if (RTMPPacket_IsReady(&packet)) {// Process packet, eg: set chunk size, set bw, ...RTMP_ClientPacket(_rtmp_ptr, &packet);if (packet.m_packetType == RTMP_PACKET_TYPE_VIDEO) {bool keyframe = 0x17 == packet.m_body[0] ? true : false;bool sequence = 0x00 == packet.m_body[1];printf("keyframe=%s, sequence=%s\n", keyframe ? "true" : "false", sequence ? "true" : "false");// SPS/PPS sequenceif (sequence) {uint32_t offset = 10;uint32_t sps_num = packet.m_body[offset++] & 0x1f;for (int i = 0; i < sps_num; i++) {uint8_t ch0 = packet.m_body[offset];uint8_t ch1 = packet.m_body[offset + 1];uint32_t sps_len = ((ch0 << 8) | ch1);offset += 2;// Write sps datafwrite(nalu_header, sizeof(uint8_t), 4, _file_ptr);fwrite(packet.m_body + offset, sizeof(uint8_t), sps_len, _file_ptr);offset += sps_len;}uint32_t pps_num = packet.m_body[offset++] & 0x1f;for (int i = 0; i < pps_num; i++) {uint8_t ch0 = packet.m_body[offset];uint8_t ch1 = packet.m_body[offset + 1];uint32_t pps_len = ((ch0 << 8) | ch1);offset += 2;// Write pps datafwrite(nalu_header, sizeof(uint8_t), 4, _file_ptr);fwrite(packet.m_body + offset, sizeof(uint8_t), pps_len, _file_ptr);offset += pps_len;}}// Nalu frameselse {uint32_t offset = 5;uint8_t ch0 = packet.m_body[offset];uint8_t ch1 = packet.m_body[offset + 1];uint8_t ch2 = packet.m_body[offset + 2];uint8_t ch3 = packet.m_body[offset + 3];uint32_t data_len = ((ch0 << 24) | (ch1 << 16) | (ch2 << 8) | ch3);offset += 4;// Write nalu data(already started with '0x00,0x00,0x00,0x01')//fwrite(nalu_header, sizeof(uint8_t), 4, _file_ptr);fwrite(packet.m_body + offset, sizeof(uint8_t), data_len, _file_ptr);offset += data_len;}}else if (packet.m_packetType == RTMP_PACKET_TYPE_AUDIO) {bool sequence = 0x00 == packet.m_body[1];printf("sequence=%s\n", sequence ? "true" : "false");// AAC sequenceif (sequence) {format = (packet.m_body[0] & 0xf0) >> 4;samplerate = (packet.m_body[0] & 0x0c) >> 2;sampledepth = (packet.m_body[0] & 0x02) >> 1;type = packet.m_body[0] & 0x01;// sequence = packet.m_body[1];// AAC(AudioSpecificConfig)if (format == 10) {uint8_t ch0 = packet.m_body[2];uint8_t ch1 = packet.m_body[3];uint16_t config = ((ch0 << 8) | ch1);object_type = (config & 0xF800) >> 11;sample_frequency_index = (config & 0x0780) >> 7;channels = (config & 0x78) >> 3;frame_length_flag = (config & 0x04) >> 2;depend_on_core_coder = (config & 0x02) >> 1;extension_flag = config & 0x01;}// Speex(Fix data here, so no need to parse...)else if (format == 11) {// 16 KHz, mono, 16bit/sampletype = 0;sampledepth = 1;samplerate = 4;}}// Audio frameselse {// ADTS(7 bytes) + AAC datauint32_t data_len = packet.m_nBodySize - 2 + 7;uint8_t adts[7];adts[0] = 0xff;adts[1] = 0xf1;adts[2] = ((object_type - 1) << 6) | (sample_frequency_index << 2) | (channels >> 2);adts[3] = ((channels & 3) << 6) + (data_len >> 11);adts[4] = (data_len & 0x7FF) >> 3;adts[5] = ((data_len & 7) << 5) + 0x1F;adts[6] = 0xfc;// Write audio framesfwrite(adts, sizeof(uint8_t), 7, fp);fwrite(packet.m_body + 2, sizeof(uint8_t), packet.m_nBodySize - 2, fp);}}else if (packet.m_packetType == RTMP_PACKET_TYPE_INFO) {// TODO:// ...}else {// TODO:// ...} }- RTMP_ReadPacket 會自動組包,組包完成可以使用 RTMPPacket_IsReady 判斷;
- 對于音視頻外的包要使用 RTMP_ClientPacket 做內(nèi)部相應(yīng)處理;
- 對于視頻包,包括視頻配置包和視頻數(shù)據(jù)包。配置包中可解析出SPS和PPS,數(shù)據(jù)包可解析出H264數(shù)據(jù),解包過程其實是推流打包的逆過程。由于H264發(fā)送時候添加了 0x00,0x00,0x00,0x01 分隔符,因此無需額外添加,但解析出的SPS和PPS前需要添加;
- 對于音頻包,包括音頻配置包和音頻數(shù)據(jù)包。配置包包含音頻配置信息,數(shù)據(jù)包包含AAC數(shù)據(jù)。RTMP推流時可以不發(fā)送音頻配置包,服務(wù)器一般會自動生成;而推流的AAC也不必包含7字節(jié)的ADTS頭,如果包含服務(wù)器也會自動去除。拉流下來的AAC數(shù)據(jù)其實也是不包含ADTS的,因此需要打上。
實際測試時遇到 RTMP_ReadPacket 崩潰的情況(不同URL情況不同),版本為librtmp2.4,VS2017。不知是編譯出庫的問題還是源碼本身bug,歡迎留言交流。
此處只是提供思路,具體優(yōu)化需要根據(jù)應(yīng)用需求深入研究。
reference:
librtmp獲取視頻流和音頻流1
總結(jié)
以上是生活随笔為你收集整理的流媒体之RTMP——librtmp拉流测试的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python去噪函数_Python |
- 下一篇: 2018HN省队集训