T31开发笔记:librtmp拉流测试
若该文为原创文章,转载请注明原文出处。
T31使用librtmp拉流并保存成FLV文件或H264和AAC文件。
librtmp编译在前面有教程,自行编译。
实现的目的是想要获取获取rtmp的AAC流并播放,实时双向对讲功能。
一、硬件和开发环境
1、硬件:T31X+SC5235
2、开发环境: ubuntu16.04-64bit
3、编译器:mips-gcc540-glibc222-32bit-r3.3.0.tar.gz
注:板子和和WIFI模块是某淘上淘的,使用的是RTL8188,使用的是USB接口,uboot和内核是自己裁剪移植的,内核默认自带WIFI驱动,所以不用移植可以直接使用。
二、使用librtmp拉流流程
- 初使化RTMP上下文
- 设置拉流地址
- 连接服务器
- 连接流地址
- 循环拉流,提取媒体数据,保存为文件或者交给解码模块
- 拉流完毕,释放资源
三、代码解析
编译代码需要用到的库,本人在T31上使用的是静态库,可以自行改成动态库,以减少编译文件的大小 。
编译所需的库有:librtmp.a、libssl.a、libcrypto.a、libz.a
下面这个例子演示了使用librtmp库从服务器拉流到本地保存为flv文件或是h264和aac文件,代码如下:
/*!******************************************************************************* Copyright ? 2017-2018 yifeng. All Rights Reserved.** \file main.c* \author yifeng* \version 1.0* \date 2022年3月3日* \brief rtmp测试代码**----------------------------------------------------------------------------* \attention********************************************************************************//*****************************************************************************change history: 1.date : 2022年3月3日author: yifengchange: create file*****************************************************************************/#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdbool.h>/* 环形队列头文件 */
#include "xiecc_rtmp.h"
#include "rtmp.h"typedef unsigned long ULONG;
typedef unsigned int UINT;
typedef unsigned char BYTE;
typedef char _TCHAR;uint8_t nalu_header[4] = { 0x00, 0x00, 0x00, 0x01 };/*!* \fn main* \brief 主函数* * \param [in] int argc #* \param [in] char *argv[] #* * \retval int*/
int main(int argc, char *argv[])
{ uint16_t object_type = 0;uint16_t sample_frequency_index = 0;uint8_t channels = 0;uint8_t frame_length_flag = 0;uint8_t depend_on_core_coder = 0;uint8_t extension_flag = 0;// 初使化RTMP上下文RTMP* pRTMP = RTMP_Alloc();RTMP_Init(pRTMP);// 设置拉流地址RTMP_SetupURL(pRTMP, (char*)"rtmp://192.168.0.109/live/stream");// 连接服务器pRTMP->Link.timeout = 10;pRTMP->Link.lFlags |= RTMP_LF_LIVE;bool b = RTMP_Connect(pRTMP, NULL);if (!b){printf("connect failed! \n");return -1;}// 连接流地址b = RTMP_ConnectStream(pRTMP, 0);if (!b){printf("connect stream failed! \n");return -1;}bool bSaveFlv = false; // 保存成FLV格式FILE *pFile = fopen("testrtmp.flv", "wb");FILE *h264_file_ptr = fopen("testrtmp.h264", "wb");FILE *aac_file_ptr = fopen("testrtmp.aac", "wb");while (RTMP_IsConnected(pRTMP)){if (bSaveFlv == true){char sBuf[4096] = {0};int bytes = RTMP_Read(pRTMP, sBuf, sizeof(sBuf));printf("RTMP_Read() ret:[%d] \n", bytes);if (bytes <= 0)break;fwrite(sBuf, 1, bytes, pFile);}else{RTMPPacket packet;RTMPPacket_Reset(&packet);packet.m_body = NULL;packet.m_chunk = NULL;b = RTMP_ReadPacket(pRTMP, &packet);if (b < 0)break;if (0 == b )continue;if (RTMPPacket_IsReady(&packet)){RTMP_ClientPacket(pRTMP, &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 dataprintf("Write sps data len: %d\n", sps_len);fwrite(nalu_header, sizeof(uint8_t), 4, h264_file_ptr);fwrite(packet.m_body + offset, sizeof(uint8_t), sps_len, h264_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 dataprintf("Write pps data len: %d\n", pps_len);fwrite(nalu_header, sizeof(uint8_t), 4, h264_file_ptr);fwrite(packet.m_body + offset, sizeof(uint8_t), pps_len, h264_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')printf("Write nalu data len: %d\n", data_len);fwrite(nalu_header, sizeof(uint8_t), 4, h264_file_ptr);fwrite(packet.m_body + offset, sizeof(uint8_t), data_len, h264_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) {uint8_t format = (packet.m_body[0] & 0xf0) >> 4;uint8_t samplerate = (packet.m_body[0] & 0x0c) >> 2;uint8_t sampledepth = (packet.m_body[0] & 0x02) >> 1;uint8_t 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 framesprintf("Write audio frames len: %d\n", packet.m_nBodySize - 2);fwrite(adts, sizeof(uint8_t), 7, aac_file_ptr);fwrite(packet.m_body + 2, sizeof(uint8_t), packet.m_nBodySize - 2, aac_file_ptr);}}else if (packet.m_packetType == RTMP_PACKET_TYPE_INFO) {// TODO:// ...printf("RTMP_PACKET_TYPE_INFO1\n");}else {// TODO:// ...printf("RTMP_PACKET_TYPE_INFO2\n");}}RTMPPacket_Free(&packet);}}fclose(pFile);RTMP_Close(pRTMP);RTMP_Free(pRTMP);return 0;
}
定义了bSaveFlv标记,是否保存成flv文件,true保存成flv文件,否则保存成aac和h264,
rtmp的Url需要根据自己的服务器修改。
四、测试结果
执行后,在当前目录下生成264和aac文件
把文件复制到pc端用vlc播放
使用librtmp拉流网友説会有问题,目前测试是正常,有遇到的网友麻烦告知一下。谢谢。
如有侵权,或需要完整代码,请及时联系博主。
相关文章:

T31开发笔记:librtmp拉流测试
若该文为原创文章,转载请注明原文出处。 T31使用librtmp拉流并保存成FLV文件或H264和AAC文件。 librtmp编译在前面有教程,自行编译。 实现的目的是想要获取获取rtmp的AAC流并播放,实时双向对讲功能。 一、硬件和开发环境 1、硬件࿱…...
2308C++概念化
原文 库 //概念化(需要C20) struct 可画 {void 画(小出流 &out) const {te::call([](auto const &s, auto &out)-> decltype(s.画(out)) { s.画(out); }, *this, out);} }; struct 方形 {void 画(小出流 &out) const { out << "方形"; } }…...
flutter开发实战-实现自定义按钮类似UIButton效果
flutter开发实战-实现自定义按钮类似UIButton效果 最近开发过程中需要实现一下UIButton效果的flutter按钮,这里使用的是监听手势点击事件。 一、GestureDetector GestureDetector属性定义 GestureDetector({super.key,this.child,this.onTapDown,this.onTapUp,t…...
深度优先搜索|1034, 1020, 1254
深度优先搜索|1034. 边界着色, 机器人的运动范围,529. 扫雷游戏 边界着色机器人的运动范围扫雷问题 边界着色 把这个题分段了,先找到包括 (row, col) 的连通分量,然后再去找符合条件的边界,找到以后涂上颜色就行。 c…...

都市信息供求网servlet+jsp新闻广告出售java源代码mysql
本项目为前几天收费帮学妹做的一个项目,Java EE JSP项目,在工作环境中基本使用不到,但是很多学校把这个当作编程入门的项目来做,故分享出本项目供初学者参考。 一、项目描述 都市信息供求网servletjsp 系统1权限:管理…...
kubeadm init:failed to pull image registry.k8s.io/pause:3.6
错误信息: Unfortunately, an error has occurred: timed out waiting for the condition This error is likely caused by: - The kubelet is not running - The kubelet is unhealthy due to a misconfiguration of the node in some way…...
设计模式之简单工厂模式、工厂模式、抽象工厂模式
参考: 设计模式笔记 简单工厂模式 ● 将类的创建过程交给工厂类实现,如果需要一个类对象,则直接通过工厂创建一个类。 ● 简单工厂模式不符合开闭原则 ● 适用场景:工厂类负责创建的对象比较少;客户端只知道传入工厂…...

C# 控制台彩色深度打印 工具类
文章目录 前言Nuget 环境安装代码使用打印结果 总结 前言 有时候我们想要靠打印获得程序信息,因为Dubeg模式需要一点一点断点进入进出,但是我们觉得断点运行实在是太慢了,还是直接打印后找结果会好一点。 Nuget 环境安装 想自己写的话可以看…...
Pytorch Tensor维度变换方法
1.torch.reshape()、torch.view()可以调整Tensor的shape 2.torch.unsqueeze(index)可以为Tensor增加一个维度 3.squeeze()可以删减维度 4.expand()扩展维度 5.repeat()维度重复,不常用 6.transpose(dim1, dim2)交换dim1与dim2࿰…...

微信小程序之点击文字文字自动转语音进行播放,微信小程序文字识别转语音播放
需求 一堆题目,题干需要在点击的时候进行语音朗读,不做音频上传,不然不便于维护 解决方案 点击查看微信官方文档:微信同声传译 使用流程 后台配置 mp.weixin.qq.com 设置 > 第三方设置 > 插件管理 小程序插件使用流…...
主动学习、半监督学习、它们之间的区别?
1、主动学习(Active Learning): 含义: 有的时候,有类标的数据比较稀少而没有类标的数据是相当丰富的,但是对数据进行人工标注又非常昂贵,这时候,学习算法可以主动地提出一些标注请…...
linux快速安装Rabbitmq
linux快速安装Rabbitmq 准备yum仓库 # root执行rpm --import https://github.com/rabbitmq/signing-keys/releases/download/2.0/rabbitmq-release-signing-key.ascrpm --import https://packagecloud.io/rabbitmq/erlang/gpgkeyrpm --import https://packagecloud.io/ra…...
spconv1.2.1库的编译与安装
SpConv是一个稀疏卷积库,在点云相关的深度学习算法中用的比较多。由于目前官方升级到了2.0,然而有些算法(比如审稿人要我复现的Cylinder3D)仍需要用到1.2.1版本,因此本人花了亿点点时间折腾了一下。。。 本机安装cuda…...

java+springboot+mysql企业邮件管理系统
项目介绍: 使用javaspringbootmysql开发的企业邮件管理系统,系统包含超级管理员、管理员、员工角色,功能如下: 超级管理员:管理员管理;员工管理;反馈管理;系统公告;个人…...

[CKA]考试之一个 Pod 封装多个容器
由于最新的CKA考试改版,不允许存储书签,本博客致力怎么一步步从官网把答案找到,如何修改把题做对,下面开始我们的 CKA之旅 题目为: Task 创建一个Pod,名字为kucc1,这个Pod包含4容器ÿ…...

iphone备份用什么软件?好用的苹果数据备份工具推荐!
众所周知,如果要将iPhone的数据跟电脑进行传输备份的话,我们需要用到iTunes这个pc工具。但是对于iTunes,不少人都反映这个软件比较难用,用不习惯。于是,顺应时代命运的iPhone备份同步工具就出现了。那iphone备份用什么…...

一语道破 python 迭代器和生成器
简而言之:迭代器是一个抽象化的概念,在python中表示访问数据集合中元素的一种方式;生成器也是一个抽象化的概念,在python 中,边循环边生成所需数据,是一种时间换空间的方法。从访问数据方式上来看ÿ…...

有哪些开源和非开源的项目管理工具?
开源和非开源项目管理工具各有其特点和优势。下面是一些常见的开源和非开源项目管理工具以及它们的简要介绍。 开源项目管理工具: OpenProject:OpenProject 是一个功能强大、易于使用的开源项目管理工具。它提供了项目计划、任务管理、团队协作、文档管…...

实战 01|「编写互动式界面」
前言 实践是最好的学习方式,技术也如此。 文章目录 前言一、功能需求(一)1、功能需求描述2、知识点3、布局与程序设计 二、功能需求(二)1、功能需求描述2、知识点1)LinearLayout2)RelativeLayou…...

开源社区寻找八月创作之星!你准备好了吗~
活动页面:https://openlab.cosmoplat.com/createStarCampaign-202308卡奥斯开源社区定位打造工业互联网行业顶级开源社区生态平台,为开发者、企业等用户提供代码托管、技术交流/共享、硬件认证/接入、培训认证、大赛活动等服务,目…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...

VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...

SpringTask-03.入门案例
一.入门案例 启动类: package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...
聊一聊接口测试的意义有哪些?
目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开,首…...

深度学习习题2
1.如果增加神经网络的宽度,精确度会增加到一个特定阈值后,便开始降低。造成这一现象的可能原因是什么? A、即使增加卷积核的数量,只有少部分的核会被用作预测 B、当卷积核数量增加时,神经网络的预测能力会降低 C、当卷…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...
【LeetCode】3309. 连接二进制表示可形成的最大数值(递归|回溯|位运算)
LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 题目描述解题思路Java代码 题目描述 题目链接:LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 给你一个长度为 3 的整数数组 nums。 现以某种顺序 连接…...

Windows电脑能装鸿蒙吗_Windows电脑体验鸿蒙电脑操作系统教程
鸿蒙电脑版操作系统来了,很多小伙伴想体验鸿蒙电脑版操作系统,可惜,鸿蒙系统并不支持你正在使用的传统的电脑来安装。不过可以通过可以使用华为官方提供的虚拟机,来体验大家心心念念的鸿蒙系统啦!注意:虚拟…...