文章目录

  • 一: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;
};

实现:

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文件,可以使用播放器直接播放;
  • 但如果想获得音视频流做后续处理则不适合此种方式。

1.2 拉流解析出H264和AAC

对于需要H264和AAC流做后续处理的情况不适合使用 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 做内部相应处理;
  • 对于视频包,包括视频配置包和视频数据包。配置包中可解析出SPS和PPS,数据包可解析出H264数据,解包过程其实是推流打包的逆过程。由于H264发送时候添加了 0x00,0x00,0x00,0x01 分隔符,因此无需额外添加,但解析出的SPS和PPS前需要添加;
  • 对于音频包,包括音频配置包和音频数据包。配置包包含音频配置信息,数据包包含AAC数据。RTMP推流时可以不发送音频配置包,服务器一般会自动生成;而推流的AAC也不必包含7字节的ADTS头,如果包含服务器也会自动去除。拉流下来的AAC数据其实也是不包含ADTS的,因此需要打上。

实际测试时遇到 RTMP_ReadPacket 崩溃的情况(不同URL情况不同),版本为librtmp2.4,VS2017。不知是编译出库的问题还是源码本身bug,欢迎留言交流。

此处只是提供思路,具体优化需要根据应用需求深入研究。

reference:

librtmp获取视频流和音频流1

流媒体之RTMP——librtmp拉流测试相关推荐

  1. 开源流媒体解决方案,流媒体服务器,推拉流,直播平台,SRS,WebRTC,移动端流媒体,网络会议,优秀博客资源等分享

    开源流媒体解决方案,流媒体服务器,推拉流,直播平台,SRS,WebRTC,移动端流媒体,网络会议,优秀博客资源等分享 一.优秀的流媒体博客资源 1.1 EasyNVR:专注于安防视频互联网化的技术 1 ...

  2. ffmpeg推流+NGINX(RTMP)+VLC-QT拉流(Win7)

    1 简介 本文旨在在Windows 7系统上实现利用FFmpeg软件推流到部署的带RTMP模块的Nginx流媒体服务器上,在拉流端,基于VLC库的VLC-QT库实现拉流播放. 2 ffmpeg下载安装 ...

  3. SRS流媒体服务器——WebRTC推拉流演示

    SRS流媒体服务器--WebRTC推拉流 目录 WebRTC推拉流配置 WebRTC拉流演示 WebRTC推流演示 SRS官方WebRTC文档:https://github.com/ossrs/srs ...

  4. linux下使用FFmpeg搭建视频推流拉流测试

    1.概述 公司内部用的是rtmp推流,前端页面展示用webrtc方式拉流展示,基于srs构建的视频体系.之前不是负责视频模块的,为了更熟悉项目,自己大概有了个概念之后,先选取用ffmpeg方式构建一个 ...

  5. FFmpeg中RTSP客户端拉流测试代码

    之前在https://blog.csdn.net/fengbingchun/article/details/91355410中给出了通过LIVE555实现拉流的测试代码,这里通过FFmpeg来实现,代 ...

  6. SRS 4.0流媒体服务器开发环境搭建:包括推流、服务器配置、拉流测试

    SRS 4.0流媒体服务器入门系列 结合SRS官方Wiki以及本人对SRS的理解,推出<SRS 4.0流媒体服务器入门系列>,包括内容: SRS 4.0 开发环境搭建 SRS 4.0 配置 ...

  7. 【SRS】流媒体服务器(推流+拉流+转流)

    文章目录 前言 安装 推流 拉流 转流 前言 课程作业需要搭建一个视频流服务器,最初我采用的是HLS推流方式,发现那延迟卡的我一愣一愣的,这主要还是由于HLS需要等待切片完成.那行吧,那就换一个吧,一 ...

  8. 网页拉流rtmp服务器,rtmp服务器以及rtmp推流/拉流/转发

    1.服务器端 一.nignxhtml 二.ffmpegnode 三.srspython 四.livegogit 五.node-rtsp-rtmp-servergithub github搜一圈也不少服务 ...

  9. ffmpeg+rtmp推流/拉流(十)

    一.搭建rtmp服务器 1.下载nginx https://github.com/nginx/nginx/archive/release-1.19.6.zip2.下载nginx-rtmp-module ...

  10. 【流媒体】推流与拉流简介

    本文目录 一.概念 1.1 推流 1.2 拉流 二.示意图 三.RTMP传输协议 四.流媒体协议与格式 一.概念 话不多说,先了解概念,再看示意图更直观: 1.1 推流 推流:将直播的内容推送至服务器 ...

最新文章

  1. oracle11g到底是什么6,Oracle11g六个重要进程
  2. 浅谈软件性能测试中关键指标的监控与分析(转)
  3. 2021暑假实习-SSM超市积分管理系统-day09笔记
  4. MySQL的varchar定义长度到底是字节还是字符
  5. 误删mysql数据库密码后,如何恢复密码
  6. python依赖注入_如何做依赖注入python方式?
  7. bat执行java程序的脚本解析
  8. CentOS7.3下部署Rsyslog+LogAnalyzer+MySQL中央日志服务器
  9. 解决最近github网页无法打开问题
  10. 资本运作/自我投资--哪项需要优先?
  11. 使用MapWinGis ActiveX控件在图层上画点
  12. webstorm 下载安装及破解
  13. 有利于排名的网页标题和描述创作
  14. 嵩天老师python爬虫笔记整理week3
  15. 汉堡大学计算机科学,汉堡大学 - 录取条件,专业,排名,学费「环俄留学」
  16. linux申请端口,linux申请端口申请书
  17. Mybatis-plus 代码生成器(新)
  18. ESP8266-Arduino编程实例-APDS-9930环境光和趋近感器驱动
  19. 215. 数组中的第 K个最大元素
  20. 阿里自研实时计算平台支撑双十一

热门文章

  1. 实现财务自由 之 不可不知的常用财务网站或应用软件
  2. 电脑管家版WiFi共享精灵简介
  3. Flexsim——初学AGV必看的知识点(如何实现AGV与控制点连接)
  4. cgi一键还原 linux分区,用一键恢复CGI工具备份还原分区和硬盘使用图文教程
  5. Linux 命令行快捷键
  6. 慕课PDF下载扩展--再也不用担心慕课不给PDF了
  7. ap音频测试仪软件,AP APX-525音频分析仪
  8. SVN可视化管理iF.SVNAdmin+LDAP认证
  9. Deep Glow mac(AE高级辉光特效插件)支持AE2022
  10. MTK6763平台手机 在国外无法成功注册VOLTE。求解